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,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
|
+
|