hqmf-parser 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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,52 @@
1
+ module HQMF1
2
+
3
+ class Expression
4
+
5
+ include HQMF1::Utilities
6
+
7
+ attr_reader :text, :value, :type
8
+
9
+ def initialize(entry)
10
+ @entry = entry
11
+ @text = @entry.xpath('./*/cda:derivationExpr').text.strip
12
+ type = attr_val('./*/cda:value/@xsi:type')
13
+ case type
14
+ when 'IVL_PQ'
15
+ @value = Range.new(@entry.xpath('./*/cda:value'))
16
+ when 'PQ'
17
+ @value = Value.new(@entry.xpath('./*/cda:value'))
18
+ when 'ANYNonNull'
19
+ @value = HQMF::AnyValue.new
20
+ else
21
+ raise "Unknown expression value type #{type}"
22
+ end
23
+ @type = match_type
24
+ end
25
+
26
+ def match_type
27
+ case @text
28
+ when /^COUNT(.*)$/
29
+ "COUNT"
30
+ when /^MIN(.*)$/
31
+ "MIN"
32
+ when /^MAX(.*)$/
33
+ "MAX"
34
+ when /^DATEDIFF(.*)$/
35
+ "DATEDIFF"
36
+ else
37
+ raise "unknown expression type: #{@text}"
38
+ end
39
+ end
40
+
41
+ def to_json
42
+
43
+ json = build_hash(self, [:text,:type])
44
+ json[:value] = self.value.to_json if self.value
45
+ json
46
+
47
+ end
48
+
49
+ end
50
+
51
+
52
+ end
@@ -0,0 +1,79 @@
1
+ module HQMF1
2
+ # Represents an HQMF population criteria
3
+ class PopulationCriteria
4
+
5
+ include HQMF1::Utilities
6
+
7
+ attr_reader :preconditions, :entry, :doc
8
+ attr_accessor :id, :hqmf_id, :stratification_id
9
+
10
+ # Create a new population criteria from the supplied HQMF entry
11
+ # @param [Nokogiri::XML::Element] the HQMF entry
12
+ def initialize(entry, doc)
13
+ @doc = doc
14
+ @entry = entry
15
+ @id = attr_val('cda:observation/cda:id/@root').upcase
16
+ @preconditions = @entry.xpath('./*/cda:sourceOf[@typeCode="PRCN"]').collect do |entry|
17
+ pc = Precondition.new(entry, nil, @doc)
18
+ if pc.preconditions.length==0 && !pc.comparison && pc.restrictions.length==0
19
+ nil
20
+ else
21
+ pc
22
+ end
23
+ end.compact
24
+ end
25
+
26
+ # Get the code for the population criteria
27
+ # @return [String] the code (e.g. IPP, DEMON, NUMER, DENEX, EXCEP)
28
+ def code
29
+ value = attr_val('cda:observation/cda:value/@code')
30
+ # exclusion population criteria has id of DENOM with actionNegationInd of true
31
+ # special case this to simply handling
32
+ if attr_val('cda:observation/@actionNegationInd')=='true'
33
+ value = HQMF::PopulationCriteria::DENEX
34
+ end
35
+ # replace measure population with NUMER. MSRPOPL is used in continuous variable calculations.
36
+ value = HQMF::PopulationCriteria::NUMER if value == 'MSRPOPL'
37
+ # replace DENEXCEP with EXCEP. DENEXCEP is used by the MAT instead of EXCEP as required by HQMF V2 and QRDA.
38
+ value = HQMF::PopulationCriteria::EXCEP if value == 'DENEXCEP'
39
+ value.upcase
40
+ end
41
+
42
+ # Get the id for the population criteria, used elsewhere in the HQMF document to
43
+ # refer to this criteria
44
+ # @return [String] the id
45
+ def id
46
+ @id
47
+ end
48
+
49
+ def title
50
+ attr_val('cda:observation/cda:value/@displayName')
51
+ end
52
+
53
+ def reference
54
+ reference = attr_val('./cda:observation/cda:sourceOf[@typeCode="PRCN"]/cda:observation[@classCode="OBS"]/cda:id/@root')
55
+ reference = reference.upcase if reference
56
+ reference
57
+ end
58
+
59
+ def to_json
60
+
61
+ json = {}
62
+ self.preconditions.compact.each do |precondition|
63
+ json[:preconditions] ||= []
64
+ json[:preconditions] << precondition.to_json
65
+ end
66
+
67
+ json[:id] = id
68
+ json[:title] = title
69
+ json[:code] = code
70
+ json[:hqmf_id] = hqmf_id if hqmf_id
71
+ json[:stratification_id] = stratification_id if stratification_id
72
+ json[:reference] = reference
73
+
74
+ {self.code => json}
75
+
76
+ end
77
+
78
+ end
79
+ end
@@ -0,0 +1,89 @@
1
+ module HQMF1
2
+
3
+ class Precondition
4
+
5
+ include HQMF1::Utilities
6
+
7
+ attr_reader :restrictions, :preconditions, :subset, :expression, :id
8
+
9
+ def initialize(entry, parent, doc)
10
+ @doc = doc
11
+ @entry = entry
12
+ @id = attr_val('./*/cda:id/@root')
13
+ @restrictions = []
14
+
15
+ local_subset = attr_val('./cda:subsetCode/@code')
16
+ if local_subset
17
+ @subset = local_subset
18
+ end
19
+ #@subset = attr_val('./cda:subsetCode/@code')
20
+
21
+ local_restrictions = @entry.xpath('./*/cda:sourceOf[@typeCode!="PRCN" and @typeCode!="COMP"]').collect do |entry|
22
+ Restriction.new(entry, self, @doc)
23
+ end
24
+ @restrictions.concat(local_restrictions)
25
+
26
+ @expression = Expression.new(@entry) if @entry.at_xpath('./*/cda:derivationExpr')
27
+
28
+ @preconditions = @entry.xpath('./*/cda:sourceOf[@typeCode="PRCN"]').collect do |entry|
29
+ Precondition.new(entry, self, @doc)
30
+ end
31
+ end
32
+
33
+ # Get the conjunction code, e.g. AND, OR
34
+ # @return [String] conjunction code
35
+ def conjunction
36
+ attr_val('./cda:conjunctionCode/@code')
37
+ end
38
+
39
+ # Return whether the precondition is negated (true) or not (false)
40
+ def negation
41
+ if @entry.at_xpath('./cda:act[@actionNegationInd="true"]')
42
+ is_negation_rationale = (comparison.restrictions.map {|restriction| restriction.type }).include? 'RSON' if comparison
43
+ if is_negation_rationale
44
+ false
45
+ else
46
+ true
47
+ end
48
+ else
49
+ false
50
+ end
51
+ end
52
+
53
+ def comparison
54
+ comparison_def = @entry.at_xpath('./*/cda:sourceOf[@typeCode="COMP"]')
55
+ if comparison_def
56
+ data_criteria_id = attr_val('./*/cda:id/@root')
57
+ @comparison = Comparison.new(data_criteria_id, comparison_def, self, @doc)
58
+ end
59
+ end
60
+
61
+ def first_comparison
62
+ if comparison
63
+ return comparison
64
+ elsif @preconditions
65
+ @preconditions.each do |precondition|
66
+ first = precondition.first_comparison
67
+ if first
68
+ return first
69
+ end
70
+ end
71
+ end
72
+ return nil
73
+ end
74
+
75
+ def to_json
76
+
77
+ json = build_hash(self, [:id,:conjunction,:negation,:subset])
78
+ json[:comparison] = self.comparison.to_json if self.comparison
79
+ json[:expression] = self.expression.to_json if self.expression
80
+ json[:preconditions] = json_array(self.preconditions)
81
+ json[:restrictions] = json_array(self.restrictions)
82
+ json
83
+
84
+ end
85
+
86
+ end
87
+
88
+
89
+ end
@@ -0,0 +1,65 @@
1
+ module HQMF1
2
+ # Represents a bound within a HQMF pauseQuantity, has a value, a unit and an
3
+ # inclusive/exclusive indicator
4
+ class Value
5
+ include HQMF1::Utilities
6
+
7
+ def initialize(entry)
8
+ @entry = entry
9
+ end
10
+
11
+ def value
12
+ attr_val('./@value')
13
+ end
14
+
15
+ def unit
16
+ attr_val('./@unit')
17
+ end
18
+
19
+ def inclusive?
20
+ case attr_val('./@inclusive')
21
+ when 'true'
22
+ true
23
+ else
24
+ false
25
+ end
26
+ end
27
+
28
+ def to_json
29
+ build_hash(self, [:value,:unit,:inclusive?])
30
+ end
31
+ end
32
+
33
+ # Represents a HQMF pauseQuantity which can have low and high bounds
34
+ class Range
35
+ include HQMF1::Utilities
36
+ attr_reader :low, :high
37
+
38
+ def initialize(entry)
39
+ @entry = entry
40
+ if @entry
41
+ @low = optional_value('./cda:low')
42
+ @high = optional_value('./cda:high')
43
+ end
44
+ end
45
+
46
+ def to_json
47
+ json = {}
48
+ json[:low] = self.low.to_json if self.low
49
+ json[:high] = self.high.to_json if self.high
50
+ json
51
+ end
52
+
53
+ private
54
+
55
+ def optional_value(xpath)
56
+ value_def = @entry.at_xpath(xpath)
57
+ if value_def
58
+ Value.new(value_def)
59
+ else
60
+ nil
61
+ end
62
+ end
63
+
64
+ end
65
+ end
@@ -0,0 +1,157 @@
1
+ module HQMF1
2
+ # Represents a restriction on the allowable values of a data item
3
+ class Restriction
4
+
5
+ include HQMF1::Utilities
6
+
7
+ attr_reader :range, :comparison, :restrictions, :subset, :preconditions
8
+ attr_accessor :from_parent
9
+
10
+ def initialize(entry, parent, doc)
11
+ @doc = doc
12
+ @entry = entry
13
+ @restrictions = []
14
+
15
+ range_def = @entry.at_xpath('./cda:pauseQuantity')
16
+ if range_def
17
+ @range = Range.new(range_def)
18
+ end
19
+
20
+ local_restrictions = @entry.xpath('./*/cda:sourceOf[@typeCode!="PRCN" and @typeCode!="COMP"]').collect do |entry|
21
+ Restriction.new(entry, self, @doc)
22
+ end
23
+
24
+ @restrictions.concat(local_restrictions)
25
+
26
+ local_subset = attr_val('./cda:subsetCode/@code')
27
+ if local_subset
28
+ @subset = local_subset
29
+ end
30
+
31
+ #@subset = attr_val('./cda:subsetCode/@code')
32
+
33
+ comparison_def = @entry.at_xpath('./*/cda:sourceOf[@typeCode="COMP"]')
34
+ if comparison_def
35
+ data_criteria_id = attr_val('./*/cda:id/@root')
36
+ data_criteria_id = comparison_def.at_xpath('./*/cda:id/@root').value if (data_criteria_id.nil? and comparison_def.at_xpath('./*/cda:id/@root'))
37
+ @comparison = Comparison.new(data_criteria_id, comparison_def, self, @doc)
38
+ end
39
+
40
+ @preconditions = @entry.xpath('./*/cda:sourceOf[@typeCode="PRCN"]').collect do |entry|
41
+ # create a dummy parent with a single restriction copied from self minus the
42
+ # nested preconditions to avoid an infinite loop
43
+ prior_comparison = nil
44
+ if parent.class==HQMF1::Precondition
45
+ prior_comparison = parent.first_comparison
46
+ else
47
+ prior_comparison = @comparsion
48
+ end
49
+ current_restriction = OpenStruct.new(
50
+ 'range' => @range,
51
+ 'comparison' => prior_comparison,
52
+ 'restrictions' => [],
53
+ 'preconditions' => [],
54
+ 'subset' => @subset,
55
+ 'type' => type,
56
+ 'target_id' => target_id,
57
+ 'field' => field,
58
+ 'field_code' => field_code,
59
+ 'field_time' => field_time,
60
+ 'value' => value)
61
+ all_restrictions = []
62
+ all_restrictions.concat @restrictions
63
+ all_restrictions << current_restriction
64
+ parent = OpenStruct.new(
65
+ 'restrictions' => all_restrictions,
66
+ 'subset' => @subset
67
+ )
68
+ p = Precondition.new(entry, parent, @doc)
69
+
70
+ end
71
+
72
+ end
73
+
74
+ # The type of restriction, e.g. SBS, SBE etc
75
+ def type
76
+ attr_val('./@typeCode')
77
+ end
78
+
79
+ # is this type negated? true or false
80
+ def negation
81
+ attr_val('./@inversionInd') == "true"
82
+ end
83
+
84
+ # The id of the data criteria or measurement property that the value
85
+ # will be compared against
86
+ def target_id
87
+ attr_val('./*/cda:id/@root')
88
+ end
89
+
90
+ def field
91
+ attr_val('./cda:observation/cda:code/@displayName')
92
+ end
93
+
94
+ def field_code
95
+ attr_val('./cda:observation/cda:code/@code') || attr_val('./cda:encounter/cda:participant/cda:roleParticipant/@classCode')
96
+ end
97
+
98
+ def field_time
99
+ effectiveTime = @entry.at_xpath('./cda:observation/cda:effectiveTime') || @entry.at_xpath('./cda:encounter/cda:participant/cda:roleParticipant/cda:effectiveTime')
100
+
101
+ time = nil
102
+ if effectiveTime
103
+ time = :start if effectiveTime.at_xpath('./cda:low')
104
+ time = :end if effectiveTime.at_xpath('./cda:high')
105
+ end
106
+ time
107
+ end
108
+
109
+
110
+ def value
111
+ value = nil
112
+ type = attr_val('./cda:observation/cda:value/@xsi:type') || 'CD'
113
+ case type
114
+ when 'IVL_PQ'
115
+ value = HQMF1::Range.new(@entry.xpath('./cda:observation/cda:value'))
116
+ when 'PQ'
117
+ value = HQMF1::Value.new(@entry.xpath('./cda:observation/cda:value'))
118
+ when 'CD'
119
+ if field && field.downcase == 'status'
120
+ code = attr_val('./cda:observation/cda:value/@displayName').downcase
121
+ value = HQMF::Coded.for_single_code('status',code,code)
122
+ elsif attr_val('./cda:observation/cda:value/@code')
123
+ oid = attr_val('./cda:observation/cda:value/@code')
124
+ title = attr_val('./cda:observation/cda:value/@displayName')
125
+ value = HQMF::Coded.for_code_list(oid,title)
126
+ elsif attr_val('./cda:encounter/cda:participant/cda:roleParticipant/cda:code/@code')
127
+ oid = attr_val('./cda:encounter/cda:participant/cda:roleParticipant/cda:code/@code')
128
+ title = attr_val('./cda:encounter/cda:participant/cda:roleParticipant/cda:code/@displayName')
129
+ value = HQMF::Coded.for_code_list(oid,title)
130
+ end
131
+ when 'ANYNonNull'
132
+ value = HQMF::AnyValue.new
133
+ else
134
+ raise "Unknown restriction value type #{type}"
135
+ end if type
136
+ value
137
+ end
138
+
139
+ def to_json
140
+ return nil if from_parent
141
+ json = build_hash(self, [:subset,:type,:target_id,:field,:field_code,:from_parent, :negation, :field_time])
142
+ json[:range] = range.to_json if range
143
+ if value
144
+ if value.is_a? String
145
+ json[:value] = value
146
+ else
147
+ json[:value] = value.to_json
148
+ end
149
+ end
150
+ json[:comparison] = comparison.to_json if comparison
151
+ json[:restrictions] = json_array(self.restrictions)
152
+ json[:preconditions] = json_array(self.preconditions)
153
+ json
154
+ end
155
+
156
+ end
157
+ end
@@ -0,0 +1,41 @@
1
+ module HQMF1
2
+ module Utilities
3
+
4
+ include HQMF::Conversion::Utilities
5
+
6
+ # Utility function to handle optional attributes
7
+ # @param xpath an XPath that identifies an XML attribute
8
+ # @return the value of the attribute or nil if the attribute is missing
9
+ def attr_val(xpath)
10
+ attr = @entry.at_xpath(xpath)
11
+ if attr
12
+ attr.value
13
+ else
14
+ nil
15
+ end
16
+ end
17
+
18
+ def clean_json(json)
19
+ json.reject!{|k,v| v.nil? || (v.respond_to?(:empty?) && v.empty?)}
20
+ end
21
+
22
+ def clean_json_recursive(json)
23
+ json.each do |k,v|
24
+ if v.is_a? Hash
25
+ clean_json_recursive(v)
26
+ clean_json(v)
27
+ elsif v.is_a? Array
28
+ v.each do |e|
29
+ if e.is_a? Hash
30
+ clean_json_recursive(e)
31
+ clean_json(e)
32
+ end
33
+ end
34
+ end
35
+
36
+ end
37
+ clean_json(json)
38
+ end
39
+
40
+ end
41
+ end