urbanopt-reopt 0.3.0 → 0.5.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,5 @@
1
1
  # *********************************************************************************
2
- # URBANopt, Copyright (c) 2019-2020, Alliance for Sustainable Energy, LLC, and other
2
+ # URBANopt (tm), Copyright (c) 2019-2020, Alliance for Sustainable Energy, LLC, and other
3
3
  # contributors. All rights reserved.
4
4
  #
5
5
  # Redistribution and use in source and binary forms, with or without modification,
@@ -56,6 +56,7 @@ module URBANopt # :nodoc:
56
56
  @use_localhost = use_localhost
57
57
  if @use_localhost
58
58
  @uri_submit = URI.parse('http//:127.0.0.1:8000/v1/job/')
59
+ @uri_submit_outagesimjob = URI.parse('http//:127.0.0.1:8000/v1/outagesimjob/')
59
60
  else
60
61
  if [nil, '', '<insert your key here>'].include? nrel_developer_key
61
62
  if [nil, '', '<insert your key here>'].include? DEVELOPER_NREL_KEY
@@ -66,6 +67,7 @@ module URBANopt # :nodoc:
66
67
  end
67
68
  @nrel_developer_key = nrel_developer_key
68
69
  @uri_submit = URI.parse("https://developer.nrel.gov/api/reopt/v1/job/?api_key=#{@nrel_developer_key}")
70
+ @uri_submit_outagesimjob = URI.parse("https://developer.nrel.gov/api/reopt/v1/outagesimjob/?api_key=#{@nrel_developer_key}")
69
71
  # initialize @@logger
70
72
  @@logger ||= URBANopt::REopt.reopt_logger
71
73
  end
@@ -88,6 +90,23 @@ module URBANopt # :nodoc:
88
90
  return URI.parse("https://developer.nrel.gov/api/reopt/v1/job/#{run_uuid}/results?api_key=#{@nrel_developer_key}")
89
91
  end
90
92
 
93
+ ##
94
+ # URL of the resilience statistics end point for a specific optimization task
95
+ ##
96
+ #
97
+ # [*parameters:*]
98
+ #
99
+ # * +run_uuid+ - _String_ - Resilience statistics for a unique run_uuid obtained from the \REopt Lite job submittal URL for a specific optimization task.
100
+ #
101
+ # [*return:*] _URI_ - Returns URI object for use in calling the \REopt Lite resilience statistics endpoint for a specifc optimization task.
102
+ ##
103
+ def uri_resilience(run_uuid) # :nodoc:
104
+ if @use_localhost
105
+ return URI.parse("http://127.0.0.1:8000/v1/job/#{run_uuid}/resilience_stats")
106
+ end
107
+ return URI.parse("https://developer.nrel.gov/api/reopt/v1/job/#{run_uuid}/resilience_stats?api_key=#{@nrel_developer_key}")
108
+ end
109
+
91
110
  def make_request(http, r, max_tries = 3)
92
111
  result = nil
93
112
  tries = 0
@@ -117,10 +136,10 @@ module URBANopt # :nodoc:
117
136
  http = Net::HTTP.new(@uri_submit.host, @uri_submit.port)
118
137
  if !@use_localhost
119
138
  http.use_ssl = true
120
- end
139
+ end
121
140
 
122
141
  request = Net::HTTP::Post.new(@uri_submit, header)
123
- request.body = data.to_json
142
+ request.body = ::JSON.generate(data, allow_nan: true)
124
143
 
125
144
  # Send the request
126
145
  response = make_request(http, request)
@@ -132,6 +151,82 @@ module URBANopt # :nodoc:
132
151
  return true
133
152
  end
134
153
 
154
+ ##
155
+ # Completes a \REopt Lite optimization. From a formatted hash, an optimization task is submitted to the API.
156
+ # Results are polled at 5 second interval until they are ready or an error is returned from the API. Results
157
+ # are written to disk.
158
+ ##
159
+ #
160
+ # [*parameters:*]
161
+ #
162
+ # * +reopt_input+ - _Hash_ - \REopt Lite formatted post containing at least required parameters.
163
+ # * +filename+ - _String_ - Path to file that will be created containing the full \REopt Lite response.
164
+ #
165
+ # [*return:*] _Bool_ - Returns true if the post succeeeds. Otherwise returns false.
166
+ ##
167
+ def resilience_request(run_uuid, filename)
168
+
169
+ if File.directory? filename
170
+ if run_uuid.nil?
171
+ run_uuid = 'error'
172
+ end
173
+ if run_uuid.downcase.include? 'error'
174
+ run_uuid = "error#{SecureRandom.uuid}"
175
+ end
176
+ filename = File.join(filename, "#{run_uuid}_resilience.json")
177
+ @@logger.info("REopt results saved to #{filename}")
178
+ end
179
+
180
+ #Submit Job
181
+ @@logger.info("Submitting Resilience Statistics job for #{run_uuid}")
182
+ header = { 'Content-Type' => 'application/json' }
183
+ http = Net::HTTP.new(@uri_submit_outagesimjob.host, @uri_submit_outagesimjob.port)
184
+ if !@use_localhost
185
+ http.use_ssl = true
186
+ end
187
+ request = Net::HTTP::Post.new(@uri_submit_outagesimjob, header)
188
+ request.body = ::JSON.generate({"run_uuid" => run_uuid, "bau" => false }, allow_nan: true)
189
+ submit_response = make_request(http, request)
190
+ @@logger.info(submit_response.body)
191
+
192
+ #Fetch Results
193
+ uri = uri_resilience(run_uuid)
194
+ http = Net::HTTP.new(uri.host, uri.port)
195
+ if !@use_localhost
196
+ http.use_ssl = true
197
+ end
198
+
199
+ elapsed_time = 0
200
+ max_elapsed_time = 60 * 5
201
+
202
+ request = Net::HTTP::Get.new(uri.request_uri)
203
+ response = make_request(http, request)
204
+
205
+ while (elapsed_time < max_elapsed_time) & (response.code == "404")
206
+ response = make_request(http, request)
207
+ elapsed_time += 5
208
+ sleep 5
209
+ end
210
+
211
+ data = JSON.parse(response.body)
212
+ text = ::JSON.generate(data, allow_nan: true)
213
+ begin
214
+ File.open(filename, 'w+') do |f|
215
+ f.puts(text)
216
+ end
217
+ rescue
218
+ @@logger.info("Cannot write - #{filename}")
219
+ end
220
+
221
+ if response.code == "200"
222
+ return data
223
+ end
224
+
225
+ @@logger.info("Error from REopt API - #{data['Error']}")
226
+ return {}
227
+
228
+ end
229
+
135
230
  ##
136
231
  # Completes a \REopt Lite optimization. From a formatted hash, an optimization task is submitted to the API.
137
232
  # Results are polled at 5 second interval until they are ready or an error is returned from the API. Results
@@ -157,13 +252,13 @@ module URBANopt # :nodoc:
157
252
  http.use_ssl = true
158
253
  end
159
254
  request = Net::HTTP::Post.new(@uri_submit, header)
160
- request.body = reopt_input.to_json
161
-
255
+ request.body = ::JSON.generate(reopt_input, allow_nan: true)
256
+
162
257
  # Send the request
163
258
  response = make_request(http, request)
164
259
 
165
260
  # Get UUID
166
- run_uuid = JSON.parse(response.body)['run_uuid']
261
+ run_uuid = JSON.parse(response.body, allow_nan:true)['run_uuid']
167
262
 
168
263
  if File.directory? filename
169
264
  if run_uuid.nil?
@@ -176,10 +271,12 @@ module URBANopt # :nodoc:
176
271
  @@logger.info("REopt results saved to #{filename}")
177
272
  end
178
273
 
274
+ text = ::JSON.generate(response.body, allow_nan: true)
179
275
  if response.code != '201'
180
- File.open(filename, 'w') do |f|
181
- f.write(response.body)
276
+ File.open(filename, 'w+') do |f|
277
+ f.puts(text)
182
278
  end
279
+ @@logger.info("Cannot write - #{filename}")
183
280
  raise "Error in REopt optimization post - see #{filename}"
184
281
  end
185
282
 
@@ -195,7 +292,9 @@ module URBANopt # :nodoc:
195
292
 
196
293
  while status == 'Optimizing...'
