quality-measure-engine 2.5.3 → 3.0.0.beta.1

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.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- NzQxMzZjMGM5NTAxM2Q0OWM1NjZhN2U4ZWY2YzQ4NzEzYThkZjc3YQ==
4
+ ODgyZTA5NDU0MGQ0NjZlOTc2YWYwZTYyYmE3OWZlZTUzNDNmYTYzOA==
5
5
  data.tar.gz: !binary |-
6
- MjljNTk1NDBkNTFiYjY1M2ZjM2ZiM2QyNGY5MjE0YzhmZGIwZmEzOQ==
7
- SHA512:
6
+ ZjIwZDA5NTdkZWNjODBkYTMxNWY3YjA3ZWE3OWI3NTkwZTMwOTQxMQ==
7
+ !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- MWE3YTAwMTFlMzBiMzc2ODgyZWVhYTRhNGY5NTQ0MmNmNDkwMjVjODI3ZTg2
10
- MzM4MjYwY2RmNzc4NDVhZTViZGVlMzI5YWMyYTVhNzdhZjhhNTI4ZWU1MjIy
11
- Y2M3ZDI1Yjc5ZDgzMDcwMzNmZTA2ZGU0NjEzOWI0MTFmN2E5NzQ=
9
+ NTkxNTdlMWJiMzJjNDY0MjQxYjc0MjFjMGQyYTc4ZGZlZGU5ODhmNzIyMWI5
10
+ YWUxZDM0ODc5YzE0ZmI3OWRlMTg3ZWM5MWE5NDkyZGNkOTQ3YzI5ZWViNWUx
11
+ NzgzNWI0ZGQwZDE3MTk1MTU2N2ZjZjRhMGViMjY4MzIwNTFlZDM=
12
12
  data.tar.gz: !binary |-
13
- ZjQ4ODgxN2ExMDQwYzgzMDY2MDMyMTg3ZTU3ZTEyNWZlY2I2Zjg3ZDNjYWFl
14
- MmFlYzE5NjM4MjZhNDExNzAyNzliMDFhYTBlOTc0ZWNiMDU5NGYwNmM5MjI2
15
- YjAxZmIxN2U5M2IxMTEwMjgxZGRhY2JjODIxY2RkMmNiNDQyOWE=
13
+ ZDMwZTJkYTlkMWRmZWM1ZTc2YzZlNjM1ZTdlYTU0YzNlYTlmZThiNzhlN2Zh
14
+ NTA3NmVjNTk0ZjkyNjZkODZlYTZkYTVhZjYxZGQxMDUzNjMyMTZiZDdiNDRi
15
+ NWFhMjg1NmY1NzEzMzQ3NDAyOTA2MmVkMDhmYjU5ZjllMzQwZTI=
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- quality-measure-engine (2.5.3)
4
+ quality-measure-engine (3.0.0.beta.1)
5
5
  delayed_job_mongoid (~> 2.0.0)
6
6
  mongoid (~> 3.1.4)
7
7
  moped (~> 1.5.1)
@@ -62,7 +62,7 @@ GEM
62
62
  treetop (~> 1.4.8)
63
63
  method_source (0.8.2)
64
64
  mime-types (1.24)
65
- mini_portile (0.5.2)
65
+ mini_portile (0.5.1)
66
66
  minitest (4.7.5)
67
67
  mongoid (3.1.5)
68
68
  activemodel (~> 3.2)
@@ -70,7 +70,7 @@ GEM
70
70
  origin (~> 1.0)
71
71
  tzinfo (~> 0.3.29)
72
72
  moped (1.5.1)
73
- multi_json (1.7.9)
73
+ multi_json (1.8.2)
74
74
  nokogiri (1.6.0)
75
75
  mini_portile (~> 0.5.0)
76
76
  origin (1.1.0)
@@ -131,7 +131,7 @@ GEM
131
131
  polyglot (>= 0.3.1)
132
132
  turn (0.9.6)
133
133
  ansi
134
- tzinfo (0.3.37)
134
+ tzinfo (0.3.38)
135
135
 
136
136
  PLATFORMS
137
137
  ruby
@@ -1,30 +1,10 @@
1
1
  module QME
