urbanopt-reopt 0.5.4 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,21 +1,31 @@
1
1
  # *********************************************************************************
2
- # URBANopt (tm), Copyright (c) 2019-2020, Alliance for Sustainable Energy, LLC, and other
2
+ # URBANopt™, Copyright (c) 2019-2021, 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,
6
6
  # are permitted provided that the following conditions are met:
7
- #
7
+
8
8
  # Redistributions of source code must retain the above copyright notice, this list
9
9
  # of conditions and the following disclaimer.
10
- #
10
+
11
11
  # Redistributions in binary form must reproduce the above copyright notice, this
12
12
  # list of conditions and the following disclaimer in the documentation and/or other
13
13
  # materials provided with the distribution.
14
- #
14
+
15
15
  # Neither the name of the copyright holder nor the names of its contributors may be
16
16
  # used to endorse or promote products derived from this software without specific
17
17
  # prior written permission.
18
- #
18
+
19
+ # Redistribution of this software, without modification, must refer to the software
20
+ # by the same designation. Redistribution of a modified version of this software
21
+ # (i) may not refer to the modified version by the same designation, or by any
22
+ # confusingly similar designation, and (ii) must refer to the underlying software
23
+ # originally provided by Alliance as “URBANopt”. Except to comply with the foregoing,
24
+ # the term “URBANopt”, or any confusingly similar designation may not be used to
25
+ # refer to any modified version of this software or any modified version of the
26
+ # underlying software originally provided by Alliance without the prior written
27
+ # consent of Alliance.
28
+
19
29
  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20
30
  # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21
31
  # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
@@ -87,6 +97,7 @@ module URBANopt # :nodoc:
87
97
  if @use_localhost
88
98
  return URI.parse("http://127.0.0.1:8000/v1/job/#{run_uuid}/results")
89
99
  end
100
+
90
101
  return URI.parse("https://developer.nrel.gov/api/reopt/v1/job/#{run_uuid}/results?api_key=#{@nrel_developer_key}")
91
102
  end
92
103
 
@@ -104,6 +115,7 @@ module URBANopt # :nodoc:
104
115
  if @use_localhost
105
116
  return URI.parse("http://127.0.0.1:8000/v1/job/#{run_uuid}/resilience_stats")
106
117
  end
118
+
107
119
  return URI.parse("https://developer.nrel.gov/api/reopt/v1/job/#{run_uuid}/resilience_stats?api_key=#{@nrel_developer_key}")
108
120
  end
109
121
 
@@ -112,11 +124,20 @@ module URBANopt # :nodoc:
112
124
  tries = 0
113
125
  while tries < max_tries
114
126
  begin
115
- result = http.request(r)
116
- tries = 4
117
- rescue StandardError
118
- tries += 1
119
- end
127
+ result = http.request(r)
128
+ # Result codes sourced from https://developer.nrel.gov/docs/errors/
129
+ if result.code == '429'
130
+ @@logger.fatal('Exceeded the REopt-Lite API limit of 300 requests per hour')
131
+ puts 'Using the URBANopt CLI to submit a Scenario optimization counts as one request per scenario'
132
+ puts 'Using the URBANopt CLI to submit a Feature optimization counts as one request per feature'
133
+ abort('Please wait and try again once the time period has elapsed')
134
+ elsif (result.code != '201') && (result.code != '200') # Anything in the 200s is success
135
+ @@logger.debug("REopt-Lite has returned a '#{result.code}' status code. Visit https://developer.nrel.gov/docs/errors/ for more status code information")
136
+ end
137
+ tries = 4
138
+ rescue StandardError
139
+ tries += 1
140
+ end
120
141
  end
121
142
  return result
122
143
  end
@@ -138,11 +159,11 @@ module URBANopt # :nodoc:
138
159
  http.use_ssl = true
139
160
  end
140
161
 
