3.2. Technical Documentation

This part of the documentation lists and explains technical details of the implementation of EHRbase.

3.2.1. Overview

Warning

WIP

  • openEHR
  • REST
  • AQL
  • etc.

3.2.2. Service Layer

Warning

WIP

3.2.2.1. General

The service layer of EHRbase is composed of …

3.2.2.2. openEHR Platform Abstract Service Model

Based on the openEHR Platform Abstract Service Model the following check list is build to give an overview and document the current state. Each service component has a table documenting the current state of

  • implementation of the method itself, if applicable
  • implementation and utilization of the pre checks of the method, if applicable
  • implementation and utilization of the post checks of the method, if applicable

Services

3.2.2.2.1. EHR

For more details see I_EHR_SERVICE in the official documentation.

Method Implemented Pre Post
has_ehr Yes / /
has_ehr_for_subject I / /
create_ehr C / No
create_ehr_with_id C No No
create_ehr_for_subject No / /
create_ehr_for_subject_with_id No No /
get_ehr No No /
get_ehrs_for_subject No / /
i_ehr No / /

Methods with I note are currently indirectly implemented. Their functionality is available, but the general signature might be different.

Methods with C note are currently combined in a more general createEhr method.

3.2.2.2.2. EHR_STATUS

For more details see I_EHR_STATUS the in official documentation.

Method Implemented Pre Post
has_ehr_status_version I Yes /
get_ehr_status Yes Yes /
get_ehr_status_at_time I Yes /
set_ehr_queryable C No No
set_ehr_modifiable C No No
clear_ehr_queryable C No No
clear_ehr_modifiable C No No
update_other_details C / /
get_ehr_status_at_version Yes Yes /
get_versioned_ehr_status No No No

Methods with I note are currently indirectly implemented. Their functionality is available, but the general signature might be different.

Methods with C note are currently combined in a more general updateStatus method.

3.2.2.2.3. DIRECTORY

For more details see I_EHR_DIRECTORY the in official documentation.

Method Implemented Pre Post
has_directory      
has_path      
create_directory      
get_directory      
get_directory_at_time      
update_directory      
delete_directory      
has_directory_version      
get_directory_at_version      
get_versioned_directory      

3.2.2.2.4. COMPOSITION

For more details see I_EHR_COMPOSITION the in official documentation.

Method Implemented Pre Post
has_composition      
get_composition_latest      
get_composition_at_time      
get_composition_at_version      
get_versioned_composition      
create_composition      
update_composition      
delete_composition      

3.2.2.2.5. CONTRIBUTION

For more details see I_EHR_CONTRIBUTION the in official documentation.

Method Implemented Pre Post
has_contribution      
get_contribution      
commit_contribution      
list_contributions      
contribution_count      

3.2.3. New Contain Clause Resolution Strategy

  1. Chevalley 3.7.20

3.2.4. Backgroud

AQL specifies the important clause ‘CONTAINS’. This allows to specify a containment criteria on specified archetypes anywhere into composition projections. The specification is found in openEHR AQL containment. As mentioned in the specification, ‘CONTAINS’ specifies an hierarchical relationship with the Tree based data architecture (hence not to be confused with a WHERE clause criteria). Hierarchical constraint is modelized using connected and acyclic graph; a node can be accessed from the root through a unique path.

3.2.4.1. Previous Approach

The previous strategy was based on maintaining a specific containment table based on a hierarchical data representation using PostgreSQL ltree. The algorithm was based on identified AQL paths during the composition serialization: each path expression was then stored in a simplify way as to describe the hierarchy of archetypes within the composition, this for each composition. The table was then used to build the SQL expression corresponding to an AQL statement:

  • identify the template(s) matching the contain clause
  • retrieve the path for a given contain constraint for each identified template(s)

The resulting SQL expression is a combination (UNION) of SQL statement for each template.

An example of containment records is as follows:

CONTAINS COMPOSITION c CONTAINS OBSERVATION o [openEHR-EHR-OBSERVATION.pulse-oximetry.v1]

Is translated as

SELECT composition_id FROM ehr.contain WHERE label ~= '*.openEHR_EHR_OBSERVATION_pulse_oximetry_v1'

The template Id is then retrieve from the correlation between the composition entry (ehr.entry) and the template_id attribute. The same logic is used to retrieve the path of a particular node relatively to a template.

Although this approach was initially satisfactory, it has been seen as impacting performance whenever the number of records increases. As shown in the above example, the number of entries for a single composition can be significant and, in the lack of proper indexing, the identification of a template may require costly sequential search. Further, the construction of an SQL expression corresponding to an AQL CONTAINS clause was problematic. Another issue was that item_structure in /context/other_context was not referenced in containment and then was not resolved for querying.

3.2.4.2. New Approach

3.2.4.2.1. Assumptions