2
- module DatabaseAccess
3
-
4
- # Set up the information to connect to the database. Database host
5
- # and port may be set using the environment variables TEST_DB_HOST
6
- # and TEST_DB_PORT which default to localhost and 27017 respectively.
7
- # @param [String] db_name the name of the database to use
8
- def determine_connection_information(db_name = nil)
9
- @db_name = ENV['DB_NAME'] || db_name || 'test'
10
- @db_host = ENV['TEST_DB_HOST'] || 'localhost'
11
- @db_port = ENV['TEST_DB_PORT'] ? ENV['TEST_DB_PORT'].to_i : 27017
12
- end
13
-
2
+ module DatabaseAccess
14
3
  # Lazily creates a connection to the database and initializes the
15
4
  # JavaScript environment
16
5
  # @return [Moped::Session]
17
6
  def get_db
18
- if @db == nil
19
- if @db_name==nil || @db_host==nil || @db_port==nil
20
- determine_connection_information()
21
- end
22
-
23
- @db = Moped::Session.new(["#{@db_host}:#{@db_port}"])
24
- @db.use(@db_name)
25
- end
26
-
27
- @db
7
+ Mongoid.default_session
28
8
  end
29
9
  end
30
10
  end
@@ -0,0 +1,18 @@
1
+ module QME
2
+ class ManualExclusion
3
+ include Mongoid::Document
4
+ store_in collection: 'manual_exclusions'
5
+ field :measure_id, type: String
6
+ field :sub_id, type: String
7
+ field :medical_record_id, type: String
8
+
9
+
10
+ def self.apply_manual_exclusions(measure_id, sub_id)
11
+ mids = where({measure_id: measure_id, sub_id: sub_id}).collect {|me| me.medical_record_id}
12
+ QME::PatientCache.where({'value.measure_id'=>@measure_id, 'value.sub_id'=>@sub_id, 'value.medical_record_id'=>{'$in'=>mids} })
13
+ .update_all({'$set'=>{'value.manual_exclusion'=>true}})
14
+ end
15
+
16
+ end
17
+ end
18
+
@@ -66,8 +66,8 @@ module QME
66
66
  @params[name.to_s] = value
67
67
  end
68
68
  @measure_def = measure_def
69
- @measure_def['parameters'] ||= {}
70
- @measure_def['parameters'].each do |parameter, value|
69
+ @measure_def.parameters ||= {}
70
+ @measure_def.parameters.each do |parameter, value|
71
71
  if !@params.has_key?(parameter)
72
72
  raise "No value supplied for measure parameter: #{parameter}"
73
73
  end
@@ -75,17 +75,17 @@ module QME
75
75
  # if the map function is specified then replace any erb templates with their values
76
76
  # taken from the supplied params
77
77
  # always true for actual measures, not always true for unit tests
78
- if (@measure_def['map_fn'])
79
- template = ERB.new(@measure_def['map_fn'])
78
+ if (@measure_def.map_fn)
79
+ template = ERB.new(@measure_def.map_fn)
80
80
  context = Context.new(@db, @params)
81
- @measure_def['map_fn'] = template.result(context.get_binding)
81
+ @measure_def.map_fn = template.result(context.get_binding)
82
82
  end
83
83
  end
84
84
 
85
85
  # Get the map function for the measure
86
86
  # @return [String] the map function
87
87
  def map_function
88
- @measure_def['map_fn']
88
+ @measure_def.map_fn
89
89
  end
90
90
 
91
91
  # Get the reduce function for the measure, this is a simple
@@ -100,11 +100,11 @@ module QME
100
100
  if @params['test_id'] && @params['test_id'].class==Moped::BSON::ObjectId
101
101
  reduce += " patient.test_id = new ObjectId(\"#{@params['test_id']}\");\n"
102
102
  end
103
- if @measure_def['sub_id']
104
- reduce += " patient.sub_id = \"#{@measure_def['sub_id']}\";\n"
103
+ if @measure_def.sub_id
104
+ reduce += " patient.sub_id = \"#{@measure_def.sub_id}\";\n"
105
105
  end
106
- if @measure_def['nqf_id']
107
- reduce += " patient.nqf_id = \"#{@measure_def['nqf_id']}\";\n"
106
+ if @measure_def.nqf_id
107
+ reduce += " patient.nqf_id = \"#{@measure_def.nqf_id}\";\n"
108
108
  end
