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.
Files changed (34) hide show
  1. checksums.yaml +5 -5
  2. data/Gemfile +1 -2
  3. data/README.md +2 -1
  4. data/lib/cqm-reports.rb +0 -1
  5. data/lib/html-export/qdm-patient/data_element/_data_element_codes.mustache +1 -1
  6. data/lib/html-export/qdm-patient/qdm_patient.mustache +13 -11
  7. data/lib/html-export/qdm-patient/qdm_patient.rb +50 -2
  8. data/lib/qrda-export/catI-r5/qrda_header/_record_target.mustache +1 -1
  9. data/lib/qrda-export/catI-r5/qrda_templates/assessment_performed.mustache +1 -10
  10. data/lib/qrda-export/catI-r5/qrda_templates/device_applied.mustache +1 -10
  11. data/lib/qrda-export/catI-r5/qrda_templates/device_order.mustache +0 -1
  12. data/lib/qrda-export/catI-r5/qrda_templates/diagnostic_study_performed.mustache +1 -10
  13. data/lib/qrda-export/catI-r5/qrda_templates/encounter_recommended.mustache +0 -1
  14. data/lib/qrda-export/catI-r5/qrda_templates/intervention_performed.mustache +1 -10
  15. data/lib/qrda-export/catI-r5/qrda_templates/laboratory_test_performed.mustache +5 -10
  16. data/lib/qrda-export/catI-r5/qrda_templates/medication_discharge.mustache +0 -1
  17. data/lib/qrda-export/catI-r5/qrda_templates/medication_dispensed.mustache +3 -1
  18. data/lib/qrda-export/catI-r5/qrda_templates/physical_exam_performed.mustache +5 -10
  19. data/lib/qrda-export/catI-r5/qrda_templates/procedure_performed.mustache +1 -10
  20. data/lib/qrda-export/catI-r5/qrda_templates/substance_recommended.mustache +3 -0
  21. data/lib/qrda-export/catI-r5/qrda_templates/template_partials/_reason.mustache +0 -1
  22. data/lib/qrda-export/catIII-r2-1/_header.mustache +1 -1
  23. data/lib/qrda-export/helper/date_helper.rb +6 -10
  24. data/lib/qrda-export/helper/patient_view_helper.rb +39 -0
  25. data/lib/qrda-import/base-importers/demographics_importer.rb +8 -7
  26. data/lib/qrda-import/base-importers/section_importer.rb +56 -10
  27. data/lib/qrda-import/data-element-importers/diagnostic_study_performed_importer.rb +1 -1
  28. data/lib/qrda-import/data-element-importers/encounter_performed_importer.rb +11 -1
  29. data/lib/qrda-import/data-element-importers/encounter_recommended_importer.rb +1 -1
  30. data/lib/qrda-import/data-element-importers/medication_discharge_importer.rb +1 -1
  31. data/lib/qrda-import/data-element-importers/medication_dispensed_importer.rb +1 -1
  32. data/lib/qrda-import/patient_importer.rb +67 -63
  33. metadata +20 -8
  34. data/lib/qrda-import/entry_package.rb +0 -16
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 86b46847a0a68ecf86611dce268e5a12f6d01af6
4
- data.tar.gz: 906d1984866e314876ca0d713f6b08a01a6ab8f5
2
+ SHA256:
3
+ metadata.gz: b9895c3e39093e4e3b01d4ab490bd91db2b881b5ff9870b8de0c6369fc792601
4
+ data.tar.gz: ff99962ce100bd5f73bb5bc73299d55bd4b2ed1185de971996292b713baf8060
5
5
  SHA512:
6
- metadata.gz: cc1e458ec8496af2e20bec1068f7acf723976bdfa92610cd2f1b99f36692a5cb7ddb3c6d1648be17704d820d0002b8b32c78d52881ea1d78d71643909ae0499f
7
- data.tar.gz: 9f4fa410245d83fbd1beb11183461a4cd8442859bfc391b7bffd90c5d9afeb2c91cd3ca83d54e9bc7c6a49f8ad9b6c9fe55230167161fd03cc4fc188066aec95
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', git: 'https://github.com/projecttacoma/cqm-models', branch: 'master'
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
  ==========
@@ -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: {{{given_name}}} {{familyName}}</title>
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: {{{given_name}}} {{familyName}}</h1>
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">{{{given_name}}} {{familyName}}</div>
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"></div>
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">Contact info</span></div>
43
- <div class="div-table-head"></div>
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']} " unless self['unit']
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']
@@ -18,7 +18,7 @@
18
18
  <patient>
19
19
  {{#patient}}
20
20
  <name>
21
- <given>{{{given_name}}}</given>
21
+ <given>{{given_name}}</given>
22
22
  <family>{{familyName}}</family>
23
23
  </name>
24
24
  {{/patient}}
@@ -6,16 +6,7 @@
6
6
  {{> _codes}}
7
7
  <text>{{description}}</text>
8
8
  <statusCode code="completed"/>
9
- {{#relevantDatetime}}
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
- {{#relevantDatetime}}
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
- {{#relevantDatetime}}
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
- {{#relevantDatetime}}
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
- {{#relevantDatetime}}
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
- {{#relevantDatetime}}
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
- {{#relevantDatetime}}
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="2019-05-01"/>
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 = [{ code: '21112-8', system: '2.16.840.1.113883.6.1' }]
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' && code_element['sdtc:valueSet']
197
- entry.dataElementCodes = [{ code: code_element['sdtc:valueSet'], system: '1.2.3.4.5.6.7.8.9.10' }]
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).low
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 << 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(ProgramParticipationImporter)
56
- @data_element_importers << generate_importer(ProviderCareExperienceImporter)
57
- @data_element_importers << generate_importer(RelatedPersonImporter)
58
- @data_element_importers << generate_importer(SubstanceAdministeredImporter)
59
- @data_element_importers << generate_importer(SubstanceOrderImporter)
60
- @data_element_importers << generate_importer(SubstanceRecommendedImporter)
61
- @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 << 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 |entry_package|
78
- data_elements, id_map = entry_package.package_entries(context, nrh)
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.relevantPeriod.low.to_s
116
- discharge_date_time = encounter.relevantPeriod.high.to_s
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.value}_#{related_to.namingSystem}"]
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.0.0
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-05-11 00:00:00.000000000 Z
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
- rubyforge_project:
349
- rubygems_version: 2.6.14
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).
@@ -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