Blog from January, 2018

We are excited to announce that you can now launch your SMART apps in your Logica Sandbox instance using app link cards in the CDS-Hooks Sandbox!

Complete Flow

Please see CDS Hooks Integration for details.  

Setting It Up

To set up this scenario,

  1. either create a new, or use an existing Logica Sandbox instance,
  2. create, host, and register your SMART app,
    1. make sure your SMART app can launch successfully from your Logica Sandbox instance
  3. create and host your CDS services

Launching Your App

Referring to CDS Hooks Integration, your app link cards will now be functional!  To see this, 

  1. click on the link in your app link card in the CDS Hooks sandbox,
  2. as shown in the diagram above, your app will be launched using your Logica sandbox instance as the issuer (iss=My Sandbox),
  3. your app will complete the SMART launch flow, discovering the auth server, requesting authorization, and finally receiving an access token to your Logica Sandbox!



SMART App Best Practices

Would you like your SMART on FHIR app to be optimized in production? We have launched a guide which walks you through how to integrate best practices into your app. The guide describes three ways to increase the marketing aspects of your application and to ease production implementation. The guide is composed of three parts:

  1. Creating a marketing page
  2. Social media link previews
  3. Manifest files
  4. Patient information banners

You can access the guide through this link: SMART App Best Practices.


In the previous Terminology & Profile Support blog post I introduced the Logica Terminology Server and gave step-by-step instructions on some common use cases including:

  • Utilizing FHIR Terminology Services on the Logica Terminology Server for common SMART on FHIR app development tasks
  • Validating instance data for non-profiled FHIR resources using the $validate operation
  • Retrieving StructureDefinition resources describing the FPAR resources currently in development

If you haven't read through part one, you can access it at Logica Sandbox - Terminology & Profile Support (part 1).

In part two I will continue by showing how to validate profiled instance data using the $validate operation on the Logica Sandbox.  The profiles we'll focus on are those coming from the us-core validation pack, for FHIR version STU3 (3.0.1).  These profiles have been uploaded to the Logica Terminology Server, which can be found at:

https://api-v5-stu3.logicahealth.org/stu3/open

Request Format

Our request to validate looks just like it did in our previous examples, the important distinction is the use of the Meta.profile field to indicate that this instance data conforms to a profile.  The value of the Meta.profile field should be a reference to the StructureDefinition.url of the StructureDefinition resource which defines this instance data. 

For our examples, we'll be using http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient, which is the us-core profile for the Patient resource.  You can read the docs for this profile, or you can query the structure definition file directly from the Logica Terminology Server:

sand/StructureDefinition?url=http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient

Response

omitted due to size

Putting this all together, we can formulate the $validate request as follows:

POST https://api-v5-stu3.logicahealth.org/stu3/open/Patient/$validate
--- REQUEST BODY ---
{
  "resourceType": "Parameters",
  "parameter": [
    {
      "name": "resource",
      "resource": {
        "resourceType": "Patient",
        "meta": {
    	"profile": [
    		"http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"
    		]
		},
        "identifier": [
          {
            "use": "usual",
            "type": {
              "coding": [
                {
                  "system": "http://hl7.org/fhir/v2/0203",
                  "code": "MR"
                }
              ]
            },
            "system": "urn:oid:1.2.36.146.595.217.0.1",
            "value": "12345",
            "period": {
              "start": "2001-05-06"
            },
            "assigner": {
              "display": "Acme Healthcare"
            }
          }
        ],
        "active": true,
        "gender": "male",
        "name": [
          {
            "use": "official",
            "family": "Chalmers",
            "given": [
              "Peter",
              "James"
            ]
          },
          {
            "use": "usual",
            "family": "Yep",
            "given": [
              "Jim"
            ]
          },
          {
            "use": "maiden",
            "family": "Windsor",
            "given": [
              "Peter",
              "James"
            ],
            "period": {
              "end": "2002"
            }
          }
        ]
      }
    }
  ]
}

Response:

{
    "resourceType": "OperationOutcome",
    "text": {
        "status": "generated",
        "div": "<div xmlns=\"http://www.w3.org/1999/xhtml\"><h1>Operation Outcome</h1><table border=\"0\"><tr><td style=\"font-weight: bold;\">INFORMATION</td><td>[]</td><td><pre>No issues detected during validation</pre></td>\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t</tr>\n\t\t</table>\n\t</div>"
    },
    "issue": [
        {
            "severity": "information",
            "code": "informational",
            "diagnostics": "No issues detected during validation"
        }
    ]
}

