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

- old
+ new

@@ -6,30 +6,44 @@ attr_reader :property, :type, :status, :value, :effective_time, :section attr_reader :temporal_references, :subset_operators, :children_criteria 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 - + attr_reader :specific_occurrence, :is_source_data_criteria, :comments + + 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" + SATISFIES_ALL_TEMPLATE = "0.1.2.3.4.5.6.7.8.9.3" + + CONJUNCTION_CODE_TO_DERIVATION_OP = { + 'OR' => 'UNION', + 'AND' => 'XPRODUCT' + } + + CRITERIA_GLOB = "*[substring(name(),string-length(name())-7) = \'Criteria\']" + # Create a new instance based on the supplied HQMF entry # @param [Nokogiri::XML::Element] entry the parsed HQMF entry def initialize(entry) @entry = entry + @local_variable_name = extract_local_variable_name @status = attr_val('./*/cda:statusCode/@code') - @description = attr_val('./*/cda:text/@value') + @description = attr_val("./#{CRITERIA_GLOB}/cda:text/@value") extract_negation() extract_specific_or_source() @effective_time = extract_effective_time @temporal_references = extract_temporal_references @derivation_operator = extract_derivation_operator @field_values = extract_field_values @subset_operators = extract_subset_operators @children_criteria = extract_child_criteria - @id_xpath = './*/cda:id/cda:item/@extension' + @id_xpath = './*/cda:id/@extension' @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 + # 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 # status and negation associated with that if !extract_type_from_template_id() # If no template id or not one we recognize then try to determine type from @@ -44,13 +58,15 @@ # Patch xpaths when necessary, HQMF data criteria are irregular in structure so # the same information is found in different places depending on the type of # criteria # Assumes @definition and @status are already set case @definition + when 'transfer_to', 'transfer_from' + @code_list_xpath = './cda:observationCriteria/cda:value' when 'diagnosis', 'diagnosis_family_history' @code_list_xpath = './cda:observationCriteria/cda:value' - when 'risk_category_assessment', 'procedure_result', 'laboratory_test', 'diagnostic_study_result', 'functional_status_result', 'intervention_result' + when 'physical_exam', 'risk_category_assessment', 'procedure_result', 'laboratory_test', 'diagnostic_study_result', 'functional_status_result', 'intervention_result' @value = extract_value when 'medication' case @status when 'dispensed', 'ordered' @code_list_xpath = './cda:supplyCriteria/cda:participation/cda:role/cda:code' @@ -62,11 +78,23 @@ when 'variable' @value = extract_value end end + def extract_local_variable_name + lvn = @entry.at_xpath("./cda:localVariableName") + lvn["value"] if lvn + end + def extract_type_from_definition + if @entry.at_xpath("./cda:grouperCriteria") + if @local_variable_name && @local_variable_name.match(/qdm_/) + @variable = true + end + @definition = 'derived' + return + end # See if we can find a match for the entry definition value and status. entry_type = attr_val('./*/cda:definition/*/cda:id/@extension') begin settings = HQMF::DataCriteria.get_settings_for_definition(entry_type, @status) @definition = entry_type @@ -108,20 +136,37 @@ HQMF2::Utilities.attr_val(template_def, '@root') end if template_ids.include?(HQMF::DataCriteria::SOURCE_DATA_CRITERIA_TEMPLATE_ID) @is_source_data_criteria = true end + found = false template_ids.each do |template_id| defs = HQMF::DataCriteria.definition_for_template_id(template_id) + if defs @definition = defs['definition'] @status = defs['status'].length > 0 ? defs['status'] : nil @negation = defs['negation'] + found ||= true + elsif template_id == VARIABLE_TEMPLATE + @derivation_operator = HQMF::DataCriteria::INTERSECT if @derivation_operator == HQMF::DataCriteria::XPRODUCT + @definition ||= 'derived' + @negation = false + @variable = true + found ||= true + elsif template_id == SATISFIES_ANY_TEMPLATE + @definition = HQMF::DataCriteria::SATISFIES_ANY + @negation = false return true + elsif template_id == SATISFIES_ALL_TEMPLATE + @definition = HQMF::DataCriteria::SATISFIES_ALL + @derivation_operator = HQMF::DataCriteria::INTERSECT + @negation = false + found ||= true end end - false + found end def to_s props = { :property => property, @@ -139,11 +184,16 @@ end # Get the title of the criteria, provides a human readable description # @return [String] the title of this data criteria def title - attr_val("#{@code_list_xpath}/cda:displayName/@value") || id + dispValue = attr_val("#{@code_list_xpath}/cda:displayName/@value") + desc = nil + if @description && (@description.include? ":") + desc = @description.match(/.*:\s+(.+)/)[1] + end + dispValue || desc || id end # Get the code list OID of the criteria, used as an index to the code list database # @return [String] the code list identifier of this data criteria def code_list_id @@ -164,23 +214,36 @@ nil end end def to_model + mv = value ? value.to_model : nil met = effective_time ? effective_time.to_model : nil mtr = temporal_references.collect {|ref| ref.to_model} mso = subset_operators.collect {|opr| opr.to_model} field_values = {} @field_values.each_pair do |id, val| field_values[id] = val.to_model end - + + # Model transfers as a field + if ['transfer_to', 'transfer_from'].include? @definition + field_values ||= {} + field_code_list_id = @code_list_id + if !field_code_list_id + field_code_list_id = attr_val("./#{CRITERIA_GLOB}/cda:outboundRelationship/#{CRITERIA_GLOB}/cda:value/@valueSet") + end + field_values[@definition.upcase] = HQMF::Coded.for_code_list(field_code_list_id, title) + end + + field_values = nil if field_values.empty? + HQMF::DataCriteria.new(id, title, nil, description, code_list_id, children_criteria, derivation_operator, @definition, status, mv, field_values, met, inline_code_list, @negation, @negation_code_list_id, mtr, mso, @specific_occurrence, - @specific_occurrence_const, @source_data_criteria) + @specific_occurrence_const, @source_data_criteria, @comments, @variable) end private def extract_negation @@ -192,11 +255,11 @@ @negation_code_list_id = nil end end def extract_child_criteria - @entry.xpath('./*/cda:excerpt/*/cda:id', HQMF2::Document::NAMESPACES).collect do |ref| + @entry.xpath("./*/cda:outboundRelationship[@typeCode='COMP']/cda:criteriaReference/cda:id", HQMF2::Document::NAMESPACES).collect do |ref| Reference.new(ref).id end.compact end def extract_effective_time @@ -213,50 +276,54 @@ SubsetOperator.new(subset_operator) end end def extract_derivation_operator - derivation_operators = all_subset_operators.select do |operator| - ['UNION', 'XPRODUCT'].include?(operator.type) + codes = @entry.xpath("./*/cda:outboundRelationship[@typeCode='COMP']/cda:conjunctionCode/@code", HQMF2::Document::NAMESPACES) + codes.inject(nil) do | d_op, code | + raise "More than one derivation operator in data criteria" if d_op && d_op != CONJUNCTION_CODE_TO_DERIVATION_OP[code.value] + CONJUNCTION_CODE_TO_DERIVATION_OP[code.value] end - raise "More than one derivation operator in data criteria" if derivation_operators.size>1 - derivation_operators.first ? derivation_operators.first.type : nil end def extract_subset_operators all_subset_operators.select do |operator| operator.type != 'UNION' && operator.type != 'XPRODUCT' end end def extract_specific_or_source - specific_def = @entry.at_xpath('./*/cda:outboundRelationship[cda:subsetCode/@code="SPECIFIC"]', HQMF2::Document::NAMESPACES) + specific_def = @entry.at_xpath('./*/cda:outboundRelationship[@typeCode="OCCR"]', HQMF2::Document::NAMESPACES) source_def = @entry.at_xpath('./*/cda:outboundRelationship[cda:subsetCode/@code="SOURCE"]', HQMF2::Document::NAMESPACES) if specific_def - @source_data_criteria = HQMF2::Utilities.attr_val(specific_def, './cda:observationReference/cda:id/@extension') + @source_data_criteria = HQMF2::Utilities.attr_val(specific_def, './cda:criteriaReference/cda:id/@extension') @specific_occurrence_const = HQMF2::Utilities.attr_val(specific_def, './cda:localVariableName/@controlInformationRoot') @specific_occurrence = HQMF2::Utilities.attr_val(specific_def, './cda:localVariableName/@controlInformationExtension') + if !@specific_occurrence + @specific_occurrence = "A" + @specific_occurrence_const = @source_data_criteria.upcase + end elsif source_def - @source_data_criteria = HQMF2::Utilities.attr_val(source_def, './cda:observationReference/cda:id/@extension') + @source_data_criteria = HQMF2::Utilities.attr_val(source_def, './cda:criteriaReference/cda:id/@extension') end end def extract_field_values fields = {} # extract most fields which use the same structure @entry.xpath('./*/cda:outboundRelationship[*/cda:code]', HQMF2::Document::NAMESPACES).each do |field| code = HQMF2::Utilities.attr_val(field, './*/cda:code/@code') code_id = HQMF::DataCriteria::VALUE_FIELDS[code] value = DataCriteria.parse_value(field, './*/cda:value') - fields[code_id] = value + fields[code_id] = value if value && code_id end # special case for facility location which uses a very different structure @entry.xpath('./*/cda:outboundRelationship[*/cda:participation]', HQMF2::Document::NAMESPACES).each do |field| code = HQMF2::Utilities.attr_val(field, './*/cda:participation/cda:role/@classCode') code_id = HQMF::DataCriteria::VALUE_FIELDS[code] value = Coded.new(field.at_xpath('./*/cda:participation/cda:role/cda:code', HQMF2::Document::NAMESPACES)) - fields[code_id] = value + fields[code_id] = value if value && code_id end fields end def extract_temporal_references @@ -275,10 +342,12 @@ if value_def value_type_def = value_def.at_xpath('@xsi:type', HQMF2::Document::NAMESPACES) if value_type_def value_type = value_type_def.value case value_type + when 'PQ' + value = Value.new(value_def, 'PQ', true) when 'TS' value = Value.new(value_def) when 'IVL_PQ', 'IVL_INT' value = Range.new(value_def) when 'CD' @@ -314,6 +383,6 @@ end end -end \ No newline at end of file +end