141
- request = Net::HTTP::Post.new(@uri_submit, header)
142
- request.body = ::JSON.generate(data, allow_nan: true)
162
+ post_request = Net::HTTP::Post.new(@uri_submit, header)
163
+ post_request.body = ::JSON.generate(data, allow_nan: true)
143
164
 
144
165
  # Send the request
145
- response = make_request(http, request)
166
+ response = make_request(http, post_request)
146
167
 
147
168
  if !response.is_a?(Net::HTTPSuccess)
148
169
  @@logger.error('Check_connection Failed')
@@ -165,7 +186,6 @@ module URBANopt # :nodoc:
165
186
  # [*return:*] _Bool_ - Returns true if the post succeeeds. Otherwise returns false.
166
187
  ##
167
188
  def resilience_request(run_uuid, filename)
168
-
169
189
  if File.directory? filename
170
190
  if run_uuid.nil?
171
191
  run_uuid = 'error'
@@ -176,55 +196,59 @@ module URBANopt # :nodoc:
176
196
  filename = File.join(filename, "#{run_uuid}_resilience.json")
177
197
  @@logger.info("REopt results saved to #{filename}")
178
198
  end
179
-
180
- #Submit Job
199
+
200
+ # Submit Job
181
201
  @@logger.info("Submitting Resilience Statistics job for #{run_uuid}")
182
202
  header = { 'Content-Type' => 'application/json' }
183
203
  http = Net::HTTP.new(@uri_submit_outagesimjob.host, @uri_submit_outagesimjob.port)
184
204
  if !@use_localhost
185
205
  http.use_ssl = true
186
206
  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)
207
+ post_request = Net::HTTP::Post.new(@uri_submit_outagesimjob, header)
208
+ post_request.body = ::JSON.generate({ 'run_uuid' => run_uuid, 'bau' => false }, allow_nan: true)
209
+ submit_response = make_request(http, post_request)
210
+ @@logger.debug(submit_response.body)
191
211
 
192
- #Fetch Results
212
+ # Fetch Results
193
213
  uri = uri_resilience(run_uuid)
194
214
  http = Net::HTTP.new(uri.host, uri.port)
195
215
  if !@use_localhost
196
216
  http.use_ssl = true
197
217
  end
198
218
 
219
+ # Wait a few seconds for the REopt database to update before GETing results
220
+ sleep 5
221
+ get_request = Net::HTTP::Get.new(uri.request_uri)
222
+ response = make_request(http, get_request)
223
+
224
+ # Set a limit on retries when 404s are returned from REopt API
199
225
  elapsed_time = 0
200
226
  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
227
+
228
+ # If database still hasn't updated, wait a little longer and try again
229
+ while (elapsed_time < max_elapsed_time) & (response.code == '404')
230
+ response = make_request(http, get_request)
231
+ @@logger.warn('GET request was too fast for REOpt-Lite API. Retrying...')
232
+ elapsed_time += 5
208
233
  sleep 5
209
234
  end
210
-
235
+
211
236
  data = JSON.parse(response.body)
212
237
  text = ::JSON.generate(data, allow_nan: true)
213
238
  begin
214
239
  File.open(filename, 'w+') do |f|
215
240
  f.puts(text)
216
241
  end
217
- rescue
218
- @@logger.info("Cannot write - #{filename}")
242
+ rescue StandardError
243
+ @@logger.error("Cannot write - #{filename}")
219
244
  end
220
245
 
221
- if response.code == "200"
246
+ if response.code == '200'
222
247
  return data
223
248
  end
224
-
225
- @@logger.info("Error from REopt API - #{data['Error']}")
249
+
250
+ @@logger.error("Error from REopt API - #{data['Error']}")
226
251
  return {}
227
-
228
252
  end
229
253
 
230
254
  ##
@@ -251,14 +275,14 @@ module URBANopt # :nodoc:
251
275
  if !@use_localhost
252
276
  http.use_ssl = true
253
277
  end