Profiled Instance Data Validation

Now that we know what the request looks like, lets take a closer look at some validation examples.  For starters, we'll validate the patient General Purpose Example from the official Patient Resource FHIR docs.  It looks like this:

POST https://api-v5-stu3.logicahealth.org/stu3/open/Patient/$validate
--- REQUEST BODY ---
{
  "resourceType": "Parameters",
  "parameter": [
    {
      "name": "resource",
      "resource": {
        "resourceType": "Patient",
        "meta": {
    	"profile": [
    		"http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"
    		]
		},
        "id": "example",
        "identifier": [
          {
            "use": "usual",
            "type": {
              "coding": [
                {
                  "system": "http://hl7.org/fhir/v2/0203",
                  "code": "MR"
                }
              ]
            },
            "system": "urn:oid:1.2.36.146.595.217.0.1",
            "value": "12345",
            "period": {
              "start": "2001-05-06"
            },
            "assigner": {
              "display": "Acme Healthcare"
            }
          }
        ],
        "active": true,
        "name": [
          {
            "use": "official",
            "family": "Chalmers",
            "given": [
              "Peter",
              "James"
            ]
          },
          {
            "use": "usual",
            "given": [
              "Jim"
            ]
          },
          {
            "use": "maiden",
            "family": "Windsor",
            "given": [
              "Peter",
              "James"
            ],
            "period": {
              "end": "2002"
            }
          }
        ],
        "telecom": [
          {
            "use": "home"
          },
          {
            "system": "phone",
            "value": "(03) 5555 6473",
            "use": "work",
            "rank": 1
          },
          {
            "system": "phone",
            "value": "(03) 3410 5613",
            "use": "mobile",
            "rank": 2
          },
          {
            "system": "phone",
            "value": "(03) 5555 8834",
            "use": "old",
            "period": {
              "end": "2014"
            }
          }
        ],
        "gender": "male",
        "birthDate": "1974-12-25",
        "_birthDate": {
          "extension": [
            {
              "url": "http://hl7.org/fhir/StructureDefinition/patient-birthTime",
              "valueDateTime": "1974-12-25T14:35:45-05:00"
            }
          ]
        },
        "deceasedBoolean": false,
        "address": [
          {
            "use": "home",
            "type": "both",
            "text": "534 Erewhon St PeasantVille, Rainbow, Vic  3999",
            "line": [
              "534 Erewhon St"
            ],
            "city": "PleasantVille",
            "district": "Rainbow",
            "state": "Vic",
            "postalCode": "3999",
            "period": {
              "start": "1974-12-25"
            }
          }
        ],
        "contact": [
          {
            "relationship": [
              {
                "coding": [
                  {
                    "system": "http://hl7.org/fhir/v2/0131",
                    "code": "N"
                  }
                ]
              }
            ],
            "name": {
              "family": "du Marché",
              "_family": {
                "extension": [
                  {
                    "url": "http://hl7.org/fhir/StructureDefinition/humanname-own-prefix",
                    "valueString": "VV"
                  }
                ]
              },
              "given": [
                "Bénédicte"
              ]
            },
            "telecom": [
              {
                "system": "phone",
                "value": "+33 (237) 998327"
              }
            ],
            "address": {
              "use": "home",
              "type": "both",
              "line": [
                "534 Erewhon St"
              ],
              "city": "PleasantVille",
              "district": "Rainbow",
              "state": "Vic",
              "postalCode": "3999",
              "period": {
                "start": "1974-12-25"
              }
            },
            "gender": "female",
            "period": {
              "start": "2012"
            }
          }
        ]
      }
    }
  ]
}

Response:

{
    "resourceType": "OperationOutcome",
    "text": {
        "status": "generated",
        "div": "<div xmlns=\"http://www.w3.org/1999/xhtml\"><h1>Operation Outcome</h1><table border=\"0\"><tr><td style=\"font-weight: bold;\">ERROR</td><td>[Parameters.parameter.resource.name[2]]</td><td><pre>Profile http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient, Element 'Parameters.parameter.resource.name[2].family': minimum required = 1, but only found 0</pre></td>\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t</tr>\n\t\t</table>\n\t</div>"
    },
    "issue": [
        {
            "severity": "error",
            "code": "processing",
            "diagnostics": "Profile http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient, Element 'Parameters.parameter.resource.name[2].family': minimum required = 1, but only found 0",
            "location": [
                "Parameters.parameter.resource.name[2]"
            ]
        }
    ]
}

