openstudio-analysis 0.4.4 → 0.4.5

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,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e28750e00afea6e55e5852a8629ddf0e7cec2760
4
- data.tar.gz: 8fdc2d023f6c151220fda7b06e5f01930cb4b379
3
+ metadata.gz: a460438de48ec2ba07da54a58e5c2b4df31495b0
4
+ data.tar.gz: 710522dfd0cb39a17a6f41dff61a11caf250776e
5
5
  SHA512:
6
- metadata.gz: 00526e6a206c1721906344469701b526dd1cfa0d0693c3be8f79a5240239321f6339a010a149de13bf3edb673a07e4188afdff31e3dbbc3b27a0c35b8e63e624
7
- data.tar.gz: 834f5922be6edb2f5cbf550c1e0d0bb6daa0ed3109538d440ebec09195efc753892238ba80cd9707d7790330760da42385516d0836c385eba0059cc6259087da
6
+ metadata.gz: 97237d9a0fdb49cc9b6e5cb400eaf7dd51b503de37bb457689b38ccbe7e2f977ac9cd8ed969bc8df5ce059bc2ba156ceb3f98b9846ead7002881ba5ab20e0959
7
+ data.tar.gz: f7a7cc8e3191c816cc41ed3b1fdfd3f8739eef45d4828c6f43b0ba62ea097214da807024bf249b77e48e4825e39b349b7467e1b6855551212d7186f2ab401e6d
@@ -1,6 +1,4 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.0.0
4
- - 1.9.3
5
- - jruby-19mode
6
-
3
+ - 2.0.0-p451
4
+ - 2.1
@@ -1,6 +1,13 @@
1
1
  OpenStudio Analysis Gem Change Log
2
2
  ==================================
3
3
 
4
+ Version Unreleased
5
+ ------------------
6
+ * Add diag analysis type to server_api run method
7
+ * Remove support for Rubies < 2.0.
8
+ * Add json extension to formulation name upon save if none exists
9
+ * Add zip extension to formulation zip upon save if none exists
10
+
4
11
  Version 0.4.4
5
12
  ------------------
6
13
  * Increment objective function count only if they are true
data/Gemfile CHANGED
@@ -2,9 +2,12 @@ source 'http://rubygems.org'
2
2
 
3
3
  gemspec
4
4
 
5
+ gem 'openstudio-aws', github: 'NREL/OpenStudio-aws-gem', branch: 'develop'
6
+ gem 'colored', '~> 1.2'
7
+
5
8
  group :test do
6
9
  gem 'coveralls', require: false
7
- gem 'rspec', '~> 3.3.0'
10
+ gem 'rspec', '~> 3.4'
8
11
  gem 'ci_reporter_rspec'
9
12
  gem 'rubocop', '~> 0.31'
10
13
  gem 'rubocop-checkstyle_formatter', '~> 0.2'
data/README.md CHANGED
@@ -19,35 +19,52 @@ There are two ways to create an OpenStudio Analysis description:
19
19
  * Programmatically
20
20
 
21
21
  ```
22
- analysis = OpenStudio::Analysis.create
23
- analysis.seed_model = "local/dir/seed.osm"
24
- analysis.name = "Analysis Name"
22
+ analysis = OpenStudio::Analysis.create('Analysis Name')
23
+ analysis.seed_model = 'local/dir/seed.osm'
24
+ analysis.weather_file = 'local/dir/USA_CO_Golden-NREL.724666_TMY3.epw'
25
25
 
26
26
  # override existing workflow from a file by
27
27
  analysis.workflow = OpenStudio::Analysis::Workflow.load_from_file(...)
28
28
 
29
29
  # add measures to the workflow
30
30
  wf = analysis.workflow
31
- wf.add_measure_from_path("path_to_measure")
32
- wf.add_measure_from_path("path_to_measure_2")
33
-
34
- # or allow the system to search for the measure based on default_measure_paths
35
- OpenStudio::Analysis.measure_paths = ['measures', '../project_specific_measures']
36
- wf.add_measure_by_name('measure_name')
31
+ def add_measure_from_path(instance_name, instance_display_name, local_path_to_measure)
32
+ wf.add_measure_from_path('instance_name', 'Display name', 'path_to_measure')
33
+ wf.add_measure_from_path('instance_name_2', 'Display name two', 'path_to_measure_2')
37
34
 
38
35
  # make a measure's argument a variable
39
- m = wf.add_measure("path_to_measure_3")
36
+ m = wf.add_measure_from_path('instance_name_3', 'Display name three', 'path_to_measure_3')
40
37
  m.make_variable('variable_argument_name', 'discrete')
41
38
 
42
- m = wf.add_measure('path_to_measure_4')
39
+ m = wf.add_measure_from_path('instance_name_4', 'Display name four', 'path_to_measure_4')
43
40
  m.make_variable('variable_argument_name', 'pivot')
44
- m.argument_static_value('variable_argument_name', value)
41
+ m.argument_value('variable_argument_name', value)
45
42
 
43
+ # Save off the analysis files and a static data point
44
+ run_dir = 'local/run'
45
+ analysis.save("#{run_dir}/analysis.json")
46
+ analysis.save_zip("#{run_dir}/analysis.zip")
47
+ analysis.save_static_data_point("#{run_dir}/data_point.zip")
46
48
  ```
