hqmf2js 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +10 -0
- data/.travis.yml +17 -0
- data/Gemfile +41 -0
- data/Gemfile.lock +202 -0
- data/README.md +7 -0
- data/Rakefile +22 -0
- data/VERSION +1 -0
- data/app/assets/javascripts/hqmf_util.js.coffee +776 -0
- data/app/assets/javascripts/logging_utils.js.coffee +150 -0
- data/app/assets/javascripts/patient_api_extension.js.coffee +36 -0
- data/app/assets/javascripts/specifics.js.coffee +462 -0
- data/bin/hqmf2js.rb +25 -0
- data/config/warble.rb +144 -0
- data/hqmf2js.gemspec +20 -0
- data/lib/config/codes.xml +1935 -0
- data/lib/generator/characteristic.js.erb +19 -0
- data/lib/generator/codes_to_json.rb +81 -0
- data/lib/generator/converter.rb +60 -0
- data/lib/generator/data_criteria.js.erb +47 -0
- data/lib/generator/derived_data.js.erb +5 -0
- data/lib/generator/js.rb +263 -0
- data/lib/generator/measure_period.js.erb +18 -0
- data/lib/generator/patient_data.js.erb +22 -0
- data/lib/generator/population_criteria.js.erb +4 -0
- data/lib/generator/precondition.js.erb +14 -0
- data/lib/hqmf2js.rb +20 -0
- data/lib/hquery/engine.rb +4 -0
- data/lib/tasks/codes.rake +12 -0
- data/lib/tasks/coffee.rake +15 -0
- data/lib/tasks/convert.rake +47 -0
- data/lib/tasks/cover_me.rake +8 -0
- data/test/fixtures/NQF59New.xml +1047 -0
- data/test/fixtures/codes/codes.xls +0 -0
- data/test/fixtures/codes/codes.xml +1941 -0
- data/test/fixtures/i2b2.xml +305 -0
- data/test/fixtures/invalid/missing_id.xml +18 -0
- data/test/fixtures/invalid/unknown_criteria_type.xml +16 -0
- data/test/fixtures/invalid/unknown_demographic_entry.xml +16 -0
- data/test/fixtures/invalid/unknown_population_type.xml +9 -0
- data/test/fixtures/invalid/unknown_value_type.xml +18 -0
- data/test/fixtures/js/59New.js +366 -0
- data/test/fixtures/js/test1.js +356 -0
- data/test/fixtures/js/test2.js +366 -0
- data/test/fixtures/json/0043.json +6 -0
- data/test/fixtures/json/0043_hqmf1.json +1 -0
- data/test/fixtures/json/0043_hqmf2.json +172 -0
- data/test/fixtures/json/59New.json +1352 -0
- data/test/fixtures/patient_api.js +2823 -0
- data/test/fixtures/patients/francis_drake.json +1180 -0
- data/test/fixtures/patients/larry_vanderman.json +645 -0
- data/test/test_helper.rb +58 -0
- data/test/unit/codes_to_json_test.rb +38 -0
- data/test/unit/effective_date_test.rb +48 -0
- data/test/unit/hqmf_from_json_javascript_test.rb +108 -0
- data/test/unit/hqmf_javascript_test.rb +175 -0
- data/test/unit/library_function_test.rb +553 -0
- data/test/unit/specifics_test.rb +757 -0
- 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
|