197
294
  response = make_request(http, request)
198
- data = JSON.parse(response.body)
295
+
296
+ data = JSON.parse(response.body, allow_nan:true)
297
+
199
298
  if data['outputs']['Scenario']['Site']['PV'].kind_of?(Array)
200
299
  pv_sizes = 0
201
300
  data['outputs']['Scenario']['Site']['PV'].each do |x|
@@ -216,7 +315,7 @@ module URBANopt # :nodoc:
216
315
  while (_tries < _max_retry) && check_complete
217
316
  sleep 1
218
317
  response = make_request(http, request)
219
- data = JSON.parse(response.body)
318
+ data = JSON.parse(response.body, allow_nan:true)
220
319
  if data['outputs']['Scenario']['Site']['PV'].kind_of?(Array)
221
320
  pv_sizes = 0
222
321
  data['outputs']['Scenario']['Site']['PV'].each do |x|
@@ -230,8 +329,14 @@ module URBANopt # :nodoc:
230
329
  _tries += 1
231
330
  end
232
331
 
233
- File.open(filename, 'w') do |f|
234
- f.write(data.to_json)
332
+ data = JSON.parse(response.body)
333
+ text = ::JSON.generate(data, allow_nan: true)
334
+ begin
335
+ File.open(filename, 'w+') do |f|
336
+ f.puts(text)
337
+ end
338
+ rescue
339
+ @@logger.info("Cannot write - #{filename}")
235
340
  end
236
341
 
237
342
  if status == 'optimal'
@@ -1,5 +1,5 @@
1
1
  # *********************************************************************************
2
- # URBANopt, Copyright (c) 2019-2020, Alliance for Sustainable Energy, LLC, and other
2
+ # URBANopt (tm), Copyright (c) 2019-2020, Alliance for Sustainable Energy, LLC, and other
3
3
  # contributors. All rights reserved.
4
4
  #
5
5
  # Redistribution and use in source and binary forms, with or without modification,
@@ -1,5 +1,5 @@
1
1
  # *********************************************************************************
2
- # URBANopt, Copyright (c) 2019-2020, Alliance for Sustainable Energy, LLC, and other
2
+ # URBANopt (tm), Copyright (c) 2019-2020, Alliance for Sustainable Energy, LLC, and other
3
3
  # contributors. All rights reserved.
4
4
  #
5
5
  # Redistribution and use in source and binary forms, with or without modification,
@@ -29,9 +29,8 @@
29
29
  # *********************************************************************************
30
30
 
31
31
  require 'bundler/setup'
32
- require 'urbanopt/scenario/default_reports'
32
+ require 'urbanopt/reporting/default_reports'
33
33
  require 'urbanopt/reopt/reopt_logger'
34
- require 'urbanopt/reopt'
35
34
  require 'csv'
36
35
 
37
36
  module URBANopt # :nodoc:
@@ -43,7 +42,7 @@ module URBANopt # :nodoc:
43
42
  #
44
43
  # [*parameters:*]
45
44
  #
46
- # * +scenario_report+ - _ScenarioReport_ - Optional. A scenario report that has been returned from the URBANopt::Scenario::ScenarioDefaultPostProcessor - used in creating default output file names in \REopt Lite optimizations.
45
+ # * +scenario_report+ - _ScenarioReport_ - Optional. A scenario report that has been returned from the URBANopt::Reporting::ScenarioDefaultPostProcessor - used in creating default output file names in \REopt Lite optimizations.
47
46
  # * +scenario_reopt_assumptions_file+ - _String_ - Optional. JSON file formatted for a \REopt Lite analysis containing custom input parameters for optimizations at the Scenario Report level
48
47
  # * +reopt_feature_assumptions+ - _Array_ - Optional. A list of JSON file formatted for a \REopt Lite analysis containing custom input parameters for optimizations at the Feature Report level. The order and number of files must match the Feature Reports in the scenario_report input.
49
48
  # * +use_localhost+ - _Bool_ - If this is true, requests will be sent to a version of the \REopt Lite API running on localhost. Default is false, such that the production version of \REopt Lite is accessed.
@@ -70,8 +69,9 @@ module URBANopt # :nodoc:
70
69
  if !scenario_report.nil?
71
70
  @scenario_report = scenario_report
72
71
 
73
- if !Dir.exist?(File.join(@scenario_report.directory_name, "reopt"))
72
+ if !Dir.exist?(File.join(@scenario_report.directory_name, "reopt"))
74
73
  Dir.mkdir(File.join(@scenario_report.directory_name, "reopt"))
74
+ @@logger.info("Created directory: " + File.join(@scenario_report.directory_name, "reopt"))
75
75
  end
76
76
 
77
77
  @scenario_reopt_default_output_file = File.join(@scenario_report.directory_name, "reopt/scenario_report_#{@scenario_report.id}_reopt_run.json")
@@ -80,6 +80,7 @@ module URBANopt # :nodoc:
80
80
  @scenario_report.feature_reports.each do |fr|
81
81
  if !Dir.exist?(File.join(fr.directory_name, "reopt"))
82
82
  Dir.mkdir(File.join(fr.directory_name, "reopt"))
83
+ @@logger.info("Created directory: " + File.join(fr.directory_name, "reopt"))
83
84
  end
84
85
  @feature_reports_reopt_default_output_files << File.join(fr.directory_name, "reopt/feature_report_#{fr.id}_reopt_run.json")
85
86
  end
@@ -111,25 +112,36 @@ module URBANopt # :nodoc:
111
112
  #
112
113
  # [*parameters:*]
113
114
  #
114
- # * +feature_report+ - _URBANopt::Scenario::DefaultReports::FeatureReport_ - FeatureReport which will be used in creating and then updated by a \REopt Lite opimization response.
115
+ # * +feature_report+ - _URBANopt::Reporting::DefaultReports::FeatureReport_ - FeatureReport which will be used in creating and then updated by a \REopt Lite opimization response.
115
116
  # * +reopt_assumptions_hash+ - _Hash_ - Optional. A \REopt Lite formatted hash containing default parameters (i.e. utility rate, escalation rate) which will be updated by the FeatureReport (i.e. location, roof availability)
116
117
  # * +reopt_output_file+ - _String_ - Optional. Path to a file at which REpopt Lite responses will be saved.
117
118
  # * +timeseries_csv_path+ - _String_ - Optional. Path to a file at which the new timeseries CSV for the FeatureReport will be saved.
118
119
  #
119
- # [*return:*] _URBANopt::Scenario::DefaultReports::FeatureReport_ - Returns an updated FeatureReport
120
+ # [*return:*] _URBANopt::Reporting::DefaultReports::FeatureReport_ - Returns an updated FeatureReport
120
121
  ##
121
- def run_feature_report(feature_report:, reopt_assumptions_hash:nil, reopt_output_file:nil, timeseries_csv_path:nil, save_name:nil)
122
+ def run_feature_report(feature_report:, reopt_assumptions_hash:nil, reopt_output_file:nil, timeseries_csv_path:nil, save_name:nil, run_resilience:true)
122
123
  api = URBANopt::REopt::REoptLiteAPI.new(@nrel_developer_key, @localhost)
123
124
  adapter = URBANopt::REopt::FeatureReportAdapter.new
124
125
 
125
126
  reopt_input = adapter.reopt_json_from_feature_report(feature_report, reopt_assumptions_hash)
126
127
  if reopt_output_file.nil?
127
- reopt_output_file = feature_report.directory_name
128
+ reopt_output_file = File.join(feature_report.directory_name, 'reopt')
128
129
  end
129
130
  reopt_output = api.reopt_request(reopt_input, reopt_output_file)
130
- result = adapter.update_feature_report(feature_report, reopt_output, timeseries_csv_path)
131
+ if run_resilience
132
+ run_uuid = reopt_output['outputs']['Scenario']['run_uuid']
133
+ if File.directory? reopt_output_file
134
+ resilience_stats = api.resilience_request(run_uuid, reopt_output_file)
135
+ else
136
+ resilience_stats = api.resilience_request(run_uuid, reopt_output_file.sub!('.json','_resilience.json'))
137
+ end
138
+ else
139
+ resilience_stats = nil
140
+ end
141
+ result = adapter.update_feature_report(feature_report, reopt_output, timeseries_csv_path, resilience_stats)
131
142
  if !save_name.nil?
