lib/generator/js.rb in hqmf2js-1.3.0 vs lib/generator/js.rb in hqmf2js-1.4.0

- old
+ new

@@ -1,64 +1,73 @@ module HQMF2JS module Generator - + def self.render_template(name, params) template_path = File.expand_path(File.join('..', "#{name}.js.erb"), __FILE__) template_str = File.read(template_path) template = ERB.new(template_str, nil, '-', "_templ#{TemplateCounter.instance.new_id}") context = ErbContext.new(params) - template.result(context.get_binding) + template.result(context.get_binding) end - + # Utility class used to supply a binding to Erb. Contains utility functions used # by the erb templates that are used to generate code. class ErbContext < OpenStruct - + # Create a new context # @param [Hash] vars a hash of parameter names (String) and values (Object). # Each entry is added as an accessor of the new Context def initialize(vars) super(vars) end - + # Get a binding that contains all the instance variables # @return [Binding] def get_binding binding end - + def js_for_measure_period(measure_period) HQMF2JS::Generator.render_template('measure_period', {'measure_period' => measure_period}) end - + def js_for_characteristic(criteria) HQMF2JS::Generator.render_template('characteristic', {'criteria' => criteria}) end - + def js_for_patient_data(criteria) HQMF2JS::Generator.render_template('patient_data', {'criteria' => criteria}) end - + def js_for_derived_data(criteria) HQMF2JS::Generator.render_template('derived_data', {'criteria' => criteria}) end - + def field_method(field_name) HQMF::DataCriteria::FIELDS[field_name][:coded_entry_method].to_s.camelize(:lower) end - - def field_library_method(field_name) + + def field_library_method(field_name, value=nil) field_type = HQMF::DataCriteria::FIELDS[field_name][:field_type] if field_type == :value 'filterEventsByField' + elsif field_type == :timestamp && !value.nil? && value.type == 'IVL_TS' # Handles static date comparisons + 'filterEventsByField' elsif field_type == :timestamp 'adjustBoundsForField' elsif field_type == :nested_timestamp 'denormalizeEventsByLocation' + elsif field_type == :reference + 'filterEventsByReference' end end - + + def is_result_criteria(criteria) + settings = HQMF::DataCriteria.get_settings_for_definition(criteria.definition, criteria.status) + settings && settings['sub_category'] == 'result' + end + def js_for_value(value) if value if value.respond_to?(:derived?) && value.derived? value.expression else @@ -72,10 +81,12 @@ if value.unit != nil "new PQ(#{value.value}, \"#{value.unit}\", #{value.inclusive?})" else "new PQ(#{value.value}, null, #{value.inclusive?})" end + elsif value.type=='TS' + "new TS(\"#{value.value}\", #{value.inclusive?})" elsif value.type=='ANYNonNull' "new #{value.type}()" elsif value.respond_to?(:unit) && value.unit != nil "new #{value.type}(#{value.value}, \"#{value.unit}\", #{value.inclusive?})" elsif value.respond_to?(:inclusive?) and !value.inclusive?.nil? @@ -89,16 +100,19 @@ end end def js_for_bounds(bounds) if (bounds.respond_to?(:low) && bounds.respond_to?(:high)) - "new IVL_PQ(#{js_for_value(bounds.low)}, #{js_for_value(bounds.high)})" + type = bounds.type || 'IVL_PQ' + "new #{type}(#{js_for_value(bounds.low)}, #{js_for_value(bounds.high)})" + elsif bounds.respond_to?(:reference) + "hqmfjs.#{bounds.reference.gsub(/\W/, '_')}(patient,initialSpecificContext)" else "#{js_for_value(bounds)}" end end - + def js_for_date_bound(criteria) bound = nil if criteria.effective_time if criteria.effective_time.high bound = criteria.effective_time.high @@ -117,75 +131,76 @@ else raise "do not know how to get a date for this type" end end end - + if bound "#{js_for_value(bound)}.asDate()" else 'MeasurePeriod.high.asDate()' end end - + def js_for_code_list(criteria) if criteria.inline_code_list criteria.inline_code_list.to_json elsif criteria.code_list_id.nil? "null" else "getCodes(\"#{criteria.code_list_id}\")" end end - + # Returns the JavaScript generated for a HQMF::Precondition def js_for_precondition(precondition, indent, context=false) HQMF2JS::Generator.render_template('precondition', {'doc' => doc, 'precondition' => precondition, 'indent' => indent, 'context' => context}) end - + def patient_api_method(criteria) criteria.patient_api_function end - + def conjunction_code_for(precondition) precondition.conjunction_code_with_negation end - + # Returns a Javascript compatable name based on an entity's identifier def js_name(entity) if !entity.id raise "No identifier for #{entity.to_json}" end entity.id.gsub(/\W/, '_') end - + end class JS - + # Entry point to JavaScript generator def initialize(doc) @doc = doc end def self.map_reduce_utils File.read(File.expand_path(File.join('..', '..', "assets",'javascripts','libraries','map_reduce_utils.js'), __FILE__)) end - + def to_js(population_index=0, codes=nil, force_sources=nil) population_index ||= 0 population = @doc.populations[population_index] - + if codes oid_dictionary = HQMF2JS::Generator::CodesToJson.hash_to_js(codes) else oid_dictionary = "<%= oid_dictionary %>" end - + sub_ids = ('a'..'zz').to_a sub_id = @doc.populations.size > 1 ? "'#{sub_ids[population_index]}'" : "null"; + stratified = !population[HQMF::PopulationCriteria::STRAT].nil? " // ######################### // ##### DATA ELEMENTS ##### // ######################### @@ -194,33 +209,39 @@ hqmfjs.hqmf_id = '#{@doc.hqmf_id}'; hqmfjs.sub_id = #{sub_id}; if (typeof(test_id) == 'undefined') hqmfjs.test_id = null; OidDictionary = #{oid_dictionary}; - + #{js_for_data_criteria(force_sources)} // ######################### // ##### MEASURE LOGIC ##### // ######################### - + #{js_initialize_specifics(@doc.source_data_criteria)} // INITIAL PATIENT POPULATION #{js_for(population[HQMF::PopulationCriteria::IPP], HQMF::PopulationCriteria::IPP)} + // STRATIFICATION + #{(stratified ? js_for(population[HQMF::PopulationCriteria::STRAT], HQMF::PopulationCriteria::STRAT, true) : 'hqmfjs.'+HQMF::PopulationCriteria::STRAT+'=null;')} // DENOMINATOR #{js_for(population[HQMF::PopulationCriteria::DENOM], HQMF::PopulationCriteria::DENOM, true)} // NUMERATOR #{js_for(population[HQMF::PopulationCriteria::NUMER], HQMF::PopulationCriteria::NUMER)} + #{js_for(population[HQMF::PopulationCriteria::NUMEX], HQMF::PopulationCriteria::NUMEX)} #{js_for(population[HQMF::PopulationCriteria::DENEX], HQMF::PopulationCriteria::DENEX)} #{js_for(population[HQMF::PopulationCriteria::DENEXCEP], HQMF::PopulationCriteria::DENEXCEP)} // CV - #{js_for(population[HQMF::PopulationCriteria::MSRPOPL], HQMF::PopulationCriteria::MSRPOPL)} + #{js_for(population[HQMF::PopulationCriteria::MSRPOPL], HQMF::PopulationCriteria::MSRPOPL, true)} + #{js_for(population[HQMF::PopulationCriteria::MSRPOPLEX], HQMF::PopulationCriteria::MSRPOPLEX)} #{js_for(population[HQMF::PopulationCriteria::OBSERV], HQMF::PopulationCriteria::OBSERV)} + // VARIABLES + #{js_for_variables()} " end - + def js_initialize_specifics(data_criteria) specific_occurrences = [] data_criteria.each do |criteria| if (criteria.specific_occurrence) specific_occurrences << {id: "#{criteria.id}", type: "#{criteria.specific_occurrence_const}", function: "#{criteria.source_data_criteria}"} @@ -229,11 +250,11 @@ json_list = specific_occurrences.map {|occurrence| occurrence.to_json} specifics_list = json_list.join(',') specifics_list = ",#{specifics_list}" unless specifics_list.empty? "hqmfjs.initializeSpecifics = function(patient_api, hqmfjs) { hqmf.SpecificsManager.initialize(patient_api,hqmfjs#{specifics_list}) }" end - + # Generate JS for a HQMF2::PopulationCriteria def js_for(criteria_code, type=nil, when_not_found=false) # for multiple populations, criteria code will be something like IPP_1 and type will be IPP type ||= criteria_code criteria = @doc.population_criteria(criteria_code) @@ -245,21 +266,33 @@ end else "hqmfjs.#{type} = function(patient) { return new Boolean(#{when_not_found}); }" end end - + # Generate JS for a HQMF2::DataCriteria def js_for_data_criteria(force_sources=nil) HQMF2JS::Generator.render_template('data_criteria', {'all_criteria' => @doc.specific_occurrence_source_data_criteria(force_sources).concat(@doc.all_data_criteria), 'measure_period' => @doc.measure_period}) end - + + def js_for_variables() + variables_js = "" + variables_js += "hqmfjs.VARIABLES = function(patient, initialSpecificContext) {\n" + @doc.source_data_criteria.each do |criteria| + if criteria.variable && !criteria.specific_occurrence + variables_js += "hqmfjs." + criteria.id + "(patient, initialSpecificContext);\n" + end + end + variables_js += "return false;\n}" + variables_js + end + def self.library_functions(check_crosswalk=false, include_underscore=true) ctx = Sprockets::Environment.new(File.expand_path("../../..", __FILE__)) - Tilt::CoffeeScriptTemplate.default_bare = true + Tilt::CoffeeScriptTemplate.default_bare = true ctx.append_path "app/assets/javascripts" - + libraries = [] if include_underscore libraries += ["// #########################\n// ###### Underscore.js #######\n// #######################\n", ctx.find_asset('underscore').to_s] @@ -268,11 +301,11 @@ libraries += ["// #########################\n// ###### PATIENT API #######\n// #########################\n", HqueryPatientApi::Generator.patient_api_javascript.to_s, "// #########################\n// ## SPECIFIC OCCURRENCES ##\n// #########################\n", ctx.find_asset('specifics').to_s, "// #########################\n// ### LIBRARY FUNCTIONS ####\n// #########################\n", - ctx.find_asset('hqmf_util').to_s, + ctx.find_asset('hqmf_util').to_s, "// #########################\n// ### PATIENT EXTENSION ####\n// #########################\n", ctx.find_asset('patient_api_extension').to_s, "// #########################\n// ## CUSTOM CALCULATIONS ###\n// #########################\n", ctx.find_asset('custom_calculations').to_s, "// #########################\n// ##### LOGGING UTILS ######\n// #########################\n", @@ -285,35 +318,35 @@ end libraries.join("\n") end - + # Allow crosswalk functionality to be loaded separately from main JS libraries def self.crosswalk_functions ctx = Sprockets::Environment.new(File.expand_path("../../..", __FILE__)) Tilt::CoffeeScriptTemplate.default_bare = true ctx.append_path "app/assets/javascripts" ctx.find_asset('crosswalk').to_s end end - + # Simple class to issue monotonically increasing integer identifiers class Counter def initialize @count = 0 end - + def new_id @count+=1 end end - + # Singleton to keep a count of function identifiers class FunctionCounter < Counter include Singleton end - + # Singleton to keep a count of template identifiers class TemplateCounter < Counter include Singleton end end