Class: SimpleXml::Precondition

Inherits:
Object
  • Object
show all
Includes:
Utilities
Defined in:
lib/model/precondition.rb

Constant Summary collapse

FUNCTIONAL_OP =
'functionalOp'
LOGICAL_OP =
'logicalOp'
SET_OP =
'setOp'
TEMPORAL_OP =
'relationalOp'
DATA_CRITERIA_OP =
'elementRef'
SUB_TREE =
'subTreeRef'
SATISFIES_ALL =
'SATISFIES ALL'
SATISFIES_ANY =
'SATISFIES ANY'
INTERSECTION =
'INTERSECT'
UNION =
'UNION'
AGE_AT =
'AGE AT'
DATETIMEDIFF =
'DATETIMEDIFF'

Constants included from Utilities

Utilities::MEASURE_ATTRIBUTES_MAP

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Utilities

#attr_val, attr_val, build_value, #children_of, #comments_on, #create_age_timing, #create_birthdate_criteria

Constructor Details

#initialize(entry, doc, negated = false) ⇒ Precondition

Returns a new instance of Precondition.



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/model/precondition.rb', line 23

def initialize(entry, doc, negated=false)
  @doc = doc
  @entry = entry
  entry = nil
  @id = HQMF::Counter.instance.next
  @preconditions = []
  @negation = negated
  @negated_logical = false
  @subset = nil

  if @entry.name == FUNCTIONAL_OP
    handle_functional
  end

  # if we have a subset then we want this to be a grouping data criteria
  if @entry.name == LOGICAL_OP && @subset.nil?
    handle_logical
  elsif @entry.name == SET_OP && @subset.nil?
    handle_set_op
  elsif  @entry.name == TEMPORAL_OP && @entry["type"] == "FULFILLS"
    handle_fulfills
  elsif @entry.name == TEMPORAL_OP && @subset.nil?
    handle_temporal
  elsif attr_val('@type') == DATETIMEDIFF
    handle_grouping_functional
  elsif (attr_val('@type') == SATISFIES_ALL || attr_val('@type') == SATISFIES_ANY) && @subset.nil?
    handle_satisfies
  elsif @entry.name == DATA_CRITERIA_OP || @subset
    handle_data_criteria
  elsif @entry.name == SUB_TREE
    handle_sub_tree
  else
    raise "unknown precondition type: #{@entry.name}"
  end

  @comments = comments_on(@entry) unless comments_on(@entry).empty?
end

Instance Attribute Details

#commentsObject

Returns the value of attribute comments.



21
22
23
# File 'lib/model/precondition.rb', line 21

def comments
  @comments
end

#conjunction_codeObject (readonly)

Returns the value of attribute conjunction_code.



20
21
22
# File 'lib/model/precondition.rb', line 20

def conjunction_code
  @conjunction_code
end

#idObject (readonly)

Returns the value of attribute id.



20
21
22
# File 'lib/model/precondition.rb', line 20

def id
  @id
end

#negated_logicalObject

Returns the value of attribute negated_logical.



21
22
23
# File 'lib/model/precondition.rb', line 21

def negated_logical
  @negated_logical
end

#negationObject (readonly)

Returns the value of attribute negation.



20
21
22
# File 'lib/model/precondition.rb', line 20

def negation
  @negation
end

#preconditionsObject

Returns the value of attribute preconditions.



21
22
23
# File 'lib/model/precondition.rb', line 21

def preconditions
  @preconditions
end

#referenceObject

Returns the value of attribute reference.



21
22
23
# File 'lib/model/precondition.rb', line 21

def reference
  @reference
end

Instance Method Details

#copy(other) ⇒ Object



297
298
299
300
301
302
303
304
# File 'lib/model/precondition.rb', line 297

def copy(other)
  @id = other.id
  @preconditions = other.preconditions
  @reference = other.reference
  @conjunction_code = other.conjunction_code
  # do not copy negation... we want the negation from the parent to remain
  #@negation = other.negation
end

#handle_age_atObject



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/model/precondition.rb', line 84

