hqmf-parser 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. data/Gemfile +23 -0
  2. data/README.md +903 -0
  3. data/Rakefile +19 -0
  4. data/VERSION +1 -0
  5. data/lib/hqmf-generator/hqmf-generator.rb +308 -0
  6. data/lib/hqmf-model/attribute.rb +35 -0
  7. data/lib/hqmf-model/data_criteria.rb +322 -0
  8. data/lib/hqmf-model/document.rb +172 -0
  9. data/lib/hqmf-model/population_criteria.rb +90 -0
  10. data/lib/hqmf-model/precondition.rb +85 -0
  11. data/lib/hqmf-model/types.rb +318 -0
  12. data/lib/hqmf-model/utilities.rb +52 -0
  13. data/lib/hqmf-parser.rb +54 -0
  14. data/lib/hqmf-parser/1.0/attribute.rb +68 -0
  15. data/lib/hqmf-parser/1.0/comparison.rb +34 -0
  16. data/lib/hqmf-parser/1.0/data_criteria.rb +105 -0
  17. data/lib/hqmf-parser/1.0/document.rb +209 -0
  18. data/lib/hqmf-parser/1.0/expression.rb +52 -0
  19. data/lib/hqmf-parser/1.0/population_criteria.rb +79 -0
  20. data/lib/hqmf-parser/1.0/precondition.rb +89 -0
  21. data/lib/hqmf-parser/1.0/range.rb +65 -0
  22. data/lib/hqmf-parser/1.0/restriction.rb +157 -0
  23. data/lib/hqmf-parser/1.0/utilities.rb +41 -0
  24. data/lib/hqmf-parser/2.0/data_criteria.rb +319 -0
  25. data/lib/hqmf-parser/2.0/document.rb +165 -0
  26. data/lib/hqmf-parser/2.0/population_criteria.rb +53 -0
  27. data/lib/hqmf-parser/2.0/precondition.rb +44 -0
  28. data/lib/hqmf-parser/2.0/types.rb +223 -0
  29. data/lib/hqmf-parser/2.0/utilities.rb +30 -0
  30. data/lib/hqmf-parser/converter/pass1/data_criteria_converter.rb +254 -0
  31. data/lib/hqmf-parser/converter/pass1/document_converter.rb +183 -0
  32. data/lib/hqmf-parser/converter/pass1/population_criteria_converter.rb +135 -0
  33. data/lib/hqmf-parser/converter/pass1/precondition_converter.rb +164 -0
  34. data/lib/hqmf-parser/converter/pass1/precondition_extractor.rb +159 -0
  35. data/lib/hqmf-parser/converter/pass1/simple_data_criteria.rb +35 -0
  36. data/lib/hqmf-parser/converter/pass1/simple_operator.rb +89 -0
  37. data/lib/hqmf-parser/converter/pass1/simple_population_criteria.rb +10 -0
  38. data/lib/hqmf-parser/converter/pass1/simple_precondition.rb +63 -0
  39. data/lib/hqmf-parser/converter/pass1/simple_restriction.rb +64 -0
  40. data/lib/hqmf-parser/converter/pass2/comparison_converter.rb +91 -0
  41. data/lib/hqmf-parser/converter/pass2/operator_converter.rb +169 -0
  42. data/lib/hqmf-parser/converter/pass3/specific_occurrence_converter.rb +86 -0
  43. data/lib/hqmf-parser/converter/pass3/specific_occurrence_converter_bak.rb +70 -0
  44. data/lib/hqmf-parser/parser.rb +22 -0
  45. data/lib/hqmf-parser/value_sets/value_set_parser.rb +206 -0
  46. data/lib/tasks/coverme.rake +8 -0
  47. data/lib/tasks/hqmf.rake +141 -0
  48. data/lib/tasks/value_sets.rake +23 -0
  49. metadata +159 -0