132
- result.save_feature_report save_name
143
+ #result.save_feature_report save_name
144
+ result.save_json_report(save_name)
133
145
  end
134
146
  return result
135
147
  end
@@ -140,13 +152,13 @@ module URBANopt # :nodoc:
140
152
  #
141
153
  # [*parameters:*]
142
154
  #
143
- # * +feature_report+ - _URBANopt::Scenario::DefaultReports::ScenarioReport_ - ScenarioReport which will be used in creating and then updated by a \REopt Lite opimization response.
155
+ # * +feature_report+ - _URBANopt::Reporting::DefaultReports::ScenarioReport_ - ScenarioReport which will be used in creating and then updated by a \REopt Lite opimization response.
144
156
  # * +reopt_assumptions_hash+ - _Hash_ - Optional. A \REopt Lite formatted hash containing default parameters (i.e. utility rate, escalation rate) which will be updated by the ScenarioReport (i.e. location, roof availability)
145
157
  # * +reopt_output_file+ - _String_ - Optional. Path to a file at which REpopt Lite responses will be saved.
146
158
  # * +timeseries_csv_path+ - _String_ - Optional. Path to a file at which the new timeseries CSV for the ScenarioReport will be saved.
147
159
  #
148
160
  # [*return:*] _URBANopt::Scenario::DefaultReports::ScenarioReport_ Returns an updated ScenarioReport
149
- def run_scenario_report(scenario_report:, reopt_assumptions_hash:nil, reopt_output_file:nil, timeseries_csv_path:nil, save_name:nil)
161
+ def run_scenario_report(scenario_report:, reopt_assumptions_hash:nil, reopt_output_file:nil, timeseries_csv_path:nil, save_name:nil, run_resilience:true)
150
162
  if !reopt_assumptions_hash.nil?
151
163
  @scenario_reopt_default_assumptions_hash = reopt_assumptions_hash
152
164
  end
@@ -163,8 +175,18 @@ module URBANopt # :nodoc:
163
175
  reopt_input = adapter.reopt_json_from_scenario_report(scenario_report, @scenario_reopt_default_assumptions_hash)
164
176
 
165
177
  reopt_output = api.reopt_request(reopt_input, @scenario_reopt_default_output_file)
178
+ if run_resilience
179
+ run_uuid = reopt_output['outputs']['Scenario']['run_uuid']
180
+ if File.directory? @scenario_reopt_default_output_file
181
+ resilience_stats = api.resilience_request(run_uuid, @scenario_reopt_default_output_file)
182
+ else
183
+ resilience_stats = api.resilience_request(run_uuid, @scenario_reopt_default_output_file.sub!('.json','_resilience.json'))
184
+ end
185
+ else
186
+ resilience_stats = nil
187
+ end
166
188
 
167
- result = adapter.update_scenario_report(scenario_report, reopt_output, @scenario_timeseries_default_output_file)
189
+ result = adapter.update_scenario_report(scenario_report, reopt_output, @scenario_timeseries_default_output_file, resilience_stats)
168
190
  if !save_name.nil?
169
191
  result.save save_name
170
192
  end
@@ -176,13 +198,13 @@ module URBANopt # :nodoc:
176
198
  #
177
199
  # [*parameters:*]
178
200
  #
179
- # * +feature_reports+ - _Array_ - An array of _URBANopt::Scenario::DefaultReports::FeatureReport_ objetcs which will each be used to create (and are subsquently updated by) a \REopt Lite opimization response.
201
+ # * +feature_reports+ - _Array_ - An array of _URBANopt::Reporting::DefaultReports::FeatureReport_ objetcs which will each be used to create (and are subsquently updated by) a \REopt Lite opimization response.
180
202
  # * +reopt_assumptions_hashes+ - _Array_ - Optional. An array of \REopt Lite formatted hashes containing default parameters (i.e. utility rate, escalation rate) which will be updated by the ScenarioReport (i.e. location, roof availability). The number and order of the hashes should match the feature_reports array.
181
203
  # * +reopt_output_files+ - _Array_ - Optional. A array of paths to files at which REpopt Lite responses will be saved. The number and order of the paths should match the feature_reports array.
182
204
  # * +timeseries_csv_path+ - _Array_ - Optional. A array of paths to files at which the new timeseries CSV for the FeatureReports will be saved. The number and order of the paths should match the feature_reports array.
183
205
  #
184
206
  # [*return:*] _Array_ Returns an array of updated _URBANopt::Scenario::DefaultReports::FeatureReport_ objects
185
- def run_feature_reports(feature_reports:, reopt_assumptions_hashes:[], reopt_output_files:[], timeseries_csv_paths:[], save_names:nil)
207
+ def run_feature_reports(feature_reports:, reopt_assumptions_hashes:[], reopt_output_files:[], timeseries_csv_paths:[], save_names:nil, run_resilience:true)
186
208
 
187
209
  if !reopt_assumptions_hashes.empty?
188
210
  @feature_reports_reopt_default_assumption_hashes = reopt_assumptions_hashes
@@ -215,11 +237,22 @@ module URBANopt # :nodoc:
215
237
  begin
216
238
  reopt_input = feature_adapter.reopt_json_from_feature_report(feature_report, @feature_reports_reopt_default_assumption_hashes[idx])
217
239
  reopt_output = api.reopt_request(reopt_input, @feature_reports_reopt_default_output_files[idx])
218
- new_feature_report = feature_adapter.update_feature_report(feature_report, reopt_output, @feature_reports_timeseries_default_output_files[idx])
240
+ if run_resilience
241
+ run_uuid = reopt_output['outputs']['Scenario']['run_uuid']
242
+ if File.directory? @feature_reports_reopt_default_output_files[idx]
243
+ resilience_stats = api.resilience_request(run_uuid, @feature_reports_reopt_default_output_files[idx])
244
+ else
245
+ resilience_stats = api.resilience_request(run_uuid, @feature_reports_reopt_default_output_files[idx].sub!('.json','_resilience.json'))
246
+ end
247
+ else
248
+ resilience_stats = nil
249
+ end
250
+ new_feature_report = feature_adapter.update_feature_report(feature_report, reopt_output, @feature_reports_timeseries_default_output_files[idx], resilience_stats)
219
251
  new_feature_reports.push(new_feature_report)
220
252
  if !save_names.nil?
221
253
  if save_names.length == feature_reports.length
222
- new_feature_report.save_feature_report save_names[idx]
254
+ #new_feature_report.save_feature_report save_names[idx]
255
+ new_feature_report.save_json_report save_names[idx]
223
256
  else
224
257
  warn "Could not save feature reports - the number of save names provided did not match the number of feature reports"
225
258
  end
@@ -228,7 +261,7 @@ module URBANopt # :nodoc:
228
261
  @@logger.info("Could not optimize Feature Report #{feature_report.name} #{feature_report.id}")
229
262
  end
230
263
  end
231
-
264
+
232
265
  return new_feature_reports
233
266
  end
234
267
 
@@ -237,22 +270,22 @@ module URBANopt # :nodoc:
237
270
  #
238
271
  # [*parameters:*]
239
272
  #
240
- # * +scenario_report+ - _Array_ - A _URBANopt::Scenario::DefaultReports::ScenarioReport_ which will each be used to create (and is subsquently updated by) \REopt Lite opimization responses for each of its FeatureReports.
273
+ # * +scenario_report+ - _Array_ - A _URBANopt::Reporting::DefaultReports::ScenarioReport_ which will each be used to create (and is subsquently updated by) \REopt Lite opimization responses for each of its FeatureReports.
241
274
  # * +reopt_assumptions_hashes+ - _Array_ - Optional. An array of \REopt Lite formatted hashes containing default parameters (i.e. utility rate, escalation rate) which will be updated by the ScenarioReport (i.e. location, roof availability). The number and order of the hashes should match the array in ScenarioReport.feature_reports.
242
275
  # * +reopt_output_files+ - _Array_ - Optional. An array of paths to files at which REpopt Lite responses will be saved. The number and order of the paths should match the array in ScenarioReport.feature_reports.
243
276
  # * +feature_report_timeseries_csv_paths+ - _Array_ - Optional. An array of paths to files at which the new timeseries CSV for the FeatureReports will be saved. The number and order of the paths should match the array in ScenarioReport.feature_reports.
244
277
  #
