hqmf2js 1.0.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 (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