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.
- 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,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 -%>
|
data/lib/generator/js.rb
ADDED
@@ -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,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,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
|
+
|