245
278
  # [*return:*] _URBANopt::Scenario::DefaultReports::ScenarioReport_ - Returns an updated ScenarioReport
246
- def run_scenario_report_features(scenario_report:, reopt_assumptions_hashes:[], reopt_output_files:[], feature_report_timeseries_csv_paths:[], save_names_feature_reports:nil, save_name_scenario_report:nil)
247
- new_feature_reports = run_feature_reports(feature_reports:scenario_report.feature_reports, reopt_assumptions_hashes:reopt_assumptions_hashes, reopt_output_files:reopt_output_files, timeseries_csv_paths:feature_report_timeseries_csv_paths,save_names:save_names_feature_reports)
279
+ def run_scenario_report_features(scenario_report:, reopt_assumptions_hashes:[], reopt_output_files:[], feature_report_timeseries_csv_paths:[], save_names_feature_reports:nil, save_name_scenario_report:nil, run_resilience:true)
280
+ new_feature_reports = run_feature_reports(feature_reports:scenario_report.feature_reports, reopt_assumptions_hashes:reopt_assumptions_hashes, reopt_output_files:reopt_output_files, timeseries_csv_paths:feature_report_timeseries_csv_paths,save_names:save_names_feature_reports, run_resilience:run_resilience)
248
281
 
249
- new_scenario_report = URBANopt::Scenario::DefaultReports::ScenarioReport.new
282
+ new_scenario_report = URBANopt::Reporting::DefaultReports::ScenarioReport.new
250
283
  new_scenario_report.id = scenario_report.id
251
284
  new_scenario_report.name = scenario_report.name
252
285
  new_scenario_report.directory_name = scenario_report.directory_name
253
286
 
254
287
  timeseries_hash = { column_names: scenario_report.timeseries_csv.column_names }
255
- new_scenario_report.timeseries_csv = URBANopt::Scenario::DefaultReports::TimeseriesCSV.new(timeseries_hash)
288
+ new_scenario_report.timeseries_csv = URBANopt::Reporting::DefaultReports::TimeseriesCSV.new(timeseries_hash)
256
289
 
257
290
  new_feature_reports.each do |feature_report|
258
291
  new_scenario_report.add_feature_report(feature_report)
@@ -1,5 +1,5 @@
1
1
  # *********************************************************************************
2
- # URBANopt, Copyright (c) 2019-2020, Alliance for Sustainable Energy, LLC, and other
2
+ # URBANopt (tm), Copyright (c) 2019-2020, Alliance for Sustainable Energy, LLC, and other
3
3
  # contributors. All rights reserved.
4
4
  #
5
5
  # Redistribution and use in source and binary forms, with or without modification,
@@ -1,5 +1,5 @@
1
1
  # *********************************************************************************
2
- # URBANopt, Copyright (c) 2019-2020, Alliance for Sustainable Energy, LLC, and other
2
+ # URBANopt (tm), Copyright (c) 2019-2020, Alliance for Sustainable Energy, LLC, and other
3
3
  # contributors. All rights reserved.
4
4
  #
5
5
  # Redistribution and use in source and binary forms, with or without modification,
@@ -28,11 +28,12 @@
28
28
  # OF THE POSSIBILITY OF SUCH DAMAGE.
29
29
  # *********************************************************************************
30
30
 
31
- require 'urbanopt/scenario/default_reports'
31
+ require 'urbanopt/reporting/default_reports'
32
32
  require 'urbanopt/reopt/reopt_logger'
33
33
  require 'matrix'
34
34
  require 'csv'
35
35
  require 'time'
36
+ require_relative 'utilities'
36
37
 
37
38
  module URBANopt # :nodoc:
38
39
  module REopt # :nodoc:
@@ -51,7 +52,7 @@ module URBANopt # :nodoc:
51
52
  #
52
53
  # [*parameters:*]
53
54
  #
54
- # * +scenario_report+ - _URBANopt::Scenario::DefaultReports::ScenarioReport_ - ScenarioReport to use in converting the +reopt_assumptions_hash+, if provided, to a \REopt Lite post. Otherwise, if the +reopt_assumptions_hash+ is nil a default post will be updated from this ScenarioReport and submitted to the \REopt Lite API.
55
+ # * +scenario_report+ - _URBANopt::Reporting::DefaultReports::ScenarioReport_ - ScenarioReport to use in converting the +reopt_assumptions_hash+, if provided, to a \REopt Lite post. Otherwise, if the +reopt_assumptions_hash+ is nil a default post will be updated from this ScenarioReport and submitted to the \REopt Lite API.
55
56
  # * +reopt_assumptions_hash+ - _Hash_ - Optional. A hash formatted for submittal to the \REopt Lite API containing default values. Values will be overwritten from the ScenarioReport where available (i.e. latitude, roof_squarefeet). Missing optional parameters will be filled in with default values by the API.
56
57
  #
57
58
  # [*return:*] _Hash_ - Returns hash formatted for submittal to the \REopt Lite API
@@ -70,31 +71,32 @@ module URBANopt # :nodoc:
70
71
  end
71
72
 
72
73
  # Update required info
73
- if scenario_report.location.latitude.nil? || scenario_report.location.longitude.nil? || (scenario_report.location.latitude == 0) || (scenario_report.location.longitude == 0)
74
+ if scenario_report.location.latitude_deg.nil? || scenario_report.location.longitude_deg.nil? || (scenario_report.location.latitude_deg == 0) || (scenario_report.location.longitude_deg == 0)
74
75
  if !scenario_report.feature_reports.nil? && (scenario_report.feature_reports != [])
75
76
  lats = []
76
77
  longs = []
77
78
  scenario_report.feature_reports.each do |x|
78
- if ![nil, 0].include?(x[:location][:latitude]) && ![nil, 0].include?(x[:location][:longitude])
79
- lats.push(x[:location][:latitude])
80
- longs.push(x[:location][:longitude])
79
+ puts " ERROR: #{x.location.latitude_deg}"
80
+ if ![nil].include?(x.location.latitude_deg) && ![nil].include?(x.location.longitude_deg)
81
+ lats.push(x.location.latitude_deg)
82
+ longs.push(x.location.longitude_deg)
81
83
  end
82
84
  end
83
85
 
84
86
  if !lats.empty? && !longs.empty?
85
- scenario_report.location.latitude = lats.reduce(:+) / lats.size.to_f
86
- scenario_report.location.longitude = longs.reduce(:+) / longs.size.to_f
87
+ scenario_report.location.latitude_deg = lats.reduce(:+) / lats.size.to_f
88
+ scenario_report.location.longitude_deg = longs.reduce(:+) / longs.size.to_f
87
89
  end
88
90
  end
89
91
  end
90
92
 
91
93
  # Update required info
92
94
  requireds_names = ['latitude', 'longitude']
93
- requireds = [scenario_report.location.latitude, scenario_report.location.longitude]
95
+ requireds = [scenario_report.location.latitude_deg, scenario_report.location.longitude_deg]
94
96
 
95
97
  if requireds.include?(nil) || requireds.include?(0)
96
- requireds.each_with_index do |i, x|
97
- if [nil, 0].include? x
98
+ requireds.each_with_index do |x, i|
99
+ if [nil].include? x
98
100
  n = requireds_names[i]
99
101
  raise "Missing value for #{n} - this is a required input"
100
102
  end
@@ -103,20 +105,25 @@ module URBANopt # :nodoc:
103
105
 
104
106
  reopt_inputs[:Scenario][:description] = description
105
107
 
106
- reopt_inputs[:Scenario][:Site][:latitude] = scenario_report.location.latitude
107
- reopt_inputs[:Scenario][:Site][:longitude] = scenario_report.location.longitude
108
+ reopt_inputs[:Scenario][:Site][:latitude] = scenario_report.location.latitude_deg
109
+ reopt_inputs[:Scenario][:Site][:longitude] = scenario_report.location.longitude_deg
108
110
 
109
111
  # Update optional info
110
- if !scenario_report.program.roof_area.nil?
111
- reopt_inputs[:Scenario][:Site][:roof_squarefeet] = scenario_report.program.roof_area[:available_roof_area]
112
+ # REK: attribute names should be updated
113
+ if reopt_inputs[:Scenario][:Site][:roof_squarefeet].nil?
114
+ if !scenario_report.program.roof_area_sqft.nil?
115
+ reopt_inputs[:Scenario][:Site][:roof_squarefeet] = scenario_report.program.roof_area_sqft[:available_roof_area_sqft]
116
+ end
112
117
  end