254
- request = Net::HTTP::Post.new(@uri_submit, header)
255
- request.body = ::JSON.generate(reopt_input, allow_nan: true)
256
-
278
+ post_request = Net::HTTP::Post.new(@uri_submit, header)
279
+ post_request.body = ::JSON.generate(reopt_input, allow_nan: true)
280
+
257
281
  # Send the request
258
- response = make_request(http, request)
282
+ response = make_request(http, post_request)
259
283
 
260
284
  # Get UUID
261
- run_uuid = JSON.parse(response.body, allow_nan:true)['run_uuid']
285
+ run_uuid = JSON.parse(response.body, allow_nan: true)['run_uuid']
262
286
 
263
287
  if File.directory? filename
264
288
  if run_uuid.nil?
@@ -276,7 +300,7 @@ module URBANopt # :nodoc:
276
300
  File.open(filename, 'w+') do |f|
277
301
  f.puts(text)
278
302
  end
279
- @@logger.info("Cannot write - #{filename}")
303
+ @@logger.error("Cannot write - #{filename}")
280
304
  raise "Error in REopt optimization post - see #{filename}"
281
305
  end
282
306
 
@@ -288,18 +312,18 @@ module URBANopt # :nodoc:
288
312
  http.use_ssl = true
289
313
  end
290
314
 
291
- request = Net::HTTP::Get.new(uri.request_uri)
315
+ get_request = Net::HTTP::Get.new(uri.request_uri)
292
316
 
293
317
  while status == 'Optimizing...'
294
- response = make_request(http, request)
295
-
296
- data = JSON.parse(response.body, allow_nan:true)
318
+ response = make_request(http, get_request)
319
+
320
+ data = JSON.parse(response.body, allow_nan: true)
297
321
 
298
- if data['outputs']['Scenario']['Site']['PV'].kind_of?(Array)
322
+ if data['outputs']['Scenario']['Site']['PV'].is_a?(Array)
299
323
  pv_sizes = 0
300
324
  data['outputs']['Scenario']['Site']['PV'].each do |x|
301
- pv_sizes = pv_sizes + x['size_kw'].to_f
302
- end
325
+ pv_sizes += x['size_kw'].to_f
326
+ end
303
327
  else
304
328
  pv_sizes = data['outputs']['Scenario']['Site']['PV']['size_kw'] || 0
305
329
  end
@@ -313,14 +337,14 @@ module URBANopt # :nodoc:
313
337
  _tries = 0
314
338
  (check_complete = sizes == 0) && ((data['outputs']['Scenario']['Site']['Financial']['npv_us_dollars'] || 0) > 0)
315
339
  while (_tries < _max_retry) && check_complete
316
- sleep 1
317
- response = make_request(http, request)
318
- data = JSON.parse(response.body, allow_nan:true)
319
- if data['outputs']['Scenario']['Site']['PV'].kind_of?(Array)
340
+ sleep 3
341
+ response = make_request(http, get_request)
342
+ data = JSON.parse(response.body, allow_nan: true)
343
+ if data['outputs']['Scenario']['Site']['PV'].is_a?(Array)
320
344
  pv_sizes = 0
321
345
  data['outputs']['Scenario']['Site']['PV'].each do |x|
322
- pv_sizes = pv_sizes + x['size_kw'].to_f
323
- end
346
+ pv_sizes += x['size_kw'].to_f
347
+ end
324
348
  else
325
349
  pv_sizes = data['outputs']['Scenario']['Site']['PV']['size_kw'] || 0
326
350
  end
@@ -335,8 +359,8 @@ module URBANopt # :nodoc:
335
359
  File.open(filename, 'w+') do |f|
336
360
  f.puts(text)
337
361
  end
338
- rescue
339
- @@logger.info("Cannot write - #{filename}")
362
+ rescue StandardError
363
+ @@logger.error("Cannot write - #{filename}")
340
364
  end
341
365
 
342
366
  if status == 'optimal'
@@ -1,21 +1,31 @@
1
1
  # *********************************************************************************