109
109
 
110
110
  reduce += "patient.effective_date = #{@params['effective_date']};
@@ -15,18 +15,24 @@ module QME
15
15
  # @param [String] measure_id the measure identifier
16
16
  # @param [String] sub_id the measure sub-identifier or null if the measure is single numerator
17
17
  # @param [Hash] parameter_values a hash that may contain the following keys: 'effective_date' the measurement period end date, 'test_id' an identifier for a specific set of patients
18
- def initialize(measure_id, sub_id, parameter_values)
18
+ def initialize(measure_id,sub_id, parameter_values)
19
+
19
20
  @measure_id = measure_id
20
- @sub_id = sub_id
21
+ @sub_id =sub_id
22
+
21
23
  @parameter_values = parameter_values
22
- @measure_def = QualityMeasure.new(@measure_id, @sub_id, parameter_values['bundle_id']).definition
23
- determine_connection_information()
24
+ q_filter = {hqmf_id: @measure_id,sub_id: @sub_id}
25
+ if @parameter_values.keys.index("bundle_id")
26
+ q_filter["bundle_id"] == @parameter_values['bundle_id']
27
+ @bundle_id = @parameter_values['bundle_id']
28
+ end
29
+ @measure_def = QualityMeasure.where(q_filter).first
24
30
  end
25
31
 
26
32
  def build_query
27
33
  pipeline = []
28
34
 
29
- filters = @parameter_values['filters']
35
+ filters = @parameter_values["filters"]
30
36
 
31
37
 
32
38
  match = {'value.measure_id' => @measure_id,
@@ -77,7 +83,7 @@ module QME
77
83
  'value.test_id' => @parameter_values['test_id'],
78
84
  'value.manual_exclusion' => {'$in' => [nil, false]}}
79
85
 
