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 +4 -4
- data/Gemfile +0 -1
- data/README.md +27 -0
- data/lib/cqm_report.rb +0 -1
- data/lib/qrda-export/catI-r5/_header.mustache +2 -2
- data/lib/qrda-export/catI-r5/qrda1_r5.mustache +2 -2
- data/lib/qrda-export/catI-r5/qrda1_r5.rb +24 -2
- data/lib/qrda-export/catI-r5/qrda_header/_record_target.mustache +24 -11
- data/lib/qrda-export/catI-r5/qrda_templates/immunization_administered.mustache +6 -1
- data/lib/qrda-export/catI-r5/qrda_templates/medication_discharge.mustache +0 -2
- data/lib/qrda-export/catI-r5/qrda_templates/substance_recommended.mustache +1 -0
- data/lib/qrda-export/catI-r5/qrda_templates/template_partials/_medication_supply_request.mustache +1 -1
- data/lib/qrda-export/helper/cat1_view_helper.rb +2 -0
- data/lib/qrda-import/base-importers/demographics_importer.rb +3 -3
- data/lib/qrda-import/base-importers/section_importer.rb +54 -10
- data/lib/qrda-import/data-element-importers/diagnostic_study_performed_importer.rb +1 -1
- data/lib/qrda-import/data-element-importers/encounter_performed_importer.rb +10 -0
- data/lib/qrda-import/data-element-importers/immunization_order_importer.rb +1 -1
- data/lib/qrda-import/patient_importer.rb +61 -60
- metadata +16 -3
- data/lib/qrda-import/entry_package.rb +0 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6af15ff075a9533c61c844f95c5adc741e999dec
|
4
|
+
data.tar.gz: 2cf3f0aeb6c3e1744e5e2f8a45a8d0e90a1ab5a1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a4ab0c6c763b00dfa1c7985110969e6262ecc977d9a6509ca64c82e3979e6fd6e27f1bd888ae05ac5eaeecf600645a7cfea95d7013f8a5b01d1b85e865d80f52
|
7
|
+
data.tar.gz: fdfe64bbfb95f37e11dae78380147076daeb9b155a05eadb663c063191ffb9155390af0ae32a6f9643d87d852d605a87cb6fd48c4dc1cf0c9f631a7f929c4538
|
data/Gemfile
CHANGED
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
|
data/lib/cqm_report.rb
CHANGED
@@ -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="
|
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="
|
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="
|
16
|
-
<templateId extension="
|
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
|
-
|
5
|
-
<
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
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
|
-
|
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">
|
data/lib/qrda-export/catI-r5/qrda_templates/template_partials/_medication_supply_request.mustache
CHANGED
@@ -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="
|
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'
|
193
|
-
|
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)
|
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)
|
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 <<
|
16
|
-
@data_element_importers <<
|
17
|
-
@data_element_importers <<
|
18
|
-
@data_element_importers <<
|
19
|
-
@data_element_importers <<
|
20
|
-
@data_element_importers <<
|
21
|
-
@data_element_importers <<
|
22
|
-
@data_element_importers <<
|
23
|
-
@data_element_importers <<
|
24
|
-
@data_element_importers <<
|
25
|
-
@data_element_importers <<
|
26
|
-
@data_element_importers <<
|
27
|
-
@data_element_importers <<
|
28
|
-
@data_element_importers <<
|
29
|
-
@data_element_importers <<
|
30
|
-
@data_element_importers <<
|
31
|
-
@data_element_importers <<
|
32
|
-
@data_element_importers <<
|
33
|
-
@data_element_importers <<
|
34
|
-
@data_element_importers <<
|
35
|
-
@data_element_importers <<
|
36
|
-
@data_element_importers <<
|
37
|
-
@data_element_importers <<
|
38
|
-
@data_element_importers <<
|
39
|
-
@data_element_importers <<
|
40
|
-
@data_element_importers <<
|
41
|
-
@data_element_importers <<
|
42
|
-
@data_element_importers <<
|
43
|
-
@data_element_importers <<
|
44
|
-
@data_element_importers <<
|
45
|
-
@data_element_importers <<
|
46
|
-
@data_element_importers <<
|
47
|
-
@data_element_importers <<
|
48
|
-
@data_element_importers <<
|
49
|
-
@data_element_importers <<
|
50
|
-
@data_element_importers <<
|
51
|
-
@data_element_importers <<
|
52
|
-
@data_element_importers <<
|
53
|
-
@data_element_importers <<
|
54
|
-
@data_element_importers <<
|
55
|
-
@data_element_importers <<
|
56
|
-
@data_element_importers <<
|
57
|
-
@data_element_importers <<
|
58
|
-
@data_element_importers <<
|
59
|
-
@data_element_importers <<
|
60
|
-
@data_element_importers <<
|
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 |
|
77
|
-
data_elements, id_map =
|
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
|
115
|
-
discharge_date_time = encounter
|
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.
|
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:
|
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
|