113
118
 
114
- if !scenario_report.program.site_area.nil?
115
- reopt_inputs[:Scenario][:Site][:land_acres] = scenario_report.program.site_area * 1.0 / 43560 # acres/sqft
119
+ if reopt_inputs[:Scenario][:Site][:land_acres].nil?
120
+ if !scenario_report.program.site_area_sqft.nil?
121
+ reopt_inputs[:Scenario][:Site][:land_acres] = scenario_report.program.site_area_sqft * 1.0 / 43560 # acres/sqft
122
+ end
116
123
  end
117
124
 
118
- unless scenario_report.timesteps_per_hour.nil?
119
- reopt_inputs[:Scenario][:time_steps_per_hour] = scenario_report.timesteps_per_hour
125
+ if reopt_inputs[:Scenario][:time_steps_per_hour].nil?
126
+ reopt_inputs[:Scenario][:time_steps_per_hour] = 1
120
127
  end
121
128
 
122
129
  # Update load profile info
@@ -126,27 +133,51 @@ module URBANopt # :nodoc:
126
133
  energy_timeseries_kw = t.by_col[col_num].map { |e| ((e * scenario_report.timesteps_per_hour || 0) ) }
127
134
  if energy_timeseries_kw.length < (scenario_report.timesteps_per_hour * 8760)
128
135
  start_date = Time.parse(t.by_col["Datetime"][0])
