Skip to content
This repository has been archived by the owner on Jun 24, 2021. It is now read-only.

Commit

Permalink
Incomplete parsing of MAT-generated HQMF 2.1 xml
Browse files Browse the repository at this point in the history
  • Loading branch information
jtferns authored and pkmitre committed Aug 6, 2015
1 parent 36b0127 commit d0c51b2
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 44 deletions.
39 changes: 34 additions & 5 deletions lib/hqmf-parser/2.0/data_criteria.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class DataCriteria
attr_reader :derivation_operator, :negation, :negation_code_list_id, :description
attr_reader :field_values, :source_data_criteria, :specific_occurrence_const
attr_reader :specific_occurrence, :is_source_data_criteria, :comments
attr_reader :id

VARIABLE_TEMPLATE = "0.1.2.3.4.5.6.7.8.9.1"
SATISFIES_ANY_TEMPLATE = "0.1.2.3.4.5.6.7.8.9.2"
Expand All @@ -27,7 +28,7 @@ def initialize(entry)
@entry = entry
@local_variable_name = extract_local_variable_name
@status = attr_val('./*/cda:statusCode/@code')
@description = attr_val("./#{CRITERIA_GLOB}/cda:text/@value")
@description = attr_val("./#{CRITERIA_GLOB}/cda:text/@value") || attr_val("./#{CRITERIA_GLOB}/cda:title/@value") || attr_val("./#{CRITERIA_GLOB}/cda:id/@extension")
extract_negation()
extract_specific_or_source()
@effective_time = extract_effective_time
Expand All @@ -37,10 +38,11 @@ def initialize(entry)
@subset_operators = extract_subset_operators
@children_criteria = extract_child_criteria
@id_xpath = './*/cda:id/@extension'
@id = attr_val(@id_xpath)
@code_list_xpath = './*/cda:code'
@value_xpath = './*/cda:value'
@comments = @entry.xpath("./#{CRITERIA_GLOB}/cda:text/cda:xml/cda:qdmUserComments/cda:item/text()", HQMF2::Document::NAMESPACES).map{ |v| v.content }
@variable = false
@variable = extract_variable

# Try to determine what kind of data criteria we are dealing with
# First we look for a template id and if we find one just use the definition
Expand Down Expand Up @@ -94,6 +96,7 @@ def extract_type_from_definition
return
end
# See if we can find a match for the entry definition value and status.
# FIXME: Resolve issue with improperly defined data criteria
entry_type = attr_val('./*/cda:definition/*/cda:id/@extension')
begin
settings = HQMF::DataCriteria.get_settings_for_definition(entry_type, @status)
Expand Down Expand Up @@ -176,11 +179,12 @@ def to_s
"DataCriteria#{props.to_s}"
end

# TODO: Remove id method if id attribute is sufficient
# Get the identifier of the criteria, used elsewhere within the document for referencing
# @return [String] the identifier of this data criteria
def id
attr_val(@id_xpath)
end
# def id
# attr_val(@id_xpath)
# end

# Get the title of the criteria, provides a human readable description
# @return [String] the title of this data criteria
Expand Down Expand Up @@ -243,6 +247,24 @@ def to_model
@specific_occurrence_const, @source_data_criteria, @comments, @variable)
end

# Return a new DataCriteria instance with only source data criteria attributes set
def extract_source_data_criteria
DataCriteria.new(@entry).extract_as_source_data_criteria(@source_data_criteria || @id)
end

# Set this data criteria's specific attributes to empty/nil
# SHOULD only be called on the source data criteria instance
def extract_as_source_data_criteria(id)
@field_values = {}
@temporal_references = []
@subset_operators = []
@is_source_data_criteria = true
@specific_occurrence = nil
@specific_occurrence_const = nil
@id = id
self
end

private

def extract_negation
Expand Down Expand Up @@ -389,6 +411,13 @@ def definition_for_demographic

end

# Determine if this instance is a qdm variable
def extract_variable
variable = @local_variable_name.start_with? "qdm_var_" unless @local_variable_name.blank?
variable ||= @id.start_with? "qdm_var_" unless @id.blank?
variable ||= false
end

