app/assets/javascripts/specifics.js.coffee in hqmf2js-1.3.0 vs app/assets/javascripts/specifics.js.coffee in hqmf2js-1.4.0
- old
+ new
@@ -25,13 +25,26 @@
@hqmfjs = hqmfjs
for occurrenceKey,i in occurrences
@keyLookup[i] = occurrenceKey.id
@indexLookup[occurrenceKey.id] = i
@functionLookup[i] = occurrenceKey.function
- @typeLookup[occurrenceKey.type] ||= []
- @typeLookup[occurrenceKey.type].push(i)
-
+ # LDY 8/25/17
+ # Something changed in the MAT so the "type" is no longer included in the HQMF. The backup
+ # type included too much detail (OccurrenceA_... and OccurrenceB_...), which made the types
+ # appear different for two occurrences of the same type.
+ # The code below ignores "Occ..." strings within the "type". This makes it so the type will
+ # now appear the same where appropriate.
+ # Note: OccurrenceA... is used for regular instances of an occurrence. OccA... is used for
+ # QDM variables.
+ generic_type = occurrenceKey.type
+ match = generic_type.match(/^occ[a-z]*_(.*)/i)
+ if match
+ generic_type = match[1]
+ if generic_type not of @typeLookup
+ @typeLookup[generic_type] = []
+ @typeLookup[generic_type].push(i)
+
_generateCartisian: (allValues) ->
_.reduce(allValues, (as, bs) ->
product = []
for a in as
for b in bs
@@ -40,45 +53,38 @@
, [[]])
identity: ->
new hqmf.SpecificOccurrence([new Row(undefined)])
- setIfNull: (events,subsets) ->
- if (!events.specificContext? || events.length == 0)
- events.specificContext=hqmf.SpecificsManager.identity()
+ setIfNull: (events) ->
+ # Add specifics if missing, appropriately based on the truthiness
+ if !events.specificContext?
+ if events.isTrue()
+ events.specificContext=hqmf.SpecificsManager.identity()
+ else
+ events.specificContext=hqmf.SpecificsManager.empty()
+ events
getColumnIndex: (occurrenceID) ->
columnIndex = @indexLookup[occurrenceID]
if typeof columnIndex == "undefined"
- throw "Unknown occurrence identifier: "+occurrenceID
+ throw new Error("Unknown occurrence identifier: "+occurrenceID)
columnIndex
empty: ->
new hqmf.SpecificOccurrence([])
+ # Extract events for leftmost of supplied rows, returning copies with a specificRow attribute set
extractEventsForLeftMost: (rows) ->
events = []
for row in rows
- events.push(@extractEvent(row.leftMost, row)) if row.leftMost? || row.tempValue?
+ for event in row.leftMostEvents()
+ event = new event.constructor(event.json)
+ event.specificRow = row
+ events.push(event)
events
-
- extractEvents: (key, rows) ->
- events = []
- for row in rows
- events.push(@extractEvent(key, row))
- events
-
- extractEvent: (key, row) ->
- index = @indexLookup[key]
- if index?
- entry = row.values[index]
- else
- entry = row.tempValue
- entry = new hQuery.CodedEntry(entry.json)
- entry.specificRow = row
- entry
-
+
intersectSpecifics: (nextPopulation, previousPopulation, occurrenceIDs) ->
# we need to pass the episode indicies all the way down through the interesection to the match function
# this must be done because we need to ensure that on intersection of populations the * does not allow an episode through
# that was not part of a previous population
episodeIndices = null
@@ -117,17 +123,17 @@
# Returns a boolean indication of whether all of the supplied population criteria are
# met
validate: (intersectedPopulation) ->
intersectedPopulation.isTrue() and intersectedPopulation.specificContext.hasRows()
- intersectAll: (boolVal, values, negate=false, episodeIndices) ->
+ intersectAll: (boolVal, values, negate=false, episodeIndices, options = {}) ->
result = new hqmf.SpecificOccurrence
# add identity row
result.addIdentityRow()
for value in values
if value.specificContext?
- result = result.intersect(value.specificContext, episodeIndices)
+ result = result.intersect(value.specificContext, episodeIndices, options)
if negate and (!result.hasRows() or result.hasSpecifics())
result = result.negate()
result = result.compactReusedEvents()
# this is a little odd, but it appears when we have a negation with specifics we can
# ignore the logical result of the negation. The reason we do this is because we may
@@ -151,14 +157,37 @@
# ignore the logical result of the negation. See comment in intersectAll.
# we need to verify that we actually have some occurrences
boolVal = new Boolean(true) if @occurrences.length > 0
boolVal.specificContext = result
boolVal
-
+
+ # Given a set of events with a specificContext, filter the events to include only those
+ # referenced in the specific context
+ filterEventsAgainstSpecifics: (events) ->
+ # If there are no specifics (ie identity) we return them all as-is
+ return events unless events.specificContext.hasSpecifics()
+
+ # Find all the events referenced in the specific context
+ referencedEvents = hqmf.SpecificsManager.extractEventsForLeftMost(events.specificContext.rows)
+ referencedEventIds = _(referencedEvents).pluck('id')
+
+ # Filter original events to only return referenced ones (and ones without an ID, likely dates)
+ result = _(events).select (e) -> !e.id || _(referencedEventIds).contains(e.id)
+
+ # Copy the specifics over and return the result
+ hqmf.SpecificsManager.maintainSpecifics(result, events)
+ return result
+
# copy the specifics parameters from an existing element onto the new value element
maintainSpecifics: (newElement, existingElement) ->
- newElement.specificContext = existingElement.specificContext
+ # We handle a special case: if the existing element is falsy (ie an empty result set), and the new element
+ # is truthy (ie a boolean true), and the specific context is the empty set (no rows), we change it to the
+ # identity; this can happen, for example, if the new element is checking COUNT=0 of the existing element
+ if newElement.isTrue() && existingElement.isFalse() && existingElement.specificContext? && !existingElement.specificContext.hasRows()
+ newElement.specificContext = hqmf.SpecificsManager.identity()
+ else
+ newElement.specificContext = existingElement.specificContext
newElement.specific_occurrence = existingElement.specific_occurrence
newElement
flattenToIds: (specificContext) ->
specificContext?.flattenToIds() || []
@@ -192,15 +221,14 @@
rowsToAdd.push(row)
result = new hqmf.SpecificOccurrence(rowsToAdd)
result
removeDuplicateRows: () ->
- deduped = new hqmf.SpecificOccurrence
- for row in @rows
- # this could potentially be hasRow to dump even more rows.
- deduped.addRows([row]) if !deduped.hasExactRow(row)
- deduped
+ # Uniq rows based on each row's string transformation
+ uniqRows = {}
+ uniqRows[row.toHashKey()] = row for row in @rows
+ new hqmf.SpecificOccurrence(_(uniqRows).values())
# Returns a count of unique events for a supplied column index
uniqueEvents: (columnIndices) ->
eventIds = []
for columnIndex in columnIndices
@@ -227,25 +255,25 @@
union: (other) ->
value = new hqmf.SpecificOccurrence()
value.rows = @rows.concat(other.rows)
value.removeDuplicateRows()
- intersect: (other, episodeIndices) ->
+ intersect: (other, episodeIndices, options = {}) ->
value = new hqmf.SpecificOccurrence()
for leftRow in @rows
for rightRow in other.rows
- result = leftRow.intersect(rightRow, episodeIndices)
+ result = leftRow.intersect(rightRow, episodeIndices, options)
value.rows.push(result) if result?
value.removeDuplicateRows()
getLeftMost: ->
- leftMost = undefined
+ specificLeftMost = undefined
for row in @rows
- leftMost = row.leftMost unless leftMost?
- return undefined if leftMost != row.leftMost
- leftMost
-
+ specificLeftMost = row.specificLeftMost unless specificLeftMost?
+ return undefined if specificLeftMost != row.specificLeftMost
+ specificLeftMost
+
negate: ->
negatedRows = []
keys = []
allValues = []
for index in @specificsWithValues()
@@ -271,10 +299,26 @@
ids.push(myRow.values[index].id) if myRow.values[index] != hqmf.SpecificsManager.any
goodRow &&= ids.length == _.unique(ids).length
newRows.push(myRow) if goodRow
new hqmf.SpecificOccurrence(newRows)
+ # Given a set of events, return new specifics removing any rows that *do not* refer to that set of events
+ filterSpecificsAgainstEvents: (events) ->
+ # If there are no specifics (ie identity) return what we have as-is
+ return this unless @hasSpecifics()
+
+ # Keep and return the rows that refer to any of the provided events (via a leftMost)
+ rowsToKeep = _(@rows).select (row) ->
+ _(row.leftMostEvents()).any (leftMostEvent) ->
+ _(events).any (event) ->
+ # We consider events the same if either 1) both have ids and the ids are the same, or 2) both are
+ # dates, and the dates are the same
+ (event instanceof Date && leftMostEvent instanceof Date && event.getTime() == leftMostEvent.getTime()) ||
+ (event.id? && leftMostEvent.id? && event.id == leftMostEvent.id)
+
+ new hqmf.SpecificOccurrence(rowsToKeep)
+
hasRow: (row) ->
found = false
for myRow in @rows
result = myRow.intersect(row)
return true if result?
@@ -295,36 +339,46 @@
anyHaveSpecifics ||= row.hasSpecifics()
anyHaveSpecifics
finalizeEvents: (eventsContext, boundsContext) ->
result = this
- result = result.intersect(eventsContext) if (eventsContext?)
- result = result.intersect(boundsContext) if (boundsContext?)
+ result = result.intersect(eventsContext) if eventsContext?
+ result = result.intersect(boundsContext) if boundsContext?
result.compactReusedEvents()
+ # Group rows by everything except the leftmost to apply the subset only to the events from the specific
+ # occurrence context rows on the leftmost column. eg for "MOST RECENT: Occurrence A of Lab Result during
+ # Occurrence A of Encounter" we want to group by the encounter and apply the most recent to the set of
+ # lab results per group (ie encounter)
group: ->
groupedRows = {}
for row in @rows
groupedRows[row.groupKeyForLeftMost()] ||= []
groupedRows[row.groupKeyForLeftMost()].push(row)
groupedRows
- COUNT: (range) ->
- @applyRangeSubset(COUNT, range)
+ COUNT: (range, fields) ->
+ @applyRangeSubset(COUNT, range, fields)
- MIN: (range) ->
- @applyRangeSubset(MIN, range)
+ MIN: (range, fields) ->
+ @applyRangeSubset(MIN, range, fields)
- MAX: (range) ->
- @applyRangeSubset(MAX, range)
+ MAX: (range, fields) ->
+ @applyRangeSubset(MAX, range, fields)
+
+ SUM: (range, fields) ->
+ @applyRangeSubset(SUM, range, fields)
+
+ MEDIAN: (range, fields) ->
+ @applyRangeSubset(MEDIAN, range, fields)
- applyRangeSubset: (func, range) ->
+ applyRangeSubset: (func, range, fields) ->
return this if !@hasSpecifics()
resultRows = []
groupedRows = @group()
for groupKey, group of groupedRows
- if func(hqmf.SpecificsManager.extractEventsForLeftMost(group), range).isTrue()
+ if func(hqmf.SpecificsManager.extractEventsForLeftMost(group), range, null, fields).isTrue()
resultRows = resultRows.concat(group)
new hqmf.SpecificOccurrence(resultRows)
FIRST: ->
@applySubset(FIRST)
@@ -347,11 +401,11 @@
RECENT: ->
@applySubset(RECENT)
hasLeftMost: ->
for row in @rows
- if row.leftMost? || row.tempValue?
+ if row.specificLeftMost? || row.nonSpecificLeftMost?
return true
return false
applySubset: (func) ->
return this if !@hasSpecifics() || !@hasLeftMost()
@@ -371,16 +425,15 @@
result.push(row.flattenToIds())
result
class Row
# {'OccurrenceAEncounter':1, 'OccurrenceBEncounter'2}
- constructor: (leftMost, occurrences={}) ->
- throw "left most key must be a string or undefined was: #{leftMost}" if typeof(leftMost) != 'string' and typeof(leftMost) != 'undefined'
+ constructor: (specificLeftMost, occurrences={}) ->
@length = hqmf.SpecificsManager.occurrences.length
@values = []
- @leftMost = leftMost
- @tempValue = occurrences[undefined]
+ @specificLeftMost = specificLeftMost
+ @nonSpecificLeftMost = occurrences[undefined]
for i in [0...@length]
key = hqmf.SpecificsManager.keyLookup[i]
value = occurrences[key] || hqmf.SpecificsManager.any
@values[i] = value
@@ -399,19 +452,30 @@
foundSpecificIndexes
equals: (other) ->
equal = true;
- equal &&= Row.valuesEqual(@tempValue, other.tempValue)
+ equal &&= Row.valuesEqual(@nonSpecificLeftMost, other.nonSpecificLeftMost)
for value,i in @values
equal &&= Row.valuesEqual(value, other.values[i])
equal
- intersect: (other, episodeIndices) ->
- intersectedRow = new Row(@leftMost, {})
- intersectedRow.tempValue = @tempValue
+ intersect: (other, episodeIndices, options = {}) ->
+ # When we're calculating an actual intersection, where we're returning a set of events, we want to make sure that rows that reference
+ # disjoint expressions aren't combined; this isn't true if we're calculating a boolean AND, chaining temporal operators, etc
+ if options.considerLeftMost
+ # If rows being intersected have different leftMost values, with neither null, then the rows reference disjoint expressions and can't be intersected
+ return undefined if @specificLeftMost && other.specificLeftMost && !Row.valuesEqual(@specificLeftMost, other.specificLeftMost)
+ return undefined if @nonSpecificLeftMost && other.nonSpecificLeftMost && !Row.valuesEqual(@nonSpecificLeftMost, other.nonSpecificLeftMost)
+ # 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
+ intersectedRow = new Row(@specificLeftMost || other.specificLeftMost, {})
+ intersectedRow.nonSpecificLeftMost = @nonSpecificLeftMost || other.nonSpecificLeftMost
+ else
+ intersectedRow = new Row(@specificLeftMost, {})
+ intersectedRow.nonSpecificLeftMost = @nonSpecificLeftMost
+
# if all the episodes are any, then they were not referenced by the parent population. This occurs when an intersection is done
# against the identity row. In this case we want to allow the specific occurrences through. This happens when we intersect against a measure
# without a denomninator, and on regular intersections since we start with the identity row in the context.
allEpisodesAny = (episodeIndices? && (@allValuesAny(episodeIndices) || other.allValuesAny(episodeIndices)))
@@ -429,20 +493,24 @@
for i in indicies
return false if @values[i] != hqmf.SpecificsManager.any
return true
groupKeyForLeftMost: ->
- @groupKey(@leftMost)
-
- groupKey: (key=null) ->
+ # Get the key(s) to group by, handling hash of specifics or single specific
+ if _.isObject(@specificLeftMost)
+ @groupKey(_(@specificLeftMost).chain().values().flatten().value())
+ else
+ @groupKey([@specificLeftMost])
+
+ groupKey: (keys) ->
+ keys = [keys] if _.isString(keys)
keyForGroup = ''
for i in [0...@length]
- value = hqmf.SpecificsManager.any
- value = @values[i].id if @values[i] != hqmf.SpecificsManager.any
- if hqmf.SpecificsManager.keyLookup[i] == key
+ if _(keys).include(hqmf.SpecificsManager.keyLookup[i])
keyForGroup += "X_"
else
+ value = if @values[i] != hqmf.SpecificsManager.any then @values[i].id else hqmf.SpecificsManager.any
keyForGroup += "#{value}_"
keyForGroup
@match: (left, right, isEpisodeOfCare) ->
@@ -479,12 +547,20 @@
matchKeys = (if _.isObject(matchesKey) then matchesKey[match.id] else [matchesKey])
if (matchKeys)
for matchKey in matchKeys
occurrences = {}
occurrences[entryKey] = entry
- occurrences[matchKey] = match
+ occurrences[matchKey] = match if matchKey? # We don't want to track RHS unless it's a specific occurrence
rows.push(new Row(entryKey, occurrences))
+ else
+ # Handle case where the match is not a specific occurrence (may have specific occurrences on the RHS)
+ nonSpecificLeftMostRows = _(matches.specificContext.rows).select (r) -> r.nonSpecificLeftMost?.id == match.id
+ entryOccurrences = {}
+ entryOccurrences[entryKey] = entry
+ for nonSpecificLeftMostRow in nonSpecificLeftMostRows
+ result = nonSpecificLeftMostRow.intersect(new Row(entryKey, entryOccurrences))
+ rows.push(result) if result?
rows
# build specific for a given entry (there are no temporal references)
@buildForDataCriteria: (entryKey, entries) ->
rows = []
@@ -501,11 +577,30 @@
result.push(value)
else
result.push(value.id)
result
-
+ toHashKey: ->
+ @flattenToIds().join(",") + ",#{@specificLeftMost}" + ",#{@nonSpecificLeftMost?.id}"
+
+ # If the row references a leftmost, either specific or not, return the event(s)
+ # (because a UNION can place multiple events in the specific leftMost, this can be > 1)
+ leftMostEvents: ->
+ if @nonSpecificLeftMost?
+ return [@nonSpecificLeftMost]
+ if @specificLeftMost? && _.isString(@specificLeftMost)
+ specificIndex = hqmf.SpecificsManager.getColumnIndex(@specificLeftMost)
+ return [@values[specificIndex]] if @values[specificIndex]? && @values[specificIndex] != hqmf.SpecificsManager.any
+ if @specificLeftMost? && _.isObject(@specificLeftMost)
+ events = []
+ for id, occurrences of @specificLeftMost
+ for occurrence in _.uniq(occurrences)
+ specificIndex = hqmf.SpecificsManager.getColumnIndex(occurrence)
+ events.push(@values[specificIndex]) if @values[specificIndex]? && @values[specificIndex] != hqmf.SpecificsManager.any
+ return events
+ return []
+
@Row = Row
###
Wrap methods to maintain specificContext and specific_occurrence
###
@@ -557,6 +652,5 @@
result = func(codeSet, start, end, includeNegated)
result.specificContext = context
result.specific_occurrence = occurrence
return result;
);
-