hqmf-parser 1.0.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 +23 -0
- data/README.md +903 -0
- data/Rakefile +19 -0
- data/VERSION +1 -0
- data/lib/hqmf-generator/hqmf-generator.rb +308 -0
- data/lib/hqmf-model/attribute.rb +35 -0
- data/lib/hqmf-model/data_criteria.rb +322 -0
- data/lib/hqmf-model/document.rb +172 -0
- data/lib/hqmf-model/population_criteria.rb +90 -0
- data/lib/hqmf-model/precondition.rb +85 -0
- data/lib/hqmf-model/types.rb +318 -0
- data/lib/hqmf-model/utilities.rb +52 -0
- data/lib/hqmf-parser.rb +54 -0
- data/lib/hqmf-parser/1.0/attribute.rb +68 -0
- data/lib/hqmf-parser/1.0/comparison.rb +34 -0
- data/lib/hqmf-parser/1.0/data_criteria.rb +105 -0
- data/lib/hqmf-parser/1.0/document.rb +209 -0
- data/lib/hqmf-parser/1.0/expression.rb +52 -0
- data/lib/hqmf-parser/1.0/population_criteria.rb +79 -0
- data/lib/hqmf-parser/1.0/precondition.rb +89 -0
- data/lib/hqmf-parser/1.0/range.rb +65 -0
- data/lib/hqmf-parser/1.0/restriction.rb +157 -0
- data/lib/hqmf-parser/1.0/utilities.rb +41 -0
- data/lib/hqmf-parser/2.0/data_criteria.rb +319 -0
- data/lib/hqmf-parser/2.0/document.rb +165 -0
- data/lib/hqmf-parser/2.0/population_criteria.rb +53 -0
- data/lib/hqmf-parser/2.0/precondition.rb +44 -0
- data/lib/hqmf-parser/2.0/types.rb +223 -0
- data/lib/hqmf-parser/2.0/utilities.rb +30 -0
- data/lib/hqmf-parser/converter/pass1/data_criteria_converter.rb +254 -0
- data/lib/hqmf-parser/converter/pass1/document_converter.rb +183 -0
- data/lib/hqmf-parser/converter/pass1/population_criteria_converter.rb +135 -0
- data/lib/hqmf-parser/converter/pass1/precondition_converter.rb +164 -0
- data/lib/hqmf-parser/converter/pass1/precondition_extractor.rb +159 -0
- data/lib/hqmf-parser/converter/pass1/simple_data_criteria.rb +35 -0
- data/lib/hqmf-parser/converter/pass1/simple_operator.rb +89 -0
- data/lib/hqmf-parser/converter/pass1/simple_population_criteria.rb +10 -0
- data/lib/hqmf-parser/converter/pass1/simple_precondition.rb +63 -0
- data/lib/hqmf-parser/converter/pass1/simple_restriction.rb +64 -0
- data/lib/hqmf-parser/converter/pass2/comparison_converter.rb +91 -0
- data/lib/hqmf-parser/converter/pass2/operator_converter.rb +169 -0
- data/lib/hqmf-parser/converter/pass3/specific_occurrence_converter.rb +86 -0
- data/lib/hqmf-parser/converter/pass3/specific_occurrence_converter_bak.rb +70 -0
- data/lib/hqmf-parser/parser.rb +22 -0
- data/lib/hqmf-parser/value_sets/value_set_parser.rb +206 -0
- data/lib/tasks/coverme.rake +8 -0
- data/lib/tasks/hqmf.rake +141 -0
- data/lib/tasks/value_sets.rake +23 -0
- 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
|