end

end
56 changes: 30 additions & 26 deletions lib/hqmf-parser/2.0/document.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,20 @@ class Document
NAMESPACES = {'cda' => 'urn:hl7-org:v3', 'xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'qdm'=>'urn:hhs-qdm:hqmf-r2-extensions:v1'}

attr_reader :measure_period, :id, :hqmf_set_id, :hqmf_version_number, :populations, :attributes, :source_data_criteria

# Create a new HQMF2::Document instance by parsing at file at the supplied path
# @param [String] path the path to the HQMF document
def initialize(hqmf_contents)
@doc = @entry = Document.parse(hqmf_contents)
remove_popultaion_preconditions(@doc)
@id = attr_val('cda:QualityMeasureDocument/cda:id/@extension')
@hqmf_set_id = attr_val('cda:QualityMeasureDocument/cda:setId/@extension')
@id = attr_val('cda:QualityMeasureDocument/cda:id/@extension') || attr_val('cda:QualityMeasureDocument/cda:id/@root').upcase
@hqmf_set_id = attr_val('cda:QualityMeasureDocument/cda:setId/@extension') || attr_val('cda:QualityMeasureDocument/cda:setId/@root').upcase
@hqmf_version_number = attr_val('cda:QualityMeasureDocument/cda:versionNumber/@value').to_i
measure_period_def = @doc.at_xpath('cda:QualityMeasureDocument/cda:controlVariable/cda:measurePeriod/cda:value', NAMESPACES)
if measure_period_def
@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/@root', NAMESPACES).try(:value)
Expand Down Expand Up @@ -75,13 +75,13 @@ def initialize(hqmf_contents)
end

# Handle the cms_id
if name == "eMeasure Identifier"
if name.include? "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 = []
Expand All @@ -93,14 +93,17 @@ def initialize(hqmf_contents)
@data_criteria << criteria
end
end


# Extract the source data criteria from data criteria
@source_data_criteria = @data_criteria.map{|dc| dc.extract_source_data_criteria}.uniq! { |sdc| sdc.id }

# Extract the population criteria and population collections
@populations = []
@population_criteria = []

population_counters = {}
ids_by_hqmf_id = {}

@doc.xpath('cda:QualityMeasureDocument/cda:component/cda:populationCriteriaSection', NAMESPACES).each_with_index do |population_def, population_index|
population = {}

Expand All @@ -117,16 +120,16 @@ def initialize(hqmf_contents)
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

criteria = PopulationCriteria.new(criteria_def, self)

# check to see if we have an identical population criteria.
# this can happen since the hqmf 2.0 will export a DENOM, NUMER, etc for each population, even if identical.
# if we have identical, just re-use it rather than creating DENOM_1, NUMER_1, etc.
identical = @population_criteria.select {|pc| pc.to_model.base_json.to_json == criteria.to_model.base_json.to_json}

if (identical.empty?)
# 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.
Expand All @@ -142,8 +145,8 @@ def initialize(hqmf_contents)
end
ids_by_hqmf_id["#{criteria.hqmf_id}-#{population['stratification']}"] = criteria.id
end


@population_criteria << criteria
population[criteria_id] = criteria.id
else
Expand Down Expand Up @@ -187,63 +190,63 @@ def initialize(hqmf_contents)
end
ids_by_hqmf_id["#{criteria.hqmf_id}"] = criteria.id
end

@population_criteria << criteria

population[criteria_id] = criteria.id
@populations << population
end
end
end

end

# Get the title of the measure
# @return [String] the title
def title
@doc.at_xpath('cda:QualityMeasureDocument/cda:title/@value', NAMESPACES).inner_text
end

# Get the description of the measure
# @return [String] the description
def description
description = @doc.at_xpath('cda:QualityMeasureDocument/cda:text/@value', NAMESPACES)
description==nil ? '' : description.inner_text
end

# Get all the population criteria defined by the measure
# @return [Array] an array of HQMF2::PopulationCriteria
def all_population_criteria
@population_criteria
end

# Get a specific population criteria by id.
# @param [String] id the population identifier
# @return [HQMF2::PopulationCriteria] the matching criteria, raises an Exception if not found
def population_criteria(id)
find(@population_criteria, :id, id)
end

# Get all the data criteria defined by the measure
# @return [Array] an array of HQMF2::DataCriteria describing the data elements used by the measure
def all_data_criteria
@data_criteria
end

# Get a specific data criteria by id.
# @param [String] id the data criteria identifier
# @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 from the supplied contents
# @return [Nokogiri::XML::Document]
def self.parse(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}
Expand Down Expand Up @@ -273,14 +276,15 @@ def update_data_criteria(data_criteria, source_data_criteria)

def remove_popultaion_preconditions(doc)
#population sections
pop_ids = doc.xpath("//cda:populationCriteriaSection/cda:component[@typeCode='COMP']/*/cda:id",NAMESPACES)
pop_ids = doc.xpath("//cda:populationCriteriaSection/cda:component[@typeCode='COMP']/*/cda:id",NAMESPACES)
#find the population entries and get their ids
pop_ids.each do |p_id|
doc.xpath("//cda:precondition[./cda:criteriaReference/cda:id[@extension='#{p_id["extension"]}' and @root='#{p_id["root"]}']]",NAMESPACES).remove
end
end

private

def find(collection, attribute, value)
collection.find {|e| e.send(attribute)==value}
end
Expand Down
17 changes: 9 additions & 8 deletions lib/hqmf-parser/2.0/population_criteria.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,21 @@ module HQMF2
# Represents an HQMF population criteria, also supports all the same methods as
# HQMF2::Precondition
class PopulationCriteria

include HQMF2::Utilities

attr_reader :preconditions, :id, :hqmf_id, :title, :aggregator, :comments
#need to do this to allow for setting the type to OBSERV for
#need to do this to allow for setting the type to OBSERV for
attr_accessor :type
# Create a new population criteria from the supplied HQMF entry
# @param [Nokogiri::XML::Element] the HQMF entry
def initialize(entry, doc)
@doc = doc
@entry = entry
@hqmf_id = attr_val('./*/cda:id/@extension') || attr_val('./*/cda:typeId/@extension')
@title = attr_val('./*/cda:code/cda:displayName/@value')
@title = attr_val('./*/cda:code/cda:displayName/@value')
@type = attr_val('./*/cda:code/@code')
@type = 'IPP' if @type == 'IPOP'
@aggregator = nil
@comments = @entry.xpath("./*/cda:text/cda:xml/cda:qdmUserComments/cda:item/text()", HQMF2::Document::NAMESPACES)
.map{ |v| v.content }
Expand All @@ -31,11 +32,11 @@ def initialize(entry, doc)
Precondition.new(precondition, @doc)
end
end

def create_human_readable_id(id)
@id = id
end

# Return true of this precondition represents a conjunction with nested preconditions
# or false of this precondition is a reference to a data criteria
def conjunction?
Expand All @@ -54,12 +55,12 @@ def conjunction_code
raise "Unknown population type [#{@type}]"
end
end

def to_model
mps = preconditions.collect {|p| p.to_model}
HQMF::PopulationCriteria.new(id, hqmf_id, type, mps, title, aggregator, comments)
end

end

end
10 changes: 5 additions & 5 deletions lib/hqmf-parser/parser.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
module HQMF
class Parser

HQMF_VERSION_1 = "1.0"
HQMF_VERSION_2 = "2.0"

class V2Parser
def initialize
end
Expand Down Expand Up @@ -34,7 +34,7 @@ def version

def self.valid?(xml_contents)
doc = HQMF2::Document.parse(xml_contents)
!doc.at_xpath("/cda:QualityMeasureDocument/cda:typeId[@root='2.16.840.1.113883.1.3' and @extension='POQM_MT000001UV03']").nil?
!doc.at_xpath("/cda:QualityMeasureDocument/cda:typeId[@root='2.16.840.1.113883.1.3' and @extension='POQM_HD000001UV02']").nil?
end

end
Expand Down Expand Up @@ -73,5 +73,5 @@ def self.valid?(xml_contents)
end

end
end

end

0 comments on commit d0c51b2

Please sign in to comment.