projectcypress/health-data-standards

View on GitHub
lib/hqmf-parser/converter/pass1/precondition_converter.rb

Summary

Maintainability
B
5 hrs
Test Coverage
module HQMF
  # Class for converting an HQMF 1.0 representation to an HQMF 2.0 representation
  class PreconditionConverter
    
    def self.parse_preconditions(source,data_criteria_converter)

      # preconditions = []
      # source.each do |precondition|
      #   preconditions << HQMF::PreconditionConverter.parse_precondition(precondition,data_criteria_converter)
      # end
      # 
      # preconditions
      
      parse_and_merge_preconditions(source,data_criteria_converter)
    end
   
    # converts a precondtion to a hqmf model
    def self.parse_precondition(precondition,data_criteria_converter)
      
      # grab child preconditions, and parse recursively
      preconditions = parse_and_merge_preconditions(precondition[:preconditions],data_criteria_converter) if precondition[:preconditions] || []
      
      preconditions_from_restrictions = HQMF::PreconditionExtractor.extract_preconditions_from_restrictions(precondition[:restrictions], data_criteria_converter)
      
      # driv preconditions are preconditions that are the children of an expression
      driv_preconditions = []
      preconditions_from_restrictions.delete_if {|element| driv_preconditions << element if element.is_a? HQMF::Converter::SimpleRestriction and element.operator.type == 'DRIV'}
      
      apply_restrictions_to_comparisons(preconditions, preconditions_from_restrictions) unless preconditions_from_restrictions.empty?

      conjunction_code = convert_logical_conjunction(precondition[:conjunction])

      if (precondition[:expression])
        # this is for things like COUNT
        type = precondition[:expression][:type]
        operator = HQMF::Converter::SimpleOperator.new(HQMF::Converter::SimpleOperator.find_category(type), type, HQMF::Converter::SimpleOperator.parse_value(precondition[:expression][:value]))
        children = []
        if driv_preconditions and !driv_preconditions.empty?
          children = driv_preconditions.map(&:preconditions).flatten
        end
        
        reference = nil
        # take the conjunction code from the parent precondition
        
        restriction = HQMF::Converter::SimpleRestriction.new(operator, nil, children)
        
        comparison_precondition = HQMF::Converter::SimplePrecondition.new(nil,[restriction],reference,conjunction_code, false)
        comparison_precondition.klass = HQMF::Converter::SimplePrecondition::COMPARISON
        comparison_precondition.subset_comparison = true
        preconditions << comparison_precondition
      end
      
      reference = nil
      
      negation = precondition[:negation]
      
      
      if (precondition[:comparison])
        preconditions ||= []
        comparison_precondition = HQMF::PreconditionExtractor.convert_comparison_to_precondition(precondition[:comparison],data_criteria_converter)
        preconditions << comparison_precondition
      end


      if (precondition[:subset])
        # this is for things like FIRST on preconditions
        type = precondition[:subset]
        operator = HQMF::Converter::SimpleOperator.new(HQMF::Converter::SimpleOperator.find_category(type), type, nil)
        children = preconditions
        
        reference = nil
        # take the conjunction code from the parent precondition
        
        restriction = HQMF::Converter::SimpleRestriction.new(operator, nil, children)
        
        comparison_precondition = HQMF::Converter::SimplePrecondition.new(nil,[restriction],reference,conjunction_code, false)
        comparison_precondition.klass = HQMF::Converter::SimplePrecondition::COMPARISON
        preconditions = [comparison_precondition]
      end

      
      HQMF::Converter::SimplePrecondition.new(nil,preconditions,reference,conjunction_code, negation)
      
    end

    def self.get_comparison_preconditions(preconditions)
      comparisons = []
      preconditions.each do |precondition|
        if (precondition.comparison? and !precondition.subset_comparison)
          comparisons << precondition
        elsif(precondition.has_preconditions?)
          comparisons.concat(get_comparison_preconditions(precondition.preconditions))
        else
          raise "precondition with no comparison or children... not valid"
        end
      end
      comparisons
    end

    def self.apply_restrictions_to_comparisons(preconditions, restrictions)
      comparisons = get_comparison_preconditions(preconditions)
      raise "no comparisons to apply restriction to" if comparisons.empty?
      comparisons.each do |comparison|
        comparison.preconditions.concat(restrictions)
      end
    end
    
    private 
    
    
    def self.parse_and_merge_preconditions(source,data_criteria_converter)
      return [] unless source and source.size > 0
      preconditions_by_conjunction = {}
      source.each do |precondition|
        parsed = HQMF::PreconditionConverter.parse_precondition(precondition,data_criteria_converter)
        preconditions_by_conjunction[parsed.conjunction_code] ||= []
        preconditions_by_conjunction[parsed.conjunction_code]  << parsed
      end
      
      merge_precondtion_conjunction_groups(preconditions_by_conjunction)
    end
    
    def self.merge_precondtion_conjunction_groups(preconditions_by_conjunction)
      joined = []
      preconditions_by_conjunction.each do |conjunction_code, preconditions|
        sub_conditions = []
        negated_conditions = []
        preconditions.each do |precondition|
          unless (precondition.negation)
            sub_conditions.concat precondition.preconditions if precondition.preconditions
          else
            negated_conditions.concat precondition.preconditions if precondition.preconditions
          end
        end
        
        if (!sub_conditions.empty?)
          # if we have negated conditions, add them to a new precondition with an inverted conjunction on a negated precondition
          # the reason we invert the conjunction is because we are turning 
          # AND: NOT X
          # AND: NOT Y
          # into 
          # NOT: X OR Y
          # (i.e, demorgan's law)
          if (!negated_conditions.empty?)
            inverted_conjunction_code = HQMF::Precondition::INVERSIONS[conjunction_code]
            sub_conditions << HQMF::Converter::SimplePrecondition.new(nil,negated_conditions,nil,inverted_conjunction_code, true)
          end
          joined << HQMF::Converter::SimplePrecondition.new(nil,sub_conditions,nil,conjunction_code, false)
        elsif (!negated_conditions.empty?)
          # invert conjunction based on demorgan's law
          inverted_conjunction_code = HQMF::Precondition::INVERSIONS[conjunction_code]
          joined << HQMF::Converter::SimplePrecondition.new(nil,negated_conditions,nil,inverted_conjunction_code, true)
        end
        
      end
      joined
    end
    
    def self.convert_logical_conjunction(code)
      case code
        when 'OR'
          HQMF::Precondition::AT_LEAST_ONE_TRUE
        when 'AND'
          HQMF::Precondition::ALL_TRUE
        else
          raise "unsupported logical conjunction code conversion: #{code}"
      end
      
    end
    
   
  end  
end