Reference Implementation

Connected Diagnostics Platform

DXAPI enables 3rd party applications to consume tests from diagnostic devices in a secure, scalable and interoperable way.

If you are a Health Applications software developer, DXAPI will allow you to provide your users with information about
tests, without having to worry about connecting to different devices using different protocols. You use a simple and modern
API and the platform will take care of the rest.

If you are a diagnostics device manufacturer, implementing DXAPI will enable your device to provide value added to
your customers, by integrating into a platform that can be extended with unlimited number of applications. Your device
reports messages to a secure and private server where your customers will define who has access to what. A number
of 3rd party application can then be granted access to a subset of those tests and provide new services to patients,
health workes, clinicians and even your own support staff.

A few examples of apps that could be created using this platform are:

Notifying patients that their tests are ready via a voice call or an SMS.

Notifying community health workers about a anonymous positive TB case on their area.

{$schema:"http://json-schema.org/draft-04/schema#",type:"object",title:"en-US",properties:{institution:{type:"object",title:"Institution",properties:{uuid:{title:"Uuid",type:"string",searchable:true},name:{title:"Name",type:"string",searchable:false}}},site:{type:"object",title:"Site",properties:{uuid:{title:"Uuid",type:"string",searchable:true},name:{title:"Name",type:"string",searchable:false},path:{title:"Path",type:"string",searchable:true}}},patient:{type:"object",title:"Patient",properties:{id:{title:"Id",type:"string",searchable:false},name:{title:"Name",type:"string",searchable:false},dob:{title:"Dob",type:"string",format:"date-time",resolution:"second",searchable:false},gender:{title:"Gender",type:"string",enum:["male","female","other"],values:{male:{name:"Male"},female:{name:"Female"},other:{name:"Other"}},searchable:true},email:{title:"Email",type:"string",searchable:false},phone:{title:"Phone",type:"string",searchable:false}}},encounter:{type:"object",title:"Encounter",properties:{id:{title:"Id",type:"string",searchable:false},uuid:{title:"Uuid",type:"string",searchable:true},patient_age:{title:"Patient Age",type:"object",class:"duration",properties:{milliseconds:{type:"integer",title:"Patient Age milliseconds"},seconds:{type:"integer",title:"Patient Age seconds"},minutes:{type:"integer",title:"Patient Age minutes"},hours:{type:"integer",title:"Patient Age hours"},days:{type:"integer",title:"Patient Age days"},months:{type:"integer",title:"Patient Age months"},years:{type:"integer",title:"Patient Age years"}},searchable:true},start_time:{title:"Start Time",type:"string",format:"date-time",resolution:"second",searchable:true},end_time:{title:"End Time",type:"string",format:"date-time",resolution:"second",searchable:true},observations:{title:"Observations",type:"string",searchable:false},diagnosis:{title:"Diagnosis",type:"array",items:{type:"object",properties:{name:{title:"Name",type:"string",searchable:true},condition:{title:"Condition",type:"string",enum:["atsc","cd4_count","ct","emb","hiv","hiv_1_m_n","hiv_1_o","hiv_2","inh","lvx","malaria_pf","malaria_pv","mtb","ng","pas","pza","rif","stm","tch"],searchable:true},result:{title:"Result",type:"string",enum:["positive","negative","indeterminate"],values:{positive:{name:"Positive",kind:"positive"},negative:{name:"Negative",kind:"negative"},indeterminate:{name:"Indeterminate"}},searchable:true},quantitative_result:{title:"Quantitative Result",type:"string",searchable:true}}},searchable:true}}}}}

Added fields for the reference implementation

sample.uid - In order to disambiguate the sample Id, manifests should ensure that the value in this field uniquely identifies each sample institution-wise. Two tests are considered to have the same sample if and only if they belong to the same institution and have the same sample.uid value. A possible sample uid for an test is a composition of the original sample.id, the month of the test’s start_time (to handle repetitions over time) and the identifier of the laboratory where the test was run (to handle repetitions across labs). This sample.uid is currently provided by the manifest.

location.parents - The natural earth geo id of all the the device location’s parents.

CSV

Returns a list of Tests. All the filters can be specified in the POST request body or in the GET query string.

Format

The query can be answered in CSV, XML, and JSON formats.

The CSV format doesn’t include the total count.

The default response format is JSON.

Filters

Filter parameters allow querying a subset of the available tests.

The data returned will be sorted by default by the test creation date.

Date filters

All the possible dates can be filtered by since and until as in test.end_time.since and test.end_time.until, but as the start_time is the default query, it has a shortened version by only typing since and until
The available dates are:

test.start_time

test.end_time

