bonnie_bundler 2.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 (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