hqmf2js 1.3.0 → 1.4.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.
- 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
|
-
|