test.reported_time

test.updated_time

encounter.start_time

encounter.end_time

When querying from the query string the + sign must be escaped as %2B. For instance: 2014-08-01T18:10:36+07:00, will be: 2014-08-01T18:10:36%2B07:00

Example

since: will retrieve tests started after a specific date time.

/tests?since=2014-04-10T15:22:12+00:00

test.end_time.until will retrieve tests ended before a specific date time. Useful to define a time window in combination with “since”.

/tests?test.end_time.until=2014-04-10T15:22:12-0300

Location filters

location - filter tests by geo location id

/tests?location=ne:CHE_3424

Institution filters

institution.uuid - filter tests by institution UUID

/tests?institution.uuid=1

Device filters

device.uuid - filter tests by device UUID

/tests?device.uuid=9d68e8fd-3ebe-a163-2ad6-7a675dac5dde

device.model - filter tests by device model name

/tests?device.model=genexpert

device.serial_number - filter tests by device serial number

/tests?device.serial_number=A-1234567890

Site filters

site.uuid - filter tests by site UUID

/tests?site.uuid=1

test.site_user - filter tests for the user that executed the test.

/tests?system_user=jdoe

Demographic filters

patient.gender - filter tests by the patient gender

/tests?patient.gender=male

encounter.patient_age - filter tests for people by the range specified. This is the age at the moment of the encounter.

/tests?encounter.patient_age=50yo..60yo

Test filters

test.assays.result - filter by the outcome of any of the assays: positive / negative / indeterminate / n/a.

/tests?test.assays.result=positive

test.assays.condition - filter tests for the condition name of any of the assays. The possible values will be the combination of all the conditions of all the manifests in the system.

Example

Response

Every device manufacturer should provide a manifest specifying the translation from the reporting format for each device to the API standard fields.

The manifest is a json that must include two fields: metadata and field_mapping. And additionally may contain custom_fields.

Metadata

The metadata header must include the version of the manifest, the version of the API, a list of the models that it applies to, the source data type (json, xml or csv), and the list of conditions that the device reports.

Version

The current version of the manifest is “1.2.1”.

Source type

Current source types are json, xml, csv, and headless_csv for the cases when the csv contains no header data. For custom CSVs an additional separator can be specified. If no separator is specified, the default is “,”.

In some cases the CSV can include some header that is necessary to skip. The number of lines to skip can be specified using skip_lines_at_top.

Example

Conditions

The manifest conditions must be encoded using snake notation: underscore_with_dashes

Field Mappings

The field mapping is an object that describes the translation from a reported value to a core field. Each key of the object is a reference to a field, and each value is a source object that represents the required transformations to obtain its value.

This source object may contain a number of pre-defined functions in order to retrieve and transform the data provided by the device into something that matches the required format. These are:

lookup - expects the source path of the reported field, using json path if the source_data_type is json: for multiple elements the [*] notation must be used; for each nesting level, the depth is specified using a period (.). For instance, the element ‘test_result’ has a field named ‘conditions’ that contains an array, and for every element of this array, the element ‘name’ is taken.
{"lookup":"test_result.conditions[*].name"}

If the source is an xml, the XPath notation is used. In the case described above, the result would be:
{"lookup":"test_result/conditions/name/text()"}

If the source is a csv, then the path should be the column name, or if it’s a headless_csv, it should be only the column number, 0 based.
{"lookup":"0"}

case - expects the element to transform as the first parameter and an array of transformations as the second one. If a match applies, the result will be the output specified. Wildcards are specified as ‘*’.
{"case":[{"lookup":"conditions[*].condition"},[{"when":"*MTB*","then":"MTB"},{"when":"*FLU*","then":"H1N1"},{"when":"*FLUA*","then":"A1N1"}]]}

lowercase - Converts the parsed field value into a lowercase string
{"lowercase":{"lookup":"last_name"}}

concat - expects two or more parameters and returns a string containing all the parameters joined
{"concat":[{"lookup":"patient_information.last_name"},", ",{"lookup":"patient_information.first_name"}]}

strip - removes trailing spaces from the given parameter
{"strip":{"lookup":"patient_information.last_name"}}

convert_time - it will convert a numeric time from a given time unit to another one specified. The source time unit is expected first. Possible units are: years, months, days, hours, minutes, seconds, milliseconds. When reducing the unit precision, no rounding will be made. When converting from days to years, all years will be considered as 365.25 days long. When converting from days to months, all months will be considered as 30 days long.
{"convert_time":[{"lookup":"patient_information.age_in_years"},"years","days"]}

