Class: HQMF::Measure::LogicExtractor

Inherits:
Object
  • Object
show all
Defined in:
lib/measures/logic_extractor.rb

Constant Summary collapse

POPULATION_MAP =
{
  'STRAT' => 'Stratification',
  'IPP' => 'Initial Patient Population',
  'DENOM' => 'Denominator',
  'NUMER' => 'Numerator',
  'DENEXCEP' => 'Denominator Exceptions',
  'DENEX' => 'Denominator Exclusions',
  'MSRPOPL' => 'Measure Population',
  'OBSERV' => 'Measure Observations'
}
AGGREGATOR_MAP =
{
  'MEAN' => 'Mean of',
  'MEDIAN' => 'Median of'
}
LOGIC_OPERATOR_MAP =
{ 'XPRODUCT' => 'AND' }
SET_OPERATOR_MAP =
{
  'INTERSECT' => 'Intersection of',
  'UNION' => 'Union of'
}
SUBSET_MAP =
{
  'COUNT' => 'COUNT',
  'FIRST' => 'FIRST',
  'SECOND' => 'SECOND',
  'THIRD' => 'THIRD',
  'FOURTH' => 'FOURTH',
  'FIFTH' => 'FIFTH',
  'RECENT' => 'MOST RECENT',
  'LAST' => 'LAST',
  'MIN' => 'MIN',
  'MAX' => 'MAX',
  'MEAN' => 'MEAN',
  'MEDIAN' => 'MEDIAN',
  'TIMEDIFF' => 'Difference between times',
  'DATEDIFF' => 'Difference between dates',
  'DATETIMEDIFF' => 'Difference between date/times'
}
OPERATOR_MAP =
{
  'satisfies_all' => 'SATISFIES ALL',
  'satisfies_any' => 'SATISFIES ANY'
}
TIMING_MAP =
{
  'DURING' => 'During',
  'OVERLAP' => 'Overlaps',
  'SBS' => 'Starts Before Start of',
  'SAS' => 'Starts After Start of',
  'SBE' => 'Starts Before End of',
  'SAE' => 'Starts After End of',
  'EBS' => 'Ends Before Start of',
  'EAS' => 'Ends After Start of',
  'EBE' => 'Ends Before End of',
  'EAE' => 'Ends After End of',
  'SDU' => 'Starts During',
  'EDU' => 'Ends During',
  'ECW' => 'Ends Concurrent with',
  'SCW' => 'Starts Concurrent with',
  'ECWS' => 'Ends Concurrent with Start of',
  'SCWE' => 'Starts Concurrent with End of',
  'SBCW' => 'Starts Before or Concurrent with',
  'SBCWE' => 'Starts Before or Concurrent with End of',
  'SACW' => 'Starts After or Concurrent with',
  'SACWE' => 'Starts After or Concurrent with End of',
  'SBDU' => 'Starts Before or During',
  'EBCW' => 'Ends Before or Concurrent with',
  'EBCWS' => 'Ends Before or Concurrent with Start of',
  'EACW' => 'Ends After or Concurrent with',
  'EACWS' => 'Ends After or Concurrent with Start of',
  'EADU' => 'Ends After or During',
  'CONCURRENT' => 'Concurrent with'
}
UNIT_MAP =
{
  'a' => 'year',
  'mo' => 'month',
  'wk' => 'week',
  'd' => 'day',
  'h' => 'hour',
  'min' => 'minute',
  's' => 'second'
}
CONJUNCTION_MAP =
{
  'allTrue' => 'AND',
  'atLeastOneTrue' => 'OR'
}
FLIP_CONJUNCTION_MAP =
{
  'AND' => 'OR',
  'OR' => 'AND'
}
SATISFIES_DEFINITIONS =
['satisfies_all','satisfies_any']
INTERVAL_DEFINITIONS =
['IVL_PQ', 'IVL_TS']
INTERVAL_TYPES_DEFINITIONS =
['PQ', 'TS']

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.get_measure_logic_diff(measure, other, by_population = false) ⇒ Object



