hqmf2js 1.2.1 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|