47
49
 
50
+ If you would like to run the data point, then you can use the OpenStudio-workflow gem.
48
51
 
49
- ## Testing
52
+ ```
53
+ require 'openstudio-workflow'
54
+
55
+ run_dir = 'local/run'
56
+ OpenStudio::Workflow.extract_archive("#{run_dir}/analysis.zip", run_dir)
57
+
58
+ options = {
59
+ problem_filename: 'analysis.json',
60
+ datapoint_filename: 'data_point.json',
61
+ analysis_root_path: run_dir
62
+ }
63
+ k = OpenStudio::Workflow.load 'Local', run_dir, options
64
+ k.run
65
+ ```
50
66
 
67
+ ## Testing
51
68
 
52
69
  This gem used RSpec for testing. To test simply run `rspec` at the command line.
53
70
 
@@ -29,6 +29,7 @@ require 'openstudio/analysis/algorithm_attributes'
29
29
 
30
30
  # translators
31
31
  require 'openstudio/analysis/translator/excel'
32
+ require 'openstudio/analysis/translator/datapoints'
32
33
 
33
34
  # helpers / core_ext
34
35
  require 'openstudio/helpers/string'
@@ -20,5 +20,39 @@ module OpenStudio
20
20
  excel.process
21
21
  excel.analyses
22
22
  end
23
+
24
+ # Load an set of batch datapoints from a csv. This will create a analysis
25
+ # of type 'batch_datapoints' which requires 'batch_run'
26
+ def self.from_csv(filename)
27
+ csv = OpenStudio::Analysis::Translator::Datapoints.new(filename)
28
+ csv.process
29
+ csv.analysis
30
+ end
31
+
32
+ # Retrieve aws instance options from a project. This will return a hash
33
+ def self.aws_instance_options(filename)
34
+ if File.extname(filename) == '.xlsx'
35
+ excel = OpenStudio::Analysis::Translator::Excel.new(filename)
36
+ excel.process
37
+ options = {
38
+ os_server_version: excel.settings['openstudio_server_version'],
39
+ server_instance_type: excel.settings['server_instance_type'],
40
+ worker_instance_type: excel.settings['worker_instance_type'],
41
+ worker_node_number: excel.settings['worker_nodes'].to_i,
42
+ user_id: excel.settings['user_id'],
43
+ aws_tags: excel.aws_tags,
44
+ analysis_type: excel.analyses.first.analysis_type,
45
+ cluster_name: excel.cluster_name
46
+ }
47
+ elsif File.extname(filename) == '.csv'
48
+ csv = OpenStudio::Analysis::Translator::Datapoints.new(filename)
49
+ csv.process
50
+ options = csv.settings
51
+ else
52
+ fail 'Invalid file extension'
53
+ end
54
+
55
+ return options
56
+ end
23
57
  end
24
58
  end
@@ -153,7 +153,7 @@ module OpenStudio
153
153
 
154
154
  if @seed_model[:file]
155
155
  h[:analysis][:seed] = {
156
- file_type: File.extname(@seed_model[:file]).gsub('.', '').upcase,
156
+ file_type: File.extname(@seed_model[:file]).delete('.').upcase,
157
157
  path: "./seed/#{File.basename(@seed_model[:file])}"
158
158
  }
159
159
  else
@@ -171,7 +171,7 @@ module OpenStudio
171
171
 
172
172
  if wf
173
173
  h[:analysis][:weather_file] = {
174
- file_type: File.extname(wf[:file]).gsub('.', '').upcase,
174
+ file_type: File.extname(wf[:file]).delete('.').upcase,
175
175
  path: "./weather/#{File.basename(wf[:file])}"
176
176
  }