483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
# File 'lib/measures/logic_extractor.rb', line 483

def self.get_measure_logic_diff(measure, other, by_population=false)
  return if other.nil?
  measure_totals = {:total => 0, :deletions => 0, :insertions => 0, :unchanged => 0}
  unless by_population
    compute_diff(get_measure_logic_text(measure), get_measure_logic_text(other), measure_totals)
  else
    measure_text = get_measure_logic_text(measure, by_population)
    other_text = get_measure_logic_text(other, by_population)
    verify_populations(measure_text, other_text)
    diff = {:cms_id => measure.cms_id, :populations => [], :totals => {}}
    measure_text.each_with_index do |population, index|
      population_totals = {:total => 0, :deletions => 0, :insertions => 0, :unchanged => 0}
      first = population[:lines]
      second = other_text.at(index)[:lines]
      diff[:populations] << compute_diff(first, second, population_totals, population[:code])
      measure_totals[:total] += population_totals[:total]
      measure_totals[:deletions] += population_totals[:deletions]
      measure_totals[:insertions] += population_totals[:insertions]
      measure_totals[:unchanged] += population_totals[:unchanged]
    end
    diff[:totals] = measure_totals
    diff
  end
end

.get_measure_logic_text(measure, by_population = false) ⇒ Object

Diff methods ###



458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
# File 'lib/measures/logic_extractor.rb', line 458

def self.get_measure_logic_text(measure, by_population=false)
  return '' if measure.measure_logic.blank?
  unless by_population
    lines = ''
    measure.measure_logic.each do |population|
      population[:lines].each do |line|
        lines << "#{line}#{"\n" unless line.ends_with?("\n")}"
      end
    end
    lines
  else
    measure_logic_text = []
    measure.measure_logic.each do |population|
      measure_logic = {:code => population[:code], :lines => []}
      lines = ''
      population[:lines].each do |line|
        lines << "#{line}#{"\n" unless line.ends_with?("\n")}"
      end
      measure_logic[:lines] = lines
      measure_logic_text << measure_logic
    end
    measure_logic_text
  end
end

Instance Method Details

#data_criteria_logic(reference, expand_variable = false, hide_title = nil, indent = nil) ⇒ Object



230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
# File 'lib/measures/logic_extractor.rb', line 230

