Skip to content
This repository has been archived by the owner on Apr 18, 2024. It is now read-only.

Commit

Permalink
Merge pull request #3 from dehall/multiversion
Browse files Browse the repository at this point in the history
Add multiversion support - both dstu2 and stu3 now supported
  • Loading branch information
arscan authored Jun 8, 2017
2 parents daf1d98 + f776bfd commit edb5923
Show file tree
Hide file tree
Showing 25 changed files with 489 additions and 120 deletions.
10 changes: 8 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ PATH
remote: .
specs:
fhir_scorecard (3.0.1)
fhir_dstu2_models
fhir_models (>= 3.0.0)

GEM
Expand All @@ -15,12 +16,17 @@ GEM
coderay (1.1.1)
date_time_precision (0.8.1)
docile (1.1.5)
fhir_dstu2_models (1.0.2)
bcp47 (>= 0.3)
date_time_precision (>= 0.8)
mime-types (>= 1.16, < 3)
nokogiri (>= 1.7)
fhir_models (3.0.1)
bcp47 (>= 0.3)
date_time_precision (>= 0.8)
mime-types (>= 1.16, < 3)
nokogiri (>= 1.6)
i18n (0.8.1)
i18n (0.8.4)
json (2.0.3)
method_source (0.8.2)
mime-types (2.99.3)
Expand All @@ -31,7 +37,7 @@ GEM
builder
minitest (>= 5.0)
ruby-progressbar
nokogiri (1.7.1)
nokogiri (1.7.2)
mini_portile2 (~> 2.1.0)
pry (0.10.4)
coderay (~> 1.1.0)
Expand Down
1 change: 1 addition & 0 deletions fhir_scorecard.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ Gem::Specification.new do |s|
s.files = s.files = `git ls-files`.split("\n")

s.add_runtime_dependency 'fhir_models', '>= 3.0.0'
s.add_runtime_dependency 'fhir_dstu2_models'
end
21 changes: 21 additions & 0 deletions lib/fhir_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module FHIR
# check whether the given object is an instance of any version of the given resource type
# if you only care about a single version, don't use this, just use x.is_a?(FHIR::Whatever)
def self.is_any_version?(object, resource_type)
# resource type should be a string
# TODO: maybe expand this to accept a map, ex { dstu2: 'MedicationOrder', stu3: 'MedicationRequest' }

classnames = ["FHIR::#{resource_type}", "FHIR::DSTU2::#{resource_type}"]

classnames.any? do |classname|
klass = Object.const_defined?(classname) && Object.const_get(classname) # get the class for the name
# note that there may not be a class with that name, in which case const_defined => false
klass && object.is_a?(klass)
end
end

# shorter version for this one class that is used all over the place
def self.is_model?(object)
object.is_a?(FHIR::Model) || object.is_a?(FHIR::DSTU2::Model)
end
end
2 changes: 2 additions & 0 deletions lib/fhir_scorecard.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
# Top level include file that brings in all the necessary code
require 'bundler/setup'
require 'fhir_models'
require 'fhir_dstu2_models'

root = File.expand_path '..', File.dirname(File.absolute_path(__FILE__))

require File.join(root,'lib','scorecard.rb')
require File.join(root,'lib','terminology.rb')
require File.join(root,'lib','implementation_guides.rb')
require File.join(root,'lib','rubrics.rb')
require File.join(root,'lib','fhir_helper.rb')

