Overview

JAMES is a web service for creating and interpreting charts of child growth and development. The current version

  1. provides access to high-quality growth charts used by the Dutch youth health care;
  2. interchanges data coded according to the Basisdataset JGZ;
  3. screens for abnormal height, weight and head circumference;
  4. converts developmental data into the D-score;
  5. predicts future growth and development.

JAMES is a RESTful API that runs on a remote host. The following sections illustrate how a client can make requests to JAMES using various client languages. In principle, any HTTP client will work with JAMES. The document highlights some applications of the service and provides pointers to relevant background information.

The service aids in monitoring and evaluating childhood growth. JAMES is created and maintained by the Netherlands Organisation for Applied Scientific Research TNO. Please contact Stef van Buuren <stef.vanbuuren at tno.nl> for further information.

Primary JAMES user functionality

Verb API Endpoint stem Description Maps to james function
POST /data/upload/{dfm} Upload child data upload_data()
POST /charts/draw/{ffm} Draw child data on growth chart draw_chart()
POST /charts/list/{dfm} List available growth charts list_charts()
POST /charts/validate/{dfm} Validate a chart code validate_chartcode()
POST /screeners/apply/{dfm} Apply growth screeners to child data apply_screeners()
POST /screeners/list/{dfm} List available growth screeners list_screeners()
POST /site/request/{dfm} Request personalised site request_site()
POST /blend/request/{sfm} Obtain a blend from multiple end points request_blend()
POST /version/{dfm} Obtain version information version()
GET /{session}/{info} Extract session details
GET /{2}/{1}/man Consult R help help({1}_{2})

The table lists the defined API end points and the mapping to each end point to the corresponding R function.

The current OpenAPI definition of JAMES is at https://app.swaggerhub.com/apis-docs/stefvanbuuren/james (version 1.2.0, April 2022). Note that this definition may evolve over time.

Output formats

JAMES is built on top of the OpenCPU API, a powerful and flexible way for online deployment of R functionality. Although it is possible to use JAMES without knowledge of OpenCPU, it is useful to browse the OpenCPU features.

OpenCPU offers multiple output formats out of the box. JAMES supports a subset of these through the /{session}/{info} end point. Here session is a special session code generated by OpenCPU that identifies the server location containing the results of a request. The output format info is one of the following:

{info} Description
json Function result as JSON
print Function result as formatted as print
csv Function result as comma-separated format
tab Function result as tab-delimited format
md Function result as markdown format
svglite Function result as SVG graph
warnings Warnings from the R execution
messages Messages, especially data validation info
console Console print out, useful for locating errors
stdout Standard output
info Overview of JAMES deployment
parent Parent directory of all OpenCPU session output

In addition, the user can request the function result in a particular form. JAMES distinguishes the following groups of formats.

Format group Description
dfm Data format: json, csv, tab, md, print, parent
ffm Figure format: svglite, print, parent
sfm System format: json, print, parent

In general, the user can specify the desired format by appending the format name to the URL. See https://www.opencpu.org/api.html#api-formats for examples.

Objective

This document provides a quick introduction into the main JAMES features, and how these can be assessed from R and from the command line.

Features

/version: Obtain version information

Let us first check whether JAMES is running. The following code makes a simple request to JAMES to see whether it is alive and to return the version number of the underlying james R package. We illustrate both requests in R and in bash.

R

We first need to install and load packages.

install.packages(c("remotes", "httr", "jsonlite"))
remotes::install_github("growthcharts/jamesclient")
remotes::install_github("growthcharts/bdsreader")
library(jamesclient)
library(httr)
library(jsonlite)

Let’s find out the JAMES version number. In this document, we define the server that hosts JAMES as follows.

# host <- "https://james.groeidiagrammen.nl"
# host <- "https://ijgz.eaglescience.nl/modules/james"
host <- "http://localhost"
system(paste0("echo ", host, " > .host"))

We first illustrate a method that makes two requests to the server. The following commands call the /version/json end point in the JAMES API.

r <- james_post(host = host, path = "version/json")

The function result is an object of class james_post and consists of various components.

names(r)
##  [1] "url"          "status_code"  "headers"      "all_headers"  "cookies"     
##  [6] "content"      "date"         "times"        "request"      "handle"      
## [11] "request_path" "parsed"       "warnings"     "messages"     "session"
r$url
## [1] "http://localhost/version/json"