2
- # URBANopt (tm), Copyright (c) 2019-2020, Alliance for Sustainable Energy, LLC, and other
2
+ # URBANopt™, Copyright (c) 2019-2021, 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,
6
6
  # are permitted provided that the following conditions are met:
7
- #
7
+
8
8
  # Redistributions of source code must retain the above copyright notice, this list
9
9
  # of conditions and the following disclaimer.
10
- #
10
+
11
11
  # Redistributions in binary form must reproduce the above copyright notice, this
12
12
  # list of conditions and the following disclaimer in the documentation and/or other
13
13
  # materials provided with the distribution.
14
- #
14
+
15
15
  # Neither the name of the copyright holder nor the names of its contributors may be
16
16
  # used to endorse or promote products derived from this software without specific
17
17
  # prior written permission.
18
- #
18
+
19
+ # Redistribution of this software, without modification, must refer to the software
20
+ # by the same designation. Redistribution of a modified version of this software
21
+ # (i) may not refer to the modified version by the same designation, or by any
22
+ # confusingly similar designation, and (ii) must refer to the underlying software
23
+ # originally provided by Alliance as “URBANopt”. Except to comply with the foregoing,
24
+ # the term “URBANopt”, or any confusingly similar designation may not be used to
25
+ # refer to any modified version of this software or any modified version of the
26
+ # underlying software originally provided by Alliance without the prior written
27
+ # consent of Alliance.
28
+
19
29
  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20
30
  # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21
31
  # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
@@ -32,7 +42,7 @@ require 'logger'
32
42
 
33
43
  module URBANopt
34
44
  module REopt
35
- @@reopt_logger = Logger.new(STDOUT)
45
+ @@reopt_logger = Logger.new($stdout)
36
46
  ##
37
47
  # Definining class variable "@@logger" to log errors, info and warning messages.
38
48
  def self.reopt_logger
@@ -1,21 +1,31 @@
1
1
  # *********************************************************************************
2
- # URBANopt (tm), Copyright (c) 2019-2020, Alliance for Sustainable Energy, LLC, and other
2
+ # URBANopt™, Copyright (c) 2019-2021, 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,
6
6
  # are permitted provided that the following conditions are met:
7
- #
7
+
8
8
  # Redistributions of source code must retain the above copyright notice, this list
9
9
  # of conditions and the following disclaimer.
10
- #
10
+
11
11
  # Redistributions in binary form must reproduce the above copyright notice, this
12
12
  # list of conditions and the following disclaimer in the documentation and/or other
13
13
  # materials provided with the distribution.
14
- #
14
+
15
15
  # Neither the name of the copyright holder nor the names of its contributors may be
16
16
  # used to endorse or promote products derived from this software without specific
17
17
  # prior written permission.
18
- #
18
+
19
+ # Redistribution of this software, without modification, must refer to the software
20
+ # by the same designation. Redistribution of a modified version of this software
21
+ # (i) may not refer to the modified version by the same designation, or by any
22
+ # confusingly similar designation, and (ii) must refer to the underlying software
23
+ # originally provided by Alliance as “URBANopt”. Except to comply with the foregoing,
24
+ # the term “URBANopt”, or any confusingly similar designation may not be used to
25
+ # refer to any modified version of this software or any modified version of the
26
+ # underlying software originally provided by Alliance without the prior written
27
+ # consent of Alliance.
28
+
19
29
  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20
30
  # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21
31
  # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
@@ -69,18 +79,18 @@ module URBANopt # :nodoc:
69
79
  if !scenario_report.nil?
70
80
  @scenario_report = scenario_report
71
81
 
72
- if !Dir.exist?(File.join(@scenario_report.directory_name, "reopt"))
73
- Dir.mkdir(File.join(@scenario_report.directory_name, "reopt"))
74
- @@logger.info("Created directory: " + File.join(@scenario_report.directory_name, "reopt"))
82
+ if !Dir.exist?(File.join(@scenario_report.directory_name, 'reopt'))
83
+ Dir.mkdir(File.join(@scenario_report.directory_name, 'reopt'))
84
+ @@logger.info("Created directory: #{File.join(@scenario_report.directory_name, 'reopt')}")
75
85
  end
