bonnie_bundler 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +3 -0
  3. data/.travis.yml +12 -0
  4. data/Gemfile +29 -0
  5. data/Gemfile.lock +267 -0
  6. data/README.md +4 -0
  7. data/Rakefile +29 -0
  8. data/bonnie-bundler.gemspec +29 -0
  9. data/config/initializers/mongo.rb +1 -0
  10. data/config/measures/measures_2_4_0.yml +719 -0
  11. data/config/mongoid.yml +6 -0
  12. data/lib/bonnie_bundler.rb +39 -0
  13. data/lib/ext/hash.rb +28 -0
  14. data/lib/ext/railtie.rb +11 -0
  15. data/lib/ext/valueset.rb +11 -0
  16. data/lib/measures/cql_to_elm_helper.rb +90 -0
  17. data/lib/measures/elm_parser.rb +74 -0
  18. data/lib/measures/loading/base_loader_definition.rb +61 -0
  19. data/lib/measures/loading/cql_loader.rb +420 -0
  20. data/lib/measures/loading/exceptions.rb +10 -0
  21. data/lib/measures/loading/loader.rb +178 -0
  22. data/lib/measures/loading/value_set_loader.rb +137 -0
  23. data/lib/measures/logic_extractor.rb +552 -0
  24. data/lib/measures/mongo_hash_key_wrapper.rb +44 -0
  25. data/lib/models/cql_measure.rb +160 -0
  26. data/lib/models/measure.rb +330 -0
  27. data/test/fixtures/BCS_v5_0_Artifacts.zip +0 -0
  28. data/test/fixtures/CMS158_v5_4_Artifacts.zip +0 -0
  29. data/test/fixtures/CMS158_v5_4_Artifacts_Update.zip +0 -0
  30. data/test/fixtures/DRAFT_CMS2_CQL.zip +0 -0
  31. data/test/fixtures/bonnienesting01_fixed.zip +0 -0
  32. data/test/fixtures/vcr_cassettes/mat_5-4_cql_export_vsac_response.yml +4723 -0
  33. data/test/fixtures/vcr_cassettes/multi_library_webcalls.yml +1892 -0
  34. data/test/fixtures/vcr_cassettes/valid_translation_response.yml +1120 -0
  35. data/test/fixtures/vcr_cassettes/valid_vsac_response.yml +1678 -0
  36. data/test/fixtures/vcr_cassettes/valid_vsac_response_158.yml +1670 -0
  37. data/test/fixtures/vcr_cassettes/valid_vsac_response_158_update.yml +1670 -0
  38. data/test/fixtures/vcr_cassettes/valid_vsac_response_includes_draft.yml +3480 -0
  39. data/test/fixtures/vcr_cassettes/vs_loading_draft_no_profile_version.yml +1198 -0
  40. data/test/fixtures/vcr_cassettes/vs_loading_draft_profile.yml +1198 -0
  41. data/test/fixtures/vcr_cassettes/vs_loading_draft_verion.yml +1198 -0
  42. data/test/fixtures/vcr_cassettes/vs_loading_no_profile_version.yml +1198 -0
  43. data/test/fixtures/vcr_cassettes/vs_loading_profile.yml +1196 -0
  44. data/test/fixtures/vcr_cassettes/vs_loading_version.yml +20331 -0
  45. data/test/fixtures/vs_loading/DocofMeds_v5_1_Artifacts.zip +0 -0
  46. data/test/fixtures/vs_loading/DocofMeds_v5_1_Artifacts_Version.zip +0 -0
  47. data/test/fixtures/vs_loading/DocofMeds_v5_1_Artifacts_With_Profiles.zip +0 -0
  48. data/test/simplecov_init.rb +18 -0
  49. data/test/test_helper.rb +44 -0
  50. data/test/unit/load_mat_export_test.rb +181 -0
  51. data/test/unit/measure_complexity_test.rb +32 -0
  52. data/test/unit/measure_diff_test.rb +68 -0
  53. data/test/unit/mongo_hash_key_wrapper_test.rb +247 -0
  54. data/test/unit/storing_mat_export_package_test.rb +45 -0
  55. data/test/unit/value_set_loading_test.rb +109 -0
  56. data/test/vcr_setup.rb +20 -0
  57. metadata +258 -0