def data_criteria_logic(reference, expand_variable=false, hide_title=nil, indent=nil)
  results = []
  indent ||= ""

  data_criteria = @measure.data_criteria[reference]
  unless data_criteria
    data_criteria = @measure.source_data_criteria[reference]
  end
  data_criteria['key'] ||= reference


  if !data_criteria['field_values'].blank?
    data_criteria['field_values'].each do |key, field|
      if field.blank?
        field = {}
        data_criteria['field_values'][key] = field
      end
      field['key'] = key
      field['key_title'] = translate_field(key)
    end
  end
  # handle field values on data_criteria
  is_satisfies = SATISFIES_DEFINITIONS.include?(data_criteria['definition'])
  is_derived = data_criteria['type'].to_s == 'derived'
  has_children = is_derived && (!data_criteria['variable'] || expand_variable)
  is_set_op = SET_OPERATOR_MAP.keys.include?(data_criteria['derivation_operator'])

  if data_criteria['subset_operators']
    data_criteria['subset_operators'].each do |so|
      results.concat subset_operator_logic(so)
    end
  end

  if has_children
    if is_satisfies
      results.concat satisfies_logic(data_criteria['key'], indent+"\t")
    else
      if data_criteria['children_criteria']
        if is_set_op
          unless expand_variable
            results << "\n#{indent+"\t\t"}#{translate_set_operator(data_criteria['derivation_operator'])}:"
          end
        end
        line = "#{indent}"
        data_criteria['children_criteria'].each_with_index do |cc, cc_ind|
          unless is_set_op
            line << "#{translate_logic_operator(data_criteria['derivation_operator'])} : "
          end
          data_criteria_logic(cc, false, nil, indent+"\t").each_with_index do |dc, dc_ind|
            results << "#{line}\t#{dc}"
          end
        end
        if data_criteria['temporal_references']
          data_criteria['temporal_references'].each do |tr|
            results << "#{indent+"\t\t"}#{temporal_reference_logic(tr).first}"
          end
        end
      end
    end
  else
    line = "#{indent}"
    line = "" if hide_title && !results.blank?
    unless hide_title
      if data_criteria['specific_occurrence']
        line << "Occurrence #{data_criteria['specific_occurrence']}: "
      end
      line << "$" if data_criteria['variable']
      line << data_criteria['description']
    end
    if data_criteria['value']
      unless data_criteria['type'].to_s == 'characteristic'
        line << "(result#{value_logic(data_criteria['value'])[0]})"
      end
    end
    if data_criteria['field_values']
      line << " ( "
      data_criteria['field_values'].each do |field, fv|
        line << fv['key_title'] if fv['key_title']
        line << "#{value_logic(fv)[0]}" if fv['type'] != 'ANYNonNull'
      end
      line << " )"
    end
    if data_criteria['negation']
      line << " ( Not Done : #{translate_oid(data_criteria['negation_code_list_id'])} )"
    end

    results << line
    if data_criteria['temporal_references']
      if data_criteria['temporal_references'].length > 1
        data_criteria['temporal_references'].each do |tr|
          results << "#{indent}#{temporal_reference_logic(tr)}"
        end
      else
        data_criteria['temporal_references'].each do |tr|
          results << temporal_reference_logic(tr).first
        end
      end
    end
  end
  results.last << "\n" unless results.last.end_with?("\n")

  results
end

#population_criteria_logic(population) ⇒ Object



334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
# File 'lib/measures/logic_extractor.rb', line 334

def population_criteria_logic(population)
  results = []

  root_precondition = population['preconditions'][0] if population['preconditions']
  aggregator = population['aggregator']
  ( comments = root_precondition.try(:[],'comments') || [] ) | ( population['comments'] || [] )
  results.concat comments if comments
  unless root_precondition.blank?
    if root_precondition['preconditions']
      root_precondition['preconditions'].each do |precondition|
        results.concat precondition_logic(precondition, root_precondition, root_precondition['negation'] || false)
      end
    else
      unless aggregator.blank?
        results << "\t#{translate_aggregator(aggregator)}\n"
      end
      results.concat data_criteria_logic(root_precondition['reference'])
    end
  else
    results << "\tNone\n"
  end

  results
end

#population_logic(measure) ⇒ Object



359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
# File 'lib/measures/logic_extractor.rb', line 359

def population_logic(measure)
  results = []
  @measure = measure
  populations = @measure.population_criteria.keys

  populations.each do |population|
    population_results = {:code => population, :lines => []}
    population_results[:lines] << "\n#{translate_population(population)}\n"
    population_results[:lines].concat population_criteria_logic(@measure.population_criteria[population])
    results << population_results
  end
  variables_text = variables_logic
  unless variables_text.blank?
    variable_results = {:code => "VARIABLES", :lines => variables_text }
    results << variable_results
  end

  results
end

#precondition_logic(precondition, parent_precondition = nil, parent_negation = false, indent = nil) ⇒ Object



95
96
97
98
99
100
101
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
# File 'lib/measures/logic_extractor.rb', line 95

