hqmf-parser 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/Gemfile +23 -0
  2. data/README.md +903 -0
  3. data/Rakefile +19 -0
  4. data/VERSION +1 -0
  5. data/lib/hqmf-generator/hqmf-generator.rb +308 -0
  6. data/lib/hqmf-model/attribute.rb +35 -0
  7. data/lib/hqmf-model/data_criteria.rb +322 -0
  8. data/lib/hqmf-model/document.rb +172 -0
  9. data/lib/hqmf-model/population_criteria.rb +90 -0
  10. data/lib/hqmf-model/precondition.rb +85 -0
  11. data/lib/hqmf-model/types.rb +318 -0
  12. data/lib/hqmf-model/utilities.rb +52 -0
  13. data/lib/hqmf-parser.rb +54 -0
  14. data/lib/hqmf-parser/1.0/attribute.rb +68 -0
  15. data/lib/hqmf-parser/1.0/comparison.rb +34 -0
  16. data/lib/hqmf-parser/1.0/data_criteria.rb +105 -0
  17. data/lib/hqmf-parser/1.0/document.rb +209 -0
  18. data/lib/hqmf-parser/1.0/expression.rb +52 -0
  19. data/lib/hqmf-parser/1.0/population_criteria.rb +79 -0
  20. data/lib/hqmf-parser/1.0/precondition.rb +89 -0
  21. data/lib/hqmf-parser/1.0/range.rb +65 -0
  22. data/lib/hqmf-parser/1.0/restriction.rb +157 -0
  23. data/lib/hqmf-parser/1.0/utilities.rb +41 -0
  24. data/lib/hqmf-parser/2.0/data_criteria.rb +319 -0
  25. data/lib/hqmf-parser/2.0/document.rb +165 -0
  26. data/lib/hqmf-parser/2.0/population_criteria.rb +53 -0
  27. data/lib/hqmf-parser/2.0/precondition.rb +44 -0
  28. data/lib/hqmf-parser/2.0/types.rb +223 -0
  29. data/lib/hqmf-parser/2.0/utilities.rb +30 -0
  30. data/lib/hqmf-parser/converter/pass1/data_criteria_converter.rb +254 -0
  31. data/lib/hqmf-parser/converter/pass1/document_converter.rb +183 -0
  32. data/lib/hqmf-parser/converter/pass1/population_criteria_converter.rb +135 -0
  33. data/lib/hqmf-parser/converter/pass1/precondition_converter.rb +164 -0
  34. data/lib/hqmf-parser/converter/pass1/precondition_extractor.rb +159 -0
  35. data/lib/hqmf-parser/converter/pass1/simple_data_criteria.rb +35 -0
  36. data/lib/hqmf-parser/converter/pass1/simple_operator.rb +89 -0
  37. data/lib/hqmf-parser/converter/pass1/simple_population_criteria.rb +10 -0
  38. data/lib/hqmf-parser/converter/pass1/simple_precondition.rb +63 -0
  39. data/lib/hqmf-parser/converter/pass1/simple_restriction.rb +64 -0
  40. data/lib/hqmf-parser/converter/pass2/comparison_converter.rb +91 -0
  41. data/lib/hqmf-parser/converter/pass2/operator_converter.rb +169 -0
  42. data/lib/hqmf-parser/converter/pass3/specific_occurrence_converter.rb +86 -0
  43. data/lib/hqmf-parser/converter/pass3/specific_occurrence_converter_bak.rb +70 -0
  44. data/lib/hqmf-parser/parser.rb +22 -0
  45. data/lib/hqmf-parser/value_sets/value_set_parser.rb +206 -0
  46. data/lib/tasks/coverme.rake +8 -0
  47. data/lib/tasks/hqmf.rake +141 -0
  48. data/lib/tasks/value_sets.rake +23 -0
  49. metadata +159 -0