def handle_age_at
  # find the birthdate QDM element, if it exists
  birthdate_hqmf_id = nil
  @doc.source_data_criteria.each do |sdc|
    birthdate_hqmf_id = sdc.hqmf_id if sdc.definition == 'patient_characteristic_birthdate'
  end

  # if it doesn't, create one and add it to the document
  if birthdate_hqmf_id.nil?
    criteria = create_birthdate_criteria
    birthdate_hqmf_id = criteria.hqmf_id
    @doc.source_data_criteria << criteria
    @doc.criteria_map[criteria.hqmf_id] = criteria
  end

  @entry = create_age_timing(birthdate_hqmf_id, children_of(@entry).first, attr_val('@operatorType'), attr_val('@quantity'), attr_val('@unit'))
end

#handle_data_criteriaObject



228
229
230
231
# File 'lib/model/precondition.rb', line 228

def handle_data_criteria
  criteria = DataCriteria.get_criteria(@entry, @id, @doc, @subset, nil, true)
  @reference = Reference.new(criteria.id)
end

#handle_fulfillsObject



215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/model/precondition.rb', line 215

def handle_fulfills
  type = attr_val('@type')
  children = children_of(@entry)
  left_child = children[0]
  right_child = children[1]

  right = DataCriteria.get_criteria(right_child, @id, @doc, @subset, type, false)
  left = DataCriteria.get_criteria(left_child, @id, @doc, @subset, nil, true)
  left.field_values ||= {}
  left.field_values["FLFS"] = TypedReference.new(right.id,"FLFS","")
  @reference = Reference.new(left.id)
end

#handle_functionalObject



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/model/precondition.rb', line 61

def handle_functional
  type = attr_val('@type')
  case type
  when 'NOT'
    @negation = true
  when SATISFIES_ALL, SATISFIES_ANY
    ''
  when AGE_AT
    handle_age_at
  else
    comparison = attr_val('@operatorType')
    quantity = attr_val('@quantity')
    unit = attr_val('@unit')
    @subset = SubsetOperator.new(type, Utilities.build_value(comparison, quantity, unit))
  end

  children = children_of(@entry)
  if children.count == 1
    @entry = children.first
  end

end

#handle_grouping_functionalObject



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/model/precondition.rb', line 163

def handle_grouping_functional
  children = children_of(@entry)
  @preconditions = []
  children.collect do |precondition|
    @preconditions << Precondition.new(precondition, @doc)
  end
  @preconditions.select! {|p| !p.preconditions.empty? || p.reference }

  subsets = [@subset]
  # check if we have a functional wrapped by another
  if @subset.type != attr_val('@type')
    handle_functional
    subsets.unshift @subset
  end
  criteria = DataCriteria.convert_precondition_to_criteria(self, @doc, @subset.type)
  criteria.derivation_operator = HQMF::DataCriteria::XPRODUCT

  criteria.subset_operators ||= []
  criteria.subset_operators.concat subsets

  @preconditions = []
  @reference = Reference.new(criteria.id)
end

#handle_logicalObject



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/model/precondition.rb', line 135

def handle_logical
  @conjunction_code = translate_type(attr_val('@type'))
  if is_negated_logical(attr_val('@type'))
    @negation = true
    @negated_logical = true
  end
  @preconditions = []
  children_of(@entry).collect do |precondition|
    # if we have a negated child with multiple logical children, then we want to make sure we don't infer an extra AND
    if negation_with_logical_children?(precondition)
      children_of(precondition).each do |child|
        @preconditions << Precondition.new(child, @doc, true)
      end
    else
      @preconditions << Precondition.new(precondition, @doc)
    end
  end
  
  @preconditions.select! {|p| !p.preconditions.empty? || p.reference }
end

#handle_negations(parent) ⇒ Object



312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
# File 'lib/model/precondition.rb', line 312

