app/assets/javascripts/specifics.js.coffee in hqmf2js-1.0.1 vs app/assets/javascripts/specifics.js.coffee in hqmf2js-1.1.0

- old
+ new

@@ -1,109 +1,211 @@ +@hqmf ||= {} -wrap = (func, wrapper) -> - () -> - args = [func].concat(Array::slice.call(arguments, 0)); - wrapper.apply(this, args); - -bind = (func, context) -> - - return Function::bind.apply(func, Array::slice.call(arguments, 1)) if (func.bind == Function::bind && Function::bind) - throw new TypeError if (typeof func != "function") - args = Array::slice.call(arguments, 2) - return bound = -> - ctor = -> - return func.apply(context, args.concat(Array::slice.call(arguments))) if (!(this instanceof bound)) - ctor.prototype = func.prototype - self = new ctor - result = func.apply(self, args.concat(Array::slice.call(arguments))) - return result if (Object(result) == result) - self - -Array::unique = -> - output = {} - output[@[key]] = @[key] for key in [0...@length] - value for key, value of output - -Array::reduce = (accumulator) -> - throw new TypeError("Object is null or undefined") if (this==null or this==undefined) - i = 0 - l = this.length >> 0 - curr=undefined - - throw new TypeError("First argument is not callable") if(typeof accumulator != "function") - - if(arguments.length < 2) - throw new TypeError("Array length is 0 and no second argument") if (l == 0) - curr = this[0] - i = 1 - else - curr = arguments[1] - - while (i < l) - curr = accumulator.call(undefined, curr, this[i], i, this) if(`i in this`) - ++i - - return curr - - ### { rows: [ [1,3,5], [1,7,8], ] } + +A singleton class the represents the table of all specific occurrences ### -class Specifics +class hqmf.SpecificsManagerSingleton + constructor: -> + @patient = null + @any = '*' + + initialize: (patient, hqmfjs, occurrences...)-> + @occurrences = occurrences + @keyLookup = {} + @indexLookup = {} + @typeLookup = {} + @functionLookup = {} + @patient = patient + @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) - @OCCURRENCES - @KEY_LOOKUP - @TYPE_LOOKUP - @INITIALIZED: false - @PATIENT: null - @ANY = '*' + _generateCartisian: (allValues) -> + _.reduce(allValues, (as, bs) -> + product = [] + for a in as + for b in bs + product.push(a.concat(b)) + product + , [[]]) - @initialize: (patient, hqmfjs, occurrences...)-> - Specifics.OCCURRENCES = occurrences - Specifics.KEY_LOOKUP = {} - Specifics.INDEX_LOOKUP = {} - Specifics.TYPE_LOOKUP = {} - Specifics.FUNCTION_LOOKUP = {} - Specifics.PATIENT = patient - Specifics.HQMFJS = hqmfjs - for occurrenceKey,i in occurrences - Specifics.KEY_LOOKUP[i] = occurrenceKey.id - Specifics.INDEX_LOOKUP[occurrenceKey.id] = i - Specifics.FUNCTION_LOOKUP[i] = occurrenceKey.function - Specifics.TYPE_LOOKUP[occurrenceKey.type] ||= [] - Specifics.TYPE_LOOKUP[occurrenceKey.type].push(i) + identity: -> + new hqmf.SpecificOccurrence([new Row(undefined)]) + + getColumnIndex: (occurrenceID) -> + columnIndex = @indexLookup[occurrenceID] + if typeof columnIndex == "undefined" + throw "Unknown occurrence identifier: "+occurrenceID + columnIndex + + empty: -> + new hqmf.SpecificOccurrence([]) + + extractEventsForLeftMost: (rows) -> + events = [] + for row in rows + events.push(@extractEvent(row.leftMost, row)) + 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: (populations...) -> + value = @intersectAll(new Boolean(populations[0].isTrue()), populations) + value + + # Returns a count of the unique events that match the criteria for the supplied + # specific occurrence. Call after validating that population criteria are met. Returns + # 1 if occurrenceID is null, for use with patient based measures. + countUnique: (occurrenceIDs, intersectedPopulation) -> + if occurrenceIDs? + columnIndices = (@getColumnIndex(occurrenceID) for occurrenceID in occurrenceIDs) + intersectedPopulation.specificContext.uniqueEvents(columnIndices) + else if @validate(intersectedPopulation) + 1 + else + 0 + + # remove any rows from initial that have the same event id as a row in exclusions for + # the specified occurence id + exclude: (occurrenceIDs, initial, exclusions) -> + if occurrenceIDs? + resultContext = initial.specificContext + for occurrenceID in occurrenceIDs + columnIndex = @getColumnIndex(occurrenceID) + resultContext = resultContext.removeMatchingRows(columnIndex, exclusions.specificContext) + result = new Boolean(resultContext.hasRows()) + result.specificContext = resultContext + return result + else if @validate(exclusions) + return @maintainSpecifics(new Boolean(false), initial) + else + return initial + + # 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) -> + result = new hqmf.SpecificOccurrence + # add identity row + result.addIdentityRow() + for value in values + if value.specificContext? + result = result.intersect(value.specificContext) + 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 + # get too many negated values. Values that may be culled later via other specific + # occurrences. Thus we do not want to return false out of a negation because the + # values we are evaluating as false may be dropped. + # we need to verify that we actually have some occurrences + boolVal = new Boolean(true) if @occurrences.length > 0 + boolVal.specificContext = result.compactReusedEvents() + boolVal + + unionAll: (boolVal, values,negate=false) -> + result = new hqmf.SpecificOccurrence + for value in values + if value.specificContext? and (value.isTrue() or negate) + result = result.union(value.specificContext) if value.specificContext? + + if negate and (!result.hasRows() or result.hasSpecifics()) + result = result.negate() + # this is a little odd, but it appears when we have a negation with specifics we can 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 + + # copy the specifics parameters from an existing element onto the new value element + maintainSpecifics: (newElement, existingElement) -> + newElement.specificContext = existingElement.specificContext + newElement.specific_occurrence = existingElement.specific_occurrence + newElement + +@hqmf.SpecificsManager = new hqmf.SpecificsManagerSingleton + +class hqmf.SpecificOccurrence constructor: (rows=[])-> @rows = rows addRows: (rows) -> @rows = @rows.concat(rows) + # Return a new SpecificOccurrence with any matching rows removed + removeMatchingRows: (columnIndex, other) -> + removeAll = false + idsToRemove = [] + for row in other.rows + if row.values[columnIndex].id? + idsToRemove.push(row.values[columnIndex].id) + else if row.values[columnIndex] == hqmf.SpecificsManager.any + removeAll = true + rowsToAdd = [] + if not removeAll + for row in @rows + if not (row.values[columnIndex].id in idsToRemove) + rowsToAdd.push(row) + result = new hqmf.SpecificOccurrence(rowsToAdd) + result + removeDuplicateRows: () -> - deduped = new Specifics() + 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 + + # Returns a count of unique events for a supplied column index + uniqueEvents: (columnIndices) -> + eventIds = [] + for columnIndex in columnIndices + for row in @rows + event = row.values[columnIndex] + eventIds.push(event.id) if event != hqmf.SpecificsManager.any and not (event.id in eventIds) + eventIds.length hasExactRow: (other) -> for row in @rows return true if row.equals(other) return false union: (other) -> - value = new Specifics() + value = new hqmf.SpecificOccurrence() value.rows = @rows.concat(other.rows) value.removeDuplicateRows() intersect: (other) -> - value = new Specifics() + value = new hqmf.SpecificOccurrence() for leftRow in @rows for rightRow in other.rows result = leftRow.intersect(rightRow) value.rows.push(result) if result? value.removeDuplicateRows() @@ -118,42 +220,33 @@ negate: -> negatedRows = [] keys = [] allValues = [] for index in @specificsWithValues() - keys.push(Specifics.KEY_LOOKUP[index]) - allValues.push(Specifics.HQMFJS[Specifics.FUNCTION_LOOKUP[index]](Specifics.PATIENT)) - cartesian = Specifics._generateCartisian(allValues) + keys.push(hqmf.SpecificsManager.keyLookup[index]) + allValues.push(hqmf.SpecificsManager.hqmfjs[hqmf.SpecificsManager.functionLookup[index]](hqmf.SpecificsManager.patient)) + cartesian = hqmf.SpecificsManager._generateCartisian(allValues) for values in cartesian occurrences = {} for key, i in keys occurrences[key] = values[i] row = new Row(@getLeftMost(), occurrences) negatedRows.push(row) if !@hasRow(row) - (new Specifics(negatedRows)).compactReusedEvents() - - @_generateCartisian: (allValues) -> - Array::reduce.call(allValues, (as, bs) -> - product = [] - for a in as - for b in bs - product.push(a.concat(b)) - product - , [[]]) - - # removes any rows that have the save value for OccurrenceA and OccurrenceB + (new hqmf.SpecificOccurrence(negatedRows)).compactReusedEvents() + + # removes any rows that have the same value for OccurrenceA and OccurrenceB compactReusedEvents: -> newRows = [] for myRow in @rows goodRow = true - for type,indexes of Specifics.TYPE_LOOKUP + for type,indexes of hqmf.SpecificsManager.typeLookup ids = [] for index in indexes - ids.push(myRow.values[index].id) if myRow.values[index] != Specifics.ANY - goodRow &&= ids.length == ids.unique().length + 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 Specifics(newRows) + new hqmf.SpecificOccurrence(newRows) hasRow: (row) -> found = false for myRow in @rows result = myRow.intersect(row) @@ -165,11 +258,11 @@ specificsWithValues: -> foundSpecificIndexes = [] for row in @rows foundSpecificIndexes = foundSpecificIndexes.concat(row.specificsWithValues()) - foundSpecificIndexes.unique() + _.unique(foundSpecificIndexes) hasSpecifics: -> anyHaveSpecifics = false for row in @rows anyHaveSpecifics ||= row.hasSpecifics() @@ -200,13 +293,13 @@ applyRangeSubset: (func, range) -> return this if !@hasSpecifics() resultRows = [] groupedRows = @group() for groupKey, group of groupedRows - if func(Specifics.extractEventsForLeftMost(group), range).isTrue() + if func(hqmf.SpecificsManager.extractEventsForLeftMost(group), range).isTrue() resultRows = resultRows.concat(group) - new Specifics(resultRows) + new hqmf.SpecificOccurrence(resultRows) FIRST: -> @applySubset(FIRST) SECOND: -> @@ -230,112 +323,45 @@ applySubset: (func) -> return this if !@hasSpecifics() resultRows = [] groupedRows = @group() for groupKey, group of groupedRows - entries = func(Specifics.extractEventsForLeftMost(group)) + entries = func(hqmf.SpecificsManager.extractEventsForLeftMost(group)) if entries.length > 0 resultRows.push(entries[0].specificRow) - new Specifics(resultRows) + new hqmf.SpecificOccurrence(resultRows) addIdentityRow: -> - @addRows(Specifics.identity().rows) - - @identity: -> - new Specifics([new Row(undefined)]) - + @addRows(hqmf.SpecificsManager.identity().rows) - @extractEventsForLeftMost: (rows) -> - events = [] - for row in rows - events.push(Specifics.extractEvent(row.leftMost, row)) - events - - - @extractEvents: (key, rows) -> - events = [] - for row in rows - events.push(Specifics.extractEvent(key, row)) - events - - @extractEvent: (key, row) -> - index = Specifics.INDEX_LOOKUP[key] - if index? - entry = row.values[index] - else - entry = row.tempValue - entry = new hQuery.CodedEntry(entry.json) - entry.specificRow = row - entry - - @validate: (populations...) -> - value = Specifics.intersectAll(new Boolean(populations[0].isTrue()), populations) - value.isTrue() and value.specificContext.hasRows() - - @intersectAll: (boolVal, values, negate=false) -> - result = new Specifics() - # add identity row - result.addIdentityRow() - for value in values - if value.specificContext? - result = result.intersect(value.specificContext) - 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 get too many negated values. Values that may be culled later via other specific occurrences. Thus we do not want to return - # false out of a negation because the values we are evaluating as false may be dropped. - boolVal = new Boolean(true) - boolVal.specificContext = result.compactReusedEvents() - boolVal - @unionAll: (boolVal, values,negate=false) -> - result = new Specifics() - for value in values - if value.specificContext? and (value.isTrue() or negate) - result = result.union(value.specificContext) if value.specificContext? - - if negate and result.hasSpecifics() - result = result.negate() - # this is a little odd, but it appears when we have a negation with specifics we can ignore the logical result of the negation. See comment in intersectAll. - boolVal = new Boolean(true) - boolVal.specificContext = result - boolVal - - # copy the specifics parameters from an existing element onto the new value element - @maintainSpecifics: (newElement, existingElement) -> - newElement.specificContext = existingElement.specificContext - newElement.specific_occurrence = existingElement.specific_occurrence - newElement - -@Specifics = Specifics 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' - @length = Specifics.OCCURRENCES.length + @length = hqmf.SpecificsManager.occurrences.length @values = [] @leftMost = leftMost @tempValue = occurrences[undefined] for i in [0...@length] - key = Specifics.KEY_LOOKUP[i] - value = occurrences[key] || Specifics.ANY + key = hqmf.SpecificsManager.keyLookup[i] + value = occurrences[key] || hqmf.SpecificsManager.any @values[i] = value hasSpecifics: -> - @length = Specifics.OCCURRENCES.length + @length = hqmf.SpecificsManager.occurrences.length foundSpecific = false for i in [0...@length] - return true if @values[i] != Specifics.ANY + return true if @values[i] != hqmf.SpecificsManager.any false specificsWithValues: -> - @length = Specifics.OCCURRENCES.length + @length = hqmf.SpecificsManager.occurrences.length foundSpecificIndexes = [] for i in [0...@length] - foundSpecificIndexes.push(i) if @values[i]? and @values[i] != Specifics.ANY + foundSpecificIndexes.push(i) if @values[i]? and @values[i] != hqmf.SpecificsManager.any foundSpecificIndexes equals: (other) -> equal = true; @@ -360,30 +386,30 @@ @groupKey(@leftMost) groupKey: (key=null) -> keyForGroup = '' for i in [0...@length] - value = Specifics.ANY - value = @values[i].id if @values[i] != Specifics.ANY - if Specifics.KEY_LOOKUP[i] == key + value = hqmf.SpecificsManager.any + value = @values[i].id if @values[i] != hqmf.SpecificsManager.any + if hqmf.SpecificsManager.keyLookup[i] == key keyForGroup += "X_" else keyForGroup += "#{value}_" keyForGroup @match: (left, right) -> - return right if left == Specifics.ANY - return left if right == Specifics.ANY + return right if left == hqmf.SpecificsManager.any + return left if right == hqmf.SpecificsManager.any return left if left.id == right.id return undefined @valuesEqual: (left, right) -> return true if !left? and !right? return false if !left? return false if !right? - return true if left == Specifics.ANY and right == Specifics.ANY + return true if left == hqmf.SpecificsManager.any and right == hqmf.SpecificsManager.any return true if left.id == right.id return false # build specific for an entry given matching rows (with temporal references) @buildRowsForMatching: (entryKey, entry, matchesKey, matches) -> @@ -408,53 +434,53 @@ ### Wrap methods to maintain specificContext and specific_occurrence ### -hQuery.CodedEntryList::withStatuses = wrap(hQuery.CodedEntryList::withStatuses, (func, statuses, includeUndefined=true) -> +hQuery.CodedEntryList::withStatuses = _.wrap(hQuery.CodedEntryList::withStatuses, (func, statuses, includeUndefined=true) -> context = this.specificContext occurrence = this.specific_occurrence - func = bind(func, this) + func = _.bind(func, this) result = func(statuses,includeUndefined) result.specificContext = context result.specific_occurrence = occurrence return result; ); -hQuery.CodedEntryList::withNegation = wrap(hQuery.CodedEntryList::withNegation, (func, codeSet) -> +hQuery.CodedEntryList::withNegation = _.wrap(hQuery.CodedEntryList::withNegation, (func, codeSet) -> context = this.specificContext occurrence = this.specific_occurrence - func = bind(func, this) + func = _.bind(func, this) result = func(codeSet) result.specificContext = context result.specific_occurrence = occurrence return result; ); -hQuery.CodedEntryList::withoutNegation = wrap(hQuery.CodedEntryList::withoutNegation, (func) -> +hQuery.CodedEntryList::withoutNegation = _.wrap(hQuery.CodedEntryList::withoutNegation, (func) -> context = this.specificContext occurrence = this.specific_occurrence - func = bind(func, this) + func = _.bind(func, this) result = func() result.specificContext = context result.specific_occurrence = occurrence return result; ); -hQuery.CodedEntryList::concat = wrap(hQuery.CodedEntryList::concat, (func, otherEntries) -> +hQuery.CodedEntryList::concat = _.wrap(hQuery.CodedEntryList::concat, (func, otherEntries) -> context = this.specificContext occurrence = this.specific_occurrence - func = bind(func, this) + func = _.bind(func, this) result = func(otherEntries) result.specificContext = context result.specific_occurrence = occurrence return result; ); -hQuery.CodedEntryList::match = wrap(hQuery.CodedEntryList::match, (func, codeSet, start, end, includeNegated=false) -> +hQuery.CodedEntryList::match = _.wrap(hQuery.CodedEntryList::match, (func, codeSet, start, end, includeNegated=false) -> context = this.specificContext occurrence = this.specific_occurrence - func = bind(func, this) + func = _.bind(func, this) result = func(codeSet, start, end, includeNegated) result.specificContext = context result.specific_occurrence = occurrence return result; );