hqmf2js 1.2.1 → 1.3.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 +15 -0
- data/.travis.yml +2 -5
- data/Gemfile +6 -4
- data/Gemfile.lock +83 -86
- data/Rakefile +9 -5
- data/app/assets/javascripts/hqmf_util.js.coffee +98 -23
- data/app/assets/javascripts/logging_utils.js.coffee +25 -16
- data/app/assets/javascripts/patient_api_extension.js.coffee +85 -0
- data/app/assets/javascripts/specifics.js.coffee +60 -14
- data/hqmf2js.gemspec +8 -8
- data/lib/assets/javascripts/libraries/map_reduce_utils.js +130 -0
- data/lib/generator/characteristic.js.erb +28 -17
- data/lib/generator/codes_to_json.rb +5 -5
- data/lib/generator/data_criteria.js.erb +5 -5
- data/lib/generator/execution.rb +144 -0
- data/lib/generator/js.rb +46 -21
- data/lib/generator/observation_criteria.js.erb +1 -1
- data/lib/generator/patient_data.js.erb +22 -19
- data/lib/generator/population_criteria.js.erb +6 -1
- data/lib/generator/precondition.js.erb +5 -7
- data/lib/hqmf2js.rb +1 -0
- data/test/fixtures/codes/codes.json +54 -0
- data/test/fixtures/json/0495.json +1014 -0
- data/test/fixtures/json/59New.json +148 -0
- data/test/fixtures/json/data_criteria/specific_occurrence.json +27 -0
- data/test/fixtures/json/data_criteria/temporals_with_anynonnull.json +27 -0
- data/test/fixtures/patients/larry_vanderman.json +4 -4
- data/test/simplecov.rb +19 -0
- data/test/test_helper.rb +1 -1
- data/test/unit/cmd_test.rb +92 -0
- data/test/unit/codes_to_json_test.rb +32 -0
- data/test/unit/erb_context_test.rb +64 -0
- data/test/unit/hqmf_from_json_javascript_test.rb +37 -2
- data/test/unit/hqmf_javascript_test.rb +18 -0
- data/test/unit/js_object_test.rb +27 -0
- data/test/unit/library_function_test.rb +22 -21
- data/test/unit/specifics_test.rb +7 -2
- metadata +16 -121
- data/lib/tasks/cover_me.rake +0 -8
@@ -7,6 +7,38 @@ hQuery.Patient::allDevices = -> this.conditions().concat(this.procedures()).conc
|
|
7
7
|
hQuery.Patient::activeDiagnoses = -> this.conditions().concat(this.socialHistories()).withStatuses(['active'])
|
8
8
|
hQuery.Patient::inactiveDiagnoses = -> this.conditions().concat(this.socialHistories()).withStatuses(['inactive'])
|
9
9
|
hQuery.Patient::resolvedDiagnoses = -> this.conditions().concat(this.socialHistories()).withStatuses(['resolved'])
|
10
|
+
|
11
|
+
hQuery.Patient::getAndCacheEvents = (key, that, fn, args...) ->
|
12
|
+
this.cache ||= {}
|
13
|
+
if !this.cache[key]
|
14
|
+
this.cache[key] = fn.apply(that, args)
|
15
|
+
this.cache[key]
|
16
|
+
hQuery.Patient::getEvents = (eventCriteria) ->
|
17
|
+
cacheKey = eventCriteria.type
|
18
|
+
events = this.getAndCacheEvents(cacheKey, this, this[eventCriteria.type])
|
19
|
+
if eventCriteria.statuses && eventCriteria.statuses.length > 0
|
20
|
+
cacheKey = cacheKey + "_" + String(eventCriteria.statuses)
|
21
|
+
events = this.getAndCacheEvents(cacheKey, events, events.withStatuses, eventCriteria.statuses, eventCriteria.includeEventsWithoutStatus)
|
22
|
+
cacheKey = cacheKey + "_" + String(eventCriteria.negated) + String(eventCriteria.negationValueSetId)
|
23
|
+
if eventCriteria.negated
|
24
|
+
codes = getCodes(eventCriteria.negationValueSetId)
|
25
|
+
events = this.getAndCacheEvents(cacheKey, events, events.withNegation, codes)
|
26
|
+
else
|
27
|
+
events = this.getAndCacheEvents(cacheKey, events, events.withoutNegation)
|
28
|
+
if eventCriteria.valueSetId
|
29
|
+
cacheKey = cacheKey + "_" + String(eventCriteria.valueSetId) + "_" + String(eventCriteria.start) + "_" + String(eventCriteria.stop)
|
30
|
+
codes = getCodes(eventCriteria.valueSetId)
|
31
|
+
events = this.getAndCacheEvents(cacheKey, events, events.match, codes, eventCriteria.start, eventCriteria.stop, true)
|
32
|
+
else if eventCriteria.valueSet
|
33
|
+
events = events.match(eventCriteria.valueSet, eventCriteria.start, eventCriteria.stop, true)
|
34
|
+
events = events.slice(0) # clone cached array before we add on specific occurrence
|
35
|
+
if eventCriteria.specificOccurrence
|
36
|
+
events.specific_occurrence = eventCriteria.specificOccurrence
|
37
|
+
events
|
38
|
+
|
39
|
+
hQuery.Patient::deathdate = ->
|
40
|
+
hQuery.dateFromUtcSeconds this.json["deathdate"]
|
41
|
+
|
10
42
|
hQuery.CodedEntry::asIVL_TS = ->
|
11
43
|
tsLow = new TS()
|
12
44
|
tsLow.date = this.startDate() || this.date() || null
|
@@ -22,6 +54,59 @@ hQuery.Encounter::lengthOfStay = (unit) ->
|
|
22
54
|
ivl_ts = this.asIVL_TS()
|
23
55
|
ivl_ts.low.difference(ivl_ts.high, unit)
|
24
56
|
|
57
|
+
hQuery.AdministrationTiming::dosesPerDay = () ->
|
58
|
+
#figure out the units and value and calculate
|
59
|
+
p = this.period()
|
60
|
+
switch(p.unit())
|
61
|
+
when "h"
|
62
|
+
24/p.value()
|
63
|
+
when "d"
|
64
|
+
1/p.value()
|
65
|
+
|
66
|
+
|
67
|
+
hQuery.Fulfillment::daysInRange = (dateRange,doesPerDay) ->
|
68
|
+
# this will give us the number of days this fullfilment was for
|
69
|
+
totalDays = this.quantityDispensed().value()/doesPerDay
|
70
|
+
totalDays = 0 if isNaN(totalDays)
|
71
|
+
endDate = new Date(this.dispenseDate().getTime() + (totalDays*60*60*24*1000))
|
72
|
+
high = if dateRange && dateRange.high then dateRange.high.asDate() else endDate
|
73
|
+
low = if dateRange && dateRange.low then dateRange.low.asDate() else this.dispenseDate()
|
74
|
+
# from the date it was deispensed add the total number of days to
|
75
|
+
# get the end date of the fullfillment. Note that this may not
|
76
|
+
# be the actual number of days the person took the meds but the
|
77
|
+
# measure developers and the guidance given around CMD have not been
|
78
|
+
# thought out very well at all. For reporting this should really just
|
79
|
+
# be done based off of start and end dates and not any sort of tallying of
|
80
|
+
# doses.
|
81
|
+
startDiff = TS.daysDifference(low,this.dispenseDate())
|
82
|
+
endDiff = TS.daysDifference(endDate,high)
|
83
|
+
# startDiff will be - if the start date was before the date range
|
84
|
+
# so we can trim those first days off the total
|
85
|
+
totalDays += startDiff if startDiff < 0
|
86
|
+
#endDiff will be negative if the end date was after the rage hi value
|
87
|
+
# so we can trim those days off
|
88
|
+
totalDays += endDiff if endDiff < 0
|
89
|
+
#if we have a negative value set it to zero
|
90
|
+
totalDays = 0 if isNaN(totalDays) || totalDays < 0
|
91
|
+
totalDays
|
92
|
+
|
93
|
+
# Determin sum the cmd for each fullFillment history based on the
|
94
|
+
# date range
|
95
|
+
hQuery.Medication::fulfillmentTotals = (dateRange)->
|
96
|
+
dpd = this.administrationTiming().dosesPerDay()
|
97
|
+
this.fulfillmentHistory().reduce (t, s) ->
|
98
|
+
t + s.daysInRange(dateRange,dpd)
|
99
|
+
, 0
|
100
|
+
|
101
|
+
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.fulfillmentTotals(dateRange) if this.administrationTiming()
|
107
|
+
|
108
|
+
|
109
|
+
|
25
110
|
hQuery.CodedEntry::respondTo = (functionName) ->
|
26
111
|
typeof(@[functionName]) == "function"
|
27
112
|
|
@@ -41,7 +41,11 @@ class hqmf.SpecificsManagerSingleton
|
|
41
41
|
|
42
42
|
identity: ->
|
43
43
|
new hqmf.SpecificOccurrence([new Row(undefined)])
|
44
|
-
|
44
|
+
|
45
|
+
setIfNull: (events,subsets) ->
|
46
|
+
if (!events.specificContext? || events.length == 0)
|
47
|
+
events.specificContext=hqmf.SpecificsManager.identity()
|
48
|
+
|
45
49
|
getColumnIndex: (occurrenceID) ->
|
46
50
|
columnIndex = @indexLookup[occurrenceID]
|
47
51
|
if typeof columnIndex == "undefined"
|
@@ -54,7 +58,7 @@ class hqmf.SpecificsManagerSingleton
|
|
54
58
|
extractEventsForLeftMost: (rows) ->
|
55
59
|
events = []
|
56
60
|
for row in rows
|
57
|
-
events.push(@extractEvent(row.leftMost, row))
|
61
|
+
events.push(@extractEvent(row.leftMost, row)) if row.leftMost? || row.tempValue?
|
58
62
|
events
|
59
63
|
|
60
64
|
extractEvents: (key, rows) ->
|
@@ -143,7 +147,8 @@ class hqmf.SpecificsManagerSingleton
|
|
143
147
|
|
144
148
|
if negate and (!result.hasRows() or result.hasSpecifics())
|
145
149
|
result = result.negate()
|
146
|
-
# this is a little odd, but it appears when we have a negation with specifics we can
|
150
|
+
# this is a little odd, but it appears when we have a negation with specifics we can
|
151
|
+
# ignore the logical result of the negation. See comment in intersectAll.
|
147
152
|
# we need to verify that we actually have some occurrences
|
148
153
|
boolVal = new Boolean(true) if @occurrences.length > 0
|
149
154
|
boolVal.specificContext = result
|
@@ -154,6 +159,13 @@ class hqmf.SpecificsManagerSingleton
|
|
154
159
|
newElement.specificContext = existingElement.specificContext
|
155
160
|
newElement.specific_occurrence = existingElement.specific_occurrence
|
156
161
|
newElement
|
162
|
+
|
163
|
+
flattenToIds: (specificContext) ->
|
164
|
+
specificContext?.flattenToIds() || []
|
165
|
+
|
166
|
+
storeFinal: (key, result, target) ->
|
167
|
+
target[key] = hqmf.SpecificsManager.flattenToIds(result.specificContext)
|
168
|
+
|
157
169
|
|
158
170
|
@hqmf.SpecificsManager = new hqmf.SpecificsManagerSingleton
|
159
171
|
|
@@ -194,7 +206,17 @@ class hqmf.SpecificOccurrence
|
|
194
206
|
for columnIndex in columnIndices
|
195
207
|
for row in @rows
|
196
208
|
event = row.values[columnIndex]
|
197
|
-
|
209
|
+
if event != hqmf.SpecificsManager.any and not (event.id in eventIds)
|
210
|
+
onlyOneMatch = true
|
211
|
+
# we want to check that we do not have multiple episodes of care matching on this row. If we do, then that means
|
212
|
+
# that we have a reliance on multiple episodes of care for this row when we can only rely on one. If we have multiple
|
213
|
+
# then we want to disregard this row.
|
214
|
+
if (columnIndices.length > 1)
|
215
|
+
for columnIndexInside in columnIndices
|
216
|
+
onlyOneMatch = false if (columnIndexInside != columnIndex and row.values[columnIndexInside] != hqmf.SpecificsManager.any)
|
217
|
+
|
218
|
+
if onlyOneMatch
|
219
|
+
eventIds.push(event.id)
|
198
220
|
eventIds.length
|
199
221
|
|
200
222
|
hasExactRow: (other) ->
|
@@ -324,20 +346,31 @@ class hqmf.SpecificOccurrence
|
|
324
346
|
|
325
347
|
RECENT: ->
|
326
348
|
@applySubset(RECENT)
|
349
|
+
|
350
|
+
hasLeftMost: ->
|
351
|
+
for row in @rows
|
352
|
+
if row.leftMost? || row.tempValue?
|
353
|
+
return true
|
354
|
+
return false
|
327
355
|
|
328
356
|
applySubset: (func) ->
|
329
|
-
return this if !@hasSpecifics()
|
357
|
+
return this if !@hasSpecifics() || !@hasLeftMost()
|
330
358
|
resultRows = []
|
331
359
|
groupedRows = @group()
|
332
360
|
for groupKey, group of groupedRows
|
333
361
|
entries = func(hqmf.SpecificsManager.extractEventsForLeftMost(group))
|
334
|
-
|
335
|
-
resultRows.push(entries[0].specificRow)
|
362
|
+
resultRows.push(entry.specificRow) for entry in entries
|
336
363
|
new hqmf.SpecificOccurrence(resultRows)
|
337
364
|
|
338
365
|
addIdentityRow: ->
|
339
366
|
@addRows(hqmf.SpecificsManager.identity().rows)
|
340
367
|
|
368
|
+
flattenToIds: ->
|
369
|
+
result = []
|
370
|
+
for row in @rows
|
371
|
+
result.push(row.flattenToIds())
|
372
|
+
result
|
373
|
+
|
341
374
|
class Row
|
342
375
|
# {'OccurrenceAEncounter':1, 'OccurrenceBEncounter'2}
|
343
376
|
constructor: (leftMost, occurrences={}) ->
|
@@ -438,15 +471,18 @@ class Row
|
|
438
471
|
@buildRowsForMatching: (entryKey, entry, matchesKey, matches) ->
|
439
472
|
rows = []
|
440
473
|
for match in matches
|
441
|
-
|
442
|
-
occurrences[entryKey] = entry
|
474
|
+
|
443
475
|
# from unions and crossproducts we may have a matches key that is a hash of object ids mapped to the specific occurrence key.
|
444
476
|
# this is because we may have multiple different specific occurrences on the right hand side if it came from a group
|
445
|
-
if
|
446
|
-
|
447
|
-
else
|
448
|
-
|
449
|
-
|
477
|
+
# we may also have the same event mapping to multiple occurrences, so if we have a hash the value will be an array.
|
478
|
+
# we make both the UNION and CROSS casses look the same as the standard case by turning the standard into an array
|
479
|
+
matchKeys = (if _.isObject(matchesKey) then matchesKey[match.id] else [matchesKey])
|
480
|
+
if (matchKeys)
|
481
|
+
for matchKey in matchKeys
|
482
|
+
occurrences = {}
|
483
|
+
occurrences[entryKey] = entry
|
484
|
+
occurrences[matchKey] = match
|
485
|
+
rows.push(new Row(entryKey, occurrences))
|
450
486
|
rows
|
451
487
|
|
452
488
|
# build specific for a given entry (there are no temporal references)
|
@@ -457,6 +493,16 @@ class Row
|
|
457
493
|
occurrences[entryKey] = entry
|
458
494
|
rows.push(new Row(entryKey, occurrences))
|
459
495
|
rows
|
496
|
+
|
497
|
+
flattenToIds: ->
|
498
|
+
result = []
|
499
|
+
for value in @values
|
500
|
+
if (value == hqmf.SpecificsManager.any)
|
501
|
+
result.push(value)
|
502
|
+
else
|
503
|
+
result.push(value.id)
|
504
|
+
result
|
505
|
+
|
460
506
|
|
461
507
|
@Row = Row
|
462
508
|
|
data/hqmf2js.gemspec
CHANGED
@@ -7,15 +7,15 @@ Gem::Specification.new do |s|
|
|
7
7
|
s.email = "hquery-talk@googlegroups.com"
|
8
8
|
s.homepage = "http://github.com/hquery/hqmf2js"
|
9
9
|
s.authors = ["Marc Hadley", "Andre Quina", "Andy Gregorowicz"]
|
10
|
-
s.version = '1.
|
10
|
+
s.version = '1.3.0'
|
11
11
|
|
12
|
-
s.add_dependency 'nokogiri', '~> 1.5.5'
|
13
|
-
s.add_dependency 'tilt', '~> 1.3.3'
|
14
|
-
s.add_dependency 'coffee-script', '~> 2.2.0'
|
15
|
-
s.add_dependency 'sprockets', '~> 2.2.2'
|
16
|
-
s.add_development_dependency "awesome_print", "~> 1.1.0"
|
17
|
-
s.add_dependency 'health-data-standards', '~> 3.1.1'
|
18
|
-
s.add_dependency 'hquery-patient-api', '~> 1.0.2'
|
12
|
+
# s.add_dependency 'nokogiri', '~> 1.5.5'
|
13
|
+
# s.add_dependency 'tilt', '~> 1.3.3'
|
14
|
+
# s.add_dependency 'coffee-script', '~> 2.2.0'
|
15
|
+
# s.add_dependency 'sprockets', '~> 2.2.2'
|
16
|
+
# s.add_development_dependency "awesome_print", "~> 1.1.0"
|
17
|
+
# s.add_dependency 'health-data-standards', '~> 3.1.1'
|
18
|
+
# s.add_dependency 'hquery-patient-api', '~> 1.0.2'
|
19
19
|
|
20
20
|
s.files = s.files = `git ls-files`.split("\n")
|
21
21
|
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
// Adds common utility functions to the root JS object. These are then
|
2
|
+
// available for use by the map-reduce functions for each measure.
|
3
|
+
// lib/qme/mongo_helpers.rb executes this function on a database
|
4
|
+
// connection.
|
5
|
+
|
6
|
+
var root = this;
|
7
|
+
|
8
|
+
root.map = function(record, population, denominator, numerator, exclusion, denexcep, msrpopl, observ, occurrenceId, isContinuousVariable) {
|
9
|
+
var value = {IPP: 0, patient_id: record._id,
|
10
|
+
medical_record_id: record.medical_record_number,
|
11
|
+
first: record.first, last: record.last, gender: record.gender,
|
12
|
+
birthdate: record.birthdate, test_id: record.test_id,
|
13
|
+
provider_performances: record.provider_performances,
|
14
|
+
race: record.race, ethnicity: record.ethnicity, languages: record.languages,
|
15
|
+
payer: extract_payer(record)};
|
16
|
+
|
17
|
+
if (isContinuousVariable) {
|
18
|
+
value = calculateCV(record, population, msrpopl, observ, occurrenceId, value)
|
19
|
+
} else {
|
20
|
+
value = calculate(record, population, denominator, numerator, exclusion, denexcep, occurrenceId, value)
|
21
|
+
}
|
22
|
+
|
23
|
+
if (typeof Logger != 'undefined') {
|
24
|
+
value['logger'] = Logger.logger
|
25
|
+
value['rationale'] = Logger.rationale
|
26
|
+
}
|
27
|
+
|
28
|
+
value.measure_id = hqmfjs.hqmf_id
|
29
|
+
value.sub_id = hqmfjs.sub_id
|
30
|
+
value.nqf_id = hqmfjs.nqf_id
|
31
|
+
value.effective_date = hqmfjs.effective_date;
|
32
|
+
value.test_id = hqmfjs.test_id;
|
33
|
+
|
34
|
+
if (value.provider_performances) {
|
35
|
+
var tmp = [];
|
36
|
+
for(var i=0; i<value.provider_performances.length; i++) {
|
37
|
+
var performance = value.provider_performances[i];
|
38
|
+
if ((performance['start_date'] <= hqmfjs.effective_date || performance['start_date'] == null) && (performance['end_date'] >= hqmfjs.effective_date || performance['end_date'] == null))
|
39
|
+
tmp.push(performance);
|
40
|
+
}
|
41
|
+
if (tmp.length == 0) tmp = null;
|
42
|
+
value.provider_performances = tmp;
|
43
|
+
} else {
|
44
|
+
value.provider_performances = null;
|
45
|
+
}
|
46
|
+
|
47
|
+
//if (value['IPP'] > 0) emit(ObjectId(), value);
|
48
|
+
emit(ObjectId(), value);
|
49
|
+
};
|
50
|
+
|
51
|
+
root.extract_payer = function(record) {
|
52
|
+
var payer = {};
|
53
|
+
if(record.insurance_providers && record.insurance_providers.length > 0){
|
54
|
+
var ip = record.insurance_providers[0];
|
55
|
+
if(ip.codes.SOP && ip.codes.SOP.length >0){
|
56
|
+
payer["code"] = ip.codes.SOP[0];
|
57
|
+
payer["codeSystem"] = "SOP";
|
58
|
+
}
|
59
|
+
}
|
60
|
+
return payer;
|
61
|
+
};
|
62
|
+
|
63
|
+
root.calculate = function(record, population, denominator, numerator, exclusion, denexcep, occurrenceId, value) {
|
64
|
+
|
65
|
+
finalSpecifics = {};
|
66
|
+
value = _.extend(value, {DENOM: 0, NUMER: 0, DENEXCEP: 0, DENEX: 0, antinumerator: 0, finalSpecifics: finalSpecifics});
|
67
|
+
|
68
|
+
var ipp = population();
|
69
|
+
hqmf.SpecificsManager.storeFinal('IPP', ipp, finalSpecifics);
|
70
|
+
if (hqmf.SpecificsManager.validate(ipp)) {
|
71
|
+
value.IPP = hqmf.SpecificsManager.countUnique(occurrenceId, ipp);
|
72
|
+
var denom = hqmf.SpecificsManager.intersectSpecifics(denominator(), ipp, occurrenceId);
|
73
|
+
hqmf.SpecificsManager.storeFinal('DENOM', denom, finalSpecifics);
|
74
|
+
if (hqmf.SpecificsManager.validate(denom)) {
|
75
|
+
|
76
|
+
value.DENOM = hqmf.SpecificsManager.countUnique(occurrenceId, denom);
|
77
|
+
var exclusions = hqmf.SpecificsManager.intersectSpecifics(exclusion(), denom, occurrenceId);
|
78
|
+
hqmf.SpecificsManager.storeFinal('DENEX', exclusions, finalSpecifics);
|
79
|
+
if (hqmf.SpecificsManager.validate(exclusions)) {
|
80
|
+
value.DENEX = hqmf.SpecificsManager.countUnique(occurrenceId, exclusions);
|
81
|
+
denom = hqmf.SpecificsManager.exclude(occurrenceId, denom, exclusions);
|
82
|
+
}
|
83
|
+
|
84
|
+
}
|
85
|
+
// DENEX is a subset of the denominator, so we should set the specifics before the exclusion
|
86
|
+
// hqmf.SpecificsManager.storeFinal('DENOM', denom, finalSpecifics);
|
87
|
+
|
88
|
+
// we need to check the denominator again to make sure we still have viable candidates after
|
89
|
+
// exclusions have been removed
|
90
|
+
if (hqmf.SpecificsManager.validate(denom)) {
|
91
|
+
var numer = hqmf.SpecificsManager.intersectSpecifics(numerator(), denom, occurrenceId);
|
92
|
+
hqmf.SpecificsManager.storeFinal('NUMER', numer, finalSpecifics);
|
93
|
+
if (hqmf.SpecificsManager.validate(numer)) {
|
94
|
+
value.NUMER = hqmf.SpecificsManager.countUnique(occurrenceId, numer);
|
95
|
+
}
|
96
|
+
|
97
|
+
var excep = hqmf.SpecificsManager.intersectSpecifics(denexcep(), denom, occurrenceId);
|
98
|
+
hqmf.SpecificsManager.storeFinal('DENEXCEP', excep, finalSpecifics);
|
99
|
+
if (hqmf.SpecificsManager.validate(excep)) {
|
100
|
+
excep = hqmf.SpecificsManager.exclude(occurrenceId, excep, numer);
|
101
|
+
value.DENEXCEP = hqmf.SpecificsManager.countUnique(occurrenceId, excep);
|
102
|
+
denom = hqmf.SpecificsManager.exclude(occurrenceId, denom, excep);
|
103
|
+
}
|
104
|
+
value.antinumerator = value.DENOM-value.NUMER;
|
105
|
+
}
|
106
|
+
|
107
|
+
}
|
108
|
+
return value;
|
109
|
+
};
|
110
|
+
|
111
|
+
root.calculateCV = function(record, population, msrpopl, observ, occurrenceId, value) {
|
112
|
+
finalSpecifics = {};
|
113
|
+
value = _.extend(value, {MSRPOPL: 0, values: [], finalSpecifics: finalSpecifics});
|
114
|
+
|
115
|
+
var ipp = population()
|
116
|
+
hqmf.SpecificsManager.storeFinal('IPP', ipp, finalSpecifics);
|
117
|
+
if (hqmf.SpecificsManager.validate(ipp)) {
|
118
|
+
value.IPP = hqmf.SpecificsManager.countUnique(occurrenceId, ipp);
|
119
|
+
var measurePopulation = hqmf.SpecificsManager.intersectSpecifics(msrpopl(), ipp, occurrenceId);
|
120
|
+
hqmf.SpecificsManager.storeFinal('MSRPOPL', measurePopulation, finalSpecifics);
|
121
|
+
if (hqmf.SpecificsManager.validate(measurePopulation)) {
|
122
|
+
var observations = observ(measurePopulation.specificContext);
|
123
|
+
value.MSRPOPL = hqmf.SpecificsManager.countUnique(occurrenceId, measurePopulation);
|
124
|
+
value.values = observations;
|
125
|
+
}
|
126
|
+
}
|
127
|
+
return value;
|
128
|
+
};
|
129
|
+
|
130
|
+
|
@@ -1,19 +1,30 @@
|
|
1
|
-
var value = patient.<%= criteria.property %>(<%= js_for_date_bound(criteria) if criteria.property == :age %>) || null;
|
2
|
-
|
3
|
-
var events = [value];
|
4
|
-
|
5
|
-
|
6
|
-
events = <%= temporal_reference.type %>(events, hqmfjs.<%= temporal_reference.reference.id %>(patient)<%= ", #{js_for_bounds(temporal_reference.range)}" if temporal_reference.range %>);
|
7
|
-
<%- end -%>
|
1
|
+
var value = patient.<%= criteria.property %>(<%= js_for_date_bound(criteria) if criteria.property == :age %>) || null;
|
2
|
+
<%- if criteria.property == :birthtime -%>
|
3
|
+
var events = [value];
|
4
|
+
<%- if criteria.temporal_references -%>
|
5
|
+
<%- criteria.temporal_references.each do |temporal_reference| -%>
|
6
|
+
events = <%= temporal_reference.type %>(events, hqmfjs.<%= temporal_reference.reference.id %>(patient)<%= ", #{js_for_bounds(temporal_reference.range)}" if temporal_reference.range %>);
|
8
7
|
<%- end -%>
|
9
|
-
events.specificContext=hqmf.SpecificsManager.identity();
|
10
|
-
return events;
|
11
|
-
<%- elsif criteria.property == :expired or criteria.property == :clinicalTrialParticipant -%>
|
12
|
-
matching = matchingValue(value, 'true');
|
13
|
-
matching.specificContext=hqmf.SpecificsManager.identity();
|
14
|
-
return matching
|
15
|
-
<%- else -%>
|
16
|
-
matching = matchingValue(value, <%= js_for_bounds(criteria.value) %>);
|
17
|
-
matching.specificContext=hqmf.SpecificsManager.identity();
|
18
|
-
return matching;
|
19
8
|
<%- end -%>
|
9
|
+
events.specificContext=hqmf.SpecificsManager.identity();
|
10
|
+
return events;
|
11
|
+
<%- elsif criteria.property == :expired %>
|
12
|
+
var return_value = matchingValue(value, 'true');
|
13
|
+
|
14
|
+
<%- if criteria.temporal_references -%>
|
15
|
+
var events = [patient.deathdate()];
|
16
|
+
<%- criteria.temporal_references.each do |temporal_reference| -%>
|
17
|
+
return_value = <%= temporal_reference.type %>(events, hqmfjs.<%= temporal_reference.reference.id %>(patient)<%= ", #{js_for_bounds(temporal_reference.range)}" if temporal_reference.range %>);
|
18
|
+
<%- end -%>
|
19
|
+
<%- end -%>
|
20
|
+
return_value.specificContext=hqmf.SpecificsManager.identity();
|
21
|
+
return return_value
|
22
|
+
<%- elsif criteria.property == :clinicalTrialParticipant -%>
|
23
|
+
matching = matchingValue(value, 'true');
|
24
|
+
matching.specificContext=hqmf.SpecificsManager.identity();
|
25
|
+
return matching;
|
26
|
+
<%- else -%>
|
27
|
+
matching = matchingValue(value, <%= js_for_bounds(criteria.value) %>);
|
28
|
+
matching.specificContext=hqmf.SpecificsManager.identity();
|
29
|
+
return matching;
|
30
|
+
<%- end -%>
|
@@ -1,6 +1,7 @@
|
|
1
1
|
module HQMF2JS
|
2
2
|
module Generator
|
3
3
|
class CodesToJson
|
4
|
+
|
4
5
|
|
5
6
|
def self.hash_to_js(hash)
|
6
7
|
hash.to_json.gsub(/\"/, "'")
|
@@ -8,16 +9,15 @@ module HQMF2JS
|
|
8
9
|
|
9
10
|
def self.from_value_sets(value_sets)
|
10
11
|
# make sure we have a string keyed hash
|
11
|
-
value_sets = JSON.parse(value_sets.to_json)
|
12
12
|
translation = {}
|
13
13
|
value_sets.each do |value_set|
|
14
14
|
code_sets = {}
|
15
|
-
value_set
|
16
|
-
code_sets[code_set
|
17
|
-
code_sets[code_set
|
15
|
+
value_set.concepts.each do |code_set|
|
16
|
+
code_sets[code_set.code_system_name] ||= []
|
17
|
+
code_sets[code_set.code_system_name].concat(code_set.code.to_a)
|
18
18
|
end
|
19
19
|
|
20
|
-
translation[value_set
|
20
|
+
translation[value_set.oid] = code_sets
|
21
21
|
end
|
22
22
|
|
23
23
|
translation
|
@@ -8,10 +8,10 @@ var <%= js_name(criteria) %> = <%= js_for_value(criteria.value) %>;
|
|
8
8
|
<%- all_criteria.select {|c| c.type != :variable}.each do |criteria| -%>
|
9
9
|
hqmfjs.<%= js_name(criteria) %> = function(patient, initialSpecificContext) {
|
10
10
|
<%- if criteria.type == :characteristic and !criteria.property.nil? -%>
|
11
|
-
<%= js_for_characteristic(criteria)
|
11
|
+
<%= js_for_characteristic(criteria) -%>
|
12
12
|
<%- else -%>
|
13
13
|
<%- if criteria.type != :derived -%>
|
14
|
-
<%= js_for_patient_data(criteria)
|
14
|
+
<%= js_for_patient_data(criteria) -%>
|
15
15
|
<%- else # derived criteria -%>
|
16
16
|
<%= js_for_derived_data(criteria) %>
|
17
17
|
<%- end -%>
|
@@ -26,13 +26,13 @@ hqmfjs.<%= js_name(criteria) %> = function(patient, initialSpecificContext) {
|
|
26
26
|
<%- if criteria.temporal_references and criteria.temporal_references.length > 0 -%>
|
27
27
|
<%- criteria.temporal_references.each do |temporal_reference| -%>
|
28
28
|
if (events.length > 0) events = <%= temporal_reference.type %>(events, hqmfjs.<%= temporal_reference.reference.id %>(patient)<%= ", #{js_for_bounds(temporal_reference.range)}" if temporal_reference.range %>);
|
29
|
-
else events.specificContext=hqmf.SpecificsManager.empty();
|
30
29
|
<%- end -%>
|
31
|
-
|
30
|
+
if (events.length == 0) events.specificContext=hqmf.SpecificsManager.empty();
|
31
|
+
<%- else # no temporal refs -%>
|
32
32
|
<%- if criteria.specific_occurrence -%>
|
33
33
|
events.specificContext=new hqmf.SpecificOccurrence(Row.buildForDataCriteria(events.specific_occurrence, events))
|
34
34
|
<%- else -%>
|
35
|
-
|
35
|
+
hqmf.SpecificsManager.setIfNull(events);
|
36
36
|
<%- end -%>
|
37
37
|
<%- end -%>
|
38
38
|
<%- if criteria.subset_operators -%>
|
@@ -0,0 +1,144 @@
|
|
1
|
+
module HQMF2JS
|
2
|
+
module Generator
|
3
|
+
class Execution
|
4
|
+
|
5
|
+
|
6
|
+
def self.quoted_string_array_or_null(arr)
|
7
|
+
if arr
|
8
|
+
quoted = arr.map {|e| "\"#{e}\""}
|
9
|
+
"[#{quoted.join(',')}]"
|
10
|
+
else
|
11
|
+
"null"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# Note that the JS returned by this function is not included when using the in-browser
|
16
|
+
# debugger. See app/views/measures/debug.js.erb for the in-browser equivalent.
|
17
|
+
def self.measure_js(hqmf_document, population_index, options)
|
18
|
+
"function() {
|
19
|
+
var patient = this;
|
20
|
+
var effective_date = <%= effective_date %>;
|
21
|
+
var enable_logging = <%= enable_logging %>;
|
22
|
+
var enable_rationale = <%= enable_rationale %>;
|
23
|
+
|
24
|
+
<% if (!test_id.nil? && test_id.class==Moped::BSON::ObjectId) %>
|
25
|
+
var test_id = new ObjectId(\"<%= test_id %>\");
|
26
|
+
<% else %>
|
27
|
+
var test_id = null;
|
28
|
+
<% end %>
|
29
|
+
|
30
|
+
hqmfjs = {}
|
31
|
+
<%= init_js_frameworks %>
|
32
|
+
|
33
|
+
hqmfjs.effective_date = effective_date;
|
34
|
+
hqmfjs.test_id = test_id;
|
35
|
+
|
36
|
+
#{logic(hqmf_document, population_index, options)}
|
37
|
+
};
|
38
|
+
"
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
def self.logic(hqmf_document, population_index=0, options)
|
43
|
+
|
44
|
+
value_sets=options[:value_sets]
|
45
|
+
episode_ids=options[:episode_ids]
|
46
|
+
continuous_variable=options[:continuous_variable]
|
47
|
+
force_sources=options[:force_sources]
|
48
|
+
custom_functions=options[:custom_functions]
|
49
|
+
check_crosswalk=options[:check_crosswalk]
|
50
|
+
|
51
|
+
gen = HQMF2JS::Generator::JS.new(hqmf_document)
|
52
|
+
codes = HQMF2JS::Generator::CodesToJson.from_value_sets(value_sets) if value_sets
|
53
|
+
force_sources = force_sources
|
54
|
+
|
55
|
+
if check_crosswalk
|
56
|
+
crosswalk_check = "result = hqmf.SpecificsManager.maintainSpecifics(new Boolean(result.isTrue() && patient_api.validateCodeSystems()), result);"
|
57
|
+
crosswalk_instrument = "instrumentTrueCrosswalk(hqmfjs);"
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
"
|
62
|
+
var patient_api = new hQuery.Patient(patient);
|
63
|
+
|
64
|
+
#{gen.to_js(population_index, codes, force_sources)}
|
65
|
+
|
66
|
+
var occurrenceId = #{quoted_string_array_or_null(episode_ids)};
|
67
|
+
|
68
|
+
hqmfjs.initializeSpecifics(patient_api, hqmfjs)
|
69
|
+
|
70
|
+
var population = function() {
|
71
|
+
return executeIfAvailable(hqmfjs.#{HQMF::PopulationCriteria::IPP}, patient_api);
|
72
|
+
}
|
73
|
+
var denominator = function() {
|
74
|
+
return executeIfAvailable(hqmfjs.#{HQMF::PopulationCriteria::DENOM}, patient_api);
|
75
|
+
}
|
76
|
+
var numerator = function() {
|
77
|
+
return executeIfAvailable(hqmfjs.#{HQMF::PopulationCriteria::NUMER}, patient_api);
|
78
|
+
}
|
79
|
+
var exclusion = function() {
|
80
|
+
return executeIfAvailable(hqmfjs.#{HQMF::PopulationCriteria::DENEX}, patient_api);
|
81
|
+
}
|
82
|
+
var denexcep = function() {
|
83
|
+
return executeIfAvailable(hqmfjs.#{HQMF::PopulationCriteria::DENEXCEP}, patient_api);
|
84
|
+
}
|
85
|
+
var msrpopl = function() {
|
86
|
+
return executeIfAvailable(hqmfjs.#{HQMF::PopulationCriteria::MSRPOPL}, patient_api);
|
87
|
+
}
|
88
|
+
var observ = function(specific_context) {
|
89
|
+
#{observation_function(custom_functions, population_index)}
|
90
|
+
}
|
91
|
+
|
92
|
+
var executeIfAvailable = function(optionalFunction, patient_api) {
|
93
|
+
if (typeof(optionalFunction)==='function') {
|
94
|
+
result = optionalFunction(patient_api);
|
95
|
+
#{crosswalk_check}
|
96
|
+
return result;
|
97
|
+
} else {
|
98
|
+
return false;
|
99
|
+
}
|
100
|
+
}
|
101
|
+
|
102
|
+
#{crosswalk_instrument}
|
103
|
+
if (typeof Logger != 'undefined') {
|
104
|
+
// clear out logger
|
105
|
+
Logger.logger = [];
|
106
|
+
Logger.rationale={};
|
107
|
+
if (typeof short_circuit == 'undefined') short_circuit = true;
|
108
|
+
|
109
|
+
// turn on logging if it is enabled
|
110
|
+
if (enable_logging || enable_rationale) {
|
111
|
+
injectLogger(hqmfjs, enable_logging, enable_rationale, short_circuit);
|
112
|
+
}
|
113
|
+
}
|
114
|
+
|
115
|
+
try {
|
116
|
+
map(patient, population, denominator, numerator, exclusion, denexcep, msrpopl, observ, occurrenceId,#{continuous_variable});
|
117
|
+
} catch(err) {
|
118
|
+
print(err.stack);
|
119
|
+
throw err;
|
120
|
+
}
|
121
|
+
|
122
|
+
"
|
123
|
+
end
|
124
|
+
|
125
|
+
def self.observation_function(custom_functions, population_index)
|
126
|
+
|
127
|
+
result = "
|
128
|
+
var observFunc = hqmfjs.#{HQMF::PopulationCriteria::OBSERV}
|
129
|
+
if (typeof(observFunc)==='function')
|
130
|
+
return observFunc(patient_api, specific_context);
|
131
|
+
else
|
132
|
+
return [];"
|
133
|
+
|
134
|
+
if (custom_functions && custom_functions[HQMF::PopulationCriteria::OBSERV])
|
135
|
+
result = "return #{custom_functions[HQMF::PopulationCriteria::OBSERV]}(patient_api, hqmfjs)"
|
136
|
+
end
|
137
|
+
|
138
|
+
result
|
139
|
+
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|