177
177
  else
@@ -249,10 +249,12 @@ module OpenStudio
249
249
 
250
250
  # save the file to JSON. Will overwrite the file if it already exists
251
251
  #
252
- # @param filename [String] Name of file to create. It will create the directory and override the file if it exists.
252
+ # @param filename [String] Name of file to create. It will create the directory and override the file if it exists. If no file extension is given, then it will use .json.
253
253
  # @param version [Integer] Version of the format to return
254
254
  # @return [Boolean]
255
255
  def save(filename, version = 1)
256
+ filename += '.json' if File.extname(filename) == ''
257
+
256
258
  FileUtils.mkdir_p File.dirname(filename) unless Dir.exist? File.dirname(filename)
257
259
  File.open(filename, 'w') { |f| f << JSON.pretty_generate(to_hash(version)) }
258
260
 
@@ -261,9 +263,13 @@ module OpenStudio
261
263
 
262
264
  # save the data point JSON with the variables set to the static values. Will overwrite the file if it already exists
263
265
  #
266
+ # @param filename [String] Name of file to create. It will create the directory and override the file if it exists. If no file extension is given, then it will use .json.
264
267
  # @param version [Integer] Version of the format to return
265
268
  # @return [Boolean]
266
269
  def save_static_data_point(filename, version = 1)
270
+ filename += '.json' if File.extname(filename) == ''
271
+
272
+ FileUtils.mkdir_p File.dirname(filename) unless Dir.exist? File.dirname(filename)
267
273
  File.open(filename, 'w') { |f| f << JSON.pretty_generate(to_static_data_point_hash(version)) }
268
274
 
269
275
  true
@@ -271,9 +277,11 @@ module OpenStudio
271
277
 
272
278
  # save the analysis zip file which contains the measures, seed model, weather file, and init/final scripts
273
279
  #
274
- # @param filename [String] Name of file to create. It will create the directory and override the file if it exists.
280
+ # @param filename [String] Name of file to create. It will create the directory and override the file if it exists. If no file extension is given, then it will use .json.
275
281
  # @return [Boolean]
276
282
  def save_zip(filename)
283
+ filename += '.zip' if File.extname(filename) == ''
284
+
277
285
  FileUtils.mkdir_p File.dirname(filename) unless Dir.exist? File.dirname(filename)
278
286
 
279
287
  save_analysis_zip(filename)
@@ -5,6 +5,9 @@ module OpenStudio
5
5
  class ServerApi
6
6
  attr_reader :hostname
7
7
 
8
+ # Define set of anlaysis methods require batch_run to be queued after them
9
+ BATCH_RUN_METHODS = %w(lhs preflight single_run repeat_run doe diag baseline_perturbation batch_datapoints)
10
+
8
11
  def initialize(options = {})
9
12
  defaults = { hostname: 'http://localhost:8080' }
10
13
  options = defaults.merge(options)
@@ -762,6 +765,80 @@ module OpenStudio
762
765
  analysis_id
763
766
  end
764
767
 
