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,19 @@
1
+ var value = patient.<%= criteria.property %>(<%= js_for_date_bound(criteria) if criteria.property == :age %>);
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 -%>
8
+ <%- end -%>
9
+ events.specificContext=Specifics.identity();
10
+ return events;
11
+ <%- elsif criteria.property == :expired or criteria.property == :clinicalTrialParticipant -%>
12
+ matching = matchingValue(value, 'true');
13
+ matching.specificContext=Specifics.identity();
14
+ return matching
15
+ <%- else -%>
16
+ matching = matchingValue(value, <%= js_for_bounds(criteria.value) %>);
17
+ matching.specificContext=Specifics.identity();
18
+ return matching;
19
+ <%- end -%>
@@ -0,0 +1,81 @@
1
+ module HQMF2JS
2
+ module Generator
3
+ class CodesToJson
4
+ def self.from_xls(code_systems_file)
5
+ value_sets = HQMF::ValueSet::Parser.new().parse(code_systems_file)
6
+ from_value_sets(value_sets)
7
+ end
8
+
9
+ def self.hash_to_js(hash)
10
+ hash.to_json.gsub(/\"/, "'")
11
+ end
12
+
13
+ def self.from_value_sets(value_sets)
14
+ # make sure we have a string keyed hash
15
+ value_sets = JSON.parse(value_sets.to_json)
16
+ translation = {}
17
+ value_sets.each do |value_set|
18
+ code_sets = {}
19
+ value_set["code_sets"].each do |code_set|
20
+ code_sets[code_set["code_set"]] ||= []
21
+ code_sets[code_set["code_set"]].concat(code_set["codes"])
22
+ end
23
+
24
+ translation[value_set["oid"]] = code_sets
25
+ end
26
+
27
+ translation
28
+ end
29
+
30
+ # Create a new Nokogiri::XML::Document instance by parsing at file at the supplied path
31
+ # from an IHE SVS XML document then converts into a JSON format. The original XML is of the format:
32
+ #
33
+ # Originally formatted like this:
34
+ # <CodeSystems>
35
+ # <ValueSet id="2.16.840.1.113883.3.464.1.14" displayName="birth date">
36
+ # <ConceptList xml:lang="en-US">
37
+ # <Concept code="00110" codeSystemName="HL7" displayName="Date/Time of birth (TS)"
38
+ # codeSystemVersion="3"/>
39
+ # </ConceptList>
40
+ # </ValueSet>
41
+ # </CodeSystems>
42
+ #
43
+ # The translated JSON will be in this structure:
44
+ # {
45
+ # '2.16.840.1.113883.3.464.1.14' => {
46
+ # 'HL7' => [ 00110 ]
47
+ # }
48
+ # }
49
+ def self.from_xml(code_systems_file)
50
+ doc = HQMF2JS::Generator::CodesToJson.parse(code_systems_file)
51
+ translation = {}
52
+ doc.xpath('//ValueSet').each do |set|
53
+ set_list = {}
54
+ set_id = set.at_xpath('@id').value
55
+
56
+ set.xpath('ConceptList').each do |list|
57
+ list.xpath('Concept').each do |concept|
58
+ system = concept.at_xpath('@codeSystemName').value
59
+ code = concept.at_xpath('@code').value
60
+
61
+ codes = set_list[system] || []
62
+ codes << code
63
+ set_list[system] = codes
64
+ end
65
+ end
66
+
67
+ translation[set_id] = set_list
68
+ end
69
+
70
+ translation
71
+ end
72
+
73
+ # Parse an XML document at the supplied path
74
+ # @return [Nokogiri::XML::Document]
75
+ def self.parse(path)
76
+ doc = Nokogiri::XML(File.new(path))
77
+ end
78
+
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,60 @@
1
+ module HQMF2JS
2
+ class Converter
3
+ def self.generate_map_reduce(hqmf_contents, codes=nil)
4
+ # First compile the CoffeeScript that enables our converted HQMF JavaScript
5
+ hqmf_utils = HQMF2JS::Generator::JS.library_functions
6
+
7
+ if !codes
8
+ # Parse the code systems that are mapped to the OIDs we support
9
+ codes_file_path = File.expand_path("../../../test/fixtures/codes/codes.xml", __FILE__)
10
+ codes = HQMF2JS::Generator::CodesToJson.from_xml(codes_file_path)
11
+ end
12
+ codes_json = HQMF2JS::Generator::CodesToJson.hash_to_js(codes)
13
+
14
+ # Convert the HQMF document included as a fixture into JavaScript
15
+ converter = HQMF2JS::Generator::JS.new(hqmf_contents)
16
+ data_criteria_code = converter.js_for_data_criteria
17
+ population_criteria_code = HQMF::PopulationCriteria::ALL_POPULATION_CODES.collect do |code|
18
+ converter.js_for(code, nil, true)
19
+ end
20
+ converted_hqmf = [
21
+ data_criteria_code,
22
+ population_criteria_code.join("\n")
23
+ ].join("\n")
24
+
25
+ # Pretty stock map/reduce functions that call out to our converted HQMF code stored in the functions variable
26
+ map = "function map(patient) {
27
+ var ipp = hqmfjs.IPP(patient);
28
+ if (Specifics.validate(ipp)) {
29
+ emit('ipp', 1);
30
+ if (Specifics.validate(hqmfjs.DENEX(patient), ipp)) {
31
+ emit('denex', 1);
32
+ } else {
33
+ var denom = hqmfjs.DENOM(patient);
34
+ if (Specifics.validate(denom, ipp)) {
35
+ if (Specifics.validate(hqmfjs.NUMER(patient), denom, ipp)) {
36
+ emit('denom', 1);
37
+ emit('numer', 1);
38
+ } else if (Specifics.validate(hqmfjs.EXCEP(patient), denom, ipp)) {
39
+ emit('excep', 1);
40
+ } else {
41
+ emit('denom', 1);
42
+ emit('antinum', 1);
43
+ }
44
+ }
45
+ }
46
+ }
47
+ };"
48
+ reduce = "function reduce(bucket, counts) {
49
+ var sum = 0;
50
+ while(counts.hasNext()){
51
+ sum += counts.next();
52
+ }
53
+ return sum;
54
+ };"
55
+ functions = "#{hqmf_utils}\nvar OidDictionary = #{codes_json};\n#{converted_hqmf}"
56
+
57
+ return { :map => map, :reduce => reduce, :functions => functions }
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,47 @@
1
+ // Measure variables
2
+ <%= js_for_measure_period(measure_period) if measure_period -%>
3
+ <%- all_criteria.select {|c| c.type == :variable}.each do |criteria| -%>
4
+ var <%= js_name(criteria) %> = <%= js_for_value(criteria.value) %>;
5
+ <%- end -%>
6
+
7
+ // Data critera
8
+ <%- all_criteria.select {|c| c.type != :variable}.each do |criteria| -%>
9
+ hqmfjs.<%= js_name(criteria) %> = function(patient) {
10
+ <%- if criteria.type == :characteristic and !criteria.property.nil? -%>
11
+ <%= js_for_characteristic(criteria) %>
12
+ <%- else -%>
13
+ <%- if criteria.type != :derived -%>
14
+ <%= js_for_patient_data(criteria) %>
15
+ <%- else # derived criteria -%>
16
+ <%= js_for_derived_data(criteria) %>
17
+ <%- end -%>
18
+ <%- if criteria.value -%>
19
+ events = filterEventsByValue(events, <%= js_for_bounds(criteria.value) %>);
20
+ <%- end -%>
21
+ <%- if criteria.field_values.present?
22
+ criteria.field_values.keys.each do |field|
23
+ -%>
24
+ events = filterEventsByField(events, "<%= field_method(field) %>", <%= js_for_bounds(criteria.field_values[field]) %>);
25
+ <%- end
26
+ end -%>
27
+ <%- if criteria.temporal_references and criteria.temporal_references.length > 0 -%>
28
+ <%- criteria.temporal_references.each do |temporal_reference| -%>
29
+ events = <%= temporal_reference.type %>(events, hqmfjs.<%= temporal_reference.reference.id %>(patient)<%= ", #{js_for_bounds(temporal_reference.range)}" if temporal_reference.range %>);
30
+ <%- end -%>
31
+ <%- else -%>
32
+ <%- if criteria.specific_occurrence -%>
33
+ events.specificContext=new Specifics(Row.buildForDataCriteria(events.specific_occurrence, events))
34
+ <%- else -%>
35
+ events.specificContext=Specifics.identity()
36
+ <%- end -%>
37
+ <%- end -%>
38
+ <%- if criteria.subset_operators -%>
39
+ <%- criteria.subset_operators.select {|subset_operator| subset_operator.type}.each do |subset_operator| -%>
40
+ events = <%= subset_operator.type %>(events<%= ", #{js_for_bounds(subset_operator.value)}" if subset_operator.value %>);
41
+ <%- end # each operator -%>
42
+ <%- end # subset operators -%>
43
+ return events;
44
+ <%- end # non-characteristic criteria -%>
45
+ }
46
+
47
+ <%- end -%>
@@ -0,0 +1,5 @@
1
+ var events = <%= criteria.derivation_operator %>(
2
+ <%- criteria.children_criteria.each_with_index do |child, index| -%>
3
+ <%= "hqmfjs.#{child}(patient)" %><%= "," if index<criteria.children_criteria.length-1 %>
4
+ <%- end -%>
5
+ );
@@ -0,0 +1,263 @@
1
+ module HQMF2JS
2
+ module Generator
3
+
4
+ def self.render_template(name, params)
5
+ template_path = File.expand_path(File.join('..', "#{name}.js.erb"), __FILE__)
6
+ template_str = File.read(template_path)
7
+ template = ERB.new(template_str, nil, '-', "_templ#{TemplateCounter.instance.new_id}")
8
+ context = ErbContext.new(params)
9
+ template.result(context.get_binding)
10
+ end
11
+
12
+ # Utility class used to supply a binding to Erb. Contains utility functions used
13
+ # by the erb templates that are used to generate code.
14
+ class ErbContext < OpenStruct
15
+
16
+ # Create a new context
17
+ # @param [Hash] vars a hash of parameter names (String) and values (Object).
18
+ # Each entry is added as an accessor of the new Context
19
+ def initialize(vars)
20
+ super(vars)
21
+ end
22
+
23
+ # Get a binding that contains all the instance variables
24
+ # @return [Binding]
25
+ def get_binding
26
+ binding
27
+ end
28
+
29
+ def js_for_measure_period(measure_period)
30
+ HQMF2JS::Generator.render_template('measure_period', {'measure_period' => measure_period})
31
+ end
32
+
33
+ def js_for_characteristic(criteria)
34
+ HQMF2JS::Generator.render_template('characteristic', {'criteria' => criteria})
35
+ end
36
+
37
+ def js_for_patient_data(criteria)
38
+ HQMF2JS::Generator.render_template('patient_data', {'criteria' => criteria})
39
+ end
40
+
41
+ def js_for_derived_data(criteria)
42
+ HQMF2JS::Generator.render_template('derived_data', {'criteria' => criteria})
43
+ end
44
+
45
+ def field_method(field_name)
46
+ HQMF::DataCriteria::FIELDS[field_name][:coded_entry_method].to_s.camelize(:lower)
47
+ end
48
+
49
+ def js_for_value(value)
50
+ if value
51
+ if value.respond_to?(:derived?) && value.derived?
52
+ value.expression
53
+ else
54
+ if value.type=='CD'
55
+ if value.code_list_id
56
+ "new CodeList(getCodes(\"#{value.code_list_id}\"))"
57
+ else
58
+ "new CD(\"#{value.code}\", \"#{value.system}\")"
59
+ end
60
+ elsif value.type=='PQ'
61
+ if value.unit != nil
62
+ "new PQ(#{value.value}, \"#{value.unit}\", #{value.inclusive?})"
63
+ else
64
+ "new PQ(#{value.value}, null, #{value.inclusive?})"
65
+ end
66
+ elsif value.type=='ANYNonNull'
67
+ "new #{value.type}()"
68
+ elsif value.respond_to?(:unit) && value.unit != nil
69
+ "new #{value.type}(#{value.value}, \"#{value.unit}\", #{value.inclusive?})"
70
+ elsif value.respond_to?(:inclusive?) and !value.inclusive?.nil?
71
+ "new #{value.type}(\"#{value.value}\", null, #{value.inclusive?})"
72
+ else
73
+ "new #{value.type}(\"#{value.value}\")"
74
+ end
75
+ end
76
+ else
77
+ 'null'
78
+ end
79
+ end
80
+
81
+ def js_for_bounds(bounds)
82
+ if (bounds.respond_to?(:low) && bounds.respond_to?(:high))
83
+ "new IVL_PQ(#{js_for_value(bounds.low)}, #{js_for_value(bounds.high)})"
84
+ else
85
+ "#{js_for_value(bounds)}"
86
+ end
87
+ end
88
+
89
+ def js_for_date_bound(criteria)
90
+ bound = nil
91
+ if criteria.effective_time
92
+ if criteria.effective_time.high
93
+ bound = criteria.effective_time.high
94
+ elsif criteria.effective_time.low
95
+ bound = criteria.effective_time.low
96
+ end
97
+ elsif criteria.temporal_references
98
+ # this is a check for age against the measurement period
99
+ measure_period_reference = criteria.temporal_references.select {|reference| reference.reference and reference.reference.id == HQMF::Document::MEASURE_PERIOD_ID}.first
100
+ if (measure_period_reference)
101
+ case measure_period_reference.type
102
+ when 'SBS','SAS','EBS','EAS'
103
+ return 'MeasurePeriod.low.asDate()'
104
+ when 'SBE','SAE','EBE','EAE'
105
+ return 'MeasurePeriod.high.asDate()'
106
+ else
107
+ raise "do not know how to get a date for this type"
108
+ end
109
+ end
110
+ end
111
+
112
+ if bound
113
+ "#{js_for_value(bound)}.asDate()"
114
+ else
115
+ 'MeasurePeriod.high.asDate()'
116
+ end
117
+ end
118
+
119
+ def js_for_code_list(criteria)
120
+ if criteria.inline_code_list
121
+ criteria.inline_code_list.to_json
122
+ else
123
+ "getCodes(\"#{criteria.code_list_id}\")"
124
+ end
125
+ end
126
+
127
+ # Returns the JavaScript generated for a HQMF::Precondition
128
+ def js_for_precondition(precondition, indent)
129
+ HQMF2JS::Generator.render_template('precondition', {'doc' => doc, 'precondition' => precondition, 'indent' => indent})
130
+ end
131
+
132
+ def patient_api_method(criteria)
133
+ criteria.patient_api_function
134
+ end
135
+
136
+ def conjunction_code_for(precondition)
137
+ precondition.conjunction_code_with_negation
138
+ end
139
+
140
+ # Returns a Javascript compatable name based on an entity's identifier
141
+ def js_name(entity)
142
+ if !entity.id
143
+ raise "No identifier for #{entity.to_json}"
144
+ end
145
+ entity.id.gsub(/\W/, '_')
146
+ end
147
+
148
+ end
149
+
150
+ class JS
151
+
152
+ # Entry point to JavaScript generator
153
+ def initialize(doc)
154
+ @doc = doc
155
+ end
156
+
157
+ def to_js(population_index=0, codes=nil)
158
+ population_index ||= 0
159
+ population = @doc.populations[population_index]
160
+
161
+ if codes
162
+ oid_dictionary = HQMF2JS::Generator::CodesToJson.hash_to_js(codes)
163
+ else
164
+ oid_dictionary = "<%= oid_dictionary %>"
165
+ end
166
+
167
+ "
168
+ // #########################
169
+ // ##### DATA ELEMENTS #####
170
+ // #########################
171
+
172
+ OidDictionary = #{oid_dictionary};
173
+
174
+ #{js_for_data_criteria()}
175
+
176
+ // #########################
177
+ // ##### MEASURE LOGIC #####
178
+ // #########################
179
+
180
+ #{js_initialize_specifics(@doc.source_data_criteria)}
181
+
182
+ // INITIAL PATIENT POPULATION
183
+ #{js_for(population[HQMF::PopulationCriteria::IPP], HQMF::PopulationCriteria::IPP, true)}
184
+ // DENOMINATOR
185
+ #{js_for(population[HQMF::PopulationCriteria::DENOM], HQMF::PopulationCriteria::DENOM, true)}
186
+ // NUMERATOR
187
+ #{js_for(population[HQMF::PopulationCriteria::NUMER], HQMF::PopulationCriteria::NUMER)}
188
+ #{js_for(population[HQMF::PopulationCriteria::DENEX], HQMF::PopulationCriteria::DENEX)}
189
+ #{js_for(population[HQMF::PopulationCriteria::EXCEP], HQMF::PopulationCriteria::EXCEP)}
190
+ "
191
+ end
192
+
193
+ def js_initialize_specifics(data_criteria)
194
+ specific_occurrences = []
195
+ data_criteria.each do |criteria|
196
+ if (criteria.specific_occurrence)
197
+ specific_occurrences << {id: "#{criteria.id}", type: "#{criteria.specific_occurrence_const}", function: "#{criteria.source_data_criteria}"}
198
+ end
199
+ end
200
+ json_list = specific_occurrences.map {|occurrence| occurrence.to_json}
201
+ specifics_list = json_list.join(',')
202
+ specifics_list = ",#{specifics_list}" unless specifics_list.empty?
203
+ "hqmfjs.initializeSpecifics = function(patient_api, hqmfjs) { Specifics.initialize(patient_api,hqmfjs#{specifics_list}) }"
204
+ end
205
+
206
+ # Generate JS for a HQMF2::PopulationCriteria
207
+ def js_for(criteria_code, type=nil, when_not_found=false)
208
+ # for multiple populations, criteria code will be something like IPP_1 and type will be IPP
209
+ type ||= criteria_code
210
+ criteria = @doc.population_criteria(criteria_code)
211
+ if criteria && criteria.preconditions && criteria.preconditions.length > 0
212
+ HQMF2JS::Generator.render_template('population_criteria', {'doc' => @doc, 'criteria' => criteria, 'type'=>type})
213
+ else
214
+ "hqmfjs.#{type} = function(patient) { return new Boolean(#{when_not_found}); }"
215
+ end
216
+ end
217
+
218
+ # Generate JS for a HQMF2::DataCriteria
219
+ def js_for_data_criteria
220
+ HQMF2JS::Generator.render_template('data_criteria', {'all_criteria' => @doc.specific_occurrence_source_data_criteria.concat(@doc.all_data_criteria), 'measure_period' => @doc.measure_period})
221
+ end
222
+
223
+ def self.library_functions
224
+ ctx = Sprockets::Environment.new(File.expand_path("../../..", __FILE__))
225
+ Tilt::CoffeeScriptTemplate.default_bare = true
226
+ ctx.append_path "app/assets/javascripts"
227
+
228
+ ["// #########################\n// ###### PATIENT API #######\n// #########################\n",
229
+ HqueryPatientApi::Generator.patient_api_javascript.to_s,
230
+ "// #########################\n// ### LIBRARY FUNCTIONS ####\n// #########################\n",
231
+ ctx.find_asset('hqmf_util').to_s,
232
+ "// #########################\n// ### PATIENT EXTENSION ####\n// #########################\n",
233
+ ctx.find_asset('patient_api_extension').to_s,
234
+ "// #########################\n// ##### LOGGING UTILS ######\n// #########################\n",
235
+ ctx.find_asset('logging_utils').to_s,
236
+ "// #########################\n// ## SPECIFIC OCCURRENCES ##\n// #########################\n",
237
+ ctx.find_asset('specifics').to_s].join("\n")
238
+ end
239
+
240
+ end
241
+
242
+ # Simple class to issue monotonically increasing integer identifiers
243
+ class Counter
244
+ def initialize
245
+ @count = 0
246
+ end
247
+
248
+ def new_id
249
+ @count+=1
250
+ end
251
+ end
252
+
253
+ # Singleton to keep a count of function identifiers
254
+ class FunctionCounter < Counter
255
+ include Singleton
256
+ end
257
+
258
+ # Singleton to keep a count of template identifiers
259
+ class TemplateCounter < Counter
260
+ include Singleton
261
+ end
262
+ end
263
+ end
@@ -0,0 +1,18 @@
1
+ var MeasurePeriod = {
2
+ "low": <%= js_for_value(measure_period.low) %>,
3
+ "high": <%= js_for_value(measure_period.high) %>
4
+ }
5
+ hqmfjs.MeasurePeriod = function(patient) {
6
+ return [new hQuery.CodedEntry(
7
+ {
8
+ "start_time": MeasurePeriod.low.asDate().getTime()/1000,
9
+ "end_time": MeasurePeriod.high.asDate().getTime()/1000,
10
+ "codes": {}
11
+ }
12
+ )];
13
+ }
14
+ if (typeof effective_date === 'number') {
15
+ MeasurePeriod.high.date = new Date(1000*effective_date);
16
+ MeasurePeriod.low.date = new Date(1000*effective_date);
17
+ MeasurePeriod.low.date.setFullYear(MeasurePeriod.low.date.getFullYear()-1);
18
+ }
@@ -0,0 +1,22 @@
1
+ var events = patient.<%= patient_api_method(criteria) %>();
2
+ <%- if criteria.specific_occurrence -%>
3
+ events.specific_occurrence = '<%= criteria.source_data_criteria %>'
4
+ <%- end -%>
5
+ <%- if criteria.status -%>
6
+ events = events.withStatuses(["<%= criteria.status %>"]<%= ", false" if criteria.hard_status %>);
7
+ <%- end -%>
8
+ <%- if criteria.negation -%>
9
+ events = events.withNegation(<%= "getCodes(\"#{criteria.negation_code_list_id}\")" if criteria.negation_code_list_id %>);
10
+ <%- else -%>
11
+ events = events.withoutNegation();
12
+ <%- end -%>
13
+ var codes = <%= js_for_code_list(criteria) %>;
14
+ var start = null;
15
+ var end = null;
16
+ <%- if criteria.effective_time -%>
17
+ <%- startExp = js_for_value(criteria.effective_time.low) -%>
18
+ start = <%= startExp %><%= '.asDate()' if startExp != 'null' %>;
19
+ <%- endExp = js_for_value(criteria.effective_time.high) -%>
20
+ end = <%= endExp %><%= '.asDate()' if endExp != 'null' %>;
21
+ <%- end -%>
22
+ events = events.match(codes, start, end, true);
@@ -0,0 +1,4 @@
1
+ hqmfjs.<%= type %> = function(patient) {
2
+ return <%= js_for_precondition(criteria, 0) -%>;
3
+ };
4
+
@@ -0,0 +1,14 @@
1
+ <%- if precondition.conjunction? -%>
2
+ <%- if indent>0 -%>
3
+ <%= "\n#{' '*(indent+1)}#{conjunction_code_for(precondition)}(" -%>
4
+ <%- else -%>
5
+ <%= "#{conjunction_code_for(precondition)}(" -%>
6
+ <%- end -%>
7
+ <%- precondition.preconditions.each_with_index do |child, index| -%>
8
+ <%= "#{' '*(indent+1)}#{js_for_precondition(child, indent+1)}" -%>
9
+ <%= "," if index < precondition.preconditions.length-1 -%>
10
+ <%- end -%>
11
+ <%= "\n#{' '*(indent+1)})" -%>
12
+ <%- else -%>
13
+ <%= "\n#{' '*(indent+1)}hqmfjs.#{js_name(precondition.reference)}(patient)" -%>
14
+ <%- end -%>
data/lib/hqmf2js.rb ADDED
@@ -0,0 +1,20 @@
1
+ # Top level include file that brings in all the necessary code
2
+ require 'bundler/setup'
3
+ require 'rubygems'
4
+ require 'erb'
5
+ require 'ostruct'
6
+ require 'singleton'
7
+ require 'json'
8
+ require 'tilt'
9
+ require 'coffee_script'
10
+ require 'sprockets'
11
+ require 'nokogiri'
12
+
13
+ require 'hqmf-parser'
14
+
15
+
16
+ require_relative 'generator/js'
17
+ require_relative 'generator/codes_to_json'
18
+ require_relative 'generator/converter'
19
+
20
+ Tilt::CoffeeScriptTemplate.default_bare = true
@@ -0,0 +1,4 @@
1
+ module HQMF2JS
2
+ class Engine < Rails::Engine
3
+ end
4
+ end
@@ -0,0 +1,12 @@
1
+ namespace :codes do
2
+ desc 'Convert code systems from XML to JSON'
3
+ task :convert, [:file] do |t, args|
4
+ f = args.file
5
+ codes_to_json = HQMF2JS::Generator::CodesToJson.new(f)
6
+
7
+ Dir.mkdir('tmp') unless Dir.exists?('tmp')
8
+ File.open('tmp/codes.js', 'w+') do |js_file|
9
+ js_file.write codes_to_json.json
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,15 @@
1
+ namespace :coffee do
2
+ desc 'Compile the CoffeeScript library'
3
+ task :compile do
4
+ ctx = Sprockets::Environment.new(File.expand_path("../../..", __FILE__))
5
+ Tilt::CoffeeScriptTemplate.default_bare=true
6
+ ctx.append_path "app/assets/javascripts"
7
+ api = ctx.find_asset('hqmf_util')
8
+
9
+ Dir.mkdir('tmp') unless Dir.exists?( 'tmp')
10
+
11
+ File.open('tmp/hqmf.js', 'w+') do |js_file|
12
+ js_file.write api
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,47 @@
1
+ require 'pathname'
2
+ require 'fileutils'
3
+
4
+ namespace :hqmf do
5
+ desc 'Convert a HQMF file to JavaScript'
6
+ task :convert, [:hqmf,:hqmf_version] do |t, args|
7
+
8
+ raise "The path to the the hqmf xml must be specified" unless args.hqmf
9
+
10
+ FileUtils.mkdir_p File.join(".","tmp",'js')
11
+ file = File.expand_path(args.hqmf)
12
+ version = args.hqmf_version || HQMF::Parser::HQMF_VERSION_1
13
+ filename = Pathname.new(file).basename
14
+ doc = HQMF::Parser.parse(File.open(file).read, version)
15
+
16
+ gen = HQMF2JS::Generator::JS.new(doc)
17
+
18
+ out_file = File.join(".","tmp",'js',"#{filename}.js")
19
+
20
+ File.open(out_file, 'w') do |f|
21
+
22
+ f.write("// #########################\n")
23
+ f.write("// ##### DATA CRITERIA #####\n")
24
+ f.write("// #########################\n\n")
25
+ f.write(gen.js_for_data_criteria())
26
+
27
+ f.write("// #########################\n")
28
+ f.write("// ##### POPULATION CRITERIA #####\n")
29
+ f.write("// #########################\n\n")
30
+
31
+ f.write("// INITIAL PATIENT POPULATION\n")
32
+ f.write(gen.js_for('IPP'))
33
+ f.write("// DENOMINATOR\n")
34
+ f.write(gen.js_for('DENOM'))
35
+ f.write("// NUMERATOR\n")
36
+ f.write(gen.js_for('NUMER'))
37
+ f.write("// EXCLUSIONS\n")
38
+ f.write(gen.js_for('EXCL'))
39
+ f.write("// DENOMINATOR EXCEPTIONS\n")
40
+ f.write(gen.js_for('DENEXCEP'))
41
+ end
42
+
43
+ puts "wrote javascript to: #{out_file}"
44
+
45
+ end
46
+ end
47
+
@@ -0,0 +1,8 @@
1
+ namespace :cover_me do
2
+
3
+ task :report do
4
+ require 'cover_me'
5
+ CoverMe.complete!
6
+ end
7
+
8
+ end