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

Sign up to get free protection for your applications and to get access to all the features.
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