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.
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