If we were to validate this patient instance data against the base FHIR specification for the Patient resource then it would pass.  Since we've claimed this resource confroms to us-core, the server is checking the instance data against the StructureDefinition for the us-core patient and we see an error.  This specific error is telling us that every name attribute MUST include a value in the family field, however our instance data does not.  We could fix this by adding a family name (see below) and the resource would pass validation.

{
  "use": "usual",
  "family": "Windsor",
  "given": [
    "Jim"
  ]
}

Let's trim down the patient resource we are tyring to validate and see if we can get some more errors.

POST https://api-v5-stu3.logicahealth.org/stu3/open/Patient/$validate
--- REQUEST BODY ---
{
  "resourceType": "Parameters",
  "parameter": [
    {
      "name": "resource",
      "resource": {
        "resourceType": "Patient",
        "meta": {
    	"profile": [
    		"http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"
    		]
		},
        "id": "example",
        "name": [
        	{
            "use": "usual",
            "given": [
              "Jim"
            ]
          },
          {
            "use": "maiden",
            "family": "Windsor",
            "period": {
              "end": "2002"
            }
          }
        ],
        "telecom": [
          {
            "use": "home"
          },
          {
            "system": "phone",
            "value": "(03) 5555 6473",
            "use": "work",
            "rank": 1
          },
          {
            "system": "phone",
            "value": "(03) 3410 5613",
            "use": "mobile",
            "rank": 2
          },
          {
            "system": "phone",
            "value": "(03) 5555 8834",
            "use": "old",
            "period": {
              "end": "2014"
            }
          }
        ],
        "birthDate": "1974-12-25",
        "address": [
          {
            "use": "home",
            "type": "both",
            "text": "534 Erewhon St PeasantVille, Rainbow, Vic  3999",
            "line": [
              "534 Erewhon St"
            ],
            "city": "PleasantVille",
            "district": "Rainbow",
            "state": "Vic",
            "postalCode": "3999",
            "period": {
              "start": "1974-12-25"
            }
          }
        ],
        "contact": [
          {
            "relationship": [
              {
                "coding": [
                  {
                    "system": "http://hl7.org/fhir/v2/0131",
                    "code": "N"
                  }
                ]
              }
            ],
            "name": {
              "family": "du Marché",
              "_family": {
                "extension": [
                  {
                    "url": "http://hl7.org/fhir/StructureDefinition/humanname-own-prefix",
                    "valueString": "VV"
                  }
                ]
              },
              "given": [
                "Bénédicte"
              ]
            },
            "telecom": [
              {
                "system": "phone",
                "value": "+33 (237) 998327"
              }
            ],
            "address": {
              "use": "home",
              "type": "both",
              "line": [
                "534 Erewhon St"
              ],
              "city": "PleasantVille",
              "district": "Rainbow",
              "state": "Vic",
              "postalCode": "3999",
              "period": {
                "start": "1974-12-25"
              }
            },
            "gender": "female",
            "period": {
              "start": "2012"
            }
          }
        ]
      }
    }
  ]
}

Response:

{
    "resourceType": "OperationOutcome",
    "text": {
        "status": "generated",
        "div": "<div xmlns=\"http://www.w3.org/1999/xhtml\"><h1>Operation Outcome</h1><table border=\"0\"><tr><td style=\"font-weight: bold;\">ERROR</td><td>[Parameters.parameter.resource]</td><td><pre>Profile http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient, Element 'Parameters.parameter.resource.identifier': minimum required = 1, but only found 0</pre></td>\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t</tr>\n\t\t\t<tr>\n\t\t\t\t<td style=\"font-weight: bold;\">ERROR</td>\n\t\t\t\t<td>[Parameters.parameter.resource]</td>\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t<td><pre>Profile http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient, Element 'Parameters.parameter.resource.gender': minimum required = 1, but only found 0</pre></td>\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t</tr>\n\t\t\t<tr>\n\t\t\t\t<td style=\"font-weight: bold;\">ERROR</td>\n\t\t\t\t<td>[Parameters.parameter.resource.name[1]]</td>\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t<td><pre>Profile http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient, Element 'Parameters.parameter.resource.name[1].family': minimum required = 1, but only found 0</pre></td>\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t</tr>\n\t\t\t<tr>\n\t\t\t\t<td style=\"font-weight: bold;\">ERROR</td>\n\t\t\t\t<td>[Parameters.parameter.resource.name[2]]</td>\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t<td><pre>Profile http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient, Element 'Parameters.parameter.resource.name[2].given': minimum required = 1, but only found 0</pre></td>\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t</tr>\n\t\t</table>\n\t</div>"
    },
    "issue": [
        {
            "severity": "error",
            "code": "processing",
            "diagnostics": "Profile http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient, Element 'Parameters.parameter.resource.identifier': minimum required = 1, but only found 0",
            "location": [
                "Parameters.parameter.resource"
            ]
        },
        {
            "severity": "error",
            "code": "processing",
            "diagnostics": "Profile http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient, Element 'Parameters.parameter.resource.gender': minimum required = 1, but only found 0",
            "location": [
                "Parameters.parameter.resource"
            ]
        },
        {
            "severity": "error",
            "code": "processing",
            "diagnostics": "Profile http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient, Element 'Parameters.parameter.resource.name[1].family': minimum required = 1, but only found 0",
            "location": [
                "Parameters.parameter.resource.name[1]"
            ]
        },
        {
            "severity": "error",
            "code": "processing",
            "diagnostics": "Profile http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient, Element 'Parameters.parameter.resource.name[2].given': minimum required = 1, but only found 0",
            "location": [
                "Parameters.parameter.resource.name[2]"
            ]
        }
    ]
}

