cqm-parsers 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +29 -0
  3. data/README.md +21 -0
  4. data/Rakefile +19 -0
  5. data/lib/ext/code.rb +10 -0
  6. data/lib/ext/data_element.rb +24 -0
  7. data/lib/hqmf-model/attribute.rb +63 -0
  8. data/lib/hqmf-model/data_criteria.rb +467 -0
  9. data/lib/hqmf-model/document.rb +253 -0
  10. data/lib/hqmf-model/population_criteria.rb +102 -0
  11. data/lib/hqmf-model/precondition.rb +94 -0
  12. data/lib/hqmf-model/types.rb +457 -0
  13. data/lib/hqmf-model/utilities.rb +52 -0
  14. data/lib/hqmf-parser.rb +116 -0
  15. data/lib/hqmf-parser/1.0/attribute.rb +121 -0
  16. data/lib/hqmf-parser/1.0/comparison.rb +34 -0
  17. data/lib/hqmf-parser/1.0/data_criteria.rb +92 -0
  18. data/lib/hqmf-parser/1.0/document.rb +195 -0
  19. data/lib/hqmf-parser/1.0/expression.rb +60 -0
  20. data/lib/hqmf-parser/1.0/observation.rb +61 -0
  21. data/lib/hqmf-parser/1.0/population_criteria.rb +75 -0
  22. data/lib/hqmf-parser/1.0/precondition.rb +90 -0
  23. data/lib/hqmf-parser/1.0/range.rb +76 -0
  24. data/lib/hqmf-parser/1.0/restriction.rb +162 -0
  25. data/lib/hqmf-parser/1.0/utilities.rb +55 -0
  26. data/lib/hqmf-parser/2.0/data_criteria.rb +372 -0
  27. data/lib/hqmf-parser/2.0/data_criteria_helpers/dc_base_extract.rb +80 -0
  28. data/lib/hqmf-parser/2.0/data_criteria_helpers/dc_definition_from_template_or_type_extract.rb +201 -0
  29. data/lib/hqmf-parser/2.0/data_criteria_helpers/dc_post_processing.rb +85 -0
  30. data/lib/hqmf-parser/2.0/data_criteria_helpers/dc_specific_occurrences_and_source_data_criteria_extract.rb +117 -0
  31. data/lib/hqmf-parser/2.0/document.rb +304 -0
  32. data/lib/hqmf-parser/2.0/document_helpers/doc_population_helper.rb +173 -0
  33. data/lib/hqmf-parser/2.0/document_helpers/doc_utilities.rb +131 -0
  34. data/lib/hqmf-parser/2.0/field_value_helper.rb +251 -0
  35. data/lib/hqmf-parser/2.0/population_criteria.rb +134 -0
  36. data/lib/hqmf-parser/2.0/precondition.rb +73 -0
  37. data/lib/hqmf-parser/2.0/source_data_criteria_helper.rb +112 -0
  38. data/lib/hqmf-parser/2.0/types.rb +448 -0
  39. data/lib/hqmf-parser/2.0/utilities.rb +45 -0
  40. data/lib/hqmf-parser/2.0/value_set_helper.rb +104 -0
  41. data/lib/hqmf-parser/converter/pass1/data_criteria_converter.rb +257 -0
  42. data/lib/hqmf-parser/converter/pass1/document_converter.rb +133 -0
  43. data/lib/hqmf-parser/converter/pass1/population_criteria_converter.rb +185 -0
  44. data/lib/hqmf-parser/converter/pass1/precondition_converter.rb +173 -0
  45. data/lib/hqmf-parser/converter/pass1/precondition_extractor.rb +201 -0
  46. data/lib/hqmf-parser/converter/pass1/simple_data_criteria.rb +26 -0
  47. data/lib/hqmf-parser/converter/pass1/simple_operator.rb +89 -0
  48. data/lib/hqmf-parser/converter/pass1/simple_population_criteria.rb +10 -0
  49. data/lib/hqmf-parser/converter/pass1/simple_precondition.rb +51 -0
  50. data/lib/hqmf-parser/converter/pass1/simple_restriction.rb +64 -0
  51. data/lib/hqmf-parser/converter/pass2/comparison_converter.rb +112 -0
  52. data/lib/hqmf-parser/converter/pass2/operator_converter.rb +102 -0
  53. data/lib/hqmf-parser/cql/data_criteria.rb +57 -0
  54. data/lib/hqmf-parser/cql/data_criteria_helpers/dc_definition_from_template_or_type_extract.rb +79 -0
  55. data/lib/hqmf-parser/cql/data_criteria_helpers/dc_post_processing.rb +43 -0
  56. data/lib/hqmf-parser/cql/document.rb +78 -0
  57. data/lib/hqmf-parser/cql/document_helpers/doc_population_helper.rb +124 -0
  58. data/lib/hqmf-parser/cql/value_set_helper.rb +103 -0
  59. data/lib/hqmf-parser/parser.rb +100 -0
  60. data/lib/qrda-export/catI-r5/qrda1_r5.rb +125 -0
  61. data/lib/qrda-export/helper/cat_1_view_helper.rb +142 -0
  62. data/lib/qrda-export/helper/code_system_helper.rb +77 -0
  63. data/lib/qrda-export/helper/date_helper.rb +81 -0
  64. data/lib/qrda-import/base-importers/demographics_importer.rb +47 -0
  65. data/lib/qrda-import/base-importers/medication_importer.rb +22 -0
  66. data/lib/qrda-import/base-importers/section_importer.rb +196 -0
  67. data/lib/qrda-import/cda_identifier.rb +19 -0
  68. data/lib/qrda-import/data-element-importers/adverse_event_importer.rb +23 -0
  69. data/lib/qrda-import/data-element-importers/allergy_intolerance_importer.rb +21 -0
  70. data/lib/qrda-import/data-element-importers/assessment_performed_importer.rb +23 -0
  71. data/lib/qrda-import/data-element-importers/communication_from_patient_to_provider_importer.rb +18 -0
  72. data/lib/qrda-import/data-element-importers/communication_from_provider_to_patient_importer.rb +18 -0
  73. data/lib/qrda-import/data-element-importers/communication_from_provider_to_provider_importer.rb +20 -0
  74. data/lib/qrda-import/data-element-importers/device_applied_importer.rb +23 -0
  75. data/lib/qrda-import/data-element-importers/device_order_importer.rb +18 -0
  76. data/lib/qrda-import/data-element-importers/diagnosis_importer.rb +23 -0
  77. data/lib/qrda-import/data-element-importers/diagnostic_study_order_importer.rb +20 -0
  78. data/lib/qrda-import/data-element-importers/diagnostic_study_performed_importer.rb +30 -0
  79. data/lib/qrda-import/data-element-importers/encounter_order_importer.rb +20 -0
  80. data/lib/qrda-import/data-element-importers/encounter_performed_importer.rb +41 -0
  81. data/lib/qrda-import/data-element-importers/immunization_administered_importer.rb +18 -0
  82. data/lib/qrda-import/data-element-importers/intervention_order_importer.rb +18 -0
  83. data/lib/qrda-import/data-element-importers/intervention_performed_importer.rb +22 -0
  84. data/lib/qrda-import/data-element-importers/laboratory_test_order_importer.rb +20 -0
  85. data/lib/qrda-import/data-element-importers/laboratory_test_performed_importer.rb +28 -0
  86. data/lib/qrda-import/data-element-importers/medication_active_importer.rb +17 -0
  87. data/lib/qrda-import/data-element-importers/medication_administered_importer.rb +17 -0
  88. data/lib/qrda-import/data-element-importers/medication_discharge_importer.rb +19 -0
  89. data/lib/qrda-import/data-element-importers/medication_dispensed_importer.rb +19 -0
  90. data/lib/qrda-import/data-element-importers/medication_order_importer.rb +16 -0
  91. data/lib/qrda-import/data-element-importers/patient_characteristic_expired.rb +21 -0
  92. data/lib/qrda-import/data-element-importers/physical_exam_performed_importer.rb +26 -0
  93. data/lib/qrda-import/data-element-importers/procedure_order_importer.rb +26 -0
  94. data/lib/qrda-import/data-element-importers/procedure_performed_importer.rb +34 -0
  95. data/lib/qrda-import/data-element-importers/substance_administered_importer.rb +16 -0
  96. data/lib/qrda-import/entry_finder.rb +20 -0
  97. data/lib/qrda-import/entry_package.rb +16 -0
  98. data/lib/qrda-import/narrative_reference_handler.rb +33 -0
  99. data/lib/qrda-import/patient_importer.rb +105 -0
  100. data/lib/util/code_system_helper.rb +76 -0
  101. data/lib/util/counter.rb +20 -0
  102. data/lib/util/hqmf_template_helper.rb +39 -0
  103. metadata +340 -0
