Skip to content

Commit

Permalink
FI-2460: Support for HL7 validator wrapper (#488)
Browse files Browse the repository at this point in the history
* FI-2460: Start adding support for HL7 validator wrapper

* Add cliContext with fields matching original validator settings

* switch dockerfile to use ubuntu jammy instead of alpine

* rework dockerfile to fetch release jar instead of completely rebuilding

* add new env vars for HL7 validator, restore env vars for inferno validator

* FI-2532: Add feature flag to toggle using HL7 validator wrapper (#497)

* FI-2532: add feature flag to switch between inferno validator and hl7 validator wrapper

* standardize name

* default hl7 validator to false and comment out its services

* use new inferno-resource-validator docker image, remove custom dockerfile

* Fix HL7 validator wrapper version at 1.0.51

* Set env var so validator sessions never expire
  • Loading branch information
dehall authored Apr 8, 2024
1 parent b0ef029 commit 785e875
Show file tree
Hide file tree
Showing 15 changed files with 382 additions and 62 deletions.
8 changes: 8 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
JS_HOST=""
G10_VALIDATOR_URL=http://validator_service:4567
G10_FHIR_RESOURCE_VALIDATOR_URL=http://hl7_validator_service:3500
REDIS_URL=redis://redis:6379/0

USE_HL7_RESOURCE_VALIDATOR=false
# To use the HL7 validator wrapper instead of the inferno validator wrapper, set the above to true,
# and uncomment the relevant settings in the following files:
# - docker-compose.yml -- "hl7_validator_service" section at the bottom
# - docker-compose.background.yml -- "hl7_validator_service" section at the bottom
# - nginx.background.conf -- "location /hl7validatorapi/" section at the bottom

# Full path to a custom JWKS json file, will use lib/onc_certification_g10_test_kit/bulk_data_jwks.json if left blank
# G10_BULK_DATA_JWKS=

Expand Down
2 changes: 2 additions & 0 deletions .env.production
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
REDIS_URL=redis://redis:6379/0
G10_VALIDATOR_URL=http://validator_service:4567
VALIDATOR_URL=http://validator_service:4567
G10_FHIR_RESOURCE_VALIDATOR_URL=http://hl7_validator_service:3500
FHIR_RESOURCE_VALIDATOR_URL=http://hl7_validator_service:3500
INFERNO_HOST=http://localhost
19 changes: 19 additions & 0 deletions config/nginx.background.conf
Original file line number Diff line number Diff line change
Expand Up @@ -82,5 +82,24 @@ http {

proxy_pass http://validator_service:4567/;
}


# To enable the HL7 Validator Wrapper, both the section below and
# the section in docker-compose.background.yml need to be uncommented
# location /hl7validatorapi/ {
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# proxy_set_header Host $http_host;
# proxy_set_header X-Forwarded-Proto $scheme;
# proxy_set_header X-Forwarded-Port $server_port;
# proxy_redirect off;
# proxy_set_header Connection '';
# proxy_http_version 1.1;
# chunked_transfer_encoding off;
# proxy_buffering off;
# proxy_cache off;
# proxy_read_timeout 600s;
#
# proxy_pass http://hl7_validator_service:3500/;
# }
}
}
11 changes: 11 additions & 0 deletions docker-compose.background.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,14 @@ services:
volumes:
- ./data/redis:/data
command: redis-server --appendonly yes
# hl7_validator_service:
# image: infernocommunity/inferno-resource-validator:1.0.51
# environment:
# # Defines how long validator sessions last if unused, in minutes:
# # Negative values mean sessions never expire, 0 means sessions immediately expire
# SESSION_CACHE_DURATION: -1
# volumes:
# - ./lib/onc_certification_g10_test_kit/igs:/home/igs
# # To let the service share your local FHIR package cache,
# # uncomment the below line
# # - ~/.fhir:/home/ktor/.fhir
5 changes: 5 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ services:
volumes:
- ./data:/opt/inferno/data
depends_on:
# - hl7_validator_service
- validator_service
worker:
build:
Expand Down Expand Up @@ -33,3 +34,7 @@ services:
extends:
file: docker-compose.background.yml
service: redis
# hl7_validator_service:
# extends:
# file: docker-compose.background.yml
# service: hl7_validator_service
46 changes: 36 additions & 10 deletions lib/onc_certification_g10_test_kit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,23 +63,39 @@ class G10CertificationSuite < Inferno::TestSuite
].freeze