@@ -0,0 +1,44 @@
1
+ module Measures
2
+ # keys can't contain periods https://docs.mongodb.com/manual/reference/limits/#Restrictions-on-Field-Names
3
+ # technically they can't start with dollar sign either, but that's prohibited for CQL naming
4
+ # so:
5
+ # periods are converted to '^p'
6
+ # and carets are converted to '^c' therefore we aren't invalidating the use of any characters (e.g. caret)
7
+ class MongoHashKeyWrapper
8
+
9
+ def self.wrapKeys(theHash)
10
+ newKeys = Hash.new
11
+ theHash.keys.each do |key|
12
+ if (key.include? '.') || (key.include? '^')
13
+ newKeys[key] = key.gsub(/[\^\.]/, '^' => '^c', '.' => '^p')
14
+ end
15
+ end
16
+ newKeys.each { |old, new| theHash[new] = theHash.delete old}
17
+ # now recurse on any contained hashes
18
+ theHash.each do |key,value|
19
+ if value.respond_to?(:key)
20
+ wrapKeys(value)
21
+ end
22
+ end
23
+ end
24
+
25
+ def self.unwrapKeys(theHash)
26
+ newKeys = Hash.new
27
+ theHash.keys.each do |key|
28
+ if (key.include? '^p') || (key.include? '^c')
29
+ newKey = key.gsub(/\^p/, '.')
30
+ newKey.gsub!(/\^c/, '^')
31
+ newKeys[key] = newKey
32
+ end
33
+ end
34
+ newKeys.each { |old, new| theHash[new] = theHash.delete old}
35
+ # now recurse on any contained hashes
36
+ theHash.each do |key,value|
37
+ if value.respond_to?(:key)
38
+ unwrapKeys(value)
39
+ end
40
+ end
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,160 @@
1
+ class CqlMeasure
2
+ include Mongoid::Document
3
+ include Mongoid::Timestamps
4
+ include Mongoid::Attributes::Dynamic
5
+
6
+ DEFAULT_EFFECTIVE_DATE = Time.gm(2012,12,31,23,59).to_i
7
+ MP_START_DATE = Time.gm(2012,1,1,0,0).to_i
8
+ TYPES = ["ep", "eh"]
9
+
10
+ field :id, type: String
11
+ field :measure_id, type: String
12
+ field :hqmf_id, type: String
13
+ field :hqmf_set_id, type: String
14
+ field :hqmf_version_number, type: Integer
15
+ field :cms_id, type: String
16
+ field :title, type: String, default: ""
17
+ field :description, type: String, default: ""
18
+ field :type, type: String
19
+ field :category, type: String, default: 'uncategorized'
20
+
21
+ field :episode_of_care, type: Boolean
22
+ field :continuous_variable, type: Boolean
23
+ field :episode_ids, type: Array
24
+
25
+ field :needs_finalize, type: Boolean, default: false
26
+
27
+ field :published, type: Boolean
28
+ field :publish_date, type: Date
29
+ field :version, type: Integer
30
+
31
+ field :elm_annotations, type: Hash
32
+
33
+ field :cql, type: Array
34
+ field :elm, type: Array
35
+ field :main_cql_library, type: String
36
+ field :cql_statement_dependencies, type: Hash
37
+
38
+ field :population_criteria, type: Hash
39
+ field :data_criteria, type: Hash
40
+ field :source_data_criteria, type: Hash
41
+ field :measure_period, type: Hash
42
+ field :measure_attributes, type: Array
43
+ field :populations, type: Array
44
+ field :populations_cql_map, type: Hash
45
+ field :observations, type: Array
46
+
47
+ field :value_set_oids, type: Array, default: []
48
+ field :value_set_oid_version_objects, type: Array, default: []
49
+
50
+ field :complexity, type: Hash
51
+
52
+ belongs_to :user
53
+ belongs_to :bundle, class_name: "HealthDataStandards::CQM::Bundle"
54
+ has_and_belongs_to_many :records, :inverse_of => nil
55
+ has_one :package, class_name: "CqlMeasurePackage", inverse_of: :measure, dependent: :delete
56
+
57
+ scope :by_measure_id, ->(id) { where({'measure_id'=>id }) }
58
+ scope :by_type, ->(type) { where({'type'=>type}) }
59
+ scope :by_user, ->(user) { where user_id: user.id }
60
+
61
+ index "user_id" => 1
62
+ # Find the measures matching a patient
63
+ def self.for_patient(record)
64
+ where user_id: record.user_id, hqmf_set_id: { '$in' => record.measure_ids }
65
+ end
66
+
67
+ def value_sets
68
+ options = { oid: value_set_oids }
69
+ options[:user_id] = user.id if user?
70
+ @value_sets ||= HealthDataStandards::SVS::ValueSet.in(options)
71
+ @value_sets
72
+ end
73
+
74
+ # Returns the hqmf-parser's ruby implementation of an HQMF document.
75
+ # Rebuild from population_criteria, data_criteria, and measure_period JSON
76
+ def as_hqmf_model
77
+ json = {
78
+ "id" => self.measure_id,
79
+ "title" => self.title,
80
+ "description" => self.description,
81
+ "population_criteria" => self.population_criteria,
82
+ "data_criteria" => self.data_criteria,
83
+ "source_data_criteria" => self.source_data_criteria,
84
+ "measure_period" => self.measure_period,
85
+ "attributes" => self.measure_attributes,
86
+ "populations" => self.populations,
87
+ "hqmf_id" => self.hqmf_id,
88
+ "hqmf_set_id" => self.hqmf_set_id,
89
+ "hqmf_version_number" => self.hqmf_version_number,
90
+ "cms_id" => self.cms_id
91
+ }
92
+ HQMF::Document.from_json(json)
93
+ end
94
+
95
+ def all_data_criteria
96
+ as_hqmf_model.all_data_criteria
97
+ end
98
+
99
+ # Note whether or not the measure is a continuous variable measure.
100
+ before_save :set_continuous_variable
101
+ def set_continuous_variable
102
+ # The return value of this function is not related to whether or not this
103
+ # measure is a CV measure. The true return value ensures false is not
104
+ # accidentally returned here, which would cause the chain of 'before_*' to
105
+ # stop executing.
106
+ self.continuous_variable = populations.map {|x| x.keys}.flatten.uniq.include? HQMF::PopulationCriteria::MSRPOPL
107
+ true
108
+ end
109
+
110
+ # When saving calculate the cyclomatic complexity of the measure
111
+ # TODO: Do we want to consider a measure other than "cyclomatic complexity" for CQL?
112
+ # TODO: THIS IS NOT CYCLOMATIC COMPLEXITY, ALL MULTIPLE ELEMENT EXPRESSIONS GET COUNTED AS HIGHER COMPLEXITY, NOT JUST LOGICAL
113
+ before_save :calculate_complexity
114
+ def calculate_complexity
115
+ # We calculate the complexity for each statement, and (at least for now) store the result in the same way
116
+ # we store the complexity for QDM variables
117
+ # TODO: consider whether this is too much of a force fit
118
+ self.complexity = { variables: [] }
119
+ # Recursively look through an expression to count the logical branches
120
+ def count_expression_logical_branches(expression)
121
+ case expression
122
+ when nil
123
+ 0
124
+ when Array
125
+ expression.map { |exp| count_expression_logical_branches(exp) }.sum
126
+ when Hash
127
+ case expression['type']
128
+ when 'And', 'Or', 'Not'
129
+ count_expression_logical_branches(expression['operand'])
130
+ when 'Query'
131
+ # TODO: Do we need to look into the source side of the query? Can there be logical operators there?
132
+ count_expression_logical_branches(expression['where']) + count_expression_logical_branches(expression['relationship'])
133
+ else
134
+ 1
135
+ end
136
+ else
137
+ 0
138
+ end
139
+ end
140
+
141
+ # Determine the complexity of each statement
142
+ self.elm.each do |elm|
143
+ if statements = elm.try(:[], 'library').try(:[], 'statements').try(:[], 'def')
144
+ statements.each do |statement|
145
+ self.complexity[:variables] << { name: statement['name'], complexity: count_expression_logical_branches(statement['expression']) }
146
+ end
147
+ end
148
+ end
149
+ self.complexity
150
+ end
151
+
152
+ end
153
+
154
+ class CqlMeasurePackage
155
+ include Mongoid::Document
156
+ include Mongoid::Timestamps
157
+
158
+ field :file, type: BSON::Binary
159
+ belongs_to :measure, class_name: "CqlMeasure", inverse_of: :package
160
+ end
@@ -0,0 +1,330 @@
1
+ class Measure
2
+ include Mongoid::Document
3
+ include Mongoid::Timestamps
4
+ include Mongoid::Attributes::Dynamic
5
+
6
+ DEFAULT_EFFECTIVE_DATE = Time.gm(2012,12,31,23,59).to_i
7
+ MP_START_DATE = Time.gm(2012,1,1,0,0).to_i
8
+ TYPES = ["ep", "eh"]
9
+
10
+ store_in collection: 'draft_measures'
11
+
12
+ field :id, type: String
13
+ field :measure_id, type: String
14
+ field :hqmf_id, type: String # should be using this one as primary id!!
15
+ field :hqmf_set_id, type: String
16
+ field :hqmf_version_number, type: Integer
17
+ field :cms_id, type: String
18
+ field :title, type: String
19
+ field :description, type: String
20
+ field :type, type: String
21
+ field :category, type: String, default: 'uncategorized'
22
+
23
+ field :episode_of_care, type: Boolean
24
+ field :continuous_variable, type: Boolean
25
+ field :episode_ids, type: Array # of String ids
26
+ field :custom_functions, type: Hash # stores a custom function for a population criteria (used only in ADE_TTR for observation)
27
+ field :force_sources, type: Array # stores a list of source data criteria to force method creation for (used only in ADE_TTR for LaboratoryTestResultInr)
28
+
29
+ field :needs_finalize, type: Boolean, default: false # if true it indicates that the measure needs to have its episodes or submeasure titles defined
30
+
31
+ field :published, type: Boolean
32
+ field :publish_date, type: Date
33
+ field :version, type: Integer
34
+
35
+ field :population_criteria, type: Hash
36
+ field :data_criteria, type: Hash, default: {}
37
+ field :source_data_criteria, type: Hash, default: {}
38
+ field :measure_period, type: Hash
39
+ field :measure_attributes, type: Array
40
+ field :populations, type: Array
41
+ field :preconditions, type: Hash
42
+
43
+ field :value_set_oids, type: Array, default: []
44
+
45
+ field :map_fns, type: Array, default: []
46
+
47
+ field :complexity, type: Hash
48
+ field :measure_logic, type: Array
49
+
50
+ #make sure that the use has a bundle associated with them
51
+ before_save :set_continuous_variable
52
+
53
+ # Cache the generated JS code, with optional options to manipulate cached result
54
+ def map_fn(population_index, options = {})
55
+ options.assert_valid_keys :clear_db_cache, :cache_result_in_db, :check_crosswalk
56
+ # Defaults are: don't clear the cache, do cache the result in the DB, use user specified crosswalk setting
57
+ options.reverse_merge! clear_db_cache: false, cache_result_in_db: true, check_crosswalk: !!self.user.try(:crosswalk_enabled)
58
+ self.map_fns[population_index] = nil if options[:clear_db_cache]
59
+ self.map_fns[population_index] ||= as_javascript(population_index, options[:check_crosswalk])
60
+ save if changed? && options[:cache_result_in_db]
61
+ self.map_fns[population_index]
62
+ end
63
+
64
+ # Generate and cache all the javascript for the measure, optionally clearing the cache first
65
+ def generate_js(options = {})
66
+ populations.each_with_index { |p, idx| map_fn(idx, options) }
67
+ end
68
+
69
+ # Clear any cached JavaScript, forcing it to be generated next time it's requested
70
+ def clear_cached_js
71
+ self.map_fns.map! { nil }
72
+ self.save
73
+ end
74
+
75
+ belongs_to :user
76
+ belongs_to :bundle, class_name: "HealthDataStandards::CQM::Bundle"
77
+ has_and_belongs_to_many :records, :inverse_of => nil
78
+
79
+ scope :by_measure_id, ->(id) { where({'measure_id'=>id }) }
80
+ scope :by_user, ->(user) { where({'user_id'=>user.id}) }
81
+ scope :by_type, ->(type) { where({'type'=>type}) }
82
+
83
+ index "user_id" => 1
84
+ # Find the measures matching a patient
85
+ def self.for_patient(record)
86
+ where user_id: record.user_id, hqmf_set_id: { '$in' => record.measure_ids }
87
+ end
88
+
89
+ TYPE_MAP = {
90
+ 'problem' => 'conditions',
91
+ 'encounter' => 'encounters',
92
+ 'labresults' => 'results',
93
+ 'procedure' => 'procedures',
94
+ 'medication' => 'medications',
95
+ 'rx' => 'medications',
96
+ 'demographics' => 'characteristic',
97
+ 'derived' => 'derived'
98
+ }
99
+
100
+ # Returns the hqmf-parser's ruby implementation of an HQMF document.
101
+ # Rebuild from population_criteria, data_criteria, and measure_period JSON
102
+ def as_hqmf_model
103
+ json = {
104
+ "id" => self.measure_id,
105
+ "title" => self.title,
106
+ "description" => self.description,
107
+ "population_criteria" => self.population_criteria,
108
+ "data_criteria" => self.data_criteria,
109
+ "source_data_criteria" => self.source_data_criteria,
110
+ "measure_period" => self.measure_period,
111
+ "attributes" => self.measure_attributes,
112
+ "populations" => self.populations,
113
+ "hqmf_id" => self.hqmf_id,
114
+ "hqmf_set_id" => self.hqmf_set_id,
115
+ "hqmf_version_number" => self.hqmf_version_number,
116
+ "cms_id" => self.cms_id
117
+ }
118
+
119
+ HQMF::Document.from_json(json)
120
+ end
121
+
122
+ def value_sets
123
+ options = { oid: value_set_oids }
124
+ options[:user_id] = user.id if user?
125
+ @value_sets ||= HealthDataStandards::SVS::ValueSet.in(options)
126
+ @value_sets
127
+ end
128
+
129
+ def all_data_criteria
130
+ as_hqmf_model.all_data_criteria
131
+ end
132
+
133
+ def as_javascript(population_index, check_crosswalk=false)
134
+ options = {
135
+ value_sets: value_sets,
136
+ episode_ids: episode_ids,
137
+ continuous_variable: continuous_variable,
138
+ force_sources: force_sources,
139
+ custom_functions: custom_functions,
140
+ check_crosswalk: check_crosswalk
141
+ }
142
+
143
+ HQMF2JS::Generator::Execution.logic(as_hqmf_model, population_index, options)
144
+ end
145
+
146
+ def set_continuous_variable
147
+ self.continuous_variable = populations.map {|x| x.keys}.flatten.uniq.include? HQMF::PopulationCriteria::MSRPOPL
148
+ true
149
+ end
150
+
151
+ ############################## Measure Criteria Keys ##############################
152
+
153
+ # Given a data criteria, return the list of all data criteria keys referenced within, either through
154
+ # children criteria or temporal references; this includes the passed in criteria reference
155
+ def data_criteria_criteria_keys(criteria_reference)
156
+ criteria_keys = [criteria_reference]
157
+ if criteria = self.data_criteria[criteria_reference]
158
+ if criteria['children_criteria'].present?
159
+ criteria_keys.concat(criteria['children_criteria'].map { |c| data_criteria_criteria_keys(c) }.flatten)
160
+ end
161
+ if criteria['temporal_references'].present?
162
+ criteria_keys.concat(criteria['temporal_references'].map { |tr| data_criteria_criteria_keys(tr['reference']) }.flatten)
163
+ end
164
+ end
165
+ criteria_keys
166
+ end
167
+
168
+ # Given a precondition, return the list of all data criteria keys referenced within
169
+ def precondition_criteria_keys(precondition)
170
+ if precondition['preconditions'] && precondition['preconditions'].size > 0
171
+ precondition['preconditions'].map { |p| precondition_criteria_keys(p) }.flatten
172
+ elsif precondition['reference']
173
+ data_criteria_criteria_keys(precondition['reference'])
174
+ else
175
+ []
176
+ end
177
+ end
178
+
179
+ # Return the list of all data criteria keys in this measure, indexed by population code
180
+ def criteria_keys_by_population
181
+ criteria_keys_by_population = {}
182
+ population_criteria.each do |name, precondition|
183
+ criteria_keys_by_population[name] = precondition_criteria_keys(precondition).reject { |ck| ck == 'MeasurePeriod' }
184
+ end
185
+ criteria_keys_by_population
186
+ end
187
+
188
+ ############################## Measure Complexity Analysis ##############################
189
+
190
+ def precondition_complexity(precondition)
191
+ # We want to calculate the number of branching paths; we can do that by simply counting the leaf nodes.
192
+ # Any children of this particular node can appear either through child preconditions or by reference to a
193
+ # data criteria. ASSERTION: a precondition can never both have child preconditions and a data criteria.
194
+ if precondition['preconditions'] && precondition['preconditions'].size > 0
195
+ precondition['preconditions'].map { |p| precondition_complexity(p) }.sum
196
+ elsif precondition['reference']
197
+ data_criteria_complexity(precondition['reference'])
198
+ else
199
+ 1
200
+ end
201
+ end
202
+
203
+ def data_criteria_complexity(criteria_reference, options = {})
204
+ options.reverse_merge! calculating_variable: false
205
+ # We want to calculate the number of branching paths, which we can normally do by counting leaf nodes.
206
+ # This is more complicated for data criteria because, in addition to direct children, the criteria can
207
+ # also have temporal references, which can themselves branch. Our approach is to calculate an initial
208
+ # number of leaf nodes through looking at direct children and then seeing if any additional leaves are
209
+ # added through temporal references. A temporal reference that doesn't branch doesn't add a leaf node.
210
+ # Finally, this reference may be a variable, in which case we consider this a leaf node *unless* we are
211
+ # explicitly calculating the complexity of the variable itself
212
+ if criteria = self.data_criteria[criteria_reference]
213
+ complexity = if criteria['children_criteria'].present? && (!criteria['variable'] || options[:calculating_variable])
214
+ criteria['children_criteria'].map { |c| data_criteria_complexity(c) }.sum
215
+ else
216
+ 1
217
+ end
218
+ complexity + if criteria['temporal_references'].present?
219
+ criteria['temporal_references'].map { |tr| data_criteria_complexity(tr['reference']) - 1 }.sum
220
+ else
221
+ 0
222
+ end
223
+ else
224
+ 1
225
+ end
226
+ end
227
+
228
+ # Calculate the complexity of the measure based on cyclomatic complexity (which for simple logical
229
+ # constructs as used to specify measure populations generally means counting clauses); we calculate
230
+ # the complexity separately for populations and individual variables; this is called when the
231
+ # measure is saved so that the calculated complexity is cached in the DB
232
+ before_save :calculate_complexity
233
+ def calculate_complexity
234
+ self.complexity = { populations: [], variables: [] }
235
+ self.population_criteria.each do |name, precondition|
236
+ complexity = precondition_complexity(precondition)
237
+ self.complexity[:populations] << { name: name, complexity: complexity }
238
+ end
239
+ self.source_data_criteria.each do |reference, criteria|
240
+ next unless criteria['variable']
241
+ name = criteria['description']
242
+ complexity = data_criteria_complexity(reference, calculating_variable: true)
243
+ self.complexity[:variables] << { name: name, complexity: complexity }
244
+ end
245
+ self.complexity
246
+ end
247
+
248
+ #########################################################################################
249
+
250
+ ############################## Measure Change Analysis ##############################
251
+
252
+ # Extract the measure logic text; this is also cached in the DB
253
+ before_save :extract_measure_logic
254
+ def extract_measure_logic
255
+ self.measure_logic = []
256
+ # There are occasional issues extracting measure logic; while we want to fix them we also don't want logic
257
+ # extraction issues to hold up loading or updating a measure
258
+ begin
259
+ self.measure_logic.concat HQMF::Measure::LogicExtractor.new().population_logic(self)
260
+ rescue => e
261
+ self.measure_logic << "Error parsing measure logic: #{e.message}"
262
+ end
263
+ self.measure_logic
264
+ end
265
+
266
+ # Compute a simplified diff hash for Complexity Dashboard usage; stored within measure.latest_diff
267
+ def diff(other)
268
+ HQMF::Measure::LogicExtractor.get_measure_logic_diff(self,other,true)
269
+ end
270
+
271
+ #########################################################################################
272
+
273
+ def measure_json(population_index=0,check_crosswalk=false)
274
+ options = {
275
+ value_sets: value_sets,
276
+ episode_ids: episode_ids,
277
+ continuous_variable: continuous_variable,
278
+ force_sources: force_sources,
279
+ custom_functions: custom_functions,
280
+ check_crosswalk: check_crosswalk
281
+ }
282
+ population_index ||= 0
283
+ json = {
284
+ id: self.hqmf_id,
285
+ nqf_id: self.measure_id,
286
+ hqmf_id: self.hqmf_id,
287
+ hqmf_set_id: self.hqmf_set_id,
288
+ hqmf_version_number: self.hqmf_version_number,
289
+ cms_id: self.cms_id,
290
+ name: self.title,
291
+ description: self.description,
292
+ type: self.type,
293
+ category: self.category,
294
+ map_fn: HQMF2JS::Generator::Execution.measure_js(self.as_hqmf_model, population_index, options),
295
+ continuous_variable: self.continuous_variable,
296
+ episode_of_care: self.episode_of_care,
297
+ hqmf_document: self.as_hqmf_model.to_json
298
+ }
299
+
300
+ if (self.populations.count > 1)
301
+ sub_ids = ('a'..'az').to_a
302
+ json[:sub_id] = sub_ids[population_index]
303
+ population_title = self.populations[population_index]['title']
304
+ json[:subtitle] = population_title
305
+ json[:short_subtitle] = population_title
306
+ end
307
+
308
+ if self.continuous_variable
309
+ observation = self.population_criteria[self.populations[population_index][HQMF::PopulationCriteria::OBSERV]]
310
+ json[:aggregator] = observation['aggregator']
311
+ end
312
+
313
+ json[:oids] = self.value_sets.map{|value_set| value_set.oid}.uniq
314
+
315
+ population_ids = {}
316
+ HQMF::PopulationCriteria::ALL_POPULATION_CODES.each do |type|
317
+ population_key = self.populations[population_index][type]
318
+ population_criteria = self.population_criteria[population_key]
319
+ if (population_criteria)
320
+ population_ids[type] = population_criteria['hqmf_id']
321
+ end
322
+ end
323
+ stratification = self['populations'][population_index]['stratification']
324
+ if stratification
325
+ population_ids['stratification'] = stratification
326
+ end
327
+ json[:population_ids] = population_ids
328
+ json
329
+ end
330
+ end