urbanopt-reopt 0.2.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -10,7 +10,7 @@
10
10
  },
11
11
  "author": "NREL",
12
12
  "dependencies": {
13
- "highlight.js": "^9.15.6",
13
+ "highlight.js": "^10.4.1",
14
14
  "json-schema-ref-parser": "^6.1.0",
15
15
  "json-schema-view-js": "git+https://git@github.com/bgschiller/json-schema-view-js.git",
16
16
  "vuepress": "^0.14.10",
data/index.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # **URBANopt REopt Gem**
2
2
 
3
- The **URBANopt REopt Gem** extends **URBANopt::Scenario::DefaultReports::ScenarioReport** and **URBANopt::Scenario::DefaultReports::FeatureReport** with the ability to derive cost-optimal distributed energy resource (DER) technology sizes and annual dispatch strageties via the [REopt Lite](https://reopt.nrel.gov/tool) decision support platform.
4
- REopt Lite is a technoeconomic model which leverages mixed integer linear programming to identify the cost-optimal sizing of solar PV, Wind, Storage and/or diesel generation given an electric load profile, a utility rate tariff and other technoeconomic parameters. See [https://developer.nrel.gov/docs/energy-optimization/reopt-v1/](https://developer.nrel.gov/docs/energy-optimization/reopt-v1/) for more detailed information on input parameters and default assumptions.
3
+ The **URBANopt<sup>&trade;</sup> REopt Gem** extends **URBANopt::Reporting::DefaultReports::ScenarioReport** and **URBANopt::Reporting::DefaultReports::FeatureReport** with the ability to derive cost-optimal distributed energy resource (DER) technology sizes and annual dispatch strageties via the [REopt Lite](https://reopt.nrel.gov/tool) decision support platform.
4
+ REopt Lite is a technoeconomic model which leverages mixed integer linear programming to identify the cost-optimal sizing of solar PV, Wind, Storage and/or diesel generation given an electric load profile, a utility rate tariff and other technoeconomic parameters. See [https://developer.nrel.gov/docs/energy-optimization/reopt-v1/](https://developer.nrel.gov/docs/energy-optimization/reopt-v1/) for more detailed information on input parameters and default assumptions.
5
5
 
6
6
  See the [example project](https://github.com/urbanopt/urbanopt-example-reopt-project.git) for more infomation about usage of this gem.
7
7
 
@@ -9,7 +9,7 @@ See the [example project](https://github.com/urbanopt/urbanopt-example-reopt-pro
9
9
 
10
10
  ## Installation
11
11
 
12
- See [https://docs.urbanopt.net/installation/installation.html](https://docs.urbanopt.net/installation/installation.html) for instructions on prerequiste software, including:
12
+ See [https://docs.urbanopt.net/installation/installation.html](https://docs.urbanopt.net/installation/installation.html) for instructions on prerequiste software, including:
13
13
  - Ruby 2.2.6
14
14
  - Bundler 1.17.0
15
15
  - OpenStudio 2.8.1
@@ -31,7 +31,7 @@ Or install it yourself as:
31
31
 
32
32
  ## Functionality
33
33
 
34
- This gem is used to call the REopt Lite API on a Scenario Report or Feature Report to update the object's Distributed Generation attributes (including system financial and sizing metrics) as shown in an example below:
34
+ This gem is used to call the REopt Lite API on a Scenario Report or Feature Report to update the object's Distributed Generation attributes (including system financial and sizing metrics) as shown in an example below:
35
35
 
36
36
  ```
37
37
  "distributed_generation": {
@@ -83,7 +83,7 @@ Moreover, the following optimal dispatch fields are added to its timeseries CSV.
83
83
  | ElectricityProduced:Wind:ToGrid | kWh |
84
84
  ```
85
85
 
86
- The REopt Lite has default values for all non-required input parameters that are used unless the user specifies custom assumptions. See [https://developer.nrel.gov/docs/energy-optimization/reopt-v1/](https://developer.nrel.gov/docs/energy-optimization/reopt-v1/) for more detailed information on input parameters and default assumptions.
86
+ The REopt Lite has default values for all non-required input parameters that are used unless the user specifies custom assumptions. See [https://developer.nrel.gov/docs/energy-optimization/reopt-v1/](https://developer.nrel.gov/docs/energy-optimization/reopt-v1/) for more detailed information on input parameters and default assumptions.
87
87
 
88
88
  <b>Note:</b> Required attributes for a REopt run include latitude and longitude. If no utility rate is specified in your REopt Lite assumption settings, then a constant default rate of $0.13 is assumed without demand charges. Also, by default, only solar PV and storage are considered in the analysis (i.e. Wind and Generators are excluded from consideration).
89
89
 
@@ -99,7 +99,7 @@ require 'urbanopt/reopt'
99
99
  feature_reports_hash = {} # <insert a Feature Report hash here>
100
100
 
101
101
  #Create a Feature Report
102
- feature_report = URBANopt::Scenario::DefaultReports::FeatureReport.new(feature_reports_hash)
102
+ feature_report = URBANopt::Reporting::DefaultReports::FeatureReport.new(feature_reports_hash)
103
103
 
104
104
  #Specify a file name where REopt Lite results will be written in JSON format
105
105
  reopt_output_file = File.join(feature_report.directory_name, 'feature_report_reopt_run1.json')
@@ -115,7 +115,7 @@ reopt_post_processor = URBANopt::REopt::REoptPostProcessor.new(nil, nil, nil, DE
115
115
 
116
116
  #Call REopt Lite with the post processor to update the feature's distributed generation attributes and timeseries CSV.
117
117
  updated_feature_report = reopt_post_processor.run_feature_report(feature_report,reopt_assumptions_file,reopt_output_file,timeseries_output_file)
118
-
118
+
119
119
  ```
120
120
 
121
121
  More commonly, this gem can be used to run REopt a collection of features stored in a Scenario Report as show here:
@@ -123,9 +123,9 @@ More commonly, this gem can be used to run REopt a collection of features stored
123
123
  ```ruby
124
124
  require 'urbanopt/reopt'
125
125
  #Create a Scenario Report
126
- scenario_report = URBANopt::Scenario::DefaultReports::ScenarioReport.new({:directory_name => File.join(File.dirname(__FILE__), '../run/example_scenario'), :timeseries_csv => {:path => File.join(File.dirname(__FILE__), '../run/example_scenario/timeseries.csv') }})
126
+ scenario_report = URBANopt::Reporting::DefaultReports::ScenarioReport.new({:directory_name => File.join(File.dirname(__FILE__), '../run/example_scenario'), :timeseries_csv => {:path => File.join(File.dirname(__FILE__), '../run/example_scenario/timeseries.csv') }})
127
127
 
128
- #Load Feature Reports into the Scenario Report
128
+ #Load Feature Reports into the Scenario Report
129
129
  (1..2).each do |i|
130
130
  feature_reports_path = File.join(File.dirname(__FILE__), "../run/example_scenario/#{i}/010_default_feature_reports/default_feature_reports.json")
131
131
 
@@ -134,8 +134,8 @@ scenario_report = URBANopt::Scenario::DefaultReports::ScenarioReport.new({:direc
134
134
  feature_reports_hash = JSON.parse(file.read, symbolize_names: true)
135
135
  end
136
136
 
137
- feature_report = URBANopt::Scenario::DefaultReports::FeatureReport.new(feature_reports_hash)
138
-
137
+ feature_report = URBANopt::Reporting::DefaultReports::FeatureReport.new(feature_reports_hash)
138
+
139
139
  feature_report_dir = File.join(File.dirname(__FILE__), "../run/example_scenario/#{i}")
140
140
  feature_report.directory_name = feature_report_dir
141
141
 
@@ -150,7 +150,7 @@ reopt_post_processor = URBANopt::REopt::REoptPostProcessor.new(scenario_report,
150
150
 
151
151
  #Call REopt Lite with the post processor once on the sceanrio's aggregated load to update the scenario's distributed generation attributes and timeseries CSV.
152
152
  updated_scenario_report = reopt_post_processor.run_scenario_report(scenario_report)
153
-
153
+
154
154
  ```
155
155
 
156
156
  ## Testing
@@ -164,7 +164,7 @@ Next, obtain a developer.nrel.gov API key from the [NREL Developer Network](http
164
164
  Finally, execute:
165
165
 
166
166
  $ bundle install
167
- $ bundle update
167
+ $ bundle update
168
168
  $ bundle exec rake
169
169
 
170
170
 
@@ -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,
@@ -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,7 +28,7 @@
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 'csv'
34
34
  require 'matrix'
@@ -38,7 +38,7 @@ module URBANopt # :nodoc:
38
38
  module REopt # :nodoc:
39
39
  class FeatureReportAdapter
40
40
  ##
41
- # FeatureReportAdapter can convert a URBANopt::Scenario::DefaultReports::FeatureReport into a \REopt Lite posts or update a URBANopt::Scenario::DefaultReports::FeatureReport from a \REopt Lite response.
41
+ # FeatureReportAdapter can convert a URBANopt::Reporting::DefaultReports::FeatureReport into a \REopt Lite posts or update a URBANopt::Reporting::DefaultReports::FeatureReport from a \REopt Lite response.
42
42
  ##
43
43
  # [*parameters:*]
44
44
  ##
@@ -52,7 +52,7 @@ module URBANopt # :nodoc:
52
52
  #
53
53
  # [*parameters:*]
54
54
  #
55
- # * +feature_report+ - _URBANopt::Scenario::DefaultReports::FeatureReport_ - FeatureReport to use in converting the optional +reopt_assumptions_hash+ to a \REopt Lite post. If a +reopt_assumptions_hash+ is not provided, a default post will be updated from this FeatureReport and submitted to the \REopt Lite API.
55
+ # * +feature_report+ - _URBANopt::Reporting::DefaultReports::FeatureReport_ - FeatureReport to use in converting the optional +reopt_assumptions_hash+ to a \REopt Lite post. If a +reopt_assumptions_hash+ is not provided, a default post will be updated from this FeatureReport and submitted to the \REopt Lite API.
56
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 FeatureReport where available (i.e. latitude, roof_squarefeet). Missing optional parameters will be filled in with default values by the API.
57
57
  #
58
58
  # [*return:*] _Hash_ - Returns hash formatted for submittal to the \REopt Lite API
@@ -69,11 +69,11 @@ module URBANopt # :nodoc:
69
69
 
70
70
  # Check FeatureReport has required data
71
71
  requireds_names = ['latitude', 'longitude']
72
- requireds = [feature_report.location.latitude, feature_report.location.longitude]
72
+ requireds = [feature_report.location.latitude_deg, feature_report.location.longitude_deg]
73
73
 
74
74
  if requireds.include?(nil) || requireds.include?(0)
75
75
  requireds.each_with_index do |i, x|
76
- if [nil, 0].include? x
76
+ if [nil].include? x
77
77
  n = requireds_names[i]
78
78
  # @@logger.error("Missing value for #{n} - this is a required input")
79
79
  raise "Missing value for #{n} - this is a required input"
@@ -84,16 +84,16 @@ module URBANopt # :nodoc:
84
84
  reopt_inputs[:Scenario][:description] = description
85
85
 
86
86
  # Parse Location
87
- reopt_inputs[:Scenario][:Site][:latitude] = feature_report.location.latitude
88
- reopt_inputs[:Scenario][:Site][:longitude] = feature_report.location.longitude
87
+ reopt_inputs[:Scenario][:Site][:latitude] = feature_report.location.latitude_deg
88
+ reopt_inputs[:Scenario][:Site][:longitude] = feature_report.location.longitude_deg
89
89
 
90
90
  # Parse Optional FeatureReport metrics
91
- unless feature_report.program.roof_area.nil?
92
- reopt_inputs[:Scenario][:Site][:roof_squarefeet] = feature_report.program.roof_area[:available_roof_area]
91
+ unless feature_report.program.roof_area_sqft.nil?
92
+ reopt_inputs[:Scenario][:Site][:roof_squarefeet] = feature_report.program.roof_area_sqft[:available_roof_area]
93
93
  end
94
94
 
95
- unless feature_report.program.site_area.nil?
96
- reopt_inputs[:Scenario][:Site][:land_acres] = feature_report.program.site_area * 1.0 / 43560 # acres/sqft
95
+ unless feature_report.program.site_area_sqft.nil?
96
+ reopt_inputs[:Scenario][:Site][:land_acres] = feature_report.program.site_area_sqft * 1.0 / 43560 # acres/sqft
97
97
  end
98
98
 
99
99
  unless feature_report.timesteps_per_hour.nil?
@@ -127,19 +127,19 @@ module URBANopt # :nodoc:
127
127
  #
128
128
  # [*parameters:*]
129
129
  #
130
- # * +feature_report+ - _URBANopt::Scenario::DefaultReports::FeatureReport_ - FeatureReport to update from a \REopt Lite reponse hash.
130
+ # * +feature_report+ - _URBANopt::Reporting::DefaultReports::FeatureReport_ - FeatureReport to update from a \REopt Lite reponse hash.
131
131
  # * +reopt_output+ - _Hash_ - A reponse hash from the \REopt Lite API to use in overwriting FeatureReport technology sizes, costs and dispatch strategies.
132
132
  # * +timeseries_csv_path+ - _String_ - Optional. The path to a file at which a new timeseries CSV will be written. If not provided a file is created based on the run_uuid of the \REopt Lite optimization task.
133
133
  #
134
- # [*return:*] _URBANopt::Scenario::DefaultReports::FeatureReport_ - Returns an updated FeatureReport.
134
+ # [*return:*] _URBANopt::Reporting::DefaultReports::FeatureReport_ - Returns an updated FeatureReport.
135
135
  ##
136
- def update_feature_report(feature_report, reopt_output, timeseries_csv_path = nil)
136
+ def update_feature_report(feature_report, reopt_output, timeseries_csv_path=nil, resilience_stats=nil)
137
137
  # Check if the \REopt Lite response is valid
138
138
  if reopt_output['outputs']['Scenario']['status'] != 'optimal'
139
139
  @@logger.info("Warning cannot Feature Report #{feature_report.name} #{feature_report.id} - REopt optimization was non-optimal")
140
140
  return feature_report
141
141
  end
142
-
142
+
143
143
  $ts_per_hour = feature_report.timesteps_per_hour
144
144
  def scale_timeseries(input, ts_per_hr=$ts_per_hour)
145
145
  if input.nil?
@@ -152,7 +152,7 @@ module URBANopt # :nodoc:
152
152
  return input
153
153
  end
154
154
  result = []
155
- input.each do |val|
155
+ input.each do |val|
156
156
  (1..ts_per_hr).each do |x|
157
157
  result.push(val/ts_per_hr.to_f)
158
158
  end
@@ -161,8 +161,8 @@ module URBANopt # :nodoc:
161
161
  end
162
162
 
163
163
  # Update location
164
- feature_report.location.latitude = reopt_output['inputs']['Scenario']['Site']['latitude']
165
- feature_report.location.longitude = reopt_output['inputs']['Scenario']['Site']['longitude']
164
+ feature_report.location.latitude_deg = reopt_output['inputs']['Scenario']['Site']['latitude']
165
+ feature_report.location.longitude_deg = reopt_output['inputs']['Scenario']['Site']['longitude']
166
166
 
167
167
  # Update timeseries csv from \REopt Lite dispatch data
168
168
  feature_report.timesteps_per_hour = reopt_output['inputs']['Scenario']['time_steps_per_hour']
@@ -174,36 +174,50 @@ module URBANopt # :nodoc:
174
174
  feature_report.distributed_generation.year_one_demand_cost_us_dollars = reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['year_one_demand_cost_us_dollars'] || 0
175
175
  feature_report.distributed_generation.year_one_bill_us_dollars = reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['year_one_bill_us_dollars'] || 0
176
176
  feature_report.distributed_generation.total_energy_cost_us_dollars = reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['total_energy_cost_us_dollars'] || 0
177
+ feature_report.distributed_generation.total_demand_cost_us_dollars = reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['total_demand_cost_us_dollars'] || 0
178
+ feature_report.distributed_generation.year_one_energy_cost_bau_us_dollars = reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['year_one_energy_cost_bau_us_dollars'] || 0
179
+ feature_report.distributed_generation.year_one_demand_cost_bau_us_dollars = reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['year_one_demand_cost_bau_us_dollars'] || 0
180
+ feature_report.distributed_generation.year_one_bill_bau_us_dollars = reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['year_one_bill_bau_us_dollars'] || 0
181
+ feature_report.distributed_generation.total_demand_cost_bau_us_dollars = reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['total_demand_cost_bau_us_dollars'] || 0
182
+ feature_report.distributed_generation.total_energy_cost_bau_us_dollars = reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['total_energy_cost_bau_us_dollars'] || 0
183
+ if !resilience_stats.nil?
184
+ feature_report.distributed_generation.resilience_hours_min = resilience_stats['resilience_hours_min']
185
+ feature_report.distributed_generation.resilience_hours_max = resilience_stats['resilience_hours_max']
186
+ feature_report.distributed_generation.resilience_hours_avg = resilience_stats['resilience_hours_avg']
187
+ feature_report.distributed_generation.probs_of_surviving = resilience_stats['probs_of_surviving']
188
+ feature_report.distributed_generation.probs_of_surviving_by_month = resilience_stats['probs_of_surviving_by_month']
189
+ feature_report.distributed_generation.probs_of_surviving_by_hour_of_the_day = resilience_stats['probs_of_surviving_by_hour_of_the_day']
190
+ end
177
191
 
178
192
  if reopt_output['outputs']['Scenario']['Site']['PV'].class == Hash
179
193
  reopt_output['outputs']['Scenario']['Site']['PV'] = [reopt_output['outputs']['Scenario']['Site']['PV']]
180
194
  elsif reopt_output['outputs']['Scenario']['Site']['PV'].nil?
181
195
  reopt_output['outputs']['Scenario']['Site']['PV'] = []
182
196
  end
183
-
184
- reopt_output['outputs']['Scenario']['Site']['PV'].each_with_index do |pv, i|
185
- feature_report.distributed_generation.add_tech 'solar_pv', URBANopt::Scenario::DefaultReports::SolarPV.new( {size_kw: (pv['size_kw'] || 0), id: i })
197
+
198
+ reopt_output['outputs']['Scenario']['Site']['PV'].each_with_index do |pv, i|
199
+ feature_report.distributed_generation.add_tech 'solar_pv', URBANopt::Reporting::DefaultReports::SolarPV.new( {size_kw: (pv['size_kw'] || 0), id: i })
186
200
  end
187
201
 
188
202
  wind = reopt_output['outputs']['Scenario']['Site']['Wind']
189
203
  if !wind['size_kw'].nil? and wind['size_kw'] != 0
190
- feature_report.distributed_generation.add_tech 'wind', URBANopt::Scenario::DefaultReports::Wind.new( {size_kw: (wind['size_kw'] || 0) })
204
+ feature_report.distributed_generation.add_tech 'wind', URBANopt::Reporting::DefaultReports::Wind.new( {size_kw: (wind['size_kw'] || 0) })
191
205
  end
192
206
 
193
207
  generator = reopt_output['outputs']['Scenario']['Site']['Generator']
194
208
  if !generator['size_kw'].nil? and generator['size_kw'] != 0
195
- feature_report.distributed_generation.add_tech 'generator', URBANopt::Scenario::DefaultReports::Generator.new( {size_kw: (generator['size_kw'] || 0) })
209
+ feature_report.distributed_generation.add_tech 'generator', URBANopt::Reporting::DefaultReports::Generator.new( {size_kw: (generator['size_kw'] || 0) })
196
210
  end
197
211
 
198
212
  storage = reopt_output['outputs']['Scenario']['Site']['Storage']
199
213
  if !storage['size_kw'].nil? and storage['size_kw'] != 0
200
- feature_report.distributed_generation.add_tech 'storage', URBANopt::Scenario::DefaultReports::Storage.new( {size_kwh: (storage['size_kwh'] || 0), size_kw: (storage['size_kw'] || 0) })
214
+ feature_report.distributed_generation.add_tech 'storage', URBANopt::Reporting::DefaultReports::Storage.new( {size_kwh: (storage['size_kwh'] || 0), size_kw: (storage['size_kw'] || 0) })
201
215
  end
202
-
216
+
203
217
  generation_timeseries_kwh = Matrix[[0] * (8760 * feature_report.timesteps_per_hour)]
204
218
 
205
219
  unless reopt_output['outputs']['Scenario']['Site']['PV'].nil?
206
- reopt_output['outputs']['Scenario']['Site']['PV'].each do |pv|
220
+ reopt_output['outputs']['Scenario']['Site']['PV'].each do |pv|
207
221
  if (pv['size_kw'] || 0) > 0
208
222
  if !pv['year_one_power_production_series_kw'].nil?
209
223
  generation_timeseries_kwh += Matrix[pv['year_one_power_production_series_kw']]
@@ -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,80 @@ 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
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
+ raise "Error from REopt API - #{data['Error']}"
226
+ end
227
+
135
228
  ##
136
229
  # Completes a \REopt Lite optimization. From a formatted hash, an optimization task is submitted to the API.
137
230
  # Results are polled at 5 second interval until they are ready or an error is returned from the API. Results
@@ -157,13 +250,13 @@ module URBANopt # :nodoc:
157
250
  http.use_ssl = true
158
251
  end
159
252
  request = Net::HTTP::Post.new(@uri_submit, header)
160
- request.body = reopt_input.to_json
253
+ request.body = ::JSON.generate(reopt_input, allow_nan: true)
161
254
 
162
255
  # Send the request
163
256
  response = make_request(http, request)
164
257
 
165
258
  # Get UUID
166
- run_uuid = JSON.parse(response.body)['run_uuid']
259
+ run_uuid = JSON.parse(response.body, allow_nan:true)['run_uuid']
167
260
 
168
261
  if File.directory? filename
169
262
  if run_uuid.nil?
@@ -176,10 +269,12 @@ module URBANopt # :nodoc:
176
269
  @@logger.info("REopt results saved to #{filename}")
177
270
  end
178
271
 
272
+ text = ::JSON.generate(response.body, allow_nan: true)
179
273
  if response.code != '201'
180
- File.open(filename, 'w') do |f|
181
- f.write(response.body)
274
+ File.open(filename, 'w+') do |f|
275
+ f.puts(text)
182
276
  end
277
+ @@logger.info("Cannot write - #{filename}")
183
278
  raise "Error in REopt optimization post - see #{filename}"
184
279
  end
185
280
 
@@ -195,7 +290,9 @@ module URBANopt # :nodoc:
195
290
 
196
291
  while status == 'Optimizing...'
197
292
  response = make_request(http, request)
198
- data = JSON.parse(response.body)
293
+
294
+ data = JSON.parse(response.body, allow_nan:true)
295
+
199
296
  if data['outputs']['Scenario']['Site']['PV'].kind_of?(Array)
200
297
  pv_sizes = 0
201
298
  data['outputs']['Scenario']['Site']['PV'].each do |x|
@@ -216,7 +313,7 @@ module URBANopt # :nodoc:
216
313
  while (_tries < _max_retry) && check_complete
217
314
  sleep 1
218
315
  response = make_request(http, request)
219
- data = JSON.parse(response.body)
316
+ data = JSON.parse(response.body, allow_nan:true)
220
317
  if data['outputs']['Scenario']['Site']['PV'].kind_of?(Array)
221
318
  pv_sizes = 0
222
319
  data['outputs']['Scenario']['Site']['PV'].each do |x|
@@ -230,8 +327,14 @@ module URBANopt # :nodoc:
230
327
  _tries += 1
231
328
  end
232
329
 
233
- File.open(filename, 'w') do |f|
234
- f.write(data.to_json)
330
+ data = JSON.parse(response.body)
331
+ text = ::JSON.generate(data, allow_nan: true)
332
+ begin
333
+ File.open(filename, 'w+') do |f|
334
+ f.puts(text)
335
+ end
336
+ rescue
337
+ @@logger.info("Cannot write - #{filename}")
235
338
  end
236
339
 
237
340
  if status == 'optimal'