Most of the element are documented in the response object in the httr package. For example, we could use the call httr::status_code(r) to obtain the status code. The function james_post() adds the last five elements:

  • r$request_path echoes the endpoint, here /version/json;
  • r$parsed is a parsed version of the element r$content. Here it is a list of elements like names of the package, its date, and so on. In case of an error of the server function, we find the error message here;
  • r$warnings contain any warnings thrown during execution;
  • r$messages contain any messages, e.g. data reading errors;
  • r$session (like x04f3cc7cfa964c) is a unique session code.

The jamesclient::james_post() function wraps the basis workhorse httr::POST() that does the actual server request. For illustration, we may obtain equivalent content by the POST function directly.

path <- "version/json"
url <- parse_url(host)
url <- modify_url(url, path = file.path(url$path, path), query = "auto_unbox=true")
r <- POST(url)
fromJSON(content(r, type = "text", encoding = "UTF-8"))
## $package
## [1] "james"
## 
## $packageVersion
## [1] "0.58.0"
## 
## $packageDate
## [1] "2022-04-03"
## 
## $Rversion
## [1] "4.1.2"
bash

We use the curl Linux command. If needed, on Ubuntu install curl as

sudo apt update
sudo apt -y install curl

Let’s find out the JAMES version number. We first illustrate a method that makes two requests to the server.

The following bash commands call the /version API end point

curl -sX POST $(cat .host)/version > resp

The response to the request consists of a set of URLs created on the server, each of which contains details on the response.

cat resp
## /ocpu/tmp/x01854ebd87f235/R/.val
## /ocpu/tmp/x01854ebd87f235/R/version
## /ocpu/tmp/x01854ebd87f235/stdout
## /ocpu/tmp/x01854ebd87f235/source
## /ocpu/tmp/x01854ebd87f235/console
## /ocpu/tmp/x01854ebd87f235/info
## /ocpu/tmp/x01854ebd87f235/files/DESCRIPTION

The path element following tmp/ is a unique session key. See https://www.opencpu.org/api.html for the interpretation of the OpenCPU API.

The next snippet constructs the URL of a JSON representation of the result and downloads the contents of the URL as a file value1.

curl -s $(cat .host)$(head -1 resp)/json?auto_unbox=true > value1
cat value1
## {
##   "package": "james",
##   "packageVersion": "0.58.0",
##   "packageDate": "2022-04-03",
##   "Rversion": "4.1.2"
## }

The above sequence makes two requests to the server. The following code compacts both steps into one.

curl -sX POST $(cat .host)/version/json?auto_unbox=true > value2
cat value2
## {
##   "package": "james",
##   "packageVersion": "0.58.0",
##   "packageDate": "2022-04-03",
##   "Rversion": "4.1.2"
## }

/data/upload: Upload child data

JAMES understands data that conform to the Basisdataset JGZ coded as JSON according to a JSON schema. This section explains how we create, validate and upload child data to JAMES.

R

Let us assume that we have already have child data in R stored as a data.frame or tibble. Here we work with longitudinal demo data maria from the bdsreader package.

library(bdsreader)
maria
## $child
## # A tibble: 1 × 13
##   src      id name  dob    sex     gad   smo    bw dobf  dobm   hgtm  hgtf etn  
##   <chr> <dbl> <chr> <chr>  <chr> <dbl> <dbl> <dbl> <chr> <chr> <dbl> <dbl> <chr>
## 1 1234   5678 Maria 11-10… fema…   189     1   990 04-0… 02-1…   167   190 NL   
## 
## $time
## # A tibble: 2 × 8
##   src      id    age sex       ga   hgt   wgt   hdc
##   <chr> <dbl>  <dbl> <chr>  <dbl> <dbl> <dbl> <dbl>
## 1 1234   5678 0.0849 female    27  38    1.25  27  
## 2 1234   5678 0.167  female    27  43.5  2.1   30.5

The dataset for Maria consists of a list of two elements: child and time. The child element contain child-level variables, such as sex or height of parents. Element time is a tibble with measured body data at two different ages.

The bdsreader package contains a function export_as_bds() that can convert and save the data in a JSON formatted file.

export_as_bds(maria, ids = 5678, name = "maria", indent = 2)
## Processing file: maria.json

This statement writes a file named "maria.json" to the working directory.