def handle_negations(parent)
  negations = []
  if @negated_logical
    # wrap the negation with an extra level so that it aligns with the display for non-logical negations
    @negation = false
    @preconditions = [ParsedPrecondition.new(HQMF::Counter.instance.next, @preconditions, nil, conjunction_code, true)]
    # we do not need to do a translation if it's a negated logical (it is already in the correct form)
  else
    @preconditions.delete_if {|p| negations << p if p.negation && !p.negated_logical}
    unless (negations.empty?)
      negations.each {|p| p.instance_variable_set(:@negation, false) }
      inverted_conjunction_code = HQMF::Precondition::INVERSIONS[conjunction_code]
      
      # if we only have negations, then do not create an extra element
      if @preconditions.empty?
        @negation = true
        @conjunction_code = inverted_conjunction_code
        @preconditions.concat negations
      else
        # create a new inverted element for the subset of the children that are negated
        @preconditions << ParsedPrecondition.new(HQMF::Counter.instance.next, negations, nil, inverted_conjunction_code, true)
      end
    end
  end
  @preconditions.each do |p|
    p.handle_negations(self)
  end
end

#handle_satisfiesObject



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/model/precondition.rb', line 102

def handle_satisfies
  children = children_of(@entry)
  # use shift to remove the left hand entry defining the left hand of the satisfies statement (we don't need that)
  children.shift
  @preconditions = []
  children.collect do |precondition|
    # if we have a negated child with multiple logical children, then we want to make sure we don't infer an extra AND
    if negation_with_logical_children?(precondition)
      children_of(precondition).each do |child|
        @preconditions << Precondition.new(child, @doc, true)
      end
    else
      @preconditions << Precondition.new(precondition, @doc)
    end
  end

  @preconditions.select! {|p| !p.preconditions.empty? || p.reference }

  if attr_val('@type') == SATISFIES_ALL
    criteria = DataCriteria.convert_precondition_to_criteria(self, @doc, 'satisfiesAll')
    criteria.derivation_operator = HQMF::DataCriteria::INTERSECT
    criteria.instance_variable_set('@definition','satisfies_all')
  elsif attr_val('@type') == SATISFIES_ANY
    criteria = DataCriteria.convert_precondition_to_criteria(self, @doc, 'satisfiesAny')
    criteria.derivation_operator = HQMF::DataCriteria::UNION
    criteria.instance_variable_set('@definition','satisfies_any')
  end
  criteria.instance_variable_set('@description', children[0]['displayName'] || attr_val('@displayName'))

  @preconditions = []
  @reference = Reference.new(criteria.id)
end

#handle_set_opObject



156
157
158
159
160
161
# File 'lib/model/precondition.rb', line 156

def handle_set_op
  handle_logical
  criteria = DataCriteria.convert_precondition_to_criteria(self, @doc, @conjunction_code)
  @reference = Reference.new(criteria.id)
  @preconditions = []
end

#handle_sub_treeObject



233
234
235
236
# File 'lib/model/precondition.rb', line 233

def handle_sub_tree
  sub_tree = @doc.sub_tree_map[attr_val('@id')]
  copy(sub_tree.precondition)
end

#handle_temporalObject



187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/model/precondition.rb', line 187

def handle_temporal
  type = attr_val('@type')
  comparison = attr_val('@operatorType')
  quantity = attr_val('@quantity')
  unit = attr_val('@unit')
  children = children_of(@entry)

  left_child = children[0]
  right_child = children[1]

  right = DataCriteria.get_criteria(right_child, @id, @doc, @subset, type, false)

  temporal = TemporalReference.new(type, comparison, quantity, unit, right)

  # check to see if we are referening MP start or end and make sure that the timing is appropriate
  update_temporal_mp_reference(temporal, right)

  if (left_child.name == LOGICAL_OP)
    # make this the left and then push down the right... we have no current node to construct
    copy(Precondition.new(left_child, @doc))
    push_down_temporal(self, temporal)
  else
    left = DataCriteria.get_criteria(left_child, @id, @doc, @subset, nil, true)
    left.add_temporal(temporal)
    @reference = Reference.new(left.id)
  end
end

#is_negated_logical(type) ⇒ Object