In this example we've violated just about all cardinality constraints defined by the us-core patient profile.  Notice that the severity of these messages is error. This indicates that the instance data is in violation of the profile and would not be considered compliant. There are also warning and information severities, which are meant to notify of best practice, but don't necessarily mean the instance data is in violation.

As a final example, let's look at the us-core smokingstatus profile for the Observation resource.  I've taken their example instance data, and deleted the resource.code field, let's try to validate it:

POST https://api-v5-stu3.logicahealth.org/stu3/open/Observation/$validate
--- REQUEST BODY ---
{
  "resourceType": "Parameters",
  "parameter": [
    {
      "name": "resource",
      "resource": {
        "resourceType": "Observation",
        "id": "some-day-smoker",
        "meta": {
          "profile": [
            "http://hl7.org/fhir/us/core/StructureDefinition/us-core-smokingstatus"
          ]
        },
        "status": "final",
        "category": [
          {
            "coding": [
              {
                "system": "http://hl7.org/fhir/observation-category",
                "code": "social-history",
                "display": "Social History"
              }
            ],
            "text": "Social History"
          }
        ],
        "subject": {
          "reference": "Patient/example",
          "display": "Amy Shaw"
        },
        "issued": "2016-03-18T05:27:04Z",
        "valueCodeableConcept": {
          "coding": [
            {
              "system": "http://snomed.info/sct",
              "code": "428041000124106",
              "display": "Current some day smoker"
            }
          ],
          "text": "Current some day smoker"
        }
      }
    }
  ]
}

Response:

