cqm-parsers 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +5 -5
  2. data/Gemfile +5 -4
  3. data/README.md +3 -0
  4. data/Rakefile +0 -1
  5. data/lib/hqmf-model/data_criteria.json +27 -0
  6. data/lib/hqmf-model/data_criteria.rb +41 -4
  7. data/lib/hqmf-model/types.rb +37 -0
  8. data/lib/hqmf-parser/2.0/document.rb +1 -1
  9. data/lib/hqmf-parser/2.0/document_helpers/doc_population_helper.rb +36 -12
  10. data/lib/hqmf-parser/2.0/value_set_helper.rb +2 -2
  11. data/lib/hqmf-parser/cql/document_helpers/doc_population_helper.rb +45 -1
  12. data/lib/hqmf-parser/cql/value_set_helper.rb +5 -2
  13. data/lib/qrda-export/catI-r5/qrda_header/_record_target.mustache +0 -1
  14. data/lib/qrda-export/catI-r5/qrda_templates/assessment_performed.mustache +3 -3
  15. data/lib/qrda-export/catI-r5/qrda_templates/encounter_ordered.mustache +0 -1
  16. data/lib/qrda-export/catI-r5/qrda_templates/encounter_performed.mustache +0 -1
  17. data/lib/qrda-import/base-importers/medication_importer.rb +1 -0
  18. data/lib/qrda-import/base-importers/section_importer.rb +2 -2
  19. data/lib/qrda-import/data-element-importers/adverse_event_importer.rb +1 -0
  20. data/lib/qrda-import/data-element-importers/allergy_intolerance_importer.rb +1 -0
  21. data/lib/qrda-import/data-element-importers/assessment_performed_importer.rb +2 -1
  22. data/lib/qrda-import/data-element-importers/communication_from_patient_to_provider_importer.rb +1 -0
  23. data/lib/qrda-import/data-element-importers/communication_from_provider_to_patient_importer.rb +1 -0
  24. data/lib/qrda-import/data-element-importers/communication_from_provider_to_provider_importer.rb +1 -0
  25. data/lib/qrda-import/data-element-importers/device_applied_importer.rb +1 -0
  26. data/lib/qrda-import/data-element-importers/device_order_importer.rb +1 -0
  27. data/lib/qrda-import/data-element-importers/diagnosis_importer.rb +1 -0
  28. data/lib/qrda-import/data-element-importers/diagnostic_study_order_importer.rb +1 -0
  29. data/lib/qrda-import/data-element-importers/diagnostic_study_performed_importer.rb +1 -0
  30. data/lib/qrda-import/data-element-importers/encounter_order_importer.rb +1 -0
  31. data/lib/qrda-import/data-element-importers/encounter_performed_importer.rb +1 -0
  32. data/lib/qrda-import/data-element-importers/intervention_order_importer.rb +1 -0
  33. data/lib/qrda-import/data-element-importers/intervention_performed_importer.rb +1 -0
  34. data/lib/qrda-import/data-element-importers/laboratory_test_order_importer.rb +1 -0
  35. data/lib/qrda-import/data-element-importers/laboratory_test_performed_importer.rb +1 -0
  36. data/lib/qrda-import/data-element-importers/patient_characteristic_expired.rb +1 -0
  37. data/lib/qrda-import/data-element-importers/physical_exam_performed_importer.rb +1 -0
  38. data/lib/qrda-import/data-element-importers/procedure_order_importer.rb +1 -0
  39. data/lib/qrda-import/data-element-importers/procedure_performed_importer.rb +1 -0
  40. data/lib/util/code_system_helper.rb +2 -1
  41. data/lib/util/hqmfr2cql_template_oid_map.json +13 -13
  42. data/lib/util/vsac_api.rb +299 -0
  43. metadata +18 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 8fee41ae3629b17642d96a32021c2a88a482e487
4
- data.tar.gz: 12beb393c3857d3bbe976680b9af8d8c40675855
2
+ SHA256:
3
+ metadata.gz: 1b1bca119f15302832fea054a9bcc5ad90736bfe3ad898ec05410ba9932c4fa6
4
+ data.tar.gz: 94776039cbf7dadafc0b8bf2308cca80b60e298a5291494d3d63d2ade0f852d4
5
5
  SHA512:
6
- metadata.gz: 399ea7c2f02c5e22bf470d4f8044f6b838bad480da19cc314f60cd29586250a3591c62ae44aaa3f5783ede0d4678093ea9af8d6cf217b109c88b7323d63e6f3a
7
- data.tar.gz: d977ad998ebb9789568542fe8e98835a225fb5ff1c0e4b4858b4d9832e0c6c7cca784f649e81af087c9a5b1fcc45d08c5cd49073dd56d0435c211d64ee26a6bb
6
+ metadata.gz: 476c7290726a10f52dfb3f65a066516f755d7913be15c74783d5fbc836a2718c7cf65b9580ef309368ec4bdb45d9d6d175881b190660eb94f12d9ed6b2eeed08
7
+ data.tar.gz: 14d8e37e7fe9c737df8175192d935e3b908a53fa4511ccff107772108ef055375091241206fa964a5c430dcb944b5ce7b05b1bd94b52a5860a902d212843b0ca
data/Gemfile CHANGED
@@ -1,8 +1,7 @@
1
- source "https://rubygems.org"
1
+ source 'https://rubygems.org'
2
2
 
3
3
  gemspec :development_group => :test
4
4
 
5
- gem 'mustache'
6
5
  gem 'cqm-models', '~> 0.8.4'
7
6
  gem 'mongoid', '~> 5.0.0'
8
7
 
@@ -18,12 +17,14 @@ end
18
17
 
19
18
  group :test do
20
19
  gem 'factory_girl', '~> 4.1.0'
21
- gem "tailor", '~> 1.1.2'
22
- gem "cane", '~> 2.3.0'
20
+ gem 'tailor', '~> 1.1.2'
21
+ gem 'cane', '~> 2.3.0'
22
+ gem 'codecov'
23
23
  gem 'simplecov', :require => false
24
24
  gem 'webmock'
25
25
  gem 'minitest', '~> 5.3'
26
26
  gem 'minitest-reporters'
27
27
  gem 'awesome_print', :require => 'ap'
28
28
  gem 'simplexml_parser', :git => 'https://github.com/projecttacoma/simplexml_parser.git', :branch => 'master'
29
+ gem 'vcr'
29
30
  end
data/README.md CHANGED
@@ -1,3 +1,6 @@
1
+ [![codecov](https://codecov.io/gh/projecttacoma/cqm-parsers/branch/master/graph/badge.svg)](https://codecov.io/gh/projecttacoma/cqm-parsers)
2
+
3
+
1
4
  cqm-parsers
2
5
  ===========
3
6
 
data/Rakefile CHANGED
@@ -1,6 +1,5 @@
1
1
  require 'rake/testtask'
2
2
  require 'cane/rake_task'
3
- require "simplecov"
4
3
 
5
4
  import 'lib/tasks/hqmf.rake'
6
5
 
@@ -647,6 +647,15 @@
647
647
  "hard_status":false,
648
648
  "patient_api_function":"communications",
649
649
  "not_supported":false},
650
+ "communication_performed":{
651
+ "title":"communication, performed",
652
+ "category":"communications",
653
+ "definition":"communication",
654
+ "status":"performed",
655
+ "sub_category":"",
656
+ "hard_status":false,
657
+ "patient_api_function":"communications",
658
+ "not_supported":false},
650
659
  "device":{
651
660
  "title":"device",
652
661
  "category":"devices",
@@ -1007,6 +1016,15 @@
1007
1016
  "hard_status":false,
1008
1017
  "patient_api_function":"encounters",
1009
1018
  "not_supported":false},
1019
+ "assessment_ordered":{
1020
+ "title":"assessment order",
1021
+ "category":"assessments",
1022
+ "definition":"assessment",
1023
+ "status":"ordered",
1024
+ "sub_category":"",
1025
+ "hard_status":false,
1026
+ "patient_api_function":"assessments",
1027
+ "not_supported":false},
1010
1028
  "assessment_performed":{
1011
1029
  "title":"assessment performed",
1012
1030
  "category":"assessments",
@@ -1042,5 +1060,14 @@
1042
1060
  "sub_category":"",
1043
1061
  "hard_status":false,
1044
1062
  "patient_api_function":"allergies",
1063
+ "not_supported":false},
1064
+ "participation":{
1065
+ "title":"participation",
1066
+ "category":"participations",
1067
+ "definition":"participation",
1068
+ "status":"",
1069
+ "sub_category":"",
1070
+ "hard_status":false,
1071
+ "patient_api_function":"participations",
1045
1072
  "not_supported":false}
1046
1073
  }