beginning_of [year, month] - Useful for date related PII, it converts a date into a less specific time span. Expects the value as the first parameter, and the time unit as the second one.
{"beginning_of":[{"lookup":"patient_information.age"},"month"]}

milliseconds_between / hours_between / minutes_between / seconds_between / years_between / months_between / days_between - measures the number of milliseconds, hours, years, etc. between two given dates. Useful to compute ages or test durations. It will always round to the smallest value.
{"years_between":[{"lookup":"patient_information.birth_date"},{"lookup":"test_information.run_at"}]}

parse_date - parse the field value using the specified format for further processing. Eg: ‘yyyy-mm-dd hh:mm:ss’. All dates must be stored using ISO 8601 format. If the device reports a date using another format, it must be parsed. If the date will be used in another function that expects a date, it must be parsed.
{"parse_date":[{"lookup":"patient_information.birth_date"},"%d-%m-%Y %I:%M:%S %p"]}

duration - It expects a duration object with years, months, days, seconds, and milliseconds. There is no required component, but at least one must be present.
{"duration":{"years":{"years_between":[{"parse_date":[{"lookup":"Birthday"},"%d.%m.%Y"]},{"parse_date":[{"lookup":"DateOfAnalysis"},"%d.%m.%Y"]}]}}}

clusterise - given an array of steps and a number, it returns the bucket that contains it. The lower boundary will always be zero and the upper bucket will always contain all the values that are greater or equal the last step value. The step value will always be the greater value of the generated cluster. In the following example, the buckets created will be: “0-5”, “6-15”, “16-45”, “46+”
{"clusterise":[{"lookup":"patient_information.age_in_years"},[5,15,45]]}

substring - it extracts the string in the specified positions. Negative values are counted from the end of the string being -1 the last element. The given example will return the original string untouched.
{"substring":[{"lookup":"test_information.assay_code"},0,-1]}

equals - Used inside an if expression. It checks if its two arguments are equal. Returns true or false and allows an if to be executed in return. The order of the arguments is not important. Any of the arguments can be any valid expression or a string.
{"equals":["A",{"lookup":"Condition"}]}

if - Used with a conditional expression. It receives an array containing a conditional in the first position, and true and false expressions in the second and third positions of the array
{"if":[{"equals":[{"lookup":"Condition"},"A"]},{"lookup":"A Column"},{"lookup":"B Column"}]}

script - Used when the needed calculation is too complex or you need to access basic CDX elements like the device, its laboratory, institution or location. The only value will be a Javascript that in the last line returns the result to be assigned for that field. The message sent by the device would be accessible through a message object, and its properties would be accessible as a regular Javascript object.
{"script":"message['Condition']"}
Or as a simple property
{"script":"message.first_name + ' ' + message.last_name"}
If the message sent is a csv, then it would be accessed by column header:
{"script":"message['Result']"}
If it doesn’t have header, it can be accessed by column number:
{"script":"message['5']"}
If the message sent is an xml, then you will have xpath available:
{"script":"message.xpath('Patient/@name').first().value"}
To access basic CDX elements just reference them:
{"script":"device.name + ', ' + device.uuid"}{"script":"location.name + ': ' + parseInt(location.lat) + ',' + parseInt(location.lng)"}{"script":"laboratory.id + '-' + new Date().getFullYear() + '-' + message['SampleId']"}

Custom Fields

If the device reports additional information that is necessary for further analysis, it should be included in the manifest definition. Additionally to the field mappings, the manifest may define custom_fields. This custom fields are to be used in the field_mapping section.

Each custom field may contain:

pii - boolean. Indicates if the field must be considered PII or not.

A sample custom field would be:

{"patient.telephone_number":{"pii":true}}

Personally Identifiable Information

The device can report information that allows the test to be linked with the patient. This information must be kept encrypted and must not be indexed. Therefore, all PII must have “pii” field set to true.

Implementation Specific Field Mapping Metadata

Manifests support implementation specific metadata at the field level. Such metadata is ignored by reference implementations, but could be of use for specific ones.

Implementation specific field mapping metadata can be included simply by adding a non-standard key-value pair to the root level of a field mapping. Implementation specific keys MUST NOT override the standard field mapping elements listed above.

Implementation specific keys can be anything, but we SUGGEST to prefix them with -x–, which makes it easier to distinguish standard field mapping elements from ad hoc ones.

As an example, let’s say there’s a need to treat some fields as Maximum Security, which has certain implications for a particular implementation. An x-max_security field could be added to those fields as shown below:

{...pii:falsevalid_values:{...}x-max_security:true...}

Given this manifest, standard manifest processors MAY ignore x-max_security, but they WON’T fail because of it. It’s then up to each implementation to provide a specific (still compliant) processor that knows what to do when a mapping includes x-max_security.