80
- keys = @measure_def["population_ids"].keys - [QME::QualityReport::OBSERVATION, "stratification"]
86
+ keys = @measure_def.population_ids.keys - [QME::QualityReport::OBSERVATION, "stratification"]
81
87
  supplemental_data = Hash[*keys.map{|k| [k,{QME::QualityReport::RACE => {},
82
88
  QME::QualityReport::ETHNICITY => {},
83
89
  QME::QualityReport::SEX => {},
@@ -131,24 +137,26 @@ module QME
131
137
  raise RuntimeError, "Expected one group from patient_cache aggregation, got #{aggregate['result'].size}"
132
138
  end
133
139
 
134
- nqf_id = @measure_def['nqf_id'] || @measure_def['id']
135
- result = {:measure_id => @measure_id, :sub_id => @sub_id, :nqf_id => nqf_id, :population_ids => @measure_def["population_ids"],
136
- :effective_date => @parameter_values['effective_date'],
137
- :test_id => @parameter_values['test_id'], :filters => @parameter_values['filters']}
140
+ nqf_id = @measure_def.nqf_id || @measure_def['id']
141
+ result = QME::QualityReportResult.new
142
+ result.population_ids=@measure_def.population_ids
143
+
138
144
 
139
- if @measure_def['continuous_variable']
145
+ if @measure_def.continuous_variable
140
146
  aggregated_value = calculate_cv_aggregation
141
147
  result[QME::QualityReport::OBSERVATION] = aggregated_value
142
148
  end
143
149
 
144
- result.merge!(aggregate['result'].first)
145
- result.reject! {|k, v| k == '_id'} # get rid of the group id the Mongo forced us to use
150
+ agg_result = aggregate['result'].first
151
+ agg_result.reject! {|k, v| k == '_id'} # get rid of the group id the Mongo forced us to use
146
152
  # result['exclusions'] += get_db['patient_cache'].find(base_query.merge({'value.manual_exclusion'=>true})).count
147
- result.merge!(execution_time: (Time.now.to_i - @parameter_values['start_time'].to_i)) if @parameter_values['start_time']
148
- result[:supplemental_data] = self.calculate_supplemental_data_elements
149
- get_db()["query_cache"].insert(result)
150
- get_db().command({:getLastError => 1}) # make sure last insert finished before we continue
153
+ agg_result.merge!(execution_time: (Time.now.to_i - @parameter_values['start_time'].to_i)) if @parameter_values['start_time']
154
+ agg_result.each_pair do |k,v|
155
+ result[k]=v
156
+ end
157
+ result.supplemental_data = self.calculate_supplemental_data_elements
151
158
  result
159
+
152
160
  end
153
161
 
154
162
  # This method calculates the aggregated value for a CV measure. It extracts all
@@ -170,7 +178,7 @@ module QME
170
178
  aggregate['result'].each do |freq_count_pair|
171
179
  frequencies[freq_count_pair['_id']] = freq_count_pair['count']
172
180
  end
173
- QME::MapReduce::CVAggregator.send(@measure_def['aggregator'].parameterize, frequencies)
181
+ QME::MapReduce::CVAggregator.send(@measure_def.aggregator.parameterize, frequencies)
174
182
  end
175
183
 
176
184
 
@@ -185,7 +193,7 @@ module QME
185
193
  :out => {:reduce => 'patient_cache'},
186
194
  :finalize => measure.finalize_function,
187
195
  :query => {:test_id => @parameter_values['test_id']})
188
- apply_manual_exclusions
196
+ QME::ManualExclusion.apply_manual_exclusions(@measure_id,@sub_id)
189
197
  end
190
198
 
191
199
  # This method runs the MapReduce job for the measure and a specific patient.
@@ -198,8 +206,9 @@ module QME
198
206
  :reduce => "function(key, values){return values;}",
199
207
  :out => {:reduce => 'patient_cache'},
200
208
  :finalize => measure.finalize_function,
201
- :query => {:medical_record_number => patient_id, :test_id => @parameter_values['test_id']})
202
- apply_manual_exclusions
209
+ :query => {:medical_record_number => patient_id, :test_id => @parameter_values["test_id"]})
210
+ QME::ManualExclusion.apply_manual_exclusions(@measure_id,@sub_id)
211
+
203
212
  end
204
213
 
205
214
  # This method runs the MapReduce job for the measure and a specific patient.
@@ -212,22 +221,13 @@ module QME
212
221
  :reduce => "function(key, values){return values;}",
213
222
  :out => {:inline => true},
214
223
  :raw => true,
215
- :finalize => measure.finalize_function,
216
- :query => {:medical_record_number => patient_id, :test_id => @parameter_values['test_id']})
224
+ :query => {:medical_record_number => patient_id, :test_id => @parameter_values["test_id"]})
225
+
217
226
  raise result['err'] if result['ok']!=1
218
227
  result['results'][0]['value']
219
228
  end
220
229
 
221
- # This collects the set of manual exclusions from the manual_exclusions collections
222
- # and sets a flag in each cached patient result for patients that have been excluded from the
223
- # current measure
224
- def apply_manual_exclusions
225
- exclusions = get_db()['manual_exclusions'].find({'measure_id'=>@measure_id, 'sub_id'=>@sub_id}).to_a.map do |exclusion|
226
- exclusion['medical_record_id']
227
- end
228
- get_db()['patient_cache'].find({'value.measure_id'=>@measure_id, 'value.sub_id'=>@sub_id, 'value.medical_record_id'=>{'$in'=>exclusions} })
229
- .update_all({'$set'=>{'value.manual_exclusion'=>true}})
230
- end
230
+
231
231
  end
232
232
  end
233
233
  end
@@ -2,30 +2,27 @@ module QME
2
2
  module MapReduce
3
3
  # A delayed_job that allows for measure calculation by a delayed_job worker. Can be created as follows:
4
4
  #
5
- # Delayed::Job.enqueue QME::MapRedude::MeasureCalculationJob.new(:measure_id => '0221', :sub_id => 'a', :effective_date => 1291352400, :test_id => xyzzy)
5
+ # Delayed::Job.enqueue QME::MapRedude::MeasureCalculationJob.new(quality_report, :effective_date => 1291352400, :test_id => xyzzy)
6
6
  #
7
- # MeasureCalculationJob will check to see if a measure has been calculated before running the calculation. It does
8
- # this by creating a QME::QualityReport and asking if it has been calculated. If so, it will complete the job without
9
- # running the MapReduce job.
7
+ # MeasureCalculationJob will check to see if a measure has been calculated before running the calculation. It will do this by
8
+ # checking the status of the quality report that this calculation job was created with.
10
9
  #