Dir.glob(File.join(root, 'lib','rubrics','**','*.rb')).each do |file|
require file
Expand Down
8 changes: 4 additions & 4 deletions lib/rubrics/codes_exist.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ def self.check_metadata(fhir_model)
value = fhir_model.instance_variable_get("@#{field_name}")
if !value.nil?
if value.is_a?(Array)
value.each{|v| results.merge!(check_metadata(v)){|k,a,b|a+b} if v.is_a?(FHIR::Model)}
value.each{|v| results.merge!(check_metadata(v)){ |k,a,b|a+b} if FHIR.is_model?(v) }
else # not an Array
results.merge!(check_metadata(value)){|k,a,b|a+b} if value.is_a?(FHIR::Model)
results.merge!(check_metadata(value)){|k,a,b|a+b} if FHIR.is_model?(value)
end
end
end
Expand All @@ -60,9 +60,9 @@ def self.type_okay(type,item)
when 'code'
item.is_a?(String)
when 'Coding'
item.is_a?(FHIR::Coding) && !item.code.nil? && !item.system.nil?
FHIR.is_any_version?(item, 'Coding') && !item.code.nil? && !item.system.nil?
when 'CodeableConcept'
item.is_a?(FHIR::CodeableConcept) && item.coding.any?{|c| c.is_a?(FHIR::Coding) && !c.code.nil? && !c.system.nil?}
FHIR.is_any_version?(item, 'CodeableConcept') && item.coding.any?{ |c| FHIR.is_any_version?(c, 'Coding') && !c.code.nil? && !c.system.nil? }
else
false
end
Expand Down
4 changes: 2 additions & 2 deletions lib/rubrics/codes_umls.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ def self.check_fields(fhir_model)
end
elsif !value.nil?
if value.is_a?(Array)
value.each{|v| results.merge!(check_fields(v)){|k,a,b|a+b} if v.is_a?(FHIR::Model)}
value.each{|v| results.merge!(check_fields(v)){|k,a,b|a+b} if FHIR.is_model?(v) }
else # not an Array
results.merge!(check_fields(value)){|k,a,b|a+b} if value.is_a?(FHIR::Model)
results.merge!(check_fields(value)){|k,a,b|a+b} if FHIR.is_model?(value)
end
end
end
Expand Down
4 changes: 2 additions & 2 deletions lib/rubrics/codes_umls_preferred_descriptions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ def self.check_fields(fhir_model)
end
elsif !value.nil?
if value.is_a?(Array)
value.each{|v| results.merge!(check_fields(v)){|k,a,b|a+b} if v.is_a?(FHIR::Model)}
value.each{|v| results.merge!(check_fields(v)){|k,a,b|a+b} if FHIR.is_model?(v)}
else # not an Array
results.merge!(check_fields(value)){|k,a,b|a+b} if value.is_a?(FHIR::Model)
results.merge!(check_fields(value)){|k,a,b|a+b} if FHIR.is_model?(value)
end
end
end
Expand Down
12 changes: 9 additions & 3 deletions lib/rubrics/completeness.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,20 @@ class Completeness < FHIR::Rubrics
'QuestionnaireResponse','Coverage'
]

MEDICATIONS = [ 'MedicationStatement','MedicationDispense','MedicationAdministration','MedicationRequest' ]
COMMON_MEDICATIONS = [ 'MedicationStatement','MedicationDispense','MedicationAdministration']

DSTU2_MEDICATIONS = COMMON_MEDICATIONS + ['MedicationOrder']

STU3_MEDICATIONS = COMMON_MEDICATIONS + ['MedicationRequest']

# A Patient Record is not complete without certain required items and medications.
rubric :completeness do |record|

curr_version_meds = record.is_a?(FHIR::Model) ? STU3_MEDICATIONS : DSTU2_MEDICATIONS

missing_required = REQUIRED.clone
missing_expected = EXPECTED.clone
missing_meds = MEDICATIONS.clone
missing_meds = curr_version_meds.clone

resources = record.entry.map{|e|e.resource}
resources.each do |resource|
Expand All @@ -32,7 +38,7 @@ class Completeness < FHIR::Rubrics

# 15 points for required resources
numerator = (REQUIRED.length.to_f - missing_required.length.to_f)
numerator += 1 if (missing_meds.length < MEDICATIONS.length)
numerator += 1 if (missing_meds.length < curr_version_meds.length)
denominator = REQUIRED.length.to_f + 1.0 # add one for medications
percentage_required = ( numerator / denominator )
percentage_required = 0.0 if percentage_required.nan?
Expand Down
2 changes: 1 addition & 1 deletion lib/rubrics/cvx_immunizations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class CVXImmunizations < FHIR::Rubrics

resources = record.entry.map{|e|e.resource}
resources.each do |resource|
if resource.is_a?(FHIR::Immunization)
if FHIR.is_any_version?(resource, 'Immunization')
results[:eligible_fields] += 1
results[:validated_fields] += 1 if cvx?(resource.vaccineCode)
end
Expand Down
17 changes: 9 additions & 8 deletions lib/rubrics/cvx_meds.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,20 @@ class CVXMeds < FHIR::Rubrics
# Medication.code (CodeableConcept)
# MedicationAdministration.medicationCodeableConcept / medicationReference
# MedicationDispense.medicationCodeableConcept / medicationReference
# MedicationRequest.medicationCodeableConcept / medicationReference
# MedicationRequest.medicationCodeableConcept / medicationReference -- in STU3
# MedicationOrder.medicationCodeableConcept / medicationReference -- in DSTU2
# MedicationStatement.medicationCodeableConcept / medicationReference