768
+ def run_baseline_perturbation(formulation_filename, analysis_zip_filename)
769
+ project_options = {}
770
+ project_id = new_project(project_options)
771
+
772
+ analysis_options = {
773
+ formulation_file: formulation_filename,
774
+ upload_file: analysis_zip_filename,
775
+ reset_uuids: true
776
+ }
777
+ analysis_id = new_analysis(project_id, analysis_options)
778
+
779
+ run_options = {
780
+ analysis_action: 'start',
781
+ without_delay: false,
782
+ analysis_type: 'baseline_perturbation',
783
+ allow_multiple_jobs: true,
784
+ use_server_as_worker: true,
785
+ simulate_data_point_filename: 'simulate_data_point.rb',
786
+ run_data_point_filename: 'run_openstudio_workflow_monthly.rb'
787
+ }
788
+ start_analysis(analysis_id, run_options)
789
+
790
+ run_options = {
791
+ analysis_action: 'start',
792
+ without_delay: false, # run in background
793
+ analysis_type: 'batch_run',
794
+ allow_multiple_jobs: true,
795
+ use_server_as_worker: true,
796
+ simulate_data_point_filename: 'simulate_data_point.rb',
797
+ run_data_point_filename: 'run_openstudio_workflow_monthly.rb'
798
+ }
799
+ start_analysis(analysis_id, run_options)
800
+
801
+ analysis_id
802
+ end
803
+
804
+ def run_batch_datapoints(formulation_filename, analysis_zip_filename)
805
+ project_options = {}
806
+ project_id = new_project(project_options)
807
+
808
+ puts 'In run_batch_datapoints'
809
+
810
+ analysis_options = {
811
+ formulation_file: formulation_filename,
812
+ upload_file: analysis_zip_filename,
813
+ reset_uuids: true
814
+ }
815
+ analysis_id = new_analysis(project_id, analysis_options)
816
+
817
+ run_options = {
818
+ analysis_action: 'start',
819
+ without_delay: false,
820
+ analysis_type: 'batch_datapoints',
821
+ allow_multiple_jobs: true,
822
+ use_server_as_worker: true,
823
+ simulate_data_point_filename: 'simulate_data_point.rb',
824
+ run_data_point_filename: 'run_openstudio_workflow_monthly.rb'
825
+ }
826
+ start_analysis(analysis_id, run_options)
827
+
828
+ run_options = {
829
+ analysis_action: 'start',
830
+ without_delay: false, # run in background
831
+ analysis_type: 'batch_run',
832
+ allow_multiple_jobs: true,
833
+ use_server_as_worker: true,
834
+ simulate_data_point_filename: 'simulate_data_point.rb',
835
+ run_data_point_filename: 'run_openstudio_workflow_monthly.rb'
836
+ }
837
+ start_analysis(analysis_id, run_options)
838
+
839
+ analysis_id
840
+ end
841
+
765
842
  def run_analysis_detailed(formulation_filename, analysis_zip_filename, analysis_type,
766
843
  allow_multiple_jobs = true, server_as_worker = true,
767
844
  run_data_point_filename = 'run_openstudio_workflow_monthly.rb')
@@ -774,6 +851,7 @@ module OpenStudio
774
851
  upload_file: analysis_zip_filename,
775
852
  reset_uuids: true
776
853
  }
854
+
777
855
  analysis_id = new_analysis(project_id, analysis_options)
778
856
 
779
857
  server_as_worker = true if analysis_type == 'optim' || analysis_type == 'rgenoud'
@@ -790,7 +868,7 @@ module OpenStudio
790
868
 
791
869
  # If the analysis is a staged analysis, then go ahead and run batch run because there is
792
870
  # no explicit way to tell the system to do it