This approach assumes that all stored compositions are bound to one known template (at the time of this writing, operational template v1.4). A template is known whenever it is defined in the platform, it is stored in the DB in table ehr.template_store

3.2.4.2.2. Objectives

The new logic consists in resolving an AQL CONTAINS clause by:

  • identifying the template(s) matching the constraints
  • resolving the paths for the nodes defined in the CONTAINS clause

Identified templates are used to build the resulting SQL expression, each identified template produces a SQL query. At the end of the process, SQL queries are chained by a UNION clause.

Resolved paths are used to construct the json path expression used to query JSONB structure in the DB.

3.2.4.2.3. Technical Approach

3.2.4.2.3.1. Operational Template Traversal

All resolution are now based on so-called WebTemplates (class OptVisitor) providing a tree construct detailing all constraints and attributes of an operational template. The tree structure is traversed using JsonPath expressions (see f.e. Baeldung’s guide on this).

For instance, to check the existence of a node containment and return the corresponding AQL path, the following logic is illustrated as follows.

Assume we want to retrieve the template(s) where the following expression is satisfied:

contains COMPOSITION c[openEHR-EHR-COMPOSITION.report-result.v1] contains CLUSTER f [openEHR-EHR-CLUSTER.case_identification.v0]

The corresponding jsonpath expression to traverse the WebTemplate is:

$..[?(@.node_id == 'openEHR-EHR-COMPOSITION.report-result.v1')]..[?(@.node_id == 'openEHR-EHR-CLUSTER.case_identification.v0')]

When applied to template Virologischer Befund, the following structure is returned (these are the attributes for the retrieved node)

{
    "min" : "1",
    "aql_path" : "/context/other_context[at0001]/items[openEHR-EHR-CLUSTER.case_identification.v0]",
    "max" : "1",
    "children" : " size = 2",
    "name" : "Fallidentifikation",
    "description" : "Zur Erfassung von Details zur Identifikation eines Falls im Gesundheitswesen.",
    "id" : "fallidentifikation",
    "type" : "CLUSTER",
    "category" : "DATA_STRUCTURE",
    "node_id" : "openEHR-EHR-CLUSTER.case_identification.v0",
}

The corresponding AQL path for node openEHR-EHR-CLUSTER.case_identification.v0 in template Virologischer Befund is /context/other_context[at0001]/items[openEHR-EHR-CLUSTER.case_identification.v0]

The corresponding WebTemplate section for this particular node is represented as follows:

{
  "min": 1,
  "aql_path": "/context/other_context[at0001]/items[openEHR-EHR-CLUSTER.case_identification.v0]",
  "max": 1,
  "children": [
    {
      "min": 1,
      "aql_path": "/context/other_context[at0001]/items[openEHR-EHR-CLUSTER.case_identification.v0]/items[at0001]",
      "max": 1,
      "name": "Fall-Kennung",
      "description": "Der Bezeichner/die Kennung dieses Falls.",
      "id": "fall_kennung",
      "category": "ELEMENT",
      "type": "DV_TEXT",
      "constraints": [
        {
          "aql_path": "/context/other_context[at0001]/items[openEHR-EHR-CLUSTER.case_identification.v0]/items[at0001]/value",
          "mandatory_attributes": [
            {
              "name": "Value",
              "attribute": "value",
              "id": "value",
              "type": "STRING"
            }
          ],
          "attribute_name": "value",
          "constraint": {
            "occurrence": {
              "min": 1,
              "max_op": "\u003c\u003d",
              "min_op": "\u003e\u003d",
              "max": 1
            }
          },
          "type": "DV_TEXT"
        }
      ],
      "node_id": "at0001"
    },
    {
      "aql_path": "/context/other_context[at0001]/items[openEHR-EHR-CLUSTER.case_identification.v0]/items",
      "name": "Items",
      "attribute": "items",
      "id": "items",
      "occurrence": {
        "min": 1,
        "max_op": "\u003c\u003d",
        "min_op": "\u003e\u003d",
        "max": 1
      },
      "category": "ATTRIBUTE",
      "type": "ITEM"
    }In other terms, t
  ],
  "name": "Fallidentifikation",
  "description": "Zur Erfassung von Details zur Identifikation eines Falls im Gesundheitswesen.",
  "id": "fallidentifikation",
  "type": "CLUSTER",
  "category": "DATA_STRUCTURE",
  "node_id": "openEHR-EHR-CLUSTER.case_identification.v0"
},

Whenever the node_id is not specified, the jsonpath expression uses class names. For example the following AQL

SELECT location FROM EHR e CONTAINS COMPOSITION CONTAINS ADMIN_ENTRY CONTAINS location [openEHR-EHR-CLUSTER.location.v1]

Is translated as:

$..[?(@.type == 'COMPOSITION')]..[?(@.type == 'ADMIN_ENTRY')]..[?(@.node_id == 'openEHR-EHR-CLUSTER.location.v1')]