{
  "Format": "2.0",
  "OrganisatieCode": 1234,
  "Referentie": "Maria",
  "ClientGegevens": [
    {
      "ElementNummer": 19,
      "Waarde": "2"
    },
    {
      "ElementNummer": 20,
      "Waarde": "20181011"
    },
    {
      "ElementNummer": 82,
      "Waarde": 189
    },
    {
      "ElementNummer": 91,
      "Waarde": "1"
    },
    {
      "ElementNummer": 110,
      "Waarde": 990
    },
    {
      "ElementNummer": 238,
      "Waarde": 1670
    },
    {
      "ElementNummer": 240,
      "Waarde": 1900
    },
    {
      "GenesteElementen": [
        {
          "ElementNummer": 63,
          "Waarde": "19950704"
        },
        {
          "ElementNummer": 71
        },
        {
          "ElementNummer": 62,
          "Waarde": "01"
        }
      ]
    },
    {
      "GenesteElementen": [
        {
          "ElementNummer": 63,
          "Waarde": "19901202"
        },
        {
          "ElementNummer": 71
        },
        {
          "ElementNummer": 62,
          "Waarde": "02"
        }
      ]
    }
  ],
  "ContactMomenten": [
    {
      "Tijdstip": "20181111",
      "Elementen": [
        {
          "ElementNummer": 235,
          "Waarde": 380
        },
        {
          "ElementNummer": 245,
          "Waarde": 1250
        },
        {
          "ElementNummer": 252,
          "Waarde": 270
        }
      ]
    },
    {
      "Tijdstip": "20181211",
      "Elementen": [
        {
          "ElementNummer": 235,
          "Waarde": 435
        },
        {
          "ElementNummer": 245,
          "Waarde": 2100
        },
        {
          "ElementNummer": 252,
          "Waarde": 305
        }
      ]
    }
  ]
}

There are now four ways to upload the data to JAMES:

  1. Upload the file "maria.json";
  2. Convert to a string and upload;
  3. Convert to a JSON object and upload;
  4. Read the JSON from a URL.

The james_post() function can call the /upload/json API end point and handles these cases as follows:

# upload as file
fn <- "maria.json"
r1 <- james_post(host = host, path = "data/upload/json", txt = fn)
status_code(r1)
## [1] 201
# upload as string
js <- read_json_js(fn)
r2 <- james_post(host = host, path = "data/upload/json", txt = js)
status_code(r2)
## [1] 201
# upload as JSON object
jo <- read_json_jo(fn)
r3 <- james_post(host = host, path = "data/upload/json", txt = jo)
status_code(r3)
## [1] 201
# upload as URL
url <- file.path(host, "ocpu/library/bdsreader/examples/maria.json")
r4 <- james_post(host = host, path = "data/upload/json", txt = url)
status_code(r4)
## [1] 201

If the status is 201, the data are uploaded to JAMES and processed. For example, the processed data after file upload is available as an R data frame under element r1$parsed.

r1$parsed
## $psn
##   id  name        dob       dobf       dobm src    sex gad ga smo  bw hgtm hgtf
## 1 -1 Maria 2018-10-11 1995-07-04 1990-12-02   0 female 189 27   1 990  167  190
##   agem etn
## 1   27  NL
## 
## $xyz
##       age xname yname zname                  zref       x     y      z
## 1  0.0849   age   hgt hgt_z nl_2012_hgt_female_27  0.0849 38.00 -0.158
## 2  0.1670   age   hgt hgt_z nl_2012_hgt_female_27  0.1670 43.50  0.047
## 3  0.0849   age   wgt wgt_z nl_2012_wgt_female_27  0.0849  1.25 -0.203
## 4  0.1670   age   wgt wgt_z nl_2012_wgt_female_27  0.1670  2.10  0.015
## 5  0.0849   age   hdc hdc_z nl_2012_hdc_female_27  0.0849 27.00 -0.709
## 6  0.1670   age   hdc hdc_z nl_2012_hdc_female_27  0.1670 30.50 -0.913
## 7  0.0849   age   bmi bmi_z nl_1997_bmi_female_nl  0.0849  8.66 -5.719
## 8  0.1670   age   bmi bmi_z nl_1997_bmi_female_nl  0.1670 11.10 -3.767
## 9  0.0849   hgt   wfh wfh_z   nl_2012_wfh_female_ 38.0000  1.25 -0.001
## 10 0.1670   hgt   wfh wfh_z   nl_2012_wfh_female_ 43.5000  2.10  0.326
## 11 0.0000   age   wgt wgt_z nl_2012_wgt_female_27  0.0000  0.99  0.190