def precondition_logic(precondition, parent_precondition=nil, parent_negation=false, indent=nil)
  results = []
  precondition_key = "precondition_#{precondition['id']}"
  parent_preocondition_key = "precondition_#{parent_precondition['id']}"
  conjunction = translate_conjunction(parent_precondition['conjunction_code'])
  suppress = true if precondition['negation'] && precondition['preconditions'] && precondition['preconditions'].length == 1
  conjunction = FLIP_CONJUNCTION_MAP[conjunction] if parent_negation
  comments = precondition['comments'] || []
  if precondition['reference']
    data_criteria = @measure['data_criteria'][precondition['reference']]
    comments.concat data_criteria['comments'] || []
  end
  indent ||= ""
  indent += "\t"

  line = ""
  unless suppress
    if comments
      results.concat comments
    end
    line << "#{indent}#{conjunction}"
    line << " NOT" if parent_negation
    line << ":"
    results << line
  end
  if precondition['preconditions']
    results.last << "\n" unless results.blank?
    precondition['preconditions'].each do |p|
      results.concat precondition_logic(p, precondition, precondition['negation'], indent)
    end
  else
    results.last << " "
    results.concat data_criteria_logic(precondition['reference'])
  end

  results
end

#satisfies_logic(reference, indent = nil) ⇒ Object



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/measures/logic_extractor.rb', line 190

def satisfies_logic(reference, indent=nil)
  results = []
  indent ||= ""

  data_criteria = @measure.data_criteria[reference]
  root_criteria = @measure.data_criteria[data_criteria['children_criteria'][0]]

  line = ""
  line << "Occurrence #{root_criteria['specific_occurrence']}:" if root_criteria['specific_occurrence']
  line << "$" if root_criteria['variable']

  line << "#{root_criteria['description']} #{translate_operator(data_criteria['definition'])}\n"
  results << line
  data_criteria['children_criteria'].each do |cc|
    results << "#{data_criteria_logic(cc, false, true, indent+"\t").join('')}"
  end
  if data_criteria['temporal_references']
    data_criteria['temporal_references'].each do |tr|
      results << "#{indent+"\t"}#{temporal_reference_logic(tr).join()}"
    end
  end

  results
end

#subset_operator_logic(subset_operator) ⇒ Object



133
134
135
136
137
138
139
140
141
142
143
# File 'lib/measures/logic_extractor.rb', line 133

def subset_operator_logic(subset_operator)
  results = []
  line = "#{translate_subset(subset_operator['type'])}"
  if subset_operator['value']
    unless subset_operator['value']['type'].to_s == 'ANYNonNull'
      line << "#{value_logic(subset_operator['value'])[0]}"
    end
  end
  line << ": "
  results << line
end

#temporal_reference_logic(temporal_reference) ⇒ Object



215
216
217
218
219
220
221
222
223
224
225
226
227
228
# File 'lib/measures/logic_extractor.rb', line 215

def temporal_reference_logic(temporal_reference)
  results = []

  line = ""
  line << "#{value_logic(temporal_reference['range'])[0]}" if temporal_reference['range']
  line << " #{translate_timing(temporal_reference['type'])} "
  if temporal_reference['reference'].to_s == "MeasurePeriod"
    line << "\"Measurement Period\""
  else
    line << "#{data_criteria_logic(temporal_reference['reference']).join()}"
  end

  results << line
end

#translate_aggregator(code) ⇒ Object



404
405
406
# File 'lib/measures/logic_extractor.rb', line 404

def translate_aggregator(code)
  AGGREGATOR_MAP[code]
end

#translate_conjunction(conjunction) ⇒ Object



452
453
454
# File 'lib/measures/logic_extractor.rb', line 452

def translate_conjunction(conjunction)
  CONJUNCTION_MAP[conjunction]
end

#translate_date(date) ⇒ Object



448
449
450
# File 'lib/measures/logic_extractor.rb', line 448

def translate_date(date)
  date
end

#translate_field(field_key) ⇒ Object



416
417
418
# File 'lib/measures/logic_extractor.rb', line 416

def translate_field(field_key)
  HQMF::DataCriteria::FIELDS[field_key][:title]
end

#translate_logic_operator(conjunction) ⇒ Object



408
409
410
# File 'lib/measures/logic_extractor.rb', line 408

