hqmf2js 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. data/.gitignore +10 -0
  2. data/.travis.yml +17 -0
  3. data/Gemfile +41 -0
  4. data/Gemfile.lock +202 -0
  5. data/README.md +7 -0
  6. data/Rakefile +22 -0
  7. data/VERSION +1 -0
  8. data/app/assets/javascripts/hqmf_util.js.coffee +776 -0
  9. data/app/assets/javascripts/logging_utils.js.coffee +150 -0
  10. data/app/assets/javascripts/patient_api_extension.js.coffee +36 -0
  11. data/app/assets/javascripts/specifics.js.coffee +462 -0
  12. data/bin/hqmf2js.rb +25 -0
  13. data/config/warble.rb +144 -0
  14. data/hqmf2js.gemspec +20 -0
  15. data/lib/config/codes.xml +1935 -0
  16. data/lib/generator/characteristic.js.erb +19 -0
  17. data/lib/generator/codes_to_json.rb +81 -0
  18. data/lib/generator/converter.rb +60 -0
  19. data/lib/generator/data_criteria.js.erb +47 -0
  20. data/lib/generator/derived_data.js.erb +5 -0
  21. data/lib/generator/js.rb +263 -0
  22. data/lib/generator/measure_period.js.erb +18 -0
  23. data/lib/generator/patient_data.js.erb +22 -0
  24. data/lib/generator/population_criteria.js.erb +4 -0
  25. data/lib/generator/precondition.js.erb +14 -0
  26. data/lib/hqmf2js.rb +20 -0
  27. data/lib/hquery/engine.rb +4 -0
  28. data/lib/tasks/codes.rake +12 -0
  29. data/lib/tasks/coffee.rake +15 -0
  30. data/lib/tasks/convert.rake +47 -0
  31. data/lib/tasks/cover_me.rake +8 -0
  32. data/test/fixtures/NQF59New.xml +1047 -0
  33. data/test/fixtures/codes/codes.xls +0 -0
  34. data/test/fixtures/codes/codes.xml +1941 -0
  35. data/test/fixtures/i2b2.xml +305 -0
  36. data/test/fixtures/invalid/missing_id.xml +18 -0
  37. data/test/fixtures/invalid/unknown_criteria_type.xml +16 -0
  38. data/test/fixtures/invalid/unknown_demographic_entry.xml +16 -0
  39. data/test/fixtures/invalid/unknown_population_type.xml +9 -0
  40. data/test/fixtures/invalid/unknown_value_type.xml +18 -0
  41. data/test/fixtures/js/59New.js +366 -0
  42. data/test/fixtures/js/test1.js +356 -0
  43. data/test/fixtures/js/test2.js +366 -0
  44. data/test/fixtures/json/0043.json +6 -0
  45. data/test/fixtures/json/0043_hqmf1.json +1 -0
  46. data/test/fixtures/json/0043_hqmf2.json +172 -0
  47. data/test/fixtures/json/59New.json +1352 -0
  48. data/test/fixtures/patient_api.js +2823 -0
  49. data/test/fixtures/patients/francis_drake.json +1180 -0
  50. data/test/fixtures/patients/larry_vanderman.json +645 -0
  51. data/test/test_helper.rb +58 -0
  52. data/test/unit/codes_to_json_test.rb +38 -0
  53. data/test/unit/effective_date_test.rb +48 -0
  54. data/test/unit/hqmf_from_json_javascript_test.rb +108 -0
  55. data/test/unit/hqmf_javascript_test.rb +175 -0
  56. data/test/unit/library_function_test.rb +553 -0
  57. data/test/unit/specifics_test.rb +757 -0
  58. metadata +183 -0