76
86
 
77
87
  @scenario_reopt_default_output_file = File.join(@scenario_report.directory_name, "reopt/scenario_report_#{@scenario_report.id}_reopt_run.json")
78
88
  @scenario_timeseries_default_output_file = File.join(@scenario_report.directory_name, "scenario_report_#{@scenario_report.id}_timeseries.csv")
79
89
 
80
90
  @scenario_report.feature_reports.each do |fr|
81
- if !Dir.exist?(File.join(fr.directory_name, "reopt"))
82
- Dir.mkdir(File.join(fr.directory_name, "reopt"))
83
- @@logger.info("Created directory: " + File.join(fr.directory_name, "reopt"))
91
+ if !Dir.exist?(File.join(fr.directory_name, 'reopt'))
92
+ Dir.mkdir(File.join(fr.directory_name, 'reopt'))
93
+ @@logger.info("Created directory: #{File.join(fr.directory_name, 'reopt')}")
84
94
  end
85
95
  @feature_reports_reopt_default_output_files << File.join(fr.directory_name, "reopt/feature_report_#{fr.id}_reopt_run.json")
86
96
  end
@@ -103,8 +113,7 @@ module URBANopt # :nodoc:
103
113
  end
104
114
  end
105
115
 
106
- attr_accessor :scenario_reopt_default_assumptions_hash, :scenario_reopt_default_output_file, :scenario_timeseries_default_output_file
107
- attr_accessor :feature_reports_reopt_default_assumption_hashes, :feature_reports_reopt_default_output_files, :feature_reports_timeseries_default_output_files
116
+ attr_accessor :scenario_reopt_default_assumptions_hash, :scenario_reopt_default_output_file, :scenario_timeseries_default_output_file, :feature_reports_reopt_default_assumption_hashes, :feature_reports_reopt_default_output_files, :feature_reports_timeseries_default_output_files
108
117
 
109
118
  ##
110
119
  # Updates a FeatureReport based on an optional set of \REopt Lite optimization assumptions.
@@ -119,7 +128,7 @@ module URBANopt # :nodoc:
119
128
  #
120
129
  # [*return:*] _URBANopt::Reporting::DefaultReports::FeatureReport_ - Returns an updated FeatureReport
121
130
  ##
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)
131
+ def run_feature_report(feature_report:, reopt_assumptions_hash: nil, reopt_output_file: nil, timeseries_csv_path: nil, save_name: nil, run_resilience: true)
123
132
  api = URBANopt::REopt::REoptLiteAPI.new(@nrel_developer_key, @localhost)
124
133
  adapter = URBANopt::REopt::FeatureReportAdapter.new
125
134
 
@@ -133,15 +142,14 @@ module URBANopt # :nodoc:
133
142
  if File.directory? reopt_output_file
134
143
  resilience_stats = api.resilience_request(run_uuid, reopt_output_file)
135
144
  else
136
- resilience_stats = api.resilience_request(run_uuid, reopt_output_file.sub!('.json','_resilience.json'))
145
+ resilience_stats = api.resilience_request(run_uuid, reopt_output_file.sub!('.json', '_resilience.json'))
137
146
  end
138
147
  else
139
148
  resilience_stats = nil
140
149
  end
141
150
  result = adapter.update_feature_report(feature_report, reopt_output, timeseries_csv_path, resilience_stats)
142
151
  if !save_name.nil?
143
- #result.save_feature_report save_name
144
- result.save_json_report(save_name)
152
+ result.save save_name
145
153
  end
146
154
  return result
147
155
  end
@@ -158,7 +166,7 @@ module URBANopt # :nodoc:
158
166
  # * +timeseries_csv_path+ - _String_ - Optional. Path to a file at which the new timeseries CSV for the ScenarioReport will be saved.