@@ -0,0 +1,159 @@
1
+ module HQMF
2
+
3
+ # preconditions can be in several places.
4
+ #
5
+ # precondition -> preconditions
6
+ # restriction -> preconditions
7
+ #
8
+ # also, restrictions can be on the following, which can then have preconditions
9
+ # restrictions
10
+ # comparisons
11
+ # preconditions
12
+ #
13
+ class PreconditionExtractor
14
+
15
+
16
+ def self.extract_preconditions_from_restrictions(restrictions,data_criteria_converter)
17
+ return [] unless restrictions
18
+ preconditions = []
19
+ restrictions.each do |restriction|
20
+ preconditions.concat(extract_preconditions_from_restriction(restriction,data_criteria_converter))
21
+ end
22
+ preconditions
23
+ end
24
+
25
+ # get all the preconditions for a restriction
26
+ # we need to iterate down
27
+ # restriction.preconditions
28
+ # restriction.comparison
29
+ # restriction.restriction
30
+ def self.extract_preconditions_from_restriction(restriction,data_criteria_converter)
31
+
32
+ target_id = data_criteria_converter.v1_data_criteria_by_id[restriction[:target_id]].id if restriction[:target_id]
33
+ type = restriction[:type]
34
+ if (restriction[:negation])
35
+ inverted = HQMF::TemporalReference::INVERSION[type]
36
+ if (inverted)
37
+ type = inverted
38
+ else
39
+ puts "\tdon't know how to invert #{type}"
40
+ end
41
+ end
42
+
43
+ # if we reference the measurement period, then we want to check if the reference is to the start or end of the measurement period
44
+ # if we SBS of the END of the measurement period, we want to convert that to SBE of the measurement period
45
+ if target_id == HQMF::Document::MEASURE_PERIOD_ID
46
+ references_start = {'SBS'=>'SBE','SAS'=>'SAE','EBS'=>'EBE','EAS'=>'EAE'}
47
+ references_end = {'EBE'=>'EBS','EAE'=>'EAS','SBE'=>'SBS','SAE'=>'SAS'}
48
+ if data_criteria_converter.measure_period_v1_keys[:measure_start] == restriction[:target_id] and references_end[type]
49
+ # before or after the END of the measurement period START. Convert to before or after the START of the measurement period.
50
+ # SAE of MPS => SAS of MP
51
+ type = references_end[type]
52
+ elsif data_criteria_converter.measure_period_v1_keys[:measure_end] == restriction[:target_id] and references_start[type]
53
+ # before or after the START of the measurement period END. Convert to before or after the END of the measurement period.
54
+ # SBS of MPE => SBE of MP
55
+ type = references_start[type]
56
+ end
57
+ end
58
+
59
+ value = nil
60
+ if (restriction[:range])
61
+ value = HQMF::Range.from_json(JSON.parse(restriction[:range].to_json)) if (restriction[:range])
62
+ elsif(restriction[:value])
63
+ value = HQMF::Converter::SimpleOperator.parse_value(restriction[:value])
64
+ end
65
+ field = restriction[:field]
66
+ field_code = restriction[:field_code]
67
+ field_time = restriction[:field_time]
68
+ operator = HQMF::Converter::SimpleOperator.new(HQMF::Converter::SimpleOperator.find_category(type), type, value, field, field_code, field_time)
69
+
70
+ # get the precondtions off of the restriction
71
+ children = HQMF::PreconditionConverter.parse_and_merge_preconditions(restriction[:preconditions],data_criteria_converter) if restriction[:preconditions]
72
+
73
+ if restriction[:comparison]
74
+ children ||= []
75
+ # check comparison and convert it to a precondition
76
+ comparison = convert_comparison_to_precondition(restriction[:comparison], data_criteria_converter)
77
+ children << comparison
78
+ end
79
+
80
+ # check restrictions
81
+ restrictions = extract_preconditions_from_restrictions(restriction[:restrictions], data_criteria_converter) if restriction[:restrictions]
82
+ HQMF::PreconditionConverter.apply_restrictions_to_comparisons(children, restrictions) unless restrictions.nil? or restrictions.empty?
83
+
84
+ container = nil
85
+ # check if there is a subset on the restriction
86
+ if restriction[:subset]
87
+ # if we have a subset, we want to create a Comparison Precondition for the subset and have it be the child of the operator on the restriction.
88
+ # the reason for this is that we want the order of operations to be SBS the FIRST of a data criteria, rather than FIRST of SBS of a data criteria
89
+
90
+ subset_type = restriction[:subset]
91
+ subset_operator = HQMF::Converter::SimpleOperator.new(HQMF::Converter::SimpleOperator.find_category(subset_type), subset_type, nil)
92
+
93
+ reference = nil
94
+ # conjunction_code = "operator"
95
+ conjunction_code = nil
96
+
97
+ restriction = HQMF::Converter::SimpleRestriction.new(subset_operator, target_id)
98
+ restriction.preconditions = children
99
+
100
+ comparison_precondition = HQMF::Converter::SimplePrecondition.new(nil, [restriction], reference, conjunction_code, false)
101
+ comparison_precondition.klass = HQMF::Converter::SimplePrecondition::COMPARISON
102
+
103
+ container = HQMF::Converter::SimpleRestriction.new(operator, nil, [comparison_precondition])
104
+ else
105
+ container = HQMF::Converter::SimpleRestriction.new(operator, target_id)
106
+ container.preconditions = children
107
+ end
108
+
109
+ [container]
110
+ end
111
+
112
+
113
+ # we want the comparisons to be converted to the leaf preconditions
114
+ def self.convert_comparison_to_precondition(comparison, data_criteria_converter)
115
+
116
+ data_criteria = data_criteria_converter.v1_data_criteria_by_id[comparison[:data_criteria_id]]
117
+ reference = HQMF::Reference.new(data_criteria.id)
118
+ # conjunction_code = "#{data_criteria.type.to_s.gsub(/(?:_|(\/))([a-z\d]*)/) { "#{$1}#{$2.capitalize}" }}Reference"
119
+ conjunction_code = nil
120
+
121
+ preconditions = []
122
+ if comparison[:restrictions]
123
+ # check for preconditions on restrictions
124
+ preconditions = extract_preconditions_from_restrictions(comparison[:restrictions], data_criteria_converter)
125
+ end
126
+
127
+ precondition = HQMF::Converter::SimplePrecondition.new(nil,preconditions,reference,conjunction_code, false)
128
+ precondition.klass = HQMF::Converter::SimplePrecondition::COMPARISON
129
+
130
+ if (comparison[:subset])
131
+ # create a restriction for a comparison subset... this is for things like first, second, etc.
132
+ type = comparison[:subset]
133
+ operator = HQMF::Converter::SimpleOperator.new(HQMF::Converter::SimpleOperator.find_category(type), type, nil)
134
+ restriction = HQMF::Converter::SimpleRestriction.new(operator, reference.id, nil)
135
+ precondition.preconditions ||= []
136
+ precondition.preconditions << restriction
137
+ end
138
+
139
+ precondition
140
+ end
141
+
142
+ # flatten a tree of preconditions into an array... if we are doing something like a count, we just want the flat list
143
+ def self.flatten_v2_preconditions(preconditions)
144
+ flattened = []
145
+ preconditions.each do |precondition|
146
+ if (precondition.reference and precondition.has_preconditions?)
147
+ raise "don't know how to handle a condition with a reference that has preconditions" if (precondition.reference and precondition.has_preconditions?)
148
+ end
149
+ if (precondition.reference)
150
+ flattened << precondition
151
+ else
152
+ flattened.concat(flatten_v2_preconditions(precondition.preconditions))
153
+ end
154
+ end
155
+ flattened
156
+ end
157
+
158
+ end
159
+ end
@@ -0,0 +1,35 @@
1
+ module HQMF
2
+
3
+ module Converter
4
+
5
+ class SimpleDataCriteria < HQMF::DataCriteria
6
+
7
+ attr_accessor :precondition_id
8
+
9
+ def self.from_data_criteria(data_criteria)
10
+ description = get_description(data_criteria)
11
+
12
+ HQMF::Converter::SimpleDataCriteria.new(data_criteria.id, data_criteria.title, data_criteria.display_name, description, data_criteria.code_list_id,
13
+ data_criteria.children_criteria, data_criteria.derivation_operator, data_criteria.definition,data_criteria.status, data_criteria.value, data_criteria.field_values,
14
+ data_criteria.effective_time, data_criteria.inline_code_list,data_criteria.negation,data_criteria.negation_code_list_id,data_criteria.temporal_references, data_criteria.subset_operators, data_criteria.specific_occurrence,data_criteria.specific_occurrence_const)
15
+ end
16
+
17
+ def assign_precondition(precondtion_id)
18
+ return if (@precondtion_id == precondtion_id)
19
+ raise "Cannot assign a second precondition to a data criteria" if @precondition_id
20
+ @precondition_id = precondtion_id
21
+ @id = "#{@id}_precondition_#{precondtion_id}"
22
+ end
23
+
24
+ def self.get_description(data_criteria)
25
+ description = data_criteria.description
26
+ status = ", #{data_criteria.status.titleize}" if data_criteria.status
27
+ description = "#{data_criteria.definition.titleize}#{status}: #{data_criteria.description}" if (data_criteria.description.downcase.match /#{data_criteria.definition.titleize.downcase}/).nil? and (data_criteria.description.downcase.match /#{data_criteria.status}/).nil?
28
+ description
29
+ end
30
+
31
+ end
32
+ end
33
+
34
+
35
+ end
@@ -0,0 +1,89 @@
1
+ module HQMF
2
+
3
+ module Converter
4
+
5
+ class SimpleOperator
6
+
7
+ TEMPORAL = 'TEMPORAL'
8
+ SUMMARY = 'SUMMARY'
9
+ UNKNOWN = 'UNKNOWN'
10
+
11
+ VALUE_FIELD_TIMES = {
12
+ 'FACILITY_LOCATION_START' => 'FACILITY_LOCATION_ARRIVAL_DATETIME',
13
+ 'FACILITY_LOCATION_END' => 'FACILITY_LOCATION_DEPARTURE_DATETIME'
14
+ }
15
+
16
+
17
+ attr_accessor :type, :value, :category, :field, :field_code, :field_time
18
+
19
+ def initialize(category, type, value, field = nil, field_code=nil, field_time=nil)
20
+ @category = category
21
+ @type = type
22
+ @value = value
23
+ @field = field
24
+ @field_code = field_code
25
+ @field_time = field_time
26
+ end
27
+
28
+ def temporal?
29
+ category == TEMPORAL
30
+ end
31
+ def summary?
32
+ category == SUMMARY
33
+ end
34
+
35
+ def to_json
36
+ json = {}
37
+ json[:category] = @category if @category
38
+ json[:type] = @type if @type
39
+ json[:field] = @field if @field
40
+ json[:field_code] = @field_code if @field_code
41
+ json[:value] = @value.to_json if @value
42
+ json
43
+ end
44
+
45
+ def field_value_key
46
+ key = HQMF::DataCriteria::VALUE_FIELDS[field_code]
47
+ key = VALUE_FIELD_TIMES["#{key}_#{field_time.to_s.upcase}"] if (field_time)
48
+ raise "unsupported field value: #{field_code}, #{field}" unless key
49
+ key
50
+ end
51
+
52
+ def self.parse_value(value)
53
+ return nil unless value
54
+ return value if value.is_a? String
55
+ if (value[:value])
56
+ # values should be inclusive since we will be asking if it equals the value, ranther than being part of a range
57
+ # if it's an offset we do not care that it is inclusive
58
+ val = HQMF::Value.from_json(JSON.parse(value.to_json))
59
+ val.inclusive=true
60
+ val
61
+ elsif (value[:high] or value[:low])
62
+ HQMF::Range.from_json(JSON.parse(value.to_json))
63
+ elsif (value[:type] == 'CD')
64
+ HQMF::Coded.from_json(JSON.parse(value.to_json))
65
+ elsif (value[:type] == 'ANYNonNull')
66
+ HQMF::AnyValue.from_json(JSON.parse(value.to_json))
67
+ else
68
+ raise "Unexpected value format: #{value.to_json}"
69
+ end
70
+ end
71
+
72
+ def self.find_category(type)
73
+ return TEMPORAL if HQMF::TemporalReference::TYPES.include? type
74
+ return SUMMARY if HQMF::SubsetOperator::TYPES.include? type
75
+ return UNKNOWN
76
+ end
77
+
78
+
79
+
80
+ end
81
+
82
+
83
+
84
+ end
85
+
86
+
87
+
88
+
89
+ end
@@ -0,0 +1,10 @@
1
+ module HQMF
2
+
3
+ module Converter
4
+
5
+ class SimplePopulationCriteria < HQMF::PopulationCriteria
6
+ attr_accessor :stratification_id
7
+
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,63 @@
1
+ module HQMF
2
+
3
+ module Converter
4
+
5
+ class SimplePrecondition < HQMF::Precondition
6
+
7
+ COMPARISON = "COMPARISON"
8
+ PRECONDITION = "PRECONDITION"
9
+
10
+ attr_accessor :klass, :processed, :subset_comparison
11
+
12
+ def initialize(id, preconditions,reference,conjunction_code,negation)
13
+ super(id, preconditions,reference,conjunction_code,negation)
14
+ @id = @@ids.next if (@id.nil?)
15
+ @klass = PRECONDITION
16
+ end
17
+
18
+ def to_json
19
+ json = super
20
+ # json[:klass] = @klass
21
+ json
22
+ end
23
+
24
+ def comparison?
25
+ @klass == COMPARISON
26
+ end
27
+ def restriction?
28
+ false
29
+ end
30
+
31
+ def has_preconditions?
32
+ preconditions and !preconditions.empty?
33
+ end
34
+
35
+ def restrictions
36
+ preconditions.select {|precondition| precondition.restriction?}
37
+ end
38
+
39
+ def reference=(reference)
40
+ @reference = reference
41
+ end
42
+
43
+ def delete_converted_restrictions!
44
+ preconditions.delete_if {|precondition| precondition.restriction? and precondition.converted}
45
+ end
46
+
47
+ # Simple class to issue monotonically increasing integer identifiers
48
+ class Counter
49
+ def initialize
50
+ @count = 0
51
+ end
52
+
53
+ def next
54
+ @count+=1
55
+ end
56
+ end
57
+ @@ids = Counter.new
58
+
59
+ end
60
+ end
61
+
62
+
63
+ end
@@ -0,0 +1,64 @@
1
+ module HQMF
2
+
3
+ module Converter
4
+
5
+ class SimpleRestriction
6
+
7
+ include HQMF::Conversion::Utilities
8
+
9
+ attr_accessor :operator, :target, :preconditions, :negation, :converted, :generated_data_criteria
10
+ def initialize(operator, target, preconditions = [])
11
+ @operator = operator
12
+ @target = target
13
+ @preconditions = preconditions
14
+ end
15
+
16
+ # Create a new population criteria from a JSON hash keyed off symbols
17
+ def self.from_json(json)
18
+ raise "not implemented"
19
+ end
20
+
21
+ def klass
22
+ "RESTRICTION"
23
+ end
24
+
25
+ def comparison?
26
+ false
27
+ end
28
+ def restriction?
29
+ true
30
+ end
31
+
32
+ def has_preconditions?
33
+ preconditions and !preconditions.empty?
34
+ end
35
+
36
+ def single_target?
37
+ !target.nil?
38
+ end
39
+ def multi_target?
40
+ has_preconditions?
41
+ end
42
+
43
+ def restrictions
44
+ preconditions.select {|precondition| precondition.restriction?}
45
+ end
46
+
47
+ def to_json
48
+ x = nil
49
+ json = {}
50
+ json[:klass] = klass
51
+ json[:operator] = @operator.to_json if @operator
52
+ json[:target] = @target if @target
53
+ json[:negation] = @negation if @negation
54
+ if (@preconditions)
55
+ json[:preconditions] = x if x = json_array(@preconditions)
56
+ end
57
+ json
58
+ end
59
+
60
+ end
61
+ end
62
+
63
+
64
+ end
@@ -0,0 +1,91 @@
1
+ module HQMF
2
+ # Class for converting an HQMF 1.0 representation to an HQMF 2.0 representation
3
+ class ComparisonConverter
4
+
5
+ def initialize(data_criteria_converter)
6
+ @data_criteria_converter = data_criteria_converter
7
+ end
8
+
9
+ def convert_comparisons(population_criteria)
10
+ population_criteria.each do |population|
11
+ walk_up_tree(population.preconditions)
12
+ end
13
+ end
14
+
15
+ def walk_up_tree(preconditions)
16
+ preconditions.each do |precondition|
17
+ if (has_child_comparison(precondition))
18
+ walk_up_tree(precondition.preconditions)
19
+ end
20
+ if (precondition.comparison? && !precondition.processed)
21
+ new_data_criteria = nil
22
+ # duplicate the data criteria referenced by the comparision (unless it's the measurement period. we don't modify the measurement period)
23
+ if precondition.reference and precondition.reference.id != HQMF::Document::MEASURE_PERIOD_ID
24
+ data_criteria = @data_criteria_converter.v2_data_criteria_by_id[precondition.reference.id]
25
+ new_data_criteria = @data_criteria_converter.duplicate_data_criteria(data_criteria, precondition.id)
26
+ precondition.reference.id = new_data_criteria.id
27
+ end
28
+ # add restrictions to the duplicated data criteria
29
+ if precondition.has_preconditions?
30
+ restrictions = precondition.restrictions
31
+ # we want to process summary operators first since they can create new data criteria
32
+ restrictions.sort! {|left, right| (right.operator.summary? and !left.operator.summary?) ? 1 : 0 }
33
+ restrictions.each do |restriction|
34
+ operator = restriction.operator
35
+ # check if the data criteria has been changed by either a grouping addition or an operator
36
+ if (precondition.reference and (new_data_criteria == nil or new_data_criteria.id != precondition.reference.id))
37
+ new_data_criteria = @data_criteria_converter.v2_data_criteria_by_id[precondition.reference.id]
38
+ end
39
+ if (operator.temporal?)
40
+ HQMF::OperatorConverter.apply_temporal(new_data_criteria, precondition, restriction, @data_criteria_converter)
41
+ elsif(operator.summary?)
42
+ HQMF::OperatorConverter.apply_summary(new_data_criteria, precondition, restriction, @data_criteria_converter)
43
+ else
44
+ case operator.type
45
+ when 'REFR'
46
+ if operator.field.downcase == 'status'
47
+ # only set the status if we don't have one. We trust the template ID statuses more than the restrictions
48
+ new_data_criteria.status ||= operator.value.code
49
+ elsif operator.field.downcase == 'result value' or operator.field.downcase == 'result'
50
+ puts "\tREFR result value is nil: #{new_data_criteria.title}" if (operator.value.nil?)
51
+ new_data_criteria.value = operator.value
52
+ else
53
+ new_data_criteria.field_values ||= {}
54
+ new_data_criteria.field_values[operator.field_value_key] = operator.value
55
+ end
56
+ restriction.converted=true
57
+ when 'RSON'
58
+ new_data_criteria.negation_code_list_id = operator.value.code_list_id
59
+ new_data_criteria.negation=true
60
+ restriction.converted=true
61
+ when 'SUBJ'
62
+ new_data_criteria.field_values ||= {}
63
+ new_data_criteria.field_values[operator.field_value_key] = operator.value
64
+ restriction.converted=true
65
+ else
66
+ puts "\tOperator is unknown: #{operator.type}"
67
+ restriction.converted=true
68
+ end
69
+ end
70
+ end
71
+ precondition.delete_converted_restrictions!
72
+ precondition.processed = true
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+ def has_child_comparison(node)
79
+ value = false
80
+ node.preconditions.each do |precondition|
81
+ if (precondition.comparison?)
82
+ value ||= true
83
+ elsif precondition.has_preconditions?
84
+ value ||= has_child_comparison(precondition)
85
+ end
86
+ end if node.preconditions
87
+ value
88
+ end
89
+
90
+ end
91
+ end