ERROR_FILTERS = [
/\A\S+: \S+: Unknown Code/,
/\A\S+: \S+: Unknown [Cc]ode/,
/\A\S+: \S+: None of the codings provided are in the value set/,
/\A\S+: \S+: The code provided \(\S*\) is not in the value set/,
/\A\S+: \S+: The Coding provided \(\S*\) is not in the value set/,
/\A\S+: \S+: The Coding provided \(\S*\) was not found in the value set/,
/\A\S+: \S+: A definition for CodeSystem '.*' could not be found, so the code cannot be validated/
/\A\S+: \S+: A definition for CodeSystem '.*' could not be found, so the code cannot be validated/,
/\A\S+: \S+: URL value '.*' does not resolve/,
/\A\S+: \S+: .*\[No server available\]/ # Catch-all for certain errors when TX server is disabled
].freeze

[
G10Options::US_CORE_3_REQUIREMENT,
G10Options::US_CORE_4_REQUIREMENT,
G10Options::US_CORE_5_REQUIREMENT,
G10Options::US_CORE_6_REQUIREMENT
def self.setup_validator(us_core_version_requirement) # rubocop:disable Metrics/CyclomaticComplexity
validator_method = if Feature.use_hl7_resource_validator?
method(:fhir_resource_validator)
else
method(:validator)
end

validator_method.call :default, required_suite_options: us_core_version_requirement do
if Feature.use_hl7_resource_validator?
url ENV.fetch('G10_FHIR_RESOURCE_VALIDATOR_URL', 'http://hl7_validator_service:3500')

cli_context do
txServer nil
displayWarnings true
disableDefaultResourceFetcher true
end

].each do |us_core_version_requirement|
validator :default, required_suite_options: us_core_version_requirement do
url ENV.fetch('G10_VALIDATOR_URL', 'http://validator_service:4567')
us_core_version_num = G10Options::US_CORE_VERSION_NUMBERS[us_core_version_requirement[:us_core_version]]

igs("hl7.fhir.us.core##{us_core_version_num}")
else
url ENV.fetch('G10_VALIDATOR_URL', 'http://validator_service:4567')
end

us_core_message_filters =
case (us_core_version_requirement[:us_core_version])
Expand Down Expand Up @@ -148,6 +164,16 @@ class G10CertificationSuite < Inferno::TestSuite
end
end

[
G10Options::US_CORE_3_REQUIREMENT,
G10Options::US_CORE_4_REQUIREMENT,
G10Options::US_CORE_5_REQUIREMENT,
G10Options::US_CORE_6_REQUIREMENT

].each do |us_core_version_requirement|
setup_validator(us_core_version_requirement)
end

