cqm-parsers 0.2.4 → 3.1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/Gemfile +8 -4
- data/README.md +57 -5
- data/Rakefile +1 -0
- data/lib/{hqmf-parser.rb → cqm-parsers.rb} +13 -45
- data/lib/ext/data_element.rb +1 -1
- data/lib/hqmf-parser/2.0/document.rb +1 -1
- data/lib/hqmf-parser/2.0/population_criteria.rb +1 -1
- data/lib/hqmf-parser/cql/document_helpers/doc_population_helper.rb +11 -6
- data/lib/measure-loader/cql_loader.rb +165 -0
- data/lib/measure-loader/elm_dependency_finder.rb +72 -0
- data/lib/measure-loader/elm_parser.rb +67 -0
- data/lib/measure-loader/exceptions.rb +10 -0
- data/lib/measure-loader/helpers.rb +11 -0
- data/lib/measure-loader/hqmf_measure_loader.rb +170 -0
- data/lib/measure-loader/mat_measure_files.rb +138 -0
- data/lib/measure-loader/source_data_criteria_loader.rb +75 -0
- data/lib/measure-loader/value_set_helpers.rb +68 -0
- data/lib/measure-loader/vsac_value_set_loader.rb +97 -0
- data/lib/tasks/hqmf.rake +1 -1
- data/lib/util/util.rb +23 -0
- data/lib/util/vsac_api.rb +166 -103
- metadata +63 -131
- data/lib/ext/code.rb +0 -10
- data/lib/qrda-export/catI-r5/_code.mustache +0 -1
- data/lib/qrda-export/catI-r5/_codes.mustache +0 -10
- data/lib/qrda-export/catI-r5/_header.mustache +0 -28
- data/lib/qrda-export/catI-r5/_measure_section.mustache +0 -59
- data/lib/qrda-export/catI-r5/_reporting_period.mustache +0 -23
- data/lib/qrda-export/catI-r5/_values.mustache +0 -10
- data/lib/qrda-export/catI-r5/qrda1_r5.mustache +0 -137
- data/lib/qrda-export/catI-r5/qrda1_r5.rb +0 -125
- data/lib/qrda-export/catI-r5/qrda_header/_author.mustache +0 -24
- data/lib/qrda-export/catI-r5/qrda_header/_custodian.mustache +0 -43
- data/lib/qrda-export/catI-r5/qrda_header/_documentation_of_service_event.mustache +0 -82
- data/lib/qrda-export/catI-r5/qrda_header/_information_recipient.mustache +0 -7
- data/lib/qrda-export/catI-r5/qrda_header/_legal_authenticator.mustache +0 -25
- data/lib/qrda-export/catI-r5/qrda_header/_participant.mustache +0 -7
- data/lib/qrda-export/catI-r5/qrda_header/_record_target.mustache +0 -28
- data/lib/qrda-export/catI-r5/qrda_templates/adverse_event.mustache +0 -28
- data/lib/qrda-export/catI-r5/qrda_templates/allergy_intolerance.mustache +0 -28
- data/lib/qrda-export/catI-r5/qrda_templates/assessment_performed.mustache +0 -25
- data/lib/qrda-export/catI-r5/qrda_templates/communication_from_patient_to_provider.mustache +0 -29
- data/lib/qrda-export/catI-r5/qrda_templates/communication_from_provider_to_patient.mustache +0 -24
- data/lib/qrda-export/catI-r5/qrda_templates/communication_from_provider_to_provider.mustache +0 -31
- data/lib/qrda-export/catI-r5/qrda_templates/device_applied.mustache +0 -32
- data/lib/qrda-export/catI-r5/qrda_templates/device_ordered.mustache +0 -31
- data/lib/qrda-export/catI-r5/qrda_templates/diagnosis.mustache +0 -38
- data/lib/qrda-export/catI-r5/qrda_templates/diagnostic_study_ordered.mustache +0 -19
- data/lib/qrda-export/catI-r5/qrda_templates/diagnostic_study_performed.mustache +0 -32
- data/lib/qrda-export/catI-r5/qrda_templates/encounter_ordered.mustache +0 -24
- data/lib/qrda-export/catI-r5/qrda_templates/encounter_performed.mustache +0 -40
- data/lib/qrda-export/catI-r5/qrda_templates/immunization_administered.mustache +0 -29
- data/lib/qrda-export/catI-r5/qrda_templates/insurance_provider.mustache +0 -11
- data/lib/qrda-export/catI-r5/qrda_templates/intervention_ordered.mustache +0 -18
- data/lib/qrda-export/catI-r5/qrda_templates/intervention_performed.mustache +0 -25
- data/lib/qrda-export/catI-r5/qrda_templates/lab_test_ordered.mustache +0 -18
- data/lib/qrda-export/catI-r5/qrda_templates/lab_test_performed.mustache +0 -22
- data/lib/qrda-export/catI-r5/qrda_templates/medication_active.mustache +0 -35
- data/lib/qrda-export/catI-r5/qrda_templates/medication_administered.mustache +0 -31
- data/lib/qrda-export/catI-r5/qrda_templates/medication_discharge.mustache +0 -55
- data/lib/qrda-export/catI-r5/qrda_templates/medication_dispensed.mustache +0 -39
- data/lib/qrda-export/catI-r5/qrda_templates/medication_ordered.mustache +0 -38
- data/lib/qrda-export/catI-r5/qrda_templates/patient_characteristic_expired.mustache +0 -16
- data/lib/qrda-export/catI-r5/qrda_templates/physical_exam_performed.mustache +0 -25
- data/lib/qrda-export/catI-r5/qrda_templates/procedure_ordered.mustache +0 -19
- data/lib/qrda-export/catI-r5/qrda_templates/procedure_performed.mustache +0 -44
- data/lib/qrda-export/catI-r5/qrda_templates/template_partials/_admission_source.mustache +0 -6
- data/lib/qrda-export/catI-r5/qrda_templates/template_partials/_anatomical_location_site.mustache +0 -1
- data/lib/qrda-export/catI-r5/qrda_templates/template_partials/_author.mustache +0 -7
- data/lib/qrda-export/catI-r5/qrda_templates/template_partials/_author_participation.mustache +0 -7
- data/lib/qrda-export/catI-r5/qrda_templates/template_partials/_component.mustache +0 -11
- data/lib/qrda-export/catI-r5/qrda_templates/template_partials/_encounter_diagnosis.mustache +0 -19
- data/lib/qrda-export/catI-r5/qrda_templates/template_partials/_encounter_facility_location.mustache +0 -16
- data/lib/qrda-export/catI-r5/qrda_templates/template_partials/_mediation_frequency.mustache +0 -3
- data/lib/qrda-export/catI-r5/qrda_templates/template_partials/_medication_details.mustache +0 -11
- data/lib/qrda-export/catI-r5/qrda_templates/template_partials/_ordinality.mustache +0 -1
- data/lib/qrda-export/catI-r5/qrda_templates/template_partials/_principal_diagnosis.mustache +0 -8
- data/lib/qrda-export/catI-r5/qrda_templates/template_partials/_reason.mustache +0 -12
- data/lib/qrda-export/catI-r5/qrda_templates/template_partials/_related_to.mustache +0 -6
- data/lib/qrda-export/catI-r5/qrda_templates/template_partials/_results.mustache +0 -19
- data/lib/qrda-export/catI-r5/qrda_templates/template_partials/_severity.mustache +0 -8
- data/lib/qrda-export/helper/cat_1_view_helper.rb +0 -150
- data/lib/qrda-export/helper/code_system_helper.rb +0 -77
- data/lib/qrda-export/helper/date_helper.rb +0 -89
- data/lib/qrda-import/base-importers/demographics_importer.rb +0 -49
- data/lib/qrda-import/base-importers/medication_importer.rb +0 -23
- data/lib/qrda-import/base-importers/section_importer.rb +0 -203
- data/lib/qrda-import/cda_identifier.rb +0 -19
- data/lib/qrda-import/data-element-importers/adverse_event_importer.rb +0 -24
- data/lib/qrda-import/data-element-importers/allergy_intolerance_importer.rb +0 -22
- data/lib/qrda-import/data-element-importers/assessment_performed_importer.rb +0 -26
- data/lib/qrda-import/data-element-importers/communication_from_patient_to_provider_importer.rb +0 -20
- data/lib/qrda-import/data-element-importers/communication_from_provider_to_patient_importer.rb +0 -20
- data/lib/qrda-import/data-element-importers/communication_from_provider_to_provider_importer.rb +0 -22
- data/lib/qrda-import/data-element-importers/device_applied_importer.rb +0 -26
- data/lib/qrda-import/data-element-importers/device_order_importer.rb +0 -21
- data/lib/qrda-import/data-element-importers/diagnosis_importer.rb +0 -24
- data/lib/qrda-import/data-element-importers/diagnostic_study_order_importer.rb +0 -23
- data/lib/qrda-import/data-element-importers/diagnostic_study_performed_importer.rb +0 -33
- data/lib/qrda-import/data-element-importers/encounter_order_importer.rb +0 -23
- data/lib/qrda-import/data-element-importers/encounter_performed_importer.rb +0 -42
- data/lib/qrda-import/data-element-importers/immunization_administered_importer.rb +0 -20
- data/lib/qrda-import/data-element-importers/intervention_order_importer.rb +0 -21
- data/lib/qrda-import/data-element-importers/intervention_performed_importer.rb +0 -25
- data/lib/qrda-import/data-element-importers/laboratory_test_order_importer.rb +0 -23
- data/lib/qrda-import/data-element-importers/laboratory_test_performed_importer.rb +0 -31
- data/lib/qrda-import/data-element-importers/medication_active_importer.rb +0 -17
- data/lib/qrda-import/data-element-importers/medication_administered_importer.rb +0 -19
- data/lib/qrda-import/data-element-importers/medication_discharge_importer.rb +0 -19
- data/lib/qrda-import/data-element-importers/medication_dispensed_importer.rb +0 -19
- data/lib/qrda-import/data-element-importers/medication_order_importer.rb +0 -18
- data/lib/qrda-import/data-element-importers/patient_characteristic_expired.rb +0 -22
- data/lib/qrda-import/data-element-importers/physical_exam_performed_importer.rb +0 -29
- data/lib/qrda-import/data-element-importers/procedure_order_importer.rb +0 -29
- data/lib/qrda-import/data-element-importers/procedure_performed_importer.rb +0 -37
- data/lib/qrda-import/data-element-importers/substance_administered_importer.rb +0 -17
- data/lib/qrda-import/entry_finder.rb +0 -20
- data/lib/qrda-import/entry_package.rb +0 -16
- data/lib/qrda-import/narrative_reference_handler.rb +0 -33
- data/lib/qrda-import/patient_importer.rb +0 -111
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 6cc1a450d55c667cc60b8258650bc2c25f7a5c451ca9578401bc9593942ac31f
|
4
|
+
data.tar.gz: b18993da420a5d52f2c1e540329d48bc923aaaccd650e2c7a8f89f9310abd171
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8d42ea3fa538e7bdb5dcbd1503127a10728319371d8a0c36dd7fb1ce2a6c280b624cefb2f88991ec96ef65522631d775871a5679271b579331078b91cb17e625
|
7
|
+
data.tar.gz: e095fe8c77601c1bdc83c585ff5ef8fa287408cde02be8e27e67a0adc8741c113d80692c64cfeff3bbd3ddba0f0b494bc76b4960655987ee71a63d2bc119f443
|
data/Gemfile
CHANGED
@@ -2,17 +2,22 @@ source 'https://rubygems.org'
|
|
2
2
|
|
3
3
|
gemspec :development_group => :test
|
4
4
|
|
5
|
-
gem '
|
6
|
-
|
5
|
+
gem 'mongoid', '~> 6.4.2'
|
6
|
+
|
7
|
+
# gem 'cqm-models', '~> 3.0.0'
|
8
|
+
# gem 'cqm-models', git: 'https://github.com/projecttacoma/cqm-models.git', branch: 'master'
|
9
|
+
# gem 'cqm-models', :path => '../cqm-models'
|
7
10
|
|
8
11
|
group :development, :test do
|
9
12
|
gem 'bundler-audit'
|
10
13
|
gem 'rubocop', '~> 0.52.1', require: false
|
14
|
+
gem 'byebug', '~> 6.0.2', platforms: [:ruby_20, :ruby_21, :ruby_22, :ruby_23]
|
15
|
+
gem 'pry'
|
16
|
+
gem 'pry-nav'
|
11
17
|
end
|
12
18
|
|
13
19
|
group :development do
|
14
20
|
gem 'rake'
|
15
|
-
gem 'byebug', '~> 6.0.2', platforms: [:ruby_20, :ruby_21, :ruby_22, :ruby_23]
|
16
21
|
end
|
17
22
|
|
18
23
|
group :test do
|
@@ -25,6 +30,5 @@ group :test do
|
|
25
30
|
gem 'minitest', '~> 5.3'
|
26
31
|
gem 'minitest-reporters'
|
27
32
|
gem 'awesome_print', :require => 'ap'
|
28
|
-
gem 'simplexml_parser', :git => 'https://github.com/projecttacoma/simplexml_parser.git', :branch => 'master'
|
29
33
|
gem 'vcr'
|
30
34
|
end
|
data/README.md
CHANGED
@@ -1,13 +1,65 @@
|
|
1
|
+
[![Build Status](https://travis-ci.com/projecttacoma/cqm-parsers.svg?branch=master)](https://travis-ci.com/projecttacoma/cqm-parsers)
|
1
2
|
[![codecov](https://codecov.io/gh/projecttacoma/cqm-parsers/branch/master/graph/badge.svg)](https://codecov.io/gh/projecttacoma/cqm-parsers)
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/cqm-parsers.svg)](https://badge.fury.io/rb/cqm-parsers)
|
2
4
|
|
5
|
+
# cqm-parsers
|
3
6
|
|
4
|
-
|
5
|
-
===========
|
7
|
+
This project contains libraries for parsing HQMF documents and parsing MAT packages.
|
6
8
|
|
7
|
-
|
9
|
+
## Usage (MAT Package Loading)
|
8
10
|
|
9
|
-
|
10
|
-
|
11
|
+
To load measures from a MAT package file into the measure model, use the `Measures::CqlLoader` class. It can be used to create an array of measure models. For a composite measure, the array will contain the component measures and the last element will be the composite measure. For a non-composite measure (most measures), the array will contain one item.
|
12
|
+
Example measure loading:
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
|
16
|
+
# Set the VSACValueSetLoader options; in this example we are fetching a specific profile.
|
17
|
+
vsac_options = { profile: 'MU2 Update 2016-04-01' }
|
18
|
+
|
19
|
+
# Set the measure details. For defaults, you can just pass in {}.
|
20
|
+
measure_details = { 'episode_of_care'=> false }
|
21
|
+
|
22
|
+
# Load a MAT package from test fixtures.
|
23
|
+
measure_file = File.new File.join('some/path/CMS158_v5_4_Artifacts.zip')
|
24
|
+
|
25
|
+
# Initialize a value set loader, in this case we are using the VSACValueSetLoader.
|
26
|
+
value_set_loader = Measures::VSACValueSetLoader.new(vsac_options, get_ticket_granting_ticket)
|
27
|
+
|
28
|
+
# Initialize the CqlLoader with the needed parameters.
|
29
|
+
loader = Measures::CqlLoader.new(measure_file, measure_details, value_set_loader)
|
30
|
+
# Build an array of measure models.
|
31
|
+
measures = loader.extract_measures
|
32
|
+
|
33
|
+
```
|
34
|
+
|
35
|
+
Note that a different value set loader could be passed in; for example if you had a file containing value sets you could create a loader that read the value sets from file instead of fetching them from VSAC.
|
36
|
+
|
37
|
+
## Running the tests
|
38
|
+
|
39
|
+
```bash
|
40
|
+
|
41
|
+
bundle exec rake test
|
42
|
+
|
43
|
+
```
|
44
|
+
|
45
|
+
|
46
|
+
## Versioning
|
47
|
+
|
48
|
+
Starting with version **2.0.0** released on 6/20/19, cqm-parsers versioning has the format **X.Y.Z**, where:
|
49
|
+
|
50
|
+
* **X** maps to a version of the CQL-based HQMF IG. See the table below to see the existing mapping to CQL-based HQMF IG versions.
|
51
|
+
|
52
|
+
| X | CQL-based HQMF IG|
|
53
|
+
| --- | --- |
|
54
|
+
| 2 | R1 STU3 |
|
55
|
+
|
56
|
+
* **Y** indicates major changes (incompatible API changes)
|
57
|
+
|
58
|
+
* **Z** indicates minor changes (added functionality in a backwards-compatible manner) and patch changes (backwards-compatible bug fixes)
|
59
|
+
|
60
|
+
For the versions available, see [tags on this repository](https://github.com/projecttacoma/cqm-parsers/tags).
|
61
|
+
|
62
|
+
## License
|
11
63
|
|
12
64
|
Copyright 2018 The MITRE Corporation
|
13
65
|
|
data/Rakefile
CHANGED
@@ -7,6 +7,8 @@ require 'ostruct'
|
|
7
7
|
require_relative 'util/counter.rb'
|
8
8
|
require_relative 'util/code_system_helper'
|
9
9
|
require_relative 'util/hqmf_template_helper'
|
10
|
+
require_relative 'util/vsac_api'
|
11
|
+
require_relative 'util/util'
|
10
12
|
|
11
13
|
require_relative 'hqmf-model/utilities.rb'
|
12
14
|
|
@@ -68,49 +70,15 @@ require_relative 'hqmf-parser/converter/pass2/operator_converter'
|
|
68
70
|
|
69
71
|
require_relative 'hqmf-parser/parser'
|
70
72
|
|
71
|
-
require_relative 'qrda-export/helper/code_system_helper.rb'
|
72
|
-
require_relative 'qrda-export/helper/date_helper.rb'
|
73
|
-
require_relative 'qrda-export/helper/cat_1_view_helper.rb'
|
74
|
-
|
75
|
-
require_relative 'qrda-export/catI-r5/qrda1_r5.rb'
|
76
|
-
|
77
|
-
require_relative 'qrda-import/entry_package.rb'
|
78
|
-
require_relative 'qrda-import/cda_identifier.rb'
|
79
|
-
require_relative 'qrda-import/narrative_reference_handler.rb'
|
80
|
-
require_relative 'qrda-import/entry_finder.rb'
|
81
|
-
|
82
|
-
require_relative 'qrda-import/base-importers/section_importer.rb'
|
83
|
-
require_relative 'qrda-import/base-importers/demographics_importer.rb'
|
84
|
-
require_relative 'qrda-import/base-importers/medication_importer.rb'
|
85
|
-
|
86
|
-
require_relative 'qrda-import/data-element-importers/allergy_intolerance_importer.rb'
|
87
|
-
require_relative 'qrda-import/data-element-importers/diagnostic_study_order_importer.rb'
|
88
|
-
require_relative 'qrda-import/data-element-importers/intervention_order_importer.rb'
|
89
|
-
require_relative 'qrda-import/data-element-importers/encounter_performed_importer.rb'
|
90
|
-
require_relative 'qrda-import/data-element-importers/diagnosis_importer.rb'
|
91
|
-
require_relative 'qrda-import/data-element-importers/medication_active_importer.rb'
|
92
|
-
require_relative 'qrda-import/data-element-importers/medication_order_importer.rb'
|
93
|
-
require_relative 'qrda-import/data-element-importers/procedure_performed_importer.rb'
|
94
|
-
require_relative 'qrda-import/data-element-importers/physical_exam_performed_importer.rb'
|
95
|
-
require_relative 'qrda-import/data-element-importers/laboratory_test_performed_importer.rb'
|
96
|
-
require_relative 'qrda-import/data-element-importers/adverse_event_importer.rb'
|
97
|
-
require_relative 'qrda-import/data-element-importers/assessment_performed_importer.rb'
|
98
|
-
require_relative 'qrda-import/data-element-importers/communication_from_patient_to_provider_importer.rb'
|
99
|
-
require_relative 'qrda-import/data-element-importers/communication_from_provider_to_patient_importer.rb'
|
100
|
-
require_relative 'qrda-import/data-element-importers/communication_from_provider_to_provider_importer.rb'
|
101
|
-
require_relative 'qrda-import/data-element-importers/device_applied_importer.rb'
|
102
|
-
require_relative 'qrda-import/data-element-importers/device_order_importer.rb'
|
103
|
-
require_relative 'qrda-import/data-element-importers/diagnostic_study_performed_importer.rb'
|
104
|
-
require_relative 'qrda-import/data-element-importers/encounter_order_importer.rb'
|
105
|
-
require_relative 'qrda-import/data-element-importers/immunization_administered_importer.rb'
|
106
|
-
require_relative 'qrda-import/data-element-importers/intervention_performed_importer.rb'
|
107
|
-
require_relative 'qrda-import/data-element-importers/laboratory_test_order_importer.rb'
|
108
|
-
require_relative 'qrda-import/data-element-importers/medication_administered_importer.rb'
|
109
|
-
require_relative 'qrda-import/data-element-importers/medication_discharge_importer.rb'
|
110
|
-
require_relative 'qrda-import/data-element-importers/medication_dispensed_importer.rb'
|
111
|
-
require_relative 'qrda-import/data-element-importers/patient_characteristic_expired.rb'
|
112
|
-
require_relative 'qrda-import/data-element-importers/procedure_order_importer.rb'
|
113
|
-
require_relative 'qrda-import/data-element-importers/substance_administered_importer.rb'
|
114
|
-
require_relative 'qrda-import/patient_importer.rb'
|
115
73
|
require_relative 'ext/data_element.rb'
|
116
|
-
|
74
|
+
|
75
|
+
require_relative 'measure-loader/helpers'
|
76
|
+
require_relative 'measure-loader/cql_loader'
|
77
|
+
require_relative 'measure-loader/elm_dependency_finder'
|
78
|
+
require_relative 'measure-loader/elm_parser'
|
79
|
+
require_relative 'measure-loader/exceptions'
|
80
|
+
require_relative 'measure-loader/hqmf_measure_loader'
|
81
|
+
require_relative 'measure-loader/mat_measure_files'
|
82
|
+
require_relative 'measure-loader/source_data_criteria_loader'
|
83
|
+
require_relative 'measure-loader/value_set_helpers'
|
84
|
+
require_relative 'measure-loader/vsac_value_set_loader'
|
data/lib/ext/data_element.rb
CHANGED
@@ -2,7 +2,7 @@ module QDM
|
|
2
2
|
class DataElement
|
3
3
|
def merge!(other)
|
4
4
|
# ensure they're the same category (e.g. 'encounter')
|
5
|
-
return unless
|
5
|
+
return unless qdmCategory == other.qdmCategory
|
6
6
|
|
7
7
|
# ensure they're the same status (e.g. 'performed'), and that they both have a status set (or that they both don't)
|
8
8
|
return if respond_to?(:qdmStatus) && !other.respond_to?(:qdmStatus)
|
@@ -192,7 +192,7 @@ module HQMF2
|
|
192
192
|
value_obj = handle_attribute_value(attribute, value) if attribute.at_xpath('./cda:value', NAMESPACES)
|
193
193
|
|
194
194
|
# Handle the cms_id - changed to eCQM in MAT 5.4 (QDM 5.3)
|
195
|
-
@cms_id = "CMS#{value}v#{@hqmf_version_number.to_i}" if
|
195
|
+
@cms_id = "CMS#{value}v#{@hqmf_version_number.to_i}" if name&.start_with?('eMeasure Identifier', 'eCQM Identifier')
|
196
196
|
|
197
197
|
HQMF::Attribute.new(id, code, value, nil, name, id_obj, code_obj, value_obj)
|
198
198
|
end
|
@@ -74,7 +74,7 @@ module HQMF2
|
|
74
74
|
fail 'Measure Observations criteria is missing computed expression(s) ' if exp.nil?
|
75
75
|
parts = exp.to_s.split('-')
|
76
76
|
dc = parse_parts_to_dc(parts)
|
77
|
-
@doc.add_data_criteria(dc)
|
77
|
+
@doc.add_data_criteria(dc) unless dc.nil?
|
78
78
|
# Update reference_ids with any newly referenced data criteria
|
79
79
|
dc.children_criteria.each { |cc| @doc.add_reference_id(cc) } unless dc&.children_criteria.nil?
|
80
80
|
dc
|
@@ -11,7 +11,7 @@ module HQMF2CQL
|
|
11
11
|
end
|
12
12
|
|
13
13
|
# Extracts potential measure observations from the CQL based HQMF.
|
14
|
-
# This function needs to return a boolean so that it will continue to work with
|
14
|
+
# This function needs to return a boolean so that it will continue to work with
|
15
15
|
# HQMF2::DocumentPopulationHelper::extract_populations_and_criteria
|
16
16
|
# This function is being overridden because in CQL the observations are no longer data criteria in the HQMF.
|
17
17
|
def extract_observations
|
@@ -22,10 +22,10 @@ module HQMF2CQL
|
|
22
22
|
observation_section = @doc.xpath('/cda:QualityMeasureDocument/cda:component/cda:measureObservationSection',
|
23
23
|
HQMF2::Document::NAMESPACES)
|
24
24
|
unless observation_section.empty?
|
25
|
-
observation_section.each do |
|
25
|
+
observation_section.xpath('cda:definition').each do |obs_def|
|
26
26
|
# Need to add population criteria for observations
|
27
27
|
criteria_id = 'OBSERV'
|
28
|
-
criteria = HQMF2::PopulationCriteria.new(
|
28
|
+
criteria = HQMF2::PopulationCriteria.new(obs_def, @document, @id_generator)
|
29
29
|
criteria.type = 'OBSERV'
|
30
30
|
if @ids_by_hqmf_id["#{criteria.hqmf_id}"]
|
31
31
|
criteria.create_human_readable_id(@ids_by_hqmf_id[criteria.hqmf_id])
|
@@ -39,11 +39,16 @@ module HQMF2CQL
|
|
39
39
|
cql_define_function = {}
|
40
40
|
# The at_xpath(...).values returns an array of a single element.
|
41
41
|
# The match returns an array and since we don't want the double quotes we take the second element
|
42
|
-
cql_define_function[:function_name] =
|
42
|
+
cql_define_function[:function_name] = obs_def.at_xpath("cda:measureObservationDefinition/cda:value/cda:expression").values.first.match('\\"([A-Za-z0-9 ]+)\\"')[1]
|
43
|
+
cql_define_function[:function_aggregation_type] = obs_def.at_xpath("cda:measureObservationDefinition/cda:methodCode/cda:item").attributes['code'].value
|
44
|
+
cql_define_function[:function_hqmf_oid] = obs_def.at_xpath("cda:measureObservationDefinition/cda:id").attributes['root'].value
|
43
45
|
# The criteria_reference_id is the id of the measurePopulationCriteria that should be used for this observation function
|
44
|
-
measure_population_id =
|
46
|
+
measure_population_id = obs_def.at_xpath("cda:measureObservationDefinition/cda:component/cda:criteriaReference/cda:id").attributes['root'].value
|
45
47
|
# Get the name of the parameter to the observation function within the measurePopulationCriteria section
|
46
|
-
|
48
|
+
|
49
|
+
measure_population_name = obs_def.at_xpath("cda:measureObservationDefinition/cda:component/cda:criteriaReference/cda:id").attributes['extension'].value
|
50
|
+
criteria_reference_id = @doc.at_xpath("cda:QualityMeasureDocument/cda:component/cda:populationCriteriaSection/cda:component/cda:#{measure_population_name}Criteria/cda:id[@root = \"#{measure_population_id}\"]/../cda:precondition/cda:criteriaReference/cda:id")
|
51
|
+
cql_define_function[:parameter] = criteria_reference_id.attributes['extension'].value.match('\\"([A-Za-z0-9 ]+)\\"')[1]
|
47
52
|
|
48
53
|
@observations << cql_define_function
|
49
54
|
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
module Measures
|
2
|
+
class CqlLoader
|
3
|
+
|
4
|
+
def initialize(measure_zip, measure_details, value_set_loader = nil)
|
5
|
+
@measure_zip = measure_zip
|
6
|
+
@measure_details = measure_details.deep_symbolize_keys
|
7
|
+
@vs_model_cache = {}
|
8
|
+
@value_set_loader = value_set_loader
|
9
|
+
@value_set_loader.vs_model_cache = @vs_model_cache if @value_set_loader.present?
|
10
|
+
end
|
11
|
+
|
12
|
+
# Returns an array of measures, will contain a single measure if it is a non-composite measure
|
13
|
+
def extract_measures
|
14
|
+
measure_files = MATMeasureFiles.create_from_zip_file(@measure_zip)
|
15
|
+
|
16
|
+
measures = []
|
17
|
+
if measure_files.components.present?
|
18
|
+
measure, component_measures = create_measure_and_components(measure_files)
|
19
|
+
measures.push(*component_measures)
|
20
|
+
else
|
21
|
+
measure = create_measure(measure_files)
|
22
|
+
end
|
23
|
+
measure.package = CQM::MeasurePackage.new(file: BSON::Binary.new(@measure_zip.read))
|
24
|
+
measures << measure
|
25
|
+
|
26
|
+
measures.each { |m| CqlLoader.update_population_set_and_strat_titles(m, @measure_details[:population_titles]) }
|
27
|
+
return measures
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.update_population_set_and_strat_titles(measure, population_titles)
|
31
|
+
# Sample population_titles: [pop set 1 title, pop set 2 title, pop set 1 strat 1 title,
|
32
|
+
# pop set 1 strat 2 title, pop set 2 strat 1 title, pop set 2 strat 2 title]
|
33
|
+
# Note RE composite measures: components and composite must have same population sets and strats
|
34
|
+
return if population_titles.nil? || population_titles.empty?
|
35
|
+
title_idx = 0
|
36
|
+
measure.population_sets.each do |population_set|
|
37
|
+
population_set.title = population_titles[title_idx] if population_titles[title_idx].present?
|
38
|
+
title_idx += 1
|
39
|
+
break if title_idx >= population_titles.size
|
40
|
+
end
|
41
|
+
|
42
|
+
return if title_idx >= population_titles.size
|
43
|
+
|
44
|
+
measure.population_sets.flat_map(&:stratifications).each do |strat|
|
45
|
+
strat.title = population_titles[title_idx] if population_titles[title_idx].present?
|
46
|
+
title_idx += 1
|
47
|
+
break if title_idx >= population_titles.size
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def create_measure_and_components(measure_files)
|
54
|
+
top_level_library_ids = measure_files.cql_libraries.map { |lib| "#{lib.id}_v#{lib.version}" }
|
55
|
+
add_component_cql_library_files_to_composite_measure_files(measure_files)
|
56
|
+
measure = create_measure(measure_files)
|
57
|
+
component_measures = create_component_measures(measure_files, measure.hqmf_set_id)
|
58
|
+
measure.component_hqmf_set_ids = component_measures.map(&:hqmf_set_id)
|
59
|
+
unset_top_level_flag_on_cql_libraries_imported_from_components(measure, top_level_library_ids)
|
60
|
+
|
61
|
+
return measure, component_measures
|
62
|
+
end
|
63
|
+
|
64
|
+
def create_component_measures(measure_files, composite_measure_hqmf_set_id)
|
65
|
+
component_measures = measure_files.components.map { |comp_files| create_measure(comp_files) }
|
66
|
+
component_measures.each do |component_measure|
|
67
|
+
# Set the components' hqmf_set_id to: <composite_hqmf_set_id>&<component_hqmf_set_id>
|
68
|
+
component_measure.hqmf_set_id = "#{composite_measure_hqmf_set_id}&#{component_measure.hqmf_set_id}"
|
69
|
+
component_measure.component = true
|
70
|
+
component_measure.composite_hqmf_set_id = composite_measure_hqmf_set_id
|
71
|
+
end
|
72
|
+
return component_measures
|
73
|
+
end
|
74
|
+
|
75
|
+
def unset_top_level_flag_on_cql_libraries_imported_from_components(composite_measure, top_level_library_ids)
|
76
|
+
composite_measure.cql_libraries.each do |lib|
|
77
|
+
unless "#{lib.library_name}_v#{lib.library_version}".in? top_level_library_ids
|
78
|
+
lib.is_top_level = false # is_top_level defaults to true
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def add_component_cql_library_files_to_composite_measure_files(measure_files)
|
84
|
+
component_cql_library_files = measure_files.components.flat_map(&:cql_libraries)
|
85
|
+
measure_files.cql_libraries.push(*component_cql_library_files)
|
86
|
+
measure_files.cql_libraries.uniq! { |cl| cl.id + cl.version }
|
87
|
+
return nil
|
88
|
+
end
|
89
|
+
|
90
|
+
# Creates and returns a measure
|
91
|
+
def create_measure(measure_files)
|
92
|
+
hqmf_xml = measure_files.hqmf_xml
|
93
|
+
# update the valueset info in each elm (update version and remove urn:oid)
|
94
|
+
measure_files.cql_libraries.each { |cql_lib_files| modify_elm_valueset_information(cql_lib_files.elm) }
|
95
|
+
|
96
|
+
measure = CQM::Measure.new(HQMFMeasureLoader.extract_fields(hqmf_xml))
|
97
|
+
measure.cql_libraries = create_cql_libraries(measure_files.cql_libraries, measure.main_cql_library)
|
98
|
+
measure.composite = measure_files.components.present?
|
99
|
+
measure.calculation_method = @measure_details[:episode_of_care] ? 'EPISODE_OF_CARE' : 'PATIENT'
|
100
|
+
measure.calculate_sdes = @measure_details[:calculate_sdes]
|
101
|
+
|
102
|
+
hqmf_model = HQMF::Parser::V2CQLParser.new.parse(hqmf_xml) # TODO: move away from using V2CQLParser
|
103
|
+
|
104
|
+
elms = measure.cql_libraries.map(&:elm)
|
105
|
+
elm_valuesets = ValueSetHelpers.unique_list_of_valuesets_referenced_by_elms(elms)
|
106
|
+
verify_hqmf_valuesets_match_elm_valuesets(elm_valuesets, hqmf_model)
|
107
|
+
|
108
|
+
value_sets_from_single_code_references = ValueSetHelpers.make_fake_valuesets_from_single_code_references(elms, @vs_model_cache)
|
109
|
+
measure.source_data_criteria = SourceDataCriteriaLoader.new(hqmf_xml, value_sets_from_single_code_references).extract_data_criteria
|
110
|
+
measure.value_sets = value_sets_from_single_code_references
|
111
|
+
measure.value_sets.concat(@value_set_loader.retrieve_and_modelize_value_sets_from_vsac(elm_valuesets)) if @value_set_loader.present?
|
112
|
+
|
113
|
+
## this to_json is needed, it doesn't actually produce json, it just makes a hash that is better
|
114
|
+
## suited for our uses (e.g. source_data_criteria goes from an array to a hash keyed by id)
|
115
|
+
hqmf_model_hash = hqmf_model.to_json.deep_symbolize_keys!
|
116
|
+
HQMFMeasureLoader.add_fields_from_hqmf_model_hash(measure, hqmf_model_hash)
|
117
|
+
|
118
|
+
return measure
|
119
|
+
end
|
120
|
+
|
121
|
+
def create_cql_libraries(cql_library_files, main_cql_lib)
|
122
|
+
cql_statement_dependencies_all_libs = ElmDependencyFinder.find_dependencies(cql_library_files, main_cql_lib)
|
123
|
+
|
124
|
+
cql_libraries = cql_library_files.map do |cql_lib_files|
|
125
|
+
cql_statement_dependencies = cql_statement_dependencies_all_libs[cql_lib_files.id]
|
126
|
+
is_main_cql_lib = cql_lib_files.id == main_cql_lib
|
127
|
+
modelize_cql_library(cql_lib_files, cql_statement_dependencies, is_main_cql_lib)
|
128
|
+
end
|
129
|
+
return cql_libraries
|
130
|
+
end
|
131
|
+
|
132
|
+
def modelize_cql_library(cql_lib_files, cql_statement_dependencies, is_main_cql_lib)
|
133
|
+
return CQM::CQLLibrary.new(
|
134
|
+
library_name: cql_lib_files.id,
|
135
|
+
library_version: cql_lib_files.version,
|
136
|
+
elm: cql_lib_files.elm,
|
137
|
+
cql: cql_lib_files.cql,
|
138
|
+
elm_annotations: ElmParser.parse(cql_lib_files.elm_xml),
|
139
|
+
statement_dependencies: modelize_cql_statement_dependencies(cql_statement_dependencies),
|
140
|
+
is_main_library: is_main_cql_lib
|
141
|
+
)
|
142
|
+
end
|
143
|
+
|
144
|
+
def modelize_cql_statement_dependencies(cql_statment_deps)
|
145
|
+
return cql_statment_deps.map do |name, refs|
|
146
|
+
refs = refs.map { |ref| CQM::StatementReference.new(ref) }
|
147
|
+
CQM::StatementDependency.new(statement_name: name, statement_references: refs)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def modify_elm_valueset_information(elm)
|
152
|
+
ValueSetHelpers.remove_urnoid(elm)
|
153
|
+
ValueSetHelpers.modify_value_set_versions(elm)
|
154
|
+
return nil
|
155
|
+
end
|
156
|
+
|
157
|
+
def verify_hqmf_valuesets_match_elm_valuesets(elm_valuesets, measure_hqmf_model)
|
158
|
+
# Exclude patient birthdate OID (2.16.840.1.113883.3.117.1.7.1.70) and patient expired
|
159
|
+
# OID (2.16.840.1.113883.3.117.1.7.1.309) used by SimpleXML parser for AGE_AT handling
|
160
|
+
# and bad oid protection in missing VS check
|
161
|
+
missing = (measure_hqmf_model.all_code_set_oids - elm_valuesets.map {|v| v[:oid]} - ['2.16.840.1.113883.3.117.1.7.1.70', '2.16.840.1.113883.3.117.1.7.1.309'])
|
162
|
+
raise MeasureLoadingInvalidPackageException.new("The HQMF file references the following valuesets not present in the CQL: #{missing}") unless missing.empty?
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Measures
|
2
|
+
module ElmDependencyFinder
|
3
|
+
class << self
|
4
|
+
|
5
|
+
def find_dependencies(cql_library_files, main_cql_library_id)
|
6
|
+
elms = cql_library_files.map(&:elm)
|
7
|
+
all_elms_dep_map = Hash[elms.map { |elm| [Helpers.elm_id(elm), make_statement_deps_for_elm(elm)] }]
|
8
|
+
needed_deps_map = Hash[elms.map { |elm| [Helpers.elm_id(elm), {}] }]
|
9
|
+
|
10
|
+
needed_deps_map[main_cql_library_id] = all_elms_dep_map[main_cql_library_id]
|
11
|
+
needed_deps_map[main_cql_library_id].each_value do |stmnts|
|
12
|
+
stmnts.each { |stmnt| deep_add_external_library_deps(stmnt, needed_deps_map, all_elms_dep_map) }
|
13
|
+
end
|
14
|
+
return needed_deps_map
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def make_library_alias_to_path_hash(elm)
|
20
|
+
lib_alias_to_path = { nil => Helpers.elm_id(elm) } # nil value used for statements without libraryName
|
21
|
+
(elm.dig('library','includes','def') || []).each do |library_hash|
|
22
|
+
lib_alias_to_path[library_hash['localIdentifier']] = library_hash['path']
|
23
|
+
end
|
24
|
+
return lib_alias_to_path
|
25
|
+
end
|
26
|
+
|
27
|
+
def make_statement_deps_for_elm(elm)
|
28
|
+
deps = {}
|
29
|
+
lib_alias_to_path = make_library_alias_to_path_hash(elm)
|
30
|
+
make_statement_deps_for_elm_helper(elm, nil, deps, lib_alias_to_path)
|
31
|
+
deps.each_value(&:uniq!)
|
32
|
+
return deps
|
33
|
+
end
|
34
|
+
|
35
|
+
def make_statement_deps_for_elm_helper(obj, parent_name, deps, lib_alias_to_path)
|
36
|
+
if obj.is_a? Array
|
37
|
+
obj.each { |el| make_statement_deps_for_elm_helper(el, parent_name, deps, lib_alias_to_path) }
|
38
|
+
elsif obj.is_a? Hash
|
39
|
+
if obj['type'].in?(['ExpressionRef', 'FunctionRef']) && parent_name != 'Patient'
|
40
|
+
dep = { library_name: lib_alias_to_path[obj['libraryName']], statement_name: obj['name'] }
|
41
|
+
deps[parent_name] << dep
|
42
|
+
elsif obj.key?('name') && obj.key?('expression')
|
43
|
+
parent_name = obj['name']
|
44
|
+
deps[parent_name] = [] unless deps.key?('parent_name')
|
45
|
+
end
|
46
|
+
obj.each_pair do |k,v|
|
47
|
+
make_statement_deps_for_elm_helper(v, parent_name, deps, lib_alias_to_path) unless k == 'annotation'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def deep_add_external_library_deps(statement, needed_deps_map, all_elms_dep_map)
|
53
|
+
statement_library = statement[:library_name]
|
54
|
+
statement_name = statement[:statement_name]
|
55
|
+
|
56
|
+
return unless needed_deps_map.dig(statement_library, statement_name).nil? # return if key already exists
|
57
|
+
|
58
|
+
if all_elms_dep_map[statement_library].nil?
|
59
|
+
raise MeasureLoadingInvalidPackageException.new("Elm library #{statement_library} referenced but not found.")
|
60
|
+
end
|
61
|
+
if all_elms_dep_map[statement_library][statement_name].nil?
|
62
|
+
raise MeasureLoadingException.new("Elm statement '#{statement_name}' referenced but not found in library '#{statement_library}'.")
|
63
|
+
end
|
64
|
+
deps_to_add = all_elms_dep_map[statement_library][statement_name]
|
65
|
+
needed_deps_map.deep_merge!(statement_library => { statement_name => deps_to_add })
|
66
|
+
|
67
|
+
deps_to_add.each { |stmnt| deep_add_external_library_deps(stmnt, needed_deps_map, all_elms_dep_map) }
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|