cqm-validators 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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