129
- start_ts = (((start_date.yday * 60.0 * 60.0 * 24) + (start_date.hour * 60.0 * 60.0) + (start_date.min * 60.0) + start_date.sec) /
136
+ start_ts = (((start_date.yday * 60.0 * 60.0 * 24) + (start_date.hour * 60.0 * 60.0) + (start_date.min * 60.0) + start_date.sec) / \
130
137
  (( 60 / scenario_report.timesteps_per_hour ) * 60)).to_int
131
138
  end_date = Time.parse(t.by_col["Datetime"][-1])
132
- end_ts = (((end_date.yday * 60.0 * 60.0 * 24) + (end_date.hour * 60.0 * 60.0) + (end_date.min * 60.0) + end_date.sec) /
139
+ end_ts = (((end_date.yday * 60.0 * 60.0 * 24) + (end_date.hour * 60.0 * 60.0) + (end_date.min * 60.0) + end_date.sec) / \
133
140
  (( 60 / scenario_report.timesteps_per_hour ) * 60)).to_int
134
141
  energy_timeseries_kw = [0.0]*(start_ts-1) + energy_timeseries_kw + [0.0]*((scenario_report.timesteps_per_hour * 8760) - end_ts)
135
142
  end
136
- reopt_inputs[:Scenario][:Site][:LoadProfile][:loads_kw] = energy_timeseries_kw.map { |e| e ? e : 0 }[0,(scenario_report.timesteps_per_hour * 8760)]
143
+ energy_timeseries_kw = energy_timeseries_kw.map { |e| e ? e : 0 }[0,(scenario_report.timesteps_per_hour * 8760)]
137
144
  rescue StandardError
138
145
  @@logger.error("Could not parse the annual electric load from the timeseries csv - #{scenario_report.timeseries_csv.path}")
139
146
  raise "Could not parse the annual electric load from the timeseries csv - #{scenario_report.timeseries_csv.path}"
140
147
  end
148
+
149
+ # Convert load to REopt Resolution
150
+ begin
151
+ reopt_inputs[:Scenario][:Site][:LoadProfile][:loads_kw] = convert_powerflow_resolution(energy_timeseries_kw, scenario_report.timesteps_per_hour, reopt_inputs[:Scenario][:time_steps_per_hour])
152
+ rescue
153
+ @@logger.error("Could not convert the annual electric load from a resolution of #{scenario_report.timesteps_per_hour} to #{reopt_inputs[:Scenario][:time_steps_per_hour]}")
154
+ raise "Could not convert the annual electric load from a resolution of #{scenario_report.timesteps_per_hour} to #{reopt_inputs[:Scenario][:time_steps_per_hour]}"
155
+ end
156
+
157
+ if reopt_inputs[:Scenario][:Site][:ElectricTariff][:coincident_peak_load_active_timesteps].nil?
158
+ n_top_values = 100
159
+ tmp1 = reopt_inputs[:Scenario][:Site][:LoadProfile][:loads_kw]
160
+ tmp2 = tmp1.each_index.max_by(n_top_values*reopt_inputs[:Scenario][:time_steps_per_hour]){|i| tmp1[i]}
161
+ for i in (0...tmp2.count)
162
+ tmp2[i] += 1
163
+ end
164
+ reopt_inputs[:Scenario][:Site][:ElectricTariff][:coincident_peak_load_active_timesteps] = tmp2
165
+ end
166
+
167
+ if reopt_inputs[:Scenario][:Site][:ElectricTariff][:coincident_peak_load_charge_us_dollars_per_kw].nil?
168
+ reopt_inputs[:Scenario][:Site][:ElectricTariff][:coincident_peak_load_charge_us_dollars_per_kw] = 0
169
+ end
170
+
141
171
  return reopt_inputs
142
172
  end
143
173
 
174
+
144
175
  ##
145
176
  # Converts a FeatureReport list from a ScenarioReport into an array of \REopt Lite posts
146
177
  #
147
178
  # [*parameters:*]
148
179
  #
149
- # * +scenario_report+ - _URBANopt::Scenario::DefaultReports::ScenarioReport_ - ScenarioReport to use in converting FeatureReports and respecitive +reopt_assumptions_hashes+, if provided, to a \REopt Lite post. If no +reopt_assumptions_hashes+ are provided default posts will be updated from these FeatureReports and submitted to the \REopt Lite API.
180
+ # * +scenario_report+ - _URBANopt::Reporting::DefaultReports::ScenarioReport_ - ScenarioReport to use in converting FeatureReports and respecitive +reopt_assumptions_hashes+, if provided, to a \REopt Lite post. If no +reopt_assumptions_hashes+ are provided default posts will be updated from these FeatureReports and submitted to the \REopt Lite API.
150
181
  # * +reopt_assumptions_hashes+ - _Array_ - Optional. An array of hashes formatted for submittal to the \REopt Lite API containing default values. Values will be overwritten from the ScenarioReport where available (i.e. latitude, roof_squarefeet). Missing optional parameters will be filled in with default values by the API. The order should match the list in ScenarioReport.feature_reports.
151
182
  #
152
183
  # [*return:*] _Array_ - Returns an array of hashes formatted for submittal to the \REopt Lite API in the order of the FeatureReports lited in ScenarioReport.feature_reports.
@@ -168,44 +199,21 @@ module URBANopt # :nodoc:
168
199
  #
169
200
  # [*parameters:*]
170
201
  #
171
- # * +scenario_report+ - _URBANopt::Scenario::DefaultReports::ScenarioReport_ - ScenarioReport to update from a \REopt Lite response.
202
+ # * +scenario_report+ - _URBANopt::Reporting::DefaultReports::ScenarioReport_ - ScenarioReport to update from a \REopt Lite response.
172
203
  # * +reopt_output+ - _Hash_ - A hash response from the \REopt Lite API.
173
204
  # * +timeseries_csv_path+ - _String_ - Optional. The path to a file at which new timeseries data will be written. If not provided a file is created based on the run_uuid of the \REopt Lite optimization task.
174
205
  #
175
- # [*return:*] _URBANopt::Scenario::DefaultReports::ScenarioReport_ - Returns an updated ScenarioReport
206
+ # [*return:*] _URBANopt::Reporting::DefaultReports::ScenarioReport_ - Returns an updated ScenarioReport
176
207
  ##
177
- def update_scenario_report(scenario_report, reopt_output, timeseries_csv_path = nil)
208
+ def update_scenario_report(scenario_report, reopt_output, timeseries_csv_path=nil, resilience_stats=nil)
178
209
  if reopt_output['outputs']['Scenario']['status'] != 'optimal'
179
210
  @@logger.info("Warning cannot Feature Report #{scenario_report.name} #{scenario_report.id} - REopt optimization was non-optimal")
180
211
  return scenario_report
181
212
  end
182
213
 
183
- $ts_per_hour = scenario_report.timesteps_per_hour
184
- def scale_timeseries(input, ts_per_hr=$ts_per_hour)
185
- if input.nil?
186
- return nil
187
- end
188
- if input.length ==0
189
- return nil
190
- end
191
- if input.length == (8760 * ts_per_hr)
192
- return input
193
- end
194
- result = []
195
- input.each do |val|
196
- (1..ts_per_hr).each do |x|
197
- result.push(val/ts_per_hr.to_f)
198
- end
199
- end
200
- return result
201
- end
202
-
203
214
  # Update location
204
- scenario_report.location.latitude = reopt_output['inputs']['Scenario']['Site']['latitude']
205
- scenario_report.location.longitude = reopt_output['inputs']['Scenario']['Site']['longitude']
206
-
207
- # Update timeseries csv from \REopt Lite dispatch data
208
- scenario_report.timesteps_per_hour = reopt_output['inputs']['Scenario']['time_steps_per_hour']
215
+ scenario_report.location.latitude_deg = reopt_output['inputs']['Scenario']['Site']['latitude']
216
+ scenario_report.location.longitude_deg = reopt_output['inputs']['Scenario']['Site']['longitude']
209
217
 
210
218
  # Update distributed generation sizing and financials
211
219
 
@@ -215,55 +223,61 @@ module URBANopt # :nodoc:
215
223
  scenario_report.distributed_generation.year_one_demand_cost_us_dollars = reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['year_one_demand_cost_us_dollars'] || 0
216
224
  scenario_report.distributed_generation.year_one_bill_us_dollars = reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['year_one_bill_us_dollars'] || 0
217
225
  scenario_report.distributed_generation.total_energy_cost_us_dollars = reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['total_energy_cost_us_dollars'] || 0
218
-
226
+ scenario_report.distributed_generation.total_demand_cost_us_dollars = reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['total_demand_cost_us_dollars'] || 0
227
+ scenario_report.distributed_generation.year_one_energy_cost_bau_us_dollars = reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['year_one_energy_cost_bau_us_dollars'] || 0
228
+ scenario_report.distributed_generation.year_one_demand_cost_bau_us_dollars = reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['year_one_demand_cost_bau_us_dollars'] || 0
229
+ scenario_report.distributed_generation.year_one_bill_bau_us_dollars = reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['year_one_bill_bau_us_dollars'] || 0
230
+ scenario_report.distributed_generation.total_demand_cost_bau_us_dollars = reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['total_demand_cost_bau_us_dollars'] || 0
231
+ scenario_report.distributed_generation.total_energy_cost_bau_us_dollars = reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['total_energy_cost_bau_us_dollars'] || 0
232
+ if !resilience_stats.nil?
233
+ scenario_report.distributed_generation.resilience_hours_min = resilience_stats['resilience_hours_min']
234
+ scenario_report.distributed_generation.resilience_hours_max = resilience_stats['resilience_hours_max']
235
+ scenario_report.distributed_generation.resilience_hours_avg = resilience_stats['resilience_hours_avg']
236
+ scenario_report.distributed_generation.probs_of_surviving = resilience_stats['probs_of_surviving']
237
+ scenario_report.distributed_generation.probs_of_surviving_by_month = resilience_stats['probs_of_surviving_by_month']
238
+ scenario_report.distributed_generation.probs_of_surviving_by_hour_of_the_day = resilience_stats['probs_of_surviving_by_hour_of_the_day']
239
+ end
240
+
219
241
  if reopt_output['outputs']['Scenario']['Site']['PV'].class == Hash
220
242
  reopt_output['outputs']['Scenario']['Site']['PV'] = [reopt_output['outputs']['Scenario']['Site']['PV']]
221
243
  elsif reopt_output['outputs']['Scenario']['Site']['PV'].nil?
222
244
  reopt_output['outputs']['Scenario']['Site']['PV'] = []
223
245
  end
224
-
225
- reopt_output['outputs']['Scenario']['Site']['PV'].each_with_index do |pv, i|
226
- scenario_report.distributed_generation.add_tech 'solar_pv', URBANopt::Scenario::DefaultReports::SolarPV.new( {size_kw: (pv['size_kw'] || 0), id: i })
246
+
247
+ reopt_output['outputs']['Scenario']['Site']['PV'].each_with_index do |pv, i|
248
+ scenario_report.distributed_generation.add_tech 'solar_pv', URBANopt::Reporting::DefaultReports::SolarPV.new( {size_kw: (pv['size_kw'] || 0), id: i })
227
249
  end
228
250
 
229
251
  wind = reopt_output['outputs']['Scenario']['Site']['Wind']
230
252
  if !wind['size_kw'].nil? and wind['size_kw'] != 0
231
- scenario_report.distributed_generation.add_tech 'wind', URBANopt::Scenario::DefaultReports::Wind.new( {size_kw: (wind['size_kw'] || 0) })
253
+ scenario_report.distributed_generation.add_tech 'wind', URBANopt::Reporting::DefaultReports::Wind.new( {size_kw: (wind['size_kw'] || 0) })
232
254
  end
233
255
 
234
256
  generator = reopt_output['outputs']['Scenario']['Site']['Generator']
235
257
  if !generator['size_kw'].nil? and generator['size_kw'] != 0
236
- scenario_report.distributed_generation.add_tech 'generator', URBANopt::Scenario::DefaultReports::Generator.new( {size_kw: (generator['size_kw'] || 0) })
258
+ scenario_report.distributed_generation.add_tech 'generator', URBANopt::Reporting::DefaultReports::Generator.new( {size_kw: (generator['size_kw'] || 0) })
237
259
  end
238
260
 
239
261
  storage = reopt_output['outputs']['Scenario']['Site']['Storage']
240
262
  if !storage['size_kw'].nil? and storage['size_kw'] != 0
241
- scenario_report.distributed_generation.add_tech 'storage', URBANopt::Scenario::DefaultReports::Storage.new( {size_kwh: (storage['size_kwh'] || 0), size_kw: (storage['size_kw'] || 0) })
263
+ scenario_report.distributed_generation.add_tech 'storage', URBANopt::Reporting::DefaultReports::Storage.new( {size_kwh: (storage['size_kwh'] || 0), size_kw: (storage['size_kw'] || 0) })
242
264
  end
243
265
 
266
+ reopt_resolution = reopt_output['inputs']['Scenario']['time_steps_per_hour']
244
267
  generation_timeseries_kwh = Matrix[[0] * (8760 * scenario_report.timesteps_per_hour)]
245
-
246
268
 
247
- reopt_output['outputs']['Scenario']['Site']['PV'].each do |pv|
269
+ reopt_output['outputs']['Scenario']['Site']['PV'].each do |pv|
248
270
  if (pv['size_kw'] || 0) > 0
249
271
  if !pv['year_one_power_production_series_kw'].nil?
250
- generation_timeseries_kwh += Matrix[pv['year_one_power_production_series_kw']]
272
+ generation_timeseries_kwh += Matrix[convert_powerflow_resolution(pv['year_one_power_production_series_kw'], reopt_resolution, scenario_report.timesteps_per_hour)]
251
273
  end
252
274
  end
253
275
  end
254
276
 
255
- unless reopt_output['outputs']['Scenario']['Site']['Storage'].nil?
256
- if (reopt_output['outputs']['Scenario']['Site']['Storage']['size_kw'] or 0) > 0
257
- if !reopt_output['outputs']['Scenario']['Site']['Storage']['year_one_to_grid_series_kw'].nil?
258
- generation_timeseries_kwh = generation_timeseries_kwh + Matrix[reopt_output['outputs']['Scenario']['Site']['Storage']['year_one_to_grid_series_kw']]
259
- end
260
- end
261
- end
262
-
263
277
  unless reopt_output['outputs']['Scenario']['Site']['Wind'].nil?
264
278
  if (reopt_output['outputs']['Scenario']['Site']['Wind']['size_kw'] || 0) > 0
265
279
  if !reopt_output['outputs']['Scenario']['Site']['Wind']['year_one_power_production_series_kw'].nil?
266
- generation_timeseries_kwh += Matrix[reopt_output['outputs']['Scenario']['Site']['Wind']['year_one_power_production_series_kw']]
280
+ generation_timeseries_kwh += Matrix[convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['Wind']['year_one_power_production_series_kw'], reopt_resolution, scenario_report.timesteps_per_hour)]
267
281
  end
268
282
  end
269
283
  end
@@ -271,7 +285,7 @@ module URBANopt # :nodoc:
271
285
  unless reopt_output['outputs']['Scenario']['Site']['Generator'].nil?
272
286
  if (reopt_output['outputs']['Scenario']['Site']['Generator']['size_kw'] || 0) > 0
273
287
  if !reopt_output['outputs']['Scenario']['Site']['Generator']['year_one_power_production_series_kw'].nil?
274
- generation_timeseries_kwh += Matrix[reopt_output['outputs']['Scenario']['Site']['Generator']['year_one_power_production_series_kw']]
288
+ generation_timeseries_kwh += Matrix[convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['Generator']['year_one_power_production_series_kw'], reopt_resolution, scenario_report.timesteps_per_hour)]
275
289
  end
