cqm-reports 2.0.5 → 2.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: dd0b9911f4358fceaa323afe6557281f181c2357
4
- data.tar.gz: 2c2ba002d028ff2870fb7d6622231984611d0b9d
3
+ metadata.gz: 6af15ff075a9533c61c844f95c5adc741e999dec
4
+ data.tar.gz: 2cf3f0aeb6c3e1744e5e2f8a45a8d0e90a1ab5a1
5
5
  SHA512:
6
- metadata.gz: 7980f689cd77ad9e2ba0869f89abdd1867a065108cbe8b9e534c7547de51fe3ded3169fc67e49826240bfe821e7bba779e0a1dc02a34955505e26afb6197e396
7
- data.tar.gz: d37767e44596b127a5ea5197cb9c18dcdb36450661c23817ca84366e9a04ab7eebe4744d4e26e69682c76f4dd82bcd237667d46138cb4ec32f85a2b710b0df83
6
+ metadata.gz: a4ab0c6c763b00dfa1c7985110969e6262ecc977d9a6509ca64c82e3979e6fd6e27f1bd888ae05ac5eaeecf600645a7cfea95d7013f8a5b01d1b85e865d80f52
7
+ data.tar.gz: fdfe64bbfb95f37e11dae78380147076daeb9b155a05eadb663c063191ffb9155390af0ae32a6f9643d87d852d605a87cb6fd48c4dc1cf0c9f631a7f929c4538
data/Gemfile CHANGED
@@ -28,6 +28,5 @@ group :test do
28
28
  gem 'minitest', '~> 5.3'
29
29
  gem 'minitest-reporters'
30
30
  gem 'awesome_print', :require => 'ap'
31
- gem 'cqm-validators'
32
31
  gem 'nokogiri-diff'
33
32
  end
data/README.md CHANGED
@@ -18,6 +18,33 @@ Starting with version **2.0.0** released on 6/20/2019, cqm-reports versioning ha
18
18
 