@@ -24,24 +24,35 @@ module HQMF
24
24
  # `code`: The code for the entry. This should be included to make HQMF generation work properly. This is whatever code is dictated in the HQMF. For Diagnosis, this is in [HQMF QDM IG](http://www.hl7.org/implement/standards/product_brief.cfm?product_id=346) vol 2 page 155 and is `29308-4`.
25
25
  # `code_system`: This is the oid for whatever code system contains `code`. For Diagnosis, this is LOINC: `2.16.840.1.113883.6.1`. This is also located at (http://www.hl7.org/implement/standards/product_brief.cfm?product_id=346) vol 2 page 155.
26
26
  # `template_id`: These appear to be related to HQMFr1 template ids. These appear to be dangerously out of date. Don't use.
27
- FIELDS = {'ABATEMENT_DATETIME' => {title:'Abatement Datetime', coded_entry_method: :end_date, field_type: :timestamp},
28
- 'ACTIVE_DATETIME' => {title:'Active Date/Time', coded_entry_method: :active_date_time, field_type: :timestamp},
27
+ FIELDS = {'ABATEMENT_DATETIME' => {title:'Abatement Datetime', coded_entry_method: :end_date, field_type: :timestamp}, # ABATEMENT_DATETIME is no longer used.
28
+ 'ACTIVE_DATETIME' => {title:'Active Date/Time', coded_entry_method: :active_datetime, field_type: :timestamp},
29
+ # ADMISSION_DATETIME is no longer used.
29
30
  'ADMISSION_DATETIME' => {title:'Admission Date/Time', coded_entry_method: :admit_time, code: '399423000', code_system:'2.16.840.1.113883.6.96', field_type: :timestamp},
30
31
  # QDM 5.0 addition. This is the same as FACILITY_LOCATION.
31
32
  # TODO: (LDY 10/5/2016) this is a new attribute from QDM 5.0. We do not yet have the code or template_id for this. This should be updated when we do.
32
33
  'ADMISSION_SOURCE' => {title:'Admission Source', coded_entry_method: :admission_source, field_type: :value},
34
+ # ANATOMICAL_APPROACH_SITE is no longer used.
33
35
  'ANATOMICAL_APPROACH_SITE' => {title:'Anatomical Approach Site', coded_entry_method: :anatomical_approach, field_type: :value},
34
36
  'ANATOMICAL_LOCATION_SITE' => {title:'Anatomical Location Site', coded_entry_method: :anatomical_location, field_type: :value},
37
+ # ANATOMICAL_STRUCTURE is no longer used.
35
38
  'ANATOMICAL_STRUCTURE' => {title:'Anatomical Structure', coded_entry_method: :anatomical_structure, code: '91723000', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.3.560.1.1000.2', field_type: :value},
39
+ # Added in QDM 5.4
40
+ 'CATEGORY' => {title:'Category', coded_entry_method: :category, field_type: :value},
36
41
  'CAUSE' => {title:'Cause', coded_entry_method: :cause_of_death, code: '42752001', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.3.560.1.1017.2', field_type: :value},
37
42
  # TODO: Determine actual code and code_system for component attribute
38
43
  'COMPONENT' => {title: 'Component', coded_entry_method: :components, field_type: :value},
44
+ # CUMULATIVE_MEDICATION_DURATION is no longer used.
39
45
  'CUMULATIVE_MEDICATION_DURATION' => {title:'Cumulative Medication Duration', coded_entry_method: :cumulative_medication_duration, code: '261773006', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.3.560.1.1001.3', field_type: :value},
40
46
  # MISSING Date - The date that the patient passed away. - Patient Characteristic Expired
47
+ # Could not find a code and code_system for this data criteria (days supplied) in HQMF QDM IG v3
48
+ 'DAYS_SUPPLIED' => {title:'Days Supplied', coded_entry_method: :days_supplied, field_type: :value},
41
49
  'DIAGNOSIS' => {title:'Diagnosis', coded_entry_method: :diagnosis, field_type: :value},
50
+ # DISCHARGE_DATETIME is no longer used.
42
51
  'DISCHARGE_DATETIME' => {title:'Discharge Date/Time', coded_entry_method: :discharge_time, code: '442864001', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.3.560.1.1025.1', field_type: :timestamp},
43
52
  # TODO: (LDY 10/5/2016) this changed from "discharge status" to "discharge disposition". likely there is a code and template id change necessary. these are not yet known.
53
+ # DISCHARGE_STATUS is no longer used.
44
54
  'DISCHARGE_STATUS' => {title:'Discharge Disposition', coded_entry_method: :discharge_disposition, code: '309039003', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.3.560.1.1003.2', field_type: :value},
55
+ 'DISPENSER_IDENTIFIER' => {title:'Dispenser Identifier', coded_entry_method: :dispenser_identifier, field_type: :value},
45
56
  # TODO: (LDY 10/4/2016) this changed from "dose" to "dosage". it's possible that there's another code associated with this. this code was not available at the time of this change.
46
57
  'DOSE' => {title:'Dosage', coded_entry_method: :dose, code: '398232005', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.3.560.1.1004.1', field_type: :value},
47
58
  'FACILITY_LOCATION' => {title:'Facility Location', coded_entry_method: :facility, code: 'SDLOC', field_type: :value},
@@ -49,49 +60,72 @@ module HQMF
49
60
  'FACILITY_LOCATION_ARRIVAL_DATETIME' => {title:'Location Period Start Date/Time', coded_entry_method: :facility_arrival, code: 'SDLOC_ARRIVAL', field_type: :nested_timestamp},
50
61
  'FACILITY_LOCATION_DEPARTURE_DATETIME' => {title:'Location Period End Date/Time', coded_entry_method: :facility_departure, code: 'SDLOC_DEPARTURE', field_type: :nested_timestamp},
51
62
  'FREQUENCY' => {title:'Frequency', coded_entry_method: :administration_timing, code: '307430002', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.3.560.1.1006.1', field_type: :value},
63
+ # HEALTH_RECORD_FIELD is no longer used.
52
64
  'HEALTH_RECORD_FIELD' => {title: 'Health Record Field', coded_entry_method: :health_record_field, code: '395676008', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.10.20.28.3.102:2014-11-24', field_type: :value},
53
65
  'INCISION_DATETIME' => {title:'Incision Date/Time', coded_entry_method: :incision_time, code: '34896006', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.10.20.24.3.89', field_type: :timestamp},
66
+ # LATERALITY is no longer used.
54
67
  'LATERALITY' => {title:'Laterality', coded_entry_method: :laterality, code: '272741003', code_system:'2.16.840.1.113883.6.96', template_id: '', field_type: :value},
55
68
  'LENGTH_OF_STAY' => {title:'Length of Stay', coded_entry_method: :length_of_stay, code: '183797002', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.3.560.1.1029.3', field_type: :value},
69
+ # Added in QDM 5.4
70
+ 'MEDIUM' => {title:'Medium', coded_entry_method: :medium, field_type: :value},
56
71
  'METHOD' => {title:'Method', coded_entry_method: :method, template_id: '', field_type: :value},
57
72
  # Negation Rationale isn't encoded
73
+ # ONSET_AGE is no longer used.
58
74
  'ONSET_AGE' => {title:'Onset Age', coded_entry_method: :onset_age, code: '445518008', code_system:'2.16.840.1.113883.6.96', template_id: '', field_type: :value},
75
+ # ONSET_DATETIME is no longer used.
59
76
  'ONSET_DATETIME' => {title:'Onset Datetime', coded_entry_method: :start_date, field_type: :timestamp},
77
+ # ORDINAL is no longer used.
60
78
  'ORDINAL' => {title:'Ordinality', coded_entry_method: :ordinality, code: '117363000', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.3.560.1.1012.2', field_type: :value}, # previous
61
79
  'ORDINALITY' => {title:'Ordinality', coded_entry_method: :ordinality, code: '117363000', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.3.560.1.1012.2', field_type: :value},
80
+ # PATIENT_PREFERENCE is no longer used.
62
81
  'PATIENT_PREFERENCE' => {title:'Patient Preference', coded_entry_method: :patient_preference, code: 'PAT', code_system: '2.16.840.1.113883.5.8', template_id: '2.16.840.1.113883.10.20.24.3.83', field_type: :value},
82
+ 'PRESCRIBER_IDENTIFIER' => {title:'Prescriber Identifier', coded_entry_method: :prescriber_identifier, field_type: :value},
63
83
  'PRINCIPAL_DIAGNOSIS' => {title:'Principal Diagnosis', coded_entry_method: :principal_diagnosis, field_type: :value},
84
+ # PROVIDER_PREFERENCE is no longer used.
64
85
  'PROVIDER_PREFERENCE' => {title:'Provider Preference', coded_entry_method: :provider_preference, code: '103323008', code_system: '2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.10.20.24.3.84', field_type: :value},
86
+ # RADIATION_DOSAGE is no longer used.
65
87
  'RADIATION_DOSAGE' => {title:'Radiation Dosage', coded_entry_method: :radiation_dose, code: '228815006', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.10.20.24.3.91', field_type: :value},
88
+ # RADIATION_DURATION is no longer used.
66
89
  'RADIATION_DURATION' => {title:'Radiation Duration', coded_entry_method: :radiation_duration, code: '306751006', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.10.20.24.3.91', field_type: :value},
90
+ # REACTION is no longer used.
67
91
  'REACTION'=> {title:'Reaction', coded_entry_method: :reaction, code: '263851003', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.10.20.24.3.85', field_type: :value},
68
92
  'REASON' => {title:'Reason', coded_entry_method: :reason, code: '410666004', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.10.20.24.3.88', field_type: :value},
93
+ # Added in QDM 5.4
94
+ 'RECIPIENT' => {title:'Recipient', coded_entry_method: :recipient, field_type: :value},
95
+ # RECORDED_DATETIME is no longer used.
69
96
  'RECORDED_DATETIME' => {title:'Recorded Datetime', coded_entry_method: :start_date, field_type: :timestamp},
70
97
  'REFERENCE_RANGE_HIGH' => {title:'Reference Range - High', coded_entry_method: :reference_range_high, field_type: :value},
71
98
  'REFERENCE_RANGE_LOW' => {title:'Reference Range - Low', coded_entry_method: :reference_range_low, field_type: :value},
72
99
  'REFILLS' => {title:'Refills', coded_entry_method: :refills, field_type: :value},
73
100
  'RELATED_TO' => {title:'Related To', coded_entry_method: :related_to, code: 'REL', codeSystem: '2.16.840.1.113883.1.11.11603', field_type: :value},
74
101
  'RELATIONSHIP' => {title:'Relationship', coded_entry_method: :relationship_to_patient, field_type: :value},
102
+ # REMOVAL_DATETIME is no longer used.
75
103
  'REMOVAL_DATETIME' => {title:'Removal Date/Time', coded_entry_method: :removal_time, code: '118292001', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.3.560.1.1032.1', field_type: :timestamp},
76
104
  # Result isn't encoded
77
105
  # TODO: (LDY 10/4/2016) RESULT_DATETIME is a new attribute in QDM 5.0. We do not yet have codes/template information for this.
78
106
  'RESULT_DATETIME' => {title:'Result Date/Time', coded_entry_method: :result_date_time, field_type: :timestamp},
79
107
  'ROUTE' => {title:'Route', coded_entry_method: :route, code: '263513008', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.3.560.1.1020.2', field_type: :value},
108
+ # Added in QDM 5.4
109
+ 'SENDER' => {title:'Sender', coded_entry_method: :sender, field_type: :value},
110
+ 'SETTING' => {title:'Setting', coded_entry_method: :setting, field_type: :value},
80
111
  'SEVERITY' => {title:'Severity', coded_entry_method: :severity, code: 'SEV', code_system:'2.16.840.1.113883.5.4', template_id: '2.16.840.1.113883.10.20.22.4.8', field_type: :value},
112
+ # SIGNED_DATETIME is no longer used.
81
113
  'SIGNED_DATETIME' => {title:'Signed Date/Time', coded_entry_method: :signed_date_time, field_type: :timestamp},
114
+ # START_DATETIME is no longer used.
82
115
  'START_DATETIME' => {title:'Start Date/Time', coded_entry_method: :start_date, code: '398201009', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.3.560.1.1027.1', field_type: :timestamp},
83
116
  # STATUS is referenced in the code as `qdm_status` because entry/Record already has a `status`/`status_code` field which has a different meaning
84
117
  'STATUS' => {title: 'Status', coded_entry_method: :qdm_status, code: '33999-4', code_system:'2.16.840.1.113883.6.1', field_type: :value},
118
+ # STOP_DATETIME is no longer used.
85
119
  'STOP_DATETIME' => {title:'Stop Date/Time', coded_entry_method: :end_date, code: '397898000', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.3.560.1.1026.1', field_type: :timestamp},
86
120
  # TODO: (LDY 10/4/2016) SUPPLY is a new attribute in QDM 5.0. We do not yet have codes/template information for this.
87
121
  'SUPPLY' => {title:'Supply', coded_entry_method: :supply, field_type: :value},
88
122
  'TARGET_OUTCOME' => {title:'Target Outcome', coded_entry_method: :target_outcome, code: '385676005', code_system:'2.16.840.1.113883.6.96', template_id: '', field_type: :value},
89
- # MISSING Time - The time that the patient passed away
90
123
 
91
124
  # Custom field values
92
125
  # QDM 5.3 Update: "Related To" replaces Fulfills. We are keeping the fulfills code and only make changes to the UI.
93
126
  'FLFS' => {title:'Related To', coded_entry_method: :fulfills, code: 'FLFS', field_type: :reference},
94
127
  'SOURCE' => {title:'Source', coded_entry_method: :source, code: '260753009', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.3.560.1.2001.2', field_type: :value},
128
+ # TRANSFER_FROM, TRANSFER_FROM_DATETIME, TRANSFER_TO, and TRANSFER_TO_DATETIME are no longer used.
95
129
  'TRANSFER_FROM' => {title:'Transfer From', coded_entry_method: :transfer_from, code: 'TRANSFER_FROM', template_id: '2.16.840.1.113883.10.20.24.3.81', field_type: :value},
96
130
  'TRANSFER_FROM_DATETIME' => {title:'Transfer From Date/Time', coded_entry_method: :transfer_from_time, code: 'ORG_TIME', template_id: '2.16.840.1.113883.10.20.24.3.81', field_type: :nested_timestamp},
97
131
  'TRANSFER_TO' => {title:'Transfer To', coded_entry_method: :transfer_to, code: 'TRANSFER_TO', template_id: '2.16.840.1.113883.10.20.24.3.82', field_type: :value},
@@ -455,6 +489,10 @@ module HQMF
455
489
  value = HQMF::Collection.from_json(json)
456
490
  when 'FAC'
457
491
  value = Facility.new(json)
492
+ when 'ID'
493
+ value = HQMF::Identifier.from_json(json)
494
+ when 'RT'
495
+ value = HQMF::Ratio.from_json(json)
458
496
  else
459
497
  raise "Unknown value type [#{type}]"
460
498
  end
@@ -463,5 +501,4 @@ module HQMF
463
501
 
464
502
 
465
503
  end
466
-
467
504
  end
@@ -453,5 +453,42 @@ module HQMF
453
453
  check_equality(self,other)
454
454
  end
455
455
  end
456
+
457
+ class Ratio
458
+ include HQMF::Conversion::Utilities
459
+ attr_accessor :type, :numerator, :denominator
460
+
461
+ # @param [String] type
462
+ # @param [Value] numerator
463
+ # @param [Value] denominator
464
+ def initialize(type, numerator, denominator)
465
+ @type = type || 'RT'
466
+ @numerator = numerator
467
+ @denominator = denominator
468
+ end
469
+
470
+ def self.from_json(json)
471
+ type = json["type"] if json["type"]
472
+ numerator = json["numerator"] if json["numerator"]
473
+ denominator = json["denominator"] if json["denominator"]
474
+ HQMF::Ratio.new(type, numerator, denominator)
475
+ end
476
+
477
+ def numerator?
478
+ @numerator
479
+ end
480
+
481
+ def denominator?
482
+ @denominator
483
+ end
484
+
485
+ def to_json
486
+ build_hash(self, [:type, :numerator, :denominator])
487
+ end
488
+
489
+ def ==(other)
490
+ check_equality(self, other)
491
+ end
492
+ end
456
493
 
457
494
  end
@@ -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 (name.include? 'eMeasure Identifier') || (name.include? 'eCQM Identifier')
195
+ @cms_id = "CMS#{value}v#{@hqmf_version_number.to_i}" if (!name.nil?) && ((name.include? 'eMeasure Identifier') || (name.include? 'eCQM Identifier'))
196
196
 
197
197
  HQMF::Attribute.new(id, code, value, nil, name, id_obj, code_obj, value_obj)
198
198
  end
@@ -32,24 +32,17 @@ module HQMF2
32
32
  # Returns the population descriptions and criteria found in this document
33
33
  def extract_populations_and_criteria
34
34
  has_observation = extract_observations
35
- document_populations = @doc.xpath('cda:QualityMeasureDocument/cda:component/cda:populationCriteriaSection',
36
- HQMF2::Document::NAMESPACES)
37
- # Sort the populations based on the id/extension, since the populations may be out of order; there doesn't seem to
38
- # be any other way that order is indicated in the HQMF
39
- document_populations = document_populations.sort_by do |pop|
40
- pop.at_xpath('cda:id/@extension', HQMF2::Document::NAMESPACES).try(:value)
41
- end
35
+ document_populations = get_document_populations
42
36
  number_of_populations = document_populations.length
43
37
  document_populations.each_with_index do |population_def, population_index|
44
38
  population = {}
45
39
  handle_base_populations(population_def, population)
46
40
 
47
- id_def = population_def.at_xpath('cda:id/@extension', HQMF2::Document::NAMESPACES)
48
- population['id'] = id_def ? id_def.value : "Population#{population_index}"
49
- title_def = population_def.at_xpath('cda:title/@value', HQMF2::Document::NAMESPACES)
50
- population['title'] = title_def ? title_def.value : "Population #{population_index}"
41
+ id_def = get_id_def(population_def)
42
+ add_id_to_population(id_def, population, population_index)
43
+ add_title_to_population(population_def, population)
44
+ add_observ_to_population(has_observation, population)
51
45
 
52
- population['OBSERV'] = 'OBSERV' if has_observation
53
46
  @populations << population
54
47
  handle_stratifications(population_def, number_of_populations, population, id_def, population_index)
55
48
  end
@@ -59,6 +52,37 @@ module HQMF2
59
52
  [@populations, @population_criteria]
60
53
  end
61
54
 
55
+ def get_document_populations
56
+ document_populations = @doc.xpath('cda:QualityMeasureDocument/cda:component/cda:populationCriteriaSection',
57
+ HQMF2::Document::NAMESPACES)
58
+ # Sort the populations based on the id/extension, since the populations may be out of order; there doesn't seem to
59
+ # be any other way that order is indicated in the HQMF
60
+ document_populations.sort_by do |pop|
61
+ pop.at_xpath('cda:id/@extension', HQMF2::Document::NAMESPACES).try(:value)
62
+ end
63
+ end
64
+
65
+ def get_id_def(population_def)
66
+ population_def.at_xpath('cda:id/@extension', HQMF2::Document::NAMESPACES)
67
+ end
68
+
69
+ def get_title_def(population_def)
70
+ population_def.at_xpath('cda:title/@value', HQMF2::Document::NAMESPACES)
71
+ end
72
+
73
+ def add_id_to_population(id_def, population, population_index)
74
+ population['id'] = id_def ? id_def.value : "Population#{population_index}"
75
+ end
76
+
77
+ def add_title_to_population(population_def, population)
78
+ title_def = get_title_def(population_def)
79
+ population['title'] = title_def ? title_def.value : "Population #{population_index}"
80
+ end
81
+
82
+ def add_observ_to_population(has_observation, population)
83
+ population['OBSERV'] = 'OBSERV' if has_observation
84
+ end
85
+
62
86
  # Extracts the measure observations, will return true if one exists
63
87
  def extract_observations
64
88
  has_observation = false
@@ -92,8 +92,8 @@ module HQMF2
92
92
  '2.16.840.1.113883.10.20.28.3.117' => { valueset_path: './*/cda:code', result_path: './*/cda:value' },
93
93
  '2.16.840.1.113883.10.20.28.3.118' => { valueset_path: './*/cda:code', result_path: nil },
94
94
  '2.16.840.1.113883.10.20.28.3.119' => { valueset_path: "./*/cda:participation[@typeCode='CSM']/cda:role[@classCode='MANU']/cda:playingEntity[@classCode='MMAT']/cda:code", result_path: nil },
95
- '2.16.840.1.113883.10.20.28.3.120' => { valueset_path: "./*/cda:participation[@typeCode='CSM']/cda:role[@classCode='MANU']/cda:playingEntity[@classCode='MMAT']/cda:code", result_path: nil }
96
-
95
+ '2.16.840.1.113883.10.20.28.3.120' => { valueset_path: "./*/cda:participation[@typeCode='CSM']/cda:role[@classCode='MANU']/cda:playingEntity[@classCode='MMAT']/cda:code", result_path: nil },
96
+ '2.16.840.1.113883.10.20.28.3.130' => { valueset_path: './*/cda:value', result_path: nil }
97
97
  }
98
98
  # rubocop:enable Metrics/LineLength
99
99
 
@@ -40,7 +40,11 @@ module HQMF2CQL
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
42
  cql_define_function[:function_name] = entry.at_xpath("*/cda:measureObservationDefinition/cda:value/cda:expression").values.first.match('\\"([A-Za-z0-9 ]+)\\"')[1]
43
- cql_define_function[:parameter] = entry.at_xpath("*/cda:measureObservationDefinition/cda:component/cda:criteriaReference/cda:id").attributes['extension'].value.match('\\"([A-Za-z0-9 ]+)\\"')[1]
43
+ # The criteria_reference_id is the id of the measurePopulationCriteria that should be used for this observation function
44
+ measure_population_id = entry.at_xpath("*/cda:measureObservationDefinition/cda:component/cda:criteriaReference/cda:id").attributes['root'].value
45
+ # Get the name of the parameter to the observation function within the measurePopulationCriteria section
46
+ cql_define_function[:parameter] = @doc.at_xpath("cda:QualityMeasureDocument/cda:component/cda:populationCriteriaSection/cda:component/cda:measurePopulationCriteria/cda:id[@root = \"#{measure_population_id}\"]/../cda:precondition/cda:criteriaReference/cda:id").attributes['extension'].value.match('\\"([A-Za-z0-9 ]+)\\"')[1]
47
+
44
48
  @observations << cql_define_function
45
49
  end
46
50
  end
@@ -120,5 +124,45 @@ module HQMF2CQL
120
124
  end
121
125
  end
122
126
 
127
+ # Returns the population descriptions and criteria found in this document
128
+ def extract_populations_and_criteria
129
+ has_observation = extract_observations
130
+ document_populations = get_document_populations
131
+ number_of_populations = document_populations.length
132
+ document_populations.each_with_index do |population_def, population_index|
133
+ population = {}
134
+ handle_base_populations(population_def, population)
135
+
136
+ id_def = get_id_def(population_def)
137
+ add_id_to_population(id_def, population, population_index)
138
+ add_title_to_population(population_def, population)
139
+ add_observ_to_population(has_observation, population)
140
+
141
+ add_supplemental_data_elements_to_population(population_def, population)
142
+
143
+ @populations << population
144
+ handle_stratifications(population_def, number_of_populations, population, id_def, population_index)
145
+ end
146
+
147
+ # Push in the stratification populations after the unstratified populations
148
+ @populations.concat(@stratifications)
149
+ [@populations, @population_criteria]
150
+ end
151
+
152
+ def add_supplemental_data_elements_to_population(population_def, population)
153
+ begin
154
+ supplemental_data_elements_def = population_def.xpath('cda:component/cql-ext:supplementalDataElement')
155
+ rescue Nokogiri::XML::XPath::SyntaxError
156
+ # Older fixtures without SDEs don't have the cql-ext namespace
157
+ return
158
+ end
159
+
160
+ supplemental_data_elements = []
161
+ supplemental_data_elements_def.each do |sde_def|
162
+ cql_definition_name = sde_def.at_xpath('cda:precondition/cda:criteriaReference/cda:id').attribute('extension').to_s.match(/"([^"]*)"/)[1]
163
+ supplemental_data_elements << cql_definition_name
164
+ end
165
+ population['supplemental_data_elements'] = supplemental_data_elements
166
+ end
123
167
  end
124
168
  end
@@ -75,7 +75,7 @@ module HQMF2CQL
75
75
  '2.16.840.1.113883.10.20.28.4.75' => { valueset_path: "./*/cda:participation[@typeCode='CSM']/cda:role[@classCode='ADMM']/cda:playingMaterial[@classCode='MAT' and @determinerCode='KIND']/cda:code", result_path: nil },
76
76
  '2.16.840.1.113883.10.20.28.4.76' => { valueset_path: "./*/cda:participation[@typeCode='CSM']/cda:role[@classCode='ADMM']/cda:playingMaterial[@classCode='MAT' and @determinerCode='KIND']/cda:code", result_path: nil },
77
77
  '2.16.840.1.113883.10.20.28.4.77' => { valueset_path: "./*/cda:participation[@typeCode='CSM']/cda:role[@classCode='ADMM']/cda:playingMaterial[@classCode='MAT' and @determinerCode='KIND']/cda:code", result_path: nil },
78
- '2.16.840.1.113883.10.20.28.4.78' => { valueset_path: "./*/cda:participation[@typeCode='CSM']/cda:role[@classCode='MANU']/cda:playingMaterial[@classCode='MMAT' and @determinerCode='KIND']/cda:code", result_path: nil },
78
+ '2.16.840.1.113883.10.20.28.4.78' => { valueset_path: "./*/cda:participation[@typeCode='CSM']/cda:role[@classCode='ADMM']/cda:playingMaterial[@classCode='MAT' and @determinerCode='KIND']/cda:code", result_path: nil },
79
79
  '2.16.840.1.113883.10.20.28.4.79' => { valueset_path: './*/cda:value', result_path: nil },
80
80
  '2.16.840.1.113883.10.20.28.4.80' => { valueset_path: './*/cda:value', result_path: nil },
81
81
  '2.16.840.1.113883.10.20.28.4.81' => { valueset_path: './*/cda:value', result_path: nil },
@@ -92,7 +92,10 @@ module HQMF2CQL
92
92
  '2.16.840.1.113883.10.20.28.4.117' => { valueset_path: './*/cda:code', result_path: './*/cda:value' },
93
93
  '2.16.840.1.113883.10.20.28.4.118' => { valueset_path: './*/cda:code', result_path: nil },
94
94
  '2.16.840.1.113883.10.20.28.4.119' => { valueset_path: "./*/cda:participation[@typeCode='CSM']/cda:role[@classCode='MANU']/cda:playingEntity[@classCode='MMAT']/cda:code", result_path: nil },
95
- '2.16.840.1.113883.10.20.28.4.120' => { valueset_path: "./*/cda:participation[@typeCode='CSM']/cda:role[@classCode='MANU']/cda:playingEntity[@classCode='MMAT']/cda:code", result_path: nil }
95
+ '2.16.840.1.113883.10.20.28.4.120' => { valueset_path: "./*/cda:participation[@typeCode='CSM']/cda:role[@classCode='MANU']/cda:playingEntity[@classCode='MMAT']/cda:code", result_path: nil },
96
+ '2.16.840.1.113883.10.20.28.4.130' => { valueset_path: './*/cda:value', result_path: nil },
97
+ '2.16.840.1.113883.10.20.28.4.131' => { valueset_path: './*/cda:code', result_path: nil },
98
+ '2.16.840.1.113883.10.20.28.4.132' => { valueset_path: "./*/cda:outboundRelationship[@typeCode='RSON']/cda:observationCriteria/cda:value", result_path: nil }
96
99
  }
97
100
  # rubocop:enable Metrics/LineLength
98
101
 
@@ -1,6 +1,5 @@
1
1
  <recordTarget>
2
2
  <patientRole>
3
- <id extension="{{mrn}}" root="Bonnie"/>
4
3
  <id extension="{{mrn}}" root="1.3.6.1.4.1.115" />
5
4
  <addr use="HP">
6
5
  <streetAddressLine>202 Burlington Rd.</streetAddressLine>
@@ -9,12 +9,12 @@
9
9
  {{#relevantPeriod}}
10
10
  {{{relevant_period}}}
11
11
  {{/relevantPeriod}}
12
+ {{#result}}
13
+ {{{result_value}}}
14
+ {{/result}}
12
15
  {{#authorDatetime}}
13
16
  {{> qrda_templates/template_partials/_author}}
14
17
  {{/authorDatetime}}
15
- {{#result}}
16
- {{> qrda_templates/template_partials/_results}}
17
- {{/result}}
18
18
  {{#negationRationale}}
19
19
  {{> qrda_templates/template_partials/_reason}}
20
20
  {{/negationRationale}}
@@ -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.132" extension="2017-08-01"/>
4
- <id root="1.3.6.1.4.1.115" extension="{{object_id}}"/>
5
4
  <code code="ENC" codeSystem="2.16.840.1.113883.5.6" displayName="Encounter" codeSystemName="ActClass"/>
6
5
  <entryRelationship typeCode="SUBJ">
7
6
  <encounter classCode="ENC" moodCode="RQO">
@@ -2,7 +2,6 @@
2
2
  <act classCode="ACT" moodCode="EVN">
3
3
  <!--Encounter performed Act -->
4
4
  <templateId extension="2017-08-01" root="2.16.840.1.113883.10.20.24.3.133"/>
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="EVN">
@@ -4,6 +4,7 @@ module QRDA
4
4
 
5
5
  def initialize(entry_finder = nil)
6
6
  super(entry_finder)
7
+ @id_xpath = './cda:id'
7
8
  @code_xpath = "./cda:consumable/cda:manufacturedProduct/cda:manufacturedMaterial/cda:code"
8
9
  @relevant_period_xpath = "./cda:effectiveTime"
9
10
  @author_datetime_xpath = "./cda:author/cda:time"
@@ -38,8 +38,8 @@ module QRDA
38
38
 
39
39
  def create_entry(entry_element, _nrh = NarrativeReferenceHandler.new)
40
40
  entry = @entry_class.new
41
- @entry_id_map[extract_id(entry_element, "./cda:id").value] ||= []
42
- @entry_id_map[extract_id(entry_element, "./cda:id").value] << entry.id
41
+ @entry_id_map[extract_id(entry_element, @id_xpath).value] ||= []
42
+ @entry_id_map[extract_id(entry_element, @id_xpath).value] << entry.id
43
43
  entry.dataElementCodes = extract_codes(entry_element, @code_xpath)
44
44
  extract_dates(entry_element, entry)
45
45
  if @result_xpath
@@ -3,6 +3,7 @@ module QRDA
3
3
  class AdverseEventImporter < SectionImporter
4
4
  def initialize(entry_finder = QRDA::Cat1::EntryFinder.new("./cda:entry/cda:observation[cda:templateId/@root = '2.16.840.1.113883.10.20.24.3.146']"))
5
5
  super(entry_finder)
6
+ @id_xpath = './cda:id'
6
7
  @code_xpath = './cda:entryRelationship/cda:observation/cda:value'
7
8
  @author_datetime_xpath = './cda:author/cda:time'
8
9
  @relevant_period_xpath = './cda:effectiveTime'
@@ -3,6 +3,7 @@ module QRDA
3
3
  class AllergyIntoleranceImporter < SectionImporter
4
4
  def initialize(entry_finder = QRDA::Cat1::EntryFinder.new("./cda:entry/cda:observation[cda:templateId/@root = '2.16.840.1.113883.10.20.24.3.147']"))
5
5
  super(entry_finder)
6
+ @id_xpath = './cda:id'
6
7
  @code_xpath = './cda:participant/cda:participantRole/cda:playingEntity/cda:code'
7
8
  @author_datetime_xpath = "./cda:author/cda:time"
8
9
  @prevalence_period_xpath = "./cda:effectiveTime"
@@ -3,9 +3,10 @@ module QRDA
3
3
  class AssessmentPerformedImporter < SectionImporter
4
4
  def initialize(entry_finder = QRDA::Cat1::EntryFinder.new("./cda:entry/cda:observation[cda:templateId/@root = '2.16.840.1.113883.10.20.24.3.144']"))
5
5
  super(entry_finder)
6
+ @id_xpath = './cda:id'
6
7
  @code_xpath = './cda:code'
7
8
  @author_datetime_xpath = "./cda:author/cda:time"
8
- @result_xpath = "./cda:value | ./cda:entryRelationship[@typeCode='REFR']/cda:observation/cda:value"
9
+ @result_xpath = "./cda:value"
9
10
  @method_xpath = './cda:methodCode'
10
11
  @components_xpath = "./cda:entryRelationship/cda:observation[cda:templateId/@root = '2.16.840.1.113883.10.20.24.3.149']"
11
12
  @entry_class = QDM::AssessmentPerformed
@@ -3,6 +3,7 @@ module QRDA
3
3
  class CommunicationFromPatientToProviderImporter < 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.2']"))
5
5
  super(entry_finder)
6
+ @id_xpath = './cda:id'
6
7
  @code_xpath = './cda:code'
7
8
  @author_datetime_xpath = "./cda:author/cda:time"
8
9
  @entry_class = QDM::CommunicationFromPatientToProvider
@@ -3,6 +3,7 @@ module QRDA
3
3
  class CommunicationFromProviderToPatientImporter < 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.3']"))
5
5
  super(entry_finder)
6
+ @id_xpath = './cda:id'
6
7
  @code_xpath = './cda:code'
7
8
  @author_datetime_xpath = "./cda:author/cda:time"
8
9
  @entry_class = QDM::CommunicationFromProviderToPatient
@@ -3,6 +3,7 @@ module QRDA
3
3
  class CommunicationFromProviderToProviderImporter < 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.4']"))
5
5
  super(entry_finder)
6
+ @id_xpath = './cda:id'
6
7
  @code_xpath = './cda:code'
7
8
  @author_datetime_xpath = "./cda:author/cda:time"
8
9
  @related_to_xpath = "./sdtc:inFulfillmentOf1/sdtc:actReference"
@@ -3,6 +3,7 @@ module QRDA
3
3
  class DeviceAppliedImporter < SectionImporter
4
4
  def initialize(entry_finder = QRDA::Cat1::EntryFinder.new("./cda:entry/cda:procedure[cda:templateId/@root = '2.16.840.1.113883.10.20.24.3.7']"))
5
5
  super(entry_finder)
6
+ @id_xpath = './cda:id'
6
7
  @code_xpath = './cda:participant/cda:participantRole/cda:playingDevice/cda:code'
7
8
  @author_datetime_xpath = "./cda:author/cda:time"
8
9
  @relevant_period_xpath = "./cda:effectiveTime"
@@ -3,6 +3,7 @@ module QRDA
3
3
  class DeviceOrderImporter < 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.130']"))
5
5
  super(entry_finder)
6
+ @id_xpath = './cda:entryRelationship/cda:supply/cda:id'
6
7
  @code_xpath = "./cda:entryRelationship/cda:supply[cda:templateId/@root = '2.16.840.1.113883.10.20.24.3.9']/cda:participant/cda:participantRole/cda:playingDevice/cda:code"
7
8
  @author_datetime_xpath = "./cda:entryRelationship/cda:supply[cda:templateId/@root = '2.16.840.1.113883.10.20.24.3.9']/cda:author/cda:time"
8
9
  @entry_class = QDM::DeviceOrder
@@ -3,6 +3,7 @@ module QRDA
3
3
  class DiagnosisImporter < 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.137']"))
5
5
  super(entry_finder)
6
+ @id_xpath = './cda:entryRelationship/cda:observation/cda:id'
6
7
  @code_xpath = "./cda:entryRelationship/cda:observation[cda:templateId/@root = '2.16.840.1.113883.10.20.24.3.135']/cda:value"
7
8
  @author_datetime_xpath = "./cda:author/cda:time"
8
9
  @prevalence_period_xpath = "./cda:entryRelationship/cda:observation[cda:templateId/@root = '2.16.840.1.113883.10.20.24.3.135']/cda:effectiveTime"
@@ -3,6 +3,7 @@ module QRDA
3
3
  class DiagnosticStudyOrderImporter < SectionImporter
4
4
  def initialize(entry_finder = QRDA::Cat1::EntryFinder.new("./cda:entry/cda:observation[cda:templateId/@root = '2.16.840.1.113883.10.20.24.3.17']"))
5
5
  super(entry_finder)
6
+ @id_xpath = './cda:id'
6
7
  @code_xpath = './cda:code'
7
8
  @author_datetime_xpath = "./cda:author/cda:time"
8
9
  @method_xpath = './cda:methodCode'
@@ -3,6 +3,7 @@ module QRDA
3
3
  class DiagnosticStudyPerformedImporter < SectionImporter
4
4
  def initialize(entry_finder = QRDA::Cat1::EntryFinder.new("./cda:entry/cda:observation[cda:templateId/@root = '2.16.840.1.113883.10.20.24.3.18']"))
5
5
  super(entry_finder)
6
+ @id_xpath = './cda:id'
6
7
  @code_xpath = './cda:code'
7
8
  @relevant_period_xpath = "./cda:effectiveTime"
8
9
  @author_datetime_xpath = "./cda:author/cda:time"
@@ -3,6 +3,7 @@ module QRDA
3
3
  class EncounterOrderImporter < 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.132']"))
5
5
  super(entry_finder)
6
+ @id_xpath = './cda:entryRelationship/cda:encounter/cda:id'
6
7
  @code_xpath = "./cda:entryRelationship/cda:encounter[cda:templateId/@root = '2.16.840.1.113883.10.20.24.3.22']/cda:code"
7
8
  @author_datetime_xpath = "./cda:entryRelationship/cda:encounter[cda:templateId/@root = '2.16.840.1.113883.10.20.24.3.22']/cda:author/cda:time"
8
9
  @facility_location_xpath = "./cda:entryRelationship/cda:encounter[cda:templateId/@root = '2.16.840.1.113883.10.20.24.3.22']/cda:participant[cda:templateId/@root = '2.16.840.1.113883.10.20.24.3.100']/cda:participantRole[@classCode='SDLOC']/cda:code"
@@ -3,6 +3,7 @@ module QRDA
3
3
  class EncounterPerformedImporter < 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.133']"))
5
5
  super(entry_finder)
6
+ @id_xpath = './cda:entryRelationship/cda:encounter/cda:id'
6
7
  @code_xpath = "./cda:entryRelationship/cda:encounter[cda:templateId/@root = '2.16.840.1.113883.10.20.24.3.23']/cda:code"
7
8
  @relevant_period_xpath = "./cda:entryRelationship/cda:encounter[cda:templateId/@root = '2.16.840.1.113883.10.20.24.3.23']/cda:effectiveTime"
8
9
  @author_datetime_xpath = "./cda:entryRelationship/cda:encounter[cda:templateId/@root = '2.16.840.1.113883.10.20.24.3.23']/cda:author/cda:time"
@@ -3,6 +3,7 @@ module QRDA
3
3
  class InterventionOrderImporter < 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.31']"))
5
5
  super(entry_finder)
6
+ @id_xpath = './cda:id'
6
7
  @code_xpath = './cda:code'
7
8
  @author_datetime_xpath = "./cda:author/cda:time"
8
9
  @entry_class = QDM::InterventionOrder
@@ -3,6 +3,7 @@ module QRDA
3
3
  class InterventionPerformedImporter < 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.32']"))
5
5
  super(entry_finder)
6
+ @id_xpath = './cda:id'
6
7
  @code_xpath = './cda:code'
7
8
  @relevant_period_xpath = "./cda:effectiveTime"
8
9
  @author_datetime_xpath = "./cda:author/cda:time"
@@ -3,6 +3,7 @@ module QRDA
3
3
  class LaboratoryTestOrderImporter < SectionImporter
4
4
  def initialize(entry_finder = QRDA::Cat1::EntryFinder.new("./cda:entry/cda:observation[cda:templateId/@root = '2.16.840.1.113883.10.20.24.3.37']"))
5
5
  super(entry_finder)
6
+ @id_xpath = './cda:id'
6
7
  @code_xpath = './cda:code'
7
8
  @author_datetime_xpath = "./cda:author/cda:time"
8
9
  @method_xpath = './cda:methodCode'
@@ -3,6 +3,7 @@ module QRDA
3
3
  class LaboratoryTestPerformedImporter < SectionImporter
4
4
  def initialize(entry_finder = QRDA::Cat1::EntryFinder.new("./cda:entry/cda:observation[cda:templateId/@root = '2.16.840.1.113883.10.20.24.3.38']"))
5
5
  super(entry_finder)
6
+ @id_xpath = './cda:id'
6
7
  @code_xpath = './cda:code'
7
8
  @relevant_period_xpath = "./cda:effectiveTime"
8
9
  @author_datetime_xpath = "./cda:author/cda:time"
@@ -3,6 +3,7 @@ module QRDA
3
3
  class PatientCharacteristicExpired < SectionImporter
4
4
  def initialize(entry_finder = QRDA::Cat1::EntryFinder.new("./cda:entry/cda:observation[cda:templateId/@root = '2.16.840.1.113883.10.20.24.3.54']"))
5
5
  super(entry_finder)
6
+ @id_xpath = './cda:id'
6
7
  @code_xpath = './cda:value'
7
8
  @expired_datetime_xpath = './cda:effectiveTime/cda:low'
8
9
  @cause = "./cda:entryRelationship/cda:observation[cda:templateId/@root = '2.16.840.1.113883.10.20.22.4.4']/cda:value"
@@ -3,6 +3,7 @@ module QRDA
3
3
  class PhysicalExamPerformedImporter < SectionImporter
4
4
  def initialize(entry_finder = QRDA::Cat1::EntryFinder.new("./cda:entry/cda:observation[cda:templateId/@root = '2.16.840.1.113883.10.20.24.3.59']"))
5
5
  super(entry_finder)
6
+ @id_xpath = './cda:id'
6
7
  @code_xpath = "./cda:code"
7
8
  @relevant_period_xpath = "./cda:effectiveTime"
8
9
  @author_datetime_xpath = "./cda:author/cda:time"
@@ -3,6 +3,7 @@ module QRDA
3
3
  class ProcedureOrderImporter < SectionImporter
4
4
  def initialize(entry_finder = QRDA::Cat1::EntryFinder.new("./cda:entry/cda:procedure[cda:templateId/@root = '2.16.840.1.113883.10.20.24.3.63']"))
5
5
  super(entry_finder)
6
+ @id_xpath = './cda:id'
6
7
  @code_xpath = './cda:code'
7
8
  @author_datetime_xpath = "./cda:author/cda:time"
8
9
  @method_xpath = './cda:methodCode'
@@ -3,6 +3,7 @@ module QRDA
3
3
  class ProcedurePerformedImporter < SectionImporter
4
4
  def initialize(entry_finder = QRDA::Cat1::EntryFinder.new("./cda:entry/cda:procedure[cda:templateId/@root = '2.16.840.1.113883.10.20.24.3.64']"))
5
5
  super(entry_finder)
6
+ @id_xpath = './cda:id'
6
7
  @code_xpath = "./cda:code"
7
8
  @relevant_period_xpath = "./cda:effectiveTime"
8
9
  @author_datetime_xpath = "./cda:author/cda:time"
@@ -34,7 +34,8 @@ module HQMF
34
34
  '2.16.840.1.113883.1.11.78' => "HL7 Observation Interpretation",
35
35
  '2.16.840.1.113883.3.221.5' => "Source of Payment Typology",
36
36
  '2.16.840.1.113883.6.13' => 'CDT',
37
- '2.16.840.1.113883.18.2' => 'AdministrativeSex'
37
+ '2.16.840.1.113883.18.2' => 'AdministrativeSex',
38
+ '2.16.840.1.113883.5.1' => 'AdministrativeGender'
38
39
  }
39
40
 
40
41
  CODE_SYSTEM_ALIASES = {
@@ -219,18 +219,6 @@
219
219
  "definition":"care_goal",
220
220
  "status":"",
221
221
  "negation":false},
222
- "2.16.840.1.113883.10.20.28.4.8":{
223
- "definition":"communication_from_patient_to_provider",
224
- "status":"",
225
- "negation":false},
226
- "2.16.840.1.113883.10.20.28.4.9":{
227
- "definition":"communication_from_provider_to_patient",
228
- "status":"",
229
- "negation":false},
230
- "2.16.840.1.113883.10.20.28.4.10":{
231
- "definition":"communication_from_provider_to_provider",
232
- "status":"",
233
- "negation":false},
234
222
  "2.16.840.1.113883.10.20.28.4.13":{
235
223
  "definition":"device",
236
224
  "status":"applied",
@@ -371,6 +359,10 @@
371
359
  "definition":"transfer_to",
372
360
  "status":"",
373
361
  "negation":false},
362
+ "2.16.840.1.113883.10.20.28.4.131":{
363
+ "definition":"assessment",
364
+ "status":"ordered",
365
+ "negation":false},
374
366
  "2.16.840.1.113883.10.20.28.4.117":{
375
367
  "definition":"assessment",
376
368
  "status":"performed",
@@ -386,5 +378,13 @@
386
378
  "2.16.840.1.113883.10.20.28.4.119": {
387
379
  "definition":"allergy_intolerance",
388
380
  "status":"",
389
- "negation":false}
381
+ "negation":false},
382
+ "2.16.840.1.113883.10.20.28.4.130": {
383
+ "definition":"participation",
384
+ "status":"",
385
+ "negation":false},
386
+ "2.16.840.1.113883.10.20.28.4.132": {
387
+ "definition":"communication",
388
+ "status":"performed",
389
+ "negation":true}
390
390
  }
@@ -0,0 +1,299 @@
1
+ require 'rest_client'
2
+ require 'uri'
3
+
4
+ module Util
5
+ module VSAC
6
+ # Generic VSAC related exception.
7
+ class VSACError < StandardError
8
+ end
9
+
10
+ # Error represnting a not found response from the API. Includes OID for reporting to user.
11
+ class VSNotFoundError < VSACError
12
+ attr_reader :oid
13
+ def initialize(oid)
14
+ super("Value Set (#{oid}) was not found.")
15
+ @oid = oid
16
+ end
17
+ end
18
+
19
+ # Error represnting a program not found response from the API.
20
+ class VSACProgramNotFoundError < VSACError
21
+ attr_reader :oid
22
+ def initialize(program)
23
+ super("VSAC Program #{program} does not exist.")
24
+ end
25
+ end
26
+
27
+ # Error represnting a response from the API that had no concepts.
28
+ class VSEmptyError < VSACError
29
+ attr_reader :oid
30
+ def initialize(oid)
31
+ super("Value Set (#{oid}) is empty.")
32
+ @oid = oid
33
+ end
34
+ end
35
+
36
+ # Raised when the ticket granting ticket has expired.
37
+ class VSACTicketExpiredError < VSACError
38
+ def initialize
39
+ super('VSAC session expired. Please re-enter credentials and try again.')
40
+ end
41
+ end
42
+
43
+ # Raised when the user credentials were invalid.
44
+ class VSACInvalidCredentialsError < VSACError
45
+ def initialize
46
+ super('VSAC ULMS credentials are invalid.')
47
+ end
48
+ end
49
+
50
+ # Raised when a call requiring auth is attempted when no ticket_granting_ticket or credentials were provided.
51
+ class VSACNoCredentialsError < VSACError
52
+ def initialize
53
+ super('VSAC ULMS credentials were not provided.')
54
+ end
55
+ end
56
+
57
+ # Raised when the arguments passed in are bad.
58
+ class VSACArgumentError < VSACError
59
+ end
60
+
61
+ class VSACAPI
62
+ # The default program to use for get_program_details and get_program_release_names calls.
63
+ # This can be overriden by providing a :program in the config or by the single optional parameter for those
64
+ # methods.
65
+ DEFAULT_PROGRAM = "CMS eCQM"
66
+
67
+ # This is the value of the service parameter passed when getting a ticket. This never changes.
68
+ TICKET_SERVICE_PARAM = "http://umlsks.nlm.nih.gov"
69
+
70
+ # The ticket granting that will be obtained if needed. Accessible so it may be stored in user session.
71
+ # Is a hash of the :ticket and time it :expires.
72
+ attr_reader :ticket_granting_ticket
73
+
74
+ ##
75
+ # Creates a new VSACAPI. If credentials were provided they are checked now. If no credentials
76
+ # are provided then the API can still be used for utility methods.
77
+ #
78
+ # Options for the API are passed in as a hash.
79
+ # * config -
80
+ def initialize(options)
81
+ # check that :config exists and has needed fields
82
+ raise VSACArgumentError.new("Required param :config is missing or empty.") if options[:config].nil?
83
+ symbolized_config = options[:config].symbolize_keys
84
+ unless check_config symbolized_config
85
+ raise VSACArgumentError.new("Required param :config is missing required URLs.")
86
+ end
87
+ @config = symbolized_config
88
+
89
+ # if a ticket_granting_ticket was passed in, check it and raise errors if found
90
+ # username and password will be ignored
91
+ if !options[:ticket_granting_ticket].nil?
92
+ provided_ticket_granting_ticket = options[:ticket_granting_ticket]
93
+ if provided_ticket_granting_ticket[:ticket].nil? || provided_ticket_granting_ticket[:expires].nil?
94
+ raise VSACArgumentError.new("Optional param :ticket_granting_ticket is missing :ticket or :expires")
95
+ end
96
+
97
+ # check if it has expired
98
+ if Time.now > provided_ticket_granting_ticket[:expires]
99
+ raise VSACTicketExpiredError.new
100
+ end
101
+
102
+ # ticket granting ticket looks good
103
+ @ticket_granting_ticket = { ticket: provided_ticket_granting_ticket[:ticket],
104
+ expires: provided_ticket_granting_ticket[:expires] }
105
+
106
+ # if username and password were provided use them to get a ticket granting ticket
107
+ elsif !options[:username].nil? && !options[:password].nil?
108
+ @ticket_granting_ticket = get_ticket_granting_ticket(options[:username], options[:password])
109
+ end
110
+ end
111
+
112
+ ##
113
+ # Gets the list of profiles. This may be used without credentials.
114
+ #
115
+ # Returns a list of profile names. These are kept in the order that VSAC provides them in.
116
+ def get_profile_names
117
+ profiles_response = RestClient.get("#{@config[:utility_url]}/profiles")
118
+ profiles = []
119
+
120
+ # parse xml response and get text content of each profile element
121
+ doc = Nokogiri::XML(profiles_response)
122
+ profile_list = doc.at_xpath("/ProfileList")
123
+ profile_list.xpath("//profile").each do |profile|
124
+ profiles << profile.text
125
+ end
126
+
127
+ return profiles
128
+ end
129
+
130
+ ##
131
+ # Gets the list of programs. This may be used without credentials.
132
+ #
133
+ # Returns a list of program names. These are kept in the order that VSAC provides them in.
134
+ def get_program_names
135
+ programs_response = RestClient.get("#{@config[:utility_url]}/programs")
136
+ program_names = []
137
+
138
+ # parse json response and return the names of the programs
139
+ programs_info = JSON.parse(programs_response)['Program']
140
+ programs_info.each do |program|
141
+ program_names << program['name']
142
+ end
143
+
144
+ return program_names
145
+ end
146
+
147
+ ##
148
+ # Gets the details for a program. This may be used without credentials.
149
+ #
150
+ # Optional parameter program is the program to request from the API. If it is not provided it will look for
151
+ # a :program in the config passed in during construction. If there is no :program in the config it will use
152
+ # the DEFAULT_PROGRAM constant for the program.
153
+ #
154
+ # Returns the JSON parsed response for program details.
155
+ def get_program_details(program = nil)
156
+ # if no program was provided use the one in the config or default in constant
157
+ if program.nil?
158
+ program = @config.fetch(:program, DEFAULT_PROGRAM)
159
+ end
160
+
161
+ begin
162
+ # parse json response and return it
163
+ return JSON.parse(RestClient.get("#{@config[:utility_url]}/program/#{ERB::Util.url_encode(program)}"))
164
+ rescue RestClient::ResourceNotFound
165
+ raise VSACProgramNotFoundError.new(program)
166
+ end
167
+ end
168
+
169
+ ##
170
+ # Gets the latest profile for a program. This is a separate call from the program details call. It returns JSON
171
+ # with only the name of the latest profile and the timestamp of the request. ex:
172
+ # {
173
+ # "name": "eCQM Update 2018-05-04",
174
+ # "requestTime": "2018-05-21 03:39:04 PM"
175
+ # }
176
+ #
177
+ # Optional parameter program is the program to request from the API. If it is not provided it will look for
178
+ # a :program in the config passed in during construction. If there is no :program in the config it will use
179
+ # the DEFAULT_PROGRAM constant for the program.
180
+ #
181
+ # Returns the name of the latest profile for the given program.
182
+ def get_latest_profile_for_program(program = nil)
183
+ # if no program was provided use the one in the config or default in constant
184
+ if program.nil?
185
+ program = @config.fetch(:program, DEFAULT_PROGRAM)
186
+ end
187
+
188
+ begin
189
+ # parse json response and return it
190
+ parsed_response = JSON.parse(RestClient.get("#{@config[:utility_url]}/program/#{ERB::Util.url_encode(program)}/latest%20profile"))
191
+
192
+ # As of 5/17/18 VSAC does not return 404 when an invalid profile is provided. It just doesnt fill the name
193
+ # attribute in the 200 response. We need to check this.
194
+ raise VSACProgramNotFoundError.new(program) if parsed_response['name'].nil?
195
+ return parsed_response['name']
196
+
197
+ # keeping this rescue block in case the API is changed to return 404 for invalid profile
198
+ rescue RestClient::ResourceNotFound
199
+ raise VSACProgramNotFoundError.new(program)
200
+ end
201
+ end
202
+
203
+ ##
204
+ # Gets the releases for a program. This may be used without credentials.
205
+ #
206
+ # Optional parameter program is the program to request from the API. If it is not provided it will look for
207
+ # a :program in the config passed in during construction. If there is no :program in the config it will use
208
+ # the DEFAULT_PROGRAM constant for the program.
209
+ #
210
+ # Returns a list of release names in a program. These are kept in the order that VSAC provides them in.
211
+ def get_program_release_names(program = nil)
212
+ program_details = get_program_details(program)
213
+ releases = []
214
+
215
+ # pull just the release names out
216
+ program_details['release'].each do |release|
217
+ releases << release['name']
218
+ end
219
+
220
+ return releases
221
+ end
222
+
223
+ ##
224
+ # Gets a valueset. This requires credentials.
225
+ #
226
+ def get_valueset(oid, options = {})
227
+ # base parameter oid is always needed
228
+ params = { id: oid }
229
+
230
+ # release parameter, should be used moving forward
231
+ unless options[:release].nil?
232
+ params[:release] = options[:release]
233
+ end
234
+
235
+ # profile parameter, may be needed for getting draft value sets
236
+ if !options[:profile].nil?
237
+ params[:profile] = options[:profile]
238
+ unless options[:include_draft].nil?
239
+ params[:includeDraft] = !options[:include_draft].nil? ? 'yes' : 'no'
240
+ end
241
+ else
242
+ unless options[:include_draft].nil?
243
+ raise VSACArgumentError.new("Option :include_draft requires :profile to be provided.")
244
+ end
245
+ end
246
+
247
+ # version parameter, rarely used
248
+ unless options[:version].nil?
249
+ params[:version] = options[:version]
250
+ end
251
+
252
+ # get a new service ticket
253
+ params[:ticket] = get_ticket
254
+
255
+ # run request
256
+ begin
257
+ return RestClient.get("#{@config[:content_url]}/RetrieveMultipleValueSets", params: params)
258
+ rescue RestClient::ResourceNotFound
259
+ raise VSNotFoundError.new(oid)
260
+ rescue RestClient::InternalServerError
261
+ raise VSACError.new("Server error response from VSAC for (#{oid}).")
262
+ end
263
+ end
264
+
265
+ private
266
+
267
+ def get_ticket
268
+ # if there is no ticket granting ticket then we should raise an error
269
+ raise VSACNoCredentialsError.new unless @ticket_granting_ticket
270
+ # if the ticket granting ticket has expired, throw an error
271
+ raise VSACTicketExpiredError.new if Time.now > @ticket_granting_ticket[:expires]
272
+
273
+ # attempt to get a ticket
274
+ begin
275
+ ticket = RestClient.post("#{@config[:auth_url]}/Ticket/#{@ticket_granting_ticket[:ticket]}", service: TICKET_SERVICE_PARAM)
276
+ return ticket.to_s
277
+ rescue RestClient::Unauthorized
278
+ @ticket_granting_ticket[:expires] = Time.now
279
+ raise VSACTicketExpiredError.new
280
+ end
281
+ end
282
+
283
+ def get_ticket_granting_ticket(username, password)
284
+ ticket = RestClient.post("#{@config[:auth_url]}/Ticket", username: username, password: password)
285
+ return { ticket: String.new(ticket), expires: Time.now + 8.hours }
286
+ rescue RestClient::Unauthorized
287
+ raise VSACInvalidCredentialsError.new
288
+ end
289
+
290
+ # Checks to ensure the API config has all necessary fields
291
+ def check_config(config)
292
+ return !config.nil? &&
293
+ !config[:auth_url].nil? &&
294
+ !config[:content_url].nil? &&
295
+ !config[:utility_url].nil?
296
+ end
297
+ end
298
+ end
299
+ end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cqm-parsers
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - The MITRE Corporation
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-10-09 00:00:00.000000000 Z
11
+ date: 2019-01-25 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: mustache
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: rest-client
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -376,6 +390,7 @@ files:
376
390
  - lib/util/hqmf_template_oid_map.json
377
391
  - lib/util/hqmfr2_template_oid_map.json
378
392
  - lib/util/hqmfr2cql_template_oid_map.json
393
+ - lib/util/vsac_api.rb
379
394
  homepage: https://github.com/projecttacoma/cqm-parsers
380
395
  licenses:
381
396
  - Apache-2.0
@@ -396,7 +411,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
396
411
  version: '0'
397
412
  requirements: []
398
413
  rubyforge_project:
399
- rubygems_version: 2.6.14
414
+ rubygems_version: 2.7.7
400
415
  signing_key:
401
416
  specification_version: 4
402
417
  summary: A library for parsing HQMF documents.