159
167
  #
160
168
  # [*return:*] _URBANopt::Scenario::DefaultReports::ScenarioReport_ Returns an updated ScenarioReport
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)
169
+ def run_scenario_report(scenario_report:, reopt_assumptions_hash: nil, reopt_output_file: nil, timeseries_csv_path: nil, save_name: nil, run_resilience: true)
162
170
  if !reopt_assumptions_hash.nil?
163
171
  @scenario_reopt_default_assumptions_hash = reopt_assumptions_hash
164
172
  end
@@ -180,7 +188,7 @@ module URBANopt # :nodoc:
180
188
  if File.directory? @scenario_reopt_default_output_file
181
189
  resilience_stats = api.resilience_request(run_uuid, @scenario_reopt_default_output_file)
182
190
  else
183
- resilience_stats = api.resilience_request(run_uuid, @scenario_reopt_default_output_file.sub!('.json','_resilience.json'))
191
+ resilience_stats = api.resilience_request(run_uuid, @scenario_reopt_default_output_file.sub!('.json', '_resilience.json'))
184
192
  end
185
193
  else
186
194
  resilience_stats = nil
@@ -204,8 +212,7 @@ module URBANopt # :nodoc:
204
212
  # * +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.
205
213
  #
206
214
  # [*return:*] _Array_ Returns an array of updated _URBANopt::Scenario::DefaultReports::FeatureReport_ objects
207
- def run_feature_reports(feature_reports:, reopt_assumptions_hashes:[], reopt_output_files:[], timeseries_csv_paths:[], save_names:nil, run_resilience:true)
208
-
215
+ def run_feature_reports(feature_reports:, reopt_assumptions_hashes: [], reopt_output_files: [], timeseries_csv_paths: [], save_names: nil, run_resilience: true, keep_existing_output: false)
209
216
  if !reopt_assumptions_hashes.empty?
210
217
  @feature_reports_reopt_default_assumption_hashes = reopt_assumptions_hashes
211
218
  end
@@ -234,37 +241,59 @@ module URBANopt # :nodoc:
234
241
  feature_adapter = URBANopt::REopt::FeatureReportAdapter.new
235
242
  new_feature_reports = []
236
243
  feature_reports.each_with_index do |feature_report, idx|
237
- begin
238
- reopt_input = feature_adapter.reopt_json_from_feature_report(feature_report, @feature_reports_reopt_default_assumption_hashes[idx])
239
- reopt_output = api.reopt_request(reopt_input, @feature_reports_reopt_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
+ # check if we should rerun
245
+ if !(keep_existing_output && output_exists(@feature_reports_reopt_default_output_files[idx]))
246
+ begin
247
+ reopt_input = feature_adapter.reopt_json_from_feature_report(feature_report, @feature_reports_reopt_default_assumption_hashes[idx])
248
+ reopt_output = api.reopt_request(reopt_input, @feature_reports_reopt_default_output_files[idx])
249
+ if run_resilience
250
+ run_uuid = reopt_output['outputs']['Scenario']['run_uuid']
251
+ if File.directory? @feature_reports_reopt_default_output_files[idx]
252
+ resilience_stats = api.resilience_request(run_uuid, @feature_reports_reopt_default_output_files[idx])
253
+ else
254
+ resilience_stats = api.resilience_request(run_uuid, @feature_reports_reopt_default_output_files[idx].sub!('.json', '_resilience.json'))
255
+ end
244
256
  else
245
- resilience_stats = api.resilience_request(run_uuid, @feature_reports_reopt_default_output_files[idx].sub!('.json','_resilience.json'))
257
+ resilience_stats = nil
246
258
  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)
