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.
Files changed (98) hide show
  1. data/Gemfile +2 -2
  2. data/README.md +4 -0
  3. data/lib/health-data-standards.rb +1 -0
  4. data/lib/health-data-standards/export/cat_1.rb +4 -4
  5. data/lib/health-data-standards/export/helper/scooped_view_helper.rb +16 -16
  6. data/lib/health-data-standards/export/qrda/hqmf-qrda-oids.json +6 -0
  7. data/lib/health-data-standards/export/view_helper.rb +8 -1
  8. data/lib/health-data-standards/import/bulk_record_importer.rb +45 -13
  9. data/lib/health-data-standards/import/bundle/importer.rb +2 -4
  10. data/lib/health-data-standards/import/cda/provider_importer.rb +2 -2
  11. data/lib/health-data-standards/import/green_c32/section_importer.rb +2 -2
  12. data/lib/health-data-standards/import/provider_import_utils.rb +2 -2
  13. data/lib/health-data-standards/models/cda_identifier.rb +1 -0
  14. data/lib/health-data-standards/models/cqm/bundle.rb +4 -1
  15. data/lib/health-data-standards/models/cqm/measure.rb +40 -25
  16. data/lib/health-data-standards/models/cqm/patient_cache.rb +61 -60
  17. data/lib/health-data-standards/models/encounter.rb +4 -12
  18. data/lib/health-data-standards/models/entry.rb +4 -8
  19. data/lib/health-data-standards/models/facility.rb +1 -0
  20. data/lib/health-data-standards/models/fulfillment_history.rb +6 -18
  21. data/lib/health-data-standards/models/guarantor.rb +1 -0
  22. data/lib/health-data-standards/models/lab_result.rb +2 -6
  23. data/lib/health-data-standards/models/medical_equipment.rb +2 -7
  24. data/lib/health-data-standards/models/medication.rb +11 -31
  25. data/lib/health-data-standards/models/metadata/link_info.rb +1 -0
  26. data/lib/health-data-standards/models/order_information.rb +5 -13
  27. data/lib/health-data-standards/models/organization.rb +1 -0
  28. data/lib/health-data-standards/models/procedure.rb +1 -4
  29. data/lib/health-data-standards/models/provider.rb +2 -1
  30. data/lib/health-data-standards/models/provider_performance.rb +1 -0
  31. data/lib/health-data-standards/models/qrda/legal_authenticator.rb +1 -0
  32. data/lib/health-data-standards/models/qrda/organization.rb +2 -0
  33. data/lib/health-data-standards/models/record.rb +7 -7
  34. data/lib/health-data-standards/models/result_value.rb +1 -0
  35. data/lib/health-data-standards/models/svs/concept.rb +1 -0
  36. data/lib/health-data-standards/models/svs/value_set.rb +1 -0
  37. data/lib/health-data-standards/models/telecom.rb +1 -0
  38. data/lib/health-data-standards/models/transfer.rb +1 -0
  39. data/lib/health-data-standards/tasks/bundle.rake +3 -3
  40. data/lib/health-data-standards/util/vs_api.rb +2 -2
  41. data/lib/hqmf-generator/attribute.xml.erb +9 -11
  42. data/lib/hqmf-generator/characteristic_criteria.xml.erb +5 -5
  43. data/lib/hqmf-generator/code.xml.erb +6 -2
  44. data/lib/hqmf-generator/condition_criteria.xml.erb +4 -5
  45. data/lib/hqmf-generator/derivation.xml.erb +6 -6
  46. data/lib/hqmf-generator/description.xml.erb +1 -1
  47. data/lib/hqmf-generator/document.xml.erb +46 -11
  48. data/lib/hqmf-generator/encounter_criteria.xml.erb +4 -5
  49. data/lib/hqmf-generator/field.xml.erb +13 -1
  50. data/lib/hqmf-generator/grouper_criteria.xml.erb +17 -0
  51. data/lib/hqmf-generator/hqmf-generator.rb +75 -8
  52. data/lib/hqmf-generator/local_variable.xml.erb +1 -0
  53. data/lib/hqmf-generator/measure_observation_definition.xml.erb +25 -0
  54. data/lib/hqmf-generator/observation_criteria.xml.erb +4 -5
  55. data/lib/hqmf-generator/population_criteria.xml.erb +2 -3
  56. data/lib/hqmf-generator/precondition.xml.erb +2 -2
  57. data/lib/hqmf-generator/precondition_cv.xml.erb +8 -0
  58. data/lib/hqmf-generator/procedure_criteria.xml.erb +4 -5
  59. data/lib/hqmf-generator/reference.xml.erb +2 -2
  60. data/lib/hqmf-generator/source.xml.erb +2 -2
  61. data/lib/hqmf-generator/specific_occurrence.xml.erb +4 -5
  62. data/lib/hqmf-generator/subset.xml.erb +16 -3
  63. data/lib/hqmf-generator/substance_criteria.xml.erb +4 -5
  64. data/lib/hqmf-generator/supply_criteria.xml.erb +4 -5
  65. data/lib/hqmf-generator/temporal_relationship.xml.erb +1 -1
  66. data/lib/hqmf-generator/value.xml.erb +35 -9
  67. data/lib/hqmf-generator/variable_criteria.xml.erb +2 -3
  68. data/lib/hqmf-model/attribute.rb +36 -8
  69. data/lib/hqmf-model/data_criteria.json +38 -204
  70. data/lib/hqmf-model/data_criteria.rb +40 -16
  71. data/lib/hqmf-model/document.rb +61 -2
  72. data/lib/hqmf-model/population_criteria.rb +11 -7
  73. data/lib/hqmf-model/precondition.rb +1 -1
  74. data/lib/hqmf-model/types.rb +91 -8
  75. data/lib/hqmf-parser/1.0/attribute.rb +55 -2
  76. data/lib/hqmf-parser/1.0/document.rb +10 -23
  77. data/lib/hqmf-parser/1.0/population_criteria.rb +2 -2
  78. data/lib/hqmf-parser/1.0/range.rb +0 -1
  79. data/lib/hqmf-parser/2.0/data_criteria.rb +90 -21
  80. data/lib/hqmf-parser/2.0/document.rb +122 -7
  81. data/lib/hqmf-parser/2.0/population_criteria.rb +18 -6
  82. data/lib/hqmf-parser/2.0/precondition.rb +4 -1
  83. data/lib/hqmf-parser/2.0/types.rb +36 -15
  84. data/lib/hqmf-parser/converter/pass1/document_converter.rb +4 -56
  85. data/lib/hqmf-parser/converter/pass1/population_criteria_converter.rb +24 -8
  86. data/lib/hqmf-parser/converter/pass1/precondition_extractor.rb +15 -2
  87. data/lib/hqmf-parser/converter/pass2/comparison_converter.rb +1 -1
  88. data/lib/hqmf-parser/parser.rb +64 -41
  89. data/templates/cat1/_2.16.840.1.113883.10.20.22.4.85.cat1.erb +0 -1
  90. data/templates/cat1/_address.cat1.erb +9 -0
  91. data/templates/cat1/_author.cat1.erb +28 -0
  92. data/templates/cat1/_id.cat1.erb +1 -0
  93. data/templates/cat1/_organization.cat1.erb +8 -0
  94. data/templates/cat1/_patient_data.cat1.erb +0 -3
  95. data/templates/cat1/_telecom.cat1.erb +1 -0
  96. data/templates/cat1/show.cat1.erb +96 -58
  97. metadata +115 -66
  98. 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 |attr|