resources = record.entry.map{|e|e.resource}
resources.each do |resource|
if resource.is_a?(FHIR::Medication)
if FHIR.is_any_version?(resource, 'Medication')
results[:eligible_fields] += 1
results[:validated_fields] += 1 if cvx?(resource.code)
elsif (
resource.is_a?(FHIR::MedicationRequest) ||
resource.is_a?(FHIR::MedicationDispense) ||
resource.is_a?(FHIR::MedicationAdministration) ||
resource.is_a?(FHIR::MedicationStatement) )
resource.is_a?(FHIR::MedicationRequest) || resource.is_a?(FHIR::DSTU2::MedicationOrder) ||
FHIR.is_any_version?(resource, 'MedicationDispense') ||
FHIR.is_any_version?(resource, 'MedicationAdministration') ||
FHIR.is_any_version?(resource, 'MedicationStatement') )
if resource.medicationCodeableConcept
results[:eligible_fields] += 1
results[:validated_fields] += 1 if cvx?(resource.medicationCodeableConcept)
Expand All @@ -48,11 +49,11 @@ def self.cvx?(codeableconcept)
def self.local_cvx_reference?(reference,record,contained)
if contained && reference.reference && reference.reference.start_with?('#')
contained.each do |resource|
return true if resource.is_a?(FHIR::Medication) && reference.reference[1..-1]==resource.id
return true if FHIR.is_any_version?(resource, 'Medication') && reference.reference[1..-1]==resource.id
end
end
record.entry.each do |entry|
if entry.resource.is_a?(FHIR::Medication) && reference_matchs?(reference,entry)
if FHIR.is_any_version?(entry.resource, 'Medication') && reference_matchs?(reference,entry)
return true
end
end
Expand Down
4 changes: 2 additions & 2 deletions lib/rubrics/datetimes_iso8601.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ def self.check_metadata(fhir_model)
value = fhir_model.instance_variable_get("@#{field_name}")
if !value.nil?
if value.is_a?(Array)
value.each{|v| results.merge!(check_metadata(v)){|k,a,b|a+b} if v.is_a?(FHIR::Model)}
value.each{|v| results.merge!(check_metadata(v)){ |k,a,b| a + b } if FHIR.is_model?(v) }
else # not an Array
results.merge!(check_metadata(value)){|k,a,b|a+b} if value.is_a?(FHIR::Model)
results.merge!(check_metadata(value)){ |k,a,b| a + b } if FHIR.is_model?(value)
end
end
end
Expand Down
4 changes: 2 additions & 2 deletions lib/rubrics/labs_loinc.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ class LabsLoinc < FHIR::Rubrics
}
resources = record.entry.map{|e|e.resource}
resources.each do |resource|
if resource.is_a?(FHIR::DiagnosticReport) || resource.is_a?(FHIR::Observation)
if FHIR.is_any_version?(resource, 'DiagnosticReport') || FHIR.is_any_version?(resource, 'Observation')
results[:eligible_fields] += 1
if resource.code
results[:validated_fields] += 1 if resource.code.coding.any?{|x| x.system=='http://loinc.org' && FHIR::Terminology.is_top_lab_code?(x.code)}
end
end
if resource.is_a?(FHIR::Observation)
if FHIR.is_any_version?(resource, 'Observation')
if resource.component
resource.component.each do |comp|
results[:eligible_fields] += 1
Expand Down
6 changes: 4 additions & 2 deletions lib/rubrics/quantities_ucum.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ class QuantitiesUcum < FHIR::Rubrics
'TestScript','Task','StructureDefinition','SearchParameter','Questionnaire','QuestionnaireResponse','Parameters','OperationDefinition',
'Group','ExplanationOfBenefit','Contract','Conformance','Claim','ActivityDefinition'
]
# Note: Task and ActivityDefinition don't exist in DSTU2

CHECK = [
'VisionPrescription','SupplyDelivery','Substance','Specimen','Sequence','Observation','NutritionRequest',
'MedicationStatement','MedicationRequest','MedicationDispense','MedicationAdministration','Medication','Immunization','CarePlan'
'VisionPrescription','SupplyDelivery','Substance','Specimen','Sequence','Observation','NutritionOrder',
'MedicationStatement','MedicationRequest','MedicationOrder','MedicationDispense','MedicationAdministration','Medication','Immunization','CarePlan'
]
# Note: MedicationRequest renamed to MedicationOrder between 2->3; Sequence doesn't exist in DSTU2