793
- if %w(lhs preflight single_run repeat_run doe).include? analysis_type
871
+ if BATCH_RUN_METHODS.include? analysis_type
794
872
  run_options = {
795
873
  analysis_action: 'start',
796
874
  without_delay: false,
@@ -0,0 +1,409 @@
1
+ module OpenStudio
2
+ module Analysis
3
+ module Translator
4
+ class Datapoints
5
+ attr_reader :version
6
+ attr_reader :settings
7
+ attr_reader :variables
8
+ attr_reader :outputs
9
+ attr_reader :models
10
+ attr_reader :measure_paths
11
+ attr_reader :weather_paths
12
+ attr_reader :worker_inits
13
+ attr_reader :worker_finals
14
+ attr_reader :export_path
15
+ attr_reader :cluster_name
16
+ attr_reader :variables
17
+ attr_reader :algorithm
18
+ attr_reader :problem
19
+ attr_reader :run_setup
20
+ attr_reader :aws_tags
21
+
22
+ # remove these once we have classes to construct the JSON file
23
+ attr_accessor :name
24
+ attr_reader :analysis_name
25
+
26
+ # Methods to override instance variables
27
+
28
+ # Pass in the filename to read
29
+ def initialize(csv_filename)
30
+ @csv_filename = csv_filename
31
+ @root_path = File.expand_path(File.dirname(@csv_filename))
32
+
33
+ @csv = nil
34
+ # Try to read the spreadsheet as a roo object
35
+ if File.exist?(@csv_filename)
36
+ @csv = CSV.read(@csv_filename)
37
+ else
38
+ fail "File #{@csv_filename} does not exist"
39
+ end
40
+
41
+ # Remove nil rows and check row length
42
+ @csv.delete_if { |row| row.uniq.length == 1 && row.uniq[0].nil? }
43
+
44
+ # Initialize some other instance variables
45
+ @version = '0.0.1'
46
+ @analyses = [] # Array o OpenStudio::Analysis. Use method to access
47
+ @name = nil
48
+ @analysis_name = nil
49
+ @cluster_name = nil
50
+ @settings = {}
51
+ @weather_paths = []
52
+ @models = []
53
+ @other_files = []
54
+ @worker_inits = []
55
+ @worker_finals = []
56
+ @export_path = './export'
57
+ @measure_paths = []
58
+ @problem = {}
59
+ @algorithm = {}
60
+ @outputs = {}
61
+ @run_setup = {}
62
+ @aws_tags = []
63
+ end
64
+
65
+ def process
66
+ # Seperate CSV into meta and measure groups
67
+ measure_tag_index = nil
68
+ @csv.each_with_index { |row, index| measure_tag_index = index if row[0] == 'BEGIN-MEASURES' }
69
+ fail "ERROR: No 'BEGIN-MEASURES' tag found in input csv file." unless measure_tag_index
70
+ meta_rows = []
71
+ measure_rows = []
72
+ @csv.each_with_index do |_, index|
73
+ meta_rows << @csv[index] if index < measure_tag_index
74
+ measure_rows << @csv[index] if index > measure_tag_index
75
+ end
76
+
77
+ @setup = parse_csv_meta(meta_rows)
78
+
79
+ @version = Semantic::Version.new @version
80
+ fail "CSV interface version #{@version} is no longer supported. Please upgrade your csv interface to at least 0.0.1" if @version < '0.0.0'
81
+
82
+ @variables = parse_csv_measures(measure_rows)
83
+
84
+ # call validate to make sure everything that is needed exists (i.e. directories)
85
+ validate_analysis
86
+ end
87
+
88
+ # Helper methods to remove models and add new ones programatically. Note that these should
89
+ # be moved into a general analysis class
90
+ def delete_models
91
+ @models = []
92
+ end
93
+
94
+ def add_model(name, display_name, type, path)
95
+ @models << {
96
+ name: name,
97
+ display_name: display_name,
98
+ type: type,
99
+ path: path
100
+ }
101
+ end
102
+
103
+ def validate_analysis
104
+ # Setup the paths and do some error checking
105
+ @measure_paths.each do |mp|
106
+ fail "Measures directory '#{mp}' does not exist" unless Dir.exist?(mp)
107
+ end
108
+
109
+ @models.uniq!
110
+ fail 'No seed models defined in spreadsheet' if @models.empty?
111
+
112
+ @models.each do |model|
113
+ fail "Seed model does not exist: #{model[:path]}" unless File.exist?(model[:path])
114
+ end
115
+
116
+ @weather_paths.uniq!
117
+ fail 'No weather files found based on what is in the spreadsheet' if @weather_paths.empty?
118
+
119
+ @weather_paths.each do |wf|
120
+ fail "Weather file does not exist: #{wf}" unless File.exist?(wf)
121
+ end
122
+
123
+ # This can be a directory as well
124
+ @other_files.each do |f|
125
+ fail "Other files do not exist for: #{f[:path]}" unless File.exist?(f[:path])
126
+ end
127
+
128
+ @worker_inits.each do |f|
129
+ fail "Worker initialization file does not exist for: #{f[:path]}" unless File.exist?(f[:path])
130
+ end
131
+
132
+ @worker_finals.each do |f|
133
+ fail "Worker finalization file does not exist for: #{f[:path]}" unless File.exist?(f[:path])
134
+ end
135
+
136
+ FileUtils.mkdir_p(@export_path)
137
+
138
+ # verify that the measure display names are unique
139
+ # puts @variables.inspect
140
+ measure_display_names = @variables.map { |m| m[:measure_data][:display_name] }.compact
141
+ measure_display_names_mult = measure_display_names.select { |m| measure_display_names.count(m) > 1 }.uniq
142
+ if measure_display_names_mult && !measure_display_names_mult.empty?
143
+ fail "Measure Display Names are not unique for '#{measure_display_names_mult.join('\', \'')}'"
144
+ end
145
+
146
+ variable_names = @variables.map { |v| v[:vars].map { |hash| hash[:display_name] } }.flatten
147
+ dupes = variable_names.select { |e| variable_names.count(e) > 1 }.uniq
148
+ if dupes.count > 0
149
+ fail "duplicate variable names found in list #{dupes.inspect}"
150
+ end
151
+ end
152
+
153
+ # convert the data in excel's parsed data into an OpenStudio Analysis Object
154
+ # @seed_model [Hash] Seed model to set the new analysis to
155
+ # @append_model_name [Boolean] Append the name of the seed model to the display name
156
+ # @return [Object] An OpenStudio::Analysis
157
+ def analysis(seed_model = nil, append_model_name = false)
158
+ fail 'There are no seed models defined in the excel file. Please add one.' if @models.size == 0
159
+ fail 'There are more than one seed models defined in the excel file. This is not supported by the CSV Translator.' if @models.size > 1 && seed_model.nil?
160
+
161
+ seed_model = @models.first if seed_model.nil?
162
+
163
+ # Use the programmatic interface to make the analysis
164
+ # append the model name to the analysis name if requested (normally if there are more than 1 models in the spreadsheet)
165
+ display_name = append_model_name ? @name + ' ' + seed_model[:display_name] : @name
166
+
167
+ a = OpenStudio::Analysis.create(display_name)
168
+
169
+ @variables.each do |measure|
170
+ @measure_paths.each do |measure_path|
171
+ measure_dir_to_add = "#{measure_path}/#{measure[:measure_data][:classname]}"
172
+ if Dir.exist? measure_dir_to_add
173
+ if File.exist? "#{measure_dir_to_add}/measure.rb"
174
+ measure[:measure_data][:local_path_to_measure] = "#{measure_dir_to_add}/measure.rb"
175
+ break
176
+ else
177
+ fail "Measure in directory '#{measure_dir_to_add}' did not contain a measure.rb file"
178
+ end
179
+ end
180
+ end
181
+
182
+ fail "Could not find measure '#{measure['name']}' in directory named '#{measure['measure_file_name_directory']}' in the measure paths '#{@measure_paths.join(', ')}'" unless measure[:measure_data][:local_path_to_measure]
183
+
184
+ a.workflow.add_measure_from_csv(measure)
185
+ end
186
+
187
+ @other_files.each do |library|
188
+ a.libraries.add(library[:path], library_name: library[:lib_zip_name])
189
+ end
190
+
191
+ @worker_inits.each do |w|
192
+ a.worker_inits.add(w[:path], args: w[:args])
193
+ end
194
+
195
+ @worker_finals.each do |w|
196
+ a.worker_finalizes.add(w[:path], args: w[:args])
197
+ end
198
+
199
+ # Add in the outputs
200
+ @outputs.each do |o|
201
+ o = Hash[o.map { |k, v| [k.to_sym, v] }]
202
+ a.add_output(o)
203
+ end
204
+
205
+ a.analysis_type = @problem['analysis_type']
206
+
207
+ # clear out the seed files before adding new ones
208
+ a.seed_model = seed_model[:path]
209
+
210
+ # clear out the weather files before adding new ones
211
+ a.weather_files.clear
212
+ @weather_paths.each do |wp|
213
+ a.weather_files.add_files(wp)
214
+ end
215
+
216
+ a
217
+ end
218
+
219
+ protected
220
+
221
+ def parse_csv_meta(meta_rows)
222
+ # Convert to hash
223
+ config_hash = {}
224
+ meta_rows.each do |row|
225
+ config_hash[row[0].to_sym] = row[1]
226
+ end
227
+
228
+ # Assign required attributes
229
+ fail 'Require setting not found: version' unless config_hash[:version]
230
+ @version = config_hash[:version]
231
+
232
+ if config_hash[:analysis_name]
233
+ @name = config_hash[:analysis_name]
234
+ else
235
+ @name = SecureRandom.uuid
236
+ end
237
+ @analysis_name = @name.snake_case
238
+
239
+ fail 'Require setting not found: measure_path' unless config_hash[:measure_paths]
240
+ config_hash[:measure_paths] = [config_hash[:measure_paths]] unless config_hash[:measure_paths].respond_to?(:each)
241
+ config_hash[:measure_paths].each do |path|
242
+ if (Pathname.new path).absolute?
243
+ @measure_paths << path
244
+ else
245
+ @measure_paths << File.expand_path(File.join(@root_path, path))
246
+ end
247
+ end
248
+
249
+ fail 'Required setting not found: weather_paths' unless config_hash[:weather_paths]
250
+ config_hash[:weather_paths] = eval("#{config_hash[:weather_paths]}")
251
+ config_hash[:weather_paths].each do |path|
252
+ if (Pathname.new path).absolute?
253
+ @weather_paths << path
254
+ else
255
+ @weather_paths << File.expand_path(File.join(@root_path, path))
256
+ end
257
+ end
258
+
259
+ fail 'Required setting not found: models' unless config_hash[:models]
260
+ config_hash[:models] = [config_hash[:models]] unless config_hash[:models].respond_to?(:each)
261
+ config_hash[:models].each do |path|
262
+ model_name = File.basename(path).split('.')[0]
263
+ model_name = SecureRandom.uuid if model_name == ''
264
+ type = File.basename(path).split('.')[1].upcase
265
+ unless (Pathname.new path).absolute?
266
+ path = File.expand_path(File.join(@root_path, path))
267
+ end
268
+ @models << { name: model_name.snake_case, display_name: model_name, type: type, path: path }
269
+ end
270
+
271
+ # Assign optional attributes
272
+ if config_hash[:output]
273
+ path = File.expand_path(File.join(@root_path, config_hash[:output].to_s))
274
+ if File.exist? path
275
+ @outputs = MultiJson.load(File.read(path))
276
+ else
277
+ fail "Could not find output json: #{config_hash[:output]}"
278
+ end
279
+ end
280
+
281
+ if config_hash[:export_path]
282
+ if (Pathname.new config_hash[:export_path]).absolute?
283
+ @export_path = config_hash[:export_path]
284
+ else
285
+ @export_path = File.expand_path(File.join(@root_path, config_hash[:export_path]))
286
+ end
287
+ end
288
+
289
+ if config_hash[:library_path]
290
+ library_name = File.basename(config_hash[:library_path]).split('.')[0]
291
+ unless (Pathname.new config_hash[:library_path]).absolute?
292
+ config_hash[:library_path] = File.expand_path(File.join(@root_path, config_hash[:library_path]))
293
+ end
294
+ @other_files << { lib_zip_name: library_name, path: config_hash[:library_path] }
295
+ end
296
+
297
+ @run_setup[:allow_multiple_jobs] = config_hash[:allow_multiple_jobs].to_s.to_bool if config_hash[:allow_multiple_jobs]
298
+ @run_setup[:use_server_as_worker] = config_hash[:use_server_as_worker].to_s.to_bool if config_hash[:use_server_as_worker]
299
+
300
+ # Assign AWS settings
301
+ @settings[:proxy_port] = config_hash[:proxy_port] if config_hash[:proxy_port]
302
+ @settings[:cluster_name] = config_hash[:cluster_name] if config_hash[:cluster_name]
303
+ @settings[:user_id] = config_hash[:user_id] if config_hash[:user_id]
304
+ @settings[:os_server_version] = config_hash[:os_server_version] if config_hash[:os_server_version]
305
+ @settings[:server_instance_type] = config_hash[:server_instance_type] if config_hash[:server_instance_type]
306
+ @settings[:worker_instance_type] = config_hash[:worker_instance_type] if config_hash[:worker_instance_type]
307
+ @settings[:worker_node_number] = config_hash[:worker_node_number].to_i if config_hash[:worker_node_number]
308
+ @settings[:aws_tags] = config_hash[:aws_tags] if config_hash[:aws_tags]
309
+ @settings[:analysis_type] = 'batch_datapoints'
310
+ end
311
+
312
+ def parse_csv_measures(measure_rows)
313
+ # Build metadata required for parsing
314
+ measures = measure_rows[0].uniq.select { |measure| !measure.nil? }.map(&:to_sym)
315
+ measure_map = {}
316
+ measure_var_list = []
317
+ measures.each do |measure|
318
+ measure_map[measure] = {}
319
+ col_ind = (0..(measure_rows[0].length - 1)).to_a.select { |i| measure_rows[0][i] == measure.to_s }
320
+ col_ind.each do |var_ind|
321
+ tuple = measure.to_s + measure_rows[1][var_ind]
322
+ fail "Multiple measure_variable tuples found for '#{measure}_#{measure_rows[1][var_ind]}'. These tuples must be unique." if measure_var_list.include? tuple
323
+ measure_var_list << tuple
324
+ measure_map[measure][measure_rows[1][var_ind].to_sym] = var_ind
325
+ end
326
+ end
327
+
328
+ # For each measure load measure json and parse out critical variable requirements
329
+ data = []
330
+ measures.each_with_index do |measure, measure_index|
331
+ data[measure_index] = {}
332
+ measure_json = ''
333
+ for i in 0..(@measure_paths.length - 1)
334
+ if File.exist? File.join(@measure_paths[i], measure.to_s, 'measure.json')
335
+ measure_json = MultiJson.load(File.read(File.join(@measure_paths[i], measure.to_s, 'measure.json')))
336
+ break
337
+ end
338
+ end
339
+ fail "Could not find measure json #{measure}.json in measure_paths: '#{@measure_paths.join("\n")}'" if measure_json == ''
340
+ measure_data = {}
341
+ measure_data[:classname] = measure_json['classname']
342
+ measure_data[:name] = measure_json['name']
343
+ measure_data[:display_name] = measure_json['display_name']
344
+ measure_data[:measure_type] = measure_json['measure_type']
345
+ measure_data[:uid] = measure_json['uid']
346
+ measure_data[:version_id] = measure_json['version_id']
347
+ data[measure_index][:measure_data] = measure_data
348
+ data[measure_index][:vars] = []
349
+ vars = measure_map[measure]
350
+ vars.each do |var|
351
+ var = var[0]
352
+ var_hash = {}
353
+ var_json = measure_json['arguments'].select { |hash| hash['local_variable'] == var.to_s }[0]
354
+ fail "measure.json for measure #{measure} does not have an argument with local_variable == #{var}" if var_json.nil?
355
+ var_hash[:variable_type] = 'variable'
356
+ var_hash[:display_name] = measure_rows[2][measure_map[measure][var]]
357
+ var_hash[:display_name_short] = var_hash[:display_name]
358
+ var_hash[:name] = var_json['local_variable']
359
+ var_hash[:type] = var_json['variable_type'].downcase
360
+ var_hash[:units] = var_json['units']
361
+ var_hash[:distribution] = {}
362
+ case var_hash[:type].downcase
363
+ when 'bool', 'boolean' # is 'boolean' necessary? it's not in the enum catch
364
+ var_hash[:distribution][:values] = (3..(measure_rows.length - 1)).map { |value| measure_rows[value.to_i][measure_map[measure][var]].to_s == 'true' }
365
+ var_hash[:distribution][:maximum] = true
366
+ var_hash[:distribution][:minimum] = false
367
+ var_hash[:distribution][:mode] = var_hash[:distribution][:values].group_by { |i| i }.max { |x, y| x[1].length <=> y[1].length }[0]
368
+ when 'choice', 'string'
369
+ var_hash[:distribution][:values] = (3..(measure_rows.length) - 1).map { |value| measure_rows[value.to_i][measure_map[measure][var]].to_s }
370
+ var_hash[:distribution][:minimum] = var_hash[:distribution][:values].min
371
+ var_hash[:distribution][:maximum] = var_hash[:distribution][:values].max
372
+ var_hash[:distribution][:mode] = var_hash[:distribution][:values].group_by { |i| i }.max { |x, y| x[1].length <=> y[1].length }[0]
373
+ else
374
+ var_hash[:distribution][:values] = (3..(measure_rows.length - 1)).map { |value| eval(measure_rows[value.to_i][measure_map[measure][var]]) }
375
+ var_hash[:distribution][:minimum] = var_hash[:distribution][:values].map(&:to_i).min
376
+ var_hash[:distribution][:maximum] = var_hash[:distribution][:values].map(&:to_i).max
377
+ var_hash[:distribution][:mode] = var_hash[:distribution][:values].group_by { |i| i }.max { |x, y| x[1].length <=> y[1].length }[0]
378
+ end
379
+ var_hash[:distribution][:weights] = eval('[' + "#{1.0 / (measure_rows.length - 3)}," * (measure_rows.length - 3) + ']')
380
+ var_hash[:distribution][:type] = 'discrete'
381
+ var_hash[:distribution][:units] = var_hash[:units]
382
+ if var_hash[:type] == 'choice'
383
+ var_hash[:distribution][:enumerations] = var_json['choices']
384
+ elsif var_hash[:type] == 'bool'
385
+ var_hash[:distribution][:enumerations] = []
386
+ var_hash[:distribution][:enumerations] << 'true' # TODO: should this be a real bool?
387
+ var_hash[:distribution][:enumerations] << 'false'
388
+ end
389
+ data[measure_index][:vars] << var_hash
390
+ end
391
+ data[measure_index][:args] = []
392
+ measure_json['arguments'].each do |arg_json|
393
+ arg = {}
394
+ arg[:value_type] = arg_json['variable_type'].downcase
395
+ arg[:name] = arg_json['name']
396
+ arg[:display_name] = arg_json['display_name']
397
+ arg[:display_name_short] = arg_json['display_name']
398
+ arg[:default_value] = arg_json['default_value']
399
+ arg[:value] = arg_json['default_value']
400
+ data[measure_index][:args] << arg
401
+ end
402
+ end
403
+
404
+ data
405
+ end
406
+ end
407
+ end
408
+ end
409
+ end