267
268
269
270
271
272
273
274
275
276
# File 'lib/model/precondition.rb', line 267

def is_negated_logical(type)
  case type
  when 'andNot'
    true
  when 'orNot'
    true
  else
    false
  end
end

#negation_with_logical_children?(precondition) ⇒ Boolean

Returns:

  • (Boolean)


259
260
261
262
263
264
265
# File 'lib/model/precondition.rb', line 259

def negation_with_logical_children?(precondition)
  if precondition.name == FUNCTIONAL_OP && precondition.at_xpath('@type').value == 'NOT'
    children = children_of(precondition)
    return children.length > 1
  end
  false
end

#push_down_comments(precondition, comments) ⇒ Object



246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/model/precondition.rb', line 246

def push_down_comments(precondition, comments)
  return if comments.empty?
  if precondition.preconditions.empty?
    if @doc.data_criteria(precondition.reference.id).comments
      comments.each {|c| @doc.data_criteria(precondition.reference.id).comments << c unless @doc.data_criteria(precondition.reference.id).comments.include? c}
    else
      @doc.data_criteria(precondition.reference.id).comments = comments
    end
  else
    precondition.preconditions.each {|p| push_down_comments(p, comments)}
  end
end

#push_down_temporal(precondition, temporal) ⇒ Object



238
239
240
241
242
243
244
# File 'lib/model/precondition.rb', line 238

def push_down_temporal(precondition, temporal)
  if precondition.preconditions.empty?
    @doc.data_criteria(precondition.reference.id).push_down_temporal(temporal, @doc)
  else
    precondition.preconditions.each {|p| push_down_temporal(p, temporal)}
  end
end

#to_modelObject



306
307
308
309
310
# File 'lib/model/precondition.rb', line 306

def to_model
  pcs = preconditions.collect {|p| p.to_model}
  mr = reference ? reference.to_model : nil
  HQMF::Precondition.new(id, pcs, mr, conjunction_code, negation, @comments)
end

#translate_type(type) ⇒ Object



278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
# File 'lib/model/precondition.rb', line 278

def translate_type(type)
  case type
  when 'and'
    HQMF::Precondition::ALL_TRUE
  when 'intersection'
    SimpleXml::Precondition::INTERSECTION
  when 'andNot'
    HQMF::Precondition::AT_LEAST_ONE_TRUE
  when 'or'
    HQMF::Precondition::AT_LEAST_ONE_TRUE
  when 'union'
    SimpleXml::Precondition::UNION
  when 'orNot'
    HQMF::Precondition::ALL_TRUE
  else
    raise "Unknown population criteria type #{type}"
  end
end

#update_temporal_mp_reference(temporal, right) ⇒ Object



341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
# File 'lib/model/precondition.rb', line 341

def update_temporal_mp_reference(temporal, right)

  if (right.id== HQMF::Document::MEASURE_PERIOD_ID)
    references_start = {'SBS'=>'SBE','SAS'=>'SAE','EBS'=>'EBE','EAS'=>'EAE','SCW'=>'SCWE'}
    references_end = {'EBE'=>'EBS','EAE'=>'EAS','SBE'=>'SBS','SAE'=>'SAS','ECW'=>'ECWS'}

    if @doc.measure_period_map[right.hqmf_id] == :measure_period_start && references_end[temporal.type]
      # before or after the END of the measurement period START.  Convert to before or after the START of the measurement period.
      # SAE of MPS => SAS of MP
      # ends concurrent with measurement period START. Convert to concurrent with START of measurement period.
      # ECW of MPS => ECWS
      temporal.type = references_end[temporal.type]
    elsif @doc.measure_period_map[right.hqmf_id] == :measure_period_end && references_start[temporal.type]
      # before or after the START of the measurement period END.  Convert to before or after the END of the measurement period.
      # SBS of MPE => SBE of MP
      # starts concurrent with measurement period END. Convert to concurrent with END of measurement period.
      # SCW of MPE => SCWE
      temporal.type = references_start[temporal.type]
    end
  end
end