@@ -0,0 +1,150 @@
1
+ class @Logger
2
+ @logger: []
3
+ @info: (string) ->
4
+ @logger.push("#{Logger.indent()}#{string}")
5
+ @enabled: true
6
+ @initialized: false
7
+ @indentCount = 0
8
+ @indent: ->
9
+ indent = ''
10
+ (indent+=' ' for num in [0..@indentCount*8])
11
+ indent
12
+ @stringify: (object) ->
13
+ if object and !_.isUndefined(object) and !_.isUndefined(object.length)
14
+ "#{object.length} entries"
15
+ else
16
+ "#{object}"
17
+ @asBoolean: (object) ->
18
+ if object and !_.isUndefined(object) and !_.isUndefined(object.length)
19
+ object.length>0
20
+ else
21
+ object
22
+ @toJson: (value) ->
23
+ if (typeof(tojson) == 'function')
24
+ tojson(value)
25
+ else
26
+ JSON.stringify(value)
27
+ @classNameFor: (object) ->
28
+ funcNameRegex = ///function (.+)\(///;
29
+ results = funcNameRegex.exec(object.constructor.toString());
30
+ if (results and results.length > 1)
31
+ results[1]
32
+ else
33
+ ""
34
+ @codedValuesAsString: (codedValues) ->
35
+ "["+_.reduce(codedValues, (memo, entry) ->
36
+ memo.push("#{entry.codeSystemName()}:#{entry.code()}");
37
+ memo
38
+ , []).join(',')+"]"
39
+
40
+
41
+ @enableMeasureLogging = (hqmfjs) ->
42
+ _.each(_.functions(hqmfjs), (method) ->
43
+ hqmfjs[method] = _.wrap(hqmfjs[method], (func, patient) ->
44
+ Logger.info("#{method}:")
45
+ Logger.indentCount++
46
+ result = func(patient)
47
+ Logger.indentCount--
48
+ Logger.info("#{method} -> #{Logger.asBoolean(result)}")
49
+ return result;
50
+ );
51
+ );
52
+
53
+ @enableLogging =->
54
+ if (!Logger.initialized)
55
+ Logger.initialized=true
56
+
57
+ _.each(_.functions(hQuery.Patient.prototype), (method) ->
58
+ if (hQuery.Patient.prototype[method].length == 0)
59
+ hQuery.Patient.prototype[method] = _.wrap(hQuery.Patient.prototype[method], (func) ->
60
+ Logger.info("called patient.#{method}():")
61
+ func = _.bind(func, this)
62
+ result = func()
63
+ Logger.info("patient.#{method}() -> #{Logger.stringify(result)}")
64
+ return result;);
65
+ else
66
+ hQuery.Patient.prototype[method] = _.wrap(hQuery.Patient.prototype[method], (func) ->
67
+ args = Array.prototype.slice.call(arguments,1)
68
+ Logger.info("called patient.#{method}(#{args}):")
69
+ result = func.apply(this, args)
70
+ Logger.info("patient.#{method}(#{args}) -> #{Logger.stringify(result)}")
71
+ return result;);
72
+
73
+ );
74
+
75
+ hQuery.CodedEntryList.prototype.match = _.wrap(hQuery.CodedEntryList.prototype.match, (func, codeSet, start, end) ->
76
+
77
+ # if (codeSet)
78
+ # Logger.info("matching: codeSets(#{_.keys(codeSet).join(",")}), #{start}, #{end}")
79
+ # else
80
+ # Logger.info("matching: WARNING: CODE SETS ARE NULL, #{start}, #{end}")
81
+
82
+ func = _.bind(func, this, codeSet,start,end)
83
+ result = func(codeSet,start,end)
84
+ Logger.info("matched -> #{Logger.stringify(result)}")
85
+ return result;
86
+ );
87
+
88
+ # hQuery.CodedEntry.prototype.includesCodeFrom = _.wrap(hQuery.CodedEntry.prototype.includesCodeFrom, (func, codeSet) ->
89
+ # func = _.bind(func, this, codeSet)
90
+ # result = func(codeSet)
91
+ # matchText = "--- noMatch"
92
+ # matchText = "+++ validMatch" if result
93
+ #
94
+ # Logger.info("#{matchText}: -> #{Logger.classNameFor(this)}:#{this.freeTextType()}:#{this.date()}:#{Logger.codedValuesAsString(this.type())}")
95
+ #
96
+ # return result;
97
+ # );
98
+
99
+ @getCodes = _.wrap(@getCodes, (func, oid) ->
100
+ codes = func(oid)
101
+ Logger.info("accessed codes: #{oid}")
102
+ codes
103
+ )
104
+
105
+ @atLeastOneTrue = _.wrap(@atLeastOneTrue, (func) ->
106
+ args = Array.prototype.slice.call(arguments,1)
107
+ Logger.info("called atLeastOneTrue(#{args}):")
108
+ Logger.indentCount++
109
+ result = func.apply(this, args)
110
+ Logger.indentCount--
111
+ Logger.info("atLeastOneTrue -> #{result}")
112
+ result
113
+ )
114
+
115
+ @allTrue = _.wrap(@allTrue, (func) ->
116
+ args = Array.prototype.slice.call(arguments,1)
117
+ Logger.info("called allTrue(#{args}):")
118
+ Logger.indentCount++
119
+ result = func.apply(this, args)
120
+ Logger.indentCount--
121
+ Logger.info("allTrue -> #{result}")
122
+ result
123
+ )
124
+
125
+ @allFalse = _.wrap(@allFalse, (func) ->
126
+ args = Array.prototype.slice.call(arguments,1)
127
+ Logger.info("called allFalse(#{args}):")
128
+ Logger.indentCount++
129
+ result = func.apply(this, args)
130
+ Logger.indentCount--
131
+ Logger.info("allFalse -> #{result}")
132
+ result
133
+ )
134
+
135
+ @atLeastOneFalse = _.wrap(@atLeastOneFalse, (func) ->
136
+ args = Array.prototype.slice.call(arguments,1)
137
+ Logger.info("called atLeastOneFalse(#{args}):")
138
+ Logger.indentCount++
139
+ result = func.apply(this, args)
140
+ Logger.indentCount--
141
+ Logger.info("atLeastOneFalse -> #{result}")
142
+ result
143
+ )
144
+
145
+ @eventsMatchBounds = _.wrap(@eventsMatchBounds, (func, events, bounds, methodName, range) ->
146
+ args = Array.prototype.slice.call(arguments,1)
147
+ result = func(events, bounds, methodName, range)
148
+ Logger.info("#{methodName}(Events: #{Logger.stringify(events)}, Bounds: #{Logger.stringify(bounds)}, Range: #{Logger.toJson(range)}) -> #{Logger.stringify(result)}")
149
+ result
150
+ )
@@ -0,0 +1,36 @@
1
+ hQuery.Patient::procedureResults = -> this.results().concat(this.vitalSigns()).concat(this.procedures())
2
+ hQuery.Patient::allProcedures = -> this.procedures().concat(this.immunizations()).concat(this.medications())
3
+ hQuery.Patient::laboratoryTests = -> this.results().concat(this.vitalSigns())
4
+ hQuery.Patient::allMedications = -> this.medications().concat(this.immunizations())
5
+ hQuery.Patient::allProblems = -> this.conditions().concat(this.socialHistories()).concat(this.procedures())
6
+ hQuery.Patient::allDevices = -> this.conditions().concat(this.procedures()).concat(this.careGoals()).concat(this.medicalEquipment())
7
+ hQuery.Patient::activeDiagnoses = -> this.conditions().concat(this.socialHistories()).withStatuses(['active'])
8
+ hQuery.Patient::inactiveDiagnoses = -> this.conditions().concat(this.socialHistories()).withStatuses(['inactive'])
9
+ hQuery.Patient::resolvedDiagnoses = -> this.conditions().concat(this.socialHistories()).withStatuses(['resolved'])
10
+ hQuery.CodedEntry::asIVL_TS = ->
11
+ tsLow = new TS()
12
+ tsLow.date = this.startDate() || this.date() || null
13
+ tsHigh = new TS()
14
+ tsHigh.date = this.endDate() || this.date() || null
15
+ new IVL_TS(tsLow, tsHigh)
16
+
17
+ hQuery.CodedEntry::respondTo = (functionName) ->
18
+ typeof(@[functionName]) == "function"
19
+
20
+ hQuery.CodedEntryList::isTrue = ->
21
+ @length != 0
22
+
23
+ hQuery.CodedEntryList::isFalse = ->
24
+ @length == 0
25
+
26
+ Array::isTrue = ->
27
+ @length != 0
28
+
29
+ Array::isFalse = ->
30
+ @length == 0
31
+
32
+ Boolean::isTrue = =>
33
+ `this == true`
34
+
35
+ Boolean::isFalse = =>
36
+ `this == false`
@@ -0,0 +1,462 @@
1
+
2
+ wrap = (func, wrapper) ->
3
+ () ->
4
+ args = [func].concat(Array::slice.call(arguments, 0));
5
+ wrapper.apply(this, args);
6
+
7
+ bind = (func, context) ->
8
+
9
+ return Function::bind.apply(func, Array::slice.call(arguments, 1)) if (func.bind == Function::bind && Function::bind)
10
+ throw new TypeError if (typeof func != "function")
11
+ args = Array::slice.call(arguments, 2)
12
+ return bound = ->
13
+ ctor = ->
14
+ return func.apply(context, args.concat(Array::slice.call(arguments))) if (!(this instanceof bound))
15
+ ctor.prototype = func.prototype
16
+ self = new ctor
17
+ result = func.apply(self, args.concat(Array::slice.call(arguments)))
18
+ return result if (Object(result) == result)
19
+ self
20
+
21
+ Array::unique = ->
22
+ output = {}
23
+ output[@[key]] = @[key] for key in [0...@length]
24
+ value for key, value of output
25
+
26
+ Array::reduce = (accumulator) ->
27
+ throw new TypeError("Object is null or undefined") if (this==null or this==undefined)
28
+ i = 0
29
+ l = this.length >> 0
30
+ curr=undefined
31
+
32
+ throw new TypeError("First argument is not callable") if(typeof accumulator != "function")
33
+
34
+ if(arguments.length < 2)
35
+ throw new TypeError("Array length is 0 and no second argument") if (l == 0)
36
+ curr = this[0]
37
+ i = 1
38
+ else
39
+ curr = arguments[1]
40
+
41
+ while (i < l)
42
+ curr = accumulator.call(undefined, curr, this[i], i, this) if(`i in this`)
43
+ ++i
44
+
45
+ return curr
46
+
47
+
48
+ ###
49
+ {
50
+ rows: [
51
+ [1,3,5],
52
+ [1,7,8],
53
+ ]
54
+ }
55
+ ###
56
+ class Specifics
57
+
58
+ @OCCURRENCES
59
+ @KEY_LOOKUP
60
+ @TYPE_LOOKUP
61
+ @INITIALIZED: false
62
+ @PATIENT: null
63
+ @ANY = '*'
64
+
65
+ @initialize: (patient, hqmfjs, occurrences...)->
66
+ Specifics.OCCURRENCES = occurrences
67
+ Specifics.KEY_LOOKUP = {}
68
+ Specifics.INDEX_LOOKUP = {}
69
+ Specifics.TYPE_LOOKUP = {}
70
+ Specifics.FUNCTION_LOOKUP = {}
71
+ Specifics.PATIENT = patient
72
+ Specifics.HQMFJS = hqmfjs
73
+ for occurrenceKey,i in occurrences
74
+ Specifics.KEY_LOOKUP[i] = occurrenceKey.id
75
+ Specifics.INDEX_LOOKUP[occurrenceKey.id] = i
76
+ Specifics.FUNCTION_LOOKUP[i] = occurrenceKey.function
77
+ Specifics.TYPE_LOOKUP[occurrenceKey.type] ||= []
78
+ Specifics.TYPE_LOOKUP[occurrenceKey.type].push(i)
79
+
80
+ constructor: (rows=[])->
81
+ @rows = rows
82
+
83
+ addRows: (rows) ->
84
+ @rows = @rows.concat(rows)
85
+
86
+ removeDuplicateRows: () ->
87
+ deduped = new Specifics()
88
+ for row in @rows
89
+ # this could potentially be hasRow to dump even more rows.
90
+ deduped.addRows([row]) if !deduped.hasExactRow(row)
91
+ deduped
92
+
93
+ hasExactRow: (other) ->
94
+ for row in @rows
95
+ return true if row.equals(other)
96
+ return false
97
+
98
+ union: (other) ->
99
+ value = new Specifics()
100
+ value.rows = @rows.concat(other.rows)
101
+ value.removeDuplicateRows()
102
+
103
+ intersect: (other) ->
104
+ value = new Specifics()
105
+ for leftRow in @rows
106
+ for rightRow in other.rows
107
+ result = leftRow.intersect(rightRow)
108
+ value.rows.push(result) if result?
109
+ value.removeDuplicateRows()
110
+
111
+ getLeftMost: ->
112
+ leftMost = undefined
113
+ for row in @rows
114
+ leftMost = row.leftMost unless leftMost?
115
+ return undefined if leftMost != row.leftMost
116
+ leftMost
117
+
118
+ negate: ->
119
+ negatedRows = []
120
+ keys = []
121
+ allValues = []
122
+ for index in @specificsWithValues()
123
+ keys.push(Specifics.KEY_LOOKUP[index])
124
+ allValues.push(Specifics.HQMFJS[Specifics.FUNCTION_LOOKUP[index]](Specifics.PATIENT))
125
+ cartesian = Specifics._generateCartisian(allValues)
126
+ for values in cartesian
127
+ occurrences = {}
128
+ for key, i in keys
129
+ occurrences[key] = values[i]
130
+ row = new Row(@getLeftMost(), occurrences)
131
+ negatedRows.push(row) if !@hasRow(row)
132
+ (new Specifics(negatedRows)).compactReusedEvents()
133
+
134
+ @_generateCartisian: (allValues) ->
135
+ Array::reduce.call(allValues, (as, bs) ->
136
+ product = []
137
+ for a in as
138
+ for b in bs
139
+ product.push(a.concat(b))
140
+ product
141
+ , [[]])
142
+
143
+ # removes any rows that have the save value for OccurrenceA and OccurrenceB
144
+ compactReusedEvents: ->
145
+ newRows = []
146
+ for myRow in @rows
147
+ goodRow = true
148
+ for type,indexes of Specifics.TYPE_LOOKUP
149
+ ids = []
150
+ for index in indexes
151
+ ids.push(myRow.values[index].id) if myRow.values[index] != Specifics.ANY
152
+ goodRow &&= ids.length == ids.unique().length
153
+ newRows.push(myRow) if goodRow
154
+ new Specifics(newRows)
155
+
156
+ hasRow: (row) ->
157
+ found = false
158
+ for myRow in @rows
159
+ result = myRow.intersect(row)
160
+ return true if result?
161
+ return false
162
+
163
+ hasRows: ->
164
+ @rows.length > 0
165
+
166
+ specificsWithValues: ->
167
+ foundSpecificIndexes = []
168
+ for row in @rows
169
+ foundSpecificIndexes = foundSpecificIndexes.concat(row.specificsWithValues())
170
+ foundSpecificIndexes.unique()
171
+
172
+ hasSpecifics: ->
173
+ anyHaveSpecifics = false
174
+ for row in @rows
175
+ anyHaveSpecifics ||= row.hasSpecifics()
176
+ anyHaveSpecifics
177
+
178
+ finalizeEvents: (eventsContext, boundsContext) ->
179
+ result = this
180
+ result = result.intersect(eventsContext) if (eventsContext?)
181
+ result = result.intersect(boundsContext) if (boundsContext?)
182
+ result.compactReusedEvents()
183
+
184
+ group: ->
185
+ groupedRows = {}
186
+ for row in @rows
187
+ groupedRows[row.groupKeyForLeftMost()] ||= []
188
+ groupedRows[row.groupKeyForLeftMost()].push(row)
189
+ groupedRows
190
+
191
+ COUNT: (range) ->
192
+ @applyRangeSubset(COUNT, range)
193
+
194
+ MIN: (range) ->
195
+ @applyRangeSubset(MIN, range)
196
+
197
+ MAX: (range) ->
198
+ @applyRangeSubset(MAX, range)
199
+
200
+ applyRangeSubset: (func, range) ->
201
+ return this if !@hasSpecifics()
202
+ resultRows = []
203
+ groupedRows = @group()
204
+ for groupKey, group of groupedRows
205
+ if func(Specifics.extractEventsForLeftMost(group), range).isTrue()
206
+ resultRows = resultRows.concat(group)
207
+ new Specifics(resultRows)
208
+
209
+ FIRST: ->
210
+ @applySubset(FIRST)
211
+
212
+ SECOND: ->
213
+ @applySubset(SECOND)
214
+
215
+ THIRD: ->
216
+ @applySubset(THIRD)
217
+
218
+ FOURTH: ->
219
+ @applySubset(FOURTH)
220
+
221
+ FIFTH: ->
222
+ @applySubset(FIFTH)
223
+
224
+ LAST: ->
225
+ @applySubset(LAST)
226
+
227
+ RECENT: ->
228
+ @applySubset(RECENT)
229
+
230
+ applySubset: (func) ->
231
+ return this if !@hasSpecifics()
232
+ resultRows = []
233
+ groupedRows = @group()
234
+ for groupKey, group of groupedRows
235
+ entries = func(Specifics.extractEventsForLeftMost(group))
236
+ if entries.length > 0
237
+ resultRows.push(entries[0].specificRow)
238
+ new Specifics(resultRows)
239
+
240
+ addIdentityRow: ->
241
+ @addRows(Specifics.identity().rows)
242
+
243
+ @identity: ->
244
+ new Specifics([new Row(undefined)])
245
+
246
+
247
+ @extractEventsForLeftMost: (rows) ->
248
+ events = []
249
+ for row in rows
250
+ events.push(Specifics.extractEvent(row.leftMost, row))
251
+ events
252
+
253
+
254
+ @extractEvents: (key, rows) ->
255
+ events = []
256
+ for row in rows
257
+ events.push(Specifics.extractEvent(key, row))
258
+ events
259
+
260
+ @extractEvent: (key, row) ->
261
+ index = Specifics.INDEX_LOOKUP[key]
262
+ if index?
263
+ entry = row.values[index]
264
+ else
265
+ entry = row.tempValue
266
+ entry = new hQuery.CodedEntry(entry.json)
267
+ entry.specificRow = row
268
+ entry
269
+
270
+ @validate: (populations...) ->
271
+ value = Specifics.intersectAll(new Boolean(populations[0].isTrue()), populations)
272
+ value.isTrue() and value.specificContext.hasRows()
273
+
274
+ @intersectAll: (boolVal, values, negate=false) ->
275
+ result = new Specifics()
276
+ # add identity row
277
+ result.addIdentityRow()
278
+ for value in values
279
+ if value.specificContext?
280
+ result = result.intersect(value.specificContext)
281
+ if negate and (!result.hasRows() or result.hasSpecifics())
282
+ result = result.negate()
283
+ result = result.compactReusedEvents()
284
+ # this is a little odd, but it appears when we have a negation with specifics we can ignore the logical result of the negation.
285
+ # the reason we do this is because we may get too many negated values. Values that may be culled later via other specific occurrences. Thus we do not want to return
286
+ # false out of a negation because the values we are evaluating as false may be dropped.
287
+ boolVal = new Boolean(true)
288
+ boolVal.specificContext = result.compactReusedEvents()
289
+ boolVal
290
+
291
+ @unionAll: (boolVal, values,negate=false) ->
292
+ result = new Specifics()
293
+ for value in values
294
+ if value.specificContext? and (value.isTrue() or negate)
295
+ result = result.union(value.specificContext) if value.specificContext?
296
+
297
+ if negate and result.hasSpecifics()
298
+ result = result.negate()
299
+ # 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.
300
+ boolVal = new Boolean(true)
301
+ boolVal.specificContext = result
302
+ boolVal
303
+
304
+ # copy the specifics parameters from an existing element onto the new value element
305
+ @maintainSpecifics: (newElement, existingElement) ->
306
+ newElement.specificContext = existingElement.specificContext
307
+ newElement.specific_occurrence = existingElement.specific_occurrence
308
+ newElement
309
+
310
+ @Specifics = Specifics
311
+
312
+ class Row
313
+ # {'OccurrenceAEncounter':1, 'OccurrenceBEncounter'2}
314
+ constructor: (leftMost, occurrences={}) ->
315
+ throw "left most key must be a string or undefined was: #{leftMost}" if typeof(leftMost) != 'string' and typeof(leftMost) != 'undefined'
316
+ @length = Specifics.OCCURRENCES.length
317
+ @values = []
318
+ @leftMost = leftMost
319
+ @tempValue = occurrences[undefined]
320
+ for i in [0...@length]
321
+ key = Specifics.KEY_LOOKUP[i]
322
+ value = occurrences[key] || Specifics.ANY
323
+ @values[i] = value
324
+
325
+ hasSpecifics: ->
326
+ @length = Specifics.OCCURRENCES.length
327
+ foundSpecific = false
328
+ for i in [0...@length]
329
+ return true if @values[i] != Specifics.ANY
330
+ false
331
+
332
+ specificsWithValues: ->
333
+ @length = Specifics.OCCURRENCES.length
334
+ foundSpecificIndexes = []
335
+ for i in [0...@length]
336
+ foundSpecificIndexes.push(i) if @values[i]? and @values[i] != Specifics.ANY
337
+ foundSpecificIndexes
338
+
339
+ equals: (other) ->
340
+ equal = true;
341
+
342
+ equal &&= Row.valuesEqual(@tempValue, other.tempValue)
343
+ for value,i in @values
344
+ equal &&= Row.valuesEqual(value, other.values[i])
345
+ equal
346
+
347
+ intersect: (other) ->
348
+ intersectedRow = new Row(@leftMost, {})
349
+ intersectedRow.tempValue = @tempValue
350
+ allMatch = true
351
+ for value,i in @values
352
+ result = Row.match(value, other.values[i])
353
+ if result?
354
+ intersectedRow.values[i] = result
355
+ else
356
+ return undefined
357
+ intersectedRow
358
+
359
+ groupKeyForLeftMost: ->
360
+ @groupKey(@leftMost)
361
+
362
+ groupKey: (key=null) ->
363
+ keyForGroup = ''
364
+ for i in [0...@length]
365
+ value = Specifics.ANY
366
+ value = @values[i].id if @values[i] != Specifics.ANY
367
+ if Specifics.KEY_LOOKUP[i] == key
368
+ keyForGroup += "X_"
369
+ else
370
+ keyForGroup += "#{value}_"
371
+ keyForGroup
372
+
373
+
374
+ @match: (left, right) ->
375
+ return right if left == Specifics.ANY
376
+ return left if right == Specifics.ANY
377
+ return left if left.id == right.id
378
+ return undefined
379
+
380
+ @valuesEqual: (left, right) ->
381
+ return true if !left? and !right?
382
+ return false if !left?
383
+ return false if !right?
384
+ return true if left == Specifics.ANY and right == Specifics.ANY
385
+ return true if left.id == right.id
386
+ return false
387
+
388
+ # build specific for an entry given matching rows (with temporal references)
389
+ @buildRowsForMatching: (entryKey, entry, matchesKey, matches) ->
390
+ rows = []
391
+ for match in matches
392
+ occurrences={}
393
+ occurrences[entryKey] = entry
394
+ occurrences[matchesKey] = match
395
+ rows.push(new Row(entryKey, occurrences))
396
+ rows
397
+
398
+ # build specific for a given entry (there are no temporal references)
399
+ @buildForDataCriteria: (entryKey, entries) ->
400
+ rows = []
401
+ for entry in entries
402
+ occurrences={}
403
+ occurrences[entryKey] = entry
404
+ rows.push(new Row(entryKey, occurrences))
405
+ rows
406
+
407
+ @Row = Row
408
+
409
+ ###
410
+ Wrap methods to maintain specificContext and specific_occurrence
411
+ ###
412
+
413
+ hQuery.CodedEntryList::withStatuses = wrap(hQuery.CodedEntryList::withStatuses, (func, statuses, includeUndefined=true) ->
414
+ context = this.specificContext
415
+ occurrence = this.specific_occurrence
416
+ func = bind(func, this)
417
+ result = func(statuses,includeUndefined)
418
+ result.specificContext = context
419
+ result.specific_occurrence = occurrence
420
+ return result;
421
+ );
422
+
423
+ hQuery.CodedEntryList::withNegation = wrap(hQuery.CodedEntryList::withNegation, (func, codeSet) ->
424
+ context = this.specificContext
425
+ occurrence = this.specific_occurrence
426
+ func = bind(func, this)
427
+ result = func(codeSet)
428
+ result.specificContext = context
429
+ result.specific_occurrence = occurrence
430
+ return result;
431
+ );
432
+
433
+ hQuery.CodedEntryList::withoutNegation = wrap(hQuery.CodedEntryList::withoutNegation, (func) ->
434
+ context = this.specificContext
435
+ occurrence = this.specific_occurrence
436
+ func = bind(func, this)
437
+ result = func()
438
+ result.specificContext = context
439
+ result.specific_occurrence = occurrence
440
+ return result;
441
+ );
442
+
443
+ hQuery.CodedEntryList::concat = wrap(hQuery.CodedEntryList::concat, (func, otherEntries) ->
444
+ context = this.specificContext
445
+ occurrence = this.specific_occurrence
446
+ func = bind(func, this)
447
+ result = func(otherEntries)
448
+ result.specificContext = context
449
+ result.specific_occurrence = occurrence
450
+ return result;
451
+ );
452
+
453
+ hQuery.CodedEntryList::match = wrap(hQuery.CodedEntryList::match, (func, codeSet, start, end, includeNegated=false) ->
454
+ context = this.specificContext
455
+ occurrence = this.specific_occurrence
456
+ func = bind(func, this)
457
+ result = func(codeSet, start, end, includeNegated)
458
+ result.specificContext = context
459
+ result.specific_occurrence = occurrence
460
+ return result;
461
+ );
462
+
data/bin/hqmf2js.rb ADDED
@@ -0,0 +1,25 @@
1
+ #! /usr/bin/env ruby
2
+ require_relative '../lib/hqmf2js'
3
+
4
+ if ARGV.length != 1 || !File.exists?(ARGV[0])
5
+ puts "Usage: hqmf2js hqmf_file"
6
+ else
7
+ hqmf_contents = File.open(ARGV[0]).read
8
+ gen = HQMF2JS::Generator::JS.new(hqmf_contents)
9
+
10
+ codes = HQMF2JS::Generator::CodesToJson.new(File.expand_path("../../test/fixtures/codes.xml", __FILE__))
11
+ codes_json = codes.json
12
+ puts "var OidDictionary = #{codes_json};"
13
+
14
+ ctx = Sprockets::Environment.new(File.expand_path("../..", __FILE__))
15
+ Tilt::CoffeeScriptTemplate.default_bare = true
16
+ ctx.append_path "app/assets/javascripts"
17
+ hqmf_utils = ctx.find_asset('hqmf_util').to_s
18
+ puts hqmf_utils
19
+
20
+ puts gen.js_for_data_criteria()
21
+ puts gen.js_for('IPP')
22
+ puts gen.js_for('DENOM')
23
+ puts gen.js_for('NUMER')
24
+ puts gen.js_for('DENEXCEP')
25
+ end