Class: URBANopt::REopt::REoptAPI
- Inherits:
-
Object
- Object
- URBANopt::REopt::REoptAPI
- Defined in:
- lib/urbanopt/reopt/reopt_api.rb
Instance Method Summary collapse
-
#check_connection(data) ⇒ Object
Checks if a optimization task can be submitted to the REopt API.
-
#initialize(api_key = nil) ⇒ REoptAPI
constructor
REoptAPI manages submitting optimization tasks to the REopt API and receiving results.
- #make_request(http, req, max_tries = 3) ⇒ Object
-
#reopt_request(reopt_input, filename) ⇒ Object
Completes a REopt optimization.
-
#resilience_request(run_uuid, filename, reopt_input, erp_assumptions_file) ⇒ Object
Completes a REopt optimization.
-
#uri_resilience(run_uuid) ⇒ Object
URL of the resilience statistics end point for a specific optimization task.
-
#uri_results(run_uuid) ⇒ Object
URL of the results end point for a specific optimization task.
Constructor Details
#initialize(api_key = nil) ⇒ REoptAPI
REoptAPI manages submitting optimization tasks to the REopt API and receiving results. Results can be sourced from the production REopt API with an API key or from custom endpoints via the REOPT_BASE_URL environment variable.
- parameters:
-
api_key- String - API key used to access the REopt API. Required only for developer.nlr.gov and developer.nlr.gov endpoints. Obtain from developer.nlr.gov/signup/
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
# File 'lib/urbanopt/reopt/reopt_api.rb', line 28 def initialize(api_key = nil) # Handle API key validation for official developer URLs if [nil, '', '<insert your key here>'].include? api_key if [nil, '', '<insert your key here>'].include? DEVELOPER_API_KEY # Check if we need an API key based on the URL that will be used url_config_test = URLConfig.new if url_config_test.requires_api_key? raise 'A developer.nlr.gov API key is required. Please see https://developer.nlr.gov/signup/ then update the file developer_api_key.rb' end else api_key = DEVELOPER_API_KEY end end # Initialize URL configuration @url_config = URLConfig.new(api_key: api_key) # Cache frequently used URIs @uri_submit = @url_config.submit_uri @uri_submit_outagesimjob = @url_config.erp_submit_uri # initialize @@logger @@logger ||= URBANopt::REopt.reopt_logger end |
Instance Method Details
#check_connection(data) ⇒ Object
Checks if a optimization task can be submitted to the REopt API
- parameters:
-
data- Hash - Default REopt formatted post containing at least all the required parameters.
- return:
-
Bool - Returns true if the post succeeds. Otherwise returns false.
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/urbanopt/reopt/reopt_api.rb', line 134 def check_connection(data) header = { 'Content-Type' => 'application/json' } http = Net::HTTP.new(@uri_submit.host, @uri_submit.port) @url_config.configure_ssl(http) post_request = Net::HTTP::Post.new(@uri_submit, header) post_request.body = ::JSON.generate(data, allow_nan: true) # Send the request response = make_request(http, post_request) if !response.is_a?(Net::HTTPSuccess) @@logger.error('Check_connection Failed') raise 'Check_connection Failed' end return true end |
#make_request(http, req, max_tries = 3) ⇒ Object
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
# File 'lib/urbanopt/reopt/reopt_api.rb', line 82 def make_request(http, req, max_tries = 3) result = nil tries = 0 while tries < max_tries begin result = http.request(req) # Result codes sourced from https://developer.nlr.gov/docs/errors/ if result.code == '429' @@logger.fatal('Exceeded the REopt API limit of 300 requests per hour') puts 'Using the URBANopt CLI to submit a Scenario optimization counts as one request per scenario' puts 'Using the URBANopt CLI to submit a Feature optimization counts as one request per feature' abort('Please wait and try again once the time period has elapsed. The URBANopt CLI flag --reopt-keep-existing can be used to resume the optimization') elsif result.code == '404' @@logger.info("REOpt is still calculating. We'll give it a moment and check again") sleep 15 tries += 1 next elsif (result.code != '201') && (result.code != '200') # Anything in the 200s is success @@logger.warn("REopt has returned a '#{result.code}' status code. Visit https://developer.nlr.gov/docs/errors/ for more status code information") # display error messages json_res = JSON.parse(result.body, allow_nan: true) json_res['messages'].delete('warnings') if json_res['messages']['warnings'] json_res['messages'].delete('Deprecations') if json_res['messages']['Deprecations'] if json_res['messages'] @@logger.error("MESSAGES: #{json_res['messages']}") end end tries = max_tries rescue StandardError => e @@logger.error("error from REopt API: #{e}") if tries + 1 < max_tries @@logger.debug('trying again...') else @@logger.warn('max tries reached!') return result end tries += 1 end end return result end |
#reopt_request(reopt_input, filename) ⇒ Object
Completes a REopt optimization. From a formatted hash, an optimization task is submitted to the API. Results are polled at 5 second interval until they are ready or an error is returned from the API. Results are written to disk.
- parameters:
-
reopt_input- Hash - REopt formatted post containing at least required parameters. -
filename- String - Path to file that will be created containing the full REopt response.
- return:
-
Bool - Returns true if the post succeeds. Otherwise returns false.
277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 |
# File 'lib/urbanopt/reopt/reopt_api.rb', line 277 def reopt_request(reopt_input, filename) description = reopt_input[:description] @@logger.info("Submitting #{description} to REopt API") # Format the request header = { 'Content-Type' => 'application/json' } http = Net::HTTP.new(@uri_submit.host, @uri_submit.port) @url_config.configure_ssl(http) post_request = Net::HTTP::Post.new(@uri_submit, header) post_request.body = ::JSON.generate(reopt_input, allow_nan: true) # Send the request response = make_request(http, post_request) if !response.is_a?(Net::HTTPSuccess) @@logger.error('make_request Failed') raise 'REopt connection Failed' end # Get UUID run_uuid = JSON.parse(response.body, allow_nan: true)['run_uuid'] if File.directory? filename if run_uuid.nil? run_uuid = 'error' end if run_uuid.downcase.include? 'error' run_uuid = "error#{SecureRandom.uuid}" end filename = File.join(filename, "#{description}_#{run_uuid}.json") @@logger.info("REopt results saved to #{filename}") end text = JSON.parse(response.body, allow_nan: true) if response.code != '201' File.open(filename, 'w+') do |f| f.puts(JSON.pretty_generate(text)) end raise "Error in REopt optimization post - see #{filename}" end # Poll results until ready or error occurs status = 'Optimizing...' uri = uri_results(run_uuid) http = Net::HTTP.new(uri.host, uri.port) @url_config.configure_ssl(http) get_request = Net::HTTP::Get.new(uri.request_uri) counter = 0 while status == 'Optimizing...' response = make_request(http, get_request) data = JSON.parse(response.body, allow_nan: true) if !data['outputs']['PV'] pv_sizes = 0 sizes = 0 else # there should be results in there now if data['outputs']['PV'].is_a?(Array) pv_sizes = 0 data['outputs']['PV'].each do |x| pv_sizes += x['size_kw'].to_f end else data['outputs'].each do |energy_source, data| if data.is_a?(Hash) && data.key?('size_kw') @@logger.debug("#{energy_source}: size_kw = #{data['size_kw'].to_f}") sizes += data['size_kw'].to_f end end end end status = data['status'] @@logger.debug("STATUS: #{status}") if status == 'error' puts "response.code: #{response.code}" puts "message: #{response.}" = data['messages']['errors'] raise "Error from REopt API - #{}" end sleep 15 end max_retry = 5 tries = 0 check_complete = sizes == 0 # I don't know what this line does: # (((data['outputs'] && data['outputs'].key?('Financial') && data['outputs']['Financial']['npv']) || 0) > 0) if check_complete @@logger.info('sizes are 0...checking optimization complete') while (tries < max_retry) && check_complete sleep 3 response = make_request(http, get_request) data = JSON.parse(response.body, allow_nan: true) if data['outputs'].key?('PV') && data['outputs']['PV'].is_a?(Array) pv_sizes = 0 data['outputs']['PV'].each do |x| pv_sizes += x['size_kw'].to_f end else data['outputs'].each do |energy_source, data| if data.is_a?(Hash) && data.key?('size_kw') @@logger.debug("#{energy_source}: size_kw = #{data['size_kw'].to_f}") sizes += data['size_kw'].to_f end end end # I don't understand this line fully: #(check_complete = sizes == 0) && ((data['outputs']['Financial']['npv'] || 0) > 0) check_complete = sizes == 0 tries += 1 end end @@logger.info('REopt optimization complete and processed') data = JSON.parse(response.body, allow_nan: true) text = JSON.pretty_generate(data) begin File.open(filename, 'w+') do |f| f.puts(text) end rescue StandardError @@logger.error("Cannot write - #{filename}") end if status == 'optimal' return { 'data' => data, 'run_uuid' => run_uuid } end = data['messages']['error'] raise "Error from REopt API - #{}" end |
#resilience_request(run_uuid, filename, reopt_input, erp_assumptions_file) ⇒ Object
Completes a REopt optimization. From a formatted hash, an optimization task is submitted to the API. Results are polled at 5 second interval until they are ready or an error is returned from the API. Results are written to disk.
- parameters:
-
reopt_input- Hash - REopt formatted post containing at least required parameters. -
filename- String - Path to file that will be created containing the full REopt response.
- return:
-
Bool - Returns true if the post succeeds. Otherwise returns false.
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 |
# File 'lib/urbanopt/reopt/reopt_api.rb', line 165 def resilience_request(run_uuid, filename, reopt_input, erp_assumptions_file) if File.directory? filename if run_uuid.nil? run_uuid = 'error' end if run_uuid.downcase.include? 'error' run_uuid = "error#{SecureRandom.uuid}" end filename = File.join(filename, "#{run_uuid}_resilience.json") # Save the REopt resilience results to a file @@logger.info("REopt results saved to #{filename}") end # Add info message to logger @@logger.info("Submitting Resilience Statistics job for #{run_uuid}") # Format HTTP request header = { 'Content-Type' => 'application/json' } http = Net::HTTP.new(@uri_submit_outagesimjob.host, @uri_submit_outagesimjob.port) @url_config.configure_ssl(http) # POST to erp endpoint post_request = Net::HTTP::Post.new(@uri_submit_outagesimjob, header) post = erp_assumptions_file post["reopt_run_uuid"] = run_uuid post_request.body = ::JSON.generate(post, allow_nan: true) # Send the request submit_response = make_request(http, post_request) if !submit_response.is_a?(Net::HTTPSuccess) @@logger.error('make_request Failed') raise 'REopt connection Failed' end @@logger.debug(submit_response.body) # Get <erp_run_uuid> erp_run_uuid = JSON.parse(submit_response.body, allow_nan: true)['run_uuid'] if File.directory? filename if erp_run_uuid.nil? erp_run_uuid = 'error' end if erp_run_uuid.downcase.include? 'error' erp_run_uuid = "error#{SecureRandom.uuid}" end filename = File.join(filename, "#{description}_#{erp_run_uuid}.json") @@logger.info("REopt ERP results saved to #{filename}") end text = JSON.parse(submit_response.body, allow_nan: true) if submit_response.code != '201' File.open(filename, 'w+') do |f| f.puts(JSON.pretty_generate(text)) end raise "Error in REopt optimization post - see #{filename}" end # Fetch Results, pass on <erp_run_uuid> uri = uri_resilience(erp_run_uuid) http = Net::HTTP.new(uri.host, uri.port) @url_config.configure_ssl(http) # Wait for the REopt API before attempting to GET results sleep 30 get_request = Net::HTTP::Get.new(uri.request_uri) response = make_request(http, get_request, 8) # Set a limit on retries when 404s are returned from REopt API elapsed_time = 0 max_elapsed_time = 60 * 15 # If database still hasn't updated, wait longer and try again while (elapsed_time < max_elapsed_time) && (response && response.code == '404') response = make_request(http, get_request) @@logger.warn('GET request was too fast for REOpt-API. Retrying...') elapsed_time += 15 sleep 15 end data = JSON.parse(response.body, allow_nan: true) text = JSON.pretty_generate(data) begin File.open(filename, 'w+') do |f| f.puts(text) end rescue StandardError => e @@logger.error("Cannot write - #{filename}") @@logger.error("ERROR: #{e}") end if response.code == '200' return data end @@logger.error("Error from REopt API - #{data['Error']}") return {} end |
#uri_resilience(run_uuid) ⇒ Object
URL of the resilience statistics end point for a specific optimization task
- parameters:
-
run_uuid- String - Resilience statistics for a unique run_uuid obtained from the REopt job submittal URL for a specific optimization task.
- return:
-
URI - Returns URI object for use in calling the REopt resilience statistics endpoint for a specific optimization task.
78 79 80 |
# File 'lib/urbanopt/reopt/reopt_api.rb', line 78 def uri_resilience(run_uuid) # :nodoc: @url_config.erp_results_uri(run_uuid) end |
#uri_results(run_uuid) ⇒ Object
URL of the results end point for a specific optimization task
- parameters:
-
run_uuid- String - Unique run_uuid obtained from the REopt job submittal URL for a specific optimization task.
- return:
-
URI - Returns URI object for use in calling the REopt results endpoint for a specific optimization task.
64 65 66 |
# File 'lib/urbanopt/reopt/reopt_api.rb', line 64 def uri_results(run_uuid) # :nodoc: @url_config.results_uri(run_uuid) end |