def self.jwks_json
bulk_data_jwks = JSON.parse(File.read(
ENV.fetch('G10_BULK_DATA_JWKS',
Expand Down
16 changes: 12 additions & 4 deletions lib/onc_certification_g10_test_kit/configuration_checker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
module ONCCertificationG10TestKit
class ConfigurationChecker
EXPECTED_VALIDATOR_VERSION = '2.3.2'.freeze
EXPECTED_HL7_VALIDATOR_VERSION = '"6.3.0"'.freeze

def configuration_messages
validator_version_message + terminology_messages + version_message
Expand All @@ -21,24 +22,31 @@ def validator_url
end

def validator_version_message
response = Faraday.get "#{validator_url}/version"
if Feature.use_hl7_resource_validator?
expected_validator_version = EXPECTED_HL7_VALIDATOR_VERSION
validator_version_url = "#{validator_url}/validator/version"
else
expected_validator_version = EXPECTED_VALIDATOR_VERSION
validator_version_url = "#{validator_url}/version"
end

response = Faraday.get validator_version_url
if response.body.starts_with? '{'
version_json = JSON.parse(response.body)
version = version_json['inferno-framework/fhir-validator-wrapper']
else
version = response.body
end

if version == EXPECTED_VALIDATOR_VERSION
if version == expected_validator_version
[{
type: 'info',
message: "FHIR validator is the expected version `#{EXPECTED_VALIDATOR_VERSION}`"
message: "FHIR validator is the expected version `#{expected_validator_version}`"
}]
else
[{
type: 'error',
message: "Expected FHIR validator version `#{EXPECTED_VALIDATOR_VERSION}`, but found `#{version}`"
message: "Expected FHIR validator version `#{expected_validator_version}`, but found `#{version}`"
}]
end
rescue JSON::ParserError => e
Expand Down
9 changes: 4 additions & 5 deletions lib/onc_certification_g10_test_kit/feature.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
module ONCCertificationG10TestKit
module Feature
class << self # rubocop:disable Lint/EmptyClass
# This is how you can define feature flags to be used in the g10 test kit
# def us_core_v4?
# ENV.fetch('US_CORE_4_ENABLED', 'false')&.casecmp?('true')
# end
class << self
def use_hl7_resource_validator?
ENV.fetch('USE_HL7_RESOURCE_VALIDATOR', 'false')&.casecmp?('true')
end
end
end
end
7 changes: 7 additions & 0 deletions lib/onc_certification_g10_test_kit/g10_options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ module G10Options
US_CORE_5 = 'us_core_5'.freeze
US_CORE_6 = 'us_core_6'.freeze

US_CORE_VERSION_NUMBERS = {
US_CORE_3 => '3.1.1',
US_CORE_4 => '4.0.0',
US_CORE_5 => '5.0.1',
US_CORE_6 => '6.1.0'
}.freeze

BULK_DATA_1 = 'multi_patient_api_stu1'.freeze
BULK_DATA_2 = 'multi_patient_api_stu2'.freeze

Expand Down
42 changes: 42 additions & 0 deletions spec/fixtures/ValidationResponse-code-invalid-1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"outcomes": [
{
"fileInfo": {
"fileName": "manually_entered_file.json",
"fileContent": "{\n \"resourceType\": \"Immunization\",\n \"subpotentReason\": [{\n \"coding\": [{\n \"system\": \"http://terminology.hl7.org/CodeSystem/immunization-subpotent-reason\",\n \"code\": \"partialdose\"\n }]\n }]\n}",
"fileType": "json"
},
"issues": [
{
"source": "TerminologyEngine",
"server": null,
"line": 3,
"col": 4,
"location": "Immunization.subpotentReason[0].coding[0].code",
"message": "Unknown code 'partialdose' in the CodeSystem 'http://terminology.hl7.org/CodeSystem/immunization-subpotent-reason' version '1.0.0'",
"messageId": null,
"type": "CODEINVALID",
"level": "ERROR",
"html": "Unknown code 'partialdose' in the CodeSystem 'http://terminology.hl7.org/CodeSystem/immunization-subpotent-reason' version '1.0.0'",
"locationLink": null,
"txLink": null,
"sliceHtml": null,
"sliceText": null,
"slicingHint": false,
"signpost": false,
"criticalSignpost": false,
"ruleDate": null,
"matched": false,
"ignorableError": false,
"invId": null,
"comment": null,
"sliceInfo": null,
"error": true,
"display": "ERROR: Immunization.subpotentReason[0].coding[0].code: Unknown code 'partialdose' in the CodeSystem 'http://terminology.hl7.org/CodeSystem/immunization-subpotent-reason' version '1.0.0'"
}
]
}
],
"sessionId": "f4af8f4f-15d0-44f3-8401-dc9f9014aeaf",
"validationTimes": {}
}
40 changes: 40 additions & 0 deletions spec/fixtures/ValidationResponse-code-invalid-2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"outcomes" : [ {
"fileInfo" : {
"fileName" : "manually_entered_file.json",
"fileContent" : "{\n \"resourceType\": \"AllergyIntolerance\",\n \"clinicalStatus\": {\n \"coding\": [\n {\n \"system\": \"http://terminology.hl7.org/CodeSystem/allergyintolerance-clinical\",\n \"code\": \"actives\",\n \"display\": \"Active\"\n }\n ]\n }\n}",
"fileType" : "json"
},
"issues" : [
{
"source" : "TerminologyEngine",
"server" : null,
"line" : 3,
"col" : 4,
"location" : "AllergyIntolerance.clinicalStatus",
"message" : "None of the codings provided are in the value set 'AllergyIntolerance Clinical Status Codes' (http://hl7.org/fhir/ValueSet/allergyintolerance-clinical|4.0.1), and a coding from this value set is required) (codes = http://terminology.hl7.org/CodeSystem/allergyintolerance-clinical#actives)",
"messageId" : "Terminology_TX_NoValid_1_CC",
"type" : "CODEINVALID",
"level" : "ERROR",
"html" : "None of the codings provided are in the value set 'AllergyIntolerance Clinical Status Codes' (http://hl7.org/fhir/ValueSet/allergyintolerance-clinical|4.0.1), and a coding from this value set is required) (codes = http://terminology.hl7.org/CodeSystem/allergyintolerance-clinical#actives)",
"locationLink" : null,
"txLink" : null,
"sliceHtml" : null,
"sliceText" : null,
"slicingHint" : false,
"signpost" : false,
"criticalSignpost" : false,
"ruleDate" : null,
"matched" : false,
"ignorableError" : false,
"invId" : null,
"comment" : null,
"sliceInfo" : null,
"display" : "ERROR: AllergyIntolerance.clinicalStatus: None of the codings provided are in the value set 'AllergyIntolerance Clinical Status Codes' (http://hl7.org/fhir/ValueSet/allergyintolerance-clinical|4.0.1), and a coding from this value set is required) (codes = http://terminology.hl7.org/CodeSystem/allergyintolerance-clinical#actives)",
"error" : true
}
]
} ],
"sessionId" : "7c0cb248-4dd9-4063-9ed9-03623bbe221a",
"validationTimes" : { }
}
38 changes: 38 additions & 0 deletions spec/fixtures/ValidationResponse-code-invalid-3.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"outcomes" : [ {
"fileInfo" : {
"fileName" : "manually_entered_file.json",
"fileContent" : "{\n \"resourceType\": \"Patient\",\n \n \"extension\": [ {\n \"url\": \"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race\",\n \"extension\": [ {\n \"url\": \"ombCategory\",\n \"valueCoding\": {\n \"system\": \"urn:oid:2.16.840.1.113883.6.238\",\n \"code\": \"2299-0\",\n \"display\": \"Asian\"\n }\n }, {\n \"url\": \"text\",\n \"valueString\": \"Asian\"\n } ]\n }, \n {\n \"url\": \"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity\",\n \"extension\": [ {\n \"url\": \"ombCategory\",\n \"valueCoding\": {\n \"system\": \"urn:oid:2.16.840.1.113883.6.238\",\n \"code\": \"2186-5\",\n \"display\": \"Not Hispanic or Latino\"\n }\n }, {\n \"url\": \"text\",\n \"valueString\": \"Not Hispanic or Latino\"\n } ]\n }\n ]\n}",
"fileType" : "json"
},
"issues" : [{
"source" : "TerminologyEngine",
"server" : null,
"line" : 8,
"col" : 12,
"location" : "Patient.extension[0].extension[0].value.ofType(Coding)",
"message" : "The Coding provided (urn:oid:2.16.840.1.113883.6.238#2106-3) is not in the value set http://hl7.org/fhir/us/core/ValueSet/omb-race-category, and a code is required from this value set. (error message = Not in value set http://hl7.org/fhir/us/core/ValueSet/omb-race-category)",
"messageId" : "Terminology_TX_Confirm_4a",
"type" : "CODEINVALID",
"level" : "ERROR",
"html" : "The Coding provided (urn:oid:2.16.840.1.113883.6.238#2106-3) is not in the value set http://hl7.org/fhir/us/core/ValueSet/omb-race-category, and a code is required from this value set. (error message = Not in value set http://hl7.org/fhir/us/core/ValueSet/omb-race-category)",
"locationLink" : null,
"txLink" : null,
"sliceHtml" : null,
"sliceText" : null,
"slicingHint" : false,
"signpost" : false,
"criticalSignpost" : false,
"ruleDate" : null,
"matched" : false,
"ignorableError" : false,
"invId" : null,
"comment" : null,
"sliceInfo" : null,
"display" : "ERROR: Patient.extension[0].extension[0].value.ofType(Coding): The Coding provided (urn:oid:2.16.840.1.113883.6.238#2106-3) is not in the value set http://hl7.org/fhir/us/core/ValueSet/omb-race-category, and a code is required from this value set. (error message = Not in value set http://hl7.org/fhir/us/core/ValueSet/omb-race-category)",
"error" : true
}]
} ],
"sessionId" : "7c0cb248-4dd9-4063-9ed9-03623bbe221a",
"validationTimes" : { }
}
38 changes: 38 additions & 0 deletions spec/fixtures/ValidationResponse-no-name.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"outcomes" : [ {
"fileInfo" : {
"fileName" : "manually_entered_file.json",
"fileContent" : "{ \"resourceType\": \"Patient\" }",
"fileType" : "json"
},
"issues" : [{
"source" : "InstanceValidator",
"server" : null,
"line" : 1,
"col" : 30,
"location" : "Patient",
"message" : "Patient.name: minimum required = 1, but only found 0 (from http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient|3.1.1)",
"messageId" : "Validation_VAL_Profile_Minimum",
"type" : "STRUCTURE",
"level" : "ERROR",
"html" : "Patient.name: minimum required = 1, but only found 0 (from http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient|3.1.1)",
"locationLink" : null,
"txLink" : null,
"sliceHtml" : null,
"sliceText" : null,
"slicingHint" : false,
"signpost" : false,
"criticalSignpost" : false,
"ruleDate" : null,
"matched" : false,
"ignorableError" : false,
"invId" : null,
"comment" : null,
"sliceInfo" : null,
"display" : "ERROR: Patient: Patient.name: minimum required = 1, but only found 0 (from http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient|3.1.1)",
"error" : true
} ]
} ],
"sessionId" : "4d9d2dc3-5df1-461f-a4d6-bfc2788a1933",
"validationTimes" : { }
}
Loading

0 comments on commit 785e875

Please sign in to comment.