hqmf2js 1.3.0 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -13
- data/.travis.yml +1 -1
- data/Gemfile +1 -25
- data/Gemfile.lock +170 -146
- data/app/assets/javascripts/crosswalk.js.coffee +17 -19
- data/app/assets/javascripts/custom_calculations.js.coffee +44 -17
- data/app/assets/javascripts/hqmf_util.js.coffee +559 -161
- data/app/assets/javascripts/logging_utils.js.coffee +6 -4
- data/app/assets/javascripts/patient_api_extension.js.coffee +41 -9
- data/app/assets/javascripts/specifics.js.coffee +163 -69
- data/hqmf2js.gemspec +7 -12
- data/lib/assets/javascripts/libraries/map_reduce_utils.js +151 -64
- data/lib/generator/characteristic.js.erb +23 -12
- data/lib/generator/codes_to_json.rb +1 -1
- data/lib/generator/data_criteria.js.erb +15 -3
- data/lib/generator/derived_data.js.erb +5 -0
- data/lib/generator/execution.rb +41 -11
- data/lib/generator/js.rb +74 -41
- data/lib/generator/patient_data.js.erb +1 -1
- data/lib/hqmf2js.rb +0 -1
- data/lib/hquery/engine.rb +3 -1
- data/lib/tasks/convert.rake +20 -12
- data/test/fixtures/NQF59New.json +1423 -0
- data/test/fixtures/fulfills.xml +917 -0
- data/test/fixtures/patients/larry_vanderman.json +573 -654
- data/test/{simplecov.rb → simplecov_init.rb} +0 -0
- data/test/test_helper.rb +2 -3
- data/test/unit/cmd_test.rb +145 -19
- data/test/unit/codes_to_json_test.rb +12 -12
- data/test/unit/custom_calculations_test.rb +2 -6
- data/test/unit/effective_date_test.rb +3 -4
- data/test/unit/erb_context_test.rb +12 -12
- data/test/unit/filter_by_reference_test.rb +39 -0
- data/test/unit/hqmf_from_json_javascript_test.rb +2 -1
- data/test/unit/hqmf_javascript_test.rb +12 -13
- data/test/unit/js_object_test.rb +2 -2
- data/test/unit/library_function_test.rb +210 -42
- data/test/unit/specifics_test.rb +402 -321
- metadata +57 -15
- data/config/warble.rb +0 -144
@@ -9,7 +9,9 @@ class @Logger
|
|
9
9
|
if @enable_rationale and result? and typeof(result.isTrue) == 'function'
|
10
10
|
if result.isTrue() and result.length
|
11
11
|
json_results = _.map(result,(item) -> {id: item.id, json: item.json})
|
12
|
-
|
12
|
+
if result.specificContext?
|
13
|
+
specific_ids = result.specificContext.flattenToIds()
|
14
|
+
@rationale[id] = {results: json_results, specifics: specific_ids }
|
13
15
|
else
|
14
16
|
@rationale[id] = result.isTrue()
|
15
17
|
|
@@ -132,9 +134,9 @@ class @Logger
|
|
132
134
|
);
|
133
135
|
|
134
136
|
# Wrap selected HQMF Util functions
|
135
|
-
hqmf.SpecificsManagerSingleton.prototype.intersectAll = _.wrap(hqmf.SpecificsManagerSingleton.prototype.intersectAll, (func, boolVal, values, negate=false, episodeIndices) ->
|
136
|
-
func = _.bind(func, this, boolVal, values, negate, episodeIndices)
|
137
|
-
result = func(boolVal, values, negate, episodeIndices)
|
137
|
+
hqmf.SpecificsManagerSingleton.prototype.intersectAll = _.wrap(hqmf.SpecificsManagerSingleton.prototype.intersectAll, (func, boolVal, values, negate=false, episodeIndices, options) ->
|
138
|
+
func = _.bind(func, this, boolVal, values, negate, episodeIndices, options)
|
139
|
+
result = func(boolVal, values, negate, episodeIndices, options)
|
138
140
|
Logger.info("Intersecting (#{values.length}):")
|
139
141
|
for value in values
|
140
142
|
Logger.logSpecificContext(value)
|
@@ -54,6 +54,17 @@ hQuery.Encounter::lengthOfStay = (unit) ->
|
|
54
54
|
ivl_ts = this.asIVL_TS()
|
55
55
|
ivl_ts.low.difference(ivl_ts.high, unit)
|
56
56
|
|
57
|
+
hQuery.Encounter::transferTime = () ->
|
58
|
+
transfer = (@json['transferFrom'] || @json['transferTo'])
|
59
|
+
time = transfer.time if transfer
|
60
|
+
if time
|
61
|
+
hQuery.dateFromUtcSeconds(time)
|
62
|
+
else
|
63
|
+
if @json['transferTo']
|
64
|
+
@endDate()
|
65
|
+
else
|
66
|
+
@startDate()
|
67
|
+
|
57
68
|
hQuery.AdministrationTiming::dosesPerDay = () ->
|
58
69
|
#figure out the units and value and calculate
|
59
70
|
p = this.period()
|
@@ -64,9 +75,9 @@ hQuery.AdministrationTiming::dosesPerDay = () ->
|
|
64
75
|
1/p.value()
|
65
76
|
|
66
77
|
|
67
|
-
hQuery.Fulfillment::daysInRange = (dateRange,
|
78
|
+
hQuery.Fulfillment::daysInRange = (dateRange,dose, dosesPerDay) ->
|
68
79
|
# this will give us the number of days this fullfilment was for
|
69
|
-
totalDays = this.quantityDispensed().value()/
|
80
|
+
totalDays = this.quantityDispensed().value()/dose/dosesPerDay
|
70
81
|
totalDays = 0 if isNaN(totalDays)
|
71
82
|
endDate = new Date(this.dispenseDate().getTime() + (totalDays*60*60*24*1000))
|
72
83
|
high = if dateRange && dateRange.high then dateRange.high.asDate() else endDate
|
@@ -94,18 +105,39 @@ hQuery.Fulfillment::daysInRange = (dateRange,doesPerDay) ->
|
|
94
105
|
# date range
|
95
106
|
hQuery.Medication::fulfillmentTotals = (dateRange)->
|
96
107
|
dpd = this.administrationTiming().dosesPerDay()
|
108
|
+
dose = this.dose().scalar
|
97
109
|
this.fulfillmentHistory().reduce (t, s) ->
|
98
|
-
t + s.daysInRange(dateRange,dpd)
|
110
|
+
t + s.daysInRange(dateRange,dose,dpd)
|
99
111
|
, 0
|
100
|
-
|
112
|
+
|
113
|
+
# returns cumulativeMedicationDuration in terms of days
|
101
114
|
hQuery.Medication::cumulativeMedicationDuration = (dateRange) ->
|
102
|
-
#assuming that the dose is the same across fills and that fills is stated in individual
|
103
|
-
#doses not total amount. Will need to flush this out more at a later point in time.
|
104
|
-
#Considering that liquid meds are probaly dispensed as total volume ex 325ml with a dose of
|
105
|
-
#say 25ml per dose. Will definatley need to revisit this.
|
106
|
-
this.
|
115
|
+
#assuming that the dose is the same across fills and that fills is stated in individual
|
116
|
+
#doses not total amount. Will need to flush this out more at a later point in time.
|
117
|
+
#Considering that liquid meds are probaly dispensed as total volume ex 325ml with a dose of
|
118
|
+
#say 25ml per dose. Will definatley need to revisit this.
|
119
|
+
if this.administrationTiming() && this.dose() && @json['fulfillmentHistory']
|
120
|
+
this.fulfillmentTotals(dateRange)
|
121
|
+
else if this.administrationTiming() && this.allowedAdministrations()
|
122
|
+
# this happens if we have a Medication, Order.
|
123
|
+
cumulativeMedicationDuration = this.allowedAdministrations() / this.administrationTiming().dosesPerDay()
|
124
|
+
# need to do in case of divide by zero error
|
125
|
+
cumulativeMedicationDuration = 0 if isNaN(cumulativeMedicationDuration)
|
126
|
+
cumulativeMedicationDuration
|
127
|
+
|
128
|
+
class hQuery.Reference
|
129
|
+
constructor: (@json) ->
|
130
|
+
referenced_id: -> @json["referenced_id"]
|
131
|
+
referenced_type: -> @json["reference"]
|
132
|
+
type: -> @json["type"]
|
133
|
+
|
107
134
|
|
135
|
+
hQuery.CodedEntry::references = () ->
|
136
|
+
for ref in (@json["references"] || [])
|
137
|
+
new hQuery.Reference(ref)
|
108
138
|
|
139
|
+
hQuery.CodedEntry::referencesByType = (type) ->
|
140
|
+
e for e in @references() when e.type() == type
|
109
141
|
|
110
142
|
hQuery.CodedEntry::respondTo = (functionName) ->
|
111
143
|
typeof(@[functionName]) == "function"
|
@@ -27,9 +27,22 @@ class hqmf.SpecificsManagerSingleton
|
|
27
27
|
@keyLookup[i] = occurrenceKey.id
|
28
28
|
@indexLookup[occurrenceKey.id] = i
|
29
29
|
@functionLookup[i] = occurrenceKey.function
|
30
|
-
|
31
|
-
|
32
|
-
|
30
|
+
# LDY 8/25/17
|
31
|
+
# Something changed in the MAT so the "type" is no longer included in the HQMF. The backup
|
32
|
+
# type included too much detail (OccurrenceA_... and OccurrenceB_...), which made the types
|
33
|
+
# appear different for two occurrences of the same type.
|
34
|
+
# The code below ignores "Occ..." strings within the "type". This makes it so the type will
|
35
|
+
# now appear the same where appropriate.
|
36
|
+
# Note: OccurrenceA... is used for regular instances of an occurrence. OccA... is used for
|
37
|
+
# QDM variables.
|
38
|
+
generic_type = occurrenceKey.type
|
39
|
+
match = generic_type.match(/^occ[a-z]*_(.*)/i)
|
40
|
+
if match
|
41
|
+
generic_type = match[1]
|
42
|
+
if generic_type not of @typeLookup
|
43
|
+
@typeLookup[generic_type] = []
|
44
|
+
@typeLookup[generic_type].push(i)
|
45
|
+
|
33
46
|
_generateCartisian: (allValues) ->
|
34
47
|
_.reduce(allValues, (as, bs) ->
|
35
48
|
product = []
|
@@ -42,41 +55,34 @@ class hqmf.SpecificsManagerSingleton
|
|
42
55
|
identity: ->
|
43
56
|
new hqmf.SpecificOccurrence([new Row(undefined)])
|
44
57
|
|
45
|
-
setIfNull: (events
|
46
|
-
if
|
47
|
-
|
58
|
+
setIfNull: (events) ->
|
59
|
+
# Add specifics if missing, appropriately based on the truthiness
|
60
|
+
if !events.specificContext?
|
61
|
+
if events.isTrue()
|
62
|
+
events.specificContext=hqmf.SpecificsManager.identity()
|
63
|
+
else
|
64
|
+
events.specificContext=hqmf.SpecificsManager.empty()
|
65
|
+
events
|
48
66
|
|
49
67
|
getColumnIndex: (occurrenceID) ->
|
50
68
|
columnIndex = @indexLookup[occurrenceID]
|
51
69
|
if typeof columnIndex == "undefined"
|
52
|
-
throw "Unknown occurrence identifier: "+occurrenceID
|
70
|
+
throw new Error("Unknown occurrence identifier: "+occurrenceID)
|
53
71
|
columnIndex
|
54
72
|
|
55
73
|
empty: ->
|
56
74
|
new hqmf.SpecificOccurrence([])
|
57
75
|
|
76
|
+
# Extract events for leftmost of supplied rows, returning copies with a specificRow attribute set
|
58
77
|
extractEventsForLeftMost: (rows) ->
|
59
78
|
events = []
|
60
79
|
for row in rows
|
61
|
-
|
80
|
+
for event in row.leftMostEvents()
|
81
|
+
event = new event.constructor(event.json)
|
82
|
+
event.specificRow = row
|
83
|
+
events.push(event)
|
62
84
|
events
|
63
|
-
|
64
|
-
extractEvents: (key, rows) ->
|
65
|
-
events = []
|
66
|
-
for row in rows
|
67
|
-
events.push(@extractEvent(key, row))
|
68
|
-
events
|
69
|
-
|
70
|
-
extractEvent: (key, row) ->
|
71
|
-
index = @indexLookup[key]
|
72
|
-
if index?
|
73
|
-
entry = row.values[index]
|
74
|
-
else
|
75
|
-
entry = row.tempValue
|
76
|
-
entry = new hQuery.CodedEntry(entry.json)
|
77
|
-
entry.specificRow = row
|
78
|
-
entry
|
79
|
-
|
85
|
+
|
80
86
|
intersectSpecifics: (nextPopulation, previousPopulation, occurrenceIDs) ->
|
81
87
|
# we need to pass the episode indicies all the way down through the interesection to the match function
|
82
88
|
# this must be done because we need to ensure that on intersection of populations the * does not allow an episode through
|
@@ -119,13 +125,13 @@ class hqmf.SpecificsManagerSingleton
|
|
119
125
|
validate: (intersectedPopulation) ->
|
120
126
|
intersectedPopulation.isTrue() and intersectedPopulation.specificContext.hasRows()
|
121
127
|
|
122
|
-
intersectAll: (boolVal, values, negate=false, episodeIndices) ->
|
128
|
+
intersectAll: (boolVal, values, negate=false, episodeIndices, options = {}) ->
|
123
129
|
result = new hqmf.SpecificOccurrence
|
124
130
|
# add identity row
|
125
131
|
result.addIdentityRow()
|
126
132
|
for value in values
|
127
133
|
if value.specificContext?
|
128
|
-
result = result.intersect(value.specificContext, episodeIndices)
|
134
|
+
result = result.intersect(value.specificContext, episodeIndices, options)
|
129
135
|
if negate and (!result.hasRows() or result.hasSpecifics())
|
130
136
|
result = result.negate()
|
131
137
|
result = result.compactReusedEvents()
|
@@ -153,10 +159,33 @@ class hqmf.SpecificsManagerSingleton
|
|
153
159
|
boolVal = new Boolean(true) if @occurrences.length > 0
|
154
160
|
boolVal.specificContext = result
|
155
161
|
boolVal
|
156
|
-
|
162
|
+
|
163
|
+
# Given a set of events with a specificContext, filter the events to include only those
|
164
|
+
# referenced in the specific context
|
165
|
+
filterEventsAgainstSpecifics: (events) ->
|
166
|
+
# If there are no specifics (ie identity) we return them all as-is
|
167
|
+
return events unless events.specificContext.hasSpecifics()
|
168
|
+
|
169
|
+
# Find all the events referenced in the specific context
|
170
|
+
referencedEvents = hqmf.SpecificsManager.extractEventsForLeftMost(events.specificContext.rows)
|
171
|
+
referencedEventIds = _(referencedEvents).pluck('id')
|
172
|
+
|
173
|
+
# Filter original events to only return referenced ones (and ones without an ID, likely dates)
|
174
|
+
result = _(events).select (e) -> !e.id || _(referencedEventIds).contains(e.id)
|
175
|
+
|
176
|
+
# Copy the specifics over and return the result
|
177
|
+
hqmf.SpecificsManager.maintainSpecifics(result, events)
|
178
|
+
return result
|
179
|
+
|
157
180
|
# copy the specifics parameters from an existing element onto the new value element
|
158
181
|
maintainSpecifics: (newElement, existingElement) ->
|
159
|
-
|
182
|
+
# We handle a special case: if the existing element is falsy (ie an empty result set), and the new element
|
183
|
+
# is truthy (ie a boolean true), and the specific context is the empty set (no rows), we change it to the
|
184
|
+
# identity; this can happen, for example, if the new element is checking COUNT=0 of the existing element
|
185
|
+
if newElement.isTrue() && existingElement.isFalse() && existingElement.specificContext? && !existingElement.specificContext.hasRows()
|
186
|
+
newElement.specificContext = hqmf.SpecificsManager.identity()
|
187
|
+
else
|
188
|
+
newElement.specificContext = existingElement.specificContext
|
160
189
|
newElement.specific_occurrence = existingElement.specific_occurrence
|
161
190
|
newElement
|
162
191
|
|
@@ -194,11 +223,10 @@ class hqmf.SpecificOccurrence
|
|
194
223
|
result
|
195
224
|
|
196
225
|
removeDuplicateRows: () ->
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
deduped
|
226
|
+
# Uniq rows based on each row's string transformation
|
227
|
+
uniqRows = {}
|
228
|
+
uniqRows[row.toHashKey()] = row for row in @rows
|
229
|
+
new hqmf.SpecificOccurrence(_(uniqRows).values())
|
202
230
|
|
203
231
|
# Returns a count of unique events for a supplied column index
|
204
232
|
uniqueEvents: (columnIndices) ->
|
@@ -229,21 +257,21 @@ class hqmf.SpecificOccurrence
|
|
229
257
|
value.rows = @rows.concat(other.rows)
|
230
258
|
value.removeDuplicateRows()
|
231
259
|
|
232
|
-
intersect: (other, episodeIndices) ->
|
260
|
+
intersect: (other, episodeIndices, options = {}) ->
|
233
261
|
value = new hqmf.SpecificOccurrence()
|
234
262
|
for leftRow in @rows
|
235
263
|
for rightRow in other.rows
|
236
|
-
result = leftRow.intersect(rightRow, episodeIndices)
|
264
|
+
result = leftRow.intersect(rightRow, episodeIndices, options)
|
237
265
|
value.rows.push(result) if result?
|
238
266
|
value.removeDuplicateRows()
|
239
267
|
|
240
268
|
getLeftMost: ->
|
241
|
-
|
269
|
+
specificLeftMost = undefined
|
242
270
|
for row in @rows
|
243
|
-
|
244
|
-
return undefined if
|
245
|
-
|
246
|
-
|
271
|
+
specificLeftMost = row.specificLeftMost unless specificLeftMost?
|
272
|
+
return undefined if specificLeftMost != row.specificLeftMost
|
273
|
+
specificLeftMost
|
274
|
+
|
247
275
|
negate: ->
|
248
276
|
negatedRows = []
|
249
277
|
keys = []
|
@@ -273,6 +301,22 @@ class hqmf.SpecificOccurrence
|
|
273
301
|
newRows.push(myRow) if goodRow
|
274
302
|
new hqmf.SpecificOccurrence(newRows)
|
275
303
|
|
304
|
+
# Given a set of events, return new specifics removing any rows that *do not* refer to that set of events
|
305
|
+
filterSpecificsAgainstEvents: (events) ->
|
306
|
+
# If there are no specifics (ie identity) return what we have as-is
|
307
|
+
return this unless @hasSpecifics()
|
308
|
+
|
309
|
+
# Keep and return the rows that refer to any of the provided events (via a leftMost)
|
310
|
+
rowsToKeep = _(@rows).select (row) ->
|
311
|
+
_(row.leftMostEvents()).any (leftMostEvent) ->
|
312
|
+
_(events).any (event) ->
|
313
|
+
# We consider events the same if either 1) both have ids and the ids are the same, or 2) both are
|
314
|
+
# dates, and the dates are the same
|
315
|
+
(event instanceof Date && leftMostEvent instanceof Date && event.getTime() == leftMostEvent.getTime()) ||
|
316
|
+
(event.id? && leftMostEvent.id? && event.id == leftMostEvent.id)
|
317
|
+
|
318
|
+
new hqmf.SpecificOccurrence(rowsToKeep)
|
319
|
+
|
276
320
|
hasRow: (row) ->
|
277
321
|
found = false
|
278
322
|
for myRow in @rows
|
@@ -297,10 +341,14 @@ class hqmf.SpecificOccurrence
|
|
297
341
|
|
298
342
|
finalizeEvents: (eventsContext, boundsContext) ->
|
299
343
|
result = this
|
300
|
-
result = result.intersect(eventsContext) if
|
301
|
-
result = result.intersect(boundsContext) if
|
344
|
+
result = result.intersect(eventsContext) if eventsContext?
|
345
|
+
result = result.intersect(boundsContext) if boundsContext?
|
302
346
|
result.compactReusedEvents()
|
303
347
|
|
348
|
+
# Group rows by everything except the leftmost to apply the subset only to the events from the specific
|
349
|
+
# occurrence context rows on the leftmost column. eg for "MOST RECENT: Occurrence A of Lab Result during
|
350
|
+
# Occurrence A of Encounter" we want to group by the encounter and apply the most recent to the set of
|
351
|
+
# lab results per group (ie encounter)
|
304
352
|
group: ->
|
305
353
|
groupedRows = {}
|
306
354
|
for row in @rows
|
@@ -308,21 +356,27 @@ class hqmf.SpecificOccurrence
|
|
308
356
|
groupedRows[row.groupKeyForLeftMost()].push(row)
|
309
357
|
groupedRows
|
310
358
|
|
311
|
-
COUNT: (range) ->
|
312
|
-
@applyRangeSubset(COUNT, range)
|
359
|
+
COUNT: (range, fields) ->
|
360
|
+
@applyRangeSubset(COUNT, range, fields)
|
361
|
+
|
362
|
+
MIN: (range, fields) ->
|
363
|
+
@applyRangeSubset(MIN, range, fields)
|
364
|
+
|
365
|
+
MAX: (range, fields) ->
|
366
|
+
@applyRangeSubset(MAX, range, fields)
|
313
367
|
|
314
|
-
|
315
|
-
@applyRangeSubset(
|
368
|
+
SUM: (range, fields) ->
|
369
|
+
@applyRangeSubset(SUM, range, fields)
|
316
370
|
|
317
|
-
|
318
|
-
@applyRangeSubset(
|
371
|
+
MEDIAN: (range, fields) ->
|
372
|
+
@applyRangeSubset(MEDIAN, range, fields)
|
319
373
|
|
320
|
-
applyRangeSubset: (func, range) ->
|
374
|
+
applyRangeSubset: (func, range, fields) ->
|
321
375
|
return this if !@hasSpecifics()
|
322
376
|
resultRows = []
|
323
377
|
groupedRows = @group()
|
324
378
|
for groupKey, group of groupedRows
|
325
|
-
if func(hqmf.SpecificsManager.extractEventsForLeftMost(group), range).isTrue()
|
379
|
+
if func(hqmf.SpecificsManager.extractEventsForLeftMost(group), range, null, fields).isTrue()
|
326
380
|
resultRows = resultRows.concat(group)
|
327
381
|
new hqmf.SpecificOccurrence(resultRows)
|
328
382
|
|
@@ -349,7 +403,7 @@ class hqmf.SpecificOccurrence
|
|
349
403
|
|
350
404
|
hasLeftMost: ->
|
351
405
|
for row in @rows
|
352
|
-
if row.
|
406
|
+
if row.specificLeftMost? || row.nonSpecificLeftMost?
|
353
407
|
return true
|
354
408
|
return false
|
355
409
|
|
@@ -373,12 +427,11 @@ class hqmf.SpecificOccurrence
|
|
373
427
|
|
374
428
|
class Row
|
375
429
|
# {'OccurrenceAEncounter':1, 'OccurrenceBEncounter'2}
|
376
|
-
constructor: (
|
377
|
-
throw "left most key must be a string or undefined was: #{leftMost}" if typeof(leftMost) != 'string' and typeof(leftMost) != 'undefined'
|
430
|
+
constructor: (specificLeftMost, occurrences={}) ->
|
378
431
|
@length = hqmf.SpecificsManager.occurrences.length
|
379
432
|
@values = []
|
380
|
-
@
|
381
|
-
@
|
433
|
+
@specificLeftMost = specificLeftMost
|
434
|
+
@nonSpecificLeftMost = occurrences[undefined]
|
382
435
|
for i in [0...@length]
|
383
436
|
key = hqmf.SpecificsManager.keyLookup[i]
|
384
437
|
value = occurrences[key] || hqmf.SpecificsManager.any
|
@@ -401,14 +454,25 @@ class Row
|
|
401
454
|
equals: (other) ->
|
402
455
|
equal = true;
|
403
456
|
|
404
|
-
equal &&= Row.valuesEqual(@
|
457
|
+
equal &&= Row.valuesEqual(@nonSpecificLeftMost, other.nonSpecificLeftMost)
|
405
458
|
for value,i in @values
|
406
459
|
equal &&= Row.valuesEqual(value, other.values[i])
|
407
460
|
equal
|
408
461
|
|
409
|
-
intersect: (other, episodeIndices) ->
|
410
|
-
|
411
|
-
|
462
|
+
intersect: (other, episodeIndices, options = {}) ->
|
463
|
+
|
464
|
+
# When we're calculating an actual intersection, where we're returning a set of events, we want to make sure that rows that reference
|
465
|
+
# disjoint expressions aren't combined; this isn't true if we're calculating a boolean AND, chaining temporal operators, etc
|
466
|
+
if options.considerLeftMost
|
467
|
+
# If rows being intersected have different leftMost values, with neither null, then the rows reference disjoint expressions and can't be intersected
|
468
|
+
return undefined if @specificLeftMost && other.specificLeftMost && !Row.valuesEqual(@specificLeftMost, other.specificLeftMost)
|
469
|
+
return undefined if @nonSpecificLeftMost && other.nonSpecificLeftMost && !Row.valuesEqual(@nonSpecificLeftMost, other.nonSpecificLeftMost)
|
470
|
+
# We can set the result row to leftMost + tempValue of whichever of row has it set, since they'll either be the same or one will be undefined
|
471
|
+
intersectedRow = new Row(@specificLeftMost || other.specificLeftMost, {})
|
472
|
+
intersectedRow.nonSpecificLeftMost = @nonSpecificLeftMost || other.nonSpecificLeftMost
|
473
|
+
else
|
474
|
+
intersectedRow = new Row(@specificLeftMost, {})
|
475
|
+
intersectedRow.nonSpecificLeftMost = @nonSpecificLeftMost
|
412
476
|
|
413
477
|
# if all the episodes are any, then they were not referenced by the parent population. This occurs when an intersection is done
|
414
478
|
# against the identity row. In this case we want to allow the specific occurrences through. This happens when we intersect against a measure
|
@@ -431,16 +495,20 @@ class Row
|
|
431
495
|
return true
|
432
496
|
|
433
497
|
groupKeyForLeftMost: ->
|
434
|
-
|
435
|
-
|
436
|
-
|
498
|
+
# Get the key(s) to group by, handling hash of specifics or single specific
|
499
|
+
if _.isObject(@specificLeftMost)
|
500
|
+
@groupKey(_(@specificLeftMost).chain().values().flatten().value())
|
501
|
+
else
|
502
|
+
@groupKey([@specificLeftMost])
|
503
|
+
|
504
|
+
groupKey: (keys) ->
|
505
|
+
keys = [keys] if _.isString(keys)
|
437
506
|
keyForGroup = ''
|
438
507
|
for i in [0...@length]
|
439
|
-
|
440
|
-
value = @values[i].id if @values[i] != hqmf.SpecificsManager.any
|
441
|
-
if hqmf.SpecificsManager.keyLookup[i] == key
|
508
|
+
if _(keys).include(hqmf.SpecificsManager.keyLookup[i])
|
442
509
|
keyForGroup += "X_"
|
443
510
|
else
|
511
|
+
value = if @values[i] != hqmf.SpecificsManager.any then @values[i].id else hqmf.SpecificsManager.any
|
444
512
|
keyForGroup += "#{value}_"
|
445
513
|
keyForGroup
|
446
514
|
|
@@ -481,8 +549,16 @@ class Row
|
|
481
549
|
for matchKey in matchKeys
|
482
550
|
occurrences = {}
|
483
551
|
occurrences[entryKey] = entry
|
484
|
-
occurrences[matchKey] = match
|
552
|
+
occurrences[matchKey] = match if matchKey? # We don't want to track RHS unless it's a specific occurrence
|
485
553
|
rows.push(new Row(entryKey, occurrences))
|
554
|
+
else
|
555
|
+
# Handle case where the match is not a specific occurrence (may have specific occurrences on the RHS)
|
556
|
+
nonSpecificLeftMostRows = _(matches.specificContext.rows).select (r) -> r.nonSpecificLeftMost?.id == match.id
|
557
|
+
entryOccurrences = {}
|
558
|
+
entryOccurrences[entryKey] = entry
|
559
|
+
for nonSpecificLeftMostRow in nonSpecificLeftMostRows
|
560
|
+
result = nonSpecificLeftMostRow.intersect(new Row(entryKey, entryOccurrences))
|
561
|
+
rows.push(result) if result?
|
486
562
|
rows
|
487
563
|
|
488
564
|
# build specific for a given entry (there are no temporal references)
|
@@ -503,7 +579,26 @@ class Row
|
|
503
579
|
result.push(value.id)
|
504
580
|
result
|
505
581
|
|
506
|
-
|
582
|
+
toHashKey: ->
|
583
|
+
@flattenToIds().join(",") + ",#{@specificLeftMost}" + ",#{@nonSpecificLeftMost?.id}"
|
584
|
+
|
585
|
+
# If the row references a leftmost, either specific or not, return the event(s)
|
586
|
+
# (because a UNION can place multiple events in the specific leftMost, this can be > 1)
|
587
|
+
leftMostEvents: ->
|
588
|
+
if @nonSpecificLeftMost?
|
589
|
+
return [@nonSpecificLeftMost]
|
590
|
+
if @specificLeftMost? && _.isString(@specificLeftMost)
|
591
|
+
specificIndex = hqmf.SpecificsManager.getColumnIndex(@specificLeftMost)
|
592
|
+
return [@values[specificIndex]] if @values[specificIndex]? && @values[specificIndex] != hqmf.SpecificsManager.any
|
593
|
+
if @specificLeftMost? && _.isObject(@specificLeftMost)
|
594
|
+
events = []
|
595
|
+
for id, occurrences of @specificLeftMost
|
596
|
+
for occurrence in _.uniq(occurrences)
|
597
|
+
specificIndex = hqmf.SpecificsManager.getColumnIndex(occurrence)
|
598
|
+
events.push(@values[specificIndex]) if @values[specificIndex]? && @values[specificIndex] != hqmf.SpecificsManager.any
|
599
|
+
return events
|
600
|
+
return []
|
601
|
+
|
507
602
|
@Row = Row
|
508
603
|
|
509
604
|
###
|
@@ -559,4 +654,3 @@ hQuery.CodedEntryList::match = _.wrap(hQuery.CodedEntryList::match, (func, codeS
|
|
559
654
|
result.specific_occurrence = occurrence
|
560
655
|
return result;
|
561
656
|
);
|
562
|
-
|