{
    "resourceType": "OperationOutcome",
    "text": {
        "status": "generated",
        "div": "<div xmlns=\"http://www.w3.org/1999/xhtml\"><h1>Operation Outcome</h1><table border=\"0\"><tr><td style=\"font-weight: bold;\">WARNING</td><td>[Parameters.parameter.resource]</td><td><pre>All observations should have a performer</pre></td>\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t</tr>\n\t\t\t<tr>\n\t\t\t\t<td style=\"font-weight: bold;\">INFORMATION</td>\n\t\t\t\t<td>[Parameters.parameter.resource]</td>\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t<td><pre>All observations should have a performer</pre></td>\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t</tr>\n\t\t\t<tr>\n\t\t\t\t<td style=\"font-weight: bold;\">WARNING</td>\n\t\t\t\t<td>[Parameters.parameter.resource]</td>\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t<td><pre>All observations should have an effectiveDateTime or an effectivePeriod</pre></td>\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t</tr>\n\t\t\t<tr>\n\t\t\t\t<td style=\"font-weight: bold;\">INFORMATION</td>\n\t\t\t\t<td>[Parameters.parameter.resource]</td>\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t<td><pre>All observations should have an effectiveDateTime or an effectivePeriod</pre></td>\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t</tr>\n\t\t\t<tr>\n\t\t\t\t<td style=\"font-weight: bold;\">ERROR</td>\n\t\t\t\t<td>[Parameters.parameter.resource]</td>\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t<td><pre>If code is the same as a component code then the value element associated with the code SHALL NOT be present [value.empty() or code!=component.code]</pre></td>\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t</tr>\n\t\t\t<tr>\n\t\t\t\t<td style=\"font-weight: bold;\">ERROR</td>\n\t\t\t\t<td>[Parameters.parameter.resource]</td>\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t<td><pre>Profile http://hl7.org/fhir/us/core/StructureDefinition/us-core-smokingstatus, Element 'Parameters.parameter.resource.code': minimum required = 1, but only found 0</pre></td>\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t</tr>\n\t\t</table>\n\t</div>"
    },
    "issue": [
        {
            "severity": "warning",
            "code": "processing",
            "diagnostics": "All observations should have a performer",
            "location": [
                "Parameters.parameter.resource"
            ]
        },
        {
            "severity": "information",
            "code": "processing",
            "diagnostics": "All observations should have a performer",
            "location": [
                "Parameters.parameter.resource"
            ]
        },
        {
            "severity": "warning",
            "code": "processing",
            "diagnostics": "All observations should have an effectiveDateTime or an effectivePeriod",
            "location": [
                "Parameters.parameter.resource"
            ]
        },
        {
            "severity": "information",
            "code": "processing",
            "diagnostics": "All observations should have an effectiveDateTime or an effectivePeriod",
            "location": [
                "Parameters.parameter.resource"
            ]
        },
        {
            "severity": "error",
            "code": "processing",
            "diagnostics": "If code is the same as a component code then the value element associated with the code SHALL NOT be present [value.empty() or code!=component.code]",
            "location": [
                "Parameters.parameter.resource"
            ]
        },
        {
            "severity": "error",
            "code": "processing",
            "diagnostics": "Profile http://hl7.org/fhir/us/core/StructureDefinition/us-core-smokingstatus, Element 'Parameters.parameter.resource.code': minimum required = 1, but only found 0",
            "location": [
                "Parameters.parameter.resource"
            ]
        }
    ]
}

In the response we can see a list of issues, most of the classified as information or warning.  The last two issues are error level and are a result of deleting the LOINC smoking-status code, these indicate that the resource instance does not actually conform to the indicated profile.

Validating Resource Without "Parameters" Resource

If a user wants to validate a resource without embedding in it as a parameter in a Parameters FHIR resource, that is also an option. Here's an example of a heart rate Observation example:

{
  "resourceType": "Observation",
  "id": "heart-rate",
  "meta": {
    "profile": [
      "http://hl7.org/fhir/StructureDefinition/heartrate"
    ]
  },
  "status": "final",
  "category": [
    {
      "coding": [
        {
          "system": "http://hl7.org/fhir/observation-category",
          "code": "vital-signs",
          "display": "Vital Signs"
        }
      ],
      "text": "Vital Signs"
    }
  ],
  "code": {
    "coding": [
      {
        "system": "http://loinc.org",
        "code": "8867-4",
        "display": "Heart rate"
      }
    ],
    "text": "Heart rate"
  },
  "subject": {
    "reference": "Patient/example"
  },
  "performer": {
  	"reference": "Practitioner/example"
  },
  "effectiveDateTime": "1999-07-02",
  "valueQuantity": {
    "value": 44,
    "unit": "beats/minute",
    "system": "http://unitsofmeasure.org",
    "code": "/minute"
  }
}

Limitations of Logica Sandbox Validation

In the FHIR Validation spec, there is a mention of being able to pass a custom profile via the url (https://www.hl7.org/fhir/validation.html#op). The call would look something like this:

POST [base]/Observation/$validate?profile=http://hl7.org/fhir/StructureDefinition/heartrate

Although this approach would be convenient, HAPI, the FHIR server off of which the Logica servers are built, does not support this functionality. 

Another possible approach to adding a profile for validation is to implement a Parameters resource and to add a parameter in the list that states the profile url (https://www.hl7.org/fhir/operation-resource-validate.html):

{
  "resourceType": "Parameters",
  "parameter": [
  	{
  		"name": "profile",
  		"valueUri": "http://hl7.org/fhir/StructureDefinition/heartrate"
  	},
    {
      "name": "resource",
      "resource": {
...

This, however, is also not supported by HAPI. 

So for now, using the approaches described earlier (passing the profile url through the meta.profile parameter) are the only way to validate against custom profiles. We will update our documentation if/when either approach becomes available.

Conclusion

Validating instance data through a FHIR Terminology Service like the Logica Terminology Server is a great way of ensuring data quality and interoperability.  The example's we've shown have been for the us-core profiles, but should work equally well for any profiles built using the standard StructureDefinition resources and loaded into the terminology service.