It’s up to the reference implementations to define this attributes for the core fields, as the manifest will only allow to define them for the custom fields of each device model.

The inlined values must follow the default values schema. If a different implementation is needed, it can only be provided with the external uri and schema.

Location

The location fields, such as the laboratory location or the patient location, will include an extra “locations” field. This field can contain a uri of the locations provider, or it can contain the inlined locations.

Example

Locations Dictionary

Locations inlined inside the field definition. This will be feasible only for small number of locations, and should be avoided for a hierarchy with over a hundred nodes. The inlined locations must follow the default locations schema. If a different implementation is needed, it can only be provided with the external uri and schema.

sample.id - This field represents the identifier of the sample exactly as entered by the lab user on the diagnostics machine. Its purpose is to keep track of the originally reported sample identifier. Note that the values in this field may not be unique across different laboratories or even over time.

sample.uuid - The internal id in CDX. An automatically generated UUID to unequivocally identify the sample in CDX.

sample.type - The type of the sample. String.

sample.collection_date - The date when the sample was collected.

test.id - The id given by the device. If the device reports two tests with the same test.id, then the first one will be updated.

test.uuid - The internal id in CDX. an automatically generated UUID to unequivocally identify the test in CDX.

test.start_time - The timestamp when the test started running in the device.

test.end_time - The timestamp when the test finished running in the device.

test.reported_time - The creation timestamp in CDX.

test.updated_time - The last update timestamp in CDX (if two tests are reported with the same test.id, this field will be updated).

test.error_code - A numeric result code. This will follow the manufacturer’s own coding and won’t be standardized.

test.error_description - User friendly error description.

test.site_user - The user that ran the test. This is not necessarily a cdx user.

test.name - The name of the test as provided by the device.

test.status - An enumerated global result of the test.

The possible values are:

invalid

error

no_result

success

in_progress

test.assays - A single test can run multiple assays.

test.assays.name - The code name of the assay used in the test as provided by the device.

test.assays.condition - The condition that this particular assay tests. It must be one of the conditions listed in the manifest’s metadata, with the same notation.

test.assays.result - An enumerated result of the assay, if available.

The possible values are:

positive

negative

indeterminate

n/a

test.assays.quantitative_result - The result of the test, if measurable, in a numeric scale. This scale will follow the manufacturer’s convention.

test.type - If the test is from a real sample or if it’s just a quality control test.

The possible values are:

specimen

qc

device.uuid - The internal id that identifies the device in CDX.

device.name - The name of the device in CDX.

device.lab_user - The name of the user running the tests.

device.serial_number - The serial number of the device, identifiable in any external system.

institution.uuid - The internal CDX id of the Institution that owns the device.

institution.name - The name of that Institution.

site.uuid - The internal CDX id of the Laboratory where the device is located.

site.name - The name of that Laboratory.

patient.id - The id of the patient in CDX.

patient.name - The patient’s name.

patient.dob - The patient’s date of birth.

patient.gender - The birth gender of the patient.

The possible values are:

male

female

other

patient.email - The patient’s email.

patient.phone - The patient’s phone.

location.id - The Natural Earth geo id of the device location at the moment of the test.

location.parents - The list of parents, in Natural Earth geo ids, of the device location at the moment of the test.

location.admin_levels - The list of parents, in Natural Earth geo ids indexed by administrative level, of the device location at the moment of the test.

location.lat - The latitude of the device location at the moment of the test.

location.lng - The longitude of the device location at the moment of the test.

encounter.id - This field represents the identifier of the encounter exactly as entered by the lab user on the diagnostics machine.

encounter.uuid - The internal id in CDX. an automatically generated UUID to unequivocally identify the test in CDX.

encounter.patient_age - The age of the patient at the moment the test order was generated.

encounter.start_time - The start time of the first test of the test order.

encounter.end_time - The end time of the last test of the test order.

encounter.observations - A field for the technician to write down any observations about the test order.

encounter.diagnosis - A single Encounter can have multiple diagnosis, one per condition.

encounter.diagnosis.name - The code name of the assay used in the test as provided by the device.

encounter.diagnosis.condition - The condition that this particular assay tests. It must be one of the conditions listed in the manifest’s metadata, with the same notation.

encounter.diagnosis.result - An enumerated result of the assay, if available.

The possible values are:

positive

negative

indeterminate

n/a

encounter.diagnosis.quantitative_result - The result of the test, if measurable, in a numeric scale. This scale will follow the manufacturer’s convention.

All location, site and institution information will be automatically filled by CDX when a test is reported using the information already available in the system.