The resources produced by JAMES results will remain available for two hours under the session key. The session key is your entrance to the resource and can be retrieved from the james_post class from the session element. For example, if we want to see the result of the last session in markdown:

(session <- r1$session)
## [1] "x062dba398f3bb1"
resp <- james_get(host = host, path = file.path(session, "md"))
cat(resp$parsed, "\n")
## 
## 
##   * **psn**:
## 
##     ------------------------------------------------------------------------------
##      id   name       dob          dobf         dobm      src   dnr    sex     gad
##     ---- ------- ------------ ------------ ------------ ----- ----- -------- -----
##      -1   Maria   2018-10-11   1995-07-04   1990-12-02    0    NA    female   189
##     ------------------------------------------------------------------------------
## 
##     Table: Table continues below
## 
## 
##     -------------------------------------------
##      ga   smo   bw    hgtm   hgtf   agem   etn
##     ---- ----- ----- ------ ------ ------ -----
##      27    1    990   167    190     27    NL
##     -------------------------------------------
## 
##   * **xyz**:
## 
##     ----------------------------------------------------------------------------------
##       age     xname   yname   zname           zref              x        y       z
##     -------- ------- ------- ------- ----------------------- -------- ------- --------
##      0.0849    age     hgt    hgt_z   nl_2012_hgt_female_27   0.0849    38     -0.158
## 
##      0.167     age     hgt    hgt_z   nl_2012_hgt_female_27   0.167    43.5    0.047
## 
##      0.0849    age     wgt    wgt_z   nl_2012_wgt_female_27   0.0849   1.25    -0.203
## 
##      0.167     age     wgt    wgt_z   nl_2012_wgt_female_27   0.167     2.1    0.015
## 
##      0.0849    age     hdc    hdc_z   nl_2012_hdc_female_27   0.0849    27     -0.709
## 
##      0.167     age     hdc    hdc_z   nl_2012_hdc_female_27   0.167    30.5    -0.913
## 
##      0.0849    age     bmi    bmi_z   nl_1997_bmi_female_nl   0.0849   8.657   -5.719
## 
##      0.167     age     bmi    bmi_z   nl_1997_bmi_female_nl   0.167    11.1    -3.767
## 
##      0.0849    hgt     wfh    wfh_z    nl_2012_wfh_female_      38     1.25    -0.001
## 
##      0.167     hgt     wfh    wfh_z    nl_2012_wfh_female_     43.5     2.1    0.326
## 
##        0       age     wgt    wgt_z   nl_2012_wgt_female_27     0      0.99     0.19
##     ----------------------------------------------------------------------------------
## 
## 
## <!-- end of list -->
## 
## 
## 
bash

We start from child data in the file maria.json that we wish to process with JAMES. For testing purposes, you may change the values, but keep the general structure intact. The following curl commands uploads the file and processes the data.

curl -sF 'txt=@maria.json' -D headers $(cat .host)/data/upload/json > content
head content
## {
##   "psn": [
##     {
##       "id": -1,
##       "name": "Maria",
##       "dob": "2018-10-11",
##       "dobf": "1995-07-04",
##       "dobm": "1990-12-02",
##       "src": "0",
##       "sex": "female",

Alternatively, we may read the file into a JSON string, and upload as follows:

JS=$(jq '.' maria.json | jq -sR '.')
curl -s $(cat .host)/data/upload/json -d "txt=$JS" > content

Finally, if the data are located at a URL, use

URL=$(cat .host)/ocpu/library/bdsreader/examples/maria.json
curl -s $(cat .host)/data/upload/json -d "txt='$URL'" > content

/charts/draw: Draw child data on growth chart

R

Maria is a preterm born at 27 weeks of gestational age. We already uploaded her data. We may now plot her growth data on the A4 chart for preterms as follows:

r5 <- james_post(host = host, 
                 path = "/charts/draw/svglite", 
                 session = r1$session,
                 chartcode = "PMAAN27", selector = "chartcode",
                 query = list(height = 29.7/2.54, width = 21/2.54))
mysvg <- tempfile(pattern = "chart", fileext = rep(".svg", 3))
writeLines(r5$parsed, con = mysvg[1])
## Warning in knitr::include_graphics(mysvg[1]): It is highly recommended to
## use relative paths for images. You had absolute paths: "/var/folders/tb/
## pdznvscd1z5bqq1y8c68q9nm0000gn/T//Rtmp9b7vT1/chartf0d2217816bc.svg"