cqm-validators 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +7 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +201 -0
- data/QRDA_Schematron_License.txt +191 -0
- data/README.md +40 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/cqm_validators.gemspec +28 -0
- data/lib/base_validator.rb +22 -0
- data/lib/cqm_validators.rb +15 -0
- data/lib/cqm_validators/version.rb +3 -0
- data/lib/data_validator.rb +82 -0
- data/lib/measure_validator.rb +130 -0
- data/lib/performance_rate_validator.rb +92 -0
- data/lib/qrda_qdm_template_validator.rb +320 -0
- data/lib/reported_result_extractor.rb +172 -0
- data/lib/schema/infrastructure/cda/CDA_SDTC.xsd +44 -0
- data/lib/schema/infrastructure/cda/POCD_MT000040_SDTC.xsd +1500 -0
- data/lib/schema/infrastructure/cda/SDTC.xsd +210 -0
- data/lib/schema/processable/coreschemas/NarrativeBlock.xsd +557 -0
- data/lib/schema/processable/coreschemas/datatypes-base_SDTC.xsd +1850 -0
- data/lib/schema/processable/coreschemas/datatypes.xsd +1375 -0
- data/lib/schema/processable/coreschemas/infrastructureRoot.xsd +27 -0
- data/lib/schema/processable/coreschemas/voc.xsd +2124 -0
- data/lib/schema_validator.rb +23 -0
- data/lib/schematron/c_processor.rb +26 -0
- data/lib/schematron/iso-schematron-xslt1/ExtractSchFromRNG.xsl +75 -0
- data/lib/schematron/iso-schematron-xslt1/ExtractSchFromXSD.xsl +77 -0
- data/lib/schematron/iso-schematron-xslt1/iso_abstract_expand.xsl +297 -0
- data/lib/schematron/iso-schematron-xslt1/iso_dsdl_include.xsl +1509 -0
- data/lib/schematron/iso-schematron-xslt1/iso_schematron_message.xsl +55 -0
- data/lib/schematron/iso-schematron-xslt1/iso_schematron_skeleton_for_xslt1.xsl +1844 -0
- data/lib/schematron/iso-schematron-xslt1/iso_svrl_for_xslt1.xsl +605 -0
- data/lib/schematron/iso-schematron-xslt1/readme.txt +101 -0
- data/lib/schematron/iso-schematron-xslt1/schematron-skeleton-api.htm +723 -0
- data/lib/schematron/iso-schematron-xslt2/ExtractSchFromRNG-2.xsl +75 -0
- data/lib/schematron/iso-schematron-xslt2/ExtractSchFromXSD-2.xsl +77 -0
- data/lib/schematron/iso-schematron-xslt2/iso_abstract_expand.xsl +297 -0
- data/lib/schematron/iso-schematron-xslt2/iso_dsdl_include.xsl +1508 -0
- data/lib/schematron/iso-schematron-xslt2/iso_schematron_message_xslt2.xsl +55 -0
- data/lib/schematron/iso-schematron-xslt2/iso_schematron_skeleton_for_saxon.xsl +2299 -0
- data/lib/schematron/iso-schematron-xslt2/iso_svrl_for_xslt2.xsl +684 -0
- data/lib/schematron/iso-schematron-xslt2/readme.txt +100 -0
- data/lib/schematron/iso-schematron-xslt2/sch-messages-cs.xhtml +56 -0
- data/lib/schematron/iso-schematron-xslt2/sch-messages-de.xhtml +57 -0
- data/lib/schematron/iso-schematron-xslt2/sch-messages-en.xhtml +57 -0
- data/lib/schematron/iso-schematron-xslt2/sch-messages-fr.xhtml +54 -0
- data/lib/schematron/iso-schematron-xslt2/sch-messages-nl.xhtml +58 -0
- data/lib/schematron/iso-schematron-xslt2/schematron-skeleton-api.htm +723 -0
- data/lib/schematron/java_processor.rb +92 -0
- data/lib/schematron/qrda/cat_1/HL7_CDAR2_QRDA_Category_I_2_12_16.sch +4693 -0
- data/lib/schematron/qrda/cat_1/voc.xml +1177 -0
- data/lib/schematron/qrda/cat_1_r2/QRDA Category I Release 2.sch +4069 -0
- data/lib/schematron/qrda/cat_1_r2/voc.xml +1065 -0
- data/lib/schematron/qrda/cat_1_r3_1/HL7 QRDA Category I STU 3.1.sch +3573 -0
- data/lib/schematron/qrda/cat_1_r3_1/HL7 QRDA Category III STU 1.1.sch +464 -0
- data/lib/schematron/qrda/cat_1_r3_1/QRDA Category I STU Release 3.1.sch +5394 -0
- data/lib/schematron/qrda/cat_1_r3_1/voc.xml +1229 -0
- data/lib/schematron/qrda/cat_1_r4/HL7 QRDA Category I STU 4.sch +3526 -0
- data/lib/schematron/qrda/cat_1_r4/voc.xml +1186 -0
- data/lib/schematron/qrda/cat_1_r5/HL7 QRDA Category I STU 5.sch +3069 -0
- data/lib/schematron/qrda/cat_1_r5/voc.xml +1186 -0
- data/lib/schematron/qrda/cat_3/QRDA Category III.sch +675 -0
- data/lib/schematron/qrda/cat_3/voc.xml +21 -0
- data/lib/schematron/qrda/cat_3_r1_1/HL7 QRDA Category III STU 1.1.sch +528 -0
- data/lib/schematron/qrda/cat_3_r1_1/voc.xml +8 -0
- data/lib/schematron/qrda/cat_3_r2/HL7 QRDA Category III STU 2.sch +677 -0
- data/lib/schematron/qrda/cat_3_r2/voc.xml +1186 -0
- data/lib/schematron/qrda/cat_3_r2_1/HL7 QRDA Category III STU 2.1.sch +678 -0
- data/lib/schematron/qrda/cat_3_r2_1/voc.xml +1186 -0
- data/lib/schematron_validator.rb +38 -0
- data/lib/validation_error.rb +10 -0
- data/lib/validators.rb +136 -0
- metadata +177 -0
data/Rakefile
ADDED
data/bin/console
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require "cqm_validators"
|
|
5
|
+
|
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
|
8
|
+
|
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
10
|
+
# require "pry"
|
|
11
|
+
# Pry.start
|
|
12
|
+
|
|
13
|
+
require "irb"
|
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
|
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require "cqm_validators/version"
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |spec|
|
|
7
|
+
spec.name = "cqm-validators"
|
|
8
|
+
spec.version = CqmValidators::VERSION
|
|
9
|
+
spec.authors = ["Laura", "Michael O'Keefe"]
|
|
10
|
+
spec.email = ["laclark@mitre.org", 'mokeefe@mitre.org']
|
|
11
|
+
spec.license = 'Apache-2.0'
|
|
12
|
+
|
|
13
|
+
spec.summary = "new cqm validator library"
|
|
14
|
+
spec.description = "new cqm validator library"
|
|
15
|
+
spec.homepage = "https://github.com/projecttacoma/cqm-validators"
|
|
16
|
+
|
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
|
18
|
+
f.match(%r{^(test|spec|features)/})
|
|
19
|
+
end
|
|
20
|
+
spec.bindir = "exe"
|
|
21
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
22
|
+
spec.require_paths = ["lib"]
|
|
23
|
+
|
|
24
|
+
spec.add_dependency "nokogiri", "~>1.8.2"
|
|
25
|
+
spec.add_development_dependency "bundler", "~> 1.16"
|
|
26
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
|
27
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
|
28
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module CqmValidators
|
|
2
|
+
module BaseValidator
|
|
3
|
+
|
|
4
|
+
def build_error(msg, loc, file_name)
|
|
5
|
+
ValidationError.new(message: msg, location: loc, file_name: file_name, validator: @name)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def get_document(input)
|
|
9
|
+
doc = case input
|
|
10
|
+
when File
|
|
11
|
+
input.read
|
|
12
|
+
when Nokogiri::XML::Document
|
|
13
|
+
return input
|
|
14
|
+
else
|
|
15
|
+
input
|
|
16
|
+
end
|
|
17
|
+
Nokogiri::XML(doc.to_s) { |conf| conf.strict.nonet.noblanks } #grumble, grumble nokogiri java @SS
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
end
|
|
22
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
require "cqm_validators/version"
|
|
2
|
+
require 'reported_result_extractor'
|
|
3
|
+
require 'base_validator'
|
|
4
|
+
require 'data_validator'
|
|
5
|
+
require 'measure_validator'
|
|
6
|
+
require 'performance_rate_validator'
|
|
7
|
+
require 'qrda_qdm_template_validator'
|
|
8
|
+
require 'schema_validator'
|
|
9
|
+
require 'schematron_validator'
|
|
10
|
+
require 'validators'
|
|
11
|
+
require "schematron/c_processor"
|
|
12
|
+
|
|
13
|
+
module CqmValidators
|
|
14
|
+
# Your code goes here...
|
|
15
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
module CqmValidators
|
|
2
|
+
class DataValidator
|
|
3
|
+
include BaseValidator
|
|
4
|
+
|
|
5
|
+
HL7_QRDA_OIDS = ["2.16.840.1.113883.3.221.5",
|
|
6
|
+
"2.16.840.1.113883.3.88.12.3221.8.7",
|
|
7
|
+
"2.16.840.1.113883.3.88.12.3221.8.9",
|
|
8
|
+
"2.16.840.1.113883.1.11.12839",
|
|
9
|
+
"2.16.840.1.113883.3.88.12.3221.8.11",
|
|
10
|
+
"2.16.840.1.113883.3.88.12.3221.6.2",
|
|
11
|
+
"2.16.840.1.113883.11.20.9.40",
|
|
12
|
+
"2.16.840.1.113883.11.20.9.23",
|
|
13
|
+
"2.16.840.1.113883.3.88.12.3221.7.4",
|
|
14
|
+
"2.16.840.1.113883.11.20.9.18",
|
|
15
|
+
"2.16.840.1.113883.11.20.9.22",
|
|
16
|
+
"2.16.840.1.113883.1.11.16866",
|
|
17
|
+
"2.16.840.1.113883.1.11.20275",
|
|
18
|
+
"2.16.840.1.113883.11.20.9.34",
|
|
19
|
+
"2.16.840.1.113883.3.88.12.3221.7.2",
|
|
20
|
+
"2.16.840.1.113883.3.88.12.80.17",
|
|
21
|
+
"2.16.840.1.113883.3.88.12.80.22",
|
|
22
|
+
"2.16.840.1.113883.3.88.12.80.64",
|
|
23
|
+
"2.16.840.1.113883.3.88.12.3221.6.8",
|
|
24
|
+
"2.16.840.1.113883.1.11.78",
|
|
25
|
+
"2.16.840.1.113883.11.20.9.25",
|
|
26
|
+
"2.16.840.1.113883.11.20.9.39",
|
|
27
|
+
"2.16.840.1.113883.3.88.12.80.32",
|
|
28
|
+
"2.16.840.1.113883.11.20.9.21",
|
|
29
|
+
"2.16.840.1.113883.3.88.12.80.68",
|
|
30
|
+
"2.16.840.1.113883.1.11.20.12",
|
|
31
|
+
"2.16.840.1.113883.11.20.9.24",
|
|
32
|
+
"2.16.840.1.113883.11.20.9.41",
|
|
33
|
+
"2.16.840.1.113883.1.11.16926",
|
|
34
|
+
"2.16.840.1.113883.1.11.12212",
|
|
35
|
+
"2.16.840.1.113883.1.11.19185",
|
|
36
|
+
"2.16.840.1.113883.1.11.14914",
|
|
37
|
+
"2.16.840.1.114222.4.11.837",
|
|
38
|
+
"2.16.840.1.113883.1.11.19563",
|
|
39
|
+
"2.16.840.1.113883.1.11.11526",
|
|
40
|
+
"2.16.840.1.113883.11.20.9.20",
|
|
41
|
+
"2.16.840.1.113883.3.88.12.80.2",
|
|
42
|
+
"2.16.840.1.113883.3.88.12.80.63",
|
|
43
|
+
"2.16.840.1.113883.1.11.12249",
|
|
44
|
+
"2.16.840.1.113883.1.11.1",
|
|
45
|
+
"2.16.840.1.113883.1.11.12199",
|
|
46
|
+
"2.16.840.1.113883.11.20.9.33",
|
|
47
|
+
"2.16.840.1.114222.4.11.1066",
|
|
48
|
+
"2.16.840.1.113883.1.11.19579"]
|
|
49
|
+
|
|
50
|
+
def initialize(bundle, measure_ids)
|
|
51
|
+
@bundle = bundle
|
|
52
|
+
measures = @bundle.measures.in(hqmf_id: measure_ids)
|
|
53
|
+
|
|
54
|
+
@oids = measures.collect{|m| m.oids}.flatten.uniq + HL7_QRDA_OIDS
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def validate(file, options={})
|
|
58
|
+
doc = get_document(file)
|
|
59
|
+
|
|
60
|
+
doc.xpath("//*[@sdtc:valueSet]").inject([]) do |errors, node|
|
|
61
|
+
oid = node.at_xpath("@sdtc:valueSet")
|
|
62
|
+
vs = @bundle.value_sets.where({"oid" => oid}).first
|
|
63
|
+
code = node.at_xpath("@code")
|
|
64
|
+
code_system = node.at_xpath("@codeSystem")
|
|
65
|
+
null_flavor = node.at_xpath("@nullFlavor")
|
|
66
|
+
if !vs
|
|
67
|
+
errors << build_error("The valueset #{oid} declared in the document cannot be found", node.path, options[:file_name])
|
|
68
|
+
elsif !@oids.include?(oid.value)
|
|
69
|
+
errors << build_error("File appears to contain data criteria outside that required by the measures. Valuesets in file not in measures tested #{oid}'",
|
|
70
|
+
node.path, options[:file_name])
|
|
71
|
+
elsif vs.concepts.where({"code" => code, "code_system"=>code_system}).count() == 0
|
|
72
|
+
if !null_flavor
|
|
73
|
+
errors << build_error("The code #{code} in codeSystem #{code_system} cannot be found in the declared valueset #{oid}",
|
|
74
|
+
node.path, options[:file_name])
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
errors
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
module CqmValidators
|
|
2
|
+
class MeasureValidator
|
|
3
|
+
include BaseValidator
|
|
4
|
+
|
|
5
|
+
def initialize(template_oid)
|
|
6
|
+
@template_oid = template_oid
|
|
7
|
+
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def validate(file, data={})
|
|
11
|
+
@errors = []
|
|
12
|
+
@doc = get_document(file)
|
|
13
|
+
@doc.root.add_namespace_definition('cda', 'urn:hl7-org:v3')
|
|
14
|
+
measure_ids = HealthDataStandards::CQM::Measure.all.map(&:hqmf_id)
|
|
15
|
+
doc_measure_ids = @doc.xpath(measure_selector).map(&:value).map(&:upcase)
|
|
16
|
+
#list of all of the set ids in the QRDA
|
|
17
|
+
doc_neutral_ids = @doc.xpath(neutral_measure_selector).map(&:value).map(&:upcase).sort
|
|
18
|
+
#list of all of the setids in the QRDA that are also in the bundle, includes duplicates if code appears twice in document
|
|
19
|
+
bundle_neutral_ids = HealthDataStandards::CQM::Measure.distinct(:hqmf_set_id)
|
|
20
|
+
doc_bundle_neutral_ids = doc_neutral_ids - (doc_neutral_ids - bundle_neutral_ids)
|
|
21
|
+
validate_measure_ids(doc_measure_ids, measure_ids, data)
|
|
22
|
+
validate_set_ids(doc_neutral_ids, doc_bundle_neutral_ids, data)
|
|
23
|
+
if validate_no_repeating_measure_population_ids(data)
|
|
24
|
+
validate_measure_ids_set_ids_usage(doc_bundle_neutral_ids, doc_measure_ids, data)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
@errors
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
#returns true if there are no repeating measures, check to see that the measure id usage is correct
|
|
33
|
+
def validate_no_repeating_measure_population_ids(data={})
|
|
34
|
+
noDuplicateMeasures = true
|
|
35
|
+
doc_population_ids = @doc.xpath(measure_population_selector).map(&:value).map(&:upcase).sort
|
|
36
|
+
duplicates = doc_population_ids.group_by{ |e| e }.select { |k, v| v.size > 1 }.map(&:first)
|
|
37
|
+
duplicates.each do |duplicate|
|
|
38
|
+
begin
|
|
39
|
+
measureId = @doc.xpath(find_measure_node_for_population(duplicate)).at_xpath("cda:reference/cda:externalDocument/cda:id[./@root='2.16.840.1.113883.4.738']/@extension")
|
|
40
|
+
@errors << build_error("Population #{duplicate} for Measure #{measureId.value} reported more than once", "/", data[:file_name])
|
|
41
|
+
rescue
|
|
42
|
+
@errors << build_error("Population #{duplicate} for reported more than once", "/", data[:file_name])
|
|
43
|
+
end
|
|
44
|
+
noDuplicateMeasures = false
|
|
45
|
+
end
|
|
46
|
+
return noDuplicateMeasures
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def validate_measure_ids(doc_measure_ids, measure_ids, data={})
|
|
50
|
+
(doc_measure_ids - measure_ids).map do |hqmf_id|
|
|
51
|
+
@errors << build_error("Invalid HQMF ID Found: #{hqmf_id}", "/", data[:file_name])
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def validate_set_ids(doc_neutral_ids, doc_bundle_neutral_ids, data={})
|
|
56
|
+
#an error will be returned for all of the setids that are in the QRDA that aren't in the bundle
|
|
57
|
+
(doc_neutral_ids - doc_bundle_neutral_ids).map do |hqmf_set_id|
|
|
58
|
+
@errors << build_error("Invalid HQMF Set ID Found: #{hqmf_set_id}", "/", data[:file_name])
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
#does not work if the same measure is reported more than once, nested under the repeating measures test
|
|
63
|
+
def validate_measure_ids_set_ids_usage(doc_bundle_neutral_ids, doc_measure_ids, data={})
|
|
64
|
+
#for each of the setIds that are in the bundle, check that they are for the correct measure id
|
|
65
|
+
entries_start_position = @doc.xpath(first_entry)
|
|
66
|
+
previous = ""
|
|
67
|
+
index = 1
|
|
68
|
+
doc_bundle_neutral_ids.each do |hqmf_set_id|
|
|
69
|
+
#selects the measure id that is in the same entry as the set id
|
|
70
|
+
#iterates through multiple instances of the same setId
|
|
71
|
+
if previous == hqmf_set_id
|
|
72
|
+
index = index + 1
|
|
73
|
+
else
|
|
74
|
+
index = 1
|
|
75
|
+
end
|
|
76
|
+
measure_id_entry = doc_measure_ids[(@doc.xpath(location_of_set_id(hqmf_set_id,index)) - entries_start_position)]
|
|
77
|
+
previous = hqmf_set_id
|
|
78
|
+
#queries database to see if there is a measure with the combindation of setId and measureId
|
|
79
|
+
if HealthDataStandards::CQM::Measure.where(hqmf_id: measure_id_entry, hqmf_set_id: hqmf_set_id).length() == 0
|
|
80
|
+
@errors << build_error("Invalid HQMF Set ID Found: #{hqmf_set_id} for HQMF ID: #{measure_id_entry}", "/", data[:file_name])
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def measure_selector
|
|
86
|
+
"/cda:ClinicalDocument/cda:component/cda:structuredBody/cda:component/cda:section/cda:entry" +
|
|
87
|
+
"/cda:organizer[./cda:templateId[@root='#{@template_oid}']]/cda:reference[@typeCode='REFR']" +
|
|
88
|
+
"/cda:externalDocument[@classCode='DOC']/cda:id[@root='2.16.840.1.113883.4.738']/@extension"
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
#finds all of the setIds in the QRDA document
|
|
92
|
+
def neutral_measure_selector
|
|
93
|
+
"/cda:ClinicalDocument/cda:component/cda:structuredBody/cda:component/cda:section/cda:entry" +
|
|
94
|
+
"/cda:organizer[./cda:templateId[@root='#{@template_oid}']]/cda:reference[@typeCode='REFR']" +
|
|
95
|
+
"/cda:externalDocument[@classCode='DOC']/cda:setId/@root"
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
#finds the node index of the first entry element in the measure template
|
|
99
|
+
def first_entry
|
|
100
|
+
"count(//cda:entry[cda:organizer[./cda:templateId[@root='#{@template_oid}']]" +
|
|
101
|
+
"/cda:reference[@typeCode='REFR']/cda:externalDocument[@classCode='DOC']" +
|
|
102
|
+
"/cda:id[@root='2.16.840.1.113883.4.738']][1]/preceding-sibling::*)+1"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
#finds the node index of the extry that the specified setId is in, index is used if the same setId appears twice
|
|
106
|
+
def location_of_set_id(set_id,index)
|
|
107
|
+
"count(//cda:entry[cda:organizer[./cda:templateId[@root='#{@template_oid}']]" +
|
|
108
|
+
"/cda:reference[@typeCode='REFR']/cda:externalDocument[@classCode='DOC']" +
|
|
109
|
+
"/cda:setId[@root[contains(translate(.,'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLOMNOPQRSTUVWXYZ')" +
|
|
110
|
+
",'#{set_id}')]]][#{index}]/preceding-sibling::*)+1"
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def measure_population_selector
|
|
115
|
+
"/cda:ClinicalDocument/cda:component/cda:structuredBody/cda:component/cda:section/cda:entry" +
|
|
116
|
+
"/cda:organizer[./cda:templateId[@root='2.16.840.1.113883.10.20.27.3.1']]/cda:component" +
|
|
117
|
+
"/cda:observation[./cda:templateId[@root='2.16.840.1.113883.10.20.27.3.5']]/cda:reference" +
|
|
118
|
+
"/cda:externalObservation/cda:id/@root"
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def find_measure_node_for_population(id)
|
|
122
|
+
"/cda:ClinicalDocument/cda:component/cda:structuredBody/cda:component/cda:section/cda:entry" +
|
|
123
|
+
"/cda:organizer[ ./cda:templateId[@root='2.16.840.1.113883.10.20.27.3.1']" +
|
|
124
|
+
"and ./cda:component/cda:observation[./cda:templateId[@root='2.16.840.1.113883.10.20.27.3.5']]/cda:reference" +
|
|
125
|
+
"/cda:externalObservation/cda:id[@root[contains(translate(.,'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLOMNOPQRSTUVWXYZ')" +
|
|
126
|
+
",'#{id.upcase}')]]]"
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
require_relative "reported_result_extractor"
|
|
2
|
+
module CqmValidators
|
|
3
|
+
class PerformanceRateValidator
|
|
4
|
+
include ReportedResultExtractor
|
|
5
|
+
include BaseValidator
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def initialize()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Nothing to see here - Move along
|
|
15
|
+
def validate(file, data = {})
|
|
16
|
+
errorsList = []
|
|
17
|
+
document = get_document(file)
|
|
18
|
+
#grab measure IDs from QRDA file
|
|
19
|
+
measure_ids = document.xpath(measure_selector).map(&:value).map(&:upcase)
|
|
20
|
+
measure_ids.each do |measure_id|
|
|
21
|
+
measures = HealthDataStandards::CQM::Measure.where(id: measure_id)
|
|
22
|
+
measures.each do |measure|
|
|
23
|
+
result_key = measure["population_ids"].dup
|
|
24
|
+
reported_result, errors = extract_results_by_ids(measure['id'], result_key, document)
|
|
25
|
+
#only check performace rate when there is one
|
|
26
|
+
if reported_result['PR'] != nil
|
|
27
|
+
error = check_performance_rates(reported_result, result_key, measure['id'], data)
|
|
28
|
+
if error != nil
|
|
29
|
+
errorsList << error
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
errorsList
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def calculate_performance_rates(reported_result)
|
|
38
|
+
#Just in case a measure does not report these populations
|
|
39
|
+
denex = 0
|
|
40
|
+
denexcep = 0
|
|
41
|
+
denom = 0
|
|
42
|
+
numer = 0
|
|
43
|
+
if reported_result['DENEX'] != nil
|
|
44
|
+
denex = reported_result['DENEX']
|
|
45
|
+
end
|
|
46
|
+
if reported_result['DENEXCEP'] != nil
|
|
47
|
+
denexcep = reported_result['DENEXCEP']
|
|
48
|
+
end
|
|
49
|
+
if reported_result['DENOM'] != nil
|
|
50
|
+
denom = reported_result['DENOM']
|
|
51
|
+
end
|
|
52
|
+
if reported_result['NUMER'] != nil
|
|
53
|
+
numer = reported_result['NUMER']
|
|
54
|
+
end
|
|
55
|
+
denom = denom - denex - denexcep
|
|
56
|
+
pr = 0
|
|
57
|
+
if denom == 0
|
|
58
|
+
pr = "NA"
|
|
59
|
+
else
|
|
60
|
+
pr = numer / denom.to_f
|
|
61
|
+
end
|
|
62
|
+
return pr
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def check_performance_rates(reported_result, population_ids, measure_id, data = {})
|
|
66
|
+
expected = calculate_performance_rates(reported_result)
|
|
67
|
+
_ids = population_ids
|
|
68
|
+
if expected == "NA"
|
|
69
|
+
if reported_result['PR']['nullFlavor'] != "NA"
|
|
70
|
+
return build_error("Reported Performance Rate for Numerator #{_ids['NUMER']} should be NA", "/", data[:file_name])
|
|
71
|
+
end
|
|
72
|
+
else
|
|
73
|
+
if reported_result['PR']['nullFlavor'] == "NA"
|
|
74
|
+
return build_error("Reported Performance Rate for Numerator #{_ids['NUMER']} should not be NA", "/", data[:file_name])
|
|
75
|
+
else
|
|
76
|
+
if (reported_result['PR']['value'].split('.',2).last.size > 6)
|
|
77
|
+
return build_error("Reported Performance Rate SHALL not have a precision greater than .000001 ", "/", data[:file_name])
|
|
78
|
+
elsif (reported_result['PR']['value'].to_f - expected.round(6)).abs > 0.0000001
|
|
79
|
+
return build_error("Reported Performance Rate of #{reported_result['PR']['value']} for Numerator #{_ids['NUMER']} does not match expected value of #{expected.round(6)}.", "/", data[:file_name])
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def measure_selector
|
|
86
|
+
"/cda:ClinicalDocument/cda:component/cda:structuredBody/cda:component/cda:section/cda:entry" +
|
|
87
|
+
"/cda:organizer[./cda:templateId[@root='2.16.840.1.113883.10.20.27.3.1']]/cda:reference[@typeCode='REFR']" +
|
|
88
|
+
"/cda:externalDocument[@classCode='DOC']/cda:id[@root='2.16.840.1.113883.4.738']/@extension"
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
module CqmValidators
|
|
2
|
+
class QrdaQdmTemplateValidator
|
|
3
|
+
include BaseValidator
|
|
4
|
+
|
|
5
|
+
# Hash of templateIds/extensions specified in the Patient Data Section QDM for QRDA R3
|
|
6
|
+
QRDA_CAT_1_R3_QDM_OIDS = {
|
|
7
|
+
'2.16.840.1.113883.10.20.24.3.1' => '2014-12-01',
|
|
8
|
+
'2.16.840.1.113883.10.20.24.3.2' => '2014-12-01',
|
|
9
|
+
'2.16.840.1.113883.10.20.24.3.3' => '2014-12-01',
|
|
10
|
+
'2.16.840.1.113883.10.20.24.3.4' => '2014-12-01',
|
|
11
|
+
'2.16.840.1.113883.10.20.24.3.5' => '2014-12-01',
|
|
12
|
+
'2.16.840.1.113883.10.20.24.3.6' => '2014-12-01',
|
|
13
|
+
'2.16.840.1.113883.10.20.24.3.7' => '2014-12-01',
|
|
14
|
+
'2.16.840.1.113883.10.20.24.3.8' => '2014-12-01',
|
|
15
|
+
'2.16.840.1.113883.10.20.24.3.9' => '2014-12-01',
|
|
16
|
+
'2.16.840.1.113883.10.20.24.3.10' => '2014-12-01',
|
|
17
|
+
'2.16.840.1.113883.10.20.24.3.121' => '2014-12-01',
|
|
18
|
+
'2.16.840.1.113883.10.20.24.3.12' => '2014-12-01',
|
|
19
|
+
'2.16.840.1.113883.10.20.24.3.123' => '2014-12-01',
|
|
20
|
+
'2.16.840.1.113883.10.20.24.3.125' => '2014-12-01',
|
|
21
|
+
'2.16.840.1.113883.10.20.24.3.15' => '2014-12-01',
|
|
22
|
+
'2.16.840.1.113883.10.20.24.3.16' => '2014-12-01',
|
|
23
|
+
'2.16.840.1.113883.10.20.24.3.17' => '2014-12-01',
|
|
24
|
+
'2.16.840.1.113883.10.20.24.3.18' => '2014-12-01',
|
|
25
|
+
'2.16.840.1.113883.10.20.24.3.19' => '2014-12-01',
|
|
26
|
+
'2.16.840.1.113883.10.20.24.3.105' => '2014-12-01',
|
|
27
|
+
'2.16.840.1.113883.10.20.24.3.21' => '2014-12-01',
|
|
28
|
+
'2.16.840.1.113883.10.20.24.3.22' => '2014-12-01',
|
|
29
|
+
'2.16.840.1.113883.10.20.24.3.23' => '2014-12-01',
|
|
30
|
+
'2.16.840.1.113883.10.20.24.3.24' => '2014-12-01',
|
|
31
|
+
'2.16.840.1.113883.10.20.24.3.25' => '2014-12-01',
|
|
32
|
+
'2.16.840.1.113883.10.20.24.3.26' => '2014-12-01',
|
|
33
|
+
'2.16.840.1.113883.10.20.24.3.27' => '2014-12-01',
|
|
34
|
+
'2.16.840.1.113883.10.20.24.3.29' => '2014-12-01',
|
|
35
|
+
'2.16.840.1.113883.10.20.24.3.30' => '2014-12-01',
|
|
36
|
+
'2.16.840.1.113883.10.20.24.3.31' => '2014-12-01',
|
|
37
|
+
'2.16.840.1.113883.10.20.24.3.32' => '2014-12-01',
|
|
38
|
+
'2.16.840.1.113883.10.20.24.3.33' => '2014-12-01',
|
|
39
|
+
'2.16.840.1.113883.10.20.24.3.35' => '2014-12-01',
|
|
40
|
+
'2.16.840.1.113883.10.20.24.3.36' => '2014-12-01',
|
|
41
|
+
'2.16.840.1.113883.10.20.24.3.37' => '2014-12-01',
|
|
42
|
+
'2.16.840.1.113883.10.20.24.3.38' => '2014-12-01',
|
|
43
|
+
'2.16.840.1.113883.10.20.24.3.39' => '2014-12-01',
|
|
44
|
+
'2.16.840.1.113883.10.20.24.3.41' => '2014-12-01',
|
|
45
|
+
'2.16.840.1.113883.10.20.24.3.42' => '2014-12-01',
|
|
46
|
+
'2.16.840.1.113883.10.20.24.3.43' => '2014-12-01',
|
|
47
|
+
'2.16.840.1.113883.10.20.24.3.44' => '2014-12-01',
|
|
48
|
+
'2.16.840.1.113883.10.20.24.3.45' => '2014-12-01',
|
|
49
|
+
'2.16.840.1.113883.10.20.24.3.46' => '2014-12-01',
|
|
50
|
+
'2.16.840.1.113883.10.20.24.3.47' => '2014-12-01',
|
|
51
|
+
'2.16.840.1.113883.10.20.24.3.48' => '2014-12-01',
|
|
52
|
+
'2.16.840.1.113883.10.20.24.3.51' => '2014-12-01',
|
|
53
|
+
'2.16.840.1.113883.10.20.24.3.54' => '2014-12-01',
|
|
54
|
+
'2.16.840.1.113883.10.20.24.3.103' => '2014-12-01',
|
|
55
|
+
'2.16.840.1.113883.10.20.24.3.55' => nil,
|
|
56
|
+
'2.16.840.1.113883.10.20.24.3.58' => '2014-12-01',
|
|
57
|
+
'2.16.840.1.113883.10.20.24.3.59' => '2014-12-01',
|
|
58
|
+
'2.16.840.1.113883.10.20.24.3.60' => '2014-12-01',
|
|
59
|
+
'2.16.840.1.113883.10.20.24.3.61' => '2014-12-01',
|
|
60
|
+
'2.16.840.1.113883.10.20.24.3.62' => '2014-12-01',
|
|
61
|
+
'2.16.840.1.113883.10.20.24.3.63' => '2014-12-01',
|
|
62
|
+
'2.16.840.1.113883.10.20.24.3.64' => '2014-12-01',
|
|
63
|
+
'2.16.840.1.113883.10.20.24.3.65' => '2014-12-01',
|
|
64
|
+
'2.16.840.1.113883.10.20.24.3.67' => '2014-12-01',
|
|
65
|
+
'2.16.840.1.113883.10.20.24.3.114' => '2014-12-01',
|
|
66
|
+
'2.16.840.1.113883.10.20.24.3.69' => '2014-12-01',
|
|
67
|
+
'2.16.840.1.113883.10.20.24.3.75' => '2014-12-01',
|
|
68
|
+
'2.16.840.1.113883.10.20.24.3.120' => '2014-12-01',
|
|
69
|
+
'2.16.840.1.113883.10.20.24.3.127' => '2014-12-01',
|
|
70
|
+
'2.16.840.1.113883.10.20.24.3.122' => '2014-12-01',
|
|
71
|
+
'2.16.840.1.113883.10.20.24.3.124' => '2014-12-01',
|
|
72
|
+
'2.16.840.1.113883.10.20.24.3.81' => '2014-12-01',
|
|
73
|
+
'2.16.840.1.113883.10.20.24.3.82' => '2014-12-01'
|
|
74
|
+
}
|
|
75
|
+
# Hash of templateIds/extensions specified in the Patient Data Section QDM for QRDA R3_1
|
|
76
|
+
QRDA_CAT_1_R3_1_QDM_OIDS = {
|
|
77
|
+
'2.16.840.1.113883.10.20.24.3.1' => '2016-02-01',
|
|
78
|
+
'2.16.840.1.113883.10.20.24.3.2' => '2016-02-01',
|
|
79
|
+
'2.16.840.1.113883.10.20.24.3.3' => '2016-02-01',
|
|
80
|
+
'2.16.840.1.113883.10.20.24.3.4' => '2016-02-01',
|
|
81
|
+
'2.16.840.1.113883.10.20.24.3.5' => '2016-02-01',
|
|
82
|
+
'2.16.840.1.113883.10.20.24.3.6' => '2016-02-01',
|
|
83
|
+
'2.16.840.1.113883.10.20.24.3.7' => '2016-02-01',
|
|
84
|
+
'2.16.840.1.113883.10.20.24.3.8' => '2016-02-01',
|
|
85
|
+
'2.16.840.1.113883.10.20.24.3.130' => nil,
|
|
86
|
+
'2.16.840.1.113883.10.20.24.3.131' => nil,
|
|
87
|
+
'2.16.840.1.113883.10.20.24.3.137' => nil,
|
|
88
|
+
'2.16.840.1.113883.10.20.24.3.15' => '2016-02-01',
|
|
89
|
+
'2.16.840.1.113883.10.20.24.3.16' => '2016-02-01',
|
|
90
|
+
'2.16.840.1.113883.10.20.24.3.17' => '2016-02-01',
|
|
91
|
+
'2.16.840.1.113883.10.20.24.3.18' => '2016-02-01',
|
|
92
|
+
'2.16.840.1.113883.10.20.24.3.19' => '2016-02-01',
|
|
93
|
+
'2.16.840.1.113883.10.20.24.3.105' => '2016-02-01',
|
|
94
|
+
'2.16.840.1.113883.10.20.24.3.21' => '2016-02-01',
|
|
95
|
+
'2.16.840.1.113883.10.20.24.3.132' => nil,
|
|
96
|
+
'2.16.840.1.113883.10.20.24.3.133' => nil,
|
|
97
|
+
'2.16.840.1.113883.10.20.24.3.134' => nil,
|
|
98
|
+
'2.16.840.1.113883.10.20.24.3.12' => '2016-02-01',
|
|
99
|
+
'2.16.840.1.113883.10.20.24.3.25' => '2016-02-01',
|
|
100
|
+
'2.16.840.1.113883.10.20.24.3.26' => '2016-02-01',
|
|
101
|
+
'2.16.840.1.113883.10.20.24.3.27' => '2016-02-01',
|
|
102
|
+
'2.16.840.1.113883.10.20.24.3.140' => nil,
|
|
103
|
+
'2.16.840.1.113883.10.20.24.3.143' => nil,
|
|
104
|
+
'2.16.840.1.113883.10.20.24.3.29' => '2016-02-01',
|
|
105
|
+
'2.16.840.1.113883.10.20.24.3.30' => '2016-02-01',
|
|
106
|
+
'2.16.840.1.113883.10.20.24.3.31' => '2016-02-01',
|
|
107
|
+
'2.16.840.1.113883.10.20.24.3.32' => '2016-02-01',
|
|
108
|
+
'2.16.840.1.113883.10.20.24.3.33' => '2016-02-01',
|
|
109
|
+
'2.16.840.1.113883.10.20.24.3.35' => '2016-02-01',
|
|
110
|
+
'2.16.840.1.113883.10.20.24.3.36' => '2016-02-01',
|
|
111
|
+
'2.16.840.1.113883.10.20.24.3.37' => '2016-02-01',
|
|
112
|
+
'2.16.840.1.113883.10.20.24.3.38' => '2016-02-01',
|
|
113
|
+
'2.16.840.1.113883.10.20.24.3.39' => '2016-02-01',
|
|
114
|
+
'2.16.840.1.113883.10.20.24.3.41' => '2016-02-01',
|
|
115
|
+
'2.16.840.1.113883.10.20.24.3.42' => '2016-02-01',
|
|
116
|
+
'2.16.840.1.113883.10.20.24.3.43' => '2016-02-01',
|
|
117
|
+
'2.16.840.1.113883.10.20.24.3.44' => '2016-02-01',
|
|
118
|
+
'2.16.840.1.113883.10.20.24.3.139' => nil,
|
|
119
|
+
'2.16.840.1.113883.10.20.24.3.46' => '2016-02-01',
|
|
120
|
+
'2.16.840.1.113883.10.20.24.3.47' => '2016-02-01',
|
|
121
|
+
'2.16.840.1.113883.10.20.24.3.48' => '2016-02-01',
|
|
122
|
+
'2.16.840.1.113883.10.20.24.3.51' => '2016-02-01',
|
|
123
|
+
'2.16.840.1.113883.10.20.24.3.54' => '2016-02-01',
|
|
124
|
+
'2.16.840.1.113883.10.20.24.3.103' => '2016-02-01',
|
|
125
|
+
'2.16.840.1.113883.10.20.24.3.58' => '2016-02-01',
|
|
126
|
+
'2.16.840.1.113883.10.20.24.3.59' => '2016-02-01',
|
|
127
|
+
'2.16.840.1.113883.10.20.24.3.55' => nil,
|
|
128
|
+
'2.16.840.1.113883.10.20.24.3.60' => '2016-02-01',
|
|
129
|
+
'2.16.840.1.113883.10.20.24.3.61' => '2016-02-01',
|
|
130
|
+
'2.16.840.1.113883.10.20.24.3.62' => '2016-02-01',
|
|
131
|
+
'2.16.840.1.113883.10.20.24.3.63' => '2016-02-01',
|
|
132
|
+
'2.16.840.1.113883.10.20.24.3.64' => '2016-02-01',
|
|
133
|
+
'2.16.840.1.113883.10.20.24.3.65' => '2016-02-01',
|
|
134
|
+
'2.16.840.1.113883.10.20.24.3.67' => '2016-02-01',
|
|
135
|
+
'2.16.840.1.113883.10.20.24.3.69' => '2016-02-01',
|
|
136
|
+
'2.16.840.1.113883.10.20.24.3.75' => '2016-02-01',
|
|
137
|
+
'2.16.840.1.113883.10.20.24.3.138' => nil,
|
|
138
|
+
'2.16.840.1.113883.10.20.24.3.141' => nil,
|
|
139
|
+
'2.16.840.1.113883.10.20.24.3.142' => nil
|
|
140
|
+
}
|
|
141
|
+
# Hash of templateIds/extensions specified in the Patient Data Section QDM for QRDA R4
|
|
142
|
+
QRDA_CAT_1_R4_QDM_OIDS = {
|
|
143
|
+
'2.16.840.1.113883.10.20.24.3.1' => '2016-02-01',
|
|
144
|
+
'2.16.840.1.113883.10.20.24.3.2' => '2016-02-01',
|
|
145
|
+
'2.16.840.1.113883.10.20.24.3.3' => '2016-02-01',
|
|
146
|
+
'2.16.840.1.113883.10.20.24.3.4' => '2016-02-01',
|
|
147
|
+
'2.16.840.1.113883.10.20.24.3.5' => '2016-02-01',
|
|
148
|
+
'2.16.840.1.113883.10.20.24.3.6' => '2016-02-01',
|
|
149
|
+
'2.16.840.1.113883.10.20.24.3.7' => '2016-02-01',
|
|
150
|
+
'2.16.840.1.113883.10.20.24.3.8' => '2016-02-01',
|
|
151
|
+
'2.16.840.1.113883.10.20.24.3.130' => nil,
|
|
152
|
+
'2.16.840.1.113883.10.20.24.3.131' => nil,
|
|
153
|
+
'2.16.840.1.113883.10.20.24.3.137' => '2016-08-01',
|
|
154
|
+
'2.16.840.1.113883.10.20.24.3.15' => '2016-02-01',
|
|
155
|
+
'2.16.840.1.113883.10.20.24.3.16' => '2016-02-01',
|
|
156
|
+
'2.16.840.1.113883.10.20.24.3.17' => '2016-02-01',
|
|
157
|
+
'2.16.840.1.113883.10.20.24.3.18' => '2016-02-01',
|
|
158
|
+
'2.16.840.1.113883.10.20.24.3.19' => '2016-02-01',
|
|
159
|
+
'2.16.840.1.113883.10.20.24.3.105' => '2016-02-01',
|
|
160
|
+
'2.16.840.1.113883.10.20.24.3.21' => '2016-02-01',
|
|
161
|
+
'2.16.840.1.113883.10.20.24.3.132' => nil,
|
|
162
|
+
'2.16.840.1.113883.10.20.24.3.133' => nil,
|
|
163
|
+
'2.16.840.1.113883.10.20.24.3.134' => nil,
|
|
164
|
+
'2.16.840.1.113883.10.20.24.3.12' => '2016-02-01',
|
|
165
|
+
'2.16.840.1.113883.10.20.24.3.140' => nil,
|
|
166
|
+
'2.16.840.1.113883.10.20.24.3.143' => nil,
|
|
167
|
+
'2.16.840.1.113883.10.20.24.3.29' => '2016-02-01',
|
|
168
|
+
'2.16.840.1.113883.10.20.24.3.30' => '2016-02-01',
|
|
169
|
+
'2.16.840.1.113883.10.20.24.3.31' => '2016-02-01',
|
|
170
|
+
'2.16.840.1.113883.10.20.24.3.32' => '2016-02-01',
|
|
171
|
+
'2.16.840.1.113883.10.20.24.3.33' => '2016-02-01',
|
|
172
|
+
'2.16.840.1.113883.10.20.24.3.35' => '2016-02-01',
|
|
173
|
+
'2.16.840.1.113883.10.20.24.3.36' => '2016-02-01',
|
|
174
|
+
'2.16.840.1.113883.10.20.24.3.37' => '2016-02-01',
|
|
175
|
+
'2.16.840.1.113883.10.20.24.3.38' => '2016-02-01',
|
|
176
|
+
'2.16.840.1.113883.10.20.24.3.39' => '2016-02-01',
|
|
177
|
+
'2.16.840.1.113883.10.20.24.3.41' => '2016-02-01',
|
|
178
|
+
'2.16.840.1.113883.10.20.24.3.42' => '2016-02-01',
|
|
179
|
+
'2.16.840.1.113883.10.20.24.3.43' => '2016-02-01',
|
|
180
|
+
'2.16.840.1.113883.10.20.24.3.44' => '2016-02-01',
|
|
181
|
+
'2.16.840.1.113883.10.20.24.3.139' => nil,
|
|
182
|
+
'2.16.840.1.113883.10.20.24.3.46' => '2016-02-01',
|
|
183
|
+
'2.16.840.1.113883.10.20.24.3.47' => '2016-02-01',
|
|
184
|
+
'2.16.840.1.113883.10.20.24.3.48' => '2016-02-01',
|
|
185
|
+
'2.16.840.1.113883.10.20.24.3.51' => '2016-02-01',
|
|
186
|
+
'2.16.840.1.113883.10.20.24.3.54' => '2016-02-01',
|
|
187
|
+
'2.16.840.1.113883.10.20.24.3.103' => '2016-02-01',
|
|
188
|
+
'2.16.840.1.113883.10.20.24.3.58' => '2016-02-01',
|
|
189
|
+
'2.16.840.1.113883.10.20.24.3.59' => '2016-02-01',
|
|
190
|
+
'2.16.840.1.113883.10.20.24.3.55' => nil,
|
|
191
|
+
'2.16.840.1.113883.10.20.24.3.60' => '2016-02-01',
|
|
192
|
+
'2.16.840.1.113883.10.20.24.3.61' => '2016-02-01',
|
|
193
|
+
'2.16.840.1.113883.10.20.24.3.62' => '2016-02-01',
|
|
194
|
+
'2.16.840.1.113883.10.20.24.3.63' => '2016-02-01',
|
|
195
|
+
'2.16.840.1.113883.10.20.24.3.64' => '2016-02-01',
|
|
196
|
+
'2.16.840.1.113883.10.20.24.3.65' => '2016-02-01',
|
|
197
|
+
'2.16.840.1.113883.10.20.24.3.67' => '2016-02-01',
|
|
198
|
+
'2.16.840.1.113883.10.20.24.3.75' => '2016-02-01',
|
|
199
|
+
'2.16.840.1.113883.10.20.24.3.138' => '2016-08-01',
|
|
200
|
+
'2.16.840.1.113883.10.20.24.3.141' => nil,
|
|
201
|
+
'2.16.840.1.113883.10.20.24.3.142' => nil,
|
|
202
|
+
'2.16.840.1.113883.10.20.24.3.144' => '2016-08-01',
|
|
203
|
+
'2.16.840.1.113883.10.20.24.3.145' => '2016-08-01'
|
|
204
|
+
}
|
|
205
|
+
# Hash of templateIds/extensions specified in the Patient Data Section QDM for QRDA R4
|
|
206
|
+
QRDA_CAT_1_R5_QDM_OIDS = {
|
|
207
|
+
'2.16.840.1.113883.10.20.24.3.1' => '2017-08-01',
|
|
208
|
+
'2.16.840.1.113883.10.20.24.3.2' => '2017-08-01',
|
|
209
|
+
'2.16.840.1.113883.10.20.24.3.3' => '2017-08-01',
|
|
210
|
+
'2.16.840.1.113883.10.20.24.3.4' => '2017-08-01',
|
|
211
|
+
# '2.16.840.1.113883.10.20.24.3.5' => '2016-02-01', Removed
|
|
212
|
+
# '2.16.840.1.113883.10.20.24.3.6' => '2016-02-01', Removed
|
|
213
|
+
'2.16.840.1.113883.10.20.24.3.7' => '2017-08-01',
|
|
214
|
+
# '2.16.840.1.113883.10.20.24.3.8' => '2016-02-01',
|
|
215
|
+
'2.16.840.1.113883.10.20.24.3.130' => '2017-08-01',
|
|
216
|
+
'2.16.840.1.113883.10.20.24.3.131' => '2017-08-01',
|
|
217
|
+
'2.16.840.1.113883.10.20.24.3.137' => '2017-08-01',
|
|
218
|
+
# '2.16.840.1.113883.10.20.24.3.15' => '2016-02-01', Removed
|
|
219
|
+
# '2.16.840.1.113883.10.20.24.3.16' => '2016-02-01', Removed
|
|
220
|
+
'2.16.840.1.113883.10.20.24.3.17' => '2017-08-01',
|
|
221
|
+
'2.16.840.1.113883.10.20.24.3.18' => '2017-08-01',
|
|
222
|
+
'2.16.840.1.113883.10.20.24.3.19' => '2017-08-01',
|
|
223
|
+
'2.16.840.1.113883.10.20.24.3.105' => '2016-02-01',
|
|
224
|
+
# '2.16.840.1.113883.10.20.24.3.21' => '2016-02-01', Removed
|
|
225
|
+
'2.16.840.1.113883.10.20.24.3.132' => '2017-08-01',
|
|
226
|
+
'2.16.840.1.113883.10.20.24.3.133' => '2017-08-01',
|
|
227
|
+
'2.16.840.1.113883.10.20.24.3.134' => '2017-08-01',
|
|
228
|
+
'2.16.840.1.113883.10.20.24.3.12' => '2017-08-01',
|
|
229
|
+
'2.16.840.1.113883.10.20.24.3.140' => '2017-08-01',
|
|
230
|
+
'2.16.840.1.113883.10.20.24.3.143' => '2017-08-01',
|
|
231
|
+
# '2.16.840.1.113883.10.20.24.3.29' => '2016-02-01', Removed
|
|
232
|
+
# '2.16.840.1.113883.10.20.24.3.30' => '2016-02-01', Removed
|
|
233
|
+
'2.16.840.1.113883.10.20.24.3.31' => '2017-08-01',
|
|
234
|
+
'2.16.840.1.113883.10.20.24.3.32' => '2017-08-01',
|
|
235
|
+
'2.16.840.1.113883.10.20.24.3.33' => '2017-08-01',
|
|
236
|
+
# '2.16.840.1.113883.10.20.24.3.35' => '2016-02-01', Removed
|
|
237
|
+
# '2.16.840.1.113883.10.20.24.3.36' => '2016-02-01', Removed
|
|
238
|
+
'2.16.840.1.113883.10.20.24.3.37' => '2017-08-01',
|
|
239
|
+
'2.16.840.1.113883.10.20.24.3.38' => '2017-08-01',
|
|
240
|
+
'2.16.840.1.113883.10.20.24.3.39' => '2017-08-01',
|
|
241
|
+
'2.16.840.1.113883.10.20.24.3.41' => '2017-08-01',
|
|
242
|
+
'2.16.840.1.113883.10.20.24.3.42' => '2017-08-01',
|
|
243
|
+
# '2.16.840.1.113883.10.20.24.3.43' => '2016-02-01', Removed
|
|
244
|
+
# '2.16.840.1.113883.10.20.24.3.44' => '2016-02-01', Removed
|
|
245
|
+
'2.16.840.1.113883.10.20.24.3.139' => '2017-08-01',
|
|
246
|
+
# '2.16.840.1.113883.10.20.24.3.46' => '2016-02-01', Removed
|
|
247
|
+
'2.16.840.1.113883.10.20.24.3.47' => '2017-08-01',
|
|
248
|
+
'2.16.840.1.113883.10.20.24.3.48' => '2017-08-01',
|
|
249
|
+
'2.16.840.1.113883.10.20.24.3.51' => '2017-08-01',
|
|
250
|
+
'2.16.840.1.113883.10.20.24.3.54' => '2016-02-01',
|
|
251
|
+
'2.16.840.1.113883.10.20.24.3.103' => '2017-08-01',
|
|
252
|
+
'2.16.840.1.113883.10.20.24.3.58' => '2017-08-01',
|
|
253
|
+
'2.16.840.1.113883.10.20.24.3.59' => '2017-08-01',
|
|
254
|
+
'2.16.840.1.113883.10.20.24.3.55' => nil,
|
|
255
|
+
'2.16.840.1.113883.10.20.24.3.60' => '2017-08-01',
|
|
256
|
+
# '2.16.840.1.113883.10.20.24.3.61' => '2016-02-01', Removed
|
|
257
|
+
# '2.16.840.1.113883.10.20.24.3.62' => '2016-02-01', Removed
|
|
258
|
+
'2.16.840.1.113883.10.20.24.3.63' => '2017-08-01',
|
|
259
|
+
'2.16.840.1.113883.10.20.24.3.64' => '2017-08-01',
|
|
260
|
+
'2.16.840.1.113883.10.20.24.3.65' => '2017-08-01',
|
|
261
|
+
'2.16.840.1.113883.10.20.24.3.67' => '2017-08-01',
|
|
262
|
+
'2.16.840.1.113883.10.20.24.3.75' => '2017-08-01',
|
|
263
|
+
'2.16.840.1.113883.10.20.24.3.138' => '2017-08-01',
|
|
264
|
+
# '2.16.840.1.113883.10.20.24.3.141' => nil, Removed
|
|
265
|
+
# '2.16.840.1.113883.10.20.24.3.142' => nil, Removed
|
|
266
|
+
'2.16.840.1.113883.10.20.24.3.144' => '2017-08-01',
|
|
267
|
+
'2.16.840.1.113883.10.20.24.3.145' => '2017-08-01',
|
|
268
|
+
'2.16.840.1.113883.10.20.24.3.146' => '2017-08-01',
|
|
269
|
+
'2.16.840.1.113883.10.20.24.3.147' => '2017-08-01',
|
|
270
|
+
'2.16.840.1.113883.10.20.24.3.114' => '2017-08-01',
|
|
271
|
+
'2.16.840.1.113883.10.20.24.3.154' => '2017-08-01'
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
def initialize(qrda_version)
|
|
275
|
+
@name = 'QRDA QDM Template Validator'
|
|
276
|
+
@templateshash = case qrda_version
|
|
277
|
+
when 'r3' then QRDA_CAT_1_R3_QDM_OIDS
|
|
278
|
+
when 'r3_1' then QRDA_CAT_1_R3_1_QDM_OIDS
|
|
279
|
+
when 'r4' then QRDA_CAT_1_R4_QDM_OIDS
|
|
280
|
+
when 'r5' then QRDA_CAT_1_R5_QDM_OIDS
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# Validates that a QRDA Cat I file's Patient Data Section QDM (V3) contains entries that conform
|
|
285
|
+
# to the QDM approach to QRDA. In contrast to a QRDA Framework Patient Data Section that requires
|
|
286
|
+
# but does not specify the structured entries, the Patient Data Section QDM contained entry templates
|
|
287
|
+
# have specific requirements to align the quality measure data element type with its corresponding NQF
|
|
288
|
+
# QDM HQMF pattern, its referenced value set and potential QDM attributes.
|
|
289
|
+
# The result will be an Array of execution errors indicating use of templates that are not valid for the
|
|
290
|
+
# specified QRDA version
|
|
291
|
+
def validate(file, data={})
|
|
292
|
+
@errors = []
|
|
293
|
+
# if validator does not support the qrda version specified, no checks are made
|
|
294
|
+
unless @templateshash.nil?
|
|
295
|
+
@doc = get_document(file)
|
|
296
|
+
@doc.root.add_namespace_definition('cda', 'urn:hl7-org:v3')
|
|
297
|
+
extract_entries.each do |entry|
|
|
298
|
+
# each entry is evaluated separetly.
|
|
299
|
+
entry_value_for_qrda_version(entry, data)
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
@errors
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def entry_value_for_qrda_version(entry, data={})
|
|
306
|
+
# an entry may have multiple templateIds
|
|
307
|
+
tids = entry.xpath('./*/cda:templateId')
|
|
308
|
+
# an entry only needs one valid templateId to be acceptable
|
|
309
|
+
unless tids.map { |tid| @templateshash.has_key?(tid['root']) && @templateshash[tid['root']] == tid['extension'] }.include? true
|
|
310
|
+
msg = "#{tids.map { |tid| "#{tid['root']}:#{tid['extension']}" }} are not valid Patient Data Section QDM entries for this QRDA Version"
|
|
311
|
+
@errors << build_error(msg, entry.path, data[:file_name])
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
# returns a list of the patient data entries
|
|
316
|
+
def extract_entries
|
|
317
|
+
@doc.xpath('//cda:component/cda:section[cda:templateId/@root="2.16.840.1.113883.10.20.24.2.1"]/cda:entry')
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
end
|