cqm-reports 3.0.0 → 3.1.3
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 +5 -5
- data/Gemfile +1 -2
- data/README.md +2 -1
- data/lib/cqm-reports.rb +0 -1
- data/lib/html-export/qdm-patient/data_element/_data_element_codes.mustache +1 -1
- data/lib/html-export/qdm-patient/qdm_patient.mustache +13 -11
- data/lib/html-export/qdm-patient/qdm_patient.rb +50 -2
- data/lib/qrda-export/catI-r5/qrda_header/_record_target.mustache +1 -1
- data/lib/qrda-export/catI-r5/qrda_templates/assessment_performed.mustache +1 -10
- data/lib/qrda-export/catI-r5/qrda_templates/device_applied.mustache +1 -10
- data/lib/qrda-export/catI-r5/qrda_templates/device_order.mustache +0 -1
- data/lib/qrda-export/catI-r5/qrda_templates/diagnostic_study_performed.mustache +1 -10
- data/lib/qrda-export/catI-r5/qrda_templates/encounter_recommended.mustache +0 -1
- data/lib/qrda-export/catI-r5/qrda_templates/intervention_performed.mustache +1 -10
- data/lib/qrda-export/catI-r5/qrda_templates/laboratory_test_performed.mustache +5 -10
- data/lib/qrda-export/catI-r5/qrda_templates/medication_discharge.mustache +0 -1
- data/lib/qrda-export/catI-r5/qrda_templates/medication_dispensed.mustache +3 -1
- data/lib/qrda-export/catI-r5/qrda_templates/physical_exam_performed.mustache +5 -10
- data/lib/qrda-export/catI-r5/qrda_templates/procedure_performed.mustache +1 -10
- data/lib/qrda-export/catI-r5/qrda_templates/substance_recommended.mustache +3 -0
- data/lib/qrda-export/catI-r5/qrda_templates/template_partials/_reason.mustache +0 -1
- data/lib/qrda-export/catIII-r2-1/_header.mustache +1 -1
- data/lib/qrda-export/helper/date_helper.rb +6 -10
- data/lib/qrda-export/helper/patient_view_helper.rb +39 -0
- data/lib/qrda-import/base-importers/demographics_importer.rb +8 -7
- data/lib/qrda-import/base-importers/section_importer.rb +56 -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 +11 -1
- data/lib/qrda-import/data-element-importers/encounter_recommended_importer.rb +1 -1
- data/lib/qrda-import/data-element-importers/medication_discharge_importer.rb +1 -1
- data/lib/qrda-import/data-element-importers/medication_dispensed_importer.rb +1 -1
- data/lib/qrda-import/patient_importer.rb +67 -63
- metadata +20 -8
- data/lib/qrda-import/entry_package.rb +0 -16
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: b9895c3e39093e4e3b01d4ab490bd91db2b881b5ff9870b8de0c6369fc792601
|
|
4
|
+
data.tar.gz: ff99962ce100bd5f73bb5bc73299d55bd4b2ed1185de971996292b713baf8060
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b2b306d0fc7131de73ebad247d9bdb6d1032fb18d25397e55ddf0926c53002f141bed46facc8e37926b38b27a57f3d2e8d11d6071403502a4eeca751e2839e20
|
|
7
|
+
data.tar.gz: 27b7e2a699c9f3c897ecbeda9a06cba45dd21e87fc5ddb5315fa07acddd659e6ef9654d9fc4bdf89d28d784ffdecfb1cc907902c228635ffcd94a1971b499acc
|
data/Gemfile
CHANGED
|
@@ -17,7 +17,7 @@ group :development do
|
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
group :test do
|
|
20
|
-
gem 'cqm-models',
|
|
20
|
+
gem 'cqm-models', '~> 3.0.3'
|
|
21
21
|
gem 'factory_girl', '~> 4.1.0'
|
|
22
22
|
gem 'tailor', '~> 1.1.2'
|
|
23
23
|
gem 'cane', '~> 2.3.0'
|
|
@@ -27,6 +27,5 @@ group :test do
|
|
|
27
27
|
gem 'minitest', '~> 5.3'
|
|
28
28
|
gem 'minitest-reporters'
|
|
29
29
|
gem 'awesome_print', :require => 'ap'
|
|
30
|
-
gem 'cqm-validators', git: 'https://github.com/projecttacoma/cqm-validators', branch: 'master'
|
|
31
30
|
gem 'nokogiri-diff'
|
|
32
31
|
end
|
data/README.md
CHANGED
|
@@ -11,6 +11,7 @@ Starting with version **2.0.0** released on 6/20/2019, cqm-reports versioning ha
|
|
|
11
11
|
| X | QRDA Cat 1 | QRDA Cat 3 |
|
|
12
12
|
| --- | --- | --- |
|
|
13
13
|
| 2 | R1 STU5.1 | R1 STU2.1 |
|
|
14
|
+
| 3 | R1 STU5.2 | R1 STU2.1 |
|
|
14
15
|
|
|
15
16
|
* **Y** indicates major changes (incompatible API changes)
|
|
16
17
|
|
|
@@ -24,7 +25,7 @@ Importing QRDA
|
|
|
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.
|
|
25
26
|
|
|
26
27
|
doc = Nokogiri::XML(file)
|
|
27
|
-
patient = QRDA::Cat1::PatientImporter.instance.parse_cat1(doc)
|
|
28
|
+
patient, warnings = QRDA::Cat1::PatientImporter.instance.parse_cat1(doc)
|
|
28
29
|
|
|
29
30
|
Exporting QRDA Category I
|
|
30
31
|
==========
|
data/lib/cqm-reports.rb
CHANGED
|
@@ -19,7 +19,6 @@ require_relative 'html-export/qdm-patient/qdm_patient.rb'
|
|
|
19
19
|
require_relative 'qrda-export/catI-r5/qrda1_r5.rb'
|
|
20
20
|
require_relative 'qrda-export/catIII-r2-1/qrda3_r21.rb'
|
|
21
21
|
|
|
22
|
-
require_relative 'qrda-import/entry_package.rb'
|
|
23
22
|
require_relative 'qrda-import/cda_identifier.rb'
|
|
24
23
|
require_relative 'qrda-import/narrative_reference_handler.rb'
|
|
25
24
|
require_relative 'qrda-import/entry_finder.rb'
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<div class="div-table-body">
|
|
3
3
|
{{#dataElementCodes}}
|
|
4
4
|
<div class="div-head-row">
|
|
5
|
-
<div class="div-table-head--no-border"><span class="criteria-heading">{{{code_system_name}}}:</span> {{code}}</div>
|
|
5
|
+
<div class="div-table-head--no-border"><span class="criteria-heading">{{{code_system_name}}}:</span> {{code}}{{{code_description}}}</div>
|
|
6
6
|
</div>
|
|
7
7
|
{{/dataElementCodes}}
|
|
8
8
|
</div>
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
|
5
5
|
{{#patient}}
|
|
6
|
-
<title>Cypress Certification Patient Test Record: {{
|
|
6
|
+
<title>Cypress Certification Patient Test Record: {{given_name}} {{familyName}}</title>
|
|
7
7
|
{{/patient}}
|
|
8
8
|
{{#include_style?}}
|
|
9
9
|
{{> _header_css}}
|
|
@@ -11,36 +11,38 @@
|
|
|
11
11
|
</head>
|
|
12
12
|
<body>
|
|
13
13
|
{{#patient}}
|
|
14
|
-
<h1 class="h1center">Cypress Certification Patient Test Record: {{
|
|
14
|
+
<h1 class="h1center">Cypress Certification Patient Test Record: {{given_name}} {{familyName}}</h1>
|
|
15
15
|
<div class="div-table">
|
|
16
16
|
<div class="div-table-body">
|
|
17
17
|
<div class="div-head-row patient_narr_tr panel panel-default patient-details">
|
|
18
18
|
<div class="div-table-head patient_narr_th panel-heading"><span class="td_label">Patient</span></div>
|
|
19
|
-
<div class="div-table-head">{{
|
|
19
|
+
<div class="div-table-head">{{given_name}} {{familyName}}</div>
|
|
20
20
|
<div class="div-table-head patient_narr_th panel-heading"><span class="td_label">Sex</span></div>
|
|
21
|
-
<div class="div-table-head">{{{gender}}}</div>
|
|
21
|
+
<div class="div-table-head">{{{gender}}}{{#demographic_code_description}}gender{{/demographic_code_description}}</div>
|
|
22
22
|
</div>
|
|
23
23
|
<div class="div-head-row patient_narr_tr panel panel-default patient-details">
|
|
24
24
|
<div class="div-table-head patient_narr_th panel-heading"><span class="td_label">Date of birth</span></div>
|
|
25
25
|
<div class="div-table-head">{{{birthdate}}}</div>
|
|
26
26
|
<div class="div-table-head patient_narr_th panel-heading"><span class="td_label">Date of expiration</span></div>
|
|
27
|
-
<div class="div-table-head"
|
|
27
|
+
<div class="div-table-head">{{{expiration}}}</div>
|
|
28
28
|
</div>
|
|
29
29
|
<div class="div-head-row patient_narr_tr panel panel-default patient-details">
|
|
30
30
|
<div class="div-table-head patient_narr_th panel-heading"><span class="td_label">Race</span></div>
|
|
31
|
-
<div class="div-table-head">{{{race}}}</div>
|
|
31
|
+
<div class="div-table-head">{{{race}}}{{#demographic_code_description}}race{{/demographic_code_description}}</div>
|
|
32
32
|
<div class="div-table-head patient_narr_th panel-heading"><span class="td_label">Ethnicity</span></div>
|
|
33
|
-
<div class="div-table-head">{{{ethnic_group}}}</div>
|
|
34
|
-
</div>
|
|
33
|
+
<div class="div-table-head">{{{ethnic_group}}}{{#demographic_code_description}}ethnic_group{{/demographic_code_description}}</div>
|
|
34
|
+
</div>
|
|
35
35
|
<div class="div-head-row patient_narr_tr panel panel-default patient-details">
|
|
36
36
|
<div class="div-table-head patient_narr_th panel-heading"><span class="td_label">Insurance Providers</span></div>
|
|
37
|
-
<div class="div-table-head">{{{payer}}}</div>
|
|
37
|
+
<div class="div-table-head">{{{payer}}}{{#demographic_code_description}}payer{{/demographic_code_description}}</div>
|
|
38
38
|
<div class="div-table-head patient_narr_th panel-heading"><span class="td_label">Patient IDs</span></div>
|
|
39
39
|
<div class="div-table-head">{{{mrn}}} Cypress</div>
|
|
40
40
|
</div>
|
|
41
41
|
<div class="div-head-row patient_narr_tr panel panel-default patient-details">
|
|
42
|
-
<div class="div-table-head patient_narr_th panel-heading"><span class="td_label">
|
|
43
|
-
<div class="div-table-head"
|
|
42
|
+
<div class="div-table-head patient_narr_th panel-heading"><span class="td_label">Address</span></div>
|
|
43
|
+
<div class="div-table-head">{{{patient_addresses}}}</div>
|
|
44
|
+
<div class="div-table-head patient_narr_th panel-heading"><span class="td_label">Telecom</span></div>
|
|
45
|
+
<div class="div-table-head">{{{patient_telecoms}}}</div>
|
|
44
46
|
</div>
|
|
45
47
|
</div>
|
|
46
48
|
</div>
|
|
@@ -9,6 +9,36 @@ class QdmPatient < Mustache
|
|
|
9
9
|
@include_style = include_style
|
|
10
10
|
@patient = patient
|
|
11
11
|
@qdmPatient = patient.qdmPatient
|
|
12
|
+
@patient_addresses = patient['addresses']
|
|
13
|
+
@patient_telecoms = patient['telecoms']
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def patient_addresses
|
|
17
|
+
@patient_addresses ||= [CQM::Address.new(
|
|
18
|
+
use: 'HP',
|
|
19
|
+
street: ['202 Burlington Rd.'],
|
|
20
|
+
city: 'Bedford',
|
|
21
|
+
state: 'MA',
|
|
22
|
+
zip: '01730',
|
|
23
|
+
country: 'US'
|
|
24
|
+
)]
|
|
25
|
+
address_str = ""
|
|
26
|
+
@patient_addresses.each do |address|
|
|
27
|
+
# create formatted address
|
|
28
|
+
address_str += "<address>"
|
|
29
|
+
address['street'].each { |street| address_str += "#{street}<br>" }
|
|
30
|
+
address_str += "#{address['city']}, #{address['state']} #{address['zip']}<br> #{address['country']} </address>"
|
|
31
|
+
end
|
|
32
|
+
address_str
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def patient_telecoms
|
|
36
|
+
@patient_telecoms ||= [CQM::Telecom.new(
|
|
37
|
+
use: 'HP',
|
|
38
|
+
value: '555-555-2003'
|
|
39
|
+
)]
|
|
40
|
+
# create formatted telecoms
|
|
41
|
+
@patient_telecoms.map { |telecom| "(#{telecom['use']}) #{telecom['value']}" }.join("<br>")
|
|
12
42
|
end
|
|
13
43
|
|
|
14
44
|
def include_style?
|
|
@@ -25,7 +55,7 @@ class QdmPatient < Mustache
|
|
|
25
55
|
end
|
|
26
56
|
|
|
27
57
|
def unit_string
|
|
28
|
-
return "#{self['value']} "
|
|
58
|
+
return "#{self['value']} " if !self['unit'] || self['unit'] == '1'
|
|
29
59
|
"#{self['value']} #{self['unit']}"
|
|
30
60
|
end
|
|
31
61
|
|
|
@@ -61,13 +91,31 @@ class QdmPatient < Mustache
|
|
|
61
91
|
end
|
|
62
92
|
|
|
63
93
|
def code_for_element(element)
|
|
64
|
-
"#{element['code']} (#{HQMF::Util::CodeSystemHelper.code_system_for(element['system'])})"
|
|
94
|
+
"#{element['code']} (#{HQMF::Util::CodeSystemHelper.code_system_for(element['system'])})#{code_description(element)}"
|
|
65
95
|
end
|
|
66
96
|
|
|
67
97
|
def code_system_name
|
|
68
98
|
HQMF::Util::CodeSystemHelper.code_system_for(self['system'])
|
|
69
99
|
end
|
|
70
100
|
|
|
101
|
+
def code_description(element = self)
|
|
102
|
+
has_descriptions = @patient.respond_to?(:code_description_hash) && !@patient.code_description_hash.empty?
|
|
103
|
+
# mongo keys cannot contain '.', so replace all '.', key example: '21112-8:2_16_840_1_113883_6_1'
|
|
104
|
+
return " - #{@patient.code_description_hash["#{element['code']}:#{element['system']}".tr('.', '_')]}" if has_descriptions
|
|
105
|
+
# no code description available
|
|
106
|
+
""
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def demographic_code_description(code)
|
|
110
|
+
# only have code, don't need code system
|
|
111
|
+
has_descriptions = code && @patient.respond_to?(:code_description_hash) && !@patient.code_description_hash.empty?
|
|
112
|
+
if has_descriptions
|
|
113
|
+
key = @patient.code_description_hash.keys.detect { |k| k.starts_with?("#{send(code)}:") }
|
|
114
|
+
return " - #{@patient.code_description_hash[key]}"
|
|
115
|
+
end
|
|
116
|
+
""
|
|
117
|
+
end
|
|
118
|
+
|
|
71
119
|
def result_string
|
|
72
120
|
return unit_string if self['value']
|
|
73
121
|
return code_code_system_string if self['code']
|
|
@@ -6,16 +6,7 @@
|
|
|
6
6
|
{{> _codes}}
|
|
7
7
|
<text>{{description}}</text>
|
|
8
8
|
<statusCode code="completed"/>
|
|
9
|
-
{{
|
|
10
|
-
<!-- QDM Attribute: Relevant dateTime -->
|
|
11
|
-
{{{relevant_date_time_value}}}
|
|
12
|
-
{{/relevantDatetime}}
|
|
13
|
-
{{^relevantDatetime}}
|
|
14
|
-
{{#relevantPeriod}}
|
|
15
|
-
<!-- QDM Attribute: Relevant Period -->
|
|
16
|
-
{{{relevant_period}}}
|
|
17
|
-
{{/relevantPeriod}}
|
|
18
|
-
{{/relevantDatetime}}
|
|
9
|
+
{{{relevant_date_period_or_null_flavor}}}
|
|
19
10
|
{{#result}}
|
|
20
11
|
<!-- QDM Attribute: Result -->
|
|
21
12
|
{{{result_value}}}
|
|
@@ -8,16 +8,7 @@
|
|
|
8
8
|
<code code="360030002" codeSystem="2.16.840.1.113883.6.96" codeSystemName="SNOMED CT" displayName="application of device"/>
|
|
9
9
|
<text>{{description}}</text>
|
|
10
10
|
<statusCode code="completed"/>
|
|
11
|
-
{{
|
|
12
|
-
<!-- QDM Attribute: Relevant dateTime -->
|
|
13
|
-
{{{relevant_date_time_value}}}
|
|
14
|
-
{{/relevantDatetime}}
|
|
15
|
-
{{^relevantDatetime}}
|
|
16
|
-
{{#relevantPeriod}}
|
|
17
|
-
<!-- QDM Attribute: Relevant Period -->
|
|
18
|
-
{{{relevant_period}}}
|
|
19
|
-
{{/relevantPeriod}}
|
|
20
|
-
{{/relevantDatetime}}
|
|
11
|
+
{{{relevant_date_period_or_null_flavor}}}
|
|
21
12
|
{{#anatomicalLocationSite}}
|
|
22
13
|
<!-- QDM Attribute: Anatomical Location Site -->
|
|
23
14
|
{{> qrda_templates/template_partials/_anatomical_location_site}}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
<entry>
|
|
2
2
|
<act classCode="ACT" moodCode="RQO" {{{negation_ind}}}>
|
|
3
3
|
<templateId root="2.16.840.1.113883.10.20.24.3.130" extension="2019-12-01"/>
|
|
4
|
-
<id root="1.3.6.1.4.1.115" extension="{{object_id}}"/>
|
|
5
4
|
<code code="SPLY" codeSystem="2.16.840.1.113883.5.6" displayName="Supply" codeSystemName="ActClass"/>
|
|
6
5
|
<entryRelationship typeCode="SUBJ">
|
|
7
6
|
<supply classCode="SPLY" moodCode="RQO">
|
|
@@ -9,16 +9,7 @@
|
|
|
9
9
|
{{> _codes}}
|
|
10
10
|
<text>{{description}}</text>
|
|
11
11
|
<statusCode code="completed"/>
|
|
12
|
-
{{
|
|
13
|
-
<!-- QDM Attribute: Relevant dateTime -->
|
|
14
|
-
{{{relevant_date_time_value}}}
|
|
15
|
-
{{/relevantDatetime}}
|
|
16
|
-
{{^relevantDatetime}}
|
|
17
|
-
{{#relevantPeriod}}
|
|
18
|
-
<!-- QDM Attribute: Relevant Period -->
|
|
19
|
-
{{{relevant_period}}}
|
|
20
|
-
{{/relevantPeriod}}
|
|
21
|
-
{{/relevantDatetime}}
|
|
12
|
+
{{{relevant_date_period_or_null_flavor}}}
|
|
22
13
|
<value nullFlavor="NA" xsi:type="CD" />
|
|
23
14
|
{{#method}}
|
|
24
15
|
<!-- QDM Attribute: Method -->
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
<act classCode="ACT" moodCode="INT" {{{negation_ind}}}>
|
|
3
3
|
<!--Encounter Recommended Act (V2) -->
|
|
4
4
|
<templateId root="2.16.840.1.113883.10.20.24.3.134" extension="2019-12-01"/>
|
|
5
|
-
<id extension="{{object_id}}" root="1.3.6.1.4.1.115"/>
|
|
6
5
|
<code code="ENC" codeSystem="2.16.840.1.113883.5.6" codeSystemName="ActClass" displayName="Encounter"/>
|
|
7
6
|
<entryRelationship typeCode="SUBJ">
|
|
8
7
|
<encounter classCode="ENC" moodCode="INT">
|
|
@@ -8,16 +8,7 @@
|
|
|
8
8
|
{{> _codes}}
|
|
9
9
|
<text>{{description}}</text>
|
|
10
10
|
<statusCode code="completed"/>
|
|
11
|
-
{{
|
|
12
|
-
<!-- QDM Attribute: Relevant dateTime -->
|
|
13
|
-
{{{relevant_date_time_value}}}
|
|
14
|
-
{{/relevantDatetime}}
|
|
15
|
-
{{^relevantDatetime}}
|
|
16
|
-
{{#relevantPeriod}}
|
|
17
|
-
<!-- QDM Attribute: Relevant Period -->
|
|
18
|
-
{{{relevant_period}}}
|
|
19
|
-
{{/relevantPeriod}}
|
|
20
|
-
{{/relevantDatetime}}
|
|
11
|
+
{{{relevant_date_period_or_null_flavor}}}
|
|
21
12
|
{{#authorDatetime}}
|
|
22
13
|
<!-- QDM Attribute: Author dateTime -->
|
|
23
14
|
{{> qrda_templates/template_partials/_author}}
|
|
@@ -6,16 +6,7 @@
|
|
|
6
6
|
{{> _codes}}
|
|
7
7
|
<text>{{description}}</text>
|
|
8
8
|
<statusCode code="completed"/>
|
|
9
|
-
{{
|
|
10
|
-
<!-- QDM Attribute: Relevant dateTime -->
|
|
11
|
-
{{{relevant_date_time_value}}}
|
|
12
|
-
{{/relevantDatetime}}
|
|
13
|
-
{{^relevantDatetime}}
|
|
14
|
-
{{#relevantPeriod}}
|
|
15
|
-
<!-- QDM Attribute: Relevant Period -->
|
|
16
|
-
{{{relevant_period}}}
|
|
17
|
-
{{/relevantPeriod}}
|
|
18
|
-
{{/relevantDatetime}}
|
|
9
|
+
{{{relevant_date_period_or_null_flavor}}}
|
|
19
10
|
{{#method}}
|
|
20
11
|
<!-- QDM Attribute: Method -->
|
|
21
12
|
{{> qrda_templates/template_partials/_method}}
|
|
@@ -50,5 +41,9 @@
|
|
|
50
41
|
<!-- QDM Attribute: Components -->
|
|
51
42
|
{{> qrda_templates/template_partials/_component}}
|
|
52
43
|
{{/components}}
|
|
44
|
+
{{#encounter_id}}
|
|
45
|
+
<!-- QDM Attribute: relatedTo -->
|
|
46
|
+
{{> qrda_templates/template_partials/_related_to}}
|
|
47
|
+
{{/encounter_id}}
|
|
53
48
|
</observation>
|
|
54
49
|
</entry>
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
<act classCode="ACT" moodCode="RQO" {{{negation_ind}}}>
|
|
3
3
|
<!-- Discharge Medication Entry -->
|
|
4
4
|
<templateId root="2.16.840.1.113883.10.20.24.3.105" extension="2019-12-01"/>
|
|
5
|
-
<id root="1.3.6.1.4.1.115" extension="{{object_id}}"/>
|
|
6
5
|
<code code="75311-1" codeSystem="2.16.840.1.113883.6.1" codeSystemName="LOINC" displayName="Discharge medications"/>
|
|
7
6
|
<statusCode code="active"/>
|
|
8
7
|
{{#prescriber}}
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
<act classCode="ACT" moodCode="EVN" {{{negation_ind}}}>
|
|
3
3
|
<!-- Medication Dispensed Act -->
|
|
4
4
|
<templateId root="2.16.840.1.113883.10.20.24.3.139" extension="2019-12-01"/>
|
|
5
|
-
<id root="1.3.6.1.4.1.115" extension="{{object_id}}"/>
|
|
6
5
|
<code code="SPLY" codeSystem="2.16.840.1.113883.5.6" displayName="supply" codeSystemName="ActClass"/>
|
|
7
6
|
<entryRelationship typeCode="SUBJ">
|
|
8
7
|
<!--Medication dispensed -->
|
|
@@ -23,6 +22,9 @@
|
|
|
23
22
|
<!-- QDM Attribute: Relevant Period -->
|
|
24
23
|
{{{medication_duration_effective_time}}}
|
|
25
24
|
{{/relevantPeriod}}
|
|
25
|
+
{{^relevantPeriod}}
|
|
26
|
+
<effectiveTime xsi:type="IVL_TS" nullFlavor="UNK"/>
|
|
27
|
+
{{/relevantPeriod}}
|
|
26
28
|
{{/relevantDatetime}}
|
|
27
29
|
<product>
|
|
28
30
|
<manufacturedProduct classCode="MANU">
|
|
@@ -9,16 +9,7 @@
|
|
|
9
9
|
{{> _codes}}
|
|
10
10
|
<text>{{description}}</text>
|
|
11
11
|
<statusCode code="completed"/>
|
|
12
|
-
{{
|
|
13
|
-
<!-- QDM Attribute: Relevant dateTime -->
|
|
14
|
-
{{{relevant_date_time_value}}}
|
|
15
|
-
{{/relevantDatetime}}
|
|
16
|
-
{{^relevantDatetime}}
|
|
17
|
-
{{#relevantPeriod}}
|
|
18
|
-
<!-- QDM Attribute: Relevant Period -->
|
|
19
|
-
{{{relevant_period}}}
|
|
20
|
-
{{/relevantPeriod}}
|
|
21
|
-
{{/relevantDatetime}}
|
|
12
|
+
{{{relevant_date_period_or_null_flavor}}}
|
|
22
13
|
{{#result}}
|
|
23
14
|
<!-- QDM Attribute: Result -->
|
|
24
15
|
{{{result_value}}}
|
|
@@ -57,5 +48,9 @@
|
|
|
57
48
|
<!-- QDM Attribute: Components -->
|
|
58
49
|
{{> qrda_templates/template_partials/_component}}
|
|
59
50
|
{{/components}}
|
|
51
|
+
{{#encounter_id}}
|
|
52
|
+
<!-- QDM Attribute: relatedTo -->
|
|
53
|
+
{{> qrda_templates/template_partials/_related_to}}
|
|
54
|
+
{{/encounter_id}}
|
|
60
55
|
</observation>
|
|
61
56
|
</entry>
|
|
@@ -8,16 +8,7 @@
|
|
|
8
8
|
{{> _codes}}
|
|
9
9
|
<text>{{description}}</text>
|
|
10
10
|
<statusCode code="completed"/>
|
|
11
|
-
{{
|
|
12
|
-
<!-- QDM Attribute: Relevant dateTime -->
|
|
13
|
-
{{{relevant_date_time_value}}}
|
|
14
|
-
{{/relevantDatetime}}
|
|
15
|
-
{{^relevantDatetime}}
|
|
16
|
-
{{#relevantPeriod}}
|
|
17
|
-
<!-- QDM Attribute: Relevant Period -->
|
|
18
|
-
{{{relevant_period}}}
|
|
19
|
-
{{/relevantPeriod}}
|
|
20
|
-
{{/relevantDatetime}}
|
|
11
|
+
{{{relevant_date_period_or_null_flavor}}}
|
|
21
12
|
{{#method}}
|
|
22
13
|
<!-- QDM Attribute: Method -->
|
|
23
14
|
{{> qrda_templates/template_partials/_method}}
|
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
<templateId root="2.16.840.1.113883.10.20.24.3.75" extension="2019-12-01"/>
|
|
7
7
|
<id root="1.3.6.1.4.1.115" extension="{{object_id}}"/>
|
|
8
8
|
<statusCode code="active"/>
|
|
9
|
+
<effectiveTime xsi:type="IVL_TS">
|
|
10
|
+
<low nullFlavor="NA"/>
|
|
11
|
+
</effectiveTime>
|
|
9
12
|
{{> qrda_templates/template_partials/_medication_details}}
|
|
10
13
|
<consumable>
|
|
11
14
|
<manufacturedProduct classCode="MANU">
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
<templateId root="2.16.840.1.113883.10.20.24.3.88" extension="2017-08-01"/>
|
|
4
4
|
<id root="1.3.6.1.4.1.115" extension="{{random_id}}" />
|
|
5
5
|
<code code="77301-0" codeSystem="2.16.840.1.113883.6.1" displayName="reason" codeSystemName="LOINC"/>
|
|
6
|
-
<statusCode code="completed"/>
|
|
7
6
|
{{#relevantPeriod}}
|
|
8
7
|
{{{relevant_period}}}
|
|
9
8
|
{{/relevantPeriod}}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<!-- US Realm Header Template Id -->
|
|
5
5
|
<templateId root="2.16.840.1.113883.10.20.27.1.1" extension="2017-06-01"/>
|
|
6
6
|
<!-- QRDA Category III Report - CMS (V4) -->
|
|
7
|
-
<templateId root="2.16.840.1.113883.10.20.27.1.2" extension="
|
|
7
|
+
<templateId root="2.16.840.1.113883.10.20.27.1.2" extension="2020-05-01"/>
|
|
8
8
|
<!-- This is the globally unique identifier for this QRDA document -->
|
|
9
9
|
<id root="{{random_id}}"/>
|
|
10
10
|
<!-- QRDA III document type code -->
|
|
@@ -96,20 +96,16 @@ module Qrda
|
|
|
96
96
|
"</effectiveTime>"
|
|
97
97
|
end
|
|
98
98
|
|
|
99
|
-
def relevant_period_as_value
|
|
100
|
-
"<effectiveTime #{value_or_null_flavor(self['relevantPeriod']['low'])}/>"
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def relevant_date_time_low
|
|
104
|
-
"<effectiveTime>"\
|
|
105
|
-
"<low #{value_or_null_flavor(self['relevantDatetime'])}/>"\
|
|
106
|
-
"</effectiveTime>"
|
|
107
|
-
end
|
|
108
|
-
|
|
109
99
|
def relevant_date_time_value
|
|
110
100
|
"<effectiveTime #{value_or_null_flavor(self['relevantDatetime'])}/>"
|
|
111
101
|
end
|
|
112
102
|
|
|
103
|
+
def relevant_date_period_or_null_flavor
|
|
104
|
+
return relevant_period if self['relevantPeriod']
|
|
105
|
+
return relevant_date_time_value if self['relevantDatetime']
|
|
106
|
+
"<effectiveTime nullFlavor='UNK'/>"
|
|
107
|
+
end
|
|
108
|
+
|
|
113
109
|
def medication_duration_effective_time
|
|
114
110
|
"<effectiveTime xsi:type=\"IVL_TS\">"\
|
|
115
111
|
"<low #{value_or_null_flavor(self['relevantPeriod']['low'])}/>"\
|
|
@@ -16,16 +16,19 @@ module Qrda
|
|
|
16
16
|
|
|
17
17
|
def provider_npi
|
|
18
18
|
return nil unless self['ids']
|
|
19
|
+
|
|
19
20
|
self['ids'].map { |id| id if id['namingSystem'] == '2.16.840.1.113883.4.6' }.compact
|
|
20
21
|
end
|
|
21
22
|
|
|
22
23
|
def provider_tin
|
|
23
24
|
return nil unless self['ids']
|
|
25
|
+
|
|
24
26
|
self['ids'].map { |id| id if id['namingSystem'] == '2.16.840.1.113883.4.2' }.compact
|
|
25
27
|
end
|
|
26
28
|
|
|
27
29
|
def provider_ccn
|
|
28
30
|
return nil unless self['ids']
|
|
31
|
+
|
|
29
32
|
self['ids'].map { |id| id if id['namingSystem'] == '2.16.840.1.113883.4.336' }.compact
|
|
30
33
|
end
|
|
31
34
|
|
|
@@ -40,6 +43,42 @@ module Qrda
|
|
|
40
43
|
def given_name
|
|
41
44
|
self['givenNames'].join(' ')
|
|
42
45
|
end
|
|
46
|
+
|
|
47
|
+
def gender
|
|
48
|
+
gender_elements = @qdmPatient.dataElements.select { |de| de._type == "QDM::PatientCharacteristicSex" }
|
|
49
|
+
return if gender_elements.empty?
|
|
50
|
+
gender_elements.first.dataElementCodes.first['code']
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def birthdate
|
|
54
|
+
birthdate_elements = @qdmPatient.dataElements.select { |de| de._type == "QDM::PatientCharacteristicBirthdate" }
|
|
55
|
+
return "None" if birthdate_elements.empty?
|
|
56
|
+
birthdate_elements.first['birthDatetime']
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def expiration
|
|
60
|
+
expired_elements = @qdmPatient.dataElements.select { |de| de._type == "QDM::PatientCharacteristicExpired" }
|
|
61
|
+
return "None" if expired_elements.empty?
|
|
62
|
+
expired_elements.first['expiredDatetime']
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def race
|
|
66
|
+
race_elements = @qdmPatient.dataElements.select { |de| de._type == "QDM::PatientCharacteristicRace" }
|
|
67
|
+
return if race_elements.empty?
|
|
68
|
+
race_elements.first.dataElementCodes.first['code']
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def ethnic_group
|
|
72
|
+
ethnic_elements = @qdmPatient.dataElements.select { |de| de._type == "QDM::PatientCharacteristicEthnicity" }
|
|
73
|
+
return if ethnic_elements.empty?
|
|
74
|
+
ethnic_elements.first.dataElementCodes.first['code']
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def payer
|
|
78
|
+
payer_elements = @qdmPatient.dataElements.select { |de| de._type == "QDM::PatientCharacteristicPayer" }
|
|
79
|
+
return if payer_elements.empty?
|
|
80
|
+
payer_elements.first.dataElementCodes.first['code']
|
|
81
|
+
end
|
|
43
82
|
end
|
|
44
83
|
end
|
|
45
84
|
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module QRDA
|
|
2
2
|
module Cat1
|
|
3
3
|
module DemographicsImporter
|
|
4
|
-
def get_demographics(patient, doc)
|
|
4
|
+
def get_demographics(patient, doc, codes)
|
|
5
5
|
patient_role_element = doc.at_xpath('/cda:ClinicalDocument/cda:recordTarget/cda:patientRole')
|
|
6
6
|
patient_element = patient_role_element.at_xpath('./cda:patient')
|
|
7
7
|
patient.givenNames = [patient_element.at_xpath('cda:name/cda:given').text]
|
|
@@ -9,28 +9,29 @@ module QRDA
|
|
|
9
9
|
patient.qdmPatient.birthDatetime = DateTime.parse(patient_element.at_xpath('cda:birthTime')['value'])
|
|
10
10
|
pcbd = QDM::PatientCharacteristicBirthdate.new
|
|
11
11
|
pcbd.birthDatetime = patient.qdmPatient.birthDatetime
|
|
12
|
-
pcbd.dataElementCodes = [
|
|
12
|
+
pcbd.dataElementCodes = [QDM::Code.new('21112-8', '2.16.840.1.113883.6.1')]
|
|
13
|
+
codes.add("21112-8:2.16.840.1.113883.6.1")
|
|
13
14
|
patient.qdmPatient.dataElements << pcbd
|
|
14
15
|
|
|
15
16
|
pcs = QDM::PatientCharacteristicSex.new
|
|
16
17
|
code_element = patient_element.at_xpath('cda:administrativeGenderCode')
|
|
17
|
-
pcs.dataElementCodes = [code_if_present(code_element)]
|
|
18
|
+
pcs.dataElementCodes = [code_if_present(code_element, codes)]
|
|
18
19
|
patient.qdmPatient.dataElements << pcs unless pcs.dataElementCodes.compact.blank?
|
|
19
20
|
|
|
20
21
|
pcr = QDM::PatientCharacteristicRace.new
|
|
21
22
|
code_element = patient_element.at_xpath('cda:raceCode')
|
|
22
|
-
pcr.dataElementCodes = [code_if_present(code_element)]
|
|
23
|
+
pcr.dataElementCodes = [code_if_present(code_element, codes)]
|
|
23
24
|
patient.qdmPatient.dataElements << pcr unless pcr.dataElementCodes.compact.blank?
|
|
24
25
|
|
|
25
26
|
pce = QDM::PatientCharacteristicEthnicity.new
|
|
26
27
|
code_element = patient_element.at_xpath('cda:ethnicGroupCode')
|
|
27
|
-
pce.dataElementCodes = [code_if_present(code_element)]
|
|
28
|
+
pce.dataElementCodes = [code_if_present(code_element, codes)]
|
|
28
29
|
patient.qdmPatient.dataElements << pce unless pce.dataElementCodes.compact.blank?
|
|
29
30
|
end
|
|
30
31
|
|
|
31
|
-
def code_if_present(code_element)
|
|
32
|
+
def code_if_present(code_element, codes)
|
|
32
33
|
return unless code_element && code_element['code'] && code_element['codeSystem']
|
|
33
|
-
|
|
34
|
+
codes.add("#{code_element['code']}:#{code_element['codeSystem']}")
|
|
34
35
|
QDM::Code.new(code_element['code'], code_element['codeSystem'])
|
|
35
36
|
end
|
|
36
37
|
end
|
|
@@ -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, :codes_modifiers
|
|
5
5
|
|
|
6
6
|
def initialize(entry_finder)
|
|
7
7
|
@entry_finder = entry_finder
|
|
@@ -9,6 +9,9 @@ module QRDA
|
|
|
9
9
|
@entry_id_map = {}
|
|
10
10
|
@check_for_usable = true
|
|
11
11
|
@entry_class = QDM::DataElement
|
|
12
|
+
@warnings = []
|
|
13
|
+
@codes = Set.new
|
|
14
|
+
@codes_modifiers = {}
|
|
12
15
|
end
|
|
13
16
|
|
|
14
17
|
# Traverses an HL7 CDA document passed in and creates an Array of Entry
|
|
@@ -41,7 +44,7 @@ module QRDA
|
|
|
41
44
|
# This is the id found in the QRDA file
|
|
42
45
|
entry_qrda_id = extract_id(entry_element, @id_xpath)
|
|
43
46
|
# 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.
|
|
47
|
+
# that represent the same event.
|
|
45
48
|
@entry_id_map["#{entry_qrda_id.value}_#{entry_qrda_id.namingSystem}"] ||= []
|
|
46
49
|
@entry_id_map["#{entry_qrda_id.value}_#{entry_qrda_id.namingSystem}"] << entry.id
|
|
47
50
|
entry.dataElementCodes = extract_codes(entry_element, @code_xpath)
|
|
@@ -80,6 +83,7 @@ module QRDA
|
|
|
80
83
|
|
|
81
84
|
def code_if_present(code_element)
|
|
82
85
|
return unless code_element && code_element['code'] && code_element['codeSystem']
|
|
86
|
+
@codes.add("#{code_element['code']}:#{code_element['codeSystem']}")
|
|
83
87
|
QDM::Code.new(code_element['code'], code_element['codeSystem'])
|
|
84
88
|
end
|
|
85
89
|
|
|
@@ -93,6 +97,9 @@ module QRDA
|
|
|
93
97
|
end
|
|
94
98
|
|
|
95
99
|
def extract_interval(parent_element, interval_xpath)
|
|
100
|
+
# nil if the time interval does not exist
|
|
101
|
+
return nil unless time_interval_exists(parent_element, interval_xpath)
|
|
102
|
+
|
|
96
103
|
if parent_element.at_xpath("#{interval_xpath}/@value")
|
|
97
104
|
low_time = DateTime.parse(parent_element.at_xpath(interval_xpath)['value'])
|
|
98
105
|
high_time = DateTime.parse(parent_element.at_xpath(interval_xpath)['value'])
|
|
@@ -111,9 +118,32 @@ module QRDA
|
|
|
111
118
|
low_time = Time.parse(parent_element.at_xpath("#{interval_xpath}/cda:center")['value'])
|
|
112
119
|
high_time = Time.parse(parent_element.at_xpath("#{interval_xpath}/cda:center")['value'])
|
|
113
120
|
end
|
|
121
|
+
if low_time && high_time && low_time > high_time
|
|
122
|
+
# pass warning: current code continues as expected, but adds warning
|
|
123
|
+
id_attr = parent_element.at_xpath(".//cda:id")&.attributes
|
|
124
|
+
id_str = id_attr ? "and id: #{id_attr['root']&.value}(root), #{id_attr['extension']&.value}(extension)" : ""
|
|
125
|
+
qrda_type = @entry_class.to_s.split("::")[1]
|
|
126
|
+
@warnings << ValidationError.new(message: "Interval with low time after high time. Located in element with QRDA type: #{qrda_type} #{id_str}",
|
|
127
|
+
location: parent_element.path)
|
|
128
|
+
end
|
|
129
|
+
if low_time.nil? && high_time.nil?
|
|
130
|
+
id_attr = parent_element.at_xpath(".//cda:id")&.attributes
|
|
131
|
+
id_str = id_attr ? "and id: #{id_attr['root']&.value}(root), #{id_attr['extension']&.value}(extension)" : ""
|
|
132
|
+
qrda_type = @entry_class.to_s.split("::")[1]
|
|
133
|
+
@warnings << ValidationError.new(message: "Interval with nullFlavor low time and nullFlavor high time. Located in element with QRDA type: #{qrda_type} #{id_str}",
|
|
134
|
+
location: parent_element.path)
|
|
135
|
+
end
|
|
114
136
|
QDM::Interval.new(low_time, high_time).shift_dates(0)
|
|
115
137
|
end
|
|
116
138
|
|
|
139
|
+
def time_interval_exists(parent_element, interval_xpath)
|
|
140
|
+
# false if the time interval does not exist
|
|
141
|
+
return false unless parent_element.at_xpath(interval_xpath)
|
|
142
|
+
# false if the time element exists but has a null Flavor
|
|
143
|
+
return false if parent_element.at_xpath(interval_xpath)['nullFlavor']
|
|
144
|
+
true
|
|
145
|
+
end
|
|
146
|
+
|
|
117
147
|
def extract_time(parent_element, datetime_xpath)
|
|
118
148
|
DateTime.parse(parent_element.at_xpath(datetime_xpath)['value']) if parent_element.at_xpath("#{datetime_xpath}/@value")
|
|
119
149
|
end
|
|
@@ -128,6 +158,7 @@ module QRDA
|
|
|
128
158
|
# If a Direct Reference Code isn't found, return nil
|
|
129
159
|
return nil unless key
|
|
130
160
|
# If a Direct Reference Code is found, return that code
|
|
161
|
+
@codes.add("#{key}:#{value[:code_system]}")
|
|
131
162
|
QDM::Code.new(key, value[:code_system])
|
|
132
163
|
end
|
|
133
164
|
|
|
@@ -153,7 +184,7 @@ module QRDA
|
|
|
153
184
|
parent_element.xpath(@result_xpath).each do |elem|
|
|
154
185
|
result << extract_result_value(elem)
|
|
155
186
|
end
|
|
156
|
-
result.size > 1 ? result : result.first
|
|
187
|
+
result.size > 1 ? result : result.first
|
|
157
188
|
end
|
|
158
189
|
|
|
159
190
|
def extract_result_value(value_element)
|
|
@@ -166,6 +197,11 @@ module QRDA
|
|
|
166
197
|
elsif value_element['code'].present?
|
|
167
198
|
return code_if_present(value_element)
|
|
168
199
|
elsif value_element.text.present?
|
|
200
|
+
id_attr = value_element.parent.at_xpath(".//cda:id")&.attributes
|
|
201
|
+
id_str = id_attr ? "and id: #{id_attr['root']&.value}(root), #{id_attr['extension']&.value}(extension)" : ""
|
|
202
|
+
qrda_type = @entry_class.to_s.split("::")[1]
|
|
203
|
+
@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}",
|
|
204
|
+
location: value_element.path)
|
|
169
205
|
return value_element.text
|
|
170
206
|
end
|
|
171
207
|
end
|
|
@@ -176,16 +212,16 @@ module QRDA
|
|
|
176
212
|
negation_indicator = parent_element['negationInd']
|
|
177
213
|
# Return and do not set reason attribute if the entry is negated
|
|
178
214
|
return nil if negation_indicator.eql?('true')
|
|
179
|
-
|
|
180
|
-
reason_element.blank? ? nil : code_if_present(reason_element.first)
|
|
215
|
+
|
|
216
|
+
reason_element.blank? ? nil : code_if_present(reason_element.first)
|
|
181
217
|
end
|
|
182
218
|
|
|
183
219
|
def extract_negation(parent_element, entry)
|
|
184
220
|
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")
|
|
185
221
|
negation_indicator = parent_element['negationInd']
|
|
186
222
|
# Return and do not set negationRationale attribute if the entry is not negated
|
|
187
|
-
return unless negation_indicator.eql?('true')
|
|
188
|
-
|
|
223
|
+
return unless negation_indicator.eql?('true')
|
|
224
|
+
|
|
189
225
|
entry.negationRationale = code_if_present(negation_element.first) unless negation_element.blank?
|
|
190
226
|
extract_negated_code(parent_element, entry)
|
|
191
227
|
end
|
|
@@ -193,8 +229,18 @@ module QRDA
|
|
|
193
229
|
def extract_negated_code(parent_element, entry)
|
|
194
230
|
code_elements = parent_element.xpath(@code_xpath)
|
|
195
231
|
code_elements.each do |code_element|
|
|
196
|
-
if code_element['nullFlavor'] == 'NA'
|
|
197
|
-
|
|
232
|
+
if code_element['nullFlavor'] == 'NA'
|
|
233
|
+
if code_element['sdtc:valueSet']
|
|
234
|
+
entry.dataElementCodes = [{ code: code_element['sdtc:valueSet'], system: '1.2.3.4.5.6.7.8.9.10' }]
|
|
235
|
+
else
|
|
236
|
+
# negated code is nullFlavored with no valueset
|
|
237
|
+
entry.dataElementCodes = [{ code: "NA", system: '1.2.3.4.5.6.7.8.9.10' }]
|
|
238
|
+
id_attr = parent_element.at_xpath(".//cda:id")&.attributes
|
|
239
|
+
id_str = id_attr ? "and id: #{id_attr['root']&.value}(root), #{id_attr['extension']&.value}(extension)" : ""
|
|
240
|
+
qrda_type = @entry_class.to_s.split("::")[1]
|
|
241
|
+
@warnings << ValidationError.new(message: "Negated code element contains nullFlavor code but no valueset. Located in element with QRDA type: #{qrda_type} #{id_str}.",
|
|
242
|
+
location: parent_element.path)
|
|
243
|
+
end
|
|
198
244
|
end
|
|
199
245
|
end
|
|
200
246
|
end
|
|
@@ -226,7 +272,7 @@ module QRDA
|
|
|
226
272
|
participant_element = facility_location_element.at_xpath("./cda:participantRole[@classCode='SDLOC']/cda:code")
|
|
227
273
|
facility_location.code = code_if_present(participant_element)
|
|
228
274
|
facility_location.locationPeriod = extract_interval(facility_location_element, './cda:time')
|
|
229
|
-
facility_locations << facility_location
|
|
275
|
+
facility_locations << facility_location if facility_location.code
|
|
230
276
|
end
|
|
231
277
|
facility_locations
|
|
232
278
|
end
|
|
@@ -21,7 +21,7 @@ module QRDA
|
|
|
21
21
|
def create_entry(entry_element, nrh = NarrativeReferenceHandler.new)
|
|
22
22
|
diagnostic_study_performed = super
|
|
23
23
|
diagnostic_study_performed.resultDatetime = extract_time(entry_element, @result_datetime_xpath)
|
|
24
|
-
diagnostic_study_performed.resultDatetime ||= extract_interval(entry_element, @result_datetime_xpath)
|
|
24
|
+
diagnostic_study_performed.resultDatetime ||= extract_interval(entry_element, @result_datetime_xpath)&.low
|
|
25
25
|
diagnostic_study_performed.status = code_if_present(entry_element.at_xpath(@status_xpath))
|
|
26
26
|
diagnostic_study_performed.method = code_if_present(entry_element.at_xpath(@method_xpath))
|
|
27
27
|
diagnostic_study_performed.facilityLocation = extract_facility_locations(entry_element)[0]
|
|
@@ -21,15 +21,25 @@ module QRDA
|
|
|
21
21
|
encounter_performed.facilityLocations = extract_facility_locations(entry_element)
|
|
22
22
|
encounter_performed.diagnoses = extract_diagnoses(entry_element)
|
|
23
23
|
if encounter_performed&.relevantPeriod&.low && encounter_performed&.relevantPeriod&.high
|
|
24
|
-
los = encounter_performed.relevantPeriod.high - encounter_performed.relevantPeriod.low
|
|
24
|
+
los = encounter_performed.relevantPeriod.high.to_date - encounter_performed.relevantPeriod.low.to_date
|
|
25
25
|
encounter_performed.lengthOfStay = QDM::Quantity.new(los.to_i, 'd')
|
|
26
26
|
end
|
|
27
27
|
encounter_performed.participant = extract_entity(entry_element, "./cda:entryRelationship/cda:encounter//cda:participant[@typeCode='PRF']")
|
|
28
|
+
extract_modifier_code(encounter_performed, entry_element)
|
|
28
29
|
encounter_performed
|
|
29
30
|
end
|
|
30
31
|
|
|
31
32
|
private
|
|
32
33
|
|
|
34
|
+
def extract_modifier_code(encounter_performed, entry_element)
|
|
35
|
+
code_element = entry_element.at_xpath(@code_xpath)
|
|
36
|
+
return unless code_element
|
|
37
|
+
|
|
38
|
+
qualifier_name = code_element.at_xpath('./cda:qualifier/cda:name')
|
|
39
|
+
qualifier_value = code_element.at_xpath('./cda:qualifier/cda:value')
|
|
40
|
+
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
|
|
41
|
+
end
|
|
42
|
+
|
|
33
43
|
def extract_diagnoses(parent_element)
|
|
34
44
|
diagnosis_elements = parent_element.xpath(@diagnosis_xpath)
|
|
35
45
|
diagnosis_list = []
|
|
@@ -3,7 +3,7 @@ module QRDA
|
|
|
3
3
|
class EncounterRecommendedImporter < SectionImporter
|
|
4
4
|
def initialize(entry_finder = QRDA::Cat1::EntryFinder.new("./cda:entry/cda:act[cda:templateId/@root = '2.16.840.1.113883.10.20.24.3.134']"))
|
|
5
5
|
super(entry_finder)
|
|
6
|
-
@id_xpath = './cda:id'
|
|
6
|
+
@id_xpath = './cda:entryRelationship/cda:encounter/cda:id'
|
|
7
7
|
@code_xpath = "./cda:entryRelationship/cda:encounter[cda:templateId/@root = '2.16.840.1.113883.10.20.24.3.24']/cda:code"
|
|
8
8
|
@author_datetime_xpath = "./cda:entryRelationship/cda:encounter[cda:templateId/@root = '2.16.840.1.113883.10.20.24.3.24']/cda:author/cda:time"
|
|
9
9
|
@facility_locations_xpath = "./cda:entryRelationship/cda:encounter[cda:templateId/@root = '2.16.840.1.113883.10.20.24.3.24']/cda:participant[cda:templateId/@root = '2.16.840.1.113883.10.20.24.3.100']"
|
|
@@ -3,7 +3,7 @@ module QRDA
|
|
|
3
3
|
class MedicationDischargeImporter < SectionImporter
|
|
4
4
|
def initialize(entry_finder = QRDA::Cat1::EntryFinder.new("./cda:entry/cda:act[cda:templateId/@root = '2.16.840.1.113883.10.20.24.3.105']"))
|
|
5
5
|
super(entry_finder)
|
|
6
|
-
@id_xpath = './cda:id'
|
|
6
|
+
@id_xpath = './cda:entryRelationship/cda:substanceAdministration/cda:id'
|
|
7
7
|
@code_xpath = "./cda:entryRelationship/cda:substanceAdministration[cda:templateId/@root = '2.16.840.1.113883.10.20.22.4.16']/cda:consumable/cda:manufacturedProduct/cda:manufacturedMaterial/cda:code"
|
|
8
8
|
@author_datetime_xpath = "./cda:entryRelationship/cda:substanceAdministration[cda:templateId/@root = '2.16.840.1.113883.10.20.22.4.16']/cda:author/cda:time"
|
|
9
9
|
@refills_xpath = "./cda:entryRelationship/cda:substanceAdministration[cda:templateId/@root = '2.16.840.1.113883.10.20.22.4.16']/cda:repeatNumber"
|
|
@@ -3,7 +3,7 @@ module QRDA
|
|
|
3
3
|
class MedicationDispensedImporter < SectionImporter
|
|
4
4
|
def initialize(entry_finder = QRDA::Cat1::EntryFinder.new("./cda:entry/cda:act[cda:templateId/@root = '2.16.840.1.113883.10.20.24.3.139']"))
|
|
5
5
|
super(entry_finder)
|
|
6
|
-
@id_xpath = './cda:id'
|
|
6
|
+
@id_xpath = './cda:entryRelationship/cda:supply/cda:id'
|
|
7
7
|
@code_xpath = "./cda:entryRelationship/cda:supply[cda:templateId/@root = '2.16.840.1.113883.10.20.22.4.18']/cda:product/cda:manufacturedProduct/cda:manufacturedMaterial/cda:code"
|
|
8
8
|
@relevant_period_xpath = "./cda:entryRelationship/cda:supply[cda:templateId/@root = '2.16.840.1.113883.10.20.22.4.18']/cda:effectiveTime"
|
|
9
9
|
@relevant_date_time_xpath = "./cda:entryRelationship/cda:supply[cda:templateId/@root = '2.16.840.1.113883.10.20.22.4.18']/cda:effectiveTime"
|
|
@@ -12,70 +12,73 @@ 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 <<
|
|
61
|
-
@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 << ProgramParticipationImporter.new
|
|
56
|
+
@data_element_importers << ProviderCareExperienceImporter.new
|
|
57
|
+
@data_element_importers << RelatedPersonImporter.new
|
|
58
|
+
@data_element_importers << SubstanceAdministeredImporter.new
|
|
59
|
+
@data_element_importers << SubstanceOrderImporter.new
|
|
60
|
+
@data_element_importers << SubstanceRecommendedImporter.new
|
|
61
|
+
@data_element_importers << SymptomImporter.new
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
def parse_cat1(doc)
|
|
65
65
|
patient = CQM::Patient.new
|
|
66
|
+
warnings = []
|
|
67
|
+
codes = Set.new
|
|
68
|
+
codes_modifiers = {}
|
|
66
69
|
entry_id_map = {}
|
|
67
|
-
import_data_elements(patient, doc, entry_id_map)
|
|
70
|
+
import_data_elements(patient, doc, entry_id_map, codes, codes_modifiers, warnings)
|
|
68
71
|
normalize_references(patient, entry_id_map)
|
|
69
|
-
get_demographics(patient, doc)
|
|
70
|
-
patient
|
|
72
|
+
get_demographics(patient, doc, codes)
|
|
73
|
+
[patient, warnings, codes, codes_modifiers]
|
|
71
74
|
end
|
|
72
75
|
|
|
73
|
-
def import_data_elements(patient, doc, entry_id_map)
|
|
76
|
+
def import_data_elements(patient, doc, entry_id_map, codes = Set.new, codes_modifiers = {}, warnings = [])
|
|
74
77
|
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']")
|
|
75
78
|
nrh = NarrativeReferenceHandler.new
|
|
76
79
|
nrh.build_id_map(doc)
|
|
77
|
-
@data_element_importers.each do |
|
|
78
|
-
data_elements, id_map =
|
|
80
|
+
@data_element_importers.each do |importer|
|
|
81
|
+
data_elements, id_map = importer.create_entries(context, nrh)
|
|
79
82
|
new_data_elements = []
|
|
80
83
|
|
|
81
84
|
id_map.each_value do |elem_ids|
|
|
@@ -92,7 +95,7 @@ module QRDA
|
|
|
92
95
|
unique_element_keys << key_elements_for_determining_encounter_uniqueness(data_element)
|
|
93
96
|
|
|
94
97
|
# Loop through all other data elements with the same id
|
|
95
|
-
elem_ids[1,elem_ids.length].each do |dup_id|
|
|
98
|
+
elem_ids[1,elem_ids.length].each do |dup_id|
|
|
96
99
|
dup_element = data_elements.find { |de| de.id == dup_id }
|
|
97
100
|
dup_element_keys = key_elements_for_determining_encounter_uniqueness(dup_element)
|
|
98
101
|
# See if a previously selected data element shared all of the keys files
|
|
@@ -107,13 +110,20 @@ module QRDA
|
|
|
107
110
|
|
|
108
111
|
patient.qdmPatient.dataElements << new_data_elements
|
|
109
112
|
entry_id_map.merge!(id_map)
|
|
113
|
+
warnings.concat(importer.warnings)
|
|
114
|
+
codes.merge(importer.codes)
|
|
115
|
+
codes_modifiers.merge!(importer.codes_modifiers)
|
|
116
|
+
# reset warnings and codes after they're captured so that the importer can be re-used
|
|
117
|
+
importer.warnings = []
|
|
118
|
+
importer.codes_modifiers = {}
|
|
119
|
+
importer.codes = Set.new
|
|
110
120
|
end
|
|
111
121
|
end
|
|
112
122
|
|
|
113
123
|
def key_elements_for_determining_encounter_uniqueness(encounter)
|
|
114
124
|
codes = encounter.codes.collect { |dec| "#{dec.code}_#{dec.system}" }.sort.to_s
|
|
115
|
-
admission_date_time = encounter
|
|
116
|
-
discharge_date_time = encounter
|
|
125
|
+
admission_date_time = encounter&.relevantPeriod&.low.to_s
|
|
126
|
+
discharge_date_time = encounter&.relevantPeriod&.high.to_s
|
|
117
127
|
"#{codes}#{admission_date_time}#{discharge_date_time}"
|
|
118
128
|
end
|
|
119
129
|
|
|
@@ -131,17 +141,11 @@ module QRDA
|
|
|
131
141
|
|
|
132
142
|
relations_to_add = []
|
|
133
143
|
data_element.relatedTo.each do |related_to|
|
|
134
|
-
relations_to_add += entry_id_map["#{related_to
|
|
144
|
+
relations_to_add += entry_id_map["#{related_to['value']}_#{related_to['namingSystem']}"]
|
|
135
145
|
end
|
|
136
146
|
data_element.relatedTo = relations_to_add.map(&:to_s)
|
|
137
147
|
end
|
|
138
148
|
end
|
|
139
|
-
|
|
140
|
-
private
|
|
141
|
-
|
|
142
|
-
def generate_importer(importer_class)
|
|
143
|
-
EntryPackage.new(importer_class.new)
|
|
144
|
-
end
|
|
145
149
|
end
|
|
146
150
|
end
|
|
147
151
|
end
|
metadata
CHANGED
|
@@ -1,17 +1,31 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cqm-reports
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.1.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- The MITRE Corporation
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2020-
|
|
11
|
+
date: 2020-10-08 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: cqm-models
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: 3.0.3
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: 3.0.3
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: cqm-validators
|
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
|
16
30
|
requirements:
|
|
17
31
|
- - "~>"
|
|
@@ -314,7 +328,6 @@ files:
|
|
|
314
328
|
- lib/qrda-import/data-element-importers/substance_recommended_importer.rb
|
|
315
329
|
- lib/qrda-import/data-element-importers/symptom_importer.rb
|
|
316
330
|
- lib/qrda-import/entry_finder.rb
|
|
317
|
-
- lib/qrda-import/entry_package.rb
|
|
318
331
|
- lib/qrda-import/narrative_reference_handler.rb
|
|
319
332
|
- lib/qrda-import/patient_importer.rb
|
|
320
333
|
- lib/util/code_system_helper.rb
|
|
@@ -330,7 +343,7 @@ homepage: https://github.com/projecttacoma/cqm-reports
|
|
|
330
343
|
licenses:
|
|
331
344
|
- Apache-2.0
|
|
332
345
|
metadata: {}
|
|
333
|
-
post_install_message:
|
|
346
|
+
post_install_message:
|
|
334
347
|
rdoc_options: []
|
|
335
348
|
require_paths:
|
|
336
349
|
- lib
|
|
@@ -345,9 +358,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
345
358
|
- !ruby/object:Gem::Version
|
|
346
359
|
version: '0'
|
|
347
360
|
requirements: []
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
signing_key:
|
|
361
|
+
rubygems_version: 3.1.4
|
|
362
|
+
signing_key:
|
|
351
363
|
specification_version: 4
|
|
352
364
|
summary: A library for import and export of reports for use with electronic Clinical
|
|
353
365
|
Quality Measures (eCQMs).
|