276
290
  end
277
291
  end
@@ -283,70 +297,70 @@ module URBANopt # :nodoc:
283
297
  scenario_report.timeseries_csv.column_names.push('REopt:ElectricityProduced:Total(kw)')
284
298
  end
285
299
 
286
- $load = scale_timeseries(reopt_output['outputs']['Scenario']['Site']['LoadProfile']['year_one_electric_load_series_kw']) || [0] * (8760 * scenario_report.timesteps_per_hour)
300
+ $load = convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['LoadProfile']['year_one_electric_load_series_kw'], reopt_resolution, scenario_report.timesteps_per_hour) || [0] * (8760 * scenario_report.timesteps_per_hour)
287
301
  $load_col = scenario_report.timeseries_csv.column_names.index('REopt:Electricity:Load:Total(kw)')
288
302
  if $load_col.nil?
289
303
  $load_col = scenario_report.timeseries_csv.column_names.length
290
304
  scenario_report.timeseries_csv.column_names.push('REopt:Electricity:Load:Total(kw)')
291
305
  end
292
306
 
293
- $utility_to_load = scale_timeseries(reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['year_one_to_load_series_kw']) || [0] * (8760 * scenario_report.timesteps_per_hour)
307
+ $utility_to_load = convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['year_one_to_load_series_kw'], reopt_resolution, scenario_report.timesteps_per_hour) || [0] * (8760 * scenario_report.timesteps_per_hour)
294
308
  $utility_to_load_col = scenario_report.timeseries_csv.column_names.index('REopt:Electricity:Grid:ToLoad(kw)')
295
309
  if $utility_to_load_col.nil?
296
310
  $utility_to_load_col = scenario_report.timeseries_csv.column_names.length
297
311
  scenario_report.timeseries_csv.column_names.push('REopt:Electricity:Grid:ToLoad(kw)')
298
312
  end
299
313
 
300
- $utility_to_battery = scale_timeseries(reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['year_one_to_battery_series_kw']) || [0] * (8760 * scenario_report.timesteps_per_hour)
314
+ $utility_to_battery = convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['year_one_to_battery_series_kw'], reopt_resolution, scenario_report.timesteps_per_hour) || [0] * (8760 * scenario_report.timesteps_per_hour)
301
315
  $utility_to_battery_col = scenario_report.timeseries_csv.column_names.index('REopt:Electricity:Grid:ToBattery(kw)')
302
316
  if $utility_to_battery_col.nil?
303
317
  $utility_to_battery_col = scenario_report.timeseries_csv.column_names.length
304
318
  scenario_report.timeseries_csv.column_names.push('REopt:Electricity:Grid:ToBattery(kw)')
305
319
  end
306
320
 
307
- $storage_to_load = scale_timeseries(reopt_output['outputs']['Scenario']['Site']['Storage']['year_one_to_load_series_kw']) || [0] * (8760 * scenario_report.timesteps_per_hour)
321
+ $storage_to_load = convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['Storage']['year_one_to_load_series_kw'], reopt_resolution, scenario_report.timesteps_per_hour) || [0] * (8760 * scenario_report.timesteps_per_hour)
308
322
  $storage_to_load_col = scenario_report.timeseries_csv.column_names.index('REopt:Electricity:Storage:ToLoad(kw)')
309
323
  if $storage_to_load_col.nil?
310
324
  $storage_to_load_col = scenario_report.timeseries_csv.column_names.length
311
325
  scenario_report.timeseries_csv.column_names.push('REopt:Electricity:Storage:ToLoad(kw)')
312
326
  end
313
327
 
314
- $storage_to_grid = scale_timeseries(reopt_output['outputs']['Scenario']['Site']['Storage']['year_one_to_grid_series_kw']) || [0] * (8760 * scenario_report.timesteps_per_hour)
328
+ $storage_to_grid = convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['Storage']['year_one_to_grid_series_kw'], reopt_resolution, scenario_report.timesteps_per_hour) || [0] * (8760 * scenario_report.timesteps_per_hour)
315
329
  $storage_to_grid_col = scenario_report.timeseries_csv.column_names.index('REopt:Electricity:Storage:ToGrid(kw)')
316
330
  if $storage_to_grid_col.nil?
317
331
  $storage_to_grid_col = scenario_report.timeseries_csv.column_names.length
318
332
  scenario_report.timeseries_csv.column_names.push('REopt:Electricity:Storage:ToGrid(kw)')
319
333
  end
320
334
 
321
- $storage_soc = scale_timeseries(reopt_output['outputs']['Scenario']['Site']['Storage']['year_one_soc_series_pct']) || [0] * (8760 * scenario_report.timesteps_per_hour)
335
+ $storage_soc = convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['Storage']['year_one_soc_series_pct'], reopt_resolution, scenario_report.timesteps_per_hour) || [0] * (8760 * scenario_report.timesteps_per_hour)
322
336
  $storage_soc_col = scenario_report.timeseries_csv.column_names.index('REopt:Electricity:Storage:StateOfCharge(pct)')
323
337
  if $storage_soc_col.nil?
324
338
  $storage_soc_col = scenario_report.timeseries_csv.column_names.length
325
339
  scenario_report.timeseries_csv.column_names.push('REopt:Electricity:Storage:StateOfCharge(pct)')
326
340
  end
327
341
 
328
- $generator_total = scale_timeseries(reopt_output['outputs']['Scenario']['Site']['Generator']['year_one_power_production_series_kw']) || [0] * (8760 * scenario_report.timesteps_per_hour)
342
+ $generator_total = convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['Generator']['year_one_power_production_series_kw'], reopt_resolution, scenario_report.timesteps_per_hour) || [0] * (8760 * scenario_report.timesteps_per_hour)
329
343
  $generator_total_col = scenario_report.timeseries_csv.column_names.index('REopt:ElectricityProduced:Generator:Total(kw)')
330
344
  if $generator_total_col.nil?
331
345
  $generator_total_col = scenario_report.timeseries_csv.column_names.length
332
346
  scenario_report.timeseries_csv.column_names.push('REopt:ElectricityProduced:Generator:Total(kw)')
333
347
  end
334
348
 
335
- $generator_to_battery = scale_timeseries(reopt_output['outputs']['Scenario']['Site']['Generator']['year_one_to_battery_series_kw']) || [0] * (8760 * scenario_report.timesteps_per_hour)
349
+ $generator_to_battery = convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['Generator']['year_one_to_battery_series_kw'], reopt_resolution, scenario_report.timesteps_per_hour) || [0] * (8760 * scenario_report.timesteps_per_hour)
336
350
  $generator_to_battery_col = scenario_report.timeseries_csv.column_names.index('REopt:ElectricityProduced:Generator:ToBattery(kw)')
337
351
  if $generator_to_battery_col.nil?
338
352
  $generator_to_battery_col = scenario_report.timeseries_csv.column_names.length
339
353
  scenario_report.timeseries_csv.column_names.push('REopt:ElectricityProduced:Generator:ToBattery(kw)')
340
354
  end
341
355
 
342
- $generator_to_load = scale_timeseries(reopt_output['outputs']['Scenario']['Site']['Generator']['year_one_to_load_series_kw']) || [0] * (8760 * scenario_report.timesteps_per_hour)
356
+ $generator_to_load = convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['Generator']['year_one_to_load_series_kw'], reopt_resolution, scenario_report.timesteps_per_hour) || [0] * (8760 * scenario_report.timesteps_per_hour)
343
357
  $generator_to_load_col = scenario_report.timeseries_csv.column_names.index('REopt:ElectricityProduced:Generator:ToLoad(kw)')
