hqmf-parser 1.0.6 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Gemfile +1 -2
- data/lib/hqmf-generator/hqmf-generator.rb +1 -17
- data/lib/hqmf-model/data_criteria.json +31 -31
- data/lib/hqmf-model/data_criteria.rb +44 -25
- data/lib/hqmf-model/document.rb +4 -0
- data/lib/hqmf-model/population_criteria.rb +3 -2
- data/lib/hqmf-model/precondition.rb +6 -0
- data/lib/hqmf-model/types.rb +1 -1
- data/lib/hqmf-parser.rb +2 -0
- data/lib/hqmf-parser/1.0/data_criteria.rb +6 -19
- data/lib/hqmf-parser/1.0/document.rb +8 -14
- data/lib/hqmf-parser/1.0/expression.rb +6 -0
- data/lib/hqmf-parser/1.0/observation.rb +61 -0
- data/lib/hqmf-parser/1.0/restriction.rb +5 -2
- data/lib/hqmf-parser/converter/pass1/data_criteria_converter.rb +13 -17
- data/lib/hqmf-parser/converter/pass1/document_converter.rb +2 -2
- data/lib/hqmf-parser/converter/pass1/population_criteria_converter.rb +15 -9
- data/lib/hqmf-parser/converter/pass1/precondition_converter.rb +12 -3
- data/lib/hqmf-parser/converter/pass1/precondition_extractor.rb +34 -5
- data/lib/hqmf-parser/converter/pass1/simple_precondition.rb +1 -13
- data/lib/hqmf-parser/converter/pass2/operator_converter.rb +2 -69
- data/lib/hqmf-parser/parser.rb +15 -2
- data/lib/util/counter.rb +20 -0
- metadata +4 -2
data/Gemfile
CHANGED
@@ -6,8 +6,7 @@ gem 'pry-nav'
|
|
6
6
|
gem 'nokogiri'
|
7
7
|
gem 'rubyzip'
|
8
8
|
|
9
|
-
|
10
|
-
gem "health-data-standards", '~> 2.1.4'
|
9
|
+
gem "health-data-standards", '~> 2.2.0'
|
11
10
|
gem "bson_ext"
|
12
11
|
|
13
12
|
# below are gems required for excel spreadsheet processing
|
@@ -4,7 +4,7 @@ module HQMF2
|
|
4
4
|
def self.render_template(name, params)
|
5
5
|
template_path = File.expand_path(File.join('..', "#{name}.xml.erb"), __FILE__)
|
6
6
|
template_str = File.read(template_path)
|
7
|
-
template = ERB.new(template_str, nil, '-', "_templ#{
|
7
|
+
template = ERB.new(template_str, nil, '-', "_templ#{HQMF::Counter.instance.next}")
|
8
8
|
context = ErbContext.new(params)
|
9
9
|
template.result(context.get_binding)
|
10
10
|
end
|
@@ -288,21 +288,5 @@ module HQMF2
|
|
288
288
|
end
|
289
289
|
end
|
290
290
|
|
291
|
-
# Simple class to issue monotonically increasing integer identifiers
|
292
|
-
class Counter
|
293
|
-
def initialize
|
294
|
-
@count = 0
|
295
|
-
end
|
296
|
-
|
297
|
-
def new_id
|
298
|
-
@count+=1
|
299
|
-
end
|
300
|
-
end
|
301
|
-
|
302
|
-
# Singleton to keep a count of template identifiers
|
303
|
-
class TemplateCounter < Counter
|
304
|
-
include Singleton
|
305
|
-
end
|
306
|
-
|
307
291
|
end
|
308
292
|
end
|
@@ -180,7 +180,7 @@
|
|
180
180
|
"definition":"encounter",
|
181
181
|
"status":"active",
|
182
182
|
"sub_category":"",
|
183
|
-
"hard_status":
|
183
|
+
"hard_status":true,
|
184
184
|
"patient_api_function":"encounters",
|
185
185
|
"standard_category":"encounter",
|
186
186
|
"qds_data_type":"encounter",
|
@@ -202,7 +202,7 @@
|
|
202
202
|
"definition":"encounter",
|
203
203
|
"status":"ordered",
|
204
204
|
"sub_category":"",
|
205
|
-
"hard_status":
|
205
|
+
"hard_status":true,
|
206
206
|
"patient_api_function":"encounters",
|
207
207
|
"standard_category":"encounter",
|
208
208
|
"qds_data_type":"encounter",
|
@@ -213,7 +213,7 @@
|
|
213
213
|
"definition":"encounter",
|
214
214
|
"status":"recommended",
|
215
215
|
"sub_category":"",
|
216
|
-
"hard_status":
|
216
|
+
"hard_status":true,
|
217
217
|
"patient_api_function":"encounters",
|
218
218
|
"standard_category":"encounter",
|
219
219
|
"qds_data_type":"encounter",
|
@@ -246,7 +246,7 @@
|
|
246
246
|
"definition":"procedure",
|
247
247
|
"status":"ordered",
|
248
248
|
"sub_category":"",
|
249
|
-
"hard_status":
|
249
|
+
"hard_status":true,
|
250
250
|
"patient_api_function":"allProcedures",
|
251
251
|
"standard_category":"procedure",
|
252
252
|
"qds_data_type":"procedure_performed",
|
@@ -290,7 +290,7 @@
|
|
290
290
|
"definition":"procedure",
|
291
291
|
"status":"recommended",
|
292
292
|
"sub_category":"",
|
293
|
-
"hard_status":
|
293
|
+
"hard_status":true,
|
294
294
|
"patient_api_function":"allProcedures",
|
295
295
|
"standard_category":"procedure",
|
296
296
|
"qds_data_type":"procedure_performed",
|
@@ -331,10 +331,10 @@
|
|
331
331
|
"diagnosis_family_history":{
|
332
332
|
"title":"diagnosis, family history",
|
333
333
|
"category":"conditions",
|
334
|
-
"definition":"
|
335
|
-
"status":"",
|
334
|
+
"definition":"diagnosis",
|
335
|
+
"status":"family_history",
|
336
336
|
"sub_category":"family_history",
|
337
|
-
"hard_status":
|
337
|
+
"hard_status":true,
|
338
338
|
"patient_api_function":"allProblems",
|
339
339
|
"standard_category":"diagnosis_condition_problem",
|
340
340
|
"qds_data_type":"diagnosis_active",
|
@@ -422,7 +422,7 @@
|
|
422
422
|
"definition":"diagnostic_study",
|
423
423
|
"status":"ordered",
|
424
424
|
"sub_category":"",
|
425
|
-
"hard_status":
|
425
|
+
"hard_status":true,
|
426
426
|
"patient_api_function":"allProcedures",
|
427
427
|
"standard_category":"diagnostic_study",
|
428
428
|
"qds_data_type":"diagnostic_study_performed",
|
@@ -444,7 +444,7 @@
|
|
444
444
|
"definition":"medication",
|
445
445
|
"status":"dispensed",
|
446
446
|
"sub_category":"",
|
447
|
-
"hard_status":
|
447
|
+
"hard_status":true,
|
448
448
|
"patient_api_function":"allMedications",
|
449
449
|
"standard_category":"medication",
|
450
450
|
"qds_data_type":"medication_dispensed",
|
@@ -455,7 +455,7 @@
|
|
455
455
|
"definition":"medication",
|
456
456
|
"status":"ordered",
|
457
457
|
"sub_category":"",
|
458
|
-
"hard_status":
|
458
|
+
"hard_status":true,
|
459
459
|
"patient_api_function":"allMedications",
|
460
460
|
"standard_category":"medication",
|
461
461
|
"qds_data_type":"medication_order",
|
@@ -477,7 +477,7 @@
|
|
477
477
|
"definition":"medication",
|
478
478
|
"status":"administered",
|
479
479
|
"sub_category":"",
|
480
|
-
"hard_status":
|
480
|
+
"hard_status":true,
|
481
481
|
"patient_api_function":"allMedications",
|
482
482
|
"standard_category":"medication",
|
483
483
|
"qds_data_type":"medication_administered",
|
@@ -486,9 +486,9 @@
|
|
486
486
|
"title":"medication, discharge",
|
487
487
|
"category":"medications",
|
488
488
|
"definition":"medication_discharge",
|
489
|
-
"status":"",
|
489
|
+
"status":"discharge",
|
490
490
|
"sub_category":"discharge",
|
491
|
-
"hard_status":
|
491
|
+
"hard_status":true,
|
492
492
|
"patient_api_function":"allMedications",
|
493
493
|
"standard_category":"medication",
|
494
494
|
"qds_data_type":"medication_discharge",
|
@@ -543,7 +543,7 @@
|
|
543
543
|
"definition":"physical_exam",
|
544
544
|
"status":"ordered",
|
545
545
|
"sub_category":"",
|
546
|
-
"hard_status":
|
546
|
+
"hard_status":true,
|
547
547
|
"patient_api_function":"procedureResults",
|
548
548
|
"standard_category":"physical_exam",
|
549
549
|
"qds_data_type":"physical_exam",
|
@@ -565,7 +565,7 @@
|
|
565
565
|
"definition":"physical_exam",
|
566
566
|
"status":"recommended",
|
567
567
|
"sub_category":"",
|
568
|
-
"hard_status":
|
568
|
+
"hard_status":true,
|
569
569
|
"patient_api_function":"procedureResults",
|
570
570
|
"standard_category":"physical_exam",
|
571
571
|
"qds_data_type":"physical_exam",
|
@@ -620,7 +620,7 @@
|
|
620
620
|
"definition":"laboratory_test",
|
621
621
|
"status":"ordered",
|
622
622
|
"sub_category":"",
|
623
|
-
"hard_status":
|
623
|
+
"hard_status":true,
|
624
624
|
"patient_api_function":"laboratoryTests",
|
625
625
|
"standard_category":"laboratory_test",
|
626
626
|
"qds_data_type":"laboratory_test",
|
@@ -631,7 +631,7 @@
|
|
631
631
|
"definition":"laboratory_test",
|
632
632
|
"status":"recommended",
|
633
633
|
"sub_category":"",
|
634
|
-
"hard_status":
|
634
|
+
"hard_status":true,
|
635
635
|
"patient_api_function":"laboratoryTests",
|
636
636
|
"standard_category":"laboratory_test",
|
637
637
|
"qds_data_type":"laboratory_test",
|
@@ -697,7 +697,7 @@
|
|
697
697
|
"definition":"device",
|
698
698
|
"status":"applied",
|
699
699
|
"sub_category":"",
|
700
|
-
"hard_status":
|
700
|
+
"hard_status":true,
|
701
701
|
"patient_api_function":"allDevices",
|
702
702
|
"standard_category":"device",
|
703
703
|
"qds_data_type":"device_applied",
|
@@ -741,7 +741,7 @@
|
|
741
741
|
"definition":"device",
|
742
742
|
"status":"ordered",
|
743
743
|
"sub_category":"",
|
744
|
-
"hard_status":
|
744
|
+
"hard_status":true,
|
745
745
|
"patient_api_function":"allDevices",
|
746
746
|
"standard_category":"device",
|
747
747
|
"qds_data_type":"device_applied",
|
@@ -752,7 +752,7 @@
|
|
752
752
|
"definition":"device",
|
753
753
|
"status":"recommended",
|
754
754
|
"sub_category":"",
|
755
|
-
"hard_status":
|
755
|
+
"hard_status":true,
|
756
756
|
"patient_api_function":"allDevices",
|
757
757
|
"standard_category":"device",
|
758
758
|
"qds_data_type":"device_applied",
|
@@ -774,7 +774,7 @@
|
|
774
774
|
"definition":"substance",
|
775
775
|
"status":"administered",
|
776
776
|
"sub_category":"",
|
777
|
-
"hard_status":
|
777
|
+
"hard_status":true,
|
778
778
|
"patient_api_function":"allMedications",
|
779
779
|
"standard_category":"medication",
|
780
780
|
"qds_data_type":"medication_administered",
|
@@ -785,7 +785,7 @@
|
|
785
785
|
"definition":"substance",
|
786
786
|
"status":"ordered",
|
787
787
|
"sub_category":"",
|
788
|
-
"hard_status":
|
788
|
+
"hard_status":true,
|
789
789
|
"patient_api_function":"allMedications",
|
790
790
|
"standard_category":"medication",
|
791
791
|
"qds_data_type":"medication_order",
|
@@ -829,7 +829,7 @@
|
|
829
829
|
"definition":"substance",
|
830
830
|
"status":"recommended",
|
831
831
|
"sub_category":"",
|
832
|
-
"hard_status":
|
832
|
+
"hard_status":true,
|
833
833
|
"patient_api_function":"allMedications",
|
834
834
|
"standard_category":"medication",
|
835
835
|
"qds_data_type":"medication_order",
|
@@ -873,7 +873,7 @@
|
|
873
873
|
"definition":"intervention",
|
874
874
|
"status":"ordered",
|
875
875
|
"sub_category":"",
|
876
|
-
"hard_status":
|
876
|
+
"hard_status":true,
|
877
877
|
"patient_api_function":"allProcedures",
|
878
878
|
"standard_category":"procedure",
|
879
879
|
"qds_data_type":"procedure_performed",
|
@@ -906,7 +906,7 @@
|
|
906
906
|
"definition":"intervention",
|
907
907
|
"status":"recommended",
|
908
908
|
"sub_category":"",
|
909
|
-
"hard_status":
|
909
|
+
"hard_status":true,
|
910
910
|
"patient_api_function":"allProcedures",
|
911
911
|
"standard_category":"procedure",
|
912
912
|
"qds_data_type":"procedure_performed",
|
@@ -939,7 +939,7 @@
|
|
939
939
|
"definition":"symptom",
|
940
940
|
"status":"assessed",
|
941
941
|
"sub_category":"",
|
942
|
-
"hard_status":
|
942
|
+
"hard_status":true,
|
943
943
|
"patient_api_function":"allProblems",
|
944
944
|
"standard_category":"symptom",
|
945
945
|
"qds_data_type":"symptom",
|
@@ -994,7 +994,7 @@
|
|
994
994
|
"definition":"functional_status",
|
995
995
|
"status":"ordered",
|
996
996
|
"sub_category":"",
|
997
|
-
"hard_status":
|
997
|
+
"hard_status":true,
|
998
998
|
"patient_api_function":"procedureResults",
|
999
999
|
"standard_category":"procedure",
|
1000
1000
|
"qds_data_type":"procedure_result",
|
@@ -1005,7 +1005,7 @@
|
|
1005
1005
|
"definition":"functional_status",
|
1006
1006
|
"status":"recommended",
|
1007
1007
|
"sub_category":"",
|
1008
|
-
"hard_status":
|
1008
|
+
"hard_status":true,
|
1009
1009
|
"patient_api_function":"procedureResults",
|
1010
1010
|
"standard_category":"procedure",
|
1011
1011
|
"qds_data_type":"procedure_result",
|
@@ -1105,7 +1105,7 @@
|
|
1105
1105
|
"status":"",
|
1106
1106
|
"sub_category":"from",
|
1107
1107
|
"hard_status":false,
|
1108
|
-
"patient_api_function":"",
|
1108
|
+
"patient_api_function":"encounters",
|
1109
1109
|
"standard_category":"",
|
1110
1110
|
"qds_data_type":"",
|
1111
1111
|
"not_supported":false},
|
@@ -1116,7 +1116,7 @@
|
|
1116
1116
|
"status":"",
|
1117
1117
|
"sub_category":"to",
|
1118
1118
|
"hard_status":false,
|
1119
|
-
"patient_api_function":"",
|
1119
|
+
"patient_api_function":"encounters",
|
1120
1120
|
"standard_category":"",
|
1121
1121
|
"qds_data_type":"",
|
1122
1122
|
"not_supported":false}
|
@@ -10,26 +10,28 @@ module HQMF
|
|
10
10
|
XPRODUCT = 'XPRODUCT'
|
11
11
|
UNION = 'UNION'
|
12
12
|
|
13
|
-
FIELDS = {'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.3.560.1.1021.2'},
|
14
|
-
'ORDINAL'=>{title:'Ordinal',coded_entry_method: :
|
15
|
-
'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.3.560.1.1017.2'},
|
16
|
-
'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'},
|
17
|
-
'CUMULATIVE_MEDICATION_DURATION'=>{title:'Cumulative Medication Duration',coded_entry_method: :cumulative_medication_duration, code: '363819003', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.3.560.1.1001.3'},
|
18
|
-
'FACILITY_LOCATION'=>{title:'Facility Location',coded_entry_method: :
|
19
|
-
'FACILITY_LOCATION_ARRIVAL_DATETIME'=>{title:'Facility Location Arrival Date/Time',coded_entry_method: :
|
20
|
-
'FACILITY_LOCATION_DEPARTURE_DATETIME'=>{title:'Facility Location Departure Date/Time',coded_entry_method: :
|
21
|
-
'DISCHARGE_DATETIME'=>{title:'Discharge Date/Time',coded_entry_method: :
|
22
|
-
'DISCHARGE_STATUS'=>{title:'Discharge Status',coded_entry_method: :
|
23
|
-
'ADMISSION_DATETIME'=>{title:'Admission Date/Time',coded_entry_method: :
|
24
|
-
'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'},
|
25
|
-
'DOSE'=>{title:'Dose',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'},
|
26
|
-
'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'},
|
27
|
-
'START_DATETIME'=>{title:'Start Date/Time',coded_entry_method: :
|
28
|
-
'FREQUENCY'=>{title:'Frequency',coded_entry_method: :frequency, code: '260864003', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.3.560.1.1006.1'},
|
29
|
-
'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'},
|
30
|
-
'STOP_DATETIME'=>{title:'Stop Date/Time',coded_entry_method: :
|
31
|
-
'INCISION_DATETIME'=>{title:'Incision Date/Time',coded_entry_method: :
|
32
|
-
'REMOVAL_DATETIME'=>{title:'Removal Date/Time',coded_entry_method: :
|
13
|
+
FIELDS = {'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.3.560.1.1021.2', field_type: :value},
|
14
|
+
'ORDINAL' => {title:'Ordinal', 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},
|
15
|
+
'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.3.560.1.1017.2', field_type: :value},
|
16
|
+
'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},
|
17
|
+
'CUMULATIVE_MEDICATION_DURATION' => {title:'Cumulative Medication Duration', coded_entry_method: :cumulative_medication_duration, code: '363819003', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.3.560.1.1001.3', field_type: :value},
|
18
|
+
'FACILITY_LOCATION' => {title:'Facility Location', coded_entry_method: :facility, code: 'SDLOC', field_type: :value},
|
19
|
+
'FACILITY_LOCATION_ARRIVAL_DATETIME' => {title:'Facility Location Arrival Date/Time', coded_entry_method: :facility_arrival, code: 'SDLOC_ARRIVAL', field_type: :nested_timestamp},
|
20
|
+
'FACILITY_LOCATION_DEPARTURE_DATETIME' => {title:'Facility Location Departure Date/Time', coded_entry_method: :facility_departure, code: 'SDLOC_DEPARTURE', field_type: :nested_timestamp},
|
21
|
+
'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},
|
22
|
+
'DISCHARGE_STATUS' => {title:'Discharge Status', 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},
|
23
|
+
'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},
|
24
|
+
'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},
|
25
|
+
'DOSE' => {title:'Dose', 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},
|
26
|
+
'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},
|
27
|
+
'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},
|
28
|
+
'FREQUENCY' => {title:'Frequency', coded_entry_method: :frequency, code: '260864003', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.3.560.1.1006.1', field_type: :value},
|
29
|
+
'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},
|
30
|
+
'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},
|
31
|
+
'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.3.560.1.1007.1', field_type: :timestamp},
|
32
|
+
'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},
|
33
|
+
'TRANSFER_TO' => {title:'Transfer To', coded_entry_method: :transfer_to, code: 'TRANSFER_TO', template_id: '2.16.840.1.113883.3.560.1.72', field_type: :value},
|
34
|
+
'TRANSFER_FROM' => {title:'Transfer From', coded_entry_method: :transfer_from, code: 'TRANSFER_FROM', template_id: '2.16.840.1.113883.3.560.1.71', field_type: :value}
|
33
35
|
}
|
34
36
|
|
35
37
|
VALUE_FIELDS = {'SEV' => 'SEVERITY',
|
@@ -176,7 +178,7 @@ module HQMF
|
|
176
178
|
json = build_hash(self, [:title,:display_name,:description,:standard_category,:qds_data_type,:code_list_id,:children_criteria, :derivation_operator, :property, :type, :definition, :status, :hard_status, :negation, :negation_code_list_id,:specific_occurrence,:specific_occurrence_const,:source_data_criteria])
|
177
179
|
json[:children_criteria] = @children_criteria unless @children_criteria.nil? || @children_criteria.empty?
|
178
180
|
json[:value] = ((@value.is_a? String) ? @value : @value.to_json) if @value
|
179
|
-
json[:field_values] = @field_values.inject({}) {|memo,(k,v)| memo[k] = v.to_json; memo} if @field_values
|
181
|
+
json[:field_values] = @field_values.inject({}) {|memo,(k,v)| memo[k] = (!v.nil? ? v.to_json : nil); memo} if @field_values
|
180
182
|
json[:effective_time] = @effective_time.to_json if @effective_time
|
181
183
|
json[:inline_code_list] = @inline_code_list if @inline_code_list
|
182
184
|
json[:temporal_references] = x if x = json_array(@temporal_references)
|
@@ -236,6 +238,26 @@ module HQMF
|
|
236
238
|
end
|
237
239
|
referenced
|
238
240
|
end
|
241
|
+
|
242
|
+
def all_code_set_oids
|
243
|
+
|
244
|
+
# root oid
|
245
|
+
referenced_oids = [code_list_id]
|
246
|
+
|
247
|
+
# value oid
|
248
|
+
referenced_oids << value.code_list_id if value != nil and value.type == 'CD'
|
249
|
+
|
250
|
+
# negation oid
|
251
|
+
referenced_oids << negation_code_list_id if negation_code_list_id != nil
|
252
|
+
|
253
|
+
# field oids
|
254
|
+
if field_values != nil
|
255
|
+
referenced_oids.concat (field_values.map {|key,field| field.code_list_id if field != nil and field.type == 'CD'})
|
256
|
+
end
|
257
|
+
|
258
|
+
referenced_oids
|
259
|
+
|
260
|
+
end
|
239
261
|
|
240
262
|
def self.get_settings_for_definition(definition, status)
|
241
263
|
settings_file = File.expand_path('../data_criteria.json', __FILE__)
|
@@ -299,9 +321,7 @@ module HQMF
|
|
299
321
|
end
|
300
322
|
|
301
323
|
def self.convert_value(json)
|
302
|
-
|
303
|
-
return nil if json.nil? || json == "null"
|
304
|
-
value = nil
|
324
|
+
return nil unless json.present?
|
305
325
|
type = json["type"]
|
306
326
|
case type
|
307
327
|
when 'TS', 'PQ'
|
@@ -313,7 +333,6 @@ module HQMF
|
|
313
333
|
when 'ANYNonNull'
|
314
334
|
value = HQMF::AnyValue.from_json(json)
|
315
335
|
else
|
316
|
-
|
317
336
|
raise "Unknown value type [#{type}]"
|
318
337
|
end
|
319
338
|
value
|
data/lib/hqmf-model/document.rb
CHANGED
@@ -127,6 +127,10 @@ module HQMF
|
|
127
127
|
@data_criteria
|
128
128
|
end
|
129
129
|
|
130
|
+
def all_code_set_oids
|
131
|
+
(@data_criteria.map {|d| d.all_code_set_oids }).flatten.compact.uniq
|
132
|
+
end
|
133
|
+
|
130
134
|
# Get the source data criteria that are specific occurrences
|
131
135
|
# @return [Array] an array of HQMF::DataCriteria describing the data elements used by the measure that are specific occurrences
|
132
136
|
def specific_occurrence_source_data_criteria
|
@@ -13,8 +13,9 @@ module HQMF
|
|
13
13
|
DENEXCEP = 'DENEXCEP'
|
14
14
|
DENEX = 'DENEX'
|
15
15
|
MSRPOPL = 'MSRPOPL'
|
16
|
+
OBSERV = 'OBSERV'
|
16
17
|
|
17
|
-
ALL_POPULATION_CODES = [IPP, DENOM, NUMER, DENEXCEP, DENEX, MSRPOPL]
|
18
|
+
ALL_POPULATION_CODES = [IPP, DENOM, NUMER, DENEXCEP, DENEX, MSRPOPL, OBSERV]
|
18
19
|
|
19
20
|
# Create a new population criteria
|
20
21
|
# @param [String] id
|
@@ -63,7 +64,7 @@ module HQMF
|
|
63
64
|
def conjunction_code
|
64
65
|
|
65
66
|
case @type
|
66
|
-
when IPP, DENOM, NUMER
|
67
|
+
when IPP, DENOM, NUMER, MSRPOPL
|
67
68
|
HQMF::Precondition::ALL_TRUE
|
68
69
|
when DENEXCEP, DENEX
|
69
70
|
HQMF::Precondition::AT_LEAST_ONE_TRUE
|
@@ -14,6 +14,12 @@ module HQMF
|
|
14
14
|
ALL_TRUE => AT_LEAST_ONE_FALSE,
|
15
15
|
AT_LEAST_ONE_FALSE => ALL_TRUE
|
16
16
|
}
|
17
|
+
INVERSIONS = {
|
18
|
+
AT_LEAST_ONE_TRUE => ALL_TRUE,
|
19
|
+
ALL_FALSE => AT_LEAST_ONE_FALSE,
|
20
|
+
ALL_TRUE => AT_LEAST_ONE_TRUE,
|
21
|
+
AT_LEAST_ONE_FALSE => ALL_FALSE
|
22
|
+
}
|
17
23
|
|
18
24
|
attr_reader :id, :preconditions, :reference, :conjunction_code
|
19
25
|
attr_accessor :negation
|
data/lib/hqmf-model/types.rb
CHANGED
@@ -257,7 +257,7 @@ module HQMF
|
|
257
257
|
class SubsetOperator
|
258
258
|
include HQMF::Conversion::Utilities
|
259
259
|
|
260
|
-
TYPES = ['COUNT', 'FIRST', 'SECOND', 'THIRD', 'FOURTH', 'FIFTH', 'RECENT', 'LAST', 'MIN', 'MAX', 'DATEDIFF']
|
260
|
+
TYPES = ['COUNT', 'FIRST', 'SECOND', 'THIRD', 'FOURTH', 'FIFTH', 'RECENT', 'LAST', 'MIN', 'MAX', 'DATEDIFF', 'TIMEDIFF', 'MEDIAN', 'MEAN']
|
261
261
|
|
262
262
|
attr_accessor :type, :value
|
263
263
|
# @param [String] type
|
data/lib/hqmf-parser.rb
CHANGED
@@ -6,6 +6,7 @@ require 'ostruct'
|
|
6
6
|
require 'health-data-standards'
|
7
7
|
|
8
8
|
# require_relative
|
9
|
+
require_relative 'util/counter.rb'
|
9
10
|
require_relative 'hqmf-model/utilities.rb'
|
10
11
|
|
11
12
|
require_relative 'hqmf-parser/1.0/utilities'
|
@@ -14,6 +15,7 @@ require_relative 'hqmf-parser/1.0/document'
|
|
14
15
|
require_relative 'hqmf-parser/1.0/data_criteria'
|
15
16
|
require_relative 'hqmf-parser/1.0/attribute'
|
16
17
|
require_relative 'hqmf-parser/1.0/population_criteria'
|
18
|
+
require_relative 'hqmf-parser/1.0/observation'
|
17
19
|
require_relative 'hqmf-parser/1.0/precondition'
|
18
20
|
require_relative 'hqmf-parser/1.0/restriction'
|
19
21
|
require_relative 'hqmf-parser/1.0/comparison'
|
@@ -4,11 +4,11 @@ module HQMF1
|
|
4
4
|
|
5
5
|
include HQMF1::Utilities
|
6
6
|
|
7
|
-
attr_accessor :code_list_id, :derived_from, :definition, :status, :negation, :specific_occurrence
|
7
|
+
attr_accessor :code_list_id, :derived_from, :definition, :status, :negation, :specific_occurrence
|
8
8
|
|
9
9
|
# Create a new instance based on the supplied HQMF entry
|
10
10
|
# @param [Nokogiri::XML::Element] entry the parsed HQMF entry
|
11
|
-
def initialize(entry)
|
11
|
+
def initialize(entry, occurrence_counters)
|
12
12
|
@entry = entry
|
13
13
|
|
14
14
|
template_map = HQMF::DataCriteria.get_template_id_map()
|
@@ -21,8 +21,8 @@ module HQMF1
|
|
21
21
|
if derived_entry
|
22
22
|
derived = derived_entry.at_xpath('cda:act/cda:id/@root') || derived_entry.at_xpath('cda:observation/cda:id/@root')
|
23
23
|
@derived_from = derived.value
|
24
|
-
|
25
|
-
@occurrence_key =
|
24
|
+
occurrence_counters[@derived_from] ||= HQMF::InstanceCounter.new
|
25
|
+
@occurrence_key = occurrence_counters[@derived_from].next-1
|
26
26
|
@specific_occurrence = "#{('A'..'ZZ').to_a[@occurrence_key]}"
|
27
27
|
end
|
28
28
|
|
@@ -74,31 +74,18 @@ module HQMF1
|
|
74
74
|
def const_name
|
75
75
|
components = title.gsub(/\W/,' ').split.collect {|word| word.strip.upcase }
|
76
76
|
if @derived_from
|
77
|
-
components <<
|
78
|
-
@specific_occurrence_const = (description.gsub(/\W/,' ').split.collect {|word| word.strip.upcase }).join '_'
|
77
|
+
components << HQMF::Counter.instance.next
|
79
78
|
end
|
80
79
|
components.join '_'
|
81
80
|
end
|
82
81
|
|
83
82
|
def to_json
|
84
|
-
json = build_hash(self, [:id,:title,:code_list_id,:derived_from,:description, :definition, :status, :negation, :specific_occurrence
|
83
|
+
json = build_hash(self, [:id,:title,:code_list_id,:derived_from,:description, :definition, :status, :negation, :specific_occurrence])
|
85
84
|
{
|
86
85
|
self.const_name => json
|
87
86
|
}
|
88
87
|
end
|
89
88
|
|
90
|
-
# Simple class to issue monotonically increasing integer identifiers
|
91
|
-
class Counter
|
92
|
-
def initialize
|
93
|
-
@count = -1
|
94
|
-
end
|
95
|
-
|
96
|
-
def next
|
97
|
-
@count+=1
|
98
|
-
end
|
99
|
-
end
|
100
|
-
@@id = Counter.new
|
101
|
-
@@occurrences = {}
|
102
89
|
|
103
90
|
end
|
104
91
|
|
@@ -9,9 +9,11 @@ module HQMF1
|
|
9
9
|
# Create a new HQMF1::Document instance by parsing the supplied contents
|
10
10
|
# @param [String] hqmf_contents the contents of an HQMF v1.0 document
|
11
11
|
def initialize(hqmf_contents)
|
12
|
+
|
12
13
|
@doc = Document.parse(hqmf_contents)
|
14
|
+
occurrence_counters = {}
|
13
15
|
@data_criteria = @doc.xpath('//cda:section[cda:code/@code="57025-9"]/cda:entry').collect do |entry|
|
14
|
-
DataCriteria.new(entry)
|
16
|
+
DataCriteria.new(entry, occurrence_counters)
|
15
17
|
end
|
16
18
|
|
17
19
|
backfill_derived_code_lists
|
@@ -22,6 +24,10 @@ module HQMF1
|
|
22
24
|
@population_criteria = @doc.xpath('//cda:section[cda:code/@code="57026-7"]/cda:entry').collect do |attr|
|
23
25
|
PopulationCriteria.new(attr, self)
|
24
26
|
end
|
27
|
+
observations = @doc.xpath('//cda:section[cda:code/@code="57027-5"]/cda:entry').collect do |attr|
|
28
|
+
Observation.new(attr, self)
|
29
|
+
end
|
30
|
+
@population_criteria.concat(observations)
|
25
31
|
|
26
32
|
@stratification = @doc.xpath('//cda:section[cda:code/@code="69669-0"]/cda:entry').collect do |attr|
|
27
33
|
PopulationCriteria.new(attr, self)
|
@@ -153,7 +159,7 @@ module HQMF1
|
|
153
159
|
criteria_json = criteria.to_json
|
154
160
|
# check if the key already exists... if it does redefine the key
|
155
161
|
if (json[:data_criteria][criteria_json.keys.first])
|
156
|
-
criteria_json = {"#{criteria_json.keys.first}_#{
|
162
|
+
criteria_json = {"#{criteria_json.keys.first}_#{HQMF::Counter.instance.next}" => criteria_json.values.first}
|
157
163
|
end
|
158
164
|
json[:data_criteria].merge! criteria_json
|
159
165
|
end
|
@@ -193,17 +199,5 @@ module HQMF1
|
|
193
199
|
collection.find {|e| e.send(attribute)==value}
|
194
200
|
end
|
195
201
|
|
196
|
-
# Simple class to issue monotonically increasing integer identifiers
|
197
|
-
class Counter
|
198
|
-
def initialize
|
199
|
-
@count = 0
|
200
|
-
end
|
201
|
-
|
202
|
-
def next
|
203
|
-
@count+=1
|
204
|
-
end
|
205
|
-
end
|
206
|
-
@@ids = Counter.new
|
207
|
-
|
208
202
|
end
|
209
203
|
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module HQMF1
|
2
|
+
# Represents an HQMF population criteria
|
3
|
+
class Observation
|
4
|
+
|
5
|
+
include HQMF1::Utilities
|
6
|
+
|
7
|
+
attr_reader :preconditions, :entry, :doc
|
8
|
+
attr_accessor :id, :hqmf_id, :stratification_id
|
9
|
+
|
10
|
+
# Create a new population criteria from the supplied HQMF entry
|
11
|
+
# @param [Nokogiri::XML::Element] the HQMF entry
|
12
|
+
def initialize(entry, doc)
|
13
|
+
@doc = doc
|
14
|
+
@entry = entry
|
15
|
+
@id = attr_val('cda:observation/cda:id/@root').upcase
|
16
|
+
@preconditions = [Precondition.new(@entry, nil, @doc)]
|
17
|
+
end
|
18
|
+
|
19
|
+
# Get the code for the population criteria
|
20
|
+
# @return [String] the code (e.g. IPP, DEMON, NUMER, DENEX, DENEXCEP)
|
21
|
+
def code
|
22
|
+
HQMF::PopulationCriteria::OBSERV
|
23
|
+
end
|
24
|
+
|
25
|
+
# Get the id for the population criteria, used elsewhere in the HQMF document to
|
26
|
+
# refer to this criteria
|
27
|
+
# @return [String] the id
|
28
|
+
def id
|
29
|
+
@id
|
30
|
+
end
|
31
|
+
|
32
|
+
def title
|
33
|
+
"Measure Observation"
|
34
|
+
end
|
35
|
+
|
36
|
+
def reference
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_json
|
41
|
+
|
42
|
+
json = {}
|
43
|
+
self.preconditions.compact.each do |precondition|
|
44
|
+
json[:preconditions] ||= []
|
45
|
+
json[:preconditions] << precondition.to_json
|
46
|
+
end
|
47
|
+
json[:preconditions].each {|p| p[:conjunction] ||= "AND"}
|
48
|
+
|
49
|
+
json[:id] = id
|
50
|
+
json[:title] = title
|
51
|
+
json[:code] = code
|
52
|
+
json[:hqmf_id] = hqmf_id if hqmf_id
|
53
|
+
json[:stratification_id] = stratification_id if stratification_id
|
54
|
+
json[:reference] = reference
|
55
|
+
|
56
|
+
{self.code => json}
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
@@ -4,7 +4,7 @@ module HQMF1
|
|
4
4
|
|
5
5
|
include HQMF1::Utilities
|
6
6
|
|
7
|
-
attr_reader :range, :comparison, :restrictions, :subset, :preconditions
|
7
|
+
attr_reader :range, :comparison, :restrictions, :subset, :preconditions, :expression
|
8
8
|
attr_accessor :from_parent
|
9
9
|
|
10
10
|
def initialize(entry, parent, doc)
|
@@ -28,7 +28,9 @@ module HQMF1
|
|
28
28
|
@subset = local_subset
|
29
29
|
end
|
30
30
|
|
31
|
-
|
31
|
+
if @entry.at_xpath('./*/cda:derivationExpr')
|
32
|
+
@expression = Expression.new(@entry)
|
33
|
+
end
|
32
34
|
|
33
35
|
comparison_def = @entry.at_xpath('./*/cda:sourceOf[@typeCode="COMP"]')
|
34
36
|
if comparison_def
|
@@ -150,6 +152,7 @@ module HQMF1
|
|
150
152
|
json[:comparison] = comparison.to_json if comparison
|
151
153
|
json[:restrictions] = json_array(self.restrictions)
|
152
154
|
json[:preconditions] = json_array(self.preconditions)
|
155
|
+
json[:expression] = self.expression.to_json if self.expression
|
153
156
|
json
|
154
157
|
end
|
155
158
|
|
@@ -53,7 +53,7 @@ module HQMF
|
|
53
53
|
# make sure nobody else is going to delete the criteria we've grouped
|
54
54
|
criteria_ids.each {|target| validate_not_deleted(target)}
|
55
55
|
|
56
|
-
id = "#{parent_id}_#{section}_#{
|
56
|
+
id = "#{parent_id}_#{section}_#{HQMF::Counter.instance.next}"
|
57
57
|
title = "#{id}"
|
58
58
|
description = ""
|
59
59
|
definition = 'derived'
|
@@ -170,12 +170,13 @@ module HQMF
|
|
170
170
|
negation = criteria[:negation]
|
171
171
|
negation_code_list_id = criteria[:negation_code_list_id]
|
172
172
|
specific_occurrence = criteria[:specific_occurrence]
|
173
|
-
specific_occurrence_const =
|
173
|
+
specific_occurrence_const = nil
|
174
174
|
|
175
175
|
# specific occurrences do not properly set the description, so we want to add the definition and status
|
176
176
|
if (specific_occurrence)
|
177
|
-
|
178
|
-
description = "#{definition.titleize}#{
|
177
|
+
statusText = ", #{status.titleize}" if status
|
178
|
+
description = "#{definition.titleize}#{statusText}: #{description}"
|
179
|
+
specific_occurrence_const = (description.gsub(/\W/,' ').split.collect {|word| word.strip.upcase }).join '_'
|
179
180
|
end
|
180
181
|
|
181
182
|
value = nil # value is filled out by backfill_patient_characteristics for things like gender and by REFR restrictions
|
@@ -189,6 +190,14 @@ module HQMF
|
|
189
190
|
inline_code_list = nil # inline code list is only used in HQMF V2, so we can just pass in nil
|
190
191
|
display_name=nil
|
191
192
|
|
193
|
+
# transfers should be modeled as a field. The code_list_id of the transfer data criteria is cleared and the oid is added to a transfer field
|
194
|
+
# The definition of the data criteria is still transfer, but it is marked as an encounter using the patient api funciton.
|
195
|
+
if ['transfer_to', 'transfer_from'].include? definition
|
196
|
+
field_values ||= {}
|
197
|
+
field_values[definition.upcase] = HQMF::Coded.for_code_list(code_list_id, title)
|
198
|
+
code_list_id = nil
|
199
|
+
end
|
200
|
+
|
192
201
|
HQMF::DataCriteria.new(id, title, display_name, description, code_list_id, children_criteria, derivation_operator, definition, status,
|
193
202
|
value, field_values, effective_time, inline_code_list, negation, negation_code_list_id, temporal_references, subset_operators,specific_occurrence,specific_occurrence_const)
|
194
203
|
|
@@ -239,18 +248,5 @@ module HQMF
|
|
239
248
|
key.to_s.downcase.gsub('_', ' ').split(' ').map {|w| w.capitalize }.join('')
|
240
249
|
end
|
241
250
|
|
242
|
-
# Simple class to issue monotonically increasing integer identifiers
|
243
|
-
class Counter
|
244
|
-
def initialize
|
245
|
-
@count = 0
|
246
|
-
end
|
247
|
-
|
248
|
-
def next
|
249
|
-
@count+=1
|
250
|
-
end
|
251
|
-
end
|
252
|
-
@@ids = Counter.new
|
253
|
-
|
254
|
-
|
255
251
|
end
|
256
252
|
end
|
@@ -98,8 +98,8 @@ module HQMF
|
|
98
98
|
data_criteria.definition = 'patient_characteristic_birthdate'
|
99
99
|
end
|
100
100
|
# this is looking for a gender characteristic that is set as a generic characteristic
|
101
|
-
gender_key = (value_set.keys.select {|set| set
|
102
|
-
if (gender_key and ['M','F'].include? value_set[gender_key].first)
|
101
|
+
gender_key = (value_set.keys.select {|set| set == 'Administrative Sex' || set == 'AdministrativeSex'}).first
|
102
|
+
if (gender_key and ['M','F'].include? value_set[gender_key].first)
|
103
103
|
data_criteria.definition = 'patient_characteristic_gender'
|
104
104
|
data_criteria.value = HQMF::Coded.new('CD','Gender',value_set[gender_key].first)
|
105
105
|
end
|
@@ -26,18 +26,22 @@ module HQMF
|
|
26
26
|
denoms = @population_criteria_by_id.select {|key, value| value.type == HQMF::PopulationCriteria::DENOM}
|
27
27
|
nums = @population_criteria_by_id.select {|key, value| value.type == HQMF::PopulationCriteria::NUMER}
|
28
28
|
msrpopls = @population_criteria_by_id.select {|key, value| value.type == HQMF::PopulationCriteria::MSRPOPL}
|
29
|
+
observs = @population_criteria_by_id.select {|key, value| value.type == HQMF::PopulationCriteria::OBSERV}
|
29
30
|
excls = @population_criteria_by_id.select {|key, value| value.type == HQMF::PopulationCriteria::DENEX}
|
30
31
|
denexcs = @population_criteria_by_id.select {|key, value| value.type == HQMF::PopulationCriteria::DENEXCEP}
|
31
32
|
|
32
|
-
if (ipps.size<=1 and denoms.size<=1 and nums.size<=1 and excls.size<=1 and denexcs.size<=1 )
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
33
|
+
if (ipps.size<=1 and denoms.size<=1 and nums.size<=1 and excls.size<=1 and denexcs.size<=1 and msrpopls.size<=1 and observs.size<=1 )
|
34
|
+
sub_measure = {}
|
35
|
+
|
36
|
+
sub_measure[HQMF::PopulationCriteria::IPP] = HQMF::PopulationCriteria::IPP if ipps.size > 0
|
37
|
+
sub_measure[HQMF::PopulationCriteria::DENOM] = HQMF::PopulationCriteria::DENOM if denoms.size > 0
|
38
|
+
sub_measure[HQMF::PopulationCriteria::NUMER] = HQMF::PopulationCriteria::NUMER if nums.size > 0
|
39
|
+
sub_measure[HQMF::PopulationCriteria::DENEXCEP] = HQMF::PopulationCriteria::DENEXCEP if denexcs.size > 0
|
40
|
+
sub_measure[HQMF::PopulationCriteria::DENEX] = HQMF::PopulationCriteria::DENEX if excls.size > 0
|
41
|
+
sub_measure[HQMF::PopulationCriteria::MSRPOPL] = HQMF::PopulationCriteria::MSRPOPL if msrpopls.size > 0
|
42
|
+
sub_measure[HQMF::PopulationCriteria::OBSERV] = HQMF::PopulationCriteria::OBSERV if observs.size > 0
|
43
|
+
|
44
|
+
@sub_measures << sub_measure
|
41
45
|
else
|
42
46
|
|
43
47
|
nums.each do |num_id, num|
|
@@ -51,6 +55,8 @@ module HQMF
|
|
51
55
|
|
52
56
|
apply_to_submeasures(@sub_measures, HQMF::PopulationCriteria::DENEX, excls.values, HQMF::PopulationCriteria::IPP, get_unmatched_population_keys(ipps, excls))
|
53
57
|
apply_to_submeasures(@sub_measures, HQMF::PopulationCriteria::DENEXCEP, denexcs.values, HQMF::PopulationCriteria::DENOM, get_unmatched_population_keys(denoms, denexcs))
|
58
|
+
|
59
|
+
apply_to_submeasures(@sub_measures, HQMF::PopulationCriteria::OBSERV, observs.values)
|
54
60
|
|
55
61
|
keep = []
|
56
62
|
|
@@ -134,13 +134,22 @@ module HQMF
|
|
134
134
|
end
|
135
135
|
|
136
136
|
if (!sub_conditions.empty?)
|
137
|
-
# if we have negated conditions, add them to a new precondition
|
137
|
+
# if we have negated conditions, add them to a new precondition with an inverted conjunction on a negated precondition
|
138
|
+
# the reason we invert the conjunction is because we are turning
|
139
|
+
# AND: NOT X
|
140
|
+
# AND: NOT Y
|
141
|
+
# into
|
142
|
+
# NOT: X OR Y
|
143
|
+
# (i.e, demorgan's law)
|
138
144
|
if (!negated_conditions.empty?)
|
139
|
-
|
145
|
+
inverted_conjunction_code = HQMF::Precondition::INVERSIONS[conjunction_code]
|
146
|
+
sub_conditions << HQMF::Converter::SimplePrecondition.new(nil,negated_conditions,nil,inverted_conjunction_code, true)
|
140
147
|
end
|
141
148
|
joined << HQMF::Converter::SimplePrecondition.new(nil,sub_conditions,nil,conjunction_code, false)
|
142
149
|
elsif (!negated_conditions.empty?)
|
143
|
-
|
150
|
+
# invert conjunction based on demorgan's law
|
151
|
+
inverted_conjunction_code = HQMF::Precondition::INVERSIONS[conjunction_code]
|
152
|
+
joined << HQMF::Converter::SimplePrecondition.new(nil,negated_conditions,nil,inverted_conjunction_code, true)
|
144
153
|
end
|
145
154
|
|
146
155
|
end
|
@@ -28,8 +28,12 @@ module HQMF
|
|
28
28
|
# restriction.comparison
|
29
29
|
# restriction.restriction
|
30
30
|
def self.extract_preconditions_from_restriction(restriction,data_criteria_converter)
|
31
|
-
|
32
|
-
target_id
|
31
|
+
target_id=nil
|
32
|
+
if restriction[:target_id] and data_criteria_converter.v1_data_criteria_by_id[restriction[:target_id]]
|
33
|
+
target_id = data_criteria_converter.v1_data_criteria_by_id[restriction[:target_id]].id
|
34
|
+
elsif restriction[:target_id]
|
35
|
+
puts "\tPrecondition Data Criteria MISSING: #{restriction[:target_id]}"
|
36
|
+
end
|
33
37
|
type = restriction[:type]
|
34
38
|
if (restriction[:negation])
|
35
39
|
inverted = HQMF::TemporalReference::INVERSION[type]
|
@@ -79,11 +83,37 @@ module HQMF
|
|
79
83
|
|
80
84
|
# check restrictions
|
81
85
|
restrictions = extract_preconditions_from_restrictions(restriction[:restrictions], data_criteria_converter) if restriction[:restrictions]
|
82
|
-
|
86
|
+
if (children)
|
87
|
+
HQMF::PreconditionConverter.apply_restrictions_to_comparisons(children, restrictions) unless restrictions.nil? or restrictions.empty?
|
88
|
+
end
|
89
|
+
|
83
90
|
|
84
91
|
container = nil
|
92
|
+
# check if there is an expression on the restriction
|
93
|
+
if (restriction[:expression])
|
94
|
+
# this is for things like TIMEDIFF
|
95
|
+
type = restriction[:expression][:type]
|
96
|
+
exp_operator = HQMF::Converter::SimpleOperator.new(HQMF::Converter::SimpleOperator.find_category(type), type, HQMF::Converter::SimpleOperator.parse_value(restriction[:expression][:value]))
|
97
|
+
preconditions = []
|
98
|
+
|
99
|
+
driv_preconditions = []
|
100
|
+
restrictions.each {|element| driv_preconditions << element if element.is_a? HQMF::Converter::SimpleRestriction and element.operator.type == 'DRIV'}
|
101
|
+
|
102
|
+
if driv_preconditions and !driv_preconditions.empty?
|
103
|
+
preconditions = driv_preconditions.map(&:preconditions).flatten
|
104
|
+
end
|
105
|
+
|
106
|
+
reference = nil
|
107
|
+
conjunction_code = nil
|
108
|
+
|
109
|
+
comparison_precondition = HQMF::Converter::SimplePrecondition.new(nil,[HQMF::Converter::SimpleRestriction.new(exp_operator, nil, preconditions)],reference,conjunction_code, false)
|
110
|
+
comparison_precondition.klass = HQMF::Converter::SimplePrecondition::COMPARISON
|
111
|
+
|
112
|
+
comparison_precondition.subset_comparison = true
|
113
|
+
container = HQMF::Converter::SimpleRestriction.new(operator, nil, [comparison_precondition])
|
114
|
+
|
85
115
|
# check if there is a subset on the restriction
|
86
|
-
|
116
|
+
elsif restriction[:subset]
|
87
117
|
# if we have a subset, we want to create a Comparison Precondition for the subset and have it be the child of the operator on the restriction.
|
88
118
|
# the reason for this is that we want the order of operations to be SBS the FIRST of a data criteria, rather than FIRST of SBS of a data criteria
|
89
119
|
|
@@ -91,7 +121,6 @@ module HQMF
|
|
91
121
|
subset_operator = HQMF::Converter::SimpleOperator.new(HQMF::Converter::SimpleOperator.find_category(subset_type), subset_type, nil)
|
92
122
|
|
93
123
|
reference = nil
|
94
|
-
# conjunction_code = "operator"
|
95
124
|
conjunction_code = nil
|
96
125
|
|
97
126
|
restriction = HQMF::Converter::SimpleRestriction.new(subset_operator, target_id)
|
@@ -11,7 +11,7 @@ module HQMF
|
|
11
11
|
|
12
12
|
def initialize(id, preconditions,reference,conjunction_code,negation)
|
13
13
|
super(id, preconditions,reference,conjunction_code,negation)
|
14
|
-
@id =
|
14
|
+
@id = HQMF::Counter.instance.next if (@id.nil?)
|
15
15
|
@klass = PRECONDITION
|
16
16
|
end
|
17
17
|
|
@@ -44,18 +44,6 @@ module HQMF
|
|
44
44
|
preconditions.delete_if {|precondition| precondition.restriction? and precondition.converted}
|
45
45
|
end
|
46
46
|
|
47
|
-
# Simple class to issue monotonically increasing integer identifiers
|
48
|
-
class Counter
|
49
|
-
def initialize
|
50
|
-
@count = 0
|
51
|
-
end
|
52
|
-
|
53
|
-
def next
|
54
|
-
@count+=1
|
55
|
-
end
|
56
|
-
end
|
57
|
-
@@ids = Counter.new
|
58
|
-
|
59
47
|
end
|
60
48
|
end
|
61
49
|
|
@@ -37,7 +37,7 @@ module HQMF
|
|
37
37
|
parent_id = "GROUP"
|
38
38
|
if restriction.generated_data_criteria.nil?
|
39
39
|
# we pass in restriction.preconditions here rather than children_criteria because we need to be able to create grouping data criteria for and and or preconditions in a tree
|
40
|
-
group_criteria = data_criteria_converter.create_group_data_criteria(restriction.preconditions, "#{type}_CHILDREN", value, parent_id,
|
40
|
+
group_criteria = data_criteria_converter.create_group_data_criteria(restriction.preconditions, "#{type}_CHILDREN", value, parent_id, HQMF::Counter.instance.next, "grouping", "temporal")
|
41
41
|
# save the generated grouping criteria so that we can reference it from other locations
|
42
42
|
restriction.generated_data_criteria = group_criteria
|
43
43
|
else
|
@@ -67,69 +67,15 @@ module HQMF
|
|
67
67
|
data_criteria = nil
|
68
68
|
if (children_criteria.length == 1)
|
69
69
|
data_criteria = children_criteria[0]
|
70
|
-
# this block used to set the order of operations of values after subset operators.
|
71
|
-
# if data_criteria and !data_criteria.value.nil?
|
72
|
-
# subset_operator.value ||= data_criteria.value
|
73
|
-
# data_criteria.value = nil
|
74
|
-
# end
|
75
70
|
data_criteria.subset_operators ||= []
|
76
71
|
# add subset operator to data criteria
|
77
72
|
data_criteria.subset_operators << subset_operator unless data_criteria.has_subset(subset_operator)
|
78
73
|
else
|
79
74
|
parent_id = "GROUP"
|
80
75
|
|
81
|
-
# this block of code used to be used to pull the value off of a data criteria and shove it onto a subset operator to evaluate MIN: lab result(result > 10) as MIN > 10: lab result
|
82
|
-
# the decision was made by NQF to not support this order of operations based on previous guidance to the measure developers
|
83
|
-
#
|
84
|
-
# unless subset_operator.value
|
85
|
-
# # scalar comparisons are used for MIN>90 etc. The value is on a REFR restriction. We need to store it on the data criteria since the value is processed before the operator is created.
|
86
|
-
# scalar_comparison = nil
|
87
|
-
#
|
88
|
-
# # check to see if we have different values referenced accross the children
|
89
|
-
# # this happens on things like most recent blood pressure reading
|
90
|
-
# multiple_differing_values = false
|
91
|
-
# children_criteria.each do |criteria|
|
92
|
-
# if scalar_comparison.nil?
|
93
|
-
# scalar_comparison = criteria.value
|
94
|
-
# else
|
95
|
-
# if scalar_comparison != criteria.value
|
96
|
-
# multiple_differing_values = true
|
97
|
-
# end
|
98
|
-
# end
|
99
|
-
# end
|
100
|
-
#
|
101
|
-
# # if we have multiple differing values, we want to keep the preconditions from the restriction, and attach them to the precondition
|
102
|
-
# # we can then apply the subset operator to all of the children individually
|
103
|
-
# if (multiple_differing_values)
|
104
|
-
# precondition.preconditions.delete(restriction)
|
105
|
-
# precondition.preconditions.concat(restriction.preconditions)
|
106
|
-
# children_criteria.each do |criteria|
|
107
|
-
# subset_operator = HQMF::SubsetOperator.new(type, value)
|
108
|
-
# subset_operator.value = criteria.value
|
109
|
-
# criteria.value = nil
|
110
|
-
# criteria.subset_operators ||= []
|
111
|
-
# criteria.subset_operators << subset_operator unless criteria.has_subset(subset_operator)
|
112
|
-
# end
|
113
|
-
# restriction.converted=true
|
114
|
-
# # we want to return since we have applied the subset to the children. We no longer want to create a grouping data critiera
|
115
|
-
# return;
|
116
|
-
# end
|
117
|
-
#
|
118
|
-
# # all the children have the same value, apply the value to the subset operator
|
119
|
-
# children_criteria.each do |criteria|
|
120
|
-
# if scalar_comparison.nil?
|
121
|
-
# scalar_comparison = criteria.value
|
122
|
-
# else
|
123
|
-
# raise "multiple different scalar comparisons for a grouping data criteria" if scalar_comparison != criteria.value
|
124
|
-
# end
|
125
|
-
# criteria.value = nil
|
126
|
-
# end
|
127
|
-
# subset_operator.value ||= scalar_comparison
|
128
|
-
# end
|
129
|
-
|
130
76
|
if restriction.generated_data_criteria.nil?
|
131
77
|
# we pass in restriction.preconditions here rather than children_criteria because we need to be able to create grouping data criteria for and and or preconditions in a tree
|
132
|
-
data_criteria = data_criteria_converter.create_group_data_criteria(restriction.preconditions, type, value, parent_id,
|
78
|
+
data_criteria = data_criteria_converter.create_group_data_criteria(restriction.preconditions, type, value, parent_id, HQMF::Counter.instance.next, "grouping", "summary")
|
133
79
|
# save the generated grouping criteria so that we can reference it from other locations
|
134
80
|
restriction.generated_data_criteria = data_criteria
|
135
81
|
else
|
@@ -152,18 +98,5 @@ module HQMF
|
|
152
98
|
restriction.converted=true
|
153
99
|
end
|
154
100
|
|
155
|
-
# Simple class to issue monotonically increasing integer identifiers
|
156
|
-
class Counter
|
157
|
-
def initialize
|
158
|
-
@count = 0
|
159
|
-
end
|
160
|
-
|
161
|
-
def next
|
162
|
-
@count+=1
|
163
|
-
end
|
164
|
-
end
|
165
|
-
@@ids = Counter.new
|
166
|
-
|
167
|
-
|
168
101
|
end
|
169
102
|
end
|
data/lib/hqmf-parser/parser.rb
CHANGED
@@ -6,7 +6,7 @@ module HQMF
|
|
6
6
|
|
7
7
|
def self.parse(hqmf_contents, version, codes = nil)
|
8
8
|
|
9
|
-
|
9
|
+
HQMF::Counter.instance.reset()
|
10
10
|
case version
|
11
11
|
when HQMF_VERSION_1
|
12
12
|
puts("\tCodes not passed in, cannot backfill properties like gender") unless codes
|
@@ -15,8 +15,21 @@ module HQMF
|
|
15
15
|
HQMF2::Document.new(hqmf_contents).to_model
|
16
16
|
else
|
17
17
|
raise "Unsupported HQMF version specified: #{version}"
|
18
|
-
end
|
19
18
|
end
|
19
|
+
end
|
20
20
|
|
21
|
+
def self.parse_id(hqmf_contents, version)
|
22
|
+
case version
|
23
|
+
when HQMF_VERSION_1
|
24
|
+
doc = HQMF1::Document.parse(hqmf_contents)
|
25
|
+
doc.at_xpath('//cda:id/@root').value.upcase
|
26
|
+
when HQMF_VERSION_2
|
27
|
+
doc = HQMF2::Document.parse(hqmf_contents)
|
28
|
+
doc.at_xpath('cda:QualityMeasureDocument/cda:id/@extension', HQMF2::Document::NAMESPACES).value.upcase
|
29
|
+
else
|
30
|
+
raise "Unsupported HQMF version specified: #{version}"
|
31
|
+
end
|
32
|
+
end
|
21
33
|
end
|
34
|
+
|
22
35
|
end
|
data/lib/util/counter.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
module HQMF
|
2
|
+
# Simple class to issue monotonically increasing integer identifiers
|
3
|
+
class InstanceCounter
|
4
|
+
def initialize
|
5
|
+
@count=0
|
6
|
+
end
|
7
|
+
|
8
|
+
def reset
|
9
|
+
@count=0
|
10
|
+
end
|
11
|
+
|
12
|
+
def next
|
13
|
+
@count+=1
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class Counter < InstanceCounter
|
18
|
+
include Singleton
|
19
|
+
end
|
20
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hqmf-parser
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2012-
|
14
|
+
date: 2012-12-19 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: rubyzip
|
@@ -96,6 +96,7 @@ files:
|
|
96
96
|
- lib/hqmf-parser/1.0/data_criteria.rb
|
97
97
|
- lib/hqmf-parser/1.0/document.rb
|
98
98
|
- lib/hqmf-parser/1.0/expression.rb
|
99
|
+
- lib/hqmf-parser/1.0/observation.rb
|
99
100
|
- lib/hqmf-parser/1.0/population_criteria.rb
|
100
101
|
- lib/hqmf-parser/1.0/precondition.rb
|
101
102
|
- lib/hqmf-parser/1.0/range.rb
|
@@ -122,6 +123,7 @@ files:
|
|
122
123
|
- lib/hqmf-parser/parser.rb
|
123
124
|
- lib/hqmf-parser/value_sets/value_set_parser.rb
|
124
125
|
- lib/hqmf-parser.rb
|
126
|
+
- lib/util/counter.rb
|
125
127
|
- lib/tasks/coverme.rake
|
126
128
|
- lib/tasks/hqmf.rake
|
127
129
|
- lib/tasks/value_sets.rake
|