EhrScape is an open health data platform providing a rich service framework that allows you to store, query and retrieve electronic health records based on the openEHR specification. The data is available as JSON via a REST web service architecture.
The overall approach is generic - we want to obtain healthcare data by calling a REST API, and then format the returned result set in R to ready the data for more sophisticated analysis.
RESTful applications use HTTP requests to read data (e.g., make queries) and post data (create and/or update). At its most basic, calling a REST API to obtain data involves making a HTTP GET request to a server. If the call succeeds, you’ll have a document that contains the requested data. To store data using REST API, a HTTP POST request needs to be made to the server along with the newly created data.
This tutorial describes the EhrscapeR
package, showcasing the use of R for handling data from EhrScape platform using REST API:
- GET /query: returns the results of the specified AQL query,
- GET /query/csv: returns the results of the specified AQL query as CSV,
- POST /composition: creates and commits a contribution consisting of multiple composition creations, updates or deletes.
Please check the EhrScape API Explorer for a complete list of REST API.
The basis of this package are functions from the:
- httr package: functions for making HTTP requests
- jsonlite package: functions for parsing JSON formated data into data frames (R-suitable form)
An important part is parsing the obtained JSON data into R-usable form. The result set in JSON format can be complex in structure with several nested levels which requires flattening of the structure to the data frame.
The package EhrscapeR
includes functions:
get_query
: for a given AQL query returns the result set in a data frame.get_query_csv
: for a given AQL query returns the CSV formated result set in a data frame.post_composition
: stores new data records (compositions) in EhrScape.
All the examples below can be checked on EhrScape API Explorer.
Data is queried from EhrScape using the get_query
function:
query_data <- get_query(baseURL, credentials = c(user_name, password), aql_query,
full_path = FALSE)
Input parameters:
baseURL
: base url address of the REST service,credentials
: authentication pair of username and passwordaql_query
: AQL query,full_path
: logical value indicating the degree of flattening. IfFALSE
, each value (even repetitions on the same path) in the JSON is assigned a unique identifier. IfTRUE
, values are sorted in the outpt data frame according to JSON path and unique sequence within the same path (if there are several repetitions on the same JSON path).
The function connects to the server with provided credentials and AQL query. The response in JSON format is parsed to a data frame and returned as the result of the function.
Example: Query body temperature, systolic and diastolic measurements for all patients
aql_query <-
"select
t/data[at0002|History|]/events[at0003|Any event|]/data[at0001]/items[at0004|Temperature|]/value as Temperature,
bp/data[at0001|history|]/events[at0006|any event|]/data[at0003]/items[at0004|Systolic|]/value as Systolic,
bp/data[at0001|history|]/events[at0006|any event|]/data[at0003]/items[at0005|Diastolic|]/value as Diastolic
from EHR e
contains COMPOSITION c
contains (
OBSERVATION t[openEHR-EHR-OBSERVATION.body_temperature.v1] and
OBSERVATION bp[openEHR-EHR-OBSERVATION.blood_pressure.v1])
offset 0 limit 100"
baseURL <- "https://rest.ehrscape.com/rest/v1/"
user_name <- "guidemo"
password <- "gui?!demo123"
Result of the above query is:
The function begins by preparing the AQL query for construction of the HTTP request. First, the rm_white
function of the qdapRegex package removes from the input aql_query
all multiple white spaces, white spaces before commas, semicolons, colons or endmarks, white spaces before or after brackets, leading and trailing white spaces. Next, line break and carriage return symbols are replaced with white space by using the gsub
function. The edited AQL query string is then URL encoded (query_enc
) and used to construct the HTTP request URL_address
aql_query <- qdapRegex::rm_white(aql_query)
aql_query <- gsub("\r?\n|\r", " ", aql_query)
query_enc <- utils::URLencode(aql_query)
URL_address = paste(baseURL, "query?aql=", query_enc, sep = "")
## chr "https://rest.ehrscape.com/rest/v1/query?aql=select%20t/data[at0002"..
The request URL_address
is executed using the GET
function of the httr package. The server response is stored as resp
. Since EhrScape requires authentication, user credentials have to be included to the GET
function call. Additionally, the content_type_json
function is used to set the expected content type returned from the server to JSON.
resp <- httr::GET(URL_address, httr::authenticate(user_name, password), httr::content_type_json())
## List of 10
## $ url : chr
## "https://rest.ehrscape.com/rest/v1/query?aql=select%20t/data[at0002%7"..
## __truncated__
## $ status_code: int 200
## $ headers :List of 7
## ..$ date : chr "Wed, 16 Dec 2015 16:12:19 GMT"
## ..$ server : chr "Jetty(9.2.11.v20150529)"
## ..$ content-language: chr "en-US"
## ..$ content-type : chr "application/json;charset=UTF-8"
## ..$ vary : chr "Accept-Encoding"
## ..$ content-encoding: chr "gzip"
## ..$ content-length : chr "1094"
## ..- attr(*, "class")= chr [1:2] "insensitive" "list"
## $ all_headers:List of 1
## ..$ :List of 3
## .. ..$ status : int 200
## .. ..$ version: chr "HTTP/1.1"
## .. ..$ headers:List of 7
## .. .. ..$ date : chr "Wed, 16 Dec 2015 16:12:19 GMT"
## .. .. ..$ server : chr "Jetty(9.2.11.v20150529)"
## .. .. ..$ content-language: chr "en-US"
## .. .. ..$ content-type : chr "application/json;charset=UTF-8"
## .. .. ..$ vary : chr "Accept-Encoding"
## .. .. ..$ content-encoding: chr "gzip"
## .. .. ..$ content-length : chr "1094"
## .. .. ..- attr(*, "class")= chr [1:2] "insensitive" "list"
## $ cookies :'data.frame': 0 obs. of 7 variables:
## ..$ domain : logi(0)
## ..$ flag : logi(0)
## ..$ path : logi(0)
## ..$ secure : logi(0)
## ..$ expiration:Classes 'POSIXct', 'POSIXt' num(0)
## ..$ name : logi(0)
## ..$ value : logi(0)
## $ content : raw [1:14549] 7b 22 6d 65 ...
## $ date : POSIXct[1:1], format: "2015-12-16 16:12:19"
## $ times : Named num [1:6] 0 0 0.047 1.404 1.482 ...
## ..- attr(*, "names")= chr [1:6] "redirect" "namelookup" "connect"
## "pretransfer" ...
## $ request :List of 7
## ..$ method : chr "GET"
## ..$ url : chr
## "https://rest.ehrscape.com/rest/v1/query?aql=select%20t/data[at0002%7"..
## __truncated__
## ..$ headers : Named chr [1:2] "application/json, text/xml,
## application/xml, */*" "application/json"
## .. ..- attr(*, "names")= chr [1:2] "Accept" "Content-Type"
## ..$ fields : NULL
## ..$ options :List of 5
## .. ..$ useragent : chr "libcurl/7.43.0 r-curl/0.9.4 httr/1.0.0"
## .. ..$ cainfo : chr "C:/R/Rlibs/httr/cacert.pem"
## .. ..$ httpauth : num 1
## .. ..$ userpwd : chr "guidemo:gui?!demo123"
## .. ..$ customrequest: chr "GET"
## ..$ auth_token: NULL
## ..$ output : list()
## .. ..- attr(*, "class")= chr [1:2] "write_memory" "write_function"
## ..- attr(*, "class")= chr "request"
## $ handle :Class 'curl_handle' <externalptr>
## - attr(*, "class")= chr "response"
Response resp
includes data such as the HTTP request, status, headers, cookies, and also the answer to the request in JSON format. This is stored in the content field, which is extracted with the content
function to a nested list json
.
json <- httr::content(resp)
## List of 4
## $ meta :List of 1
## ..$ href: chr
## "http://rest.ehrscape.com/rest/v1/query/?aql=select%20t/data%5Bat0002"..
## __truncated__
## $ aql : chr "select t/data[at0002|History|]/events[at0003|Any
## event|]/data[at0001]/items[at0004|Temperature|]/value as Temperature,
## bp/data["| __truncated__
## $ executedAql: chr "select t/data[at0002|History|]/events[at0003|Any
## event|]/data[at0001]/items[at0004|Temperature|]/value as Temperature,
## bp/data["| __truncated__
## $ resultSet :List of 56
## ..$ :List of 3
## .. ..$ Temperature:List of 4
## .. .. ..$ @class : chr "DV_QUANTITY"
## .. .. ..$ magnitude: num 37.3
## .. .. ..$ units : chr "°C"
## .. .. ..$ precision: int 1
## .. ..$ Systolic :List of 3
## .. .. ..$ @class : chr "DV_QUANTITY"
## .. .. ..$ magnitude: num 99
## .. .. ..$ units : chr "mm[Hg]"
## .. ..$ Diastolic :List of 3
## .. .. ..$ @class : chr "DV_QUANTITY"
## .. .. ..$ magnitude: num 70
## .. .. ..$ units : chr "mm[Hg]"
## ..$ :List of 3
## .. ..$ Temperature:List of 4
## .. .. ..$ @class : chr "DV_QUANTITY"
## .. .. ..$ magnitude: num 38.4
## .. .. ..$ units : chr "°C"
## .. .. ..$ precision: int 1
## .. ..$ Systolic :List of 3
## .. .. ..$ @class : chr "DV_QUANTITY"
## .. .. ..$ magnitude: num 107
## .. .. ..$ units : chr "mm[Hg]"
## .. ..$ Diastolic :List of 3
## .. .. ..$ @class : chr "DV_QUANTITY"
## .. .. ..$ magnitude: num 58
## .. .. ..$ units : chr "mm[Hg]"
## ..$ :List of 3
## .. ..$ Temperature:List of 4
## .. .. ..$ @class : chr "DV_QUANTITY"
## .. .. ..$ magnitude: num 36.2
## .. .. ..$ units : chr "°C"
## .. .. ..$ precision: int 1
## .. ..$ Systolic :List of 3
## .. .. ..$ @class : chr "DV_QUANTITY"
## .. .. ..$ magnitude: num 101
## .. .. ..$ units : chr "mm[Hg]"
## .. ..$ Diastolic :List of 3
## .. .. ..$ @class : chr "DV_QUANTITY"
## .. .. ..$ magnitude: num 62
## .. .. ..$ units : chr "mm[Hg]"
## ..$ :List of 3
## .. ..$ Temperature:List of 4
## .. .. ..$ @class : chr "DV_QUANTITY"
## .. .. ..$ magnitude: num 36
## .. .. ..$ units : chr "°C"
## .. .. ..$ precision: int 1
## .. ..$ Systolic :List of 3
## .. .. ..$ @class : chr "DV_QUANTITY"
## .. .. ..$ magnitude: num 122
## .. .. ..$ units : chr "mm[Hg]"
## .. ..$ Diastolic :List of 3
## .. .. ..$ @class : chr "DV_QUANTITY"
## .. .. ..$ magnitude: num 83
## .. .. ..$ units : chr "mm[Hg]"
## .. [list output truncated]
Sublists contain the used AQL query and URL address as well as the result, which is extracted to the json_results
named vector.
json_results <- unlist(json[[4]])
## Named chr [1:560] "DV_QUANTITY" "37.3" "°C" "1" ...
## - attr(*, "names")= chr [1:560] "Temperature.@class"
## "Temperature.magnitude" "Temperature.units" "Temperature.precision" ...
The json_results
named vector is converted to two-column data frame where the names of the vector are in one column and the values are in the other
json_nav <- as.data.frame(json_results, stringsAsFactors = FALSE)
json_nav$path <- attr(json_results, "names")
## 'data.frame': 560 obs. of 2 variables:
## $ json_results: chr "DV_QUANTITY" "37.3" "°C" "1" ...
## $ path : chr "Temperature.@class" "Temperature.magnitude"
## "Temperature.units" "Temperature.precision" ...
The names of the unlisted JSON json_results
represent paths of the values in the nested JSON. And in the next step the unique paths of the JSON content are determined (unique_path
) and used to determine the number of different records rec_num
and their index limits (limits
) in the json_results
data_frame.
unique_path <- unique(json_nav$path)
## chr [1:10] "Temperature.@class" "Temperature.magnitude" ...
runs <- rle(json_nav$path == unique_path[1])
limits <- cumsum(runs$lengths)
## int [1:112] 1 10 11 20 21 30 31 40 41 50 ...
rec_num <- sum(runs$values)
## int 56
The number of different records rec_num
and their start and end limits limits
are then used to add a new column ind
to the json_nav
data frame to assign each line to the corresponding record number
json_nav$ind <- rep(NA, dim(json_nav)[1])
for(i in 1:rec_num) {
json_nav$ind[limits[2*i - 1] : limits[2*i]] <- i
}
## 'data.frame': 560 obs. of 3 variables:
## $ json_results: chr "DV_QUANTITY" "37.3" "°C" "1" ...
## $ path : chr "Temperature.@class" "Temperature.magnitude"
## "Temperature.units" "Temperature.precision" ...
## $ ind : int 1 1 1 1 1 1 1 1 1 1 ...
Below temperature data for the first three patients from the json_nav
is visualized (the first column indicates which rows are displayed).
json_results | path | ind | |
---|---|---|---|
1 | DV_QUANTITY | Temperature.@class | 1 |
2 | 37.3 | Temperature.magnitude | 1 |
3 | °C | Temperature.units | 1 |
4 | 1 | Temperature.precision | 1 |
11 | DV_QUANTITY | Temperature.@class | 2 |
12 | 38.4 | Temperature.magnitude | 2 |
13 | °C | Temperature.units | 2 |
14 | 1 | Temperature.precision | 2 |
21 | DV_QUANTITY | Temperature.@class | 3 |
22 | 36.2 | Temperature.magnitude | 3 |
23 | °C | Temperature.units | 3 |
24 | 1 | Temperature.precision | 3 |
In the final step, the output data frame out
is prepared according to the value of the full_path
parameter, which can be either FALSE
or TRUE
.
If full_path
is set to FALSE
, the column names of the out
data frame are equal to the values of unique_path
vector. Additionally, the ind
and seq
columns are included. This way each column represents elements on the same path. The ind
column is used to annotate each row with the corresponding record number. The seq
column is used to annotate each row with the number of repetitions in the same JSON path (to distinguish between several different elements in the same JSON path). If each JSON path contains a single element, all values in the seq
are equal to 1.
The seq
column is created using the with
function, which evaluates the json_nav
data frame to the expression defined by the ave
function. The ave
function groups the values in json_results
according to unique combinations of variables ind
and path
. The out
data frame is cast (by using function dcast
) from the molten data frame json_nav
by sorting the values in json_results
according to the match between ind
and seq
on one side and path
on the other.
json_nav$seq <- with(json_nav, stats::ave(json_results, ind, path, FUN = seq_along))
json_nav$seq <- as.numeric(json_nav$seq)
out <- reshape2::dcast(ind + seq ~ path, data = json_nav, value.var = "json_results")
## 'data.frame': 56 obs. of 12 variables:
## $ ind : int 1 2 3 4 5 6 7 8 9 10 ...
## $ seq : num 1 1 1 1 1 1 1 1 1 1 ...
## $ Diastolic.@class : chr "DV_QUANTITY" "DV_QUANTITY" "DV_QUANTITY" "DV_QUANTITY" ...
## $ Diastolic.magnitude : chr "70" "58" "62" "83" ...
## $ Diastolic.units : chr "mm[Hg]" "mm[Hg]" "mm[Hg]" "mm[Hg]" ...
## $ Systolic.@class : chr "DV_QUANTITY" "DV_QUANTITY" "DV_QUANTITY" "DV_QUANTITY" ...
## $ Systolic.magnitude : chr "99" "107" "101" "122" ...
## $ Systolic.units : chr "mm[Hg]" "mm[Hg]" "mm[Hg]" "mm[Hg]" ...
## $ Temperature.@class : chr "DV_QUANTITY" "DV_QUANTITY" "DV_QUANTITY" "DV_QUANTITY" ...
## $ Temperature.magnitude: chr "37.3" "38.4" "36.2" "36" ...
## $ Temperature.precision: chr "1" "1" "1" "1" ...
## $ Temperature.units : chr "°C" "°C" "°C" "°C" ...
The first six rows of output data frame out
presented as table
ind | seq | Diastolic.@class | Diastolic.magnitude | Diastolic.units | Systolic.@class |
---|---|---|---|---|---|
1 | 1 | DV_QUANTITY | 70 | mm[Hg] | DV_QUANTITY |
2 | 1 | DV_QUANTITY | 58 | mm[Hg] | DV_QUANTITY |
3 | 1 | DV_QUANTITY | 62 | mm[Hg] | DV_QUANTITY |
4 | 1 | DV_QUANTITY | 83 | mm[Hg] | DV_QUANTITY |
5 | 1 | DV_QUANTITY | 70 | mm[Hg] | DV_QUANTITY |
6 | 1 | DV_QUANTITY | 59 | mm[Hg] | DV_QUANTITY |
Table: Table continues below
Systolic.magnitude | Systolic.units | Temperature.@class | Temperature.magnitude |
---|---|---|---|
99 | mm[Hg] | DV_QUANTITY | 37.3 |
107 | mm[Hg] | DV_QUANTITY | 38.4 |
101 | mm[Hg] | DV_QUANTITY | 36.2 |
122 | mm[Hg] | DV_QUANTITY | 36 |
88 | mm[Hg] | DV_QUANTITY | 37.7 |
96 | mm[Hg] | DV_QUANTITY | 36.7 |
Table: Table continues below
Temperature.precision | Temperature.units |
---|---|
1 | °C |
1 | °C |
1 | °C |
1 | °C |
1 | °C |
1 | °C |
If full_path
is set to TRUE
, the number of columns of the output data frame is equal to the number of unique paths of the JSON content increased for the number of paths that repeat due to repetitions on the same JSON level (plus one for the ind
column). The names of the columns are equal to the values of unique_path
with appendend integer value of the repetition, which is used to distinguish between several different elements with the same JSON path.
For this a new temporary data frame tmp
is created, which includes the columns of the json_nav
along with the column newind
created as unique combinations of the columns ind
and path
. This is achieved by using the ddply
function, which splits the input data frame (json_nav
) according to given variables (ind
and path
), applies the selected function (transform
- transform to data frame) on the splits along with the additional column newind
and combines the results into a data frame. The out
data frame is cast (by using function dcast
) from the molten data frame tmp
by sorting the values in json_results
according to the match between ind
on one side and newind
on the other.
tmp <- plyr::ddply(json_nav, plyr::.(ind, path), transform, newind = paste(path,
seq_along(path), sep = "."))
out2 <- reshape2::dcast(tmp, ind ~ newind, value.var = "json_results")
## 'data.frame': 56 obs. of 11 variables:
## $ ind : int 1 2 3 4 5 6 7 8 9 10 ...
## $ [email protected] : chr "DV_QUANTITY" "DV_QUANTITY" "DV_QUANTITY" "DV_QUANTITY" ...
## $ Diastolic.magnitude.1 : chr "70" "58" "62" "83" ...
## $ Diastolic.units.1 : chr "mm[Hg]" "mm[Hg]" "mm[Hg]" "mm[Hg]" ...
## $ [email protected] : chr "DV_QUANTITY" "DV_QUANTITY" "DV_QUANTITY" "DV_QUANTITY" ...
## $ Systolic.magnitude.1 : chr "99" "107" "101" "122" ...
## $ Systolic.units.1 : chr "mm[Hg]" "mm[Hg]" "mm[Hg]" "mm[Hg]" ...
## $ [email protected] : chr "DV_QUANTITY" "DV_QUANTITY" "DV_QUANTITY" "DV_QUANTITY" ...
## $ Temperature.magnitude.1: chr "37.3" "38.4" "36.2" "36" ...
## $ Temperature.precision.1: chr "1" "1" "1" "1" ...
## $ Temperature.units.1 : chr "°C" "°C" "°C" "°C" ...
The first six rows of output data frame out
presented as table
ind | [email protected] | Diastolic.magnitude.1 | Diastolic.units.1 | [email protected] |
---|---|---|---|---|
1 | DV_QUANTITY | 70 | mm[Hg] | DV_QUANTITY |
2 | DV_QUANTITY | 58 | mm[Hg] | DV_QUANTITY |
3 | DV_QUANTITY | 62 | mm[Hg] | DV_QUANTITY |
4 | DV_QUANTITY | 83 | mm[Hg] | DV_QUANTITY |
5 | DV_QUANTITY | 70 | mm[Hg] | DV_QUANTITY |
6 | DV_QUANTITY | 59 | mm[Hg] | DV_QUANTITY |
Table: Table continues below
Systolic.magnitude.1 | Systolic.units.1 | [email protected] | Temperature.magnitude.1 |
---|---|---|---|
99 | mm[Hg] | DV_QUANTITY | 37.3 |
107 | mm[Hg] | DV_QUANTITY | 38.4 |
101 | mm[Hg] | DV_QUANTITY | 36.2 |
122 | mm[Hg] | DV_QUANTITY | 36 |
88 | mm[Hg] | DV_QUANTITY | 37.7 |
96 | mm[Hg] | DV_QUANTITY | 36.7 |
Table: Table continues below
Temperature.precision.1 | Temperature.units.1 |
---|---|
1 | °C |
1 | °C |
1 | °C |
1 | °C |
1 | °C |
1 | °C |
In this case the out2
notation was used just to distinguish between results for different values of the full_path
argument.
EhrScape provides data structure flattening capability using GET /query/csv
, which returns the results of the specified AQL query as CSV. This allows to get a data frame directly instead of manipulating multi-level generic JSON data structure. In EhrscapeR
this is implemented in the function get_query_csv
:
query_data_csv <- get_query_csv(baseURL, credentials = c(user_name, password), aql_query)
Input parameters:
baseURL
: base url address of the REST service,credentials
: authentication pair of username and passwordaql_query
: AQL query.
The function connects to the server with provided credentials and AQL query. The response in JSON format prepared as a CSV table is parsed to a data frame and returned as the result of the function. This will be presented in more detail on an example using the previously defined parameters baseURL
, user_name
, password
and aql_query
.
The function begins by preparing the AQL query for construction of the HTTP request. The only difference compared to the get_query
function is the URL address, which uses "query/csv?aql="
instead of "query?aql="
.
aql_query <- qdapRegex::rm_white(aql_query)
aql_query <- gsub("\r?\n|\r", " ", aql_query)
query_enc <- utils::URLencode(aql_query)
URL_address = paste(baseURL, "query/csv?aql=", query_enc, sep = "")
## chr "https://rest.ehrscape.com/rest/v1/query/csv?aql=select%20t/data[at"..
The request URL_address
is executed using the GET
function of the httr package. The server response is stored as resp
.
resp <- httr::GET(URL_address, httr::authenticate(user_name, password), httr::content_type_json())
## List of 10
## $ url : chr
## "https://rest.ehrscape.com/rest/v1/query/csv?aql=select%20t/data[at00"..
## __truncated__
## $ status_code: int 200
## $ headers :List of 5
## ..$ date : chr "Wed, 16 Dec 2015 16:12:20 GMT"
## ..$ server : chr "Jetty(9.2.11.v20150529)"
## ..$ content-language : chr "en-US"
## ..$ content-type : chr "text/csv;charset=UTF-8"
## ..$ transfer-encoding: chr "chunked"
## ..- attr(*, "class")= chr [1:2] "insensitive" "list"
## $ all_headers:List of 1
## ..$ :List of 3
## .. ..$ status : int 200
## .. ..$ version: chr "HTTP/1.1"
## .. ..$ headers:List of 5
## .. .. ..$ date : chr "Wed, 16 Dec 2015 16:12:20 GMT"
## .. .. ..$ server : chr "Jetty(9.2.11.v20150529)"
## .. .. ..$ content-language : chr "en-US"
## .. .. ..$ content-type : chr "text/csv;charset=UTF-8"
## .. .. ..$ transfer-encoding: chr "chunked"
## .. .. ..- attr(*, "class")= chr [1:2] "insensitive" "list"
## $ cookies :'data.frame': 0 obs. of 7 variables:
## ..$ domain : logi(0)
## ..$ flag : logi(0)
## ..$ path : logi(0)
## ..$ secure : logi(0)
## ..$ expiration:Classes 'POSIXct', 'POSIXt' num(0)
## ..$ name : logi(0)
## ..$ value : logi(0)
## $ content : raw [1:2252] 22 54 65 6d ...
## $ date : POSIXct[1:1], format: "2015-12-16 16:12:20"
## $ times : Named num [1:6] 0 0 0 0 0.078 0.078
## ..- attr(*, "names")= chr [1:6] "redirect" "namelookup" "connect"
## "pretransfer" ...
## $ request :List of 7
## ..$ method : chr "GET"
## ..$ url : chr
## "https://rest.ehrscape.com/rest/v1/query/csv?aql=select%20t/data[at00"..
## __truncated__
## ..$ headers : Named chr [1:2] "application/json, text/xml,
## application/xml, */*" "application/json"
## .. ..- attr(*, "names")= chr [1:2] "Accept" "Content-Type"
## ..$ fields : NULL
## ..$ options :List of 5
## .. ..$ useragent : chr "libcurl/7.43.0 r-curl/0.9.4 httr/1.0.0"
## .. ..$ cainfo : chr "C:/R/Rlibs/httr/cacert.pem"
## .. ..$ httpauth : num 1
## .. ..$ userpwd : chr "guidemo:gui?!demo123"
## .. ..$ customrequest: chr "GET"
## ..$ auth_token: NULL
## ..$ output : list()
## .. ..- attr(*, "class")= chr [1:2] "write_memory" "write_function"
## ..- attr(*, "class")= chr "request"
## $ handle :Class 'curl_handle' <externalptr>
## - attr(*, "class")= chr "response"
Response resp
includes the answer to the request in JSON format. This is extracted from the content field with the content
function to a data frame json
, which is returned as the output of the function.
json <- httr::content(resp)
## 'data.frame': 56 obs. of 3 variables:
## $ Temperature: chr "37.3 °C" "38.4 °C" "36.2 °C" "36.0 °C" ...
## $ Systolic : chr "99.0 mm[Hg]" "107.0 mm[Hg]" "101.0 mm[Hg]" "122.0
## mm[Hg]" ...
## $ Diastolic : chr "70.0 mm[Hg]" "58.0 mm[Hg]" "62.0 mm[Hg]" "83.0 mm[Hg]"
## ...
Temperature | Systolic | Diastolic |
---|---|---|
37.3 °C | 99.0 mm[Hg] | 70.0 mm[Hg] |
38.4 °C | 107.0 mm[Hg] | 58.0 mm[Hg] |
36.2 °C | 101.0 mm[Hg] | 62.0 mm[Hg] |
36.0 °C | 122.0 mm[Hg] | 83.0 mm[Hg] |
37.7 °C | 88.0 mm[Hg] | 70.0 mm[Hg] |
36.7 °C | 96.0 mm[Hg] | 59.0 mm[Hg] |
Function post_composition
enables saving data records (composition) to EhrScape. Compositions are created by using web templates. Web template can be regarded as an input schema, which declares all possible settings of the input data structures for building an openEHR composition. To build a composition sample use GET /template/{templateId}/example
, which returns an example of data values for a web template. This is used as a prototype composition and further filled with actual data.
The composition has to be prepared and provided to the function in JSON format. Additionally, the composition has to be complemented by an identifier of one of the existing templates in the EhrScape and an identifier of an actual EHR record. The composition is saved to the server using the REST method POST. The function is called as:
post_data <- post_composition(baseURL, credentials = c(user_name, password),
templateId, ehrId, format, composition)
Input parameters:
baseURL
: base url address of the REST service,credentials
: authentication pair of username and passwordtemplateId
: identifier of one of the existing templates in the EhrScape platform,ehrId
: identifier of an existing EHR recordformat
: indicator of whether the provided JSON composition is in structured ("STRUCTURED"
) or flattened ("FLAT"
) formcomposition
: the OpenEhr composition in JSON format to be saved to EhrScape platform. It can be given as character string or as a structured list.
The function connects to the server with provided credentials. The response in JSON format is parsed to a list and returned as the result of the function.
Example: Save a patient's body temperature measurement to his EHR using a flattened web template composition
composition <-
'{
"ctx/language": "en",
"ctx/territory": "US",
"ctx/composer_name": "Silvia Blake",
"ctx/time": "2015-12-03T14:55:51.868+01:00",
"ctx/id_namespace": "HOSPITAL-NS",
"ctx/id_scheme": "HOSPITAL-NS",
"ctx/health_care_facility|name": "Hospital",
"ctx/health_care_facility|id": "9091",
"vital_signs/body_temperature:0/any_event:0/temperature|magnitude": 37.94,
"vital_signs/body_temperature:0/any_event:0/temperature|unit": "°C"
}'
baseURL <- "https://rest.ehrscape.com/rest/v1/"
user_name <- "****"
password <- "****"
templateId <- "Vital Signs"
ehrId <- "6e031066-14df-46db-8320-bce31199fcbd"
format <- "FLAT"
The function begins by constructing the HTTP request from the baseURL
, templateId
, ehrId
and format
arguments. For this, the modify_url
function of the httr package is used
URL_address <- httr::modify_url(paste(baseURL, "composition", sep = ""),
query = list(templateId = templateId,
ehrId = ehrId, format = format))
## chr "https://rest.ehrscape.com/rest/v1/composition?templateId=Vital%20S"..
Construction of HTTP request is followed by preparing the composition
, which can either be given as character string or a structured list or data frame. In case it is given as a character string. All inapropriate white space, line break and carriage return symbols are cleaned using the same procedure as with the function get_query
and the cleaned character string is converted to a structured list using the fromJSON
function of the jsonlite package
if(typeof(composition) == "character"){
composition <- qdapRegex::rm_white(composition)
composition <- gsub("\r?\n|\r", " ", composition)
df <- jsonlite::fromJSON(composition)
} else {
df <- composition
}
df
## $`ctx/language`
## [1] "en"
##
## $`ctx/territory`
## [1] "US"
##
## $`ctx/composer_name`
## [1] "Silvia Blake"
##
## $`ctx/time`
## [1] "2015-12-03T14:55:51.868+01:00"
##
## $`ctx/id_namespace`
## [1] "HOSPITAL-NS"
##
## $`ctx/id_scheme`
## [1] "HOSPITAL-NS"
##
## $`ctx/health_care_facility|name`
## [1] "Hospital"
##
## $`ctx/health_care_facility|id`
## [1] "9091"
##
## $`vital_signs/body_temperature:0/any_event:0/temperature|magnitude`
## [1] 37.94
##
## $`vital_signs/body_temperature:0/any_event:0/temperature|unit`
## [1] "°C"
Once the HTTP request URL_address
and composition data df
are prepared, they can be used to save the composition to EhrScape. This is done by using the POST
function of the httr package. The required arguments include the HTTP request (URL_address
), configuration settings (authentication with user_name
and password
), body (df
containing the composition) and encoding used (the encode
argument)
resp <- httr::POST(URL_address, httr::authenticate(user_name, password), body = df,
encode = "json")
## List of 10
## $ url : chr
## "https://rest.ehrscape.com/rest/v1/composition?templateId=Vital%20Sig"..
## $ status_code: int 201
## $ headers :List of 7
## ..$ date : chr "Fri, 11 Dec 2015 10:24:18 GMT"
## ..$ server : chr "Jetty(9.2.11.v20150529)"
## ..$ content-language : chr "en-US"
## ..$ content-type : chr "application/json;charset=UTF-8"
## ..$ vary : chr "Accept-Encoding"
## ..$ content-encoding : chr "gzip"
## ..$ transfer-encoding: chr "chunked"
## ..- attr(*, "class")= chr [1:2] "insensitive" "list"
## $ all_headers:List of 1
## ..$ :List of 3
## .. ..$ status : int 201
## .. ..$ version: chr "HTTP/1.1"
## .. ..$ headers:List of 7
## .. .. ..$ date : chr "Fri, 11 Dec 2015 10:24:18 GMT"
## .. .. ..$ server : chr "Jetty(9.2.11.v20150529)"
## .. .. ..$ content-language : chr "en-US"
## .. .. ..$ content-type : chr "application/json;charset=UTF-8"
## .. .. ..$ vary : chr "Accept-Encoding"
## .. .. ..$ content-encoding : chr "gzip"
## .. .. ..$ transfer-encoding: chr "chunked"
## .. .. ..- attr(*, "class")= chr [1:2] "insensitive" "list"
## $ cookies :'data.frame': 0 obs. of 7 variables:
## ..$ domain : logi(0)
## ..$ flag : logi(0)
## ..$ path : logi(0)
## ..$ secure : logi(0)
## ..$ expiration:Classes 'POSIXct', 'POSIXt' num(0)
## ..$ name : logi(0)
## ..$ value : logi(0)
## $ content : raw [1:223] 7b 22 6d 65 ...
## $ date : POSIXct[1:1], format: "2015-12-11 10:24:18"
## $ times : Named num [1:6] 0 0 0.031 0.858 0.936 ...
## ..- attr(*, "names")= chr [1:6] "redirect" "namelookup" "connect"
## "pretransfer" ...
## $ request :List of 7
## ..$ method : chr "POST"
## ..$ url : chr
## "https://rest.ehrscape.com/rest/v1/composition?templateId=Vital%20Sig"..
## ..$ headers : Named chr [1:2] "application/json, text/xml,
## application/xml, */*" "application/json"
## .. ..- attr(*, "names")= chr [1:2] "Accept" "Content-Type"
## ..$ fields : NULL
## ..$ options :List of 8
## .. ..$ useragent : chr "libcurl/7.43.0 r-curl/0.9.4 httr/1.0.0"
## .. ..$ cainfo : chr "C:/R/Rlibs/httr/cacert.pem"
## .. ..$ post : logi TRUE
## .. ..$ postfieldsize: int 404
## .. ..$ postfields : raw [1:404] 7b 22 63 74 ...
## .. ..$ httpauth : num 1
## .. ..$ userpwd : chr "****:****"
## .. ..$ customrequest: chr "POST"
## ..$ auth_token: NULL
## ..$ output : list()
## .. ..- attr(*, "class")= chr [1:2] "write_memory" "write_function"
## ..- attr(*, "class")= chr "request"
## $ handle :Class 'curl_handle' <externalptr>
## - attr(*, "class")= chr "response"
The server response contains the details of the call along with the HTTP status. In case of successfully executed request, the response body contains a link to the newly created composition. This link is parsed from the content field of the response
resp_content <- httr::content(resp, as = "parsed")
## List of 3
## $ meta :List of 1
## ..$ href: chr
## "http://rest.ehrscape.com/rest/v1/composition/33e5b31b-5d5e-46ab-b251"..
## $ action : chr "CREATE"
## $ compositionUid: chr
## "33e5b31b-5d5e-46ab-b251-93b473c3d7a6::marand.ehrscape.com::1"
The function returns a list of the full response resp
and the parsed content field containing the link to the created composition resp_content
.