urbanopt-reopt 0.5.4 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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