data/Rakefile ADDED
@@ -0,0 +1,19 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+
4
+ require_relative File.join('lib', 'hqmf-parser')
5
+
6
+ # Pull in any rake task defined in lib/tasks
7
+ Dir['lib/tasks/*.rake'].sort.each do |ext|
8
+ load ext
9
+ end
10
+
11
+ $LOAD_PATH << File.expand_path("../test",__FILE__)
12
+ desc "Run basic tests"
13
+ Rake::TestTask.new("test_unit") { |t|
14
+ t.pattern = 'test/unit/**/*_test.rb'
15
+ t.verbose = false
16
+ t.warning = false
17
+ }
18
+
19
+ task :default => [:test_unit,'cover_me:report']
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,308 @@
1
+ module HQMF2
2
+ module Generator
3
+
4
+ def self.render_template(name, params)
5
+ template_path = File.expand_path(File.join('..', "#{name}.xml.erb"), __FILE__)
6
+ template_str = File.read(template_path)
7
+ template = ERB.new(template_str, nil, '-', "_templ#{TemplateCounter.instance.new_id}")
8
+ context = ErbContext.new(params)
9
+ template.result(context.get_binding)
10
+ end
11
+
12
+ # Class to serialize HQMF::Document as HQMF V2 XML
13
+ class ModelProcessor
14
+ # Convert the supplied model instance to XML
15
+ # @param [HQMF::Document] doc the model instance
16
+ # @return [String] the serialized XML as a String
17
+ def self.to_hqmf(doc)
18
+ HQMF2::Generator.render_template('document', {'doc' => doc})
19
+ end
20
+ end
21
+
22
+ # Utility class used to supply a binding to Erb. Contains utility functions used
23
+ # by the erb templates that are used to generate the HQMF document.
24
+ class ErbContext < OpenStruct
25
+
26
+ def initialize(vars)
27
+ super(vars)
28
+ end
29
+
30
+ # Get a binding that contains all the instance variables
31
+ # @return [Binding]
32
+ def get_binding
33
+ binding
34
+ end
35
+
36
+ def xml_for_reference_id(id)
37
+ reference = HQMF::Reference.new(id)
38
+ xml_for_reference(reference)
39
+ end
40
+
41
+ def xml_for_reference(reference)
42
+ HQMF2::Generator.render_template('reference', {'doc' => doc, 'reference' => reference})
43
+ end
44
+
45
+ def xml_for_attribute(attribute)
46
+ HQMF2::Generator.render_template('attribute', {'attribute' => attribute})
47
+ end
48
+
49
+ def xml_for_fields(criteria)
50
+ fields = []
51
+ if criteria.field_values
52
+ criteria.field_values.each_pair do |key, value|
53
+ details = HQMF::DataCriteria::FIELDS[key]
54
+ details[:code_system_name] = HealthDataStandards::Util::CodeSystemHelper.code_system_for(details[:code_system])
55
+ fields << HQMF2::Generator.render_template('field', {'details' => details, 'value' => value})
56
+ end
57
+ end
58
+ if criteria.specific_occurrence
59
+ fields << HQMF2::Generator.render_template('specific_occurrence', {'source_criteria_id' => criteria.source_data_criteria, 'type' => criteria.specific_occurrence_const, 'id' => criteria.specific_occurrence})
60
+ elsif criteria.source_data_criteria
61
+ fields << HQMF2::Generator.render_template('source', {'source_criteria_id' => criteria.source_data_criteria})
62
+ end
63
+ fields.join
64
+ end
65
+
66
+ def xml_for_value(value, element_name='value', include_type=true)
67
+ HQMF2::Generator.render_template('value', {'doc' => doc, 'value' => value, 'name' => element_name, 'include_type' => include_type})
68
+ end
69
+
70
+ def xml_for_code(criteria, element_name='code', include_type=true)
71
+ HQMF2::Generator.render_template('code', {'doc' => doc, 'criteria' => criteria, 'name' => element_name, 'include_type' => include_type})
72
+ end
73
+
74
+ def xml_for_derivation(data_criteria)
75
+ xml = ''
76
+ if data_criteria.derivation_operator
77
+ xml = HQMF2::Generator.render_template('derivation', {'doc' => doc, 'criteria' => data_criteria})
78
+ end
79
+ xml
80
+ end
81
+
82
+ def xml_for_effective_time(data_criteria)
83
+ xml = ''
84
+ if data_criteria.effective_time
85
+ xml = HQMF2::Generator.render_template('effective_time', {'doc' => doc, 'effective_time' => data_criteria.effective_time})
86
+ end
87
+ xml
88
+ end
89
+
90
+ def xml_for_reason(data_criteria)
91
+ xml = ''
92
+ if data_criteria.negation && data_criteria.negation_code_list_id
93
+ xml = HQMF2::Generator.render_template('reason', {'doc' => doc, 'code_list_id' => data_criteria.negation_code_list_id})
94
+ end
95
+ xml
96
+ end
97
+
98
+ def xml_for_template(data_criteria, is_source_data_criteria)
99
+ xml = ''
100
+ templates = []
101
+ # Add a template ID if one is defined for this data criteria
102
+ template_id = HQMF::DataCriteria.template_id_for_definition(data_criteria.definition, data_criteria.status, data_criteria.negation)
103
+ if template_id
104
+ templates << {:id => template_id, :title => HQMF::DataCriteria.title_for_template_id(template_id)}
105
+ end
106
+ # Add our own template id if this is a source data criteria from HQMF V1. Source
107
+ # data criteria are the 'raw' HQMF V1 data criteria before any restrictions are applied
108
+ # they are only used for negating specific occurrences
109
+ if is_source_data_criteria
110
+ templates << {:id => HQMF::DataCriteria::SOURCE_DATA_CRITERIA_TEMPLATE_ID, :title => HQMF::DataCriteria::SOURCE_DATA_CRITERIA_TEMPLATE_TITLE}
111
+ end
112
+ if templates.length > 0
113
+ xml = HQMF2::Generator.render_template('template_id', {'templates' => templates})
114
+ end
115
+ xml
116
+ end
117
+
118
+ def xml_for_description(data_criteria)
119
+ xml = ''
120
+ if data_criteria.description
121
+ xml = HQMF2::Generator.render_template('description', {'text' => data_criteria.description})
122
+ end
123
+ xml
124
+ end
125
+
126
+ def xml_for_subsets(data_criteria)
127
+ subsets_xml = []
128
+ if data_criteria.subset_operators
129
+ subsets_xml = data_criteria.subset_operators.collect do |operator|
130
+ HQMF2::Generator.render_template('subset', {'doc' => doc, 'subset' => operator, 'criteria' => data_criteria})
131
+ end
132
+ end
133
+ subsets_xml.join()
134
+ end
135
+
136
+ def xml_for_precondition(precondition)
137
+ HQMF2::Generator.render_template('precondition', {'doc' => doc, 'precondition' => precondition})
138
+ end
139
+
140
+ def xml_for_data_criteria(data_criteria, is_source_data_criteria)
141
+ HQMF2::Generator.render_template(data_criteria_template_name(data_criteria), {'doc' => doc, 'criteria' => data_criteria, 'is_source_data_criteria' => is_source_data_criteria})
142
+ end
143
+
144
+ def xml_for_population_criteria(population, criteria_id)
145
+ xml = ''
146
+ population_criteria = doc.population_criteria(population[criteria_id])
147
+ if population_criteria
148
+ xml = HQMF2::Generator.render_template('population_criteria', {'doc' => doc, 'population' => population, 'criteria_id' => criteria_id, 'population_criteria' => population_criteria})
149
+ end
150
+ xml
151
+ end
152
+
153
+ def xml_for_temporal_references(criteria)
154
+ refs = []
155
+ if criteria.temporal_references
156
+ refs = criteria.temporal_references.collect do |reference|
157
+ HQMF2::Generator.render_template('temporal_relationship', {'doc' => doc, 'relationship' => reference})
158
+ end
159
+ end
160
+ refs.join
161
+ end
162
+
163
+ def oid_for_name(code_system_name)
164
+ HealthDataStandards::Util::CodeSystemHelper.oid_for_code_system(code_system_name)
165
+ end
166
+
167
+ def reference_element_name(id)
168
+ referenced_criteria = doc.data_criteria(id)
169
+ element_name_prefix(referenced_criteria)
170
+ end
171
+
172
+ def reference_type_name(id)
173
+ referenced_criteria = doc.data_criteria(id)
174
+ type = nil
175
+ if referenced_criteria
176
+ type = referenced_criteria.type
177
+ elsif id=="MeasurePeriod"
178
+ type = :observation
179
+ end
180
+ if !type
181
+ raise "No data criteria with ID: #{id}"
182
+ end
183
+ case type
184
+ when :encounters
185
+ 'ENC'
186
+ when :procedures
187
+ 'PROC'
188
+ when :medications, :allMedications
189
+ 'SBADM'
190
+ when :medication_supply
191
+ 'SPLY'
192
+ else
193
+ 'OBS'
194
+ end
195
+ end
196
+
197
+ def code_for_characteristic(characteristic)
198
+ case characteristic
199
+ when :birthtime
200
+ '21112-8'
201
+ when :age
202
+ '424144002'
203
+ when :gender
204
+ '263495000'
205
+ when :languages
206
+ '102902016'
207
+ when :maritalStatus
208
+ '125680007'
209
+ when :race
210
+ '103579009'
211
+ else
212
+ raise "Unknown demographic code [#{characteristic}]"
213
+ end
214
+ end
215
+
216
+ def oid_for_characteristic(characteristic)
217
+ case characteristic
218
+ when :birthtime
219
+ '2.16.840.1.113883.6.1'
220
+ else
221
+ '2.16.840.1.113883.6.96'
222
+ end
223
+ end
224
+
225
+ def data_criteria_template_name(data_criteria)
226
+ case data_criteria.definition
227
+ when 'diagnosis', 'diagnosis_family_history'
228
+ 'condition_criteria'
229
+ when 'encounter'
230
+ 'encounter_criteria'
231
+ when 'procedure', 'risk_category_assessment', 'physical_exam', 'communication_from_patient_to_provider', 'communication_from_provider_to_provider', 'device', 'diagnostic_study', 'intervention'
232
+ if data_criteria.value.nil?
233
+ 'procedure_criteria'
234
+ else
235
+ 'observation_criteria'
236
+ end
237
+ when 'medication'
238
+ case data_criteria.status
239
+ when 'dispensed', 'ordered'
240
+ 'supply_criteria'
241
+ else # active or administered
242
+ 'substance_criteria'
243
+ end
244
+ when 'patient_characteristic', 'patient_characteristic_birthdate', 'patient_characteristic_clinical_trial_participant', 'patient_characteristic_expired', 'patient_characteristic_gender', 'patient_characteristic_age', 'patient_characteristic_languages', 'patient_characteristic_marital_status', 'patient_characteristic_race'
245
+ 'characteristic_criteria'
246
+ when 'variable'
247
+ 'variable_criteria'
248
+ else
249
+ 'observation_criteria'
250
+ end
251
+ end
252
+
253
+ def section_name(data_criteria)
254
+ data_criteria.definition.to_s
255
+ end
256
+
257
+ def element_name_prefix(data_criteria)
258
+ type = data_criteria ? data_criteria.type : :observation
259
+ case type
260
+ when :encounters
261
+ 'encounter'
262
+ when :procedures
263
+ 'procedure'
264
+ when :medications, :allMedications
265
+ 'substanceAdministration'
266
+ when :medication_supply
267
+ 'supply'
268
+ else
269
+ 'observation'
270
+ end
271
+ end
272
+
273
+ def population_element_prefix(population_criteria_code)
274
+ case population_criteria_code
275
+ when HQMF::PopulationCriteria::IPP
276
+ 'patientPopulation'
277
+ when HQMF::PopulationCriteria::DENOM
278
+ 'denominator'
279
+ when HQMF::PopulationCriteria::NUMER
280
+ 'numerator'
281
+ when HQMF::PopulationCriteria::EXCEP
282
+ 'denominatorException'
283
+ when HQMF::PopulationCriteria::DENEX
284
+ 'denominatorExclusion'
285
+ else
286
+ raise "Unknown population criteria type #{population_criteria_code}"
287
+ end
288
+ end
289
+ end
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
+ end
308
+ end
@@ -0,0 +1,35 @@
1
+ module HQMF
2
+
3
+ class Attribute
4
+ include HQMF::Conversion::Utilities
5
+ attr_reader :id,:code,:value,:unit,:name
6
+ # @param [String] id
7
+ # @param [String] code
8
+ # @param [String] value
9
+ # @param [String] unit
10
+ # @param [String] name
11
+ def initialize(id,code,value,unit,name)
12
+ @id = id
13
+ @code = code
14
+ @value = value
15
+ @unit = unit
16
+ @name = name
17
+ end
18
+
19
+ def self.from_json(json)
20
+ id = json["id"] if json["id"]
21
+ code = json["code"] if json["code"]
22
+ value = json["value"] if json["value"]
23
+ unit = json["unit"] if json["unit"]
24
+ name = json["name"] if json["name"]
25
+
26
+ HQMF::Attribute.new(id,code,value,unit,name)
27
+ end
28
+
29
+ def to_json
30
+ json = build_hash(self, [:id,:code,:value,:unit,:name])
31
+ json
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,322 @@
1
+ module HQMF
2
+ # Represents a data criteria specification
3
+ class DataCriteria
4
+
5
+ include HQMF::Conversion::Utilities
6
+
7
+ SOURCE_DATA_CRITERIA_TEMPLATE_ID = '2.16.840.1.113883.3.100.1.1'
8
+ SOURCE_DATA_CRITERIA_TEMPLATE_TITLE = 'Source data criteria'
9
+
10
+ XPRODUCT = 'XPRODUCT'
11
+ UNION = 'UNION'
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: :ordinal, code: '117363000', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.3.560.1.1012.2'},
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: :facility_location, code: 'SDLOC'},
19
+ 'FACILITY_LOCATION_ARRIVAL_DATETIME'=>{title:'Facility Location Arrival Date/Time',coded_entry_method: :facility_location_arrival, code: 'SDLOC_ARRIVAL'},
20
+ 'FACILITY_LOCATION_DEPARTURE_DATETIME'=>{title:'Facility Location Departure Date/Time',coded_entry_method: :facility_location_departure, code: 'SDLOC_DEPARTURE'},
21
+ 'DISCHARGE_DATETIME'=>{title:'Discharge Date/Time',coded_entry_method: :discharge_datetime, code: '442864001', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.3.560.1.1025.1'},
22
+ 'DISCHARGE_STATUS'=>{title:'Discharge Status',coded_entry_method: :discharge_status, code: '309039003', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.3.560.1.1003.2'},
23
+ 'ADMISSION_DATETIME'=>{title:'Admission Date/Time',coded_entry_method: :admission_datetime, code: '399423000', code_system:'2.16.840.1.113883.6.96'},
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: :start_datetime, code: '398201009', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.3.560.1.1027.1'},
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: :stop_datetime, code: '397898000', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.3.560.1.1026.1'},
31
+ 'INCISION_DATETIME'=>{title:'Incision Date/Time',coded_entry_method: :incision_datetime, code: '34896006', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.3.560.1.1007.1'},
32
+ 'REMOVAL_DATETIME'=>{title:'Removal Date/Time',coded_entry_method: :removal_datetime, code: '118292001', code_system:'2.16.840.1.113883.6.96', template_id: '2.16.840.1.113883.3.560.1.1032.1'}
33
+ }
34
+
35
+ VALUE_FIELDS = {'SEV' => 'SEVERITY',
36
+ '117363000' => 'ORDINAL',
37
+ '410666004' => 'REASON',
38
+ '260753009' => 'SOURCE',
39
+ '363819003' => 'CUMULATIVE_MEDICATION_DURATION',
40
+ 'SDLOC' => 'FACILITY_LOCATION',
41
+ '442864001' => 'DISCHARGE_DATETIME',
42
+ '309039003' => 'DISCHARGE_STATUS',
43
+ '399423000' => 'ADMISSION_DATETIME',
44
+ '183797002' => 'LENGTH_OF_STAY',
45
+ '398232005' => 'DOSE',
46
+ '263513008' => 'ROUTE',
47
+ '398201009' => 'START_DATETIME',
48
+ '260864003' =>'FREQUENCY',
49
+ '91723000' => 'ANATOMICAL_STRUCTURE',
50
+ '397898000' => 'STOP_DATETIME',
51
+ '34896006' => 'INCISION_DATETIME',
52
+ '118292001' =>'REMOVAL_DATETIME'
53
+ }
54
+
55
+
56
+ attr_reader :title, :description, :code_list_id, :children_criteria, :derivation_operator , :specific_occurrence, :specific_occurrence_const, :source_data_criteria
57
+ attr_accessor :id, :value, :field_values, :effective_time, :status, :temporal_references, :subset_operators, :definition, :inline_code_list, :negation_code_list_id, :negation, :display_name
58
+
59
+ # Create a new data criteria instance
60
+ # @param [String] id
61
+ # @param [String] title
62
+ # @param [String] display_name
63
+ # @param [String] description
64
+ # @param [String] code_list_id
65
+ # @param [String] negation_code_list_id
66
+ # @param [List<String>] children_criteria (ids of children data criteria)
67
+ # @param [String] derivation_operator
68
+ # @param [String] definition
69
+ # @param [String] status
70
+ # @param [Value|Range|Coded] value
71
+ # @param [Hash<String,Value|Range|Coded>] field_values
72
+ # @param [Range] effective_time
73
+ # @param [Hash<String,[String]>] inline_code_list
74
+ # @param [boolean] negation
75
+ # @param [String] negation_code_list_id
76
+ # @param [List<TemporalReference>] temporal_references
77
+ # @param [List<SubsetOperator>] subset_operators
78
+ # @param [String] specific_occurrence
79
+ # @param [String] specific_occurrence_const
80
+ # @param [String] source_data_criteria (id for the source data criteria, important for specific occurrences)
81
+ def initialize(id, title, display_name, description, code_list_id, children_criteria, derivation_operator, definition, status, value, field_values, effective_time, inline_code_list, negation, negation_code_list_id, temporal_references, subset_operators, specific_occurrence, specific_occurrence_const, source_data_criteria=nil)
82
+
83
+ status = normalize_status(definition, status)
84
+ @settings = HQMF::DataCriteria.get_settings_for_definition(definition, status)
85
+
86
+ @id = id
87
+ @title = title
88
+ @description = description
89
+ @code_list_id = code_list_id
90
+ @negation_code_list_id = negation_code_list_id
91
+ @children_criteria = children_criteria
92
+ @derivation_operator = derivation_operator
93
+ @definition = definition
94
+ @status = status
95
+ @value = value
96
+ @field_values = field_values
97
+ @effective_time = effective_time
98
+ @inline_code_list = inline_code_list
99
+ @negation = negation
100
+ @negation_code_list_id = negation_code_list_id
101
+ @temporal_references = temporal_references
102
+ @subset_operators = subset_operators
103
+ @specific_occurrence = specific_occurrence
104
+ @specific_occurrence_const = specific_occurrence_const
105
+ @source_data_criteria = source_data_criteria || id
106
+ end
107
+
108
+ # create a new data criteria given a category and sub_category. A sub category can either be a status or a sub category
109
+ def self.create_from_category(id, title, description, code_list_id, category, sub_category=nil, negation=false, negation_code_list_id=nil)
110
+ settings = HQMF::DataCriteria.get_settings_for_definition(category, sub_category)
111
+ HQMF::DataCriteria.new(id, title, nil, description, code_list_id, nil, nil, settings['definition'], settings['status'], nil, nil, nil, nil, negation, negation_code_list_id, nil, nil, nil,nil)
112
+ end
113
+
114
+ def standard_category
115
+ @settings['standard_category']
116
+ end
117
+ def qds_data_type
118
+ @settings['qds_data_type']
119
+ end
120
+ def type
121
+ @settings['category'].to_sym
122
+ end
123
+ def property
124
+ @settings['property'].to_sym unless @settings['property'].nil?
125
+ end
126
+ def patient_api_function
127
+ @settings['patient_api_function'].to_sym unless @settings['patient_api_function'].empty?
128
+ end
129
+ def hard_status
130
+ @settings['hard_status']
131
+ end
132
+
133
+ def definition=(definition)
134
+ @definition = definition
135
+ @settings = HQMF::DataCriteria.get_settings_for_definition(@definition, @status)
136
+ end
137
+ def status=(status)
138
+ @status = status
139
+ @settings = HQMF::DataCriteria.get_settings_for_definition(@definition, @status)
140
+ end
141
+
142
+ # Create a new data criteria instance from a JSON hash keyed with symbols
143
+ def self.from_json(id, json)
144
+ title = json["title"] if json["title"]
145
+ display_name = json["display_name"] if json["display_name"]
146
+ description = json["description"] if json["description"]
147
+ code_list_id = json["code_list_id"] if json["code_list_id"]
148
+ children_criteria = json["children_criteria"] if json["children_criteria"]
149
+ derivation_operator = json["derivation_operator"] if json["derivation_operator"]
150
+ definition = json["definition"] if json["definition"]
151
+ status = json["status"] if json["status"]
152
+ value = convert_value(json["value"]) if json["value"]
153
+ field_values = json["field_values"].inject({}){|memo,(k,v)| memo[k.to_s] = convert_value(v); memo} if json["field_values"]
154
+ effective_time = HQMF::Range.from_json(json["effective_time"]) if json["effective_time"]
155
+ inline_code_list = json["inline_code_list"].inject({}){|memo,(k,v)| memo[k.to_s] = v; memo} if json["inline_code_list"]
156
+ negation = json["negation"] || false
157
+ negation_code_list_id = json['negation_code_list_id'] if json['negation_code_list_id']
158
+ temporal_references = json["temporal_references"].map {|reference| HQMF::TemporalReference.from_json(reference)} if json["temporal_references"]
159
+ subset_operators = json["subset_operators"].map {|operator| HQMF::SubsetOperator.from_json(operator)} if json["subset_operators"]
160
+ specific_occurrence = json['specific_occurrence'] if json['specific_occurrence']
161
+ specific_occurrence_const = json['specific_occurrence_const'] if json['specific_occurrence_const']
162
+ source_data_criteria = json['source_data_criteria'] if json['source_data_criteria']
163
+
164
+ HQMF::DataCriteria.new(id, title, display_name, description, code_list_id, children_criteria, derivation_operator, definition, status, value, field_values,
165
+ effective_time, inline_code_list, negation, negation_code_list_id, temporal_references, subset_operators,specific_occurrence,specific_occurrence_const,source_data_criteria)
166
+ end
167
+
168
+ def to_json
169
+ json = base_json
170
+ {self.id.to_s.to_sym => json}
171
+ end
172
+
173
+ def base_json
174
+ x = nil
175
+ 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])
176
+ json[:children_criteria] = @children_criteria unless @children_criteria.nil? || @children_criteria.empty?
177
+ json[:value] = ((@value.is_a? String) ? @value : @value.to_json) if @value
178
+ json[:field_values] = @field_values.inject({}) {|memo,(k,v)| memo[k] = v.to_json; memo} if @field_values
179
+ json[:effective_time] = @effective_time.to_json if @effective_time
180
+ json[:inline_code_list] = @inline_code_list if @inline_code_list
181
+ json[:temporal_references] = x if x = json_array(@temporal_references)
182
+ json[:subset_operators] = x if x = json_array(@subset_operators)
183
+ json
184
+ end
185
+
186
+ def has_temporal(temporal_reference)
187
+ @temporal_references.reduce(false) {|found, item| found ||= item == temporal_reference }
188
+ end
189
+ def has_subset(subset_operator)
190
+ @subset_operators.reduce(false) {|found, item| found ||= item == subset_operator }
191
+ end
192
+
193
+ def self.statuses_by_definition
194
+ settings_file = File.expand_path('../data_criteria.json', __FILE__)
195
+ settings_map = JSON.parse(File.read(settings_file))
196
+ all_defs = (settings_map.map {|key, value| {category: value['category'],definition:value['definition'],status:(value['status'].empty? ? nil : value['status']), sub_category: value['sub_category'],title:value['title']} unless value['not_supported']}).compact
197
+ by_categories = {}
198
+ all_defs.each do |definition|
199
+ by_categories[definition[:category]]||={}
200
+ status = definition[:status]
201
+ def_key = definition[:definition]
202
+ if status.nil? and definition[:sub_category] and !definition[:sub_category].empty?
203
+ status = definition[:sub_category]
204
+ def_key = def_key.gsub("_#{status}",'')
205
+ end
206
+ by_categories[definition[:category]][def_key]||={category:def_key,statuses:[]}
207
+ by_categories[definition[:category]][def_key][:statuses] << status unless status.nil?
208
+ end
209
+ status_by_category = {}
210
+ by_categories.each {|key, value| status_by_category[key] = value.values}
211
+ status_by_category.delete('derived')
212
+ status_by_category.delete('variable')
213
+ status_by_category.delete('measurement_period')
214
+ status_by_category.values.flatten
215
+ end
216
+
217
+ def referenced_data_criteria(document)
218
+ referenced = []
219
+ if (@children_criteria)
220
+ @children_criteria.each do |id|
221
+ dc = document.data_criteria(id)
222
+ referenced << id
223
+ referenced.concat(dc.referenced_data_criteria(document))
224
+ end
225
+ end
226
+ if (@temporal_references)
227
+ @temporal_references.each do |tr|
228
+ id = tr.reference.id
229
+ if (id != HQMF::Document::MEASURE_PERIOD_ID)
230
+ dc = document.data_criteria(id)
231
+ referenced << id
232
+ referenced.concat(dc.referenced_data_criteria(document))
233
+ end
234
+ end
235
+ end
236
+ referenced
237
+ end
238
+
239
+ def self.get_settings_for_definition(definition, status)
240
+ settings_file = File.expand_path('../data_criteria.json', __FILE__)
241
+ settings_map = JSON.parse(File.read(settings_file))
242
+ key = definition + ((status.nil? || status.empty?) ? '' : "_#{status}")
243
+ settings = settings_map[key]
244
+
245
+ raise "data criteria is not supported #{key}" if settings.nil? || settings["not_supported"]
246
+
247
+ settings
248
+ end
249
+
250
+ def self.definition_for_template_id(template_id)
251
+ get_template_id_map()[template_id]
252
+ end
253
+
254
+ def self.template_id_for_definition(definition, status, negation)
255
+ get_template_id_map().key({'definition' => definition, 'status' => status || '', 'negation' => negation})
256
+ end
257
+
258
+ def self.title_for_template_id(template_id)
259
+ value = get_template_id_map()[template_id]
260
+ if value
261
+ settings = self.get_settings_for_definition(value['definition'], value['status'])
262
+ if settings
263
+ settings['title']
264
+ else
265
+ 'Unknown data criteria'
266
+ end
267
+ else
268
+ 'Unknown template id'
269
+ end
270
+ end
271
+
272
+ def self.get_template_id_map
273
+ @@template_id_map ||= read_template_id_map
274
+ @@template_id_map
275
+ end
276
+
277
+ private
278
+
279
+ def self.read_template_id_map
280
+ HealthDataStandards::Util::HQMFTemplateHelper.template_id_map
281
+ end
282
+
283
+ def normalize_status(definition, status)
284
+ return status if status.nil?
285
+ case status.downcase
286
+ when 'completed', 'complete'
287
+ case definition
288
+ when 'diagnosis'
289
+ 'active'
290
+ else
291
+ 'performed'
292
+ end
293
+ when 'order'
294
+ 'ordered'
295
+ else
296
+ status.downcase
297
+ end
298
+ end
299
+
300
+ def self.convert_value(json)
301
+ return nil unless json
302
+ value = nil
303
+ type = json["type"]
304
+ case type
305
+ when 'TS', 'PQ'
306
+ value = HQMF::Value.from_json(json)
307
+ when 'IVL_PQ'
308
+ value = HQMF::Range.from_json(json)
309
+ when 'CD'
310
+ value = HQMF::Coded.from_json(json)
311
+ when 'ANYNonNull'
312
+ value = HQMF::AnyValue.from_json(json)
313
+ else
314
+ raise "Unknown value type [#{type}]"
315
+ end
316
+ value
317
+ end
318
+
319
+
320
+ end
321
+
322
+ end