def translate_logic_operator(conjunction)
  LOGIC_OPERATOR_MAP[conjunction]
end

#translate_oid(oid) ⇒ Object



440
441
442
443
444
445
446
# File 'lib/measures/logic_extractor.rb', line 440

def translate_oid(oid)
  begin
    @measure.value_sets.where({:oid => oid}).first.display_name
  rescue
    oid
  end
end

#translate_operator(definition) ⇒ Object



424
425
426
# File 'lib/measures/logic_extractor.rb', line 424

def translate_operator(definition)
  OPERATOR_MAP[definition]
end

#translate_population(code) ⇒ Object



396
397
398
399
400
401
402
# File 'lib/measures/logic_extractor.rb', line 396

def translate_population(code)
  if match = code.match(/(.*)_(\d+)/)
    "#{POPULATION_MAP[match[1]]} #{match[2].to_i + 1}"
  else
    POPULATION_MAP[code]
  end
end

#translate_set_operator(conjunction) ⇒ Object



412
413
414
# File 'lib/measures/logic_extractor.rb', line 412

def translate_set_operator(conjunction)
  SET_OPERATOR_MAP[conjunction]
end

#translate_subset(subset) ⇒ Object



420
421
422
# File 'lib/measures/logic_extractor.rb', line 420

def translate_subset(subset)
  SUBSET_MAP[subset]
end

#translate_timing(code) ⇒ Object



428
429
430
# File 'lib/measures/logic_extractor.rb', line 428

def translate_timing(code)
  TIMING_MAP[code].downcase
end

#translate_unit(unit, value) ⇒ Object



432
433
434
435
436
437
438
# File 'lib/measures/logic_extractor.rb', line 432

def translate_unit(unit, value)
  if UNIT_MAP[unit]
    UNIT_MAP[unit] + ( value.to_i > 1 ? 's' : '' )
  else
    unit
  end
end

#value_logic(value, range_comparison = nil) ⇒ Object



145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/measures/logic_extractor.rb', line 145

def value_logic(value, range_comparison=nil)
  results = []

  is_range = INTERVAL_DEFINITIONS.include?(value['type'])
  is_equivalent = is_range && value['high'] && value['low'] && (value['high']['value'] == value['low']['value']) && value['high']['inclusive?'] && value['low']['inclusive?']
  is_value = INTERVAL_TYPES_DEFINITIONS.include?(value['type'])
  is_any_non_null = value['type'].to_s == 'ANYNonNull'
  is_ts = value['type'].to_s == 'TS'

  line = ""
  unless is_any_non_null
    if is_value
      line << "#{range_comparison || ''}"
      line << "=" if value['inclusive?']
      if is_ts
        line << translate_date(value['value'])
      else
        line << " #{value['value']}"
      end
      line << " #{translate_unit(value['unit'], value['value'])}"
    else
      if is_range
        if value['high'] && value['low']
          if is_equivalent
            line = value_logic(value['low'])[0]
          else
            line = "#{value_logic(value['low'], '>')[0]} and #{value_logic(value['high'], '<')[0]}"
          end
        else
          if value['high']
            line = " #{value_logic(value['high'], '<')[0]}"
          else
            if value['low']
              line = " #{value_logic(value['low'], '>')[0]}"
            end
          end
        end
      else
        line << ": #{translate_oid(value['code_list_id'])}" if value['type'].to_s == 'CD'
      end
    end
  end
  results << line
end

#variables_logicObject



379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
# File 'lib/measures/logic_extractor.rb', line 379

def variables_logic
  results = []

  variables = @measure['source_data_criteria'].select{ |key, attrs| attrs['variable'] == true }
  has_variables = variables.length > 0

  if has_variables
    results << "\nVariables\n"
    variables.each do |title, v|
      results << "\t$#{v['description']} = \n"
      results.concat data_criteria_logic(v['source_data_criteria'], true, nil, "\t")
    end
  end

  results
end