lib/hqmf-parser/2.0/document.rb in health-data-standards-3.4.6 vs lib/hqmf-parser/2.0/document.rb in health-data-standards-3.5.0

- old
+ new

@@ -19,15 +19,68 @@ @measure_period = EffectiveTime.new(measure_period_def) end # Extract measure attributes @attributes = @doc.xpath('/cda:QualityMeasureDocument/cda:subjectOf/cda:measureAttribute', NAMESPACES).collect do |attribute| - id = attribute.at_xpath('./cda:id/@extension', NAMESPACES).try(:value) + id = attribute.at_xpath('./cda:id/@root', NAMESPACES).try(:value) code = attribute.at_xpath('./cda:code/@code', NAMESPACES).try(:value) name = attribute.at_xpath('./cda:code/cda:displayName/@value', NAMESPACES).try(:value) value = attribute.at_xpath('./cda:value/@value', NAMESPACES).try(:value) - HQMF::Attribute.new(id, code, value, nil, name) + + id_obj = nil + if attribute.at_xpath('./cda:id', NAMESPACES) + 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)) + end + + code_obj = nil; + if attribute.at_xpath('./cda:code', NAMESPACES) + nullFlavor = attribute.at_xpath('./cda:code/@nullFlavor', NAMESPACES).try(:value) + oText = attribute.at_xpath('./cda:code/cda:originalText', NAMESPACES) ? attribute.at_xpath('./cda:code/cda:originalText/@value', NAMESPACES).try(:value) : nil + code_obj = HQMF::Coded.new(attribute.at_xpath('./cda:code/@xsi:type', NAMESPACES).try(:value) || 'CD', + attribute.at_xpath('./cda:code/@codeSystem', NAMESPACES).try(:value), + code, + attribute.at_xpath('./cda:code/@valueSet', NAMESPACES).try(:value), + name, + nullFlavor, + oText) + + + # Mapping for nil values to align with 1.0 parsing + if code == nil + code = nullFlavor + end + + if name == nil + name = oText + end + + end + + value_obj = nil + if attribute.at_xpath('./cda:value', NAMESPACES) + type = attribute.at_xpath('./cda:value/@xsi:type', NAMESPACES).try(:value) + case type + when 'II' + value_obj = HQMF::Identifier.new(type, attribute.at_xpath('./cda:value/@root', NAMESPACES).try(:value), attribute.at_xpath('./cda:value/@extension', NAMESPACES).try(:value)) + if value == nil + value = attribute.at_xpath('./cda:value/@extension', NAMESPACES).try(:value) + end + when 'ED' + value_obj = HQMF::ED.new(type, value, attribute.at_xpath('./cda:value/@mediaType', NAMESPACES).try(:value)) + when 'CD' + 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)) + else + value_obj = value.present? ? HQMF::GenericValueContainer.new(type, value) : HQMF::AnyValue.new(type) + end + end + + # Handle the cms_id + if name == "eMeasure Identifier" + @cms_id = "CMS#{value}v#{@hqmf_version_number}" + end + + HQMF::Attribute.new(id, code, value, nil, name, id_obj, code_obj, value_obj) end # Extract the data criteria @data_criteria = [] @source_data_criteria = [] @@ -52,15 +105,17 @@ stratifier_id_def = population_def.at_xpath('cda:templateId/cda:item[@root="'+HQMF::Document::STRATIFIED_POPULATION_TEMPLATE_ID+'"]/@controlInformationRoot', NAMESPACES) population['stratification'] = stratifier_id_def.value if stratifier_id_def { - HQMF::PopulationCriteria::IPP => 'patientPopulationCriteria', + HQMF::PopulationCriteria::IPP => 'initialPopulationCriteria', HQMF::PopulationCriteria::DENOM => 'denominatorCriteria', HQMF::PopulationCriteria::NUMER => 'numeratorCriteria', HQMF::PopulationCriteria::DENEXCEP => 'denominatorExceptionCriteria', - HQMF::PopulationCriteria::DENEX => 'denominatorExclusionCriteria' + HQMF::PopulationCriteria::DENEX => 'denominatorExclusionCriteria', + HQMF::PopulationCriteria::STRAT => 'stratifierCriteria', + HQMF::PopulationCriteria::MSRPOPL => 'measurePopulationCriteria' }.each_pair do |criteria_id, criteria_element_name| criteria_def = population_def.at_xpath("cda:component[cda:#{criteria_element_name}]", NAMESPACES) if criteria_def @@ -93,16 +148,54 @@ else population[criteria_id] = identical.first.id end end end + + id_def = population_def.at_xpath('cda:id/@extension', NAMESPACES) population['id'] = id_def ? id_def.value : "Population#{population_index}" title_def = population_def.at_xpath('cda:title/@value', NAMESPACES) population['title'] = title_def ? title_def.value : "Population #{population_index}" + observation_section = @doc.xpath('cda:QualityMeasureDocument/cda:component/cda:measureObservationsSection', NAMESPACES) + if !observation_section.empty? + population['OBSERV'] = 'OBSERV' + end @populations << population end + + + #look for observation data in separate section but create a population for it if it exists + observation_section = @doc.xpath('cda:QualityMeasureDocument/cda:component/cda:measureObservationsSection', NAMESPACES) + if !observation_section.empty? + observation_section.xpath("cda:definition",NAMESPACES).each do |criteria_def| + criteria_id = "OBSERV" + population = {} + criteria = PopulationCriteria.new(criteria_def, self) + criteria.type="OBSERV" + # 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 + # more readable. The alternative would be to have the hqmf ids in the populations, which would work, but is difficult to read the populations. + if ids_by_hqmf_id["#{criteria.hqmf_id}"] + criteria.create_human_readable_id(ids_by_hqmf_id[criteria.hqmf_id]) + else + if population_counters[criteria_id] + population_counters[criteria_id] += 1 + criteria.create_human_readable_id("#{criteria_id}_#{population_counters[criteria_id]}") + else + population_counters[criteria_id] = 0 + criteria.create_human_readable_id(criteria_id) + end + ids_by_hqmf_id["#{criteria.hqmf_id}"] = criteria.id + end + + @population_criteria << criteria + + population[criteria_id] = criteria.id + @populations << population + end + end + end # Get the title of the measure # @return [String] the title def title @@ -140,22 +233,44 @@ # @return [HQMF2::DataCriteria] the matching data criteria, raises an Exception if not found def data_criteria(id) find(@data_criteria, :id, id) end - # Parse an XML document at the supplied path + # Parse an XML document from the supplied contents # @return [Nokogiri::XML::Document] def self.parse(hqmf_contents) - doc = Nokogiri::XML(hqmf_contents) + doc = hqmf_contents.kind_of?(Nokogiri::XML::Document) ? hqmf_contents : Nokogiri::XML(hqmf_contents) + doc.root.add_namespace_definition('cda', 'urn:hl7-org:v3') doc end def to_model + dcs = all_data_criteria.collect {|dc| dc.to_model} pcs = all_population_criteria.collect {|pc| pc.to_model} sdc = source_data_criteria.collect{|dc| dc.to_model} - HQMF::Document.new(id, id, hqmf_set_id, hqmf_version_number, nil, title, description, pcs, dcs, sdc, attributes, measure_period.to_model, populations) + dcs = update_data_criteria(dcs, sdc) + HQMF::Document.new(id, id, hqmf_set_id, hqmf_version_number, @cms_id, title, description, pcs, dcs, sdc, attributes, measure_period.to_model, populations) end + + # Update the data criteria to handle variables properly + def update_data_criteria(data_criteria, source_data_criteria) + # step through each criteria and look for groupers (type derived) with one child + data_criteria.map do |criteria| + if criteria.type == "derived".to_sym && criteria.children_criteria.length == 1 + source_data_criteria.each do |source_criteria| + if source_criteria.title == criteria.children_criteria[0] + criteria.children_criteria = source_criteria.children_criteria + #if criteria.is_same_type?(source_criteria) + criteria.update_copy( source_criteria.hard_status, source_criteria.title, source_criteria.description, + source_criteria.derivation_operator, source_criteria.definition )#, occur_letter ) + end + end + end + criteria + end + end + private def find(collection, attribute, value) collection.find {|e| e.send(attribute)==value}