Skip to content

Commit

Permalink
import api: validate incorrect observation codes (#5303)
Browse files Browse the repository at this point in the history
# Why

Our OpenAPI schema validator is not flexible enough to capture some edge
cases for observation resources–namely, it is possible to pass in a
blood pressure observation, with the systolic or diastolic code
specified twice. This will cause a failure at the database constraint
level. We instead validate this at the time of a request so that the
user is immediately informed of this error, and the request is not
propagated any further.

# The fix

We add new validations in our Imports API validator class. Any
observation resource must satisfy either one of these conditions:
* It contains a single component that is a valid Blood Sugar measurement
LOINC code.
* It contains two components, one of which is a valid systolic BP
measurement and the other is a valid diastolic BP measurement.
  • Loading branch information
tfidfwastaken authored Oct 19, 2023
1 parent 80a065f commit 8b60577
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 2 deletions.
7 changes: 5 additions & 2 deletions app/schema/api/v4/imports.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
class Api::V4::Imports
ALLOWED_BP_CODES = %w[8480-6 8462-4]
ALLOWED_BS_CODES = %w[2339-0 87422-2 88365-2 4548-4]

class << self
def all_definitions
{
Expand Down Expand Up @@ -257,7 +260,7 @@ def observation_blood_pressure
coding: {type: "array",
items: codeable_concept(
system: "http://loinc.org",
codes: %w[8480-6 8462-4],
codes: ALLOWED_BP_CODES,
description: "8480-6 for Systolic, 8462-4 for Diastolic"
),
nullable: false, minItems: 1, maxItems: 1}
Expand Down Expand Up @@ -319,7 +322,7 @@ def observation_blood_sugar
coding: {type: "array",
items: codeable_concept(
system: "http://loinc.org",
codes: %w[2339-0 87422-2 88365-2 4548-4],
codes: ALLOWED_BS_CODES,
description: "2339-0 for random, \
87422-2 for post-prandial, \
88365-2 for fasting, \
Expand Down
35 changes: 35 additions & 0 deletions app/services/bulk_api_import/validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,15 @@ def initialize(organization:, resources:)

def validate
error = validate_schema

unless error.present?
error = validate_facilities
end

unless error.present?
error = validate_observation_codes
end

error
end

Expand All @@ -21,6 +26,36 @@ def validate_schema
{schema_errors: schema_errors} if schema_errors.present?
end

def validate_observation_codes
invalid_observations = resources_by_type[:observation]&.map do |observation|
identifier = observation[:identifier][0][:value]
observation_codes = observation[:component]&.map { |component| component.dig(:code, :coding, 0, :code) }
next if valid_blood_pressure_codes?(observation_codes) || valid_blood_sugar_codes?(observation_codes)
[identifier, observation_codes]
end&.compact&.to_h

if invalid_observations.present?
{
invalid_observation_codes_error: {
invalid_observations: invalid_observations,
message: <<~ERRSTRING
Invalid set of codes for the following identifiers: #{invalid_observations.pretty_inspect}.
For blood pressure observation: Ensure that both systolic and diastolic measurements are present with the correct codes.
For blood sugar observations: Ensure that only one code is present and matching the list of accepted codes."
ERRSTRING
}
}
end
end

def valid_blood_pressure_codes?(codes)
codes.to_set == Api::V4::Imports::ALLOWED_BP_CODES.to_set
end

def valid_blood_sugar_codes?(codes)
codes.count == 1 && Api::V4::Imports::ALLOWED_BS_CODES.include?(codes.first)
end

def validate_facilities
facility_ids = [
*patient_resource_facilities,
Expand Down
85 changes: 85 additions & 0 deletions spec/services/bulk_api_import/validator_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,27 @@
end
end

context "valid resources and valid facility ID, but invalid HTN observation codes" do
it "returns an error for unmapped facility IDs" do
bp_with_two_diastolic_codes = build_observation_import_resource(:blood_pressure).merge(
performer: [{identifier: facility_identifier.identifier}],
component: [
{code: {coding: [system: "http://loinc.org", code: "8462-4"]},
valueQuantity: blood_pressure_value_quantity(:systolic)},
{code: {coding: [system: "http://loinc.org", code: "8462-4"]},
valueQuantity: blood_pressure_value_quantity(:systolic)}
]
)

expect(
described_class.new(
organization: organization.id,
resources: [bp_with_two_diastolic_codes]
).validate
).to have_key(:invalid_observation_codes_error)
end
end

context "invalid resource" do
it "returns a schema error" do
expect(
Expand Down Expand Up @@ -112,6 +133,70 @@
end
end

describe "#validate_observation_codes" do
context "valid non-observation resources" do
it "does not return any error" do
expect(
described_class.new(
organization: organization.id,
resources: [build_medication_request_import_resource]
).validate_observation_codes
).to be_nil
end
end

context "valid observation resources" do
it "does not return any error" do
expect(
described_class.new(
organization: organization.id,
resources: [build_observation_import_resource(:blood_sugar)]
).validate_observation_codes
).to be_nil
end
end

context "invalid observation resources" do
it "returns an error highlighting any unknown codes" do
bp_with_unknown_code = build_observation_import_resource(:blood_pressure).merge(
performer: [{identifier: facility_identifier.identifier}],
component: [
{code: {coding: [system: "http://loinc.org", code: "foo"]},
valueQuantity: blood_pressure_value_quantity(:systolic)},
{code: {coding: [system: "http://loinc.org", code: "8462-4"]},
valueQuantity: blood_pressure_value_quantity(:diastolic)}
]
)

expect(
described_class.new(
organization: organization.id,
resources: [bp_with_unknown_code]
).validate_observation_codes[:invalid_observation_codes_error]
).to include(invalid_observations: {bp_with_unknown_code[:identifier][0][:value] => %w[foo 8462-4]})
end

it "returns an error when an HTN code is duplicated" do
bp_with_duplicate_code = build_observation_import_resource(:blood_pressure).merge(
performer: [{identifier: facility_identifier.identifier}],
component: [
{code: {coding: [system: "http://loinc.org", code: "8480-6"]},
valueQuantity: blood_pressure_value_quantity(:systolic)},
{code: {coding: [system: "http://loinc.org", code: "8480-6"]},
valueQuantity: blood_pressure_value_quantity(:diastolic)}
]
)

expect(
described_class.new(
organization: organization.id,
resources: [bp_with_duplicate_code]
).validate_observation_codes[:invalid_observation_codes_error]
).to include(invalid_observations: {bp_with_duplicate_code[:identifier][0][:value] => %w[8480-6 8480-6]})
end
end
end

describe "resource facility extractors" do
it "extracts facilities from patient resources" do
expect(
Expand Down

0 comments on commit 8b60577

Please sign in to comment.