11
10
  # When a measure needs calculation, the job will create a QME::MapReduce::Executor and interact with it to calculate
12
11
  # the report.
13
12
  class MeasureCalculationJob
14
- attr_accessor :test_id, :measure_id, :sub_id, :effective_date, :filters
13
+ attr_accessor :quality_report
15
14
 
16
15
  def initialize(options)
17
- @measure_id = options['measure_id']
18
- @sub_id = options['sub_id']
16
+ @quality_report = QME::QualityReport.find(options["quality_report_id"])
19
17
  @options = options
18
+ @options.merge! @quality_report.attributes
20
19
  end
21
20
 
22
21
  def perform
23
- qr = QualityReport.new(@measure_id, @sub_id, @options)
24
- if qr.calculated?
25
- completed("#{@measure_id}#{@sub_id} has already been calculated")
26
- else
27
- map = QME::MapReduce::Executor.new(@measure_id, @sub_id, @options.merge('start_time' => Time.now.to_i))
28
- if !qr.patients_cached?
22
+
23
+ if !@quality_report.calculated?
24
+ map = QME::MapReduce::Executor.new(@quality_report.measure_id,@quality_report.sub_id, @options.merge('start_time' => Time.now.to_i))
25
+ if !@quality_report.patients_cached?
29
26
  tick('Starting MapReduce')
30
27
  map.map_records_into_measure_groups
31
28
  tick('MapReduce complete')
@@ -33,17 +30,74 @@ module QME
33
30
 
34
31
  tick('Calculating group totals')
35
32
  result = map.count_records_in_measure_groups
33
+ @quality_report.result=result
34
+ # backwards compatibility with previous q cahce users. Should be reomved going foward
35
+ # and provide a means to update existing results to the newer format
36
+ result.attributes.each_pair do |k,v|
37
+ unless k.to_s == "_id"
38
+ @quality_report[k]=v
39
+ end
40
+ end
41
+ @quality_report.save
36
42
  completed("#{@measure_id}#{@sub_id}: p#{result[QME::QualityReport::POPULATION]}, d#{result[QME::QualityReport::DENOMINATOR]}, n#{result[QME::QualityReport::NUMERATOR]}, excl#{result[QME::QualityReport::EXCLUSIONS]}, excep#{result[QME::QualityReport::EXCEPTIONS]}")
37
43
  end
38
44
  end
39
45
 
40
46
  def completed(message)
41
-
47
+ @quality_report.status["state"] = "completed"
48
+ @quality_report.status["log"] << message
49
+ @quality_report.calculation_time = Time.now
50
+ @quality_report.save
42
51
  end
43
52
 
44
53
  def tick(message)
45
-
54
+ @quality_report.status["state"] = "calculating"
55
+ @quality_report.status["log"] << message
56
+ @quality_report.save
57
+ end
58
+
59
+ def enqueue(job)
60
+ @quality_report.status = {"state" => "queued", "log" => ["Queued at #{Time.now}"], "job_id" => job.id}
46
61
  end
62
+
63
+
64
+ def error(job, exception)
65
+ @quality_report.status["state"] = "error"
66
+ @quality_report.status["log"] << exception.to_s
67
+ @quality_report.save
68
+ end
69
+
70
+ def failure(job)
71
+ @quality_report.status["state"] = "failed"
72
+ @quality_report.status["log"] << "Failed at #{Time.now}"
73
+ @quality_report.save
74
+ end
75
+
76
+ def after(job)
77
+ @quality_report.status.delete("job_id")
78
+ @quality_report.save
79
+ end
80
+
81
+ # Returns the status of a measure calculation job
82
+ # @param job_id the id of the job to check on
83
+ # @return [Symbol] Will return the status: :complete, :queued, :running, :failed
84
+ def self.status(job_id)
85
+ job = Delayed::Job.where(_id: job_id).first
86
+ if job.nil?
87
+ # If we can't find the job, we assume that it is complete
88
+ :complete
89
+ else
90
+ if job.locked_at.nil?
91
+ :queued
92
+ else
93
+ if job.failed?
94
+ :failed
95
+ else
96
+ :running
97
+ end
98
+ end
99
+ end
100
+ end
47
101
  end
