health-data-standards 3.4.6 → 3.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +2 -2
- data/README.md +4 -0
- data/lib/health-data-standards.rb +1 -0
- data/lib/health-data-standards/export/cat_1.rb +4 -4
- data/lib/health-data-standards/export/helper/scooped_view_helper.rb +16 -16
- data/lib/health-data-standards/export/qrda/hqmf-qrda-oids.json +6 -0
- data/lib/health-data-standards/export/view_helper.rb +8 -1
- data/lib/health-data-standards/import/bulk_record_importer.rb +45 -13
- data/lib/health-data-standards/import/bundle/importer.rb +2 -4
- data/lib/health-data-standards/import/cda/provider_importer.rb +2 -2
- data/lib/health-data-standards/import/green_c32/section_importer.rb +2 -2
- data/lib/health-data-standards/import/provider_import_utils.rb +2 -2
- data/lib/health-data-standards/models/cda_identifier.rb +1 -0
- data/lib/health-data-standards/models/cqm/bundle.rb +4 -1
- data/lib/health-data-standards/models/cqm/measure.rb +40 -25
- data/lib/health-data-standards/models/cqm/patient_cache.rb +61 -60
- data/lib/health-data-standards/models/encounter.rb +4 -12
- data/lib/health-data-standards/models/entry.rb +4 -8
- data/lib/health-data-standards/models/facility.rb +1 -0
- data/lib/health-data-standards/models/fulfillment_history.rb +6 -18
- data/lib/health-data-standards/models/guarantor.rb +1 -0
- data/lib/health-data-standards/models/lab_result.rb +2 -6
- data/lib/health-data-standards/models/medical_equipment.rb +2 -7
- data/lib/health-data-standards/models/medication.rb +11 -31
- data/lib/health-data-standards/models/metadata/link_info.rb +1 -0
- data/lib/health-data-standards/models/order_information.rb +5 -13
- data/lib/health-data-standards/models/organization.rb +1 -0
- data/lib/health-data-standards/models/procedure.rb +1 -4
- data/lib/health-data-standards/models/provider.rb +2 -1
- data/lib/health-data-standards/models/provider_performance.rb +1 -0
- data/lib/health-data-standards/models/qrda/legal_authenticator.rb +1 -0
- data/lib/health-data-standards/models/qrda/organization.rb +2 -0
- data/lib/health-data-standards/models/record.rb +7 -7
- data/lib/health-data-standards/models/result_value.rb +1 -0
- data/lib/health-data-standards/models/svs/concept.rb +1 -0
- data/lib/health-data-standards/models/svs/value_set.rb +1 -0
- data/lib/health-data-standards/models/telecom.rb +1 -0
- data/lib/health-data-standards/models/transfer.rb +1 -0
- data/lib/health-data-standards/tasks/bundle.rake +3 -3
- data/lib/health-data-standards/util/vs_api.rb +2 -2
- data/lib/hqmf-generator/attribute.xml.erb +9 -11
- data/lib/hqmf-generator/characteristic_criteria.xml.erb +5 -5
- data/lib/hqmf-generator/code.xml.erb +6 -2
- data/lib/hqmf-generator/condition_criteria.xml.erb +4 -5
- data/lib/hqmf-generator/derivation.xml.erb +6 -6
- data/lib/hqmf-generator/description.xml.erb +1 -1
- data/lib/hqmf-generator/document.xml.erb +46 -11
- data/lib/hqmf-generator/encounter_criteria.xml.erb +4 -5
- data/lib/hqmf-generator/field.xml.erb +13 -1
- data/lib/hqmf-generator/grouper_criteria.xml.erb +17 -0
- data/lib/hqmf-generator/hqmf-generator.rb +75 -8
- data/lib/hqmf-generator/local_variable.xml.erb +1 -0
- data/lib/hqmf-generator/measure_observation_definition.xml.erb +25 -0
- data/lib/hqmf-generator/observation_criteria.xml.erb +4 -5
- data/lib/hqmf-generator/population_criteria.xml.erb +2 -3
- data/lib/hqmf-generator/precondition.xml.erb +2 -2
- data/lib/hqmf-generator/precondition_cv.xml.erb +8 -0
- data/lib/hqmf-generator/procedure_criteria.xml.erb +4 -5
- data/lib/hqmf-generator/reference.xml.erb +2 -2
- data/lib/hqmf-generator/source.xml.erb +2 -2
- data/lib/hqmf-generator/specific_occurrence.xml.erb +4 -5
- data/lib/hqmf-generator/subset.xml.erb +16 -3
- data/lib/hqmf-generator/substance_criteria.xml.erb +4 -5
- data/lib/hqmf-generator/supply_criteria.xml.erb +4 -5
- data/lib/hqmf-generator/temporal_relationship.xml.erb +1 -1
- data/lib/hqmf-generator/value.xml.erb +35 -9
- data/lib/hqmf-generator/variable_criteria.xml.erb +2 -3
- data/lib/hqmf-model/attribute.rb +36 -8
- data/lib/hqmf-model/data_criteria.json +38 -204
- data/lib/hqmf-model/data_criteria.rb +40 -16
- data/lib/hqmf-model/document.rb +61 -2
- data/lib/hqmf-model/population_criteria.rb +11 -7
- data/lib/hqmf-model/precondition.rb +1 -1
- data/lib/hqmf-model/types.rb +91 -8
- data/lib/hqmf-parser/1.0/attribute.rb +55 -2
- data/lib/hqmf-parser/1.0/document.rb +10 -23
- data/lib/hqmf-parser/1.0/population_criteria.rb +2 -2
- data/lib/hqmf-parser/1.0/range.rb +0 -1
- data/lib/hqmf-parser/2.0/data_criteria.rb +90 -21
- data/lib/hqmf-parser/2.0/document.rb +122 -7
- data/lib/hqmf-parser/2.0/population_criteria.rb +18 -6
- data/lib/hqmf-parser/2.0/precondition.rb +4 -1
- data/lib/hqmf-parser/2.0/types.rb +36 -15
- data/lib/hqmf-parser/converter/pass1/document_converter.rb +4 -56
- data/lib/hqmf-parser/converter/pass1/population_criteria_converter.rb +24 -8
- data/lib/hqmf-parser/converter/pass1/precondition_extractor.rb +15 -2
- data/lib/hqmf-parser/converter/pass2/comparison_converter.rb +1 -1
- data/lib/hqmf-parser/parser.rb +64 -41
- data/templates/cat1/_2.16.840.1.113883.10.20.22.4.85.cat1.erb +0 -1
- data/templates/cat1/_address.cat1.erb +9 -0
- data/templates/cat1/_author.cat1.erb +28 -0
- data/templates/cat1/_id.cat1.erb +1 -0
- data/templates/cat1/_organization.cat1.erb +8 -0
- data/templates/cat1/_patient_data.cat1.erb +0 -3
- data/templates/cat1/_telecom.cat1.erb +1 -0
- data/templates/cat1/show.cat1.erb +96 -58
- metadata +115 -66
- checksums.yaml +0 -7
@@ -61,8 +61,61 @@ module HQMF1
|
|
61
61
|
end
|
62
62
|
|
63
63
|
def to_json
|
64
|
-
{self.const_name => build_hash(self, [:code,:value,:unit,:name,:id])}
|
64
|
+
json = {self.const_name => build_hash(self, [:code,:value,:unit,:name,:id])}
|
65
|
+
extend_json_for_enhanced_model(json)
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def extend_json_for_enhanced_model(json)
|
71
|
+
if (@entry.at_xpath('./cda:id'))
|
72
|
+
json[self.const_name][:id_obj] = id_xml_to_json('./cda:id')
|
73
|
+
end
|
74
|
+
|
75
|
+
if (@entry.at_xpath('./cda:code'))
|
76
|
+
json[self.const_name][:code_obj] = code_xml_to_json('./cda:code')
|
77
|
+
end
|
78
|
+
|
79
|
+
if (@entry.at_xpath('./cda:value'))
|
80
|
+
value_hash = { :type => attr_val('./cda:value/@xsi:type')}
|
81
|
+
case value_hash[:type]
|
82
|
+
when 'II'
|
83
|
+
value_hash.merge!(id_xml_to_json('./cda:value'))
|
84
|
+
when 'CD'
|
85
|
+
value_hash.merge!(code_xml_to_json('./cda:value'))
|
86
|
+
when 'ED'
|
87
|
+
value_hash[:value] = @entry.at_xpath('./cda:value').inner_text
|
88
|
+
value_hash[:media_type] = attr_val('./cda:value/@mediaType')
|
89
|
+
else
|
90
|
+
if (attr_val('./cda:value/@value'))
|
91
|
+
value_hash.merge!(HQMF1::Value.new(@entry.at_xpath('./cda:value')).to_json)
|
92
|
+
value_hash[:expression] = @entry.at_xpath('./cda:value/cda:expression').try(:inner_text).try(:strip)
|
93
|
+
else
|
94
|
+
value_hash[:value] = @entry.at_xpath('./cda:value').inner_text.try(:strip)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
json[self.const_name][:value_obj] = value_hash
|
98
|
+
end
|
99
|
+
json
|
100
|
+
end
|
101
|
+
|
102
|
+
def id_xml_to_json(entry_xpath)
|
103
|
+
{
|
104
|
+
:root => attr_val("#{entry_xpath}/@root"),
|
105
|
+
:extension => attr_val("#{entry_xpath}/@extension")
|
106
|
+
}
|
107
|
+
end
|
108
|
+
|
109
|
+
def code_xml_to_json(entry_xpath)
|
110
|
+
{
|
111
|
+
:type => attr_val("#{entry_xpath}/@xsi:type") || 'CD',
|
112
|
+
:system => attr_val("#{entry_xpath}/@codeSystem"),
|
113
|
+
:code => attr_val("#{entry_xpath}/@code"),
|
114
|
+
:code_list_id => attr_val("#{entry_xpath}/@valueSet"),
|
115
|
+
:title => attr_val("#{entry_xpath}/@displayName"),
|
116
|
+
:null_flavor => attr_val("#{entry_xpath}/@nullFlavor"),
|
117
|
+
:original_text => @entry.at_xpath("#{entry_xpath}/cda:originalText").try(:inner_text)
|
118
|
+
}
|
65
119
|
end
|
66
|
-
|
67
120
|
end
|
68
121
|
end
|
@@ -26,36 +26,22 @@ module HQMF1
|
|
26
26
|
@attributes = @doc.xpath('//cda:subjectOf/cda:measureAttribute').collect do |attr|
|
27
27
|
Attribute.new(attr)
|
28
28
|
end
|
29
|
-
@population_criteria = @doc.xpath('//cda:section[cda:code/@code="57026-7"]/cda:entry').collect do |
|
30
|
-
PopulationCriteria.new(
|
29
|
+
@population_criteria = @doc.xpath('//cda:section[cda:code/@code="57026-7"]/cda:entry').collect do |criteria|
|
30
|
+
PopulationCriteria.new(criteria, self)
|
31
31
|
end
|
32
|
-
observations = @doc.xpath('//cda:section[cda:code/@code="57027-5"]/cda:entry').collect do |
|
33
|
-
Observation.new(
|
32
|
+
observations = @doc.xpath('//cda:section[cda:code/@code="57027-5"]/cda:entry').collect do |observation|
|
33
|
+
Observation.new(observation, self)
|
34
34
|
end
|
35
35
|
@population_criteria.concat(observations)
|
36
36
|
|
37
|
-
@stratification = @doc.xpath('//cda:section[cda:code/@code="69669-0"]/cda:entry').collect do |
|
38
|
-
PopulationCriteria.new(
|
37
|
+
@stratification = @doc.xpath('//cda:section[cda:code/@code="69669-0"]/cda:entry').collect do |strat|
|
38
|
+
PopulationCriteria.new(strat, self)
|
39
39
|
end
|
40
40
|
|
41
41
|
if (@stratification and !@stratification.empty?)
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
@stratification.each do |stratification|
|
46
|
-
new_population = HQMF1::PopulationCriteria.new(population.entry, population.doc)
|
47
|
-
new_population.hqmf_id = new_population.id
|
48
|
-
new_population.stratification_id = stratification.id
|
49
|
-
new_population.id = "#{new_population.id}_#{stratification.id}"
|
50
|
-
ids = stratification.preconditions.map(&:id)
|
51
|
-
new_population.preconditions.delete_if {|precondition| ids.include? precondition.id}
|
52
|
-
new_population.preconditions.concat(stratification.preconditions)
|
53
|
-
new_population.preconditions.rotate!(-1*stratification.preconditions.size)
|
54
|
-
@population_criteria << new_population
|
55
|
-
end
|
56
|
-
|
42
|
+
@stratification.each do |stratification|
|
43
|
+
@population_criteria << stratification
|
57
44
|
end
|
58
|
-
|
59
45
|
end
|
60
46
|
|
61
47
|
@hqmf_set_id = @doc.at_xpath('//cda:setId/@root').value.upcase
|
@@ -136,7 +122,7 @@ module HQMF1
|
|
136
122
|
# Parse an XML document from the supplied contents
|
137
123
|
# @return [Nokogiri::XML::Document]
|
138
124
|
def self.parse(hqmf_contents)
|
139
|
-
doc = Nokogiri::XML(hqmf_contents)
|
125
|
+
doc = hqmf_contents.kind_of?(Nokogiri::XML::Document) ? hqmf_contents : Nokogiri::XML(hqmf_contents)
|
140
126
|
doc.root.add_namespace_definition('cda', 'urn:hl7-org:v3')
|
141
127
|
doc
|
142
128
|
end
|
@@ -169,6 +155,7 @@ module HQMF1
|
|
169
155
|
json[:data_criteria].merge! criteria_json
|
170
156
|
end
|
171
157
|
|
158
|
+
# TODO: Investigate why we never use json[:attributes] in the model
|
172
159
|
json[:metadata] = {}
|
173
160
|
json[:attributes] = {}
|
174
161
|
@attributes.each do |attribute|
|
@@ -26,10 +26,10 @@ module HQMF1
|
|
26
26
|
# Get the code for the population criteria
|
27
27
|
# @return [String] the code (e.g. IPP, DEMON, NUMER, DENEX, DENEXCEP)
|
28
28
|
def code
|
29
|
-
value = attr_val('cda:observation/cda:value/@code')
|
29
|
+
value = attr_val('cda:observation/cda:value/@code') || HQMF::PopulationCriteria::STRAT
|
30
30
|
# exclusion population criteria has id of DENOM with actionNegationInd of true
|
31
31
|
# special case this to simply handling
|
32
|
-
if attr_val('cda:observation/@actionNegationInd')=='true'
|
32
|
+
if attr_val('cda:observation/@actionNegationInd')=='true' && value == HQMF::PopulationCriteria::DENOM
|
33
33
|
value = HQMF::PopulationCriteria::DENEX
|
34
34
|
end
|
35
35
|
value.upcase
|
@@ -8,14 +8,26 @@ module HQMF2
|
|
8
8
|
attr_reader :temporal_references, :subset_operators, :children_criteria
|
9
9
|
attr_reader :derivation_operator, :negation, :negation_code_list_id, :description
|
10
10
|
attr_reader :field_values, :source_data_criteria, :specific_occurrence_const
|
11
|
-
attr_reader :specific_occurrence, :is_source_data_criteria
|
12
|
-
|
11
|
+
attr_reader :specific_occurrence, :is_source_data_criteria, :comments
|
12
|
+
|
13
|
+
VARIABLE_TEMPLATE = "0.1.2.3.4.5.6.7.8.9.1"
|
14
|
+
SATISFIES_ANY_TEMPLATE = "0.1.2.3.4.5.6.7.8.9.2"
|
15
|
+
SATISFIES_ALL_TEMPLATE = "0.1.2.3.4.5.6.7.8.9.3"
|
16
|
+
|
17
|
+
CONJUNCTION_CODE_TO_DERIVATION_OP = {
|
18
|
+
'OR' => 'UNION',
|
19
|
+
'AND' => 'XPRODUCT'
|
20
|
+
}
|
21
|
+
|
22
|
+
CRITERIA_GLOB = "*[substring(name(),string-length(name())-7) = \'Criteria\']"
|
23
|
+
|
13
24
|
# Create a new instance based on the supplied HQMF entry
|
14
25
|
# @param [Nokogiri::XML::Element] entry the parsed HQMF entry
|
15
26
|
def initialize(entry)
|
16
27
|
@entry = entry
|
28
|
+
@local_variable_name = extract_local_variable_name
|
17
29
|
@status = attr_val('./*/cda:statusCode/@code')
|
18
|
-
@description = attr_val(
|
30
|
+
@description = attr_val("./#{CRITERIA_GLOB}/cda:text/@value")
|
19
31
|
extract_negation()
|
20
32
|
extract_specific_or_source()
|
21
33
|
@effective_time = extract_effective_time
|
@@ -24,10 +36,12 @@ module HQMF2
|
|
24
36
|
@field_values = extract_field_values
|
25
37
|
@subset_operators = extract_subset_operators
|
26
38
|
@children_criteria = extract_child_criteria
|
27
|
-
@id_xpath = './*/cda:id
|
39
|
+
@id_xpath = './*/cda:id/@extension'
|
28
40
|
@code_list_xpath = './*/cda:code'
|
29
41
|
@value_xpath = './*/cda:value'
|
30
|
-
|
42
|
+
@comments = @entry.xpath("./#{CRITERIA_GLOB}/cda:text/cda:xml/cda:qdmUserComments/cda:item/text()", HQMF2::Document::NAMESPACES).map{ |v| v.content }
|
43
|
+
@variable = false
|
44
|
+
|
31
45
|
# Try to determine what kind of data criteria we are dealing with
|
32
46
|
# First we look for a template id and if we find one just use the definition
|
33
47
|
# status and negation associated with that
|
@@ -46,9 +60,11 @@ module HQMF2
|
|
46
60
|
# criteria
|
47
61
|
# Assumes @definition and @status are already set
|
48
62
|
case @definition
|
63
|
+
when 'transfer_to', 'transfer_from'
|
64
|
+
@code_list_xpath = './cda:observationCriteria/cda:value'
|
49
65
|
when 'diagnosis', 'diagnosis_family_history'
|
50
66
|
@code_list_xpath = './cda:observationCriteria/cda:value'
|
51
|
-
when 'risk_category_assessment', 'procedure_result', 'laboratory_test', 'diagnostic_study_result', 'functional_status_result', 'intervention_result'
|
67
|
+
when 'physical_exam', 'risk_category_assessment', 'procedure_result', 'laboratory_test', 'diagnostic_study_result', 'functional_status_result', 'intervention_result'
|
52
68
|
@value = extract_value
|
53
69
|
when 'medication'
|
54
70
|
case @status
|
@@ -64,7 +80,19 @@ module HQMF2
|
|
64
80
|
end
|
65
81
|
end
|
66
82
|
|
83
|
+
def extract_local_variable_name
|
84
|
+
lvn = @entry.at_xpath("./cda:localVariableName")
|
85
|
+
lvn["value"] if lvn
|
86
|
+
end
|
87
|
+
|
67
88
|
def extract_type_from_definition
|
89
|
+
if @entry.at_xpath("./cda:grouperCriteria")
|
90
|
+
if @local_variable_name && @local_variable_name.match(/qdm_/)
|
91
|
+
@variable = true
|
92
|
+
end
|
93
|
+
@definition = 'derived'
|
94
|
+
return
|
95
|
+
end
|
68
96
|
# See if we can find a match for the entry definition value and status.
|
69
97
|
entry_type = attr_val('./*/cda:definition/*/cda:id/@extension')
|
70
98
|
begin
|
@@ -110,16 +138,33 @@ module HQMF2
|
|
110
138
|
if template_ids.include?(HQMF::DataCriteria::SOURCE_DATA_CRITERIA_TEMPLATE_ID)
|
111
139
|
@is_source_data_criteria = true
|
112
140
|
end
|
141
|
+
found = false
|
113
142
|
template_ids.each do |template_id|
|
114
143
|
defs = HQMF::DataCriteria.definition_for_template_id(template_id)
|
144
|
+
|
115
145
|
if defs
|
116
146
|
@definition = defs['definition']
|
117
147
|
@status = defs['status'].length > 0 ? defs['status'] : nil
|
118
148
|
@negation = defs['negation']
|
149
|
+
found ||= true
|
150
|
+
elsif template_id == VARIABLE_TEMPLATE
|
151
|
+
@derivation_operator = HQMF::DataCriteria::INTERSECT if @derivation_operator == HQMF::DataCriteria::XPRODUCT
|
152
|
+
@definition ||= 'derived'
|
153
|
+
@negation = false
|
154
|
+
@variable = true
|
155
|
+
found ||= true
|
156
|
+
elsif template_id == SATISFIES_ANY_TEMPLATE
|
157
|
+
@definition = HQMF::DataCriteria::SATISFIES_ANY
|
158
|
+
@negation = false
|
119
159
|
return true
|
160
|
+
elsif template_id == SATISFIES_ALL_TEMPLATE
|
161
|
+
@definition = HQMF::DataCriteria::SATISFIES_ALL
|
162
|
+
@derivation_operator = HQMF::DataCriteria::INTERSECT
|
163
|
+
@negation = false
|
164
|
+
found ||= true
|
120
165
|
end
|
121
166
|
end
|
122
|
-
|
167
|
+
found
|
123
168
|
end
|
124
169
|
|
125
170
|
def to_s
|
@@ -141,7 +186,12 @@ module HQMF2
|
|
141
186
|
# Get the title of the criteria, provides a human readable description
|
142
187
|
# @return [String] the title of this data criteria
|
143
188
|
def title
|
144
|
-
attr_val("#{@code_list_xpath}/cda:displayName/@value")
|
189
|
+
dispValue = attr_val("#{@code_list_xpath}/cda:displayName/@value")
|
190
|
+
desc = nil
|
191
|
+
if @description && (@description.include? ":")
|
192
|
+
desc = @description.match(/.*:\s+(.+)/)[1]
|
193
|
+
end
|
194
|
+
dispValue || desc || id
|
145
195
|
end
|
146
196
|
|
147
197
|
# Get the code list OID of the criteria, used as an index to the code list database
|
@@ -166,6 +216,7 @@ module HQMF2
|
|
166
216
|
end
|
167
217
|
|
168
218
|
def to_model
|
219
|
+
|
169
220
|
mv = value ? value.to_model : nil
|
170
221
|
met = effective_time ? effective_time.to_model : nil
|
171
222
|
mtr = temporal_references.collect {|ref| ref.to_model}
|
@@ -174,11 +225,23 @@ module HQMF2
|
|
174
225
|
@field_values.each_pair do |id, val|
|
175
226
|
field_values[id] = val.to_model
|
176
227
|
end
|
177
|
-
|
228
|
+
|
229
|
+
# Model transfers as a field
|
230
|
+
if ['transfer_to', 'transfer_from'].include? @definition
|
231
|
+
field_values ||= {}
|
232
|
+
field_code_list_id = @code_list_id
|
233
|
+
if !field_code_list_id
|
234
|
+
field_code_list_id = attr_val("./#{CRITERIA_GLOB}/cda:outboundRelationship/#{CRITERIA_GLOB}/cda:value/@valueSet")
|
235
|
+
end
|
236
|
+
field_values[@definition.upcase] = HQMF::Coded.for_code_list(field_code_list_id, title)
|
237
|
+
end
|
238
|
+
|
239
|
+
field_values = nil if field_values.empty?
|
240
|
+
|
178
241
|
HQMF::DataCriteria.new(id, title, nil, description, code_list_id, children_criteria,
|
179
242
|
derivation_operator, @definition, status, mv, field_values, met, inline_code_list,
|
180
243
|
@negation, @negation_code_list_id, mtr, mso, @specific_occurrence,
|
181
|
-
@specific_occurrence_const, @source_data_criteria)
|
244
|
+
@specific_occurrence_const, @source_data_criteria, @comments, @variable)
|
182
245
|
end
|
183
246
|
|
184
247
|
private
|
@@ -194,7 +257,7 @@ module HQMF2
|
|
194
257
|
end
|
195
258
|
|
196
259
|
def extract_child_criteria
|
197
|
-
@entry.xpath(
|
260
|
+
@entry.xpath("./*/cda:outboundRelationship[@typeCode='COMP']/cda:criteriaReference/cda:id", HQMF2::Document::NAMESPACES).collect do |ref|
|
198
261
|
Reference.new(ref).id
|
199
262
|
end.compact
|
200
263
|
end
|
@@ -215,11 +278,11 @@ module HQMF2
|
|
215
278
|
end
|
216
279
|
|
217
280
|
def extract_derivation_operator
|
218
|
-
|
219
|
-
|
281
|
+
codes = @entry.xpath("./*/cda:outboundRelationship[@typeCode='COMP']/cda:conjunctionCode/@code", HQMF2::Document::NAMESPACES)
|
282
|
+
codes.inject(nil) do | d_op, code |
|
283
|
+
raise "More than one derivation operator in data criteria" if d_op && d_op != CONJUNCTION_CODE_TO_DERIVATION_OP[code.value]
|
284
|
+
CONJUNCTION_CODE_TO_DERIVATION_OP[code.value]
|
220
285
|
end
|
221
|
-
raise "More than one derivation operator in data criteria" if derivation_operators.size>1
|
222
|
-
derivation_operators.first ? derivation_operators.first.type : nil
|
223
286
|
end
|
224
287
|
|
225
288
|
def extract_subset_operators
|
@@ -229,14 +292,18 @@ module HQMF2
|
|
229
292
|
end
|
230
293
|
|
231
294
|
def extract_specific_or_source
|
232
|
-
specific_def = @entry.at_xpath('./*/cda:outboundRelationship[
|
295
|
+
specific_def = @entry.at_xpath('./*/cda:outboundRelationship[@typeCode="OCCR"]', HQMF2::Document::NAMESPACES)
|
233
296
|
source_def = @entry.at_xpath('./*/cda:outboundRelationship[cda:subsetCode/@code="SOURCE"]', HQMF2::Document::NAMESPACES)
|
234
297
|
if specific_def
|
235
|
-
@source_data_criteria = HQMF2::Utilities.attr_val(specific_def, './cda:
|
298
|
+
@source_data_criteria = HQMF2::Utilities.attr_val(specific_def, './cda:criteriaReference/cda:id/@extension')
|
236
299
|
@specific_occurrence_const = HQMF2::Utilities.attr_val(specific_def, './cda:localVariableName/@controlInformationRoot')
|
237
300
|
@specific_occurrence = HQMF2::Utilities.attr_val(specific_def, './cda:localVariableName/@controlInformationExtension')
|
301
|
+
if !@specific_occurrence
|
302
|
+
@specific_occurrence = "A"
|
303
|
+
@specific_occurrence_const = @source_data_criteria.upcase
|
304
|
+
end
|
238
305
|
elsif source_def
|
239
|
-
@source_data_criteria = HQMF2::Utilities.attr_val(source_def, './cda:
|
306
|
+
@source_data_criteria = HQMF2::Utilities.attr_val(source_def, './cda:criteriaReference/cda:id/@extension')
|
240
307
|
end
|
241
308
|
end
|
242
309
|
|
@@ -247,14 +314,14 @@ module HQMF2
|
|
247
314
|
code = HQMF2::Utilities.attr_val(field, './*/cda:code/@code')
|
248
315
|
code_id = HQMF::DataCriteria::VALUE_FIELDS[code]
|
249
316
|
value = DataCriteria.parse_value(field, './*/cda:value')
|
250
|
-
fields[code_id] = value
|
317
|
+
fields[code_id] = value if value && code_id
|
251
318
|
end
|
252
319
|
# special case for facility location which uses a very different structure
|
253
320
|
@entry.xpath('./*/cda:outboundRelationship[*/cda:participation]', HQMF2::Document::NAMESPACES).each do |field|
|
254
321
|
code = HQMF2::Utilities.attr_val(field, './*/cda:participation/cda:role/@classCode')
|
255
322
|
code_id = HQMF::DataCriteria::VALUE_FIELDS[code]
|
256
323
|
value = Coded.new(field.at_xpath('./*/cda:participation/cda:role/cda:code', HQMF2::Document::NAMESPACES))
|
257
|
-
fields[code_id] = value
|
324
|
+
fields[code_id] = value if value && code_id
|
258
325
|
end
|
259
326
|
fields
|
260
327
|
end
|
@@ -277,6 +344,8 @@ module HQMF2
|
|
277
344
|
if value_type_def
|
278
345
|
value_type = value_type_def.value
|
279
346
|
case value_type
|
347
|
+
when 'PQ'
|
348
|
+
value = Value.new(value_def, 'PQ', true)
|
280
349
|
when 'TS'
|
281
350
|
value = Value.new(value_def)
|
282
351
|
when 'IVL_PQ', 'IVL_INT'
|
@@ -316,4 +385,4 @@ module HQMF2
|
|
316
385
|
|
317
386
|
end
|
318
387
|
|
319
|
-
end
|
388
|
+
end
|
@@ -21,11 +21,64 @@ module HQMF2
|
|
21
21
|
|
22
22
|
# Extract measure attributes
|
23
23
|
@attributes = @doc.xpath('/cda:QualityMeasureDocument/cda:subjectOf/cda:measureAttribute', NAMESPACES).collect do |attribute|
|
24
|
-
id = attribute.at_xpath('./cda:id/@
|
24
|
+
id = attribute.at_xpath('./cda:id/@root', NAMESPACES).try(:value)
|
25
25
|
code = attribute.at_xpath('./cda:code/@code', NAMESPACES).try(:value)
|
26
26
|
name = attribute.at_xpath('./cda:code/cda:displayName/@value', NAMESPACES).try(:value)
|
27
27
|
value = attribute.at_xpath('./cda:value/@value', NAMESPACES).try(:value)
|
28
|
-
|
28
|
+
|
29
|
+
id_obj = nil
|
30
|
+
if attribute.at_xpath('./cda:id', NAMESPACES)
|
31
|
+
id_obj = HQMF::Identifier.new(attribute.at_xpath('./cda:id/@xsi:type', NAMESPACES).try(:value), id, attribute.at_xpath('./cda:id/@extension', NAMESPACES).try(:value))
|
32
|
+
end
|
33
|
+
|
34
|
+
code_obj = nil;
|
35
|
+
if attribute.at_xpath('./cda:code', NAMESPACES)
|
36
|
+
nullFlavor = attribute.at_xpath('./cda:code/@nullFlavor', NAMESPACES).try(:value)
|
37
|
+
oText = attribute.at_xpath('./cda:code/cda:originalText', NAMESPACES) ? attribute.at_xpath('./cda:code/cda:originalText/@value', NAMESPACES).try(:value) : nil
|
38
|
+
code_obj = HQMF::Coded.new(attribute.at_xpath('./cda:code/@xsi:type', NAMESPACES).try(:value) || 'CD',
|
39
|
+
attribute.at_xpath('./cda:code/@codeSystem', NAMESPACES).try(:value),
|
40
|
+
code,
|
41
|
+
attribute.at_xpath('./cda:code/@valueSet', NAMESPACES).try(:value),
|
42
|
+
name,
|
43
|
+
nullFlavor,
|
44
|
+
oText)
|
45
|
+
|
46
|
+
|
47
|
+
# Mapping for nil values to align with 1.0 parsing
|
48
|
+
if code == nil
|
49
|
+
code = nullFlavor
|
50
|
+
end
|
51
|
+
|
52
|
+
if name == nil
|
53
|
+
name = oText
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
value_obj = nil
|
59
|
+
if attribute.at_xpath('./cda:value', NAMESPACES)
|
60
|
+
type = attribute.at_xpath('./cda:value/@xsi:type', NAMESPACES).try(:value)
|
61
|
+
case type
|
62
|
+
when 'II'
|
63
|
+
value_obj = HQMF::Identifier.new(type, attribute.at_xpath('./cda:value/@root', NAMESPACES).try(:value), attribute.at_xpath('./cda:value/@extension', NAMESPACES).try(:value))
|
64
|
+
if value == nil
|
65
|
+
value = attribute.at_xpath('./cda:value/@extension', NAMESPACES).try(:value)
|
66
|
+
end
|
67
|
+
when 'ED'
|
68
|
+
value_obj = HQMF::ED.new(type, value, attribute.at_xpath('./cda:value/@mediaType', NAMESPACES).try(:value))
|
69
|
+
when 'CD'
|
70
|
+
value_obj = HQMF::Coded.new('CD', attribute.at_xpath('./cda:value/@codeSystem', NAMESPACES).try(:value), attribute.at_xpath('./cda:value/@code', NAMESPACES).try(:value), attribute.at_xpath('./cda:value/@valueSet', NAMESPACES).try(:value), attribute.at_xpath('./cda:value/cda:displayName/@value', NAMESPACES).try(:value))
|
71
|
+
else
|
72
|
+
value_obj = value.present? ? HQMF::GenericValueContainer.new(type, value) : HQMF::AnyValue.new(type)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Handle the cms_id
|
77
|
+
if name == "eMeasure Identifier"
|
78
|
+
@cms_id = "CMS#{value}v#{@hqmf_version_number}"
|
79
|
+
end
|
80
|
+
|
81
|
+
HQMF::Attribute.new(id, code, value, nil, name, id_obj, code_obj, value_obj)
|
29
82
|
end
|
30
83
|
|
31
84
|
# Extract the data criteria
|
@@ -54,11 +107,13 @@ module HQMF2
|
|
54
107
|
population['stratification'] = stratifier_id_def.value if stratifier_id_def
|
55
108
|
|
56
109
|
{
|
57
|
-
HQMF::PopulationCriteria::IPP => '
|
110
|
+
HQMF::PopulationCriteria::IPP => 'initialPopulationCriteria',
|
58
111
|
HQMF::PopulationCriteria::DENOM => 'denominatorCriteria',
|
59
112
|
HQMF::PopulationCriteria::NUMER => 'numeratorCriteria',
|
60
113
|
HQMF::PopulationCriteria::DENEXCEP => 'denominatorExceptionCriteria',
|
61
|
-
HQMF::PopulationCriteria::DENEX => 'denominatorExclusionCriteria'
|
114
|
+
HQMF::PopulationCriteria::DENEX => 'denominatorExclusionCriteria',
|
115
|
+
HQMF::PopulationCriteria::STRAT => 'stratifierCriteria',
|
116
|
+
HQMF::PopulationCriteria::MSRPOPL => 'measurePopulationCriteria'
|
62
117
|
}.each_pair do |criteria_id, criteria_element_name|
|
63
118
|
criteria_def = population_def.at_xpath("cda:component[cda:#{criteria_element_name}]", NAMESPACES)
|
64
119
|
|
@@ -95,12 +150,50 @@ module HQMF2
|
|
95
150
|
end
|
96
151
|
end
|
97
152
|
end
|
153
|
+
|
154
|
+
|
98
155
|
id_def = population_def.at_xpath('cda:id/@extension', NAMESPACES)
|
99
156
|
population['id'] = id_def ? id_def.value : "Population#{population_index}"
|
100
157
|
title_def = population_def.at_xpath('cda:title/@value', NAMESPACES)
|
101
158
|
population['title'] = title_def ? title_def.value : "Population #{population_index}"
|
159
|
+
observation_section = @doc.xpath('cda:QualityMeasureDocument/cda:component/cda:measureObservationsSection', NAMESPACES)
|
160
|
+
if !observation_section.empty?
|
161
|
+
population['OBSERV'] = 'OBSERV'
|
162
|
+
end
|
102
163
|
@populations << population
|
103
164
|
end
|
165
|
+
|
166
|
+
|
167
|
+
#look for observation data in separate section but create a population for it if it exists
|
168
|
+
observation_section = @doc.xpath('cda:QualityMeasureDocument/cda:component/cda:measureObservationsSection', NAMESPACES)
|
169
|
+
if !observation_section.empty?
|
170
|
+
observation_section.xpath("cda:definition",NAMESPACES).each do |criteria_def|
|
171
|
+
criteria_id = "OBSERV"
|
172
|
+
population = {}
|
173
|
+
criteria = PopulationCriteria.new(criteria_def, self)
|
174
|
+
criteria.type="OBSERV"
|
175
|
+
# this section constructs a human readable id. The first IPP will be IPP, the second will be IPP_1, etc. This allows the populations to be
|
176
|
+
# more readable. The alternative would be to have the hqmf ids in the populations, which would work, but is difficult to read the populations.
|
177
|
+
if ids_by_hqmf_id["#{criteria.hqmf_id}"]
|
178
|
+
criteria.create_human_readable_id(ids_by_hqmf_id[criteria.hqmf_id])
|
179
|
+
else
|
180
|
+
if population_counters[criteria_id]
|
181
|
+
population_counters[criteria_id] += 1
|
182
|
+
criteria.create_human_readable_id("#{criteria_id}_#{population_counters[criteria_id]}")
|
183
|
+
else
|
184
|
+
population_counters[criteria_id] = 0
|
185
|
+
criteria.create_human_readable_id(criteria_id)
|
186
|
+
end
|
187
|
+
ids_by_hqmf_id["#{criteria.hqmf_id}"] = criteria.id
|
188
|
+
end
|
189
|
+
|
190
|
+
@population_criteria << criteria
|
191
|
+
|
192
|
+
population[criteria_id] = criteria.id
|
193
|
+
@populations << population
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
104
197
|
end
|
105
198
|
|
106
199
|
# Get the title of the measure
|
@@ -142,19 +235,41 @@ module HQMF2
|
|
142
235
|
find(@data_criteria, :id, id)
|
143
236
|
end
|
144
237
|
|
145
|
-
|
238
|
+
# Parse an XML document from the supplied contents
|
146
239
|
# @return [Nokogiri::XML::Document]
|
147
240
|
def self.parse(hqmf_contents)
|
148
|
-
doc = Nokogiri::XML(hqmf_contents)
|
241
|
+
doc = hqmf_contents.kind_of?(Nokogiri::XML::Document) ? hqmf_contents : Nokogiri::XML(hqmf_contents)
|
242
|
+
doc.root.add_namespace_definition('cda', 'urn:hl7-org:v3')
|
149
243
|
doc
|
150
244
|
end
|
151
245
|
|
152
246
|
def to_model
|
247
|
+
|
153
248
|
dcs = all_data_criteria.collect {|dc| dc.to_model}
|
154
249
|
pcs = all_population_criteria.collect {|pc| pc.to_model}
|
155
250
|
sdc = source_data_criteria.collect{|dc| dc.to_model}
|
156
|
-
|
251
|
+
dcs = update_data_criteria(dcs, sdc)
|
252
|
+
HQMF::Document.new(id, id, hqmf_set_id, hqmf_version_number, @cms_id, title, description, pcs, dcs, sdc, attributes, measure_period.to_model, populations)
|
253
|
+
end
|
254
|
+
|
255
|
+
# Update the data criteria to handle variables properly
|
256
|
+
def update_data_criteria(data_criteria, source_data_criteria)
|
257
|
+
# step through each criteria and look for groupers (type derived) with one child
|
258
|
+
data_criteria.map do |criteria|
|
259
|
+
if criteria.type == "derived".to_sym && criteria.children_criteria.length == 1
|
260
|
+
source_data_criteria.each do |source_criteria|
|
261
|
+
if source_criteria.title == criteria.children_criteria[0]
|
262
|
+
criteria.children_criteria = source_criteria.children_criteria
|
263
|
+
#if criteria.is_same_type?(source_criteria)
|
264
|
+
criteria.update_copy( source_criteria.hard_status, source_criteria.title, source_criteria.description,
|
265
|
+
source_criteria.derivation_operator, source_criteria.definition )#, occur_letter )
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
criteria
|
270
|
+
end
|
157
271
|
end
|
272
|
+
|
158
273
|
|
159
274
|
private
|
160
275
|
|