@@ -0,0 +1,185 @@
1
+ module HQMF
2
+ # Class for converting an HQMF 1.0 representation to an HQMF 2.0 representation
3
+ class PopulationCriteriaConverter
4
+
5
+ attr_reader :sub_measures
6
+
7
+ def initialize(doc, data_criteria_converter)
8
+ @doc = doc
9
+ @data_criteria_converter = data_criteria_converter
10
+ @population_criteria_by_id = {}
11
+ @population_criteria_by_key = {}
12
+ @population_reference = {}
13
+ parse()
14
+ build_sub_measures()
15
+ end
16
+
17
+ def population_criteria
18
+ @population_criteria_by_key.values
19
+ end
20
+
21
+ private
22
+
23
+ def build_sub_measures()
24
+ @sub_measures = []
25
+ ipps = @population_criteria_by_id.select {|key, value| value.type == HQMF::PopulationCriteria::IPP}
26
+ denoms = @population_criteria_by_id.select {|key, value| value.type == HQMF::PopulationCriteria::DENOM}
27
+ nums = @population_criteria_by_id.select {|key, value| value.type == HQMF::PopulationCriteria::NUMER}
28
+ numexs = @population_criteria_by_id.select {|key, value| value.type == HQMF::PopulationCriteria::NUMEX}
29
+ msrpopls = @population_criteria_by_id.select {|key, value| value.type == HQMF::PopulationCriteria::MSRPOPL}
30
+ observs = @population_criteria_by_id.select {|key, value| value.type == HQMF::PopulationCriteria::OBSERV}
31
+ excls = @population_criteria_by_id.select {|key, value| value.type == HQMF::PopulationCriteria::DENEX}
32
+ denexcs = @population_criteria_by_id.select {|key, value| value.type == HQMF::PopulationCriteria::DENEXCEP}
33
+ msrpoplexs = @population_criteria_by_id.select {|key, value| value.type == HQMF::PopulationCriteria::MSRPOPLEX}
34
+
35
+ stratifications = @population_criteria_by_id.values.select {|value| value.type == HQMF::PopulationCriteria::STRAT}
36
+
37
+ if (ipps.size<=1 and denoms.size<=1 and nums.size<=1 and numexs.size<=1 and excls.size<=1 and denexcs.size<=1 and msrpopls.size<=1 and msrpoplexs.size<=1 and observs.size<=1)
38
+ sub_measure = {}
39
+
40
+ sub_measure[HQMF::PopulationCriteria::IPP] = HQMF::PopulationCriteria::IPP if ipps.size > 0
41
+ sub_measure[HQMF::PopulationCriteria::DENOM] = HQMF::PopulationCriteria::DENOM if denoms.size > 0
42
+ sub_measure[HQMF::PopulationCriteria::NUMER] = HQMF::PopulationCriteria::NUMER if nums.size > 0
43
+ sub_measure[HQMF::PopulationCriteria::NUMEX] = HQMF::PopulationCriteria::NUMEX if numexs.size > 0
44
+ sub_measure[HQMF::PopulationCriteria::DENEXCEP] = HQMF::PopulationCriteria::DENEXCEP if denexcs.size > 0
45
+ sub_measure[HQMF::PopulationCriteria::DENEX] = HQMF::PopulationCriteria::DENEX if excls.size > 0
46
+ sub_measure[HQMF::PopulationCriteria::MSRPOPL] = HQMF::PopulationCriteria::MSRPOPL if msrpopls.size > 0
47
+ sub_measure[HQMF::PopulationCriteria::OBSERV] = HQMF::PopulationCriteria::OBSERV if observs.size > 0
48
+ sub_measure[HQMF::PopulationCriteria::MSRPOPLEX] = HQMF::PopulationCriteria::MSRPOPLEX if msrpoplexs.size > 0
49
+
50
+ @sub_measures << sub_measure
51
+ else
52
+
53
+ nums.each do |num_id, num|
54
+ @sub_measures << {HQMF::PopulationCriteria::NUMER => num.id}
55
+ end
56
+ msrpopls.each do |popl_id, popl|
57
+ @sub_measures << {HQMF::PopulationCriteria::MSRPOPL => popl.id}
58
+ end
59
+ apply_to_submeasures(@sub_measures, HQMF::PopulationCriteria::DENOM, denoms.values)
60
+ apply_to_submeasures(@sub_measures, HQMF::PopulationCriteria::IPP, ipps.values)
61
+
62
+ apply_to_submeasures(@sub_measures, HQMF::PopulationCriteria::DENEX, excls.values, HQMF::PopulationCriteria::IPP, get_unmatched_population_keys(ipps, excls))
63
+ apply_to_submeasures(@sub_measures, HQMF::PopulationCriteria::DENEXCEP, denexcs.values, HQMF::PopulationCriteria::DENOM, get_unmatched_population_keys(denoms, denexcs))
64
+
65
+ apply_to_submeasures(@sub_measures, HQMF::PopulationCriteria::OBSERV, observs.values)
66
+
67
+ keep = []
68
+
69
+ @sub_measures.each do |sub|
70
+
71
+ value = sub
72
+ HQMF::PopulationCriteria::ALL_POPULATION_CODES.each do |type|
73
+ key = sub[type]
74
+ if (key)
75
+ reference_id = @population_reference[key]
76
+ reference = @population_criteria_by_id[reference_id] if reference_id
77
+ if (reference)
78
+ criteria = @population_criteria_by_key[sub[reference.type]]
79
+ value = nil if (sub[reference.type] != reference.id)
80
+ end
81
+ end
82
+ end
83
+ keep << value if (value)
84
+ end
85
+
86
+ @sub_measures = keep
87
+
88
+ end
89
+
90
+ # add stratifications if we have them
91
+ if (stratifications.size > 0)
92
+ strat_subs = []
93
+ @sub_measures.each do |sub|
94
+ stratifications.each do |stratification|
95
+ new_sub = sub.dup
96
+ new_sub[HQMF::PopulationCriteria::STRAT] = stratification.id
97
+ new_sub['stratification'] = stratification.hqmf_id
98
+ strat_subs << new_sub
99
+ end
100
+ end
101
+ @sub_measures.concat strat_subs
102
+ end
103
+
104
+ if (@sub_measures.length > 1)
105
+ @sub_measures.each_with_index do |sub, i|
106
+ sub['title'] = "Population #{i+1}"
107
+ sub['id'] = "Population#{i+1}"
108
+ end
109
+ end
110
+
111
+ end
112
+
113
+ # source are things like exceptions or exclusions, target are IPP, or denom
114
+ # we want to find any denoms or IPPs that do not have exceptions or exclusions
115
+ def get_unmatched_population_keys(target, source)
116
+ return [] if target.length == source.length
117
+ all_target_keys = target.values.map(&:id)
118
+ with_ref_keys = source.values.map(&:id).map {|key| @population_criteria_by_id[@population_reference[key]].id if @population_criteria_by_id[@population_reference[key]]}
119
+ # if we have a population without a reference, we cannot trust the results.
120
+ return [] if with_ref_keys.include? nil
121
+ all_target_keys - with_ref_keys
122
+ end
123
+
124
+ # create a copy of each submeasre adding on the new values of the given type
125
+ # skip the unpaired values. Unpaired values are denominators without exclusions or populations without exceptions
126
+ def apply_to_submeasures(subs, type, values, unpaired_type=nil, unpaired_keys=[])
127
+ new_subs = []
128
+ subs.each do |sub|
129
+ # this unless prevents us from forcing an exclusion or excepion onto a measure that has a submeasure without
130
+ # an exclusion or exception, but other populations with an exclusion or excepion.
131
+ unless unpaired_keys.include? sub[unpaired_type]
132
+ # duplicate each new value if it is set, otherwise set this key on each submeasure.
133
+ values.each do |value|
134
+ if (sub[type] and sub[type] != value.id)
135
+ tmp = {}
136
+ HQMF::PopulationCriteria::ALL_POPULATION_CODES.each do |key|
137
+ tmp[key] = sub[key] if sub[key]
138
+ end
139
+ sub = tmp
140
+ new_subs << sub
141
+ end
142
+ sub[type] = value.id
143
+ end
144
+ end
145
+ end
146
+ subs.concat(new_subs)
147
+ end
148
+
149
+ def find_sub_measures(type, value)
150
+ found = []
151
+ @sub_measures.each do |sub_measure|
152
+ found << sub_measure if sub_measure[type] and sub_measure[type] == value.id
153
+ end
154
+ found
155
+ end
156
+
157
+ def parse
158
+ @doc[:logic].each do |key,criteria|
159
+ @population_criteria_by_key[key] = convert(key.to_s, criteria)
160
+ end
161
+ end
162
+
163
+ def convert(key, population_criteria)
164
+
165
+ # @param [String] id
166
+ # @param [Array#Precondition] preconditions
167
+
168
+ preconditions = HQMF::PreconditionConverter.parse_preconditions(population_criteria[:preconditions],@data_criteria_converter)
169
+ hqmf_id = population_criteria[:hqmf_id] || population_criteria[:id]
170
+ id = population_criteria[:id]
171
+ type = population_criteria[:code]
172
+ reference = population_criteria[:reference]
173
+ title = population_criteria[:title]
174
+
175
+ criteria = HQMF::Converter::SimplePopulationCriteria.new(key, hqmf_id, type, preconditions, title)
176
+
177
+ @population_criteria_by_id[id] = criteria
178
+ @population_reference[key] = reference
179
+
180
+ criteria
181
+
182
+ end
183
+
184
+ end
185
+ end
@@ -0,0 +1,173 @@
1
+ module HQMF
2
+ # Class for converting an HQMF 1.0 representation to an HQMF 2.0 representation
3
+ class PreconditionConverter
4
+
5
+ def self.parse_preconditions(source,data_criteria_converter)
6
+
7
+ # preconditions = []
8
+ # source.each do |precondition|
9
+ # preconditions << HQMF::PreconditionConverter.parse_precondition(precondition,data_criteria_converter)
10
+ # end
11
+ #
12
+ # preconditions
13
+
14
+ parse_and_merge_preconditions(source,data_criteria_converter)
15
+ end
16
+
17
+ # converts a precondtion to a hqmf model
18
+ def self.parse_precondition(precondition,data_criteria_converter)
19
+
20
+ # grab child preconditions, and parse recursively
21
+ preconditions = parse_and_merge_preconditions(precondition[:preconditions],data_criteria_converter) if precondition[:preconditions] || []
22
+
23
+ preconditions_from_restrictions = HQMF::PreconditionExtractor.extract_preconditions_from_restrictions(precondition[:restrictions], data_criteria_converter)
24
+
25
+ # driv preconditions are preconditions that are the children of an expression
26
+ driv_preconditions = []
27
+ preconditions_from_restrictions.delete_if {|element| driv_preconditions << element if element.is_a? HQMF::Converter::SimpleRestriction and element.operator.type == 'DRIV'}
28
+
29
+ apply_restrictions_to_comparisons(preconditions, preconditions_from_restrictions) unless preconditions_from_restrictions.empty?
30
+
31
+ conjunction_code = convert_logical_conjunction(precondition[:conjunction])
32
+
33
+ if (precondition[:expression])
34
+ # this is for things like COUNT
35
+ type = precondition[:expression][:type]
36
+ operator = HQMF::Converter::SimpleOperator.new(HQMF::Converter::SimpleOperator.find_category(type), type, HQMF::Converter::SimpleOperator.parse_value(precondition[:expression][:value]))
37
+ children = []
38
+ if driv_preconditions and !driv_preconditions.empty?
39
+ children = driv_preconditions.map(&:preconditions).flatten
40
+ end
41
+
42
+ reference = nil
43
+ # take the conjunction code from the parent precondition
44
+
45
+ restriction = HQMF::Converter::SimpleRestriction.new(operator, nil, children)
46
+
47
+ comparison_precondition = HQMF::Converter::SimplePrecondition.new(nil,[restriction],reference,conjunction_code, false)
48
+ comparison_precondition.klass = HQMF::Converter::SimplePrecondition::COMPARISON
49
+ comparison_precondition.subset_comparison = true
50
+ preconditions << comparison_precondition
51
+ end
52
+
53
+ reference = nil
54
+
55
+ negation = precondition[:negation]
56
+
57
+
58
+ if (precondition[:comparison])
59
+ preconditions ||= []
60
+ comparison_precondition = HQMF::PreconditionExtractor.convert_comparison_to_precondition(precondition[:comparison],data_criteria_converter)
61
+ preconditions << comparison_precondition
62
+ end
63
+
64
+
65
+ if (precondition[:subset])
66
+ # this is for things like FIRST on preconditions
67
+ type = precondition[:subset]
68
+ operator = HQMF::Converter::SimpleOperator.new(HQMF::Converter::SimpleOperator.find_category(type), type, nil)
69
+ children = preconditions
70
+
71
+ reference = nil
72
+ # take the conjunction code from the parent precondition
73
+
74
+ restriction = HQMF::Converter::SimpleRestriction.new(operator, nil, children)
75
+
76
+ comparison_precondition = HQMF::Converter::SimplePrecondition.new(nil,[restriction],reference,conjunction_code, false)
77
+ comparison_precondition.klass = HQMF::Converter::SimplePrecondition::COMPARISON
78
+ preconditions = [comparison_precondition]
79
+ end
80
+
81
+
82
+ HQMF::Converter::SimplePrecondition.new(nil,preconditions,reference,conjunction_code, negation)
83
+
84
+ end
85
+
86
+ def self.get_comparison_preconditions(preconditions)
87
+ comparisons = []
88
+ preconditions.each do |precondition|
89
+ if (precondition.comparison? and !precondition.subset_comparison)
90
+ comparisons << precondition
91
+ elsif(precondition.has_preconditions?)
92
+ comparisons.concat(get_comparison_preconditions(precondition.preconditions))
93
+ else
94
+ raise "precondition with no comparison or children... not valid"
95
+ end
96
+ end
97
+ comparisons
98
+ end
99
+
100
+ def self.apply_restrictions_to_comparisons(preconditions, restrictions)
101
+ comparisons = get_comparison_preconditions(preconditions)
102
+ raise "no comparisons to apply restriction to" if comparisons.empty?
103
+ comparisons.each do |comparison|
104
+ comparison.preconditions.concat(restrictions)
105
+ end
106
+ end
107
+
108
+ private
109
+
110
+
111
+ def self.parse_and_merge_preconditions(source,data_criteria_converter)
112
+ return [] unless source and source.size > 0
113
+ preconditions_by_conjunction = {}
114
+ source.each do |precondition|
115
+ parsed = HQMF::PreconditionConverter.parse_precondition(precondition,data_criteria_converter)
116
+ preconditions_by_conjunction[parsed.conjunction_code] ||= []
117
+ preconditions_by_conjunction[parsed.conjunction_code] << parsed
118
+ end
119
+
120
+ merge_precondtion_conjunction_groups(preconditions_by_conjunction)
121
+ end
122
+
123
+ def self.merge_precondtion_conjunction_groups(preconditions_by_conjunction)
124
+ joined = []
125
+ preconditions_by_conjunction.each do |conjunction_code, preconditions|
126
+ sub_conditions = []
127
+ negated_conditions = []
128
+ preconditions.each do |precondition|
129
+ unless (precondition.negation)
130
+ sub_conditions.concat precondition.preconditions if precondition.preconditions
131
+ else
132
+ negated_conditions.concat precondition.preconditions if precondition.preconditions
133
+ end
134
+ end
135
+
136
+ if (!sub_conditions.empty?)
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)
144
+ if (!negated_conditions.empty?)
145
+ inverted_conjunction_code = HQMF::Precondition::INVERSIONS[conjunction_code]
146
+ sub_conditions << HQMF::Converter::SimplePrecondition.new(nil,negated_conditions,nil,inverted_conjunction_code, true)
147
+ end
148
+ joined << HQMF::Converter::SimplePrecondition.new(nil,sub_conditions,nil,conjunction_code, false)
149
+ elsif (!negated_conditions.empty?)
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)
153
+ end
154
+
155
+ end
156
+ joined
157
+ end
158
+
159
+ def self.convert_logical_conjunction(code)
160
+ case code
161
+ when 'OR'
162
+ HQMF::Precondition::AT_LEAST_ONE_TRUE
163
+ when 'AND'
164
+ HQMF::Precondition::ALL_TRUE
165
+ else
166
+ raise "unsupported logical conjunction code conversion: #{code}"
167
+ end
168
+
169
+ end
170
+
171
+
172
+ end
173
+ end
@@ -0,0 +1,201 @@
1
+ module HQMF
2
+
3
+ # preconditions can be in several places.
4
+ #
5
+ # precondition -> preconditions
6
+ # restriction -> preconditions
7
+ #
8
+ # also, restrictions can be on the following, which can then have preconditions
9
+ # restrictions
10
+ # comparisons
11
+ # preconditions
12
+ #
13
+ class PreconditionExtractor
14
+
15
+
16
+ def self.extract_preconditions_from_restrictions(restrictions,data_criteria_converter)
17
+ return [] unless restrictions
18
+ preconditions = []
19
+ restrictions.each do |restriction|
20
+ preconditions.concat(extract_preconditions_from_restriction(restriction,data_criteria_converter))
21
+ end
22
+ preconditions
23
+ end
24
+
25
+ # get all the preconditions for a restriction
26
+ # we need to iterate down
27
+ # restriction.preconditions
28
+ # restriction.comparison
29
+ # restriction.restriction
30
+ def self.extract_preconditions_from_restriction(restriction,data_criteria_converter)
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
37
+ type = restriction[:type]
38
+ if (restriction[:negation])
39
+ inverted = HQMF::TemporalReference::INVERSION[type]
40
+ if (inverted)
41
+ type = inverted
42
+ else
43
+ puts "\tdon't know how to invert #{type}"
44
+ end
45
+ end
46
+
47
+ # if we reference the measurement period, then we want to check if the reference is to the start or end of the measurement period
48
+ # if we SBS of the END of the measurement period, we want to convert that to SBE of the measurement period
49
+ if target_id == HQMF::Document::MEASURE_PERIOD_ID
50
+ references_start = {'SBS'=>'SBE','SAS'=>'SAE','EBS'=>'EBE','EAS'=>'EAE','SCW'=>'SCWE'}
51
+ references_end = {'EBE'=>'EBS','EAE'=>'EAS','SBE'=>'SBS','SAE'=>'SAS','ECW'=>'ECWS'}
52
+ if data_criteria_converter.measure_period_v1_keys[:measure_start] == restriction[:target_id] and references_end[type]
53
+ # before or after the END of the measurement period START. Convert to before or after the START of the measurement period.
54
+ # SAE of MPS => SAS of MP
55
+ # ends concurrent with measurement period START. Convert to concurrent with START of measurement period.
56
+ # ECW of MPS => ECWS
57
+ type = references_end[type]
58
+ elsif data_criteria_converter.measure_period_v1_keys[:measure_end] == restriction[:target_id] and references_start[type]
59
+ # before or after the START of the measurement period END. Convert to before or after the END of the measurement period.
60
+ # SBS of MPE => SBE of MP
61
+ # starts concurrent with measurement period END. Convert to concurrent with END of measurement period.
62
+ # SCW of MPE => SCWE
63
+ type = references_start[type]
64
+ end
65
+ end
66
+
67
+ value = nil
68
+ if (restriction[:range])
69
+ value = HQMF::Range.from_json(JSON.parse(restriction[:range].to_json)) if (restriction[:range])
70
+ elsif(restriction[:value])
71
+ value = HQMF::Converter::SimpleOperator.parse_value(restriction[:value])
72
+ end
73
+ field = restriction[:field]
74
+ field_code = restriction[:field_code]
75
+ field_time = restriction[:field_time]
76
+ operator = HQMF::Converter::SimpleOperator.new(HQMF::Converter::SimpleOperator.find_category(type), type, value, field, field_code, field_time)
77
+
78
+ # get the precondtions off of the restriction
79
+ children = HQMF::PreconditionConverter.parse_and_merge_preconditions(restriction[:preconditions],data_criteria_converter) if restriction[:preconditions]
80
+
81
+ if restriction[:comparison]
82
+ children ||= []
83
+ # check comparison and convert it to a precondition
84
+ comparison = convert_comparison_to_precondition(restriction[:comparison], data_criteria_converter)
85
+ children << comparison
86
+ end
87
+
88
+ # check restrictions
89
+ restrictions = extract_preconditions_from_restrictions(restriction[:restrictions], data_criteria_converter) if restriction[:restrictions]
90
+ if (children)
91
+ HQMF::PreconditionConverter.apply_restrictions_to_comparisons(children, restrictions) unless restrictions.nil? or restrictions.empty?
92
+ end
93
+
94
+
95
+ container = nil
96
+ # check if there is an expression on the restriction
97
+ if (restriction[:expression])
98
+ # this is for things like TIMEDIFF
99
+ type = restriction[:expression][:type]
100
+ exp_operator = HQMF::Converter::SimpleOperator.new(HQMF::Converter::SimpleOperator.find_category(type), type, HQMF::Converter::SimpleOperator.parse_value(restriction[:expression][:value]))
101
+ preconditions = []
102
+
103
+ driv_preconditions = []
104
+ restrictions.each {|element| driv_preconditions << element if element.is_a? HQMF::Converter::SimpleRestriction and element.operator.type == 'DRIV'}
105
+
106
+ if driv_preconditions and !driv_preconditions.empty?
107
+ preconditions = driv_preconditions.map(&:preconditions).flatten
108
+ end
109
+
110
+ reference = nil
111
+ conjunction_code = nil
112
+
113
+ comparison_precondition = HQMF::Converter::SimplePrecondition.new(nil,[HQMF::Converter::SimpleRestriction.new(exp_operator, nil, preconditions)],reference,conjunction_code, false)
114
+ comparison_precondition.klass = HQMF::Converter::SimplePrecondition::COMPARISON
115
+
116
+ comparison_precondition.subset_comparison = true
117
+ container = HQMF::Converter::SimpleRestriction.new(operator, nil, [comparison_precondition])
118
+
119
+ # check if there is a subset on the restriction
120
+ elsif restriction[:subset]
121
+ # 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.
122
+ # 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
123
+
124
+ subset_type = restriction[:subset]
125
+ subset_operator = HQMF::Converter::SimpleOperator.new(HQMF::Converter::SimpleOperator.find_category(subset_type), subset_type, nil)
126
+
127
+ reference = nil
128
+ conjunction_code = nil
129
+
130
+ restriction = HQMF::Converter::SimpleRestriction.new(subset_operator, target_id)
131
+ restriction.preconditions = children
132
+
133
+ comparison_precondition = HQMF::Converter::SimplePrecondition.new(nil, [restriction], reference, conjunction_code, false)
134
+ comparison_precondition.klass = HQMF::Converter::SimplePrecondition::COMPARISON
135
+
136
+ container = HQMF::Converter::SimpleRestriction.new(operator, nil, [comparison_precondition])
137
+ else
138
+ container = HQMF::Converter::SimpleRestriction.new(operator, target_id)
139
+ # handle transitive restrictions... this is where we are adding a field to a target of a timing restriction
140
+ if (restrictions && !restrictions.empty? && children.nil?)
141
+ children = []
142
+ restrictions.each do |child|
143
+ comparison_precondition = HQMF::Converter::SimplePrecondition.new(nil,[child],HQMF::Reference.new(target_id),nil, false)
144
+ comparison_precondition.klass = HQMF::Converter::SimplePrecondition::COMPARISON
145
+ children << comparison_precondition
146
+ end
147
+ end
148
+ container.preconditions = children
149
+ end
150
+
151
+ [container]
152
+ end
153
+
154
+
155
+ # we want the comparisons to be converted to the leaf preconditions
156
+ def self.convert_comparison_to_precondition(comparison, data_criteria_converter)
157
+
158
+ data_criteria = data_criteria_converter.v1_data_criteria_by_id[comparison[:data_criteria_id]]
159
+ reference = HQMF::Reference.new(data_criteria.id)
160
+ # conjunction_code = "#{data_criteria.type.to_s.gsub(/(?:_|(\/))([a-z\d]*)/) { "#{$1}#{$2.capitalize}" }}Reference"
161
+ conjunction_code = nil
162
+
163
+ preconditions = []
164
+ if comparison[:restrictions]
165
+ # check for preconditions on restrictions
166
+ preconditions = extract_preconditions_from_restrictions(comparison[:restrictions], data_criteria_converter)
167
+ end
168
+
169
+ precondition = HQMF::Converter::SimplePrecondition.new(nil,preconditions,reference,conjunction_code, false)
170
+ precondition.klass = HQMF::Converter::SimplePrecondition::COMPARISON
171
+
172
+ if (comparison[:subset])
173
+ # create a restriction for a comparison subset... this is for things like first, second, etc.
174
+ type = comparison[:subset]
175
+ operator = HQMF::Converter::SimpleOperator.new(HQMF::Converter::SimpleOperator.find_category(type), type, nil)
176
+ restriction = HQMF::Converter::SimpleRestriction.new(operator, reference.id, nil)
177
+ precondition.preconditions ||= []
178
+ precondition.preconditions << restriction
179
+ end
180
+
181
+ precondition
182
+ end
183
+
184
+ # flatten a tree of preconditions into an array... if we are doing something like a count, we just want the flat list
185
+ def self.flatten_v2_preconditions(preconditions)
186
+ flattened = []
187
+ preconditions.each do |precondition|
188
+ if (precondition.reference and precondition.has_preconditions?)
189
+ raise "don't know how to handle a condition with a reference that has preconditions" if (precondition.reference and precondition.has_preconditions?)
190
+ end
191
+ if (precondition.reference)
192
+ flattened << precondition
193
+ else
194
+ flattened.concat(flatten_v2_preconditions(precondition.preconditions))
195
+ end
196
+ end
197
+ flattened
198
+ end
199
+
200
+ end
201
+ end