19
19
  For the versions available, see [tags on this repository](https://github.com/projecttacoma/cqm-validators/tags).
20
20
 
21
+ =======
22
+ Importing QRDA
23
+ ==========
24
+
25
+ A QRDA document can be imported into a CQM::Patient (defined in [cqm-models](https://github.com/projecttacoma/cqm-models)) using the following commands.
26
+
27
+ doc = Nokogiri::XML(file)
28
+ patient, warnings = QRDA::Cat1::PatientImporter.instance.parse_cat1(doc)
29
+
30
+ Exporting QRDA Category I
31
+ ==========
32
+
33
+ Exporting a QRDA document from a CQM::Patient (defined in [cqm-models](https://github.com/projecttacoma/cqm-models)) using the following command.
34
+
35
+ Qrda1R5.new(patient, measures, options).render
36
+ * patient is a [CQM::Patient](https://github.com/projecttacoma/cqm-models/blob/master/app/models/cqm/patient.rb)
37
+ * measures is an array of [CQM::Measure](https://github.com/projecttacoma/cqm-models/blob/master/app/models/cqm/measure.rb)
38
+ * options is a hash that can be used to pass in:
39
+ * provider
40
+ * patient_addresses
41
+ * patient_telecoms
42
+ * start_time
43
+ * end_time
44
+ * submission_program
45
+
46
+ QRDA export requires the [mustache](https://github.com/mustache/mustache) gem
47
+
21
48
  ## License
22
49
 
23
50
  Copyright 2019 The MITRE Corporation
@@ -18,7 +18,6 @@ require_relative 'html-export/qdm-patient/qdm_patient.rb'
18
18
  require_relative 'qrda-export/catI-r5/qrda1_r5.rb'
19
19
  require_relative 'qrda-export/catIII-r2-1/qrda3_r21.rb'
20
20
 
21
- require_relative 'qrda-import/entry_package.rb'
22
21
  require_relative 'qrda-import/cda_identifier.rb'
23
22
  require_relative 'qrda-import/narrative_reference_handler.rb'
24
23
  require_relative 'qrda-import/entry_finder.rb'
@@ -6,9 +6,9 @@
6
6
  <!-- QRDA templateId -->
7
7
  <templateId root="2.16.840.1.113883.10.20.24.1.1" extension="2017-08-01"/>
8
8
  <!-- QDM-based QRDA templateId -->
9
- <templateId root="2.16.840.1.113883.10.20.24.1.2" extension="2017-08-01"/>
9
+ <templateId root="2.16.840.1.113883.10.20.24.1.2" extension="2018-10-01"/>
10
10
  <!-- CMS QRDA templateId -->
11
- <templateId root="2.16.840.1.113883.10.20.24.1.3" extension="2018-02-01"/>
11
+ <templateId root="2.16.840.1.113883.10.20.24.1.3" extension="2019-02-01"/>
12
12
  <!-- This is the globally unique identifier for this QRDA document -->
13
13
  <id root="{{random_id}}"/>
14
14
  <!-- QRDA document type code -->
@@ -12,8 +12,8 @@
12
12
  <!-- This is the templateId for Patient Data section -->
13
13
  <templateId root="2.16.840.1.113883.10.20.17.2.4"/>
14
14
  <!-- This is the templateId for Patient Data QDM section -->
15
- <templateId extension="2017-08-01" root="2.16.840.1.113883.10.20.24.2.1"/>
16
- <templateId extension="2018-02-01" root="2.16.840.1.113883.10.20.24.2.1.1"/>
15
+ <templateId extension="2018-10-01" root="2.16.840.1.113883.10.20.24.2.1"/>
16
+ <templateId extension="2019-02-01" root="2.16.840.1.113883.10.20.24.2.1.1"/>
17
17
  <code code="55188-7" codeSystem="2.16.840.1.113883.6.1"/>
18
18
  <title>Patient Data</title>
19
19
  <text/>
@@ -13,11 +13,33 @@ class Qrda1R5 < Mustache
13
13
  @qdmPatient = patient.qdmPatient
14
14
  @measures = measures
15
15
  @provider = options[:provider]
16
+ @patient_address_option = options[:patient_addresses]
17
+ @patient_telecom_option = options[:patient_telecoms]
16
18
  @performance_period_start = options[:start_time]
17
19
  @performance_period_end = options[:end_time]
18
20
  @submission_program = options[:submission_program]
19
21
  end
20
22
 
23
+ def patient_addresses
24
+ @patient_address_option ||= [CQM::Address.new(
25
+ use: 'HP',
26
+ street: ['202 Burlington Rd.'],
27
+ city: 'Bedford',
28
+ state: 'MA',
29
+ zip: '01730',
30
+ country: 'US'
31
+ )]
32
+ JSON.parse(@patient_address_option.to_json)
33
+ end
34
+
35
+ def patient_telecoms
36
+ @patient_telecom_option ||= [CQM::Telecom.new(
37
+ use: 'HP',
38
+ value: '555-555-2003'
39
+ )]
40
+ JSON.parse(@patient_telecom_option.to_json)
41
+ end
42
+
21
43
  def patient_characteristic_payer
22
44
  JSON.parse(@qdmPatient.get_data_elements('patient_characteristic', 'payer').to_json)
23
45
  end
@@ -173,7 +195,7 @@ class Qrda1R5 < Mustache
173
195
  def physical_exam_order
174
196
  JSON.parse(@qdmPatient.get_data_elements('physical_exam', 'order').to_json)
175
197
  end
176
-
198
+
177
199
  def physical_exam_performed
178
200
  JSON.parse(@qdmPatient.get_data_elements('physical_exam', 'performed').to_json)
179
201
  end
@@ -213,7 +235,7 @@ class Qrda1R5 < Mustache
213
235
  def substance_recommended
214
236
  JSON.parse(@qdmPatient.get_data_elements('substance', 'recommended').to_json)
215
237
  end
216
-
238
+
217
239
  def symptom
218
240
  JSON.parse(@qdmPatient.get_data_elements('symptom', nil).to_json)
219
241
  end
@@ -1,14 +1,20 @@
1
1
  <recordTarget>
2
2
  <patientRole>
3
3
  <id extension="{{mrn}}" root="1.3.6.1.4.1.115" />
4
- <addr use="HP">
5
- <streetAddressLine>202 Burlington Rd.</streetAddressLine>
6
- <city>Bedford</city>
7
- <state>MA</state>
8
- <postalCode>01730</postalCode>
9
- <country>US</country>
10
- </addr>
11
- <telecom use="WP" value="tel:+1-781-271-3000"/>
4
+ {{#patient_addresses}}
5
+ <addr use="{{use}}">
6
+ {{#street}}
7
+ <streetAddressLine>{{.}}</streetAddressLine>
8
+ {{/street}}
9
+ <city>{{city}}</city>
10
+ <state>{{state}}</state>
11
+ <postalCode>{{zip}}</postalCode>
12
+ <country>{{country}}</country>
13
+ </addr>
14
+ {{/patient_addresses}}
15
+ {{#patient_telecoms}}
16
+ <telecom use="{{use}}" value="tel:{{value}}"/>
17
+ {{/patient_telecoms}}
12
18
  <patient>
13
19
  {{#patient}}
14
20
  <name>
@@ -21,6 +27,9 @@
21
27
  <administrativeGenderCode {{> _code}}/>
22
28
  {{/dataElementCodes}}
23
29
  {{/patient_characteristic_sex}}
30
+ {{^patient_characteristic_sex}}
31
+ <administrativeGenderCode nullFlavor="UNK"/>
32
+ {{/patient_characteristic_sex}}
24
33
  {{#patient_characteristic_birthdate}}
25
34
  {{{birth_date_time}}}
26
35
  {{/patient_characteristic_birthdate}}
@@ -29,16 +38,20 @@
29
38
  <raceCode {{> _code}}/>
30
39
  {{/dataElementCodes}}
31
40
  {{/patient_characteristic_race}}
41
+ {{^patient_characteristic_race}}
42
+ <raceCode nullFlavor="UNK"/>
43
+ {{/patient_characteristic_race}}
32
44
  {{#patient_characteristic_ethnicity}}
33
45
  {{#dataElementCodes}}
34
46
  <ethnicGroupCode {{> _code}}/>
35
47
  {{/dataElementCodes}}
36
48
  {{/patient_characteristic_ethnicity}}
49
+ {{^patient_characteristic_ethnicity}}
50
+ <ethnicGroupCode nullFlavor="UNK"/>
51
+ {{/patient_characteristic_ethnicity}}
37
52
  <languageCommunication>
38
- <templateId root="2.16.840.1.113883.3.88.11.83.2" assigningAuthorityName="HITSP/C83"/>
39
- <templateId root="1.3.6.1.4.1.19376.1.5.3.1.2.1" assigningAuthorityName="IHE/PCC"/>
40
53
  <languageCode code="eng"/>
41
54
  </languageCommunication>
42
55
  </patient>
43
56
  </patientRole>
44
- </recordTarget>
57
+ </recordTarget>
@@ -1,5 +1,10 @@
1
1
  <entry>
2
- <substanceAdministration classCode="SBADM" moodCode="EVN" {{{negation_ind}}}>
2
+ {{#negated}}
3
+ <substanceAdministration classCode="SBADM" moodCode="EVN" {{{negation_ind}}}>
4
+ {{/negated}}
5
+ {{^negated}}
6
+ <substanceAdministration classCode="SBADM" moodCode="EVN" negationInd="false">
7
+ {{/negated}}
3
8
  <!-- C-CDA R2 Immunization Activity -->
4
9
  <templateId root="2.16.840.1.113883.10.20.22.4.52" extension="2015-08-01"/>
5
10
  <!-- Immunization Administered -->
@@ -9,8 +9,6 @@
9
9
  <substanceAdministration moodCode="EVN" classCode="SBADM">
10
10
  <!-- Medication Activity (consolidation) template -->
11
11
  <templateId root="2.16.840.1.113883.10.20.22.4.16" extension="2014-06-09"/>
12
- <!-- Medication, Active template -->
13
- <templateId root="2.16.840.1.113883.10.20.24.3.41" extension="2016-02-01"/>
14
12
  <id root="1.3.6.1.4.1.115" extension="{{object_id}}"/>
15
13
  <text>{{description}}</text>
16
14
  <statusCode code="active"/>
@@ -6,6 +6,7 @@
6
6
  <templateId root="2.16.840.1.113883.10.20.24.3.75" extension="2017-08-01"/>
7
7
  <id root="1.3.6.1.4.1.115" extension="{{object_id}}"/>
8
8
  <statusCode code="active"/>
9
+ {{{author_effective_time}}}
9
10
  {{> qrda_templates/template_partials/_medication_details}}
10
11
  <consumable>
11
12
  <manufacturedProduct classCode="MANU">
@@ -1,6 +1,6 @@
1
1
  <supply classCode="SPLY" moodCode="RQO">
2
2
  <!-- Medication Supply Request (V2) template -->
3
- <templateId root="2.16.840.1.113883.10.20.24.3.99" extension="2014-04-05"/>
3
+ <templateId root="2.16.840.1.113883.10.20.24.3.99" extension="2015-04-05"/>
4
4
  <!-- Conforms to C-CDA R2 Planned Supply (V2) template-->
5
5
  <templateId root="2.16.840.1.113883.10.20.22.4.43" extension="2014-06-09" />
6
6
  <id root="{{random_id}}"/>
@@ -67,6 +67,8 @@ module Qrda
67
67
  result_value_as_string(self['result'][0])
68
68
  elsif self['result'].is_a? Hash
69
69
  result_value_as_string(self['result'])
70
+ elsif self['result'].is_a? String
71
+ "<value xsi:type=\"ST\">#{self['result']}</value>"
70
72
  elsif !self['result'].nil?
71
73
  "<value xsi:type=\"PQ\" value=\"#{self['result']}\" unit=\"1\"/>"
72
74
  end
@@ -15,17 +15,17 @@ module QRDA
15
15
  pcs = QDM::PatientCharacteristicSex.new
16
16
  code_element = patient_element.at_xpath('cda:administrativeGenderCode')
17
17
  pcs.dataElementCodes = [code_if_present(code_element)]
18
- patient.qdmPatient.dataElements << pcs
18
+ patient.qdmPatient.dataElements << pcs unless pcs.dataElementCodes.compact.blank?
19
19
 
20
20
  pcr = QDM::PatientCharacteristicRace.new
21
21
  code_element = patient_element.at_xpath('cda:raceCode')
22
22
  pcr.dataElementCodes = [code_if_present(code_element)]
23
- patient.qdmPatient.dataElements << pcr
23
+ patient.qdmPatient.dataElements << pcr unless pcr.dataElementCodes.compact.blank?
24
24
 
25
25
  pce = QDM::PatientCharacteristicEthnicity.new
26
26
  code_element = patient_element.at_xpath('cda:ethnicGroupCode')
27
27
  pce.dataElementCodes = [code_if_present(code_element)]
28
- patient.qdmPatient.dataElements << pce
28
+ patient.qdmPatient.dataElements << pce unless pce.dataElementCodes.compact.blank?
29
29
  end
30
30
 
31
31
  def code_if_present(code_element)
@@ -1,7 +1,7 @@
1
1
  module QRDA
2
2
  module Cat1
3
3
  class SectionImporter
4
- attr_accessor :check_for_usable, :status_xpath, :code_xpath
4
+ attr_accessor :check_for_usable, :status_xpath, :code_xpath, :warnings, :codes_modifiers
5
5
 
6
6
  def initialize(entry_finder)
7
7
  @entry_finder = entry_finder
@@ -9,6 +9,8 @@ module QRDA
9
9
  @entry_id_map = {}
10
10
  @check_for_usable = true
11
11
  @entry_class = QDM::DataElement
12
+ @warnings = []
13
+ @codes_modifiers = {}
12
14
  end
13
15
 
14
16
  # Traverses an HL7 CDA document passed in and creates an Array of Entry
@@ -41,7 +43,7 @@ module QRDA
41
43
  # This is the id found in the QRDA file
42
44
  entry_qrda_id = extract_id(entry_element, @id_xpath)
43
45
  # Create a hash to map all of entry.ids to the same QRDA ids. This will be used to merge QRDA entries
44
- # that represent the same event.
46
+ # that represent the same event.
45
47
  @entry_id_map["#{entry_qrda_id.value}_#{entry_qrda_id.namingSystem}"] ||= []
46
48
  @entry_id_map["#{entry_qrda_id.value}_#{entry_qrda_id.namingSystem}"] << entry.id
47
49
  entry.dataElementCodes = extract_codes(entry_element, @code_xpath)
@@ -90,6 +92,9 @@ module QRDA
90
92
  end
91
93
 
92
94
  def extract_interval(parent_element, interval_xpath)
95
+ # nil if the time interval does not exist
96
+ return nil unless time_interval_exists(parent_element, interval_xpath)
97
+
93
98
  if parent_element.at_xpath("#{interval_xpath}/@value")
94
99
  low_time = DateTime.parse(parent_element.at_xpath(interval_xpath)['value'])
95
100
  high_time = DateTime.parse(parent_element.at_xpath(interval_xpath)['value'])
@@ -108,9 +113,32 @@ module QRDA
108
113
  low_time = Time.parse(parent_element.at_xpath("#{interval_xpath}/cda:center")['value'])
109
114
  high_time = Time.parse(parent_element.at_xpath("#{interval_xpath}/cda:center")['value'])
110
115
  end
116
+ if low_time && high_time && low_time > high_time
117
+ # pass warning: current code continues as expected, but adds warning
118
+ id_attr = parent_element.at_xpath(".//cda:id")&.attributes
119
+ id_str = id_attr ? "and id: #{id_attr['root']&.value}(root), #{id_attr['extension']&.value}(extension)" : ""
120
+ qrda_type = @entry_class.to_s.split("::")[1]
121
+ @warnings << ValidationError.new(message: "Interval with low time after high time. Located in element with QRDA type: #{qrda_type} #{id_str}",
122
+ location: parent_element.path)
123
+ end
124
+ if low_time.nil? && high_time.nil?
125
+ id_attr = parent_element.at_xpath(".//cda:id")&.attributes
126
+ id_str = id_attr ? "and id: #{id_attr['root']&.value}(root), #{id_attr['extension']&.value}(extension)" : ""
127
+ qrda_type = @entry_class.to_s.split("::")[1]
128
+ @warnings << ValidationError.new(message: "Interval with nullFlavor low time and nullFlavor high time. Located in element with QRDA type: #{qrda_type} #{id_str}",
129
+ location: parent_element.path)
130
+ end
111
131
  QDM::Interval.new(low_time, high_time).shift_dates(0)
112
132
  end
113
133
 
134
+ def time_interval_exists(parent_element, interval_xpath)
135
+ # false if the time interval does not exist
136
+ return false unless parent_element.at_xpath(interval_xpath)
137
+ # false if the time element exists but has a null Flavor
138
+ return false if parent_element.at_xpath(interval_xpath)['nullFlavor']
139
+ true
140
+ end
141
+
114
142
  def extract_time(parent_element, datetime_xpath)
115
143
  DateTime.parse(parent_element.at_xpath(datetime_xpath)['value']) if parent_element.at_xpath("#{datetime_xpath}/@value")
116
144
  end
@@ -150,12 +178,11 @@ module QRDA
150
178
  parent_element.xpath(@result_xpath).each do |elem|
151
179
  result << extract_result_value(elem)
152
180
  end
153
- result.size > 1 ? result : result.first
181
+ result.size > 1 ? result : result.first
154
182
  end
155
183
 
156
184
  def extract_result_value(value_element)
157
185
  return unless value_element && !value_element['nullFlavor']
158
-
159
186
  value = value_element['value']
160
187
  if value.present?
161
188
  return value.strip.to_f if (value_element['unit'] == "1" || value_element['unit'].nil?)
@@ -163,6 +190,13 @@ module QRDA
163
190
  return QDM::Quantity.new(value.strip.to_f, value_element['unit'])
164
191
  elsif value_element['code'].present?
165
192
  return code_if_present(value_element)
193
+ elsif value_element.text.present?
194
+ id_attr = value_element.parent.at_xpath(".//cda:id")&.attributes
195
+ id_str = id_attr ? "and id: #{id_attr['root']&.value}(root), #{id_attr['extension']&.value}(extension)" : ""
196
+ qrda_type = @entry_class.to_s.split("::")[1]
197
+ @warnings << ValidationError.new(message: "Value with string type found. When possible, it's best practice to use a coded value or scalar. Located in element with QRDA type: #{qrda_type} #{id_str}",
198
+ location: value_element.path)
199
+ return value_element.text
166
200
  end
167
201
  end
168
202
 
@@ -172,16 +206,16 @@ module QRDA
172
206
  negation_indicator = parent_element['negationInd']
173
207
  # Return and do not set reason attribute if the entry is negated
174
208
  return nil if negation_indicator.eql?('true')
175
-
176
- reason_element.blank? ? nil : code_if_present(reason_element.first)
209
+
210
+ reason_element.blank? ? nil : code_if_present(reason_element.first)
177
211
  end
178
212
 
179
213
  def extract_negation(parent_element, entry)
180
214
  negation_element = parent_element.xpath("./cda:entryRelationship[@typeCode='RSON']/cda:observation[cda:templateId/@root='2.16.840.1.113883.10.20.24.3.88']/cda:value")
181
215
  negation_indicator = parent_element['negationInd']
182
216
  # Return and do not set negationRationale attribute if the entry is not negated
183
- return unless negation_indicator.eql?('true')
184
-
217
+ return unless negation_indicator.eql?('true')
218
+
185
219
  entry.negationRationale = code_if_present(negation_element.first) unless negation_element.blank?
186
220
  extract_negated_code(parent_element, entry)
187
221
  end
@@ -189,8 +223,18 @@ module QRDA
189
223
  def extract_negated_code(parent_element, entry)
190
224
  code_elements = parent_element.xpath(@code_xpath)
191
225
  code_elements.each do |code_element|
192
- if code_element['nullFlavor'] == 'NA' && code_element['sdtc:valueSet']
193
- entry.dataElementCodes = [{ code: code_element['sdtc:valueSet'], codeSystemOid: '1.2.3.4.5.6.7.8.9.10' }]
226
+ if code_element['nullFlavor'] == 'NA'
227
+ if code_element['sdtc:valueSet']
228
+ entry.dataElementCodes = [{ code: code_element['sdtc:valueSet'], codeSystemOid: '1.2.3.4.5.6.7.8.9.10' }]
229
+ else
230
+ # negated code is nullFlavored with no valueset
231
+ entry.dataElementCodes = [{ code: "NA", codeSystemOid: '1.2.3.4.5.6.7.8.9.10' }]
232
+ id_attr = parent_element.at_xpath(".//cda:id")&.attributes
233
+ id_str = id_attr ? "and id: #{id_attr['root']&.value}(root), #{id_attr['extension']&.value}(extension)" : ""
234
+ qrda_type = @entry_class.to_s.split("::")[1]
235
+ @warnings << ValidationError.new(message: "Negated code element contains nullFlavor code but no valueset. Located in element with QRDA type: #{qrda_type} #{id_str}.",
236
+ location: parent_element.path)
237
+ end
194
238
  end
195
239
  end
196
240
  end
@@ -20,7 +20,7 @@ module QRDA
20
20
  def create_entry(entry_element, nrh = NarrativeReferenceHandler.new)
21
21
  diagnostic_study_performed = super
22
22
  diagnostic_study_performed.resultDatetime = extract_time(entry_element, @result_datetime_xpath)
23
- diagnostic_study_performed.resultDatetime ||= extract_interval(entry_element, @result_datetime_xpath).low
23
+ diagnostic_study_performed.resultDatetime ||= extract_interval(entry_element, @result_datetime_xpath)&.low
24
24
  diagnostic_study_performed.status = code_if_present(entry_element.at_xpath(@status_xpath))
25
25
  diagnostic_study_performed.method = code_if_present(entry_element.at_xpath(@method_xpath))
26
26
  diagnostic_study_performed.facilityLocation = extract_facility_locations(entry_element)[0]
@@ -26,11 +26,21 @@ module QRDA
26
26
  los = encounter_performed.relevantPeriod.high - encounter_performed.relevantPeriod.low
27
27
  encounter_performed.lengthOfStay = QDM::Quantity.new(los.to_i, 'd')
28
28
  end
29
+ extract_modifier_code(encounter_performed, entry_element)
29
30
  encounter_performed
30
31
  end
31
32
 
32
33
  private
33
34
 
35
+ def extract_modifier_code(encounter_performed, entry_element)
36
+ code_element = entry_element.at_xpath(@code_xpath)
37
+ return unless code_element
38
+
39
+ qualifier_name = code_element.at_xpath('./cda:qualifier/cda:name')
40
+ qualifier_value = code_element.at_xpath('./cda:qualifier/cda:value')
41
+ codes_modifiers[encounter_performed.id] = { name: code_if_present(qualifier_name), value: code_if_present(qualifier_value), xpath_location: entry_element.path } if qualifier_value || qualifier_name
42
+ end
43
+
34
44
  def extract_diagnoses(parent_element)
35
45
  diagnosis_elements = parent_element.xpath(@diagnosis_xpath)
36
46
  diagnosis_list = []
@@ -16,7 +16,7 @@ module QRDA
16
16
 
17
17
  def create_entry(entry_element, nrh = NarrativeReferenceHandler.new)
18
18
  immunization_order = super
19
- immunization_order.activeDatetime = extract_interval(entry_element, @active_datetime_xpath).low
19
+ immunization_order.activeDatetime = extract_interval(entry_element, @active_datetime_xpath)&.low
20
20
  immunization_order.dosage = extract_scalar(entry_element, @dosage_xpath)
21
21
  immunization_order.supply = extract_scalar(entry_element, @supply_xpath)
22
22
  immunization_order.route = code_if_present(entry_element.at_xpath(@route_xpath))
@@ -12,69 +12,71 @@ module QRDA
12
12
  def initialize
13
13
  # This differs from other HDS patient importers in that sections can have multiple importers
14
14
  @data_element_importers = []
15
- @data_element_importers << generate_importer(AdverseEventImporter)
16
- @data_element_importers << generate_importer(AllergyIntoleranceImporter)
17
- @data_element_importers << generate_importer(AssessmentOrderImporter)
18
- @data_element_importers << generate_importer(AssessmentPerformedImporter)
19
- @data_element_importers << generate_importer(AssessmentRecommendedImporter)
20
- @data_element_importers << generate_importer(CommunicationPerformedImporter)
21
- @data_element_importers << generate_importer(DeviceAppliedImporter)
22
- @data_element_importers << generate_importer(DeviceOrderImporter)
23
- @data_element_importers << generate_importer(DeviceRecommendedImporter)
24
- @data_element_importers << generate_importer(DiagnosisImporter)
25
- @data_element_importers << generate_importer(DiagnosticStudyOrderImporter)
26
- @data_element_importers << generate_importer(DiagnosticStudyPerformedImporter)
27
- @data_element_importers << generate_importer(DiagnosticStudyRecommendedImporter)
28
- @data_element_importers << generate_importer(EncounterOrderImporter)
29
- @data_element_importers << generate_importer(EncounterPerformedImporter)
30
- @data_element_importers << generate_importer(EncounterRecommendedImporter)
31
- @data_element_importers << generate_importer(FamilyHistoryImporter)
32
- @data_element_importers << generate_importer(ImmunizationAdministeredImporter)
33
- @data_element_importers << generate_importer(ImmunizationOrderImporter)
34
- @data_element_importers << generate_importer(InterventionOrderImporter)
35
- @data_element_importers << generate_importer(InterventionPerformedImporter)
36
- @data_element_importers << generate_importer(InterventionRecommendedImporter)
37
- @data_element_importers << generate_importer(LaboratoryTestOrderImporter)
38
- @data_element_importers << generate_importer(LaboratoryTestPerformedImporter)
39
- @data_element_importers << generate_importer(LaboratoryTestRecommendedImporter)
40
- @data_element_importers << generate_importer(MedicationActiveImporter)
41
- @data_element_importers << generate_importer(MedicationAdministeredImporter)
42
- @data_element_importers << generate_importer(MedicationDischargeImporter)
43
- @data_element_importers << generate_importer(MedicationDispensedImporter)
44
- @data_element_importers << generate_importer(MedicationOrderImporter)
45
- @data_element_importers << generate_importer(PatientCareExperienceImporter)
46
- @data_element_importers << generate_importer(PatientCharacteristicClinicalTrialParticipantImporter)
47
- @data_element_importers << generate_importer(PatientCharacteristicExpiredImporter)
48
- @data_element_importers << generate_importer(PatientCharacteristicPayerImporter)
49
- @data_element_importers << generate_importer(PhysicalExamOrderImporter)
50
- @data_element_importers << generate_importer(PhysicalExamPerformedImporter)
51
- @data_element_importers << generate_importer(PhysicalExamRecommendedImporter)
52
- @data_element_importers << generate_importer(ProcedureOrderImporter)
53
- @data_element_importers << generate_importer(ProcedurePerformedImporter)
54
- @data_element_importers << generate_importer(ProcedureRecommendedImporter)
55
- @data_element_importers << generate_importer(ProviderCareExperienceImporter)
56
- @data_element_importers << generate_importer(ProviderCharacteristicImporter)
57
- @data_element_importers << generate_importer(SubstanceAdministeredImporter)
58
- @data_element_importers << generate_importer(SubstanceOrderImporter)
59
- @data_element_importers << generate_importer(SubstanceRecommendedImporter)
60
- @data_element_importers << generate_importer(SymptomImporter)
15
+ @data_element_importers << AdverseEventImporter.new
16
+ @data_element_importers << AllergyIntoleranceImporter.new
17
+ @data_element_importers << AssessmentOrderImporter.new
18
+ @data_element_importers << AssessmentPerformedImporter.new
19
+ @data_element_importers << AssessmentRecommendedImporter.new
20
+ @data_element_importers << CommunicationPerformedImporter.new
21
+ @data_element_importers << DeviceAppliedImporter.new
22
+ @data_element_importers << DeviceOrderImporter.new
23
+ @data_element_importers << DeviceRecommendedImporter.new
24
+ @data_element_importers << DiagnosisImporter.new
25
+ @data_element_importers << DiagnosticStudyOrderImporter.new
26
+ @data_element_importers << DiagnosticStudyPerformedImporter.new
27
+ @data_element_importers << DiagnosticStudyRecommendedImporter.new
28
+ @data_element_importers << EncounterOrderImporter.new
29
+ @data_element_importers << EncounterPerformedImporter.new
30
+ @data_element_importers << EncounterRecommendedImporter.new
31
+ @data_element_importers << FamilyHistoryImporter.new
32
+ @data_element_importers << ImmunizationAdministeredImporter.new
33
+ @data_element_importers << ImmunizationOrderImporter.new
34
+ @data_element_importers << InterventionOrderImporter.new
35
+ @data_element_importers << InterventionPerformedImporter.new
36
+ @data_element_importers << InterventionRecommendedImporter.new
37
+ @data_element_importers << LaboratoryTestOrderImporter.new
38
+ @data_element_importers << LaboratoryTestPerformedImporter.new
39
+ @data_element_importers << LaboratoryTestRecommendedImporter.new
40
+ @data_element_importers << MedicationActiveImporter.new
41
+ @data_element_importers << MedicationAdministeredImporter.new
42
+ @data_element_importers << MedicationDischargeImporter.new
43
+ @data_element_importers << MedicationDispensedImporter.new
44
+ @data_element_importers << MedicationOrderImporter.new
45
+ @data_element_importers << PatientCareExperienceImporter.new
46
+ @data_element_importers << PatientCharacteristicClinicalTrialParticipantImporter.new
47
+ @data_element_importers << PatientCharacteristicExpiredImporter.new
48
+ @data_element_importers << PatientCharacteristicPayerImporter.new
49
+ @data_element_importers << PhysicalExamOrderImporter.new
50
+ @data_element_importers << PhysicalExamPerformedImporter.new
51
+ @data_element_importers << PhysicalExamRecommendedImporter.new
52
+ @data_element_importers << ProcedureOrderImporter.new
53
+ @data_element_importers << ProcedurePerformedImporter.new
54
+ @data_element_importers << ProcedureRecommendedImporter.new
55
+ @data_element_importers << ProviderCareExperienceImporter.new
56
+ @data_element_importers << ProviderCharacteristicImporter.new
57
+ @data_element_importers << SubstanceAdministeredImporter.new
58
+ @data_element_importers << SubstanceOrderImporter.new
59
+ @data_element_importers << SubstanceRecommendedImporter.new
60
+ @data_element_importers << SymptomImporter.new
61
61
  end
62
62
 
63
63
  def parse_cat1(doc)
64
64
  patient = CQM::Patient.new
65
+ warnings = []
66
+ codes_modifiers = {}
65
67
  entry_id_map = {}
66
- import_data_elements(patient, doc, entry_id_map)
68
+ import_data_elements(patient, doc, entry_id_map, codes_modifiers, warnings)
67
69
  normalize_references(patient, entry_id_map)
68
70
  get_demographics(patient, doc)
69
- patient
71
+ [patient, warnings, codes_modifiers]
70
72
  end
71
73
 
72
- def import_data_elements(patient, doc, entry_id_map)
74
+ def import_data_elements(patient, doc, entry_id_map, codes_modifiers, warnings = [])
73
75
  context = doc.xpath("/cda:ClinicalDocument/cda:component/cda:structuredBody/cda:component/cda:section[cda:templateId/@root = '2.16.840.1.113883.10.20.24.2.1']")
74
76
  nrh = NarrativeReferenceHandler.new
75
77
  nrh.build_id_map(doc)
76
- @data_element_importers.each do |entry_package|
77
- data_elements, id_map = entry_package.package_entries(context, nrh)
78
+ @data_element_importers.each do |importer|
79
+ data_elements, id_map = importer.create_entries(context, nrh)
78
80
  new_data_elements = []
79
81
 
80
82
  id_map.each_value do |elem_ids|
@@ -91,7 +93,7 @@ module QRDA
91
93
  unique_element_keys << key_elements_for_determining_encounter_uniqueness(data_element)
92
94
 
93
95
  # Loop through all other data elements with the same id
94
- elem_ids[1,elem_ids.length].each do |dup_id|
96
+ elem_ids[1,elem_ids.length].each do |dup_id|
95
97
  dup_element = data_elements.find { |de| de.id == dup_id }
96
98
  dup_element_keys = key_elements_for_determining_encounter_uniqueness(dup_element)
97
99
  # See if a previously selected data element shared all of the keys files
@@ -106,13 +108,18 @@ module QRDA
106
108
 
107
109
  patient.qdmPatient.dataElements << new_data_elements
108
110
  entry_id_map.merge!(id_map)
111
+ warnings.concat(importer.warnings)
112
+ codes_modifiers.merge!(importer.codes_modifiers)
113
+ # reset warnings after they're captured so that the importer can be re-used
114
+ importer.warnings = []
115
+ importer.codes_modifiers = {}
109
116
  end
110
117
  end
111
118
 
112
119
  def key_elements_for_determining_encounter_uniqueness(encounter)
113
120
  codes = encounter.codes.collect { |dec| "#{dec.code}_#{dec.codeSystemOid}" }.sort.to_s
114
- admission_date_time = encounter.relevantPeriod.low.to_s
115
- discharge_date_time = encounter.relevantPeriod.high.to_s
121
+ admission_date_time = encounter&.relevantPeriod&.low.to_s
122
+ discharge_date_time = encounter&.relevantPeriod&.high.to_s
116
123
  "#{codes}#{admission_date_time}#{discharge_date_time}"
117
124
  end
118
125
 
@@ -138,12 +145,6 @@ module QRDA
138
145
  end
139
146
  end
140
147
  end
141
-
142
- private
143
-
144
- def generate_importer(importer_class)
145
- EntryPackage.new(importer_class.new)
146
- end
147
148
  end
148
149
  end
149
150
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cqm-reports
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.5
4
+ version: 2.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - The MITRE Corporation
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-10-25 00:00:00.000000000 Z
11
+ date: 2020-09-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cqm-models
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: 2.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: cqm-validators
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 2.0.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 2.0.0
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: mustache
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -306,7 +320,6 @@ files:
306
320
  - lib/qrda-import/data-element-importers/substance_recommended_importer.rb
307
321
  - lib/qrda-import/data-element-importers/symptom_importer.rb
308
322
  - lib/qrda-import/entry_finder.rb
309
- - lib/qrda-import/entry_package.rb
310
323
  - lib/qrda-import/narrative_reference_handler.rb
311
324
  - lib/qrda-import/patient_importer.rb
312
325
  - lib/util/code_system_helper.rb
@@ -1,16 +0,0 @@
1
- module QRDA
2
- module Cat1
3
- class EntryPackage
4
-
5
- attr_accessor :importer_type
6
-
7
- def initialize(type)
8
- self.importer_type = type
9
- end
10
-
11
- def package_entries(doc, nrh)
12
- importer_type.create_entries(doc, nrh)
13
- end
14
- end
15
- end
16
- end