344
358
  if $generator_to_load_col.nil?
345
359
  $generator_to_load_col = scenario_report.timeseries_csv.column_names.length
346
360
  scenario_report.timeseries_csv.column_names.push('REopt:ElectricityProduced:Generator:ToLoad(kw)')
347
361
  end
348
362
 
349
- $generator_to_grid = scale_timeseries(reopt_output['outputs']['Scenario']['Site']['Generator']['year_one_to_grid_series_kw']) || [0] * (8760 * scenario_report.timesteps_per_hour)
363
+ $generator_to_grid = convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['Generator']['year_one_to_grid_series_kw'], reopt_resolution, scenario_report.timesteps_per_hour) || [0] * (8760 * scenario_report.timesteps_per_hour)
350
364
  $generator_to_grid_col = scenario_report.timeseries_csv.column_names.index('REopt:ElectricityProduced:Generator:ToGrid(kw)')
351
365
  if $generator_to_grid_col.nil?
352
366
  $generator_to_grid_col = scenario_report.timeseries_csv.column_names.length
@@ -384,10 +398,10 @@ module URBANopt # :nodoc:
384
398
 
385
399
  reopt_output['outputs']['Scenario']['Site']['PV'].each_with_index do |pv, i|
386
400
  if (pv['size_kw'] || 0) > 0
387
- $pv_total += Matrix[scale_timeseries(pv['year_one_power_production_series_kw']) || [0] * (8760 * scenario_report.timesteps_per_hour)]
388
- $pv_to_battery += Matrix[scale_timeseries(pv['year_one_to_battery_series_kw']) || [0] * (8760 * scenario_report.timesteps_per_hour)]
389
- $pv_to_load += Matrix[scale_timeseries(pv['year_one_to_load_series_kw']) || [0] * (8760 * scenario_report.timesteps_per_hour)]
390
- $pv_to_grid += Matrix[scale_timeseries(pv['year_one_to_grid_series_kw']) || [0] * (8760 * scenario_report.timesteps_per_hour)]
401
+ $pv_total += Matrix[convert_powerflow_resolution(pv['year_one_power_production_series_kw'], reopt_resolution, scenario_report.timesteps_per_hour) || [0] * (8760 * scenario_report.timesteps_per_hour)]
402
+ $pv_to_battery += Matrix[convert_powerflow_resolution(pv['year_one_to_battery_series_kw'], reopt_resolution, scenario_report.timesteps_per_hour) || [0] * (8760 * scenario_report.timesteps_per_hour)]
403
+ $pv_to_load += Matrix[convert_powerflow_resolution(pv['year_one_to_load_series_kw'], reopt_resolution, scenario_report.timesteps_per_hour) || [0] * (8760 * scenario_report.timesteps_per_hour)]
404
+ $pv_to_grid += Matrix[convert_powerflow_resolution(pv['year_one_to_grid_series_kw'], reopt_resolution, scenario_report.timesteps_per_hour) || [0] * (8760 * scenario_report.timesteps_per_hour)]
391
405
  end
392
406
  end
393
407
 
@@ -396,28 +410,28 @@ module URBANopt # :nodoc:
396
410
  $pv_to_load = $pv_to_load.to_a[0]
397
411
  $pv_to_grid = $pv_to_grid.to_a[0]
398
412
 
399
- $wind_total = scale_timeseries(reopt_output['outputs']['Scenario']['Site']['Wind']['year_one_power_production_series_kw']) || [0] * (8760 * scenario_report.timesteps_per_hour)
413
+ $wind_total = convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['Wind']['year_one_power_production_series_kw'], reopt_resolution, scenario_report.timesteps_per_hour) || [0] * (8760 * scenario_report.timesteps_per_hour)
400
414
  $wind_total_col = scenario_report.timeseries_csv.column_names.index('REopt:ElectricityProduced:Wind:Total(kw)')
401
415
  if $wind_total_col.nil?
402
416
  $wind_total_col = scenario_report.timeseries_csv.column_names.length
403
417
  scenario_report.timeseries_csv.column_names.push('REopt:ElectricityProduced:Wind:Total(kw)')
404
418
  end
405
419
 
406
- $wind_to_battery = scale_timeseries(reopt_output['outputs']['Scenario']['Site']['Wind']['year_one_to_battery_series_kw']) || [0] * (8760 * scenario_report.timesteps_per_hour)
420
+ $wind_to_battery = convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['Wind']['year_one_to_battery_series_kw'], reopt_resolution, scenario_report.timesteps_per_hour) || [0] * (8760 * scenario_report.timesteps_per_hour)
407
421
  $wind_to_battery_col = scenario_report.timeseries_csv.column_names.index('REopt:ElectricityProduced:Wind:ToBattery(kw)')
408
422
  if $wind_to_battery_col.nil?
409
423
  $wind_to_battery_col = scenario_report.timeseries_csv.column_names.length
410
424
  scenario_report.timeseries_csv.column_names.push('REopt:ElectricityProduced:Wind:ToBattery(kw)')
411
425
  end
412
426
 
413
- $wind_to_load = scale_timeseries(reopt_output['outputs']['Scenario']['Site']['Wind']['year_one_to_load_series_kw']) || [0] * (8760 * scenario_report.timesteps_per_hour)
427
+ $wind_to_load = convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['Wind']['year_one_to_load_series_kw'], reopt_resolution, scenario_report.timesteps_per_hour) || [0] * (8760 * scenario_report.timesteps_per_hour)
414
428
  $wind_to_load_col = scenario_report.timeseries_csv.column_names.index('REopt:ElectricityProduced:Wind:ToLoad(kw)')
415
429
  if $wind_to_load_col.nil?
416
430
  $wind_to_load_col = scenario_report.timeseries_csv.column_names.length
417
431
  scenario_report.timeseries_csv.column_names.push('REopt:ElectricityProduced:Wind:ToLoad(kw)')
418
432
  end
419
433
 
420
- $wind_to_grid = scale_timeseries(reopt_output['outputs']['Scenario']['Site']['Wind']['year_one_to_grid_series_kw']) || [0] * (8760 * scenario_report.timesteps_per_hour)
434
+ $wind_to_grid = convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['Wind']['year_one_to_grid_series_kw'], reopt_resolution, scenario_report.timesteps_per_hour) || [0] * (8760 * scenario_report.timesteps_per_hour)
421
435
  $wind_to_grid_col = scenario_report.timeseries_csv.column_names.index('REopt:ElectricityProduced:Wind:ToGrid(kw)')
422
436
  if $wind_to_grid_col.nil?
423
437
  $wind_to_grid_col = scenario_report.timeseries_csv.column_names.length
@@ -448,17 +462,22 @@ module URBANopt # :nodoc:
448
462
  end
449
463
 
450
464
  old_data = CSV.open(scenario_report.timeseries_csv.path).read
451
- start_date = Time.parse(old_data[1][0])
452
- start_ts = (((start_date.yday * 60.0 * 60.0 * 24) + (start_date.hour * 60.0 * 60.0) + (start_date.min * 60.0) + start_date.sec) / (( 60 / scenario_report.timesteps_per_hour ) * 60)).to_int
465
+ start_date = Time.parse(old_data[1][0]) # Time is the end of the timestep
466
+ start_ts = (
467
+ (
468
+ ((start_date.yday - 1) * 60.0 * 60.0 * 24) +
469
+ (((start_date.hour) - 1) * 60.0 * 60.0) +
470
+ (start_date.min * 60.0) + start_date.sec ) /
471
+ (( 60 / scenario_report.timesteps_per_hour ) * 60)
472
+ ).to_int
453
473
  mod_data = old_data.map.with_index do |x, i|
454
474
  if i > 0
455
- modrow(x, start_ts + i -2)
475
+ modrow(x, start_ts + i -1)
456
476
  else
457
477
  x
458
478
  end
459
479
  end
460
480
  mod_data[0] = scenario_report.timeseries_csv.column_names
461
-
462
481
  scenario_report.timeseries_csv.reload_data(mod_data)
463
482
  return scenario_report
464
483
  end