hqmf2js 1.2.1 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +15 -0
  2. data/.travis.yml +2 -5
  3. data/Gemfile +6 -4
  4. data/Gemfile.lock +83 -86
  5. data/Rakefile +9 -5
  6. data/app/assets/javascripts/hqmf_util.js.coffee +98 -23
  7. data/app/assets/javascripts/logging_utils.js.coffee +25 -16
  8. data/app/assets/javascripts/patient_api_extension.js.coffee +85 -0
  9. data/app/assets/javascripts/specifics.js.coffee +60 -14
  10. data/hqmf2js.gemspec +8 -8
  11. data/lib/assets/javascripts/libraries/map_reduce_utils.js +130 -0
  12. data/lib/generator/characteristic.js.erb +28 -17
  13. data/lib/generator/codes_to_json.rb +5 -5
  14. data/lib/generator/data_criteria.js.erb +5 -5
  15. data/lib/generator/execution.rb +144 -0
  16. data/lib/generator/js.rb +46 -21
  17. data/lib/generator/observation_criteria.js.erb +1 -1
  18. data/lib/generator/patient_data.js.erb +22 -19
  19. data/lib/generator/population_criteria.js.erb +6 -1
  20. data/lib/generator/precondition.js.erb +5 -7
  21. data/lib/hqmf2js.rb +1 -0
  22. data/test/fixtures/codes/codes.json +54 -0
  23. data/test/fixtures/json/0495.json +1014 -0
  24. data/test/fixtures/json/59New.json +148 -0
  25. data/test/fixtures/json/data_criteria/specific_occurrence.json +27 -0
  26. data/test/fixtures/json/data_criteria/temporals_with_anynonnull.json +27 -0
  27. data/test/fixtures/patients/larry_vanderman.json +4 -4
  28. data/test/simplecov.rb +19 -0
  29. data/test/test_helper.rb +1 -1
  30. data/test/unit/cmd_test.rb +92 -0
  31. data/test/unit/codes_to_json_test.rb +32 -0
  32. data/test/unit/erb_context_test.rb +64 -0
  33. data/test/unit/hqmf_from_json_javascript_test.rb +37 -2
  34. data/test/unit/hqmf_javascript_test.rb +18 -0
  35. data/test/unit/js_object_test.rb +27 -0
  36. data/test/unit/library_function_test.rb +22 -21
  37. data/test/unit/specifics_test.rb +7 -2
  38. metadata +16 -121
  39. 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 ignore the logical result of the negation. See comment in intersectAll.
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
- eventIds.push(event.id) if event != hqmf.SpecificsManager.any and not (event.id in eventIds)
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
- if entries.length > 0
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
- occurrences={}
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 (_.isObject(matchesKey))
446
- occurrences[matchesKey[match.id]] = match
447
- else
448
- occurrences[matchesKey] = match
449
- rows.push(new Row(entryKey, occurrences))
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.2.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
- <%- 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 %>);
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["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)
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["oid"]] = code_sets
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
- <%- else -%>
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
- events.specificContext=hqmf.SpecificsManager.identity()
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