30
- PopulationCriteria.new(attr, self)
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 |attr|
33
- Observation.new(attr, self)
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 |attr|
38
- PopulationCriteria.new(attr, self)
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
- initial_populations = @population_criteria.select {|pc| pc.code.starts_with? 'IPP'}
43
- initial_populations.each do |population|
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
@@ -50,7 +50,6 @@ module HQMF1
50
50
  if (@low == nil && @high == nil)
51
51
  @low = optional_value('.',true)
52
52
  @high = optional_value('.',true)
53
- puts "\tfound = relationship parsing temporal reference (bugfix)"
54
53
  end
55
54
  end
56
55
  end
@@ -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('./*/cda:text/@value')
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/cda:item/@extension'
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
- false
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") || id
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('./*/cda:excerpt/*/cda:id', HQMF2::Document::NAMESPACES).collect do |ref|
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
- derivation_operators = all_subset_operators.select do |operator|
219
- ['UNION', 'XPRODUCT'].include?(operator.type)
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[cda:subsetCode/@code="SPECIFIC"]', HQMF2::Document::NAMESPACES)
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:observationReference/cda:id/@extension')
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:observationReference/cda:id/@extension')
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/@extension', NAMESPACES).try(:value)
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
- HQMF::Attribute.new(id, code, value, nil, name)
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 => 'patientPopulationCriteria',
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
- # Parse an XML document at the supplied path
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
- HQMF::Document.new(id, id, hqmf_set_id, hqmf_version_number, nil, title, description, pcs, dcs, sdc, attributes, measure_period.to_model, populations)
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