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.
Files changed (76) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +7 -0
  3. data/Gemfile +6 -0
  4. data/LICENSE.txt +201 -0
  5. data/QRDA_Schematron_License.txt +191 -0
  6. data/README.md +40 -0
  7. data/Rakefile +10 -0
  8. data/bin/console +14 -0
  9. data/bin/setup +8 -0
  10. data/cqm_validators.gemspec +28 -0
  11. data/lib/base_validator.rb +22 -0
  12. data/lib/cqm_validators.rb +15 -0
  13. data/lib/cqm_validators/version.rb +3 -0
  14. data/lib/data_validator.rb +82 -0
  15. data/lib/measure_validator.rb +130 -0
  16. data/lib/performance_rate_validator.rb +92 -0
  17. data/lib/qrda_qdm_template_validator.rb +320 -0
  18. data/lib/reported_result_extractor.rb +172 -0
  19. data/lib/schema/infrastructure/cda/CDA_SDTC.xsd +44 -0
  20. data/lib/schema/infrastructure/cda/POCD_MT000040_SDTC.xsd +1500 -0
  21. data/lib/schema/infrastructure/cda/SDTC.xsd +210 -0
  22. data/lib/schema/processable/coreschemas/NarrativeBlock.xsd +557 -0
  23. data/lib/schema/processable/coreschemas/datatypes-base_SDTC.xsd +1850 -0
  24. data/lib/schema/processable/coreschemas/datatypes.xsd +1375 -0
  25. data/lib/schema/processable/coreschemas/infrastructureRoot.xsd +27 -0
  26. data/lib/schema/processable/coreschemas/voc.xsd +2124 -0
  27. data/lib/schema_validator.rb +23 -0
  28. data/lib/schematron/c_processor.rb +26 -0
  29. data/lib/schematron/iso-schematron-xslt1/ExtractSchFromRNG.xsl +75 -0
  30. data/lib/schematron/iso-schematron-xslt1/ExtractSchFromXSD.xsl +77 -0
  31. data/lib/schematron/iso-schematron-xslt1/iso_abstract_expand.xsl +297 -0
  32. data/lib/schematron/iso-schematron-xslt1/iso_dsdl_include.xsl +1509 -0
  33. data/lib/schematron/iso-schematron-xslt1/iso_schematron_message.xsl +55 -0
  34. data/lib/schematron/iso-schematron-xslt1/iso_schematron_skeleton_for_xslt1.xsl +1844 -0
  35. data/lib/schematron/iso-schematron-xslt1/iso_svrl_for_xslt1.xsl +605 -0
  36. data/lib/schematron/iso-schematron-xslt1/readme.txt +101 -0
  37. data/lib/schematron/iso-schematron-xslt1/schematron-skeleton-api.htm +723 -0
  38. data/lib/schematron/iso-schematron-xslt2/ExtractSchFromRNG-2.xsl +75 -0
  39. data/lib/schematron/iso-schematron-xslt2/ExtractSchFromXSD-2.xsl +77 -0
  40. data/lib/schematron/iso-schematron-xslt2/iso_abstract_expand.xsl +297 -0
  41. data/lib/schematron/iso-schematron-xslt2/iso_dsdl_include.xsl +1508 -0
  42. data/lib/schematron/iso-schematron-xslt2/iso_schematron_message_xslt2.xsl +55 -0
  43. data/lib/schematron/iso-schematron-xslt2/iso_schematron_skeleton_for_saxon.xsl +2299 -0
  44. data/lib/schematron/iso-schematron-xslt2/iso_svrl_for_xslt2.xsl +684 -0
  45. data/lib/schematron/iso-schematron-xslt2/readme.txt +100 -0
  46. data/lib/schematron/iso-schematron-xslt2/sch-messages-cs.xhtml +56 -0
  47. data/lib/schematron/iso-schematron-xslt2/sch-messages-de.xhtml +57 -0
  48. data/lib/schematron/iso-schematron-xslt2/sch-messages-en.xhtml +57 -0
  49. data/lib/schematron/iso-schematron-xslt2/sch-messages-fr.xhtml +54 -0
  50. data/lib/schematron/iso-schematron-xslt2/sch-messages-nl.xhtml +58 -0
  51. data/lib/schematron/iso-schematron-xslt2/schematron-skeleton-api.htm +723 -0
  52. data/lib/schematron/java_processor.rb +92 -0
  53. data/lib/schematron/qrda/cat_1/HL7_CDAR2_QRDA_Category_I_2_12_16.sch +4693 -0
  54. data/lib/schematron/qrda/cat_1/voc.xml +1177 -0
  55. data/lib/schematron/qrda/cat_1_r2/QRDA Category I Release 2.sch +4069 -0
  56. data/lib/schematron/qrda/cat_1_r2/voc.xml +1065 -0
  57. data/lib/schematron/qrda/cat_1_r3_1/HL7 QRDA Category I STU 3.1.sch +3573 -0
  58. data/lib/schematron/qrda/cat_1_r3_1/HL7 QRDA Category III STU 1.1.sch +464 -0
  59. data/lib/schematron/qrda/cat_1_r3_1/QRDA Category I STU Release 3.1.sch +5394 -0
  60. data/lib/schematron/qrda/cat_1_r3_1/voc.xml +1229 -0
  61. data/lib/schematron/qrda/cat_1_r4/HL7 QRDA Category I STU 4.sch +3526 -0
  62. data/lib/schematron/qrda/cat_1_r4/voc.xml +1186 -0
  63. data/lib/schematron/qrda/cat_1_r5/HL7 QRDA Category I STU 5.sch +3069 -0
  64. data/lib/schematron/qrda/cat_1_r5/voc.xml +1186 -0
  65. data/lib/schematron/qrda/cat_3/QRDA Category III.sch +675 -0
  66. data/lib/schematron/qrda/cat_3/voc.xml +21 -0
  67. data/lib/schematron/qrda/cat_3_r1_1/HL7 QRDA Category III STU 1.1.sch +528 -0
  68. data/lib/schematron/qrda/cat_3_r1_1/voc.xml +8 -0
  69. data/lib/schematron/qrda/cat_3_r2/HL7 QRDA Category III STU 2.sch +677 -0
  70. data/lib/schematron/qrda/cat_3_r2/voc.xml +1186 -0
  71. data/lib/schematron/qrda/cat_3_r2_1/HL7 QRDA Category III STU 2.1.sch +678 -0
  72. data/lib/schematron/qrda/cat_3_r2_1/voc.xml +1186 -0
  73. data/lib/schematron_validator.rb +38 -0
  74. data/lib/validation_error.rb +10 -0
  75. data/lib/validators.rb +136 -0
  76. metadata +177 -0
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ end
9
+
10
+ task :default => :test
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,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -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,3 @@
1
+ module CqmValidators
2
+ VERSION = "0.1.0"
3
+ 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