48
102
  end
49
103
  end
@@ -0,0 +1,31 @@
1
+ module QME
2
+ class PatientCache
3
+ include Mongoid::Document
4
+ include Mongoid::Timestamps
5
+ store_in collection: 'patient_cache'
6
+ index "value.last" => 1
7
+ index "bundle_id" => 1
8
+ embeds_one :value, class_name: "QME::PatientCacheValue", inverse_of: :patient_cache
9
+ end
10
+
11
+ class PatientCacheValue
12
+
13
+ include Mongoid::Document
14
+
15
+ embedded_in :patient_cache, inverse_of: :value
16
+
17
+ field :filters, type: Hash
18
+ field :manual_exclusion, type: Boolean, default: false
19
+ field :DENOM, type: Integer
20
+ field :NUMER, type: Integer
21
+ field :DENEX, type: Integer
22
+ field :DENEXCEP, type: Integer
23
+ field :MSRPOPL, type: Integer
24
+ field :OBSERV
25
+ field :antinumerator, type: Integer
26
+ field :IPP, type: Integer
27
+ field :measure_id, type: String
28
+ field :sub_id, type: String
29
+ end
30
+ end
31
+
@@ -1,61 +1,17 @@
1
1
  module QME
2
2
  class QualityMeasure
3
- include DatabaseAccess
4
- extend DatabaseAccess
5
- determine_connection_information
6
3
 
7
- # Return a list of the measures in the database
8
- # @return [Hash] an hash of measure definitions
9
- def self.all(bundle_id = nil)
10
- result = {}
11
- measures = query_measures({}, bundle_id)
12
- measures.find_all.each do |measure|
13
- id = measure['id']
14
- sub_id = measure['sub_id']
15
- measure_id = "#{id}#{sub_id}.json"
16
- result[measure_id] ||= measure
17
- end
18
- result
19
- end
20
-
21
- def self.get_measures(measure_ids, bundle_id = nil)
22
- query_measures({'id' => {"$in" => measure_ids}}, bundle_id)
23
- end
24
-
25
- def self.get(measure_id, sub_id, bundle_id = nil)
26
- query_measures({'id' => measure_id, 'sub_id' => sub_id}, bundle_id)
27
- end
28
-
29
- def self.sub_measures(measure_id, bundle_id = nil)
30
- query_measures({'id' => measure_id}, bundle_id)
31
- end
32
-
33
- # Creates a new QualityMeasure
34
- # @param [String] measure_id value of the measure's id field
35
- # @param [String] sub_id value of the measure's sub_id field, may be nil for measures with only a single numerator and denominator
36
- def initialize(measure_id, sub_id = nil, bundle_id = nil)
37
- @measure_id = measure_id
38
- @sub_id = sub_id
39
- @bundle_id = bundle_id
40
- determine_connection_information
41
- end
42
-
43
- # Retrieve a measure definition from the database
44
- # @return [Hash] a JSON hash of the encoded measure
45
- def definition
46
- if @sub_id
47
- QME::QualityMeasure.query_measures({'id' => @measure_id, 'sub_id' => @sub_id}, @bundle_id).first()
48
- else
49
- QME::QualityMeasure.query_measures({'id' => @measure_id}, @bundle_id).first()
50
- end
51
- end
4
+ include Mongoid::Document
5
+ store_in collection: 'measures'
52
6
 
53
- # Build measure collection query. Allows scoping of query to a single bundle
54
- # @param [String] criteria Moped query hash
55
- # @param [String] bundle_id the MongoDB id of the bundle to scope the query against. Leaving this as nil will scope to all bundles
56
- def self.query_measures(criteria, bundle_id=nil)
57
- criteria = bundle_id ? criteria.merge!({'bundle_id' => bundle_id}): criteria
58
- get_db()['measures'].find(criteria)
59
- end
7
+ field :id, type: String
8
+ field :sub_id, type: String
9
+ field :map_fn, type: String
10
+ field :nqf_id, type: String
11
+ field :continuous_variable, type: Boolean, default: false
12
+ field :aggregator, type: String
13
+ field :map_fn, type: String
14
+ field :population_ids, type: Hash
15
+ field :parameters, type: Hash, default: {}
60
16
  end
61
17
  end