# Physical quantities should use UCUM
rubric :ucum_quantities do |record|
Expand Down
4 changes: 2 additions & 2 deletions lib/rubrics/references_resolve.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ def self.check_metadata(fhir_model,record)
value = fhir_model.instance_variable_get("@#{field_name}")
if !value.nil?
if value.is_a?(Array)
value.each{|v| results.merge!(check_metadata(v,record)){|k,a,b|a+b} if v.is_a?(FHIR::Model)}
value.each{ |v| results.merge!(check_metadata(v, record)){ |k,a,b| a + b } if FHIR.is_model?(v) }
else # not an Array
results.merge!(check_metadata(value,record)){|k,a,b|a+b} if value.is_a?(FHIR::Model)
results.merge!(check_metadata(value, record)){ |k,a,b| a + b } if FHIR.is_model?(value)
end
end
end
Expand Down
17 changes: 9 additions & 8 deletions lib/rubrics/rxnorm_meds.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,20 @@ class RxNormMeds < FHIR::Rubrics
# Medication.code (CodeableConcept)
# MedicationAdministration.medicationCodeableConcept / medicationReference
# MedicationDispense.medicationCodeableConcept / medicationReference
# MedicationRequest.medicationCodeableConcept / medicationReference
# MedicationRequest.medicationCodeableConcept / medicationReference -- in STU3
# MedicationOrder.medicationCodeableConcept / medicationReference -- in DSTU2
# MedicationStatement.medicationCodeableConcept / medicationReference

resources = record.entry.map{|e|e.resource}
resources.each do |resource|
if resource.is_a?(FHIR::Medication)
if FHIR.is_any_version?(resource, 'Medication')
results[:eligible_fields] += 1
results[:validated_fields] += 1 if rxnorm?(resource.code)
elsif (
resource.is_a?(FHIR::MedicationRequest) ||
resource.is_a?(FHIR::MedicationDispense) ||
resource.is_a?(FHIR::MedicationAdministration) ||
resource.is_a?(FHIR::MedicationStatement) )
resource.is_a?(FHIR::MedicationRequest) || resource.is_a?(FHIR::DSTU2::MedicationOrder) ||
FHIR.is_any_version?(resource, 'MedicationDispense') ||
FHIR.is_any_version?(resource, 'MedicationAdministration') ||
FHIR.is_any_version?(resource, 'MedicationStatement') )
if resource.medicationCodeableConcept
results[:eligible_fields] += 1
results[:validated_fields] += 1 if rxnorm?(resource.medicationCodeableConcept)
Expand All @@ -48,11 +49,11 @@ def self.rxnorm?(codeableconcept)
def self.local_rxnorm_reference?(reference,record,contained)
if contained && reference.reference && reference.reference.start_with?('#')
contained.each do |resource|
return true if resource.is_a?(FHIR::Medication) && reference.reference[1..-1]==resource.id
return true if FHIR.is_any_version?(resource, 'Medication') && reference.reference[1..-1]==resource.id
end
end
record.entry.each do |entry|
if entry.resource.is_a?(FHIR::Medication) && reference_matchs?(reference,entry)
if FHIR.is_any_version?(entry.resource, 'Medication') && reference_matchs?(reference,entry)
return true
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/rubrics/smoking_status.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class SmokingStatus < FHIR::Rubrics

resources = record.entry.map{|e|e.resource}
has_smoking_status = resources.any? do |resource|
resource.is_a?(FHIR::Observation) && smoking_observation?(resource)
FHIR.is_any_version?(resource, 'Observation') && smoking_observation?(resource)
end

if has_smoking_status
Expand Down
2 changes: 1 addition & 1 deletion lib/rubrics/snomed_conditions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class SnomedConditions < FHIR::Rubrics

resources = record.entry.map{|e|e.resource}
resources.each do |resource|
if resource.is_a?(FHIR::Condition)
if FHIR.is_any_version?(resource, 'Condition')
results[:eligible_fields] += 1
results[:validated_fields] += 1 if snomed_core?(resource.code)
end
Expand Down
2 changes: 1 addition & 1 deletion lib/rubrics/vital_signs.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class VitalSigns < FHIR::Rubrics

resources = record.entry.map{|e|e.resource}
resources.each do |resource|
if resource.is_a?(FHIR::Observation)
if FHIR.is_any_version?(resource, 'Observation')
found_code = get_vital_code(resource.code)
components = not_found[ found_code ]
if components.nil?
Expand Down
Loading

0 comments on commit edb5923

Please sign in to comment.