251
- new_feature_reports.push(new_feature_report)
252
- if !save_names.nil?
253
- if save_names.length == feature_reports.length
254
- #new_feature_report.save_feature_report save_names[idx]
255
- new_feature_report.save_json_report save_names[idx]
256
- else
257
- warn "Could not save feature reports - the number of save names provided did not match the number of feature reports"
259
+ new_feature_report = feature_adapter.update_feature_report(feature_report, reopt_output, @feature_reports_timeseries_default_output_files[idx], resilience_stats)
260
+ new_feature_reports.push(new_feature_report)
261
+ if !save_names.nil?
262
+ if save_names.length == feature_reports.length
263
+ new_feature_report.save save_names[idx]
264
+ else
265
+ warn 'Could not save feature reports - the number of save names provided did not match the number of feature reports'
266
+ end
258
267
  end
268
+ rescue StandardError
269
+ @@logger.info("Could not optimize Feature Report #{feature_report.name} #{feature_report.id}")
259
270
  end
260
- rescue StandardError
261
- @@logger.info("Could not optimize Feature Report #{feature_report.name} #{feature_report.id}")
271
+ else
272
+ puts('Output file already exists...skipping')
262
273
  end
263
274
  end
264
275
 
265
276
  return new_feature_reports
266
277
  end
267
278
 
279
+ # Checks whether a feature has already been run by determining if output files already exists (for rate limit issues and larger projects)
280
+ ##
281
+ #
282
+ # [*parameters:*]
283
+ #
284
+ # * +output_file+ - _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.
285
+ # [*return:*] _Boolean_ - Returns true if file or nonempty directory exist
286
+ def output_exists(output_file)
287
+ res = false
288
+ if File.directory?(output_file) && !File.empty?(output_file)
289
+ res = true
290
+ elsif File.exist? output_file
291
+ res = true
292
+ end
293
+
294
+ return res
295
+ end
296
+
268
297
  # Updates a ScenarioReport based on an optional set of \REopt Lite optimization assumptions.
269
298
  ##
270
299
  #
@@ -276,22 +305,26 @@ module URBANopt # :nodoc:
276
305
  # * +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.
277
306
  #
278
307
  # [*return:*] _URBANopt::Scenario::DefaultReports::ScenarioReport_ - Returns an updated ScenarioReport
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)
281
-
308
+ 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, keep_existing_output: false)
309
+ 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, keep_existing_output: keep_existing_output)
310
+ puts("KEEP EXISTING? #{keep_existing_output}")
311
+ # only do this if you have run feature reports
282
312
  new_scenario_report = URBANopt::Reporting::DefaultReports::ScenarioReport.new
283
- new_scenario_report.id = scenario_report.id
284
- new_scenario_report.name = scenario_report.name
285
- new_scenario_report.directory_name = scenario_report.directory_name
313
+ if !new_feature_reports.empty?
286
314
 
287
- timeseries_hash = { column_names: scenario_report.timeseries_csv.column_names }
288
- new_scenario_report.timeseries_csv = URBANopt::Reporting::DefaultReports::TimeseriesCSV.new(timeseries_hash)
315
+ new_scenario_report.id = scenario_report.id
316
+ new_scenario_report.name = scenario_report.name
317
+ new_scenario_report.directory_name = scenario_report.directory_name
289
318
 
290
- new_feature_reports.each do |feature_report|
291
- new_scenario_report.add_feature_report(feature_report)
292
- end
293
- if !save_name_scenario_report.nil?
294
- new_scenario_report.save save_name_scenario_report
319
+ timeseries_hash = { column_names: scenario_report.timeseries_csv.column_names }
320
+ new_scenario_report.timeseries_csv = URBANopt::Reporting::DefaultReports::TimeseriesCSV.new(timeseries_hash)
321
+
322
+ new_feature_reports.each do |feature_report|
323
+ new_scenario_report.add_feature_report(feature_report)
324
+ end
325
+ if !save_name_scenario_report.nil?
326
+ new_scenario_report.save save_name_scenario_report
327
+ end
295
328
  end
296
329
  return new_scenario_report
297
330
  end