3.2.4.2.3.2. AQL Clause Interpretation

Contains clause interpretation consists in parsing the AQL expression (ANTLR) and create a corresponding list of propositions to evaluate.

The logic is based on the recursive traversal of the tree expression (AST), from bottom left to the top of the tree, and create the template traversal query as well as the boolean validations if any if the expression contains logical operators (AND, OR, XOR …).

The evaluation does check first simple containment chains (CONTAINS…CONTAINS…CONTAINS…) using WebTemplate traversals described above, and then checks the logical propositions based on these.

3.2.4.2.3.2.1. Example

AQL expression:

select
m
from EHR e
contains (
    CLUSTER f[openEHR-EHR-CLUSTER.case_identification.v0] and
    CLUSTER z[openEHR-EHR-CLUSTER.specimen.v1] and
    CLUSTER j[openEHR-EHR-CLUSTER.laboratory_test_panel.v0]
    contains CLUSTER g[openEHR-EHR-CLUSTER.laboratory_test_analyte.v1])

The containments are evaluated with the following tree

The containments are evaluated as follows:

  1. CLUSTERf[openEHR-EHR-CLUSTER.case_identification.v0]” -
  2. CLUSTERz[openEHR-EHR-CLUSTER.specimen.v1]
  3. CLUSTERg[openEHR-EHR-CLUSTER.laboratory_test_analyte.v1]” as in CLUSTER j[openEHR-EHR-CLUSTER.laboratory_test_panel.v0]  contains CLUSTER g[openEHR-EHR-CLUSTER.laboratory_test_analyte.v1])
  4. CLUSTERz[openEHR-EHR-CLUSTER.specimen.v1] and CLUSTERj[openEHR-EHR-CLUSTER.laboratory_test_panel.v0]containsCLUSTERg[openEHR-EHR-CLUSTER.laboratory_test_analyte.v1]”: check the INTERSECTION of the results from 2 AND 3 above
  5. CLUSTERf[openEHR-EHR-CLUSTER.case_identification.v0] and CLUSTERz[openEHR-EHR-CLUSTER.specimen.v1]andCLUSTERj[openEHR-EHR-CLUSTER.laboratory_test_panel.v0]containsCLUSTERg[openEHR-EHR-CLUSTER.laboratory_test_analyte.v1]”: check the INTERSECTION of the results from 1 & 4
  6. (CLUSTERf[openEHR-EHR-CLUSTER.case_identification.v0]andCLUSTERz[openEHR-EHR-CLUSTER.specimen.v1]andCLUSTERj[openEHR-EHR-CLUSTER.laboratory_test_panel.v0]containsCLUSTERg[openEHR-EHR-CLUSTER.laboratory_test_analyte.v1])”: same as 5 since it is enclosed in parenthesis.

If another operator is used: OR or XOR, then we apply UNION or DISJUNCTION respectively.

3.2.4.2.3.3. DB Changes

The two most significant changes are

  1. Deprecation of table ehr.containment. This table is now removed, as well as all logic associated to its population.
  2. New encoding of composition entry (item_structure)

The composition entry encoding (jsonb) has now the composition name encoded outside the json structure as a dv_coded_text (UDT) in table ehr.entry and removed from the archetype node id in the composition path.

This change is required since now the identified path is a generic AQL path without composition dependent values.

{
  "/name": [
    {
      "value": "Bericht"
    }
  ],
  "/$CLASS$": "Composition",
  "/composition[openEHR-EHR-COMPOSITION.report.v1 and name/value='Bericht']": {
    "/content[openEHR-EHR-OBSERVATION.blood_pressure.v2]": [
      {
        "/name": [
          {
            "value": "Blutdruck"
          }
        ],
        "/$CLASS$": "Observation"
}

The name/value attribute in the node id is now passed as an external attribute ‘name’ and the composition item_structure is encoded as

{
  "/name": [
    {
      "value": "Bericht"
    }
  ],
  "/$CLASS$": "Composition",
  "/composition[openEHR-EHR-COMPOSITION.report.v1]": {
    "/content[openEHR-EHR-OBSERVATION.blood_pressure.v2]": [
      {
        "/name": [
          {
            "value": "Blutdruck"
          }
        ],
        "/$CLASS$": "Observation
}

While name is

(Bericht,,,,)

3.2.4.2.3.4. Processing

The sequence of containment resolution is the following

  1. Consists in parsing the AQL CONTAINS expression and build the propositions as described above.
  2. The propositions are evaluated as
    1. Simple containment chains using cached WebTemplates
    2. Computed boolean expressions based on the simple containment chains
3.2.4.2.3.5. Further Enhancements
  1. At this stage, ehr_status/other_details is not part of the contains resolution. The main issue here is that it is generally not associated to a valid template.
  2. There need to do more research for archetype_slots in a ANY type.