From 49af53a8d84f2718b75f4b77d49209a041903cff Mon Sep 17 00:00:00 2001 From: Hallie Dunham <70401017+hdunham@users.noreply.github.com> Date: Thu, 13 Jun 2024 15:20:50 -0600 Subject: [PATCH 01/24] try to set reopt_version in APIMeta automatically --- julia_src/http.jl | 7 +++++-- reoptjl/api.py | 2 +- reoptjl/models.py | 1 + reoptjl/src/run_jump_model.py | 2 ++ 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/julia_src/http.jl b/julia_src/http.jl index 8d3a38eaa..ea7627839 100644 --- a/julia_src/http.jl +++ b/julia_src/http.jl @@ -22,13 +22,13 @@ end function reopt(req::HTTP.Request) d = JSON.parse(String(req.body)) error_response = Dict() - settings = d["Settings"] if !isempty(get(d, "api_key", "")) ENV["NREL_DEVELOPER_API_KEY"] = pop!(d, "api_key") else ENV["NREL_DEVELOPER_API_KEY"] = test_nrel_developer_api_key delete!(d, "api_key") end + settings = d["Settings"] solver_name = get(settings, "solver_name", "HiGHS") if solver_name == "Xpress" && !(xpress_installed=="True") solver_name = "HiGHS" @@ -142,7 +142,8 @@ function reopt(req::HTTP.Request) @info "REopt model solved with status $(results["status"])." if results["status"] == "error" response = Dict( - "results" => results + "results" => results, + "reopt_version" => pkgversion(reoptjl) ) if !isempty(inputs_with_defaults_set_in_julia) response["inputs_with_defaults_set_in_julia"] = inputs_with_defaults_set_in_julia @@ -151,12 +152,14 @@ function reopt(req::HTTP.Request) else response = Dict( "results" => results, + "reopt_version" => pkgversion(reoptjl), "inputs_with_defaults_set_in_julia" => inputs_with_defaults_set_in_julia ) return HTTP.Response(200, JSON.json(response)) end else @info "An error occured in the Julia code." + error_response["reopt_version"] = pkgversion(reoptjl) return HTTP.Response(500, JSON.json(error_response)) end end diff --git a/reoptjl/api.py b/reoptjl/api.py index bd90ef685..480460721 100644 --- a/reoptjl/api.py +++ b/reoptjl/api.py @@ -98,7 +98,7 @@ def obj_create(self, bundle, **kwargs): meta = { "run_uuid": run_uuid, "api_version": 3, - "reopt_version": "0.47.1", + # "reopt_version": "0.47.1", "status": "Validating..." } bundle.data.update({"APIMeta": meta}) diff --git a/reoptjl/models.py b/reoptjl/models.py index e9419054b..7b6aeb964 100644 --- a/reoptjl/models.py +++ b/reoptjl/models.py @@ -176,6 +176,7 @@ class APIMeta(BaseModel, models.Model): created = models.DateTimeField(auto_now_add=True) reopt_version = models.TextField( blank=True, + null=True, default="", help_text="Version number of the Julia package for REopt that is used to solve the problem." ) diff --git a/reoptjl/src/run_jump_model.py b/reoptjl/src/run_jump_model.py index 6499068ff..34ab85812 100644 --- a/reoptjl/src/run_jump_model.py +++ b/reoptjl/src/run_jump_model.py @@ -69,6 +69,7 @@ def run_jump_model(run_uuid): if response.status_code == 500: raise REoptFailedToStartError(task=name, message=response_json["error"], run_uuid=run_uuid, user_uuid=user_uuid) results = response_json["results"] + reopt_version = response_json["reopt_version"] if results["status"].strip().lower() != "error": inputs_with_defaults_set_in_julia = response_json["inputs_with_defaults_set_in_julia"] time_dict["pyjulia_run_reopt_seconds"] = time.time() - t_start @@ -107,6 +108,7 @@ def run_jump_model(run_uuid): profiler.profileEnd() # TODO save profile times + APIMeta.objects.filter(run_uuid=run_uuid).update(reopt_version=reopt_version) if status.strip().lower() != 'error': update_inputs_in_database(inputs_with_defaults_set_in_julia, run_uuid) process_results(results, run_uuid) From eb40045be0bba78dc50f61a3530d110bbd5c130f Mon Sep 17 00:00:00 2001 From: Hallie Dunham <70401017+hdunham@users.noreply.github.com> Date: Thu, 13 Jun 2024 15:32:28 -0600 Subject: [PATCH 02/24] simplify logic constructing dict --- julia_src/http.jl | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/julia_src/http.jl b/julia_src/http.jl index ea7627839..a496fdc51 100644 --- a/julia_src/http.jl +++ b/julia_src/http.jl @@ -140,21 +140,17 @@ function reopt(req::HTTP.Request) if isempty(error_response) @info "REopt model solved with status $(results["status"])." + response = Dict( + "results" => results, + "reopt_version" => pkgversion(reoptjl) + ) if results["status"] == "error" - response = Dict( - "results" => results, - "reopt_version" => pkgversion(reoptjl) - ) if !isempty(inputs_with_defaults_set_in_julia) response["inputs_with_defaults_set_in_julia"] = inputs_with_defaults_set_in_julia end return HTTP.Response(400, JSON.json(response)) else - response = Dict( - "results" => results, - "reopt_version" => pkgversion(reoptjl), - "inputs_with_defaults_set_in_julia" => inputs_with_defaults_set_in_julia - ) + response["inputs_with_defaults_set_in_julia"] = inputs_with_defaults_set_in_julia return HTTP.Response(200, JSON.json(response)) end else From a69755b16188fb80eab7bd0d4e13114f17db9e14 Mon Sep 17 00:00:00 2001 From: Hallie Dunham <70401017+hdunham@users.noreply.github.com> Date: Thu, 13 Jun 2024 15:32:40 -0600 Subject: [PATCH 03/24] convert reopt version to string --- julia_src/http.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/julia_src/http.jl b/julia_src/http.jl index a496fdc51..7629c2ae2 100644 --- a/julia_src/http.jl +++ b/julia_src/http.jl @@ -142,7 +142,7 @@ function reopt(req::HTTP.Request) @info "REopt model solved with status $(results["status"])." response = Dict( "results" => results, - "reopt_version" => pkgversion(reoptjl) + "reopt_version" => string(pkgversion(reoptjl)) ) if results["status"] == "error" if !isempty(inputs_with_defaults_set_in_julia) @@ -155,7 +155,7 @@ function reopt(req::HTTP.Request) end else @info "An error occured in the Julia code." - error_response["reopt_version"] = pkgversion(reoptjl) + error_response["reopt_version"] = string(pkgversion(reoptjl)) return HTTP.Response(500, JSON.json(error_response)) end end From 6a499e36a1de356cb7214ba4b62ca5c5db7f1314 Mon Sep 17 00:00:00 2001 From: Hallie Dunham <70401017+hdunham@users.noreply.github.com> Date: Thu, 13 Jun 2024 15:33:26 -0600 Subject: [PATCH 04/24] add reopt version number check to test --- reoptjl/test/test_job_endpoint.py | 1 + 1 file changed, 1 insertion(+) diff --git a/reoptjl/test/test_job_endpoint.py b/reoptjl/test/test_job_endpoint.py index 5ac8f3dc9..407e1333e 100644 --- a/reoptjl/test/test_job_endpoint.py +++ b/reoptjl/test/test_job_endpoint.py @@ -25,6 +25,7 @@ def test_multiple_outages(self): run_uuid = r.get('run_uuid') resp = self.api_client.get(f'/v3/job/{run_uuid}/results') r = json.loads(resp.content) + self.assertEqual(r["reopt_version"], "0.47.1") results = r["outputs"] self.assertEqual(np.array(results["Outages"]["unserved_load_series_kw"]).shape, (1,2,5)) self.assertEqual(np.array(results["Outages"]["generator_fuel_used_per_outage_gal"]).shape, (1,2)) From 5112184f3d7d1d1e56cb548348d7b46e52e85b4e Mon Sep 17 00:00:00 2001 From: Hallie Dunham <70401017+hdunham@users.noreply.github.com> Date: Fri, 14 Jun 2024 12:44:32 -0600 Subject: [PATCH 05/24] set reopt_version in ERPMeta automatically --- julia_src/http.jl | 2 ++ resilience_stats/api.py | 5 +++-- resilience_stats/models.py | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/julia_src/http.jl b/julia_src/http.jl index 7629c2ae2..21acb021c 100644 --- a/julia_src/http.jl +++ b/julia_src/http.jl @@ -168,9 +168,11 @@ function erp(req::HTTP.Request) results = Dict() try results = reoptjl.backup_reliability(erp_inputs) + results["reopt_version"] = string(pkgversion(reoptjl)) catch e @error "Something went wrong in the ERP Julia code!" exception=(e, catch_backtrace()) error_response["error"] = sprint(showerror, e) + error_response["reopt_version"] = string(pkgversion(reoptjl)) end GC.gc() if isempty(error_response) diff --git a/resilience_stats/api.py b/resilience_stats/api.py index fbe66104b..c92a9f9d2 100644 --- a/resilience_stats/api.py +++ b/resilience_stats/api.py @@ -62,7 +62,7 @@ def obj_create(self, bundle, **kwargs): meta_dict = { "run_uuid": erp_run_uuid, - "reopt_version": "0.45.0", + # "reopt_version": "0.45.0", "status": "Validating..." } @@ -450,7 +450,8 @@ def process_erp_results(results: dict, run_uuid: str) -> None: #TODO: get success or error status from julia meta = ERPMeta.objects.get(run_uuid=run_uuid) meta.status = 'Completed' #results.get("status") - meta.save(update_fields=['status']) + meta.reopt_version = results.pop("reopt_version") + meta.save(update_fields=['status','reopt_version']) ERPOutputs.create(meta=meta, **results).save() diff --git a/resilience_stats/models.py b/resilience_stats/models.py index f3aeb4b91..7da62888d 100644 --- a/resilience_stats/models.py +++ b/resilience_stats/models.py @@ -84,6 +84,7 @@ class ERPMeta(BaseModel, models.Model): created = models.DateTimeField(auto_now_add=True) reopt_version = models.TextField( blank=True, + null=True, default="", help_text="Version number of the REopt Julia package that is used to calculate reliability." ) From 4bc00de40304ddca4f4b055af3afe8069c87bb04 Mon Sep 17 00:00:00 2001 From: Hallie Dunham <70401017+hdunham@users.noreply.github.com> Date: Fri, 14 Jun 2024 12:46:47 -0600 Subject: [PATCH 06/24] check reopt_version in erp test --- resilience_stats/tests/test_erp.py | 1 + 1 file changed, 1 insertion(+) diff --git a/resilience_stats/tests/test_erp.py b/resilience_stats/tests/test_erp.py index fcd284360..8181f4290 100644 --- a/resilience_stats/tests/test_erp.py +++ b/resilience_stats/tests/test_erp.py @@ -66,6 +66,7 @@ def test_erp_long_duration_battery(self): r_sim = json.loads(resp.content) erp_run_uuid = r_sim.get('run_uuid') resp = self.get_results_sim(erp_run_uuid) + self.assertEqual(resp["reopt_version"], "0.47.1") results_sim = json.loads(resp.content)["outputs"] expected_result = ([1]*79)+[0.999543,0.994178,0.9871,0.97774,0.965753,0.949429,0.926712,0.899543,0.863584,0.826712,0.785616,0.736416,0.683105,0.626256,0.571005,0.519064,0.47226,0.429909,0.391553,0.357306,0] From 04182ceefa0eaa3bcd04b66ce362042910b4bd41 Mon Sep 17 00:00:00 2001 From: Hallie Dunham <70401017+hdunham@users.noreply.github.com> Date: Fri, 14 Jun 2024 12:48:30 -0600 Subject: [PATCH 07/24] comment out extra tests --- reoptjl/test/test_http_endpoints.py | 700 +++++++++--------- reoptjl/test/test_job_endpoint.py | 534 ++++++------- reoptjl/test/test_validator.py | 630 ++++++++-------- resilience_stats/tests/test_erp.py | 234 +++--- .../tests/test_new_post_endpoint.py | 34 +- .../tests/test_resilience_stats.py | 688 ++++++++--------- 6 files changed, 1410 insertions(+), 1410 deletions(-) diff --git a/reoptjl/test/test_http_endpoints.py b/reoptjl/test/test_http_endpoints.py index 760c10775..ae389c988 100644 --- a/reoptjl/test/test_http_endpoints.py +++ b/reoptjl/test/test_http_endpoints.py @@ -9,373 +9,373 @@ class TestHTTPEndpoints(ResourceTestCaseMixin, TestCase): - def test_chp_defaults(self): + # def test_chp_defaults(self): - inputs = {"hot_water_or_steam": "hot_water", - "avg_boiler_fuel_load_mmbtu_per_hour": 28.0 - } + # inputs = {"hot_water_or_steam": "hot_water", + # "avg_boiler_fuel_load_mmbtu_per_hour": 28.0 + # } - # Direct call of the http.jl endpoint /chp_defaults - julia_host = os.environ.get('JULIA_HOST', "julia") - response = requests.get("http://" + julia_host + ":8081/chp_defaults/", json=inputs) - http_response = response.json() + # # Direct call of the http.jl endpoint /chp_defaults + # julia_host = os.environ.get('JULIA_HOST', "julia") + # response = requests.get("http://" + julia_host + ":8081/chp_defaults/", json=inputs) + # http_response = response.json() - # Call to the django view endpoint /chp_defaults which calls the http.jl endpoint - resp = self.api_client.get(f'/v3/chp_defaults', data=inputs) - view_response = json.loads(resp.content) + # # Call to the django view endpoint /chp_defaults which calls the http.jl endpoint + # resp = self.api_client.get(f'/v3/chp_defaults', data=inputs) + # view_response = json.loads(resp.content) - mismatch = [] - for k, v in http_response["default_inputs"].items(): - if v != view_response["default_inputs"][k]: - mismatch.append(k) + # mismatch = [] + # for k, v in http_response["default_inputs"].items(): + # if v != view_response["default_inputs"][k]: + # mismatch.append(k) - self.assertEqual(mismatch, []) - - # Check the endpoint logic with the expected selection - self.assertEqual(http_response["prime_mover"], "combustion_turbine") - self.assertEqual(http_response["size_class"], 2) - self.assertGreater(http_response["chp_elec_size_heuristic_kw"], 3500.0) - self.assertEqual(http_response["default_inputs"]["federal_itc_fraction"], 0.3) - - inputs = { - "prime_mover": "micro_turbine", - "avg_electric_load_kw": 885.0247784246575, - "max_electric_load_kw": 1427.334, - "is_electric_only": "true" - } - - # Direct call of the http.jl endpoint /chp_defaults - julia_host = os.environ.get('JULIA_HOST', "julia") - response = requests.get("http://" + julia_host + ":8081/chp_defaults/", json=inputs) - http_response = response.json() - - # Check the endpoint logic with the expected selection - self.assertEqual(http_response["default_inputs"]["federal_itc_fraction"], 0.3) - - inputs = { - "prime_mover": "combustion_turbine", - "size_class": 4, - "is_electric_only": "true", - "avg_electric_load_kw": 885.0247784246575, - "max_electric_load_kw": 1427.334 - } - - # Direct call of the http.jl endpoint /chp_defaults - julia_host = os.environ.get('JULIA_HOST', "julia") - response = requests.get("http://" + julia_host + ":8081/chp_defaults/", json=inputs) - http_response = response.json() - - # Check the endpoint logic with the expected selection - self.assertEqual(http_response["default_inputs"]["federal_itc_fraction"], 0.0) - - inputs = { - "prime_mover": "recip_engine", - "size_class": 4, - "is_electric_only": "true", - "avg_electric_load_kw": 885.0247784246575, - "max_electric_load_kw": 1427.334 - } - - # Call to the django view endpoint /chp_defaults which calls the http.jl endpoint - resp = self.api_client.get(f'/v3/chp_defaults', data=inputs) - view_response = json.loads(resp.content) - - # Check the endpoint logic with the expected selection - self.assertEqual(http_response["default_inputs"]["federal_itc_fraction"], 0.0) + # self.assertEqual(mismatch, []) + + # # Check the endpoint logic with the expected selection + # self.assertEqual(http_response["prime_mover"], "combustion_turbine") + # self.assertEqual(http_response["size_class"], 2) + # self.assertGreater(http_response["chp_elec_size_heuristic_kw"], 3500.0) + # self.assertEqual(http_response["default_inputs"]["federal_itc_fraction"], 0.3) + + # inputs = { + # "prime_mover": "micro_turbine", + # "avg_electric_load_kw": 885.0247784246575, + # "max_electric_load_kw": 1427.334, + # "is_electric_only": "true" + # } + + # # Direct call of the http.jl endpoint /chp_defaults + # julia_host = os.environ.get('JULIA_HOST', "julia") + # response = requests.get("http://" + julia_host + ":8081/chp_defaults/", json=inputs) + # http_response = response.json() + + # # Check the endpoint logic with the expected selection + # self.assertEqual(http_response["default_inputs"]["federal_itc_fraction"], 0.3) + + # inputs = { + # "prime_mover": "combustion_turbine", + # "size_class": 4, + # "is_electric_only": "true", + # "avg_electric_load_kw": 885.0247784246575, + # "max_electric_load_kw": 1427.334 + # } + + # # Direct call of the http.jl endpoint /chp_defaults + # julia_host = os.environ.get('JULIA_HOST', "julia") + # response = requests.get("http://" + julia_host + ":8081/chp_defaults/", json=inputs) + # http_response = response.json() + + # # Check the endpoint logic with the expected selection + # self.assertEqual(http_response["default_inputs"]["federal_itc_fraction"], 0.0) + + # inputs = { + # "prime_mover": "recip_engine", + # "size_class": 4, + # "is_electric_only": "true", + # "avg_electric_load_kw": 885.0247784246575, + # "max_electric_load_kw": 1427.334 + # } + + # # Call to the django view endpoint /chp_defaults which calls the http.jl endpoint + # resp = self.api_client.get(f'/v3/chp_defaults', data=inputs) + # view_response = json.loads(resp.content) + + # # Check the endpoint logic with the expected selection + # self.assertEqual(http_response["default_inputs"]["federal_itc_fraction"], 0.0) - def test_steamturbine_defaults(self): - - inputs = { - "prime_mover": "steam_turbine", - "avg_boiler_fuel_load_mmbtu_per_hour": 28.0 - } - - # Direct call of the http.jl endpoint /chp_defaults - julia_host = os.environ.get('JULIA_HOST', "julia") - response = requests.get("http://" + julia_host + ":8081/chp_defaults/", json=inputs) - http_response = response.json() - - # Call to the django view endpoint /chp_defaults which calls the http.jl endpoint - resp = self.api_client.get(f'/v3/chp_defaults', data=inputs) - view_response = json.loads(resp.content) - - mismatch = [] - for k, v in http_response["default_inputs"].items(): - if v != view_response["default_inputs"][k]: - mismatch.append(k) + # def test_steamturbine_defaults(self): + + # inputs = { + # "prime_mover": "steam_turbine", + # "avg_boiler_fuel_load_mmbtu_per_hour": 28.0 + # } + + # # Direct call of the http.jl endpoint /chp_defaults + # julia_host = os.environ.get('JULIA_HOST', "julia") + # response = requests.get("http://" + julia_host + ":8081/chp_defaults/", json=inputs) + # http_response = response.json() + + # # Call to the django view endpoint /chp_defaults which calls the http.jl endpoint + # resp = self.api_client.get(f'/v3/chp_defaults', data=inputs) + # view_response = json.loads(resp.content) + + # mismatch = [] + # for k, v in http_response["default_inputs"].items(): + # if v != view_response["default_inputs"][k]: + # mismatch.append(k) - self.assertEqual(mismatch, []) + # self.assertEqual(mismatch, []) - # Check the endpoint logic with the expected selection - self.assertEqual(http_response["prime_mover"], "steam_turbine") - self.assertEqual(http_response["size_class"], 1) - self.assertGreater(http_response["chp_elec_size_heuristic_kw"], 574.419) + # # Check the endpoint logic with the expected selection + # self.assertEqual(http_response["prime_mover"], "steam_turbine") + # self.assertEqual(http_response["size_class"], 1) + # self.assertGreater(http_response["chp_elec_size_heuristic_kw"], 574.419) - def test_absorption_chiller_defaults(self): + # def test_absorption_chiller_defaults(self): - inputs = {"thermal_consumption_hot_water_or_steam": "hot_water", - "load_max_tons": 50 - } + # inputs = {"thermal_consumption_hot_water_or_steam": "hot_water", + # "load_max_tons": 50 + # } - # Direct call of the http.jl endpoint /absorption_chiller_defaults - julia_host = os.environ.get('JULIA_HOST', "julia") - response = requests.get("http://" + julia_host + ":8081/absorption_chiller_defaults/", json=inputs) - http_response = response.json() + # # Direct call of the http.jl endpoint /absorption_chiller_defaults + # julia_host = os.environ.get('JULIA_HOST', "julia") + # response = requests.get("http://" + julia_host + ":8081/absorption_chiller_defaults/", json=inputs) + # http_response = response.json() - # Call to the django view endpoint /absorption_chiller_defaults which calls the http.jl endpoint - resp = self.api_client.get(f'/v3/absorption_chiller_defaults', data=inputs) - view_response = json.loads(resp.content) + # # Call to the django view endpoint /absorption_chiller_defaults which calls the http.jl endpoint + # resp = self.api_client.get(f'/v3/absorption_chiller_defaults', data=inputs) + # view_response = json.loads(resp.content) - mismatch = [] - for k, v in http_response["default_inputs"].items(): - if v != view_response["default_inputs"][k]: - mismatch.append(k) + # mismatch = [] + # for k, v in http_response["default_inputs"].items(): + # if v != view_response["default_inputs"][k]: + # mismatch.append(k) - self.assertEqual(mismatch, []) - - # Check the endpoint logic with the expected selection - self.assertEqual(http_response["thermal_consumption_hot_water_or_steam"], "hot_water") - self.assertEqual(http_response["default_inputs"]["om_cost_per_ton"], 80.0) - self.assertEqual(http_response["default_inputs"]["installed_cost_per_ton"], 3066.0) - self.assertEqual(http_response["default_inputs"]["cop_thermal"], 0.74) - self.assertNotIn("thermal_consumption_hot_water_or_steam", http_response["default_inputs"].keys()) + # self.assertEqual(mismatch, []) + + # # Check the endpoint logic with the expected selection + # self.assertEqual(http_response["thermal_consumption_hot_water_or_steam"], "hot_water") + # self.assertEqual(http_response["default_inputs"]["om_cost_per_ton"], 80.0) + # self.assertEqual(http_response["default_inputs"]["installed_cost_per_ton"], 3066.0) + # self.assertEqual(http_response["default_inputs"]["cop_thermal"], 0.74) + # self.assertNotIn("thermal_consumption_hot_water_or_steam", http_response["default_inputs"].keys()) - def test_simulated_load(self): - - # Test heating load because REopt.jl separates SpaceHeating and DHW, so had to aggregate for this endpoint - inputs = {"load_type": "heating", - "doe_reference_name": "Hospital", - "latitude": 36.12, - "longitude": -115.5 - } - - # The /v3/simulated_load endpoint calls the http.jl /simulated_load endpoint - response = self.api_client.get(f'/v3/simulated_load', data=inputs) - http_response = json.loads(response.content) - - # Call to the v2 /simulated_load to check for consistency - resp = self.api_client.get(f'/v2/simulated_load', data=inputs) - v2_response = json.loads(resp.content) - self.assertAlmostEqual(http_response["annual_mmbtu"], v2_response["annual_mmbtu"], delta=1.0) - - # Test blended/hybrid buildings - inputs["load_type"] = "electric" - inputs["annual_kwh"] = 1.5E7 - inputs["doe_reference_name[0]"] = "Hospital" - inputs["doe_reference_name[1]"] = "LargeOffice" - inputs["percent_share[0]"] = 0.25 - inputs["percent_share[1]"] = 1.0 - inputs["percent_share[0]"] + # def test_simulated_load(self): + + # # Test heating load because REopt.jl separates SpaceHeating and DHW, so had to aggregate for this endpoint + # inputs = {"load_type": "heating", + # "doe_reference_name": "Hospital", + # "latitude": 36.12, + # "longitude": -115.5 + # } + + # # The /v3/simulated_load endpoint calls the http.jl /simulated_load endpoint + # response = self.api_client.get(f'/v3/simulated_load', data=inputs) + # http_response = json.loads(response.content) + + # # Call to the v2 /simulated_load to check for consistency + # resp = self.api_client.get(f'/v2/simulated_load', data=inputs) + # v2_response = json.loads(resp.content) + # self.assertAlmostEqual(http_response["annual_mmbtu"], v2_response["annual_mmbtu"], delta=1.0) + + # # Test blended/hybrid buildings + # inputs["load_type"] = "electric" + # inputs["annual_kwh"] = 1.5E7 + # inputs["doe_reference_name[0]"] = "Hospital" + # inputs["doe_reference_name[1]"] = "LargeOffice" + # inputs["percent_share[0]"] = 0.25 + # inputs["percent_share[1]"] = 1.0 - inputs["percent_share[0]"] - # The /v3/simulated_load endpoint calls the http.jl /simulated_load endpoint - response = self.api_client.get(f'/v3/simulated_load', data=inputs) - http_response = json.loads(response.content) - - # Call to the v2 /simulated_load to check for consistency - resp = self.api_client.get(f'/v2/simulated_load', data=inputs) - v2_response = json.loads(resp.content) - self.assertAlmostEqual(http_response["annual_kwh"], v2_response["annual_kwh"], delta=1.0) - - # Test bad inputs - inputs["invalid_key"] = "invalid_val" - resp = self.api_client.get(f'/v2/simulated_load', data=inputs) - v2_response = json.loads(resp.content) - assert("Error" in v2_response.keys()) - - def test_avert_emissions_profile_endpoint(self): - # Call to the django view endpoint dev/avert_emissions_profile which calls the http.jl endpoint - #case 1: location in CONUS (Seattle, WA) - inputs = { - "latitude": 47.606211, - "longitude": -122.336052, - "load_year": 2021 - } - resp = self.api_client.get(f'/v3/avert_emissions_profile', data=inputs) - self.assertHttpOK(resp) - view_response = json.loads(resp.content) - self.assertEquals(view_response["avert_meters_to_region"], 0.0) - self.assertEquals(view_response["avert_region"], "Northwest") - self.assertEquals(len(view_response["emissions_factor_series_lb_NOx_per_kwh"]), 8760) - #case 2: location off shore of NJ (works for AVERT, not Cambium) - inputs = { - "latitude": 39.034417, - "longitude": -74.759292, - "load_year": 2021 - } - resp = self.api_client.get(f'/v3/avert_emissions_profile', data=inputs) - self.assertHttpOK(resp) - view_response = json.loads(resp.content) - self.assertAlmostEqual(view_response["avert_meters_to_region"], 760.62, delta=1.0) - self.assertEquals(view_response["avert_region"], "Mid-Atlantic") - self.assertEquals(len(view_response["emissions_factor_series_lb_NOx_per_kwh"]), 8760) - #case 3: Honolulu, HI (works for AVERT but not Cambium) - inputs = { - "latitude": 21.3099, - "longitude": -157.8581, - "load_year": 2021 - } - resp = self.api_client.get(f'/v3/avert_emissions_profile', data=inputs) - self.assertHttpOK(resp) - view_response = json.loads(resp.content) - self.assertEquals(view_response["avert_meters_to_region"], 0.0) - self.assertEquals(view_response["avert_region"], "Hawaii (Oahu)") - self.assertEquals(len(view_response["emissions_factor_series_lb_NOx_per_kwh"]), 8760) - #case 4: location well outside of US (does not work) - inputs = { - "latitude": 0.0, - "longitude": 0.0, - "load_year": 2022 - } - resp = self.api_client.get(f'/v3/avert_emissions_profile', data=inputs) - self.assertHttpBadRequest(resp) - view_response = json.loads(resp.content) - self.assertTrue("error" in view_response) + # # The /v3/simulated_load endpoint calls the http.jl /simulated_load endpoint + # response = self.api_client.get(f'/v3/simulated_load', data=inputs) + # http_response = json.loads(response.content) + + # # Call to the v2 /simulated_load to check for consistency + # resp = self.api_client.get(f'/v2/simulated_load', data=inputs) + # v2_response = json.loads(resp.content) + # self.assertAlmostEqual(http_response["annual_kwh"], v2_response["annual_kwh"], delta=1.0) + + # # Test bad inputs + # inputs["invalid_key"] = "invalid_val" + # resp = self.api_client.get(f'/v2/simulated_load', data=inputs) + # v2_response = json.loads(resp.content) + # assert("Error" in v2_response.keys()) + + # def test_avert_emissions_profile_endpoint(self): + # # Call to the django view endpoint dev/avert_emissions_profile which calls the http.jl endpoint + # #case 1: location in CONUS (Seattle, WA) + # inputs = { + # "latitude": 47.606211, + # "longitude": -122.336052, + # "load_year": 2021 + # } + # resp = self.api_client.get(f'/v3/avert_emissions_profile', data=inputs) + # self.assertHttpOK(resp) + # view_response = json.loads(resp.content) + # self.assertEquals(view_response["avert_meters_to_region"], 0.0) + # self.assertEquals(view_response["avert_region"], "Northwest") + # self.assertEquals(len(view_response["emissions_factor_series_lb_NOx_per_kwh"]), 8760) + # #case 2: location off shore of NJ (works for AVERT, not Cambium) + # inputs = { + # "latitude": 39.034417, + # "longitude": -74.759292, + # "load_year": 2021 + # } + # resp = self.api_client.get(f'/v3/avert_emissions_profile', data=inputs) + # self.assertHttpOK(resp) + # view_response = json.loads(resp.content) + # self.assertAlmostEqual(view_response["avert_meters_to_region"], 760.62, delta=1.0) + # self.assertEquals(view_response["avert_region"], "Mid-Atlantic") + # self.assertEquals(len(view_response["emissions_factor_series_lb_NOx_per_kwh"]), 8760) + # #case 3: Honolulu, HI (works for AVERT but not Cambium) + # inputs = { + # "latitude": 21.3099, + # "longitude": -157.8581, + # "load_year": 2021 + # } + # resp = self.api_client.get(f'/v3/avert_emissions_profile', data=inputs) + # self.assertHttpOK(resp) + # view_response = json.loads(resp.content) + # self.assertEquals(view_response["avert_meters_to_region"], 0.0) + # self.assertEquals(view_response["avert_region"], "Hawaii (Oahu)") + # self.assertEquals(len(view_response["emissions_factor_series_lb_NOx_per_kwh"]), 8760) + # #case 4: location well outside of US (does not work) + # inputs = { + # "latitude": 0.0, + # "longitude": 0.0, + # "load_year": 2022 + # } + # resp = self.api_client.get(f'/v3/avert_emissions_profile', data=inputs) + # self.assertHttpBadRequest(resp) + # view_response = json.loads(resp.content) + # self.assertTrue("error" in view_response) - def test_cambium_emissions_profile_endpoint(self): - # Call to the django view endpoint v3/cambium_emissions_profile which calls the http.jl endpoint - #case 1: location in CONUS (Seattle, WA) - inputs = { - "load_year": 2021, - "scenario": "Mid-case", - "location_type": "States", - "latitude": 47.606211, # Seattle - "longitude": -122.336052, # Seattle - "start_year": 2024, - "lifetime": 25, - "metric_col": "lrmer_co2e", - "grid_level": "enduse" - } - resp = self.api_client.get(f'/v3/cambium_emissions_profile', data=inputs) - self.assertHttpOK(resp) - view_response = json.loads(resp.content) - self.assertEquals(view_response["metric_col"], "lrmer_co2e") - self.assertEquals(view_response["location"], "Washington") - self.assertEquals(len(view_response["emissions_factor_series_lb_CO2_per_kwh"]), 8760) - #case 2: location off shore of NJ (works for AVERT, not Cambium) - inputs["latitude"] = 39.034417 - inputs["longitude"] = -74.759292 - resp = self.api_client.get(f'/v3/cambium_emissions_profile', data=inputs) - self.assertHttpBadRequest(resp) - view_response = json.loads(resp.content) - self.assertTrue("error" in view_response) - #case 3: Honolulu, HI (works for AVERT but not Cambium) - inputs["latitude"] = 21.3099 - inputs["longitude"] = -157.8581 - resp = self.api_client.get(f'/v3/cambium_emissions_profile', data=inputs) - self.assertHttpBadRequest(resp) - view_response = json.loads(resp.content) - self.assertTrue("error" in view_response) - #case 4: location well outside of US (does not work) - inputs["latitude"] = 0.0 - inputs["longitude"] = 0.0 - resp = self.api_client.get(f'/v3/cambium_emissions_profile', data=inputs) - self.assertHttpBadRequest(resp) - view_response = json.loads(resp.content) - self.assertTrue("error" in view_response) - - def test_easiur_endpoint(self): - # Call to the django view endpoint dev/easiur_costs which calls the http.jl endpoint - inputs = { - "latitude": 47.606211, - "longitude": -122.336052, - "inflation": 0.025 - } - resp = self.api_client.get(f'/v3/easiur_costs', data=inputs) - self.assertHttpOK(resp) - view_response = json.loads(resp.content) - for ekey in ["NOx", "SO2", "PM25"]: - for key_format in ["{}_grid_cost_per_tonne", "{}_onsite_fuelburn_cost_per_tonne", "{}_cost_escalation_rate_fraction"]: - self.assertTrue(type(view_response[key_format.format(ekey)]) == float) - inputs = { - "latitude": 47.606211, - "longitude": 122.336052, - "inflation": 0.025 - } - resp = self.api_client.get(f'/v3/easiur_costs', data=inputs) - self.assertHttpBadRequest(resp) - view_response = json.loads(resp.content) - self.assertTrue("error" in view_response) - - def test_ghp_endpoints(self): - # Test /ghp_efficiency_thermal_factors - inputs_dict = {"latitude": 37.78, - "longitude": -122.45, - "doe_reference_name": "MediumOffice"} - - # Call to the django view endpoint /ghp_efficiency_thermal_factors which calls the http.jl endpoint - resp = self.api_client.get(f'/v3/ghp_efficiency_thermal_factors', data=inputs_dict) - view_response = json.loads(resp.content) - - self.assertEqual(view_response["cooling_efficiency_thermal_factor"], 0.43) - self.assertEqual(view_response["space_heating_efficiency_thermal_factor"], 0.46) - - # Test /ghpghx/ground_conductivity - inputs_dict = {"latitude": 37.78, - "longitude": -122.45} - - # Call to the django view endpoint /ghpghx/ground_conductivity which calls the http.jl endpoint - resp = self.api_client.get(f'/v3/ghpghx/ground_conductivity', data=inputs_dict) - view_response = json.loads(resp.content) - - self.assertEqual(view_response["thermal_conductivity"], 1.117) - - def test_default_existing_chiller_cop(self): - # Test 1: full dictionary - inputs_dict = { - "existing_chiller_max_thermal_factor_on_peak_load":1.25, - "max_load_kw": 50, - "max_load_ton":10 - } - - # Call to the django view endpoint /get_existing_chiller_default_cop which calls the http.jl endpoint - resp = self.api_client.get(f'/v3/get_existing_chiller_default_cop', data=inputs_dict) - view_response = json.loads(resp.content) - - self.assertEqual(view_response["existing_chiller_cop"], 4.4) - - # Test 2: empty dictionary, which should return unknown value - inputs_dict = {} - - # Call to the django view endpoint /get_existing_chiller_default_cop which calls the http.jl endpoint - resp = self.api_client.get(f'/v3/get_existing_chiller_default_cop', data=inputs_dict) - view_response = json.loads(resp.content) - - self.assertEqual(view_response["existing_chiller_cop"], 4.545) - - # Test 3: Check that "existing_chiller_max_thermal_factor_on_peak_load" can influence the COP; accept max_load_ton empty string - inputs_dict = { - "existing_chiller_max_thermal_factor_on_peak_load":1000, - "max_load_kw": 5, - "max_load_ton":"" - } - - # Call to the django view endpoint /get_existing_chiller_default_cop which calls the http.jl endpoint - resp = self.api_client.get(f'/v3/get_existing_chiller_default_cop', data=inputs_dict) - view_response = json.loads(resp.content) - - self.assertEqual(view_response["existing_chiller_cop"], 4.69) - - # Test 4: max_load_ton empty string - inputs_dict = { - "max_load_ton":90, - "max_load_kw":"" - } - - # Call to the django view endpoint /get_existing_chiller_default_cop which calls the http.jl endpoint - resp = self.api_client.get(f'/v3/get_existing_chiller_default_cop', data=inputs_dict) - view_response = json.loads(resp.content) - - self.assertEqual(view_response["existing_chiller_cop"], 4.69) - - #Test 5: max_load_kw only, small value yields low COP - inputs_dict = { - "max_load_kw":1 - } - - # Call to the django view endpoint /get_existing_chiller_default_cop which calls the http.jl endpoint - resp = self.api_client.get(f'/v3/get_existing_chiller_default_cop', data=inputs_dict) - view_response = json.loads(resp.content) - - self.assertEqual(view_response["existing_chiller_cop"], 4.4) + # def test_cambium_emissions_profile_endpoint(self): + # # Call to the django view endpoint v3/cambium_emissions_profile which calls the http.jl endpoint + # #case 1: location in CONUS (Seattle, WA) + # inputs = { + # "load_year": 2021, + # "scenario": "Mid-case", + # "location_type": "States", + # "latitude": 47.606211, # Seattle + # "longitude": -122.336052, # Seattle + # "start_year": 2024, + # "lifetime": 25, + # "metric_col": "lrmer_co2e", + # "grid_level": "enduse" + # } + # resp = self.api_client.get(f'/v3/cambium_emissions_profile', data=inputs) + # self.assertHttpOK(resp) + # view_response = json.loads(resp.content) + # self.assertEquals(view_response["metric_col"], "lrmer_co2e") + # self.assertEquals(view_response["location"], "Washington") + # self.assertEquals(len(view_response["emissions_factor_series_lb_CO2_per_kwh"]), 8760) + # #case 2: location off shore of NJ (works for AVERT, not Cambium) + # inputs["latitude"] = 39.034417 + # inputs["longitude"] = -74.759292 + # resp = self.api_client.get(f'/v3/cambium_emissions_profile', data=inputs) + # self.assertHttpBadRequest(resp) + # view_response = json.loads(resp.content) + # self.assertTrue("error" in view_response) + # #case 3: Honolulu, HI (works for AVERT but not Cambium) + # inputs["latitude"] = 21.3099 + # inputs["longitude"] = -157.8581 + # resp = self.api_client.get(f'/v3/cambium_emissions_profile', data=inputs) + # self.assertHttpBadRequest(resp) + # view_response = json.loads(resp.content) + # self.assertTrue("error" in view_response) + # #case 4: location well outside of US (does not work) + # inputs["latitude"] = 0.0 + # inputs["longitude"] = 0.0 + # resp = self.api_client.get(f'/v3/cambium_emissions_profile', data=inputs) + # self.assertHttpBadRequest(resp) + # view_response = json.loads(resp.content) + # self.assertTrue("error" in view_response) + + # def test_easiur_endpoint(self): + # # Call to the django view endpoint dev/easiur_costs which calls the http.jl endpoint + # inputs = { + # "latitude": 47.606211, + # "longitude": -122.336052, + # "inflation": 0.025 + # } + # resp = self.api_client.get(f'/v3/easiur_costs', data=inputs) + # self.assertHttpOK(resp) + # view_response = json.loads(resp.content) + # for ekey in ["NOx", "SO2", "PM25"]: + # for key_format in ["{}_grid_cost_per_tonne", "{}_onsite_fuelburn_cost_per_tonne", "{}_cost_escalation_rate_fraction"]: + # self.assertTrue(type(view_response[key_format.format(ekey)]) == float) + # inputs = { + # "latitude": 47.606211, + # "longitude": 122.336052, + # "inflation": 0.025 + # } + # resp = self.api_client.get(f'/v3/easiur_costs', data=inputs) + # self.assertHttpBadRequest(resp) + # view_response = json.loads(resp.content) + # self.assertTrue("error" in view_response) + + # def test_ghp_endpoints(self): + # # Test /ghp_efficiency_thermal_factors + # inputs_dict = {"latitude": 37.78, + # "longitude": -122.45, + # "doe_reference_name": "MediumOffice"} + + # # Call to the django view endpoint /ghp_efficiency_thermal_factors which calls the http.jl endpoint + # resp = self.api_client.get(f'/v3/ghp_efficiency_thermal_factors', data=inputs_dict) + # view_response = json.loads(resp.content) + + # self.assertEqual(view_response["cooling_efficiency_thermal_factor"], 0.43) + # self.assertEqual(view_response["space_heating_efficiency_thermal_factor"], 0.46) + + # # Test /ghpghx/ground_conductivity + # inputs_dict = {"latitude": 37.78, + # "longitude": -122.45} + + # # Call to the django view endpoint /ghpghx/ground_conductivity which calls the http.jl endpoint + # resp = self.api_client.get(f'/v3/ghpghx/ground_conductivity', data=inputs_dict) + # view_response = json.loads(resp.content) + + # self.assertEqual(view_response["thermal_conductivity"], 1.117) + + # def test_default_existing_chiller_cop(self): + # # Test 1: full dictionary + # inputs_dict = { + # "existing_chiller_max_thermal_factor_on_peak_load":1.25, + # "max_load_kw": 50, + # "max_load_ton":10 + # } + + # # Call to the django view endpoint /get_existing_chiller_default_cop which calls the http.jl endpoint + # resp = self.api_client.get(f'/v3/get_existing_chiller_default_cop', data=inputs_dict) + # view_response = json.loads(resp.content) + + # self.assertEqual(view_response["existing_chiller_cop"], 4.4) + + # # Test 2: empty dictionary, which should return unknown value + # inputs_dict = {} + + # # Call to the django view endpoint /get_existing_chiller_default_cop which calls the http.jl endpoint + # resp = self.api_client.get(f'/v3/get_existing_chiller_default_cop', data=inputs_dict) + # view_response = json.loads(resp.content) + + # self.assertEqual(view_response["existing_chiller_cop"], 4.545) + + # # Test 3: Check that "existing_chiller_max_thermal_factor_on_peak_load" can influence the COP; accept max_load_ton empty string + # inputs_dict = { + # "existing_chiller_max_thermal_factor_on_peak_load":1000, + # "max_load_kw": 5, + # "max_load_ton":"" + # } + + # # Call to the django view endpoint /get_existing_chiller_default_cop which calls the http.jl endpoint + # resp = self.api_client.get(f'/v3/get_existing_chiller_default_cop', data=inputs_dict) + # view_response = json.loads(resp.content) + + # self.assertEqual(view_response["existing_chiller_cop"], 4.69) + + # # Test 4: max_load_ton empty string + # inputs_dict = { + # "max_load_ton":90, + # "max_load_kw":"" + # } + + # # Call to the django view endpoint /get_existing_chiller_default_cop which calls the http.jl endpoint + # resp = self.api_client.get(f'/v3/get_existing_chiller_default_cop', data=inputs_dict) + # view_response = json.loads(resp.content) + + # self.assertEqual(view_response["existing_chiller_cop"], 4.69) + + # #Test 5: max_load_kw only, small value yields low COP + # inputs_dict = { + # "max_load_kw":1 + # } + + # # Call to the django view endpoint /get_existing_chiller_default_cop which calls the http.jl endpoint + # resp = self.api_client.get(f'/v3/get_existing_chiller_default_cop', data=inputs_dict) + # view_response = json.loads(resp.content) + + # self.assertEqual(view_response["existing_chiller_cop"], 4.4) diff --git a/reoptjl/test/test_job_endpoint.py b/reoptjl/test/test_job_endpoint.py index 407e1333e..9b234508b 100644 --- a/reoptjl/test/test_job_endpoint.py +++ b/reoptjl/test/test_job_endpoint.py @@ -36,275 +36,275 @@ def test_multiple_outages(self): self.assertAlmostEqual(results["Outages"]["microgrid_upgrade_capital_cost"], 1974429.4, delta=5000.0) self.assertAlmostEqual(results["Financial"]["lcc"], 59865240.0, delta=0.01*results["Financial"]["lcc"]) - def test_pv_battery_and_emissions_defaults_from_julia(self): - """ - Same test post as "Solar and ElectricStorage w/BAU" in the Julia package. Used in development of v3. - Also tests that inputs with defaults determined in the REopt julia package get updated in the database. - """ - post_file = os.path.join('reoptjl', 'test', 'posts', 'pv_batt_emissions.json') - post = json.load(open(post_file, 'r')) - - resp = self.api_client.post('/v3/job/', format='json', data=post) - self.assertHttpCreated(resp) - r = json.loads(resp.content) - run_uuid = r.get('run_uuid') - - resp = self.api_client.get(f'/v3/job/{run_uuid}/results') - r = json.loads(resp.content) - results = r["outputs"] - - self.assertAlmostEqual(results["Financial"]["lcc"], 12391786, places=-3) - self.assertAlmostEqual(results["Financial"]["lcc_bau"], 12766397, places=-3) - self.assertAlmostEqual(results["PV"]["size_kw"], 216.667, places=1) - self.assertAlmostEqual(results["ElectricStorage"]["size_kw"], 49.05, places=1) - self.assertAlmostEqual(results["ElectricStorage"]["size_kwh"], 83.32, places=1) + # def test_pv_battery_and_emissions_defaults_from_julia(self): + # """ + # Same test post as "Solar and ElectricStorage w/BAU" in the Julia package. Used in development of v3. + # Also tests that inputs with defaults determined in the REopt julia package get updated in the database. + # """ + # post_file = os.path.join('reoptjl', 'test', 'posts', 'pv_batt_emissions.json') + # post = json.load(open(post_file, 'r')) + + # resp = self.api_client.post('/v3/job/', format='json', data=post) + # self.assertHttpCreated(resp) + # r = json.loads(resp.content) + # run_uuid = r.get('run_uuid') + + # resp = self.api_client.get(f'/v3/job/{run_uuid}/results') + # r = json.loads(resp.content) + # results = r["outputs"] + + # self.assertAlmostEqual(results["Financial"]["lcc"], 12391786, places=-3) + # self.assertAlmostEqual(results["Financial"]["lcc_bau"], 12766397, places=-3) + # self.assertAlmostEqual(results["PV"]["size_kw"], 216.667, places=1) + # self.assertAlmostEqual(results["ElectricStorage"]["size_kw"], 49.05, places=1) + # self.assertAlmostEqual(results["ElectricStorage"]["size_kwh"], 83.32, places=1) - self.assertIsNotNone(results["Site"]["total_renewable_energy_fraction"]) - self.assertIsNotNone(results["Site"]["annual_emissions_tonnes_CO2"]) - self.assertIsNotNone(results["Site"]["lifecycle_emissions_tonnes_NOx"]) - - #test that emissions inputs got updated in the database with the defaults determined in REopt julia package - updated_inputs = r["inputs"] - self.assertIsNotNone(updated_inputs["ElectricUtility"]["emissions_factor_series_lb_CO2_per_kwh"]) - self.assertIsNotNone(updated_inputs["Financial"]["NOx_grid_cost_per_tonne"]) - self.assertIsNotNone(updated_inputs["Financial"]["SO2_onsite_fuelburn_cost_per_tonne"]) - self.assertIsNotNone(updated_inputs["Financial"]["PM25_cost_escalation_rate_fraction"]) - - def test_off_grid_defaults(self): - """ - Purpose of this test is to validate off-grid functionality and defaults in the API. - """ - post_file = os.path.join('reoptjl', 'test', 'posts', 'off_grid_defaults.json') - post = json.load(open(post_file, 'r')) - - resp = self.api_client.post('/v3/job/', format='json', data=post) - self.assertHttpCreated(resp) - r = json.loads(resp.content) - run_uuid = r.get('run_uuid') - - resp = self.api_client.get(f'/v3/job/{run_uuid}/results') - r = json.loads(resp.content) - results = r["outputs"] + # self.assertIsNotNone(results["Site"]["total_renewable_energy_fraction"]) + # self.assertIsNotNone(results["Site"]["annual_emissions_tonnes_CO2"]) + # self.assertIsNotNone(results["Site"]["lifecycle_emissions_tonnes_NOx"]) + + # #test that emissions inputs got updated in the database with the defaults determined in REopt julia package + # updated_inputs = r["inputs"] + # self.assertIsNotNone(updated_inputs["ElectricUtility"]["emissions_factor_series_lb_CO2_per_kwh"]) + # self.assertIsNotNone(updated_inputs["Financial"]["NOx_grid_cost_per_tonne"]) + # self.assertIsNotNone(updated_inputs["Financial"]["SO2_onsite_fuelburn_cost_per_tonne"]) + # self.assertIsNotNone(updated_inputs["Financial"]["PM25_cost_escalation_rate_fraction"]) + + # def test_off_grid_defaults(self): + # """ + # Purpose of this test is to validate off-grid functionality and defaults in the API. + # """ + # post_file = os.path.join('reoptjl', 'test', 'posts', 'off_grid_defaults.json') + # post = json.load(open(post_file, 'r')) + + # resp = self.api_client.post('/v3/job/', format='json', data=post) + # self.assertHttpCreated(resp) + # r = json.loads(resp.content) + # run_uuid = r.get('run_uuid') + + # resp = self.api_client.get(f'/v3/job/{run_uuid}/results') + # r = json.loads(resp.content) + # results = r["outputs"] - # Validate that we got off-grid response fields - self.assertAlmostEqual(results["Financial"]["offgrid_microgrid_lcoe_dollars_per_kwh"], 0.337, places=-3) - self.assertAlmostEqual(results["ElectricTariff"]["year_one_bill_before_tax"], 0.0) - self.assertAlmostEqual(results["ElectricLoad"]["offgrid_load_met_fraction"], 0.99999, places=-2) - self.assertAlmostEqual(sum(results["ElectricLoad"]["offgrid_load_met_series_kw"]), 8760.0, places=-1) - self.assertAlmostEqual(results["Financial"]["lifecycle_offgrid_other_annual_costs_after_tax"], 0.0, places=-2) + # # Validate that we got off-grid response fields + # self.assertAlmostEqual(results["Financial"]["offgrid_microgrid_lcoe_dollars_per_kwh"], 0.337, places=-3) + # self.assertAlmostEqual(results["ElectricTariff"]["year_one_bill_before_tax"], 0.0) + # self.assertAlmostEqual(results["ElectricLoad"]["offgrid_load_met_fraction"], 0.99999, places=-2) + # self.assertAlmostEqual(sum(results["ElectricLoad"]["offgrid_load_met_series_kw"]), 8760.0, places=-1) + # self.assertAlmostEqual(results["Financial"]["lifecycle_offgrid_other_annual_costs_after_tax"], 0.0, places=-2) - def test_process_reopt_error(self): - """ - Purpose of this test is to ensure REopt status 400 is returned using the reoptjl endpoint - """ - - post_file = os.path.join('reoptjl', 'test', 'posts', 'handle_reopt_error.json') - post = json.load(open(post_file, 'r')) - - resp = self.api_client.post('/v3/job/', format='json', data=post) - self.assertHttpCreated(resp) - r = json.loads(resp.content) - run_uuid = r.get('run_uuid') - - resp = self.api_client.get(f'/v3/job/{run_uuid}/results') - r = json.loads(resp.content) - assert('errors' in r["messages"].keys()) - assert('warnings' in r["messages"].keys()) - assert(r['messages']['has_stacktrace']==True) - assert(resp.status_code==400) - - - def test_thermal_in_results(self): - """ - Purpose of this test is to check that the expected thermal loads, techs, and storage are included in the results - """ - - post_file = os.path.join('reoptjl', 'test', 'posts', 'test_thermal_in_results.json') #includes GhpGhx responses - post = json.load(open(post_file, 'r')) - - resp = self.api_client.post('/v3/job/', format='json', data=post) - self.assertHttpCreated(resp) - r = json.loads(resp.content) - run_uuid = r.get('run_uuid') + # def test_process_reopt_error(self): + # """ + # Purpose of this test is to ensure REopt status 400 is returned using the reoptjl endpoint + # """ + + # post_file = os.path.join('reoptjl', 'test', 'posts', 'handle_reopt_error.json') + # post = json.load(open(post_file, 'r')) + + # resp = self.api_client.post('/v3/job/', format='json', data=post) + # self.assertHttpCreated(resp) + # r = json.loads(resp.content) + # run_uuid = r.get('run_uuid') + + # resp = self.api_client.get(f'/v3/job/{run_uuid}/results') + # r = json.loads(resp.content) + # assert('errors' in r["messages"].keys()) + # assert('warnings' in r["messages"].keys()) + # assert(r['messages']['has_stacktrace']==True) + # assert(resp.status_code==400) + + + # def test_thermal_in_results(self): + # """ + # Purpose of this test is to check that the expected thermal loads, techs, and storage are included in the results + # """ + + # post_file = os.path.join('reoptjl', 'test', 'posts', 'test_thermal_in_results.json') #includes GhpGhx responses + # post = json.load(open(post_file, 'r')) + + # resp = self.api_client.post('/v3/job/', format='json', data=post) + # self.assertHttpCreated(resp) + # r = json.loads(resp.content) + # run_uuid = r.get('run_uuid') - resp = self.api_client.get(f'/v3/job/{run_uuid}/results') - r = json.loads(resp.content) - inputs = r["inputs"] - results = r["outputs"] - self.assertIn("CoolingLoad", list(inputs.keys())) - self.assertIn("CoolingLoad", list(results.keys())) - self.assertIn("CHP", list(results.keys())) - self.assertIn("thermal_to_dhw_load_series_mmbtu_per_hour", list(results["CHP"].keys())) - self.assertIn("thermal_to_space_heating_load_series_mmbtu_per_hour", list(results["CHP"].keys())) - self.assertIn("thermal_to_dhw_load_series_mmbtu_per_hour", list(results["CHP"].keys())) - self.assertIn("ExistingChiller",list(results.keys())) - self.assertIn("ExistingBoiler", list(results.keys())) - self.assertIn("HeatingLoad", list(results.keys())) - self.assertIn("process_heat_thermal_load_series_mmbtu_per_hour", list(results["HeatingLoad"].keys())) - self.assertIn("process_heat_boiler_fuel_load_series_mmbtu_per_hour", list(results["HeatingLoad"].keys())) - self.assertIn("HotThermalStorage", list(results.keys())) - self.assertIn("storage_to_dhw_load_series_mmbtu_per_hour", list(results["HotThermalStorage"].keys())) - self.assertIn("storage_to_space_heating_load_series_mmbtu_per_hour", list(results["HotThermalStorage"].keys())) - self.assertIn("storage_to_dhw_load_series_mmbtu_per_hour", list(results["HotThermalStorage"].keys())) - self.assertIn("ColdThermalStorage", list(results.keys())) - self.assertIn("AbsorptionChiller", list(results.keys())) - self.assertIn("GHP", list(results.keys())) - - - def test_chp_defaults_from_julia(self): - # Test that the inputs_with_defaults_set_in_julia feature worked for CHP, consistent with /chp_defaults - post_file = os.path.join('reoptjl', 'test', 'posts', 'chp_defaults_post.json') - post = json.load(open(post_file, 'r')) - # Make average MMBtu/hr thermal steam greater than 7 MMBtu/hr threshold for combustion_turbine to be chosen - # Default ExistingBoiler efficiency for production_type = steam is 0.75 - post["SpaceHeatingLoad"]["annual_mmbtu"] = 8760 * 8 / 0.75 - post["DomesticHotWaterLoad"]["annual_mmbtu"] = 8760 * 8 / 0.75 - resp = self.api_client.post('/v3/job/', format='json', data=post) - self.assertHttpCreated(resp) - r = json.loads(resp.content) - run_uuid = r.get('run_uuid') - - resp = self.api_client.get(f'/v3/job/{run_uuid}/results') - r = json.loads(resp.content) - - inputs_chp = r["inputs"]["CHP"] - - avg_fuel_load = (post["SpaceHeatingLoad"]["annual_mmbtu"] + - post["DomesticHotWaterLoad"]["annual_mmbtu"]) / 8760.0 - inputs_chp_defaults = {"hot_water_or_steam": post["ExistingBoiler"]["production_type"], - "avg_boiler_fuel_load_mmbtu_per_hour": avg_fuel_load - } - - # Call to the django view endpoint /chp_defaults which calls the http.jl endpoint - resp = self.api_client.get(f'/v3/chp_defaults', data=inputs_chp_defaults) - view_response = json.loads(resp.content) - - for key in view_response["default_inputs"].keys(): - if post["CHP"].get(key) is None: # Check that default got assigned consistent with /chp_defaults - if key == "max_kw": - self.assertEquals(inputs_chp[key], view_response["chp_max_size_kw"]) - else: - self.assertEquals(inputs_chp[key], view_response["default_inputs"][key]) - else: # Make sure we didn't overwrite user-input - self.assertEquals(inputs_chp[key], post["CHP"][key]) - - def test_peak_load_outage_times(self): - """ - Purpose of this test is to test the endpoint /peak_load_outage_times - """ - - load = [100]*8760 - load[40*24] = 200 - load[50*24-1] = 300 - load[70*24+13] = 300 - load[170*24] = 300 - load[243*24] = 400 - outage_inputs = {"seasonal_peaks": True, - "outage_duration": 95, - "critical_load": load, - "start_not_center_on_peaks": False - } - expected_time_steps = [50*24-1-47+1, 70*24+13-47+1, 170*24-47+1, 243*24-47+1] - resp = self.api_client.post(f'/v3/peak_load_outage_times', data=outage_inputs) - self.assertHttpOK(resp) - resp = json.loads(resp.content) - self.assertEquals(resp["outage_start_time_steps"], expected_time_steps) - - outage_inputs["seasonal_peaks"] = False - outage_inputs["start_not_center_on_peaks"] = True - expected_time_steps = [243*24+1] - resp = self.api_client.post(f'/v3/peak_load_outage_times', data=outage_inputs) - self.assertHttpOK(resp) - resp = json.loads(resp.content) - self.assertEquals(resp["outage_start_time_steps"], expected_time_steps) - - def test_superset_input_fields(self): - """ - Purpose of this test is to test the API's ability to accept all relevant - input fields and send to REopt, ensuring name input consistency with REopt.jl. - Note: Does not currently test CHP inputs - """ - post_file = os.path.join('reoptjl', 'test', 'posts', 'all_inputs_test.json') - post = json.load(open(post_file, 'r')) - - resp = self.api_client.post('/v3/job/', format='json', data=post) - self.assertHttpCreated(resp) - r = json.loads(resp.content) - run_uuid = r.get('run_uuid') - - resp = self.api_client.get(f'/v3/job/{run_uuid}/results') - r = json.loads(resp.content) - results = r["outputs"] - self.assertAlmostEqual(results["Financial"]["npv"], -258533.19, delta=0.01*results["Financial"]["lcc"]) - assert(resp.status_code==200) - - def test_steamturbine_defaults_from_julia(self): - # Test that the inputs_with_defaults_set_in_julia feature worked for SteamTurbine, consistent with /chp_defaults - post_file = os.path.join('reoptjl', 'test', 'posts', 'steamturbine_defaults_post.json') - post = json.load(open(post_file, 'r')) - - # Call http.jl /reopt to run SteamTurbine scenario and get results for defaults from julia checking - resp = self.api_client.post('/v3/job/', format='json', data=post) - self.assertHttpCreated(resp) - r = json.loads(resp.content) - run_uuid = r.get('run_uuid') - - resp = self.api_client.get(f'/v3/job/{run_uuid}/results') - r = json.loads(resp.content) - - inputs_steamturbine = r["inputs"]["SteamTurbine"] - - avg_fuel_load = (post["SpaceHeatingLoad"]["annual_mmbtu"] + - post["DomesticHotWaterLoad"]["annual_mmbtu"]) / 8760.0 + # resp = self.api_client.get(f'/v3/job/{run_uuid}/results') + # r = json.loads(resp.content) + # inputs = r["inputs"] + # results = r["outputs"] + # self.assertIn("CoolingLoad", list(inputs.keys())) + # self.assertIn("CoolingLoad", list(results.keys())) + # self.assertIn("CHP", list(results.keys())) + # self.assertIn("thermal_to_dhw_load_series_mmbtu_per_hour", list(results["CHP"].keys())) + # self.assertIn("thermal_to_space_heating_load_series_mmbtu_per_hour", list(results["CHP"].keys())) + # self.assertIn("thermal_to_dhw_load_series_mmbtu_per_hour", list(results["CHP"].keys())) + # self.assertIn("ExistingChiller",list(results.keys())) + # self.assertIn("ExistingBoiler", list(results.keys())) + # self.assertIn("HeatingLoad", list(results.keys())) + # self.assertIn("process_heat_thermal_load_series_mmbtu_per_hour", list(results["HeatingLoad"].keys())) + # self.assertIn("process_heat_boiler_fuel_load_series_mmbtu_per_hour", list(results["HeatingLoad"].keys())) + # self.assertIn("HotThermalStorage", list(results.keys())) + # self.assertIn("storage_to_dhw_load_series_mmbtu_per_hour", list(results["HotThermalStorage"].keys())) + # self.assertIn("storage_to_space_heating_load_series_mmbtu_per_hour", list(results["HotThermalStorage"].keys())) + # self.assertIn("storage_to_dhw_load_series_mmbtu_per_hour", list(results["HotThermalStorage"].keys())) + # self.assertIn("ColdThermalStorage", list(results.keys())) + # self.assertIn("AbsorptionChiller", list(results.keys())) + # self.assertIn("GHP", list(results.keys())) + + + # def test_chp_defaults_from_julia(self): + # # Test that the inputs_with_defaults_set_in_julia feature worked for CHP, consistent with /chp_defaults + # post_file = os.path.join('reoptjl', 'test', 'posts', 'chp_defaults_post.json') + # post = json.load(open(post_file, 'r')) + # # Make average MMBtu/hr thermal steam greater than 7 MMBtu/hr threshold for combustion_turbine to be chosen + # # Default ExistingBoiler efficiency for production_type = steam is 0.75 + # post["SpaceHeatingLoad"]["annual_mmbtu"] = 8760 * 8 / 0.75 + # post["DomesticHotWaterLoad"]["annual_mmbtu"] = 8760 * 8 / 0.75 + # resp = self.api_client.post('/v3/job/', format='json', data=post) + # self.assertHttpCreated(resp) + # r = json.loads(resp.content) + # run_uuid = r.get('run_uuid') + + # resp = self.api_client.get(f'/v3/job/{run_uuid}/results') + # r = json.loads(resp.content) + + # inputs_chp = r["inputs"]["CHP"] + + # avg_fuel_load = (post["SpaceHeatingLoad"]["annual_mmbtu"] + + # post["DomesticHotWaterLoad"]["annual_mmbtu"]) / 8760.0 + # inputs_chp_defaults = {"hot_water_or_steam": post["ExistingBoiler"]["production_type"], + # "avg_boiler_fuel_load_mmbtu_per_hour": avg_fuel_load + # } + + # # Call to the django view endpoint /chp_defaults which calls the http.jl endpoint + # resp = self.api_client.get(f'/v3/chp_defaults', data=inputs_chp_defaults) + # view_response = json.loads(resp.content) + + # for key in view_response["default_inputs"].keys(): + # if post["CHP"].get(key) is None: # Check that default got assigned consistent with /chp_defaults + # if key == "max_kw": + # self.assertEquals(inputs_chp[key], view_response["chp_max_size_kw"]) + # else: + # self.assertEquals(inputs_chp[key], view_response["default_inputs"][key]) + # else: # Make sure we didn't overwrite user-input + # self.assertEquals(inputs_chp[key], post["CHP"][key]) + + # def test_peak_load_outage_times(self): + # """ + # Purpose of this test is to test the endpoint /peak_load_outage_times + # """ + + # load = [100]*8760 + # load[40*24] = 200 + # load[50*24-1] = 300 + # load[70*24+13] = 300 + # load[170*24] = 300 + # load[243*24] = 400 + # outage_inputs = {"seasonal_peaks": True, + # "outage_duration": 95, + # "critical_load": load, + # "start_not_center_on_peaks": False + # } + # expected_time_steps = [50*24-1-47+1, 70*24+13-47+1, 170*24-47+1, 243*24-47+1] + # resp = self.api_client.post(f'/v3/peak_load_outage_times', data=outage_inputs) + # self.assertHttpOK(resp) + # resp = json.loads(resp.content) + # self.assertEquals(resp["outage_start_time_steps"], expected_time_steps) + + # outage_inputs["seasonal_peaks"] = False + # outage_inputs["start_not_center_on_peaks"] = True + # expected_time_steps = [243*24+1] + # resp = self.api_client.post(f'/v3/peak_load_outage_times', data=outage_inputs) + # self.assertHttpOK(resp) + # resp = json.loads(resp.content) + # self.assertEquals(resp["outage_start_time_steps"], expected_time_steps) + + # def test_superset_input_fields(self): + # """ + # Purpose of this test is to test the API's ability to accept all relevant + # input fields and send to REopt, ensuring name input consistency with REopt.jl. + # Note: Does not currently test CHP inputs + # """ + # post_file = os.path.join('reoptjl', 'test', 'posts', 'all_inputs_test.json') + # post = json.load(open(post_file, 'r')) + + # resp = self.api_client.post('/v3/job/', format='json', data=post) + # self.assertHttpCreated(resp) + # r = json.loads(resp.content) + # run_uuid = r.get('run_uuid') + + # resp = self.api_client.get(f'/v3/job/{run_uuid}/results') + # r = json.loads(resp.content) + # results = r["outputs"] + # self.assertAlmostEqual(results["Financial"]["npv"], -258533.19, delta=0.01*results["Financial"]["lcc"]) + # assert(resp.status_code==200) + + # def test_steamturbine_defaults_from_julia(self): + # # Test that the inputs_with_defaults_set_in_julia feature worked for SteamTurbine, consistent with /chp_defaults + # post_file = os.path.join('reoptjl', 'test', 'posts', 'steamturbine_defaults_post.json') + # post = json.load(open(post_file, 'r')) + + # # Call http.jl /reopt to run SteamTurbine scenario and get results for defaults from julia checking + # resp = self.api_client.post('/v3/job/', format='json', data=post) + # self.assertHttpCreated(resp) + # r = json.loads(resp.content) + # run_uuid = r.get('run_uuid') + + # resp = self.api_client.get(f'/v3/job/{run_uuid}/results') + # r = json.loads(resp.content) + + # inputs_steamturbine = r["inputs"]["SteamTurbine"] + + # avg_fuel_load = (post["SpaceHeatingLoad"]["annual_mmbtu"] + + # post["DomesticHotWaterLoad"]["annual_mmbtu"]) / 8760.0 - inputs_steamturbine_defaults = { - "prime_mover": "steam_turbine", - "avg_boiler_fuel_load_mmbtu_per_hour": avg_fuel_load - } - - # Call to the django view endpoint /chp_defaults which calls the http.jl endpoint - resp = self.api_client.get(f'/v3/chp_defaults', data=inputs_steamturbine_defaults) - view_response = json.loads(resp.content) - - for key in view_response["default_inputs"].keys(): - # skip this key because its NaN in REoptInputs but is populated in /chp_defaults response. - if key != "inlet_steam_temperature_degF": - if post["SteamTurbine"].get(key) is None: # Check that default got assigned consistent with /chp_defaults - self.assertEquals(inputs_steamturbine[key], view_response["default_inputs"][key]) - else: # Make sure we didn't overwrite user-input - self.assertEquals(inputs_steamturbine[key], post["SteamTurbine"][key]) - - def test_hybridghp(self): - post_file = os.path.join('reoptjl', 'test', 'posts', 'hybrid_ghp.json') - post = json.load(open(post_file, 'r')) - - post['GHP']['ghpghx_inputs'][0]['hybrid_ghx_sizing_method'] = 'Automatic' - post['GHP']['avoided_capex_by_ghp_present_value'] = 1.0e6 - post['GHP']['ghx_useful_life_years'] = 35 - - # Call http.jl /reopt to run SteamTurbine scenario and get results for defaults from julia checking - resp = self.api_client.post('/v3/job/', format='json', data=post) - self.assertHttpCreated(resp) - r = json.loads(resp.content) - run_uuid = r.get('run_uuid') - - resp = self.api_client.get(f'/v3/job/{run_uuid}/results') - r = json.loads(resp.content) - - # calculated_ghx_residual_value 117065.83 - self.assertAlmostEqual(r["outputs"]["GHP"]["ghx_residual_value_present_value"], 117065.83, delta=500) - - def test_centralghp(self): - post_file = os.path.join('reoptjl', 'test', 'posts', 'central_plant_ghp.json') - post = json.load(open(post_file, 'r')) - - # Call http.jl /reopt to run the central plant GHP scenario and get results for defaults from julia checking - resp = self.api_client.post('/v3/job/', format='json', data=post) - self.assertHttpCreated(resp) - r = json.loads(resp.content) - run_uuid = r.get('run_uuid') - - resp = self.api_client.get(f'/v3/job/{run_uuid}/results') - r = json.loads(resp.content) - - self.assertAlmostEqual(r["outputs"]["Financial"]["lifecycle_capital_costs"], 1046066.8, delta=1000) \ No newline at end of file + # inputs_steamturbine_defaults = { + # "prime_mover": "steam_turbine", + # "avg_boiler_fuel_load_mmbtu_per_hour": avg_fuel_load + # } + + # # Call to the django view endpoint /chp_defaults which calls the http.jl endpoint + # resp = self.api_client.get(f'/v3/chp_defaults', data=inputs_steamturbine_defaults) + # view_response = json.loads(resp.content) + + # for key in view_response["default_inputs"].keys(): + # # skip this key because its NaN in REoptInputs but is populated in /chp_defaults response. + # if key != "inlet_steam_temperature_degF": + # if post["SteamTurbine"].get(key) is None: # Check that default got assigned consistent with /chp_defaults + # self.assertEquals(inputs_steamturbine[key], view_response["default_inputs"][key]) + # else: # Make sure we didn't overwrite user-input + # self.assertEquals(inputs_steamturbine[key], post["SteamTurbine"][key]) + + # def test_hybridghp(self): + # post_file = os.path.join('reoptjl', 'test', 'posts', 'hybrid_ghp.json') + # post = json.load(open(post_file, 'r')) + + # post['GHP']['ghpghx_inputs'][0]['hybrid_ghx_sizing_method'] = 'Automatic' + # post['GHP']['avoided_capex_by_ghp_present_value'] = 1.0e6 + # post['GHP']['ghx_useful_life_years'] = 35 + + # # Call http.jl /reopt to run SteamTurbine scenario and get results for defaults from julia checking + # resp = self.api_client.post('/v3/job/', format='json', data=post) + # self.assertHttpCreated(resp) + # r = json.loads(resp.content) + # run_uuid = r.get('run_uuid') + + # resp = self.api_client.get(f'/v3/job/{run_uuid}/results') + # r = json.loads(resp.content) + + # # calculated_ghx_residual_value 117065.83 + # self.assertAlmostEqual(r["outputs"]["GHP"]["ghx_residual_value_present_value"], 117065.83, delta=500) + + # def test_centralghp(self): + # post_file = os.path.join('reoptjl', 'test', 'posts', 'central_plant_ghp.json') + # post = json.load(open(post_file, 'r')) + + # # Call http.jl /reopt to run the central plant GHP scenario and get results for defaults from julia checking + # resp = self.api_client.post('/v3/job/', format='json', data=post) + # self.assertHttpCreated(resp) + # r = json.loads(resp.content) + # run_uuid = r.get('run_uuid') + + # resp = self.api_client.get(f'/v3/job/{run_uuid}/results') + # r = json.loads(resp.content) + + # self.assertAlmostEqual(r["outputs"]["Financial"]["lifecycle_capital_costs"], 1046066.8, delta=1000) \ No newline at end of file diff --git a/reoptjl/test/test_validator.py b/reoptjl/test/test_validator.py index 7f73fdc06..0554e2005 100644 --- a/reoptjl/test/test_validator.py +++ b/reoptjl/test/test_validator.py @@ -13,334 +13,334 @@ def setUp(self): post_file = os.path.join('reoptjl', 'test', 'posts', 'validator_post.json') self.post = json.load(open(post_file, 'r')) - def test_elec_load_profile_length_validation_and_resampling(self): - """ - try different lengths of load profiles, where the following are valid: - - 8760 (hourly) - - 17520 (30 min) - - 35040 (15 min) - also confirm that up/down-sampling is working. - :return: None - """ - good_lengths = [8760, 17520, 35040] - bad_lengths = [8759, 17521] - - for length in bad_lengths + good_lengths: - post = copy.deepcopy(self.post) - post["APIMeta"]["run_uuid"] = uuid.uuid4() - post['ElectricLoad']['loads_kw'] = list(range(length)) - post['ElectricLoad']['critical_loads_kw'] = list(range(length)) - validator = InputValidator(post) - validator.clean() - validator.clean_fields() - validator.cross_clean() - - if length in good_lengths: - self.assertEquals(validator.is_valid, True) - - if length > 8760: # check downsampling - self.assertEquals(len(validator.models["ElectricLoad"].loads_kw), 8760) - self.assertEquals(len(validator.models["ElectricLoad"].critical_loads_kw), 8760) - assert("resampled inputs" in validator.messages) - - elif length in bad_lengths: - self.assertEquals(validator.is_valid, False) - assert('Invalid length' in validator.validation_errors['ElectricLoad']['loads_kw']) - assert('Invalid length' in validator.validation_errors['ElectricLoad']['critical_loads_kw']) - - # check upsampling - for time_steps_per_hour in [2, 4]: - post = copy.deepcopy(self.post) - post["APIMeta"]["run_uuid"] = uuid.uuid4() - post['ElectricLoad']['loads_kw'] = list(range(8760)) - post['ElectricLoad']['critical_loads_kw'] = list(range(8760)) - post['Settings']['time_steps_per_hour'] = time_steps_per_hour - validator = InputValidator(post) - validator.clean() - validator.clean_fields() - validator.cross_clean() - self.assertEquals(validator.is_valid, True) - self.assertEquals(len(validator.models["ElectricLoad"].loads_kw), time_steps_per_hour*8760) - self.assertEquals(len(validator.models["ElectricLoad"].critical_loads_kw), time_steps_per_hour*8760) - - def test_bad_blended_profile_inputs(self): - post = copy.deepcopy(self.post) - del(post["ElectricLoad"]["doe_reference_name"]) - post["ElectricLoad"]["blended_doe_reference_names"] = ["badname", "LargeOffice"] - post["ElectricLoad"]["blended_doe_reference_percents"] = [1.5] - validator = InputValidator(post) - validator.clean_fields() - - assert("'badname' is not a valid choice" - in validator.validation_errors['ElectricLoad']['blended_doe_reference_names'][0]) - assert("Ensure this value is less than or equal to 1.0" in - validator.validation_errors['ElectricLoad']['blended_doe_reference_percents'][0]) - - post["ElectricLoad"]["blended_doe_reference_names"] = ["MediumOffice", "LargeOffice"] - post["ElectricLoad"]["blended_doe_reference_percents"] = [0.5] - post["APIMeta"]["run_uuid"] = uuid.uuid4() - validator = InputValidator(post) - validator.clean_fields() - validator.clean() - - assert("The number of blended_doe_reference_names must equal the number of blended_doe_reference_percents" in - validator.validation_errors['ElectricLoad']['blended_doe_reference_names'][0]) - assert("Sum must = 1.0." in - validator.validation_errors['ElectricLoad']['blended_doe_reference_percents'][0]) - - def test_off_grid_defaults_overrides(self): - post_file = os.path.join('reoptjl', 'test', 'posts', 'off_grid_validations.json') - post = json.load(open(post_file, 'r')) + # def test_elec_load_profile_length_validation_and_resampling(self): + # """ + # try different lengths of load profiles, where the following are valid: + # - 8760 (hourly) + # - 17520 (30 min) + # - 35040 (15 min) + # also confirm that up/down-sampling is working. + # :return: None + # """ + # good_lengths = [8760, 17520, 35040] + # bad_lengths = [8759, 17521] + + # for length in bad_lengths + good_lengths: + # post = copy.deepcopy(self.post) + # post["APIMeta"]["run_uuid"] = uuid.uuid4() + # post['ElectricLoad']['loads_kw'] = list(range(length)) + # post['ElectricLoad']['critical_loads_kw'] = list(range(length)) + # validator = InputValidator(post) + # validator.clean() + # validator.clean_fields() + # validator.cross_clean() + + # if length in good_lengths: + # self.assertEquals(validator.is_valid, True) + + # if length > 8760: # check downsampling + # self.assertEquals(len(validator.models["ElectricLoad"].loads_kw), 8760) + # self.assertEquals(len(validator.models["ElectricLoad"].critical_loads_kw), 8760) + # assert("resampled inputs" in validator.messages) + + # elif length in bad_lengths: + # self.assertEquals(validator.is_valid, False) + # assert('Invalid length' in validator.validation_errors['ElectricLoad']['loads_kw']) + # assert('Invalid length' in validator.validation_errors['ElectricLoad']['critical_loads_kw']) + + # # check upsampling + # for time_steps_per_hour in [2, 4]: + # post = copy.deepcopy(self.post) + # post["APIMeta"]["run_uuid"] = uuid.uuid4() + # post['ElectricLoad']['loads_kw'] = list(range(8760)) + # post['ElectricLoad']['critical_loads_kw'] = list(range(8760)) + # post['Settings']['time_steps_per_hour'] = time_steps_per_hour + # validator = InputValidator(post) + # validator.clean() + # validator.clean_fields() + # validator.cross_clean() + # self.assertEquals(validator.is_valid, True) + # self.assertEquals(len(validator.models["ElectricLoad"].loads_kw), time_steps_per_hour*8760) + # self.assertEquals(len(validator.models["ElectricLoad"].critical_loads_kw), time_steps_per_hour*8760) + + # def test_bad_blended_profile_inputs(self): + # post = copy.deepcopy(self.post) + # del(post["ElectricLoad"]["doe_reference_name"]) + # post["ElectricLoad"]["blended_doe_reference_names"] = ["badname", "LargeOffice"] + # post["ElectricLoad"]["blended_doe_reference_percents"] = [1.5] + # validator = InputValidator(post) + # validator.clean_fields() + + # assert("'badname' is not a valid choice" + # in validator.validation_errors['ElectricLoad']['blended_doe_reference_names'][0]) + # assert("Ensure this value is less than or equal to 1.0" in + # validator.validation_errors['ElectricLoad']['blended_doe_reference_percents'][0]) + + # post["ElectricLoad"]["blended_doe_reference_names"] = ["MediumOffice", "LargeOffice"] + # post["ElectricLoad"]["blended_doe_reference_percents"] = [0.5] + # post["APIMeta"]["run_uuid"] = uuid.uuid4() + # validator = InputValidator(post) + # validator.clean_fields() + # validator.clean() + + # assert("The number of blended_doe_reference_names must equal the number of blended_doe_reference_percents" in + # validator.validation_errors['ElectricLoad']['blended_doe_reference_names'][0]) + # assert("Sum must = 1.0." in + # validator.validation_errors['ElectricLoad']['blended_doe_reference_percents'][0]) + + # def test_off_grid_defaults_overrides(self): + # post_file = os.path.join('reoptjl', 'test', 'posts', 'off_grid_validations.json') + # post = json.load(open(post_file, 'r')) - post["APIMeta"]["run_uuid"] = uuid.uuid4() - - validator = InputValidator(post) - validator.clean_fields() - validator.clean() - validator.cross_clean() - self.assertEquals(validator.is_valid, True) - - self.assertAlmostEqual(validator.models["Wind"].operating_reserve_required_fraction, 0.5) - self.assertAlmostEqual(validator.models["PV"].operating_reserve_required_fraction, 0.25) - self.assertEqual(validator.models["Wind"].installed_cost_per_kw, 4760.0) # set based on size_class - - self.assertAlmostEqual(validator.models["ElectricLoad"].operating_reserve_required_fraction, 0.1) - self.assertAlmostEqual(validator.models["ElectricLoad"].critical_load_fraction, 1.0) - self.assertAlmostEqual(validator.models["ElectricLoad"].min_load_met_annual_fraction, 0.99999) - - self.assertAlmostEqual(validator.models["Generator"].installed_cost_per_kw, 880) - self.assertAlmostEqual(validator.models["Generator"].om_cost_per_kw, 10) - self.assertAlmostEqual(validator.models["Generator"].fuel_avail_gal, 1.0e9) - self.assertAlmostEqual(validator.models["Generator"].min_turn_down_fraction, 0.15) - self.assertAlmostEqual(validator.models["Generator"].replacement_year, 10) - self.assertAlmostEqual(validator.models["Generator"].replace_cost_per_kw, validator.models["Generator"].installed_cost_per_kw) - - ## Test that some defaults can be overriden below - - post["ElectricLoad"]["operating_reserve_required_fraction"] = 0.2 - post["ElectricLoad"]["critical_load_fraction"] = 0.95 # cant override - post["ElectricLoad"]["min_load_met_annual_fraction"] = 0.95 + # post["APIMeta"]["run_uuid"] = uuid.uuid4() + + # validator = InputValidator(post) + # validator.clean_fields() + # validator.clean() + # validator.cross_clean() + # self.assertEquals(validator.is_valid, True) + + # self.assertAlmostEqual(validator.models["Wind"].operating_reserve_required_fraction, 0.5) + # self.assertAlmostEqual(validator.models["PV"].operating_reserve_required_fraction, 0.25) + # self.assertEqual(validator.models["Wind"].installed_cost_per_kw, 4760.0) # set based on size_class + + # self.assertAlmostEqual(validator.models["ElectricLoad"].operating_reserve_required_fraction, 0.1) + # self.assertAlmostEqual(validator.models["ElectricLoad"].critical_load_fraction, 1.0) + # self.assertAlmostEqual(validator.models["ElectricLoad"].min_load_met_annual_fraction, 0.99999) + + # self.assertAlmostEqual(validator.models["Generator"].installed_cost_per_kw, 880) + # self.assertAlmostEqual(validator.models["Generator"].om_cost_per_kw, 10) + # self.assertAlmostEqual(validator.models["Generator"].fuel_avail_gal, 1.0e9) + # self.assertAlmostEqual(validator.models["Generator"].min_turn_down_fraction, 0.15) + # self.assertAlmostEqual(validator.models["Generator"].replacement_year, 10) + # self.assertAlmostEqual(validator.models["Generator"].replace_cost_per_kw, validator.models["Generator"].installed_cost_per_kw) + + # ## Test that some defaults can be overriden below + + # post["ElectricLoad"]["operating_reserve_required_fraction"] = 0.2 + # post["ElectricLoad"]["critical_load_fraction"] = 0.95 # cant override + # post["ElectricLoad"]["min_load_met_annual_fraction"] = 0.95 - post["Generator"]["om_cost_per_kw"] = 21 - post["Generator"]["fuel_avail_gal"] = 10000 - post["Generator"]["min_turn_down_fraction"] = 0.14 - post["Generator"]["replacement_year"] = 7 - post["Generator"]["replace_cost_per_kw"] = 200 - - post["Wind"]["operating_reserve_required_fraction"] = 0.35 - post["PV"]["operating_reserve_required_fraction"] = 0.35 + # post["Generator"]["om_cost_per_kw"] = 21 + # post["Generator"]["fuel_avail_gal"] = 10000 + # post["Generator"]["min_turn_down_fraction"] = 0.14 + # post["Generator"]["replacement_year"] = 7 + # post["Generator"]["replace_cost_per_kw"] = 200 + + # post["Wind"]["operating_reserve_required_fraction"] = 0.35 + # post["PV"]["operating_reserve_required_fraction"] = 0.35 - post["APIMeta"]["run_uuid"] = uuid.uuid4() + # post["APIMeta"]["run_uuid"] = uuid.uuid4() - validator = InputValidator(post) - validator.clean_fields() - validator.clean() - validator.cross_clean() - self.assertEquals(validator.is_valid, True) + # validator = InputValidator(post) + # validator.clean_fields() + # validator.clean() + # validator.cross_clean() + # self.assertEquals(validator.is_valid, True) - self.assertAlmostEqual(validator.models["PV"].operating_reserve_required_fraction, 0.35) - self.assertAlmostEqual(validator.models["Wind"].operating_reserve_required_fraction, 0.35) + # self.assertAlmostEqual(validator.models["PV"].operating_reserve_required_fraction, 0.35) + # self.assertAlmostEqual(validator.models["Wind"].operating_reserve_required_fraction, 0.35) - self.assertAlmostEqual(validator.models["ElectricLoad"].operating_reserve_required_fraction, 0.2) - self.assertAlmostEqual(validator.models["ElectricLoad"].critical_load_fraction, 1.0) # cant override - self.assertAlmostEqual(validator.models["ElectricLoad"].min_load_met_annual_fraction, 0.95) + # self.assertAlmostEqual(validator.models["ElectricLoad"].operating_reserve_required_fraction, 0.2) + # self.assertAlmostEqual(validator.models["ElectricLoad"].critical_load_fraction, 1.0) # cant override + # self.assertAlmostEqual(validator.models["ElectricLoad"].min_load_met_annual_fraction, 0.95) - self.assertAlmostEqual(validator.models["Generator"].om_cost_per_kw, 21) - self.assertAlmostEqual(validator.models["Generator"].fuel_avail_gal, 10000) - self.assertAlmostEqual(validator.models["Generator"].min_turn_down_fraction, 0.14) - self.assertAlmostEqual(validator.models["Generator"].replacement_year, 7) - self.assertAlmostEqual(validator.models["Generator"].replace_cost_per_kw, 200.0) + # self.assertAlmostEqual(validator.models["Generator"].om_cost_per_kw, 21) + # self.assertAlmostEqual(validator.models["Generator"].fuel_avail_gal, 10000) + # self.assertAlmostEqual(validator.models["Generator"].min_turn_down_fraction, 0.14) + # self.assertAlmostEqual(validator.models["Generator"].replacement_year, 7) + # self.assertAlmostEqual(validator.models["Generator"].replace_cost_per_kw, 200.0) - def existingboiler_boiler_validation(self): + # def existingboiler_boiler_validation(self): - """ - Validate clean, cross-clean methods are working as expected - """ - post_file = os.path.join('reoptjl', 'test', 'posts', 'existing_boiler.json') - post = json.load(open(post_file, 'r')) + # """ + # Validate clean, cross-clean methods are working as expected + # """ + # post_file = os.path.join('reoptjl', 'test', 'posts', 'existing_boiler.json') + # post = json.load(open(post_file, 'r')) - post["APIMeta"]["run_uuid"] = uuid.uuid4() + # post["APIMeta"]["run_uuid"] = uuid.uuid4() - validator = InputValidator(post) - validator.clean_fields() - validator.clean() - validator.cross_clean() - self.assertEquals(validator.is_valid, True) + # validator = InputValidator(post) + # validator.clean_fields() + # validator.clean() + # validator.cross_clean() + # self.assertEquals(validator.is_valid, True) - self.assertAlmostEqual(validator.models["ExistingBoiler"].emissions_factor_lb_CO2_per_mmbtu, 117, places=-1) - self.assertAlmostEqual(len(validator.models["ExistingBoiler"].fuel_cost_per_mmbtu), 8760) - self.assertAlmostEqual(sum(validator.models["ExistingBoiler"].fuel_cost_per_mmbtu), 8760*0.5) + # self.assertAlmostEqual(validator.models["ExistingBoiler"].emissions_factor_lb_CO2_per_mmbtu, 117, places=-1) + # self.assertAlmostEqual(len(validator.models["ExistingBoiler"].fuel_cost_per_mmbtu), 8760) + # self.assertAlmostEqual(sum(validator.models["ExistingBoiler"].fuel_cost_per_mmbtu), 8760*0.5) - self.assertAlmostEqual(len(validator.models["Boiler"].fuel_cost_per_mmbtu), 8760) - self.assertAlmostEqual(sum(validator.models["Boiler"].fuel_cost_per_mmbtu), 8760*0.25) + # self.assertAlmostEqual(len(validator.models["Boiler"].fuel_cost_per_mmbtu), 8760) + # self.assertAlmostEqual(sum(validator.models["Boiler"].fuel_cost_per_mmbtu), 8760*0.25) - # Ensure Hot Thermal Storage System parameter is loaded from json - self.assertAlmostEqual(validator.models["HotThermalStorage"].max_gal, 2500.0) - - # Validate 12 month fuel cost vector gets scaled correctly - - post["ExistingBoiler"]["fuel_cost_per_mmbtu"] = [1,2,1,1,1,1,1,1,1,1,1,1] - post["Boiler"]["fuel_cost_per_mmbtu"] = [0.5,1,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5] - - post["APIMeta"]["run_uuid"] = uuid.uuid4() - - validator = InputValidator(post) - validator.clean_fields() - validator.clean() - validator.cross_clean() - self.assertEquals(validator.is_valid, True) - - self.assertAlmostEqual(len(validator.models["ExistingBoiler"].fuel_cost_per_mmbtu), 8760) - self.assertEqual(sum(validator.models["ExistingBoiler"].fuel_cost_per_mmbtu), 9432.0) - self.assertAlmostEqual(len(validator.models["Boiler"].fuel_cost_per_mmbtu), 8760) - self.assertEqual(sum(validator.models["Boiler"].fuel_cost_per_mmbtu), 9432.0*0.5) - - def test_missing_required_keys(self): - #start with on_grid, and keep all keys - required_ongrid_object_names = [ - "Site", "ElectricLoad", "ElectricTariff" - ] - required_offgrid_object_names = [ - "Site", "ElectricLoad" - ] - #prior to removal of keys, test lack or errors of validator with full inputs - post = copy.deepcopy(self.post) - post["APIMeta"]["run_uuid"] = uuid.uuid4() - post["Settings"]["off_grid_flag"] = False - validator = InputValidator(post) - for key in required_ongrid_object_names: - assert (key not in validator.validation_errors.keys()) - post = copy.deepcopy(self.post) - post["APIMeta"]["run_uuid"] = uuid.uuid4() - post["Settings"]["off_grid_flag"] = True - validator = InputValidator(post) - for key in required_ongrid_object_names: - assert (key not in validator.validation_errors.keys()) - #test ongrid removal of all ongrid keys, trigger an error for all required ongrid inputs - post = copy.deepcopy(self.post) - post["APIMeta"]["run_uuid"] = uuid.uuid4() - post["Settings"]["off_grid_flag"] = False - for key in required_ongrid_object_names: - del post[key] - validator = InputValidator(post) - for key in required_ongrid_object_names: - assert("Missing required inputs." in validator.validation_errors[key]) - #test offgrid removal of all offgrid keys, should trigger an error for required offgrid but not ongrid requirements - post = copy.deepcopy(self.post) - post["APIMeta"]["run_uuid"] = uuid.uuid4() - post["Settings"]["off_grid_flag"] = True - for key in required_ongrid_object_names: - del post[key] - validator = InputValidator(post) - for key in required_ongrid_object_names: - if key in required_offgrid_object_names: - assert("Missing required inputs." in validator.validation_errors[key]) - else: - assert(key not in validator.validation_errors.keys()) - - # check for missing CHP inputs - post = copy.deepcopy(self.post) - post["APIMeta"]["run_uuid"] = uuid.uuid4() - post["CHP"].pop("fuel_cost_per_mmbtu") - validator = InputValidator(post) - validator.clean_fields() - validator.clean() - validator.cross_clean() - assert("required inputs" in validator.validation_errors["CHP"].keys()) - - def test_multiple_outages_validation(self): - """ - ensure that validation of multiple outages post works as expected and catches errors - """ - post_file = os.path.join('reoptjl', 'test', 'posts', 'outage.json') - outage_post = json.load(open(post_file, 'r')) - outage_post["APIMeta"] = {"run_uuid": uuid.uuid4()} - outage_post["Meta"] = { - "description": "test description", - "address": "test address" - } - validator = InputValidator(outage_post) - validator.clean_fields() - validator.clean() - validator.cross_clean() - self.assertEquals(validator.is_valid, True) - - # test mismatched length - post = copy.deepcopy(outage_post) - post["ElectricUtility"]["outage_durations"] = [10,20,30,40] - post["ElectricUtility"]["outage_probabilities"] = [0.8,0.2] - post["APIMeta"]["run_uuid"] = uuid.uuid4() - validator = InputValidator(post) - validator.clean() - assert("mismatched length" in validator.validation_errors["ElectricUtility"].keys()) - - # test missing outage_durations - post = copy.deepcopy(outage_post) - post["ElectricUtility"].pop("outage_durations") - post["APIMeta"]["run_uuid"] = uuid.uuid4() - validator = InputValidator(post) - validator.clean() - assert("missing required inputs" in validator.validation_errors["ElectricUtility"].keys()) - - # test sum of outage_probabilities != 1 - post = copy.deepcopy(outage_post) - post["ElectricUtility"]["outage_durations"] = [10,20] - post["ElectricUtility"]["outage_probabilities"] = [0.5,0.6] - post["APIMeta"]["run_uuid"] = uuid.uuid4() - validator = InputValidator(post) - validator.clean() - assert("outage_probabilities" in validator.validation_errors["ElectricUtility"].keys()) - - # test missing outage_probabilities - post = copy.deepcopy(outage_post) - post["ElectricUtility"]["outage_durations"] = [10,20] - post["ElectricUtility"].pop("outage_probabilities") - post["APIMeta"]["run_uuid"] = uuid.uuid4() - validator = InputValidator(post) - validator.clean_fields() - validator.clean() - validator.cross_clean() - self.assertEquals(validator.models["ElectricUtility"].outage_probabilities, [0.5, 0.5]) - self.assertEquals(validator.is_valid, True) - - def test_pv_tilt_defaults(self): - post = copy.deepcopy(self.post) - post["APIMeta"]["run_uuid"] = uuid.uuid4() - del(post["ElectricStorage"]) - del(post["CHP"]) - del(post["PV"]["tilt"]) - validator = InputValidator(post) - validator.clean_fields() - validator.clean() - validator.cross_clean() - self.assertEquals(validator.models["PV"].tilt, 20) - - post["APIMeta"]["run_uuid"] = uuid.uuid4() - post["PV"]["array_type"] = 2 - validator = InputValidator(post) - validator.clean_fields() - validator.clean() - validator.cross_clean() - self.assertAlmostEquals(validator.models["PV"].tilt, 0) - - def boiler_validation(self): - """ - Validate clean, cross-clean methods are working as expected - """ - post_file = os.path.join('job', 'test', 'posts', 'boiler_test.json') - post = json.load(open(post_file, 'r')) - - post["APIMeta"]["run_uuid"] = uuid.uuid4() - - validator = InputValidator(post) - validator.clean_fields() - validator.clean() - validator.cross_clean() - self.assertEquals(validator.is_valid, True) - - # Update with Boiler test fields - # self.assertAlmostEqual(validator.models["ExistingBoiler"].emissions_factor_lb_CO2_per_mmbtu, 117, places=-1) - # self.assertAlmostEqual(len(validator.models["ExistingBoiler"].fuel_cost_per_mmbtu), 8760) - # self.assertAlmostEqual(sum(validator.models["ExistingBoiler"].fuel_cost_per_mmbtu), 8760*0.5) + # # Ensure Hot Thermal Storage System parameter is loaded from json + # self.assertAlmostEqual(validator.models["HotThermalStorage"].max_gal, 2500.0) + + # # Validate 12 month fuel cost vector gets scaled correctly + + # post["ExistingBoiler"]["fuel_cost_per_mmbtu"] = [1,2,1,1,1,1,1,1,1,1,1,1] + # post["Boiler"]["fuel_cost_per_mmbtu"] = [0.5,1,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5] + + # post["APIMeta"]["run_uuid"] = uuid.uuid4() + + # validator = InputValidator(post) + # validator.clean_fields() + # validator.clean() + # validator.cross_clean() + # self.assertEquals(validator.is_valid, True) + + # self.assertAlmostEqual(len(validator.models["ExistingBoiler"].fuel_cost_per_mmbtu), 8760) + # self.assertEqual(sum(validator.models["ExistingBoiler"].fuel_cost_per_mmbtu), 9432.0) + # self.assertAlmostEqual(len(validator.models["Boiler"].fuel_cost_per_mmbtu), 8760) + # self.assertEqual(sum(validator.models["Boiler"].fuel_cost_per_mmbtu), 9432.0*0.5) + + # def test_missing_required_keys(self): + # #start with on_grid, and keep all keys + # required_ongrid_object_names = [ + # "Site", "ElectricLoad", "ElectricTariff" + # ] + # required_offgrid_object_names = [ + # "Site", "ElectricLoad" + # ] + # #prior to removal of keys, test lack or errors of validator with full inputs + # post = copy.deepcopy(self.post) + # post["APIMeta"]["run_uuid"] = uuid.uuid4() + # post["Settings"]["off_grid_flag"] = False + # validator = InputValidator(post) + # for key in required_ongrid_object_names: + # assert (key not in validator.validation_errors.keys()) + # post = copy.deepcopy(self.post) + # post["APIMeta"]["run_uuid"] = uuid.uuid4() + # post["Settings"]["off_grid_flag"] = True + # validator = InputValidator(post) + # for key in required_ongrid_object_names: + # assert (key not in validator.validation_errors.keys()) + # #test ongrid removal of all ongrid keys, trigger an error for all required ongrid inputs + # post = copy.deepcopy(self.post) + # post["APIMeta"]["run_uuid"] = uuid.uuid4() + # post["Settings"]["off_grid_flag"] = False + # for key in required_ongrid_object_names: + # del post[key] + # validator = InputValidator(post) + # for key in required_ongrid_object_names: + # assert("Missing required inputs." in validator.validation_errors[key]) + # #test offgrid removal of all offgrid keys, should trigger an error for required offgrid but not ongrid requirements + # post = copy.deepcopy(self.post) + # post["APIMeta"]["run_uuid"] = uuid.uuid4() + # post["Settings"]["off_grid_flag"] = True + # for key in required_ongrid_object_names: + # del post[key] + # validator = InputValidator(post) + # for key in required_ongrid_object_names: + # if key in required_offgrid_object_names: + # assert("Missing required inputs." in validator.validation_errors[key]) + # else: + # assert(key not in validator.validation_errors.keys()) + + # # check for missing CHP inputs + # post = copy.deepcopy(self.post) + # post["APIMeta"]["run_uuid"] = uuid.uuid4() + # post["CHP"].pop("fuel_cost_per_mmbtu") + # validator = InputValidator(post) + # validator.clean_fields() + # validator.clean() + # validator.cross_clean() + # assert("required inputs" in validator.validation_errors["CHP"].keys()) + + # def test_multiple_outages_validation(self): + # """ + # ensure that validation of multiple outages post works as expected and catches errors + # """ + # post_file = os.path.join('reoptjl', 'test', 'posts', 'outage.json') + # outage_post = json.load(open(post_file, 'r')) + # outage_post["APIMeta"] = {"run_uuid": uuid.uuid4()} + # outage_post["Meta"] = { + # "description": "test description", + # "address": "test address" + # } + # validator = InputValidator(outage_post) + # validator.clean_fields() + # validator.clean() + # validator.cross_clean() + # self.assertEquals(validator.is_valid, True) + + # # test mismatched length + # post = copy.deepcopy(outage_post) + # post["ElectricUtility"]["outage_durations"] = [10,20,30,40] + # post["ElectricUtility"]["outage_probabilities"] = [0.8,0.2] + # post["APIMeta"]["run_uuid"] = uuid.uuid4() + # validator = InputValidator(post) + # validator.clean() + # assert("mismatched length" in validator.validation_errors["ElectricUtility"].keys()) + + # # test missing outage_durations + # post = copy.deepcopy(outage_post) + # post["ElectricUtility"].pop("outage_durations") + # post["APIMeta"]["run_uuid"] = uuid.uuid4() + # validator = InputValidator(post) + # validator.clean() + # assert("missing required inputs" in validator.validation_errors["ElectricUtility"].keys()) + + # # test sum of outage_probabilities != 1 + # post = copy.deepcopy(outage_post) + # post["ElectricUtility"]["outage_durations"] = [10,20] + # post["ElectricUtility"]["outage_probabilities"] = [0.5,0.6] + # post["APIMeta"]["run_uuid"] = uuid.uuid4() + # validator = InputValidator(post) + # validator.clean() + # assert("outage_probabilities" in validator.validation_errors["ElectricUtility"].keys()) + + # # test missing outage_probabilities + # post = copy.deepcopy(outage_post) + # post["ElectricUtility"]["outage_durations"] = [10,20] + # post["ElectricUtility"].pop("outage_probabilities") + # post["APIMeta"]["run_uuid"] = uuid.uuid4() + # validator = InputValidator(post) + # validator.clean_fields() + # validator.clean() + # validator.cross_clean() + # self.assertEquals(validator.models["ElectricUtility"].outage_probabilities, [0.5, 0.5]) + # self.assertEquals(validator.is_valid, True) + + # def test_pv_tilt_defaults(self): + # post = copy.deepcopy(self.post) + # post["APIMeta"]["run_uuid"] = uuid.uuid4() + # del(post["ElectricStorage"]) + # del(post["CHP"]) + # del(post["PV"]["tilt"]) + # validator = InputValidator(post) + # validator.clean_fields() + # validator.clean() + # validator.cross_clean() + # self.assertEquals(validator.models["PV"].tilt, 20) + + # post["APIMeta"]["run_uuid"] = uuid.uuid4() + # post["PV"]["array_type"] = 2 + # validator = InputValidator(post) + # validator.clean_fields() + # validator.clean() + # validator.cross_clean() + # self.assertAlmostEquals(validator.models["PV"].tilt, 0) + + # def boiler_validation(self): + # """ + # Validate clean, cross-clean methods are working as expected + # """ + # post_file = os.path.join('job', 'test', 'posts', 'boiler_test.json') + # post = json.load(open(post_file, 'r')) + + # post["APIMeta"]["run_uuid"] = uuid.uuid4() + + # validator = InputValidator(post) + # validator.clean_fields() + # validator.clean() + # validator.cross_clean() + # self.assertEquals(validator.is_valid, True) + + # # Update with Boiler test fields + # # self.assertAlmostEqual(validator.models["ExistingBoiler"].emissions_factor_lb_CO2_per_mmbtu, 117, places=-1) + # # self.assertAlmostEqual(len(validator.models["ExistingBoiler"].fuel_cost_per_mmbtu), 8760) + # # self.assertAlmostEqual(sum(validator.models["ExistingBoiler"].fuel_cost_per_mmbtu), 8760*0.5) diff --git a/resilience_stats/tests/test_erp.py b/resilience_stats/tests/test_erp.py index 8181f4290..b25871776 100644 --- a/resilience_stats/tests/test_erp.py +++ b/resilience_stats/tests/test_erp.py @@ -74,125 +74,125 @@ def test_erp_long_duration_battery(self): for i in range(99):#min(length(simresults["probs_of_surviving"]), reliability_inputs["max_outage_duration"]) self.assertAlmostEqual(results_sim["mean_cumulative_survival_by_duration"][i], expected_result[i], places=4) - def test_erp_large_battery(self): - """ - Tests calling ERP with PV, a small generator, and very large battery such that final survivial should be 1. - This is the same as the first test in the "Backup Generator Reliability" testset in the REopt Julia package. - """ - post_sim_large_stor = json.load(open(self.post_sim_large_stor, 'rb')) - - resp = self.get_response_sim(post_sim_large_stor) - self.assertHttpCreated(resp) - r_sim = json.loads(resp.content) - erp_run_uuid = r_sim.get('run_uuid') - - resp = self.get_results_sim(erp_run_uuid) - results = json.loads(resp.content) - - self.assertAlmostEqual(results["outputs"]["mean_cumulative_survival_final_time_step"], 1.0, places=4) - - def test_erp_with_reopt_run_uuid(self): - """ - Tests calling ERP with a REopt run_uuid provided, but all inputs from REopt results overrided. - This ends up being the same as the second to last test in the "Backup Generator Reliability" testset in the REopt Julia package. - Then tests calling ERP with the same REopt run_uuid provided, but only the necessary additional ERP inputs provided. - This used to be the same as the last test in the "Backup Generator Reliability" testset in the REopt Julia package, but need to make consistent again. - """ - - data = json.load(open(self.post_opt_gens_batt_pv_wind, 'rb')) - data["Settings"]["optimality_tolerance"] = 1.0e-2 # REopt_tol per line 38. - resp = self.get_response_opt(data) - self.assertHttpCreated(resp) - r_opt = json.loads(resp.content) - reopt_run_uuid = r_opt.get('run_uuid') - - assert(reopt_run_uuid is not None) - post_sim = json.load(open(self.post_sim_gens_batt_pv_wind, 'rb')) - post_sim["reopt_run_uuid"] = reopt_run_uuid - post_sim["ElectricStorage"]["starting_soc_series_fraction"] = 8760 * [1] - - resp = self.get_response_sim(post_sim) - self.assertHttpCreated(resp) - r_sim = json.loads(resp.content) - erp_run_uuid = r_sim.get('run_uuid') - - resp = self.get_results_sim(erp_run_uuid) - results = json.loads(resp.content) - self.assertAlmostEqual(results["outputs"]["unlimited_fuel_cumulative_survival_final_time_step"][0], 0.858756, places=4) - self.assertAlmostEqual(results["outputs"]["cumulative_survival_final_time_step"][0], 0.858756, places=4) - self.assertAlmostEqual(results["outputs"]["mean_cumulative_survival_final_time_step"], 0.904242, places=4) - - #remove inputs that override REopt results and run again - for model, field in [ - ("Generator","size_kw"), - ("PrimeGenerator","size_kw"), - ("ElectricStorage","size_kw"), - ("ElectricStorage","size_kwh"), - ("ElectricStorage","charge_efficiency"), - ("ElectricStorage","discharge_efficiency"), - ("ElectricStorage","starting_soc_series_fraction"), - ("PV","size_kw"), - ("PV","production_factor_series"), - ("Wind","size_kw"), - ("Wind","production_factor_series"), - ("Outage","critical_loads_kw"), - ("Outage","max_outage_duration") - ]: - post_sim.get(model,{}).pop(field, None) - - # add minimum_soc_fraction input to be consistent with test in REopt.jl - post_sim["ElectricStorage"]["minimum_soc_fraction"] = 0.2 + # def test_erp_large_battery(self): + # """ + # Tests calling ERP with PV, a small generator, and very large battery such that final survivial should be 1. + # This is the same as the first test in the "Backup Generator Reliability" testset in the REopt Julia package. + # """ + # post_sim_large_stor = json.load(open(self.post_sim_large_stor, 'rb')) + + # resp = self.get_response_sim(post_sim_large_stor) + # self.assertHttpCreated(resp) + # r_sim = json.loads(resp.content) + # erp_run_uuid = r_sim.get('run_uuid') + + # resp = self.get_results_sim(erp_run_uuid) + # results = json.loads(resp.content) + + # self.assertAlmostEqual(results["outputs"]["mean_cumulative_survival_final_time_step"], 1.0, places=4) + + # def test_erp_with_reopt_run_uuid(self): + # """ + # Tests calling ERP with a REopt run_uuid provided, but all inputs from REopt results overrided. + # This ends up being the same as the second to last test in the "Backup Generator Reliability" testset in the REopt Julia package. + # Then tests calling ERP with the same REopt run_uuid provided, but only the necessary additional ERP inputs provided. + # This used to be the same as the last test in the "Backup Generator Reliability" testset in the REopt Julia package, but need to make consistent again. + # """ + + # data = json.load(open(self.post_opt_gens_batt_pv_wind, 'rb')) + # data["Settings"]["optimality_tolerance"] = 1.0e-2 # REopt_tol per line 38. + # resp = self.get_response_opt(data) + # self.assertHttpCreated(resp) + # r_opt = json.loads(resp.content) + # reopt_run_uuid = r_opt.get('run_uuid') + + # assert(reopt_run_uuid is not None) + # post_sim = json.load(open(self.post_sim_gens_batt_pv_wind, 'rb')) + # post_sim["reopt_run_uuid"] = reopt_run_uuid + # post_sim["ElectricStorage"]["starting_soc_series_fraction"] = 8760 * [1] + + # resp = self.get_response_sim(post_sim) + # self.assertHttpCreated(resp) + # r_sim = json.loads(resp.content) + # erp_run_uuid = r_sim.get('run_uuid') + + # resp = self.get_results_sim(erp_run_uuid) + # results = json.loads(resp.content) + # self.assertAlmostEqual(results["outputs"]["unlimited_fuel_cumulative_survival_final_time_step"][0], 0.858756, places=4) + # self.assertAlmostEqual(results["outputs"]["cumulative_survival_final_time_step"][0], 0.858756, places=4) + # self.assertAlmostEqual(results["outputs"]["mean_cumulative_survival_final_time_step"], 0.904242, places=4) + + # #remove inputs that override REopt results and run again + # for model, field in [ + # ("Generator","size_kw"), + # ("PrimeGenerator","size_kw"), + # ("ElectricStorage","size_kw"), + # ("ElectricStorage","size_kwh"), + # ("ElectricStorage","charge_efficiency"), + # ("ElectricStorage","discharge_efficiency"), + # ("ElectricStorage","starting_soc_series_fraction"), + # ("PV","size_kw"), + # ("PV","production_factor_series"), + # ("Wind","size_kw"), + # ("Wind","production_factor_series"), + # ("Outage","critical_loads_kw"), + # ("Outage","max_outage_duration") + # ]: + # post_sim.get(model,{}).pop(field, None) + + # # add minimum_soc_fraction input to be consistent with test in REopt.jl + # post_sim["ElectricStorage"]["minimum_soc_fraction"] = 0.2 - resp = self.get_response_sim(post_sim) - self.assertHttpCreated(resp) - r_sim = json.loads(resp.content) - erp_run_uuid = r_sim.get('run_uuid') - - resp = self.get_results_sim(erp_run_uuid) - results = json.loads(resp.content) - self.assertAlmostEqual(results["outputs"]["mean_cumulative_survival_by_duration"][23], 0.9661, delta=0.0015) - self.assertAlmostEqual(results["outputs"]["cumulative_survival_final_time_step"][0], 0.962327, places=3) - self.assertAlmostEqual(results["outputs"]["mean_cumulative_survival_final_time_step"], 0.9661, places=2) - - def test_erp_with_no_opt(self): - """ - Tests calling ERP on it's own without providing a REopt run_uuid. - Same as the second to last test in the "Backup Generator Reliability" testset in the REopt Julia package. - """ + # resp = self.get_response_sim(post_sim) + # self.assertHttpCreated(resp) + # r_sim = json.loads(resp.content) + # erp_run_uuid = r_sim.get('run_uuid') + + # resp = self.get_results_sim(erp_run_uuid) + # results = json.loads(resp.content) + # self.assertAlmostEqual(results["outputs"]["mean_cumulative_survival_by_duration"][23], 0.9661, delta=0.0015) + # self.assertAlmostEqual(results["outputs"]["cumulative_survival_final_time_step"][0], 0.962327, places=3) + # self.assertAlmostEqual(results["outputs"]["mean_cumulative_survival_final_time_step"], 0.9661, places=2) + + # def test_erp_with_no_opt(self): + # """ + # Tests calling ERP on it's own without providing a REopt run_uuid. + # Same as the second to last test in the "Backup Generator Reliability" testset in the REopt Julia package. + # """ - post_sim = json.load(open(self.post_sim_only, 'rb')) - - resp = self.get_response_sim(post_sim) - self.assertHttpCreated(resp) - r_sim = json.loads(resp.content) - #TODO: don't return run_uuid when there's a REoptFailedToStartError - erp_run_uuid = r_sim.get('run_uuid') - - resp = self.get_results_sim(erp_run_uuid) - results = json.loads(resp.content) - - self.assertAlmostEqual(results["outputs"]["mean_cumulative_survival_final_time_step"], 0.904242, places=4) #0.990784, places=4) - - # Test that zero battery doesn't cause error - post_sim["ElectricStorage"]["size_kwh"] = 0 - post_sim["ElectricStorage"]["size_kwh"] = 0 - resp = self.get_response_sim(post_sim) - self.assertHttpCreated(resp) - r_sim = json.loads(resp.content) - erp_run_uuid = r_sim.get('run_uuid') - resp = self.get_results_sim(erp_run_uuid) - self.assertHttpOK(resp) - - def test_erp_help_view(self): - """ - Tests hiting the erp/help url to get defaults and other info about inputs - """ + # post_sim = json.load(open(self.post_sim_only, 'rb')) + + # resp = self.get_response_sim(post_sim) + # self.assertHttpCreated(resp) + # r_sim = json.loads(resp.content) + # #TODO: don't return run_uuid when there's a REoptFailedToStartError + # erp_run_uuid = r_sim.get('run_uuid') + + # resp = self.get_results_sim(erp_run_uuid) + # results = json.loads(resp.content) + + # self.assertAlmostEqual(results["outputs"]["mean_cumulative_survival_final_time_step"], 0.904242, places=4) #0.990784, places=4) + + # # Test that zero battery doesn't cause error + # post_sim["ElectricStorage"]["size_kwh"] = 0 + # post_sim["ElectricStorage"]["size_kwh"] = 0 + # resp = self.get_response_sim(post_sim) + # self.assertHttpCreated(resp) + # r_sim = json.loads(resp.content) + # erp_run_uuid = r_sim.get('run_uuid') + # resp = self.get_results_sim(erp_run_uuid) + # self.assertHttpOK(resp) + + # def test_erp_help_view(self): + # """ + # Tests hiting the erp/help url to get defaults and other info about inputs + # """ - resp = self.get_help() - self.assertHttpOK(resp) - resp = json.loads(resp.content) + # resp = self.get_help() + # self.assertHttpOK(resp) + # resp = json.loads(resp.content) - resp = self.get_chp_defaults("recip_engine", True, 10000) - self.assertHttpOK(resp) - resp = json.loads(resp.content) + # resp = self.get_chp_defaults("recip_engine", True, 10000) + # self.assertHttpOK(resp) + # resp = json.loads(resp.content) \ No newline at end of file diff --git a/resilience_stats/tests/test_new_post_endpoint.py b/resilience_stats/tests/test_new_post_endpoint.py index 69262b687..291e6b05c 100644 --- a/resilience_stats/tests/test_new_post_endpoint.py +++ b/resilience_stats/tests/test_new_post_endpoint.py @@ -27,20 +27,20 @@ def get_response_sim(self, data_sim): return self.api_client.post(self.reopt_base_sim, format='json', data=data_sim) - def test_both_modules(self): - """ - temporary test setup for running an optimizatin problem that precedes the following test for new endpoint for outage simulation module. - :return: - """ - data = json.load(open(self.post_opt, 'rb')) - resp = self.get_response_opt(data) - self.assertHttpCreated(resp) - r_opt = json.loads(resp.content) - run_uuid = r_opt.get('run_uuid') - - assert(run_uuid is not None) - post_sim = {"run_uuid": run_uuid, "bau": True} - resp = self.get_response_sim(data_sim=post_sim) - self.assertHttpCreated(resp) - r_sim = json.loads(resp.content) - # print("Response from outagesimjob:", r_sim) + # def test_both_modules(self): + # """ + # temporary test setup for running an optimizatin problem that precedes the following test for new endpoint for outage simulation module. + # :return: + # """ + # data = json.load(open(self.post_opt, 'rb')) + # resp = self.get_response_opt(data) + # self.assertHttpCreated(resp) + # r_opt = json.loads(resp.content) + # run_uuid = r_opt.get('run_uuid') + + # assert(run_uuid is not None) + # post_sim = {"run_uuid": run_uuid, "bau": True} + # resp = self.get_response_sim(data_sim=post_sim) + # self.assertHttpCreated(resp) + # r_sim = json.loads(resp.content) + # # print("Response from outagesimjob:", r_sim) diff --git a/resilience_stats/tests/test_resilience_stats.py b/resilience_stats/tests/test_resilience_stats.py index 0023ff6f0..a0d495ff0 100644 --- a/resilience_stats/tests/test_resilience_stats.py +++ b/resilience_stats/tests/test_resilience_stats.py @@ -55,362 +55,362 @@ def readFiles(path): self.inputs1 = readFiles('timestep_1') self.inputs2 = readFiles('timestep_2') - def test_outage_sim_no_diesel(self): - """ - For the case that no diesel generator is on site, outage simulation with load following strategy should have the - same results as existing simulation's results. - """ - inputs = self.inputs - inputs.update(diesel_kw=0, fuel_available=0, m=0, b=0) + # def test_outage_sim_no_diesel(self): + # """ + # For the case that no diesel generator is on site, outage simulation with load following strategy should have the + # same results as existing simulation's results. + # """ + # inputs = self.inputs + # inputs.update(diesel_kw=0, fuel_available=0, m=0, b=0) - # Output parse from existing simulation - expected = { - 'resilience_hours_min': 0, - 'resilience_hours_max': 78, - 'resilience_hours_avg': 10.23, - "outage_durations": list(range(1, 79)), - "probs_of_surviving": [0.8486, 0.7963, 0.7373, 0.6624, 0.59, 0.5194, 0.4533, 0.4007, 0.3583, 0.3231, 0.2933, - 0.2691, 0.2471, 0.2297, 0.215, 0.2015, 0.1897, 0.1788, 0.1694, 0.1608, 0.153, 0.1454, - 0.1381, 0.1308, 0.1247, 0.1184, 0.1122, 0.1065, 0.1013, 0.0968, 0.0927, 0.0887, 0.0847, - 0.0807, 0.0767, 0.0727, 0.0687, 0.0648, 0.0607, 0.0562, 0.0516, 0.047, 0.0425, 0.0376, - 0.0326, 0.0277, 0.0228, 0.0179, 0.0146, 0.0116, 0.0097, 0.0081, 0.0071, 0.0066, 0.0062, - 0.0059, 0.0057, 0.0055, 0.0053, 0.005, 0.0048, 0.0046, 0.0043, 0.0041, 0.0039, 0.0037, - 0.0032, 0.0027, 0.0023, 0.0018, 0.0014, 0.0009, 0.0007, 0.0006, 0.0005, 0.0003, 0.0002, - 0.0001], - "probs_of_surviving_by_month": [ - [1.0, 0.7688, 0.7003, 0.6277, 0.5511, 0.4785, 0.4005, 0.3266, 0.2769, 0.2312, 0.1989, 0.1774, 0.168, - 0.1586, 0.1519, 0.1452, 0.1358, 0.1263, 0.1169, 0.1075, 0.0981, 0.0887, 0.078, 0.0672, 0.0591, 0.0538, - 0.0484, 0.043, 0.0376, 0.0323, 0.0309, 0.0296, 0.0282, 0.0269, 0.0255, 0.0242, 0.0228, 0.0215, 0.0202, - 0.0188, 0.0175, 0.0161, 0.0148, 0.0134, 0.0121, 0.0108, 0.0094, 0.0081, 0.0067, 0.0054, 0.004, 0.0027, - 0.0013, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [1.0, 0.8036, 0.7381, 0.6756, 0.6131, 0.5208, 0.4315, 0.3571, 0.3021, 0.2589, 0.2262, 0.2024, 0.1935, - 0.183, 0.1756, 0.1696, 0.1607, 0.1548, 0.1473, 0.1384, 0.1324, 0.1265, 0.1205, 0.1146, 0.1086, 0.1027, - 0.0967, 0.0908, 0.0848, 0.0804, 0.0774, 0.0744, 0.0729, 0.0714, 0.0699, 0.0685, 0.067, 0.0655, 0.064, - 0.061, 0.0565, 0.0521, 0.0476, 0.0446, 0.0387, 0.0327, 0.0268, 0.0208, 0.0149, 0.0134, 0.0119, 0.0104, - 0.0089, 0.0089, 0.0089, 0.0089, 0.0089, 0.0089, 0.0089, 0.0089, 0.0089, 0.0089, 0.0089, 0.0089, 0.0089, - 0.0089, 0.0089, 0.0074, 0.006, 0.0045, 0.003, 0.0015, 0, 0, 0, 0, 0, 0, 0], - [1.0, 0.8212, 0.7527, 0.6935, 0.6035, 0.5376, 0.461, 0.3898, 0.328, 0.2782, 0.2419, 0.2124, 0.1922, - 0.1761, 0.1667, 0.1559, 0.1452, 0.1358, 0.129, 0.1223, 0.1169, 0.1116, 0.1062, 0.1008, 0.0954, 0.0901, - 0.0847, 0.0793, 0.0766, 0.0753, 0.0739, 0.0726, 0.0712, 0.0699, 0.0685, 0.0672, 0.0659, 0.0645, 0.0632, - 0.0618, 0.0578, 0.0538, 0.0484, 0.043, 0.0376, 0.0323, 0.0269, 0.0215, 0.0161, 0.0134, 0.0108, 0.0081, - 0.0054, 0.0027, 0.0013, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [1.0, 0.8889, 0.8292, 0.7694, 0.6917, 0.6194, 0.5542, 0.4903, 0.4431, 0.4069, 0.3708, 0.3375, 0.3069, - 0.2792, 0.2639, 0.25, 0.2389, 0.2278, 0.2181, 0.2083, 0.1986, 0.1903, 0.1833, 0.1764, 0.1694, 0.1625, - 0.1556, 0.1486, 0.1417, 0.1347, 0.1278, 0.1208, 0.1139, 0.1069, 0.1, 0.0931, 0.0861, 0.0792, 0.0722, - 0.0653, 0.0583, 0.0514, 0.0444, 0.0375, 0.0306, 0.0236, 0.0167, 0.0097, 0.0028, 0.0014, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [1.0, 0.9059, 0.8723, 0.8293, 0.7796, 0.6962, 0.6358, 0.578, 0.5215, 0.4785, 0.4409, 0.4005, 0.3656, - 0.3333, 0.3051, 0.2809, 0.2608, 0.2419, 0.2285, 0.2177, 0.207, 0.1989, 0.1935, 0.1882, 0.1828, 0.1774, - 0.172, 0.1667, 0.1613, 0.1559, 0.1505, 0.1452, 0.1398, 0.1344, 0.129, 0.1237, 0.1183, 0.1129, 0.1075, - 0.1022, 0.0968, 0.0914, 0.086, 0.0806, 0.0753, 0.0699, 0.0645, 0.0591, 0.0538, 0.0484, 0.043, 0.039, - 0.0363, 0.0349, 0.0336, 0.0323, 0.0309, 0.0296, 0.0282, 0.0269, 0.0255, 0.0242, 0.0228, 0.0215, 0.0202, - 0.0188, 0.0175, 0.0161, 0.0148, 0.0134, 0.0121, 0.0108, 0.0094, 0.0081, 0.0067, 0.0054, 0.004, 0.0027, - 0.0013], - [1.0, 0.9111, 0.8792, 0.8292, 0.7597, 0.6708, 0.5944, 0.5403, 0.4875, 0.4444, 0.4028, 0.3667, 0.3306, - 0.2972, 0.2667, 0.2431, 0.225, 0.2083, 0.1944, 0.1861, 0.1792, 0.1736, 0.1681, 0.1625, 0.1569, 0.1514, - 0.1458, 0.1403, 0.1347, 0.1292, 0.1236, 0.1181, 0.1125, 0.1069, 0.1014, 0.0958, 0.0903, 0.0847, 0.0792, - 0.0736, 0.0681, 0.0625, 0.0569, 0.0514, 0.0458, 0.0403, 0.0347, 0.0292, 0.0236, 0.0181, 0.0125, 0.0083, - 0.0042, 0.0028, 0.0014, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [1.0, 0.9194, 0.8831, 0.8293, 0.7433, 0.6815, 0.6022, 0.5497, 0.5067, 0.4718, 0.4368, 0.4046, 0.3696, - 0.3414, 0.3172, 0.2997, 0.2823, 0.2661, 0.2513, 0.2392, 0.2285, 0.2177, 0.207, 0.1976, 0.1895, 0.1815, - 0.1734, 0.1653, 0.1573, 0.1492, 0.1425, 0.1358, 0.129, 0.1223, 0.1156, 0.1089, 0.1022, 0.0954, 0.0887, - 0.082, 0.0753, 0.0685, 0.0618, 0.0551, 0.0484, 0.0417, 0.0349, 0.0282, 0.0215, 0.0148, 0.0081, 0.0054, - 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [1.0, 0.8992, 0.8589, 0.8065, 0.7285, 0.664, 0.5968, 0.5228, 0.4583, 0.4086, 0.3723, 0.336, 0.3024, - 0.2702, 0.2433, 0.2177, 0.2016, 0.1909, 0.1815, 0.1734, 0.1653, 0.1586, 0.1519, 0.1465, 0.1411, 0.1358, - 0.129, 0.1237, 0.1183, 0.1129, 0.1075, 0.1022, 0.0968, 0.0914, 0.086, 0.0806, 0.0753, 0.0699, 0.0645, - 0.0591, 0.0538, 0.0484, 0.043, 0.0376, 0.0323, 0.0269, 0.0215, 0.0161, 0.0108, 0.0054, 0.0027, 0.0013, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [1.0, 0.8708, 0.8347, 0.7819, 0.7042, 0.6181, 0.5486, 0.475, 0.4333, 0.4028, 0.3708, 0.3375, 0.3083, - 0.2819, 0.2611, 0.2472, 0.2361, 0.2278, 0.2194, 0.2139, 0.2083, 0.2028, 0.1972, 0.1917, 0.1861, 0.1806, - 0.175, 0.1694, 0.1639, 0.1583, 0.1528, 0.1472, 0.1417, 0.1361, 0.1306, 0.125, 0.1194, 0.1139, 0.1083, - 0.1028, 0.0972, 0.0917, 0.0861, 0.0792, 0.0722, 0.0653, 0.0583, 0.0514, 0.0444, 0.0389, 0.0333, 0.0306, - 0.0292, 0.0278, 0.0264, 0.025, 0.0236, 0.0222, 0.0208, 0.0194, 0.0181, 0.0167, 0.0153, 0.0139, 0.0125, - 0.0111, 0.0097, 0.0083, 0.0069, 0.0056, 0.0042, 0.0028, 0.0014, 0, 0, 0, 0, 0, 0], - [1.0, 0.8199, 0.7608, 0.7016, 0.6223, 0.5685, 0.5161, 0.4543, 0.3952, 0.3495, 0.3172, 0.2863, 0.2648, - 0.246, 0.2272, 0.211, 0.1976, 0.1868, 0.1747, 0.1653, 0.1559, 0.1478, 0.1411, 0.1344, 0.1277, 0.121, - 0.1142, 0.1075, 0.1035, 0.0995, 0.0954, 0.0914, 0.0874, 0.0833, 0.0793, 0.0753, 0.0712, 0.0672, 0.0645, - 0.0605, 0.0551, 0.0497, 0.0444, 0.039, 0.0336, 0.0282, 0.0228, 0.0175, 0.0121, 0.0108, 0.0094, 0.0081, - 0.0081, 0.0081, 0.0081, 0.0081, 0.0081, 0.0081, 0.0081, 0.0081, 0.0081, 0.0081, 0.0081, 0.0081, 0.0081, - 0.0081, 0.0081, 0.0067, 0.0054, 0.004, 0.0027, 0.0013, 0, 0, 0, 0, 0, 0, 0], - [1.0, 0.7931, 0.7319, 0.6542, 0.5819, 0.525, 0.4653, 0.4056, 0.3542, 0.3153, 0.2792, 0.2542, 0.2375, - 0.2208, 0.2069, 0.1958, 0.1819, 0.1694, 0.1542, 0.1417, 0.1319, 0.1222, 0.1125, 0.1028, 0.0875, 0.0806, - 0.0736, 0.0667, 0.0597, 0.0542, 0.0486, 0.0458, 0.0431, 0.0403, 0.0375, 0.0347, 0.0319, 0.0292, 0.0264, - 0.0236, 0.0208, 0.0181, 0.0167, 0.0153, 0.0125, 0.0097, 0.0069, 0.0042, 0.0014, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [1.0, 0.7796, 0.7124, 0.6465, 0.5685, 0.4946, 0.4207, 0.3441, 0.2957, 0.2487, 0.2137, 0.1989, 0.1855, - 0.1747, 0.168, 0.1613, 0.1505, 0.1398, 0.129, 0.1183, 0.1075, 0.0968, 0.086, 0.0753, 0.0659, 0.0591, - 0.0524, 0.0457, 0.039, 0.0336, 0.0309, 0.0296, 0.0282, 0.0269, 0.0255, 0.0242, 0.0228, 0.0215, 0.0202, - 0.0188, 0.0175, 0.0161, 0.0148, 0.0134, 0.0121, 0.0108, 0.0094, 0.0081, 0.0067, 0.0054, 0.004, 0.0027, - 0.0013, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], - "probs_of_surviving_by_hour_of_the_day": [ - [1.0, 1.0, 1.0, 0.9863, 0.8986, 0.8, 0.5233, 0.2247, 0.1671, 0.1644, 0.1644, 0.1644, 0.1644, 0.1644, - 0.1616, 0.1616, 0.1616, 0.1534, 0.1425, 0.1068, 0.1068, 0.1068, 0.1068, 0.1068, 0.1068, 0.1068, 0.1068, - 0.1068, 0.1068, 0.1068, 0.0795, 0.0438, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, - 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, - 0.0082, 0.0082, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [1.0, 1.0, 1.0, 0.9863, 0.8959, 0.6301, 0.3014, 0.2164, 0.2137, 0.2137, 0.2137, 0.211, 0.211, 0.2082, - 0.2082, 0.2082, 0.1973, 0.1863, 0.126, 0.126, 0.126, 0.126, 0.126, 0.126, 0.126, 0.126, 0.126, 0.126, - 0.126, 0.0959, 0.0521, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, - 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0137, - 0.0082, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, - 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027], - [1.0, 1.0, 1.0, 0.9863, 0.7123, 0.3644, 0.2904, 0.2822, 0.2822, 0.2822, 0.2822, 0.2795, 0.274, 0.274, - 0.274, 0.2603, 0.2356, 0.1452, 0.1452, 0.1452, 0.1452, 0.1452, 0.1452, 0.1452, 0.1452, 0.1452, 0.1452, - 0.1452, 0.1151, 0.0575, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, - 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0192, - 0.0082, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, - 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0], - [1.0, 1.0, 1.0, 0.8055, 0.474, 0.3616, 0.3562, 0.3562, 0.3562, 0.3562, 0.3534, 0.3479, 0.3479, 0.3479, - 0.3233, 0.2904, 0.1836, 0.1836, 0.1836, 0.1836, 0.1836, 0.1836, 0.1836, 0.1836, 0.1808, 0.1808, 0.1808, - 0.1397, 0.0712, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, - 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0274, 0.0137, - 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, - 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0, 0], - [1.0, 1.0, 0.863, 0.5315, 0.4521, 0.4493, 0.4466, 0.4466, 0.4466, 0.4438, 0.4384, 0.4384, 0.4356, 0.411, - 0.3616, 0.2055, 0.2055, 0.2055, 0.2055, 0.2055, 0.2055, 0.2055, 0.2055, 0.2055, 0.2027, 0.2027, 0.1589, - 0.0904, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, - 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0411, 0.0247, 0.0027, - 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, - 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0, 0, 0], - [1.0, 0.8712, 0.5808, 0.5014, 0.4959, 0.4959, 0.4959, 0.4959, 0.4932, 0.4877, 0.4877, 0.4849, 0.4575, - 0.4027, 0.2137, 0.2137, 0.2137, 0.2137, 0.2137, 0.2137, 0.2137, 0.2137, 0.2137, 0.2137, 0.211, 0.1699, - 0.1014, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, - 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.0521, 0.0274, 0.0027, 0.0027, 0.0027, - 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, - 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0, 0, 0, 0], - [1.0, 0.7041, 0.6685, 0.6685, 0.6658, 0.6658, 0.6658, 0.663, 0.6575, 0.6575, 0.6548, 0.6055, 0.526, - 0.2712, 0.2712, 0.2685, 0.2685, 0.2685, 0.2685, 0.2685, 0.2685, 0.2685, 0.2685, 0.2658, 0.2219, 0.1534, - 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, - 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.0767, 0.0438, 0.0082, 0.0082, - 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, - 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0055, 0.0027, 0, 0, 0, 0, 0], - [1.0, 0.937, 0.9233, 0.9178, 0.9068, 0.9068, 0.9014, 0.8904, 0.8904, 0.8767, 0.7205, 0.5836, 0.3014, - 0.3014, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2521, 0.1671, 0.1288, - 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, - 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.0904, 0.0466, 0.011, 0.011, 0.011, - 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, - 0.011, 0.011, 0.011, 0.011, 0.011, 0.0082, 0.0027, 0, 0, 0, 0, 0, 0], - [1.0, 0.9671, 0.9562, 0.9479, 0.9479, 0.9425, 0.9288, 0.926, 0.9096, 0.7397, 0.5918, 0.3014, 0.3014, - 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2521, 0.1671, 0.1288, 0.1288, - 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, - 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.0904, 0.0466, 0.011, 0.011, 0.011, 0.011, - 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, - 0.011, 0.011, 0.011, 0.011, 0.0082, 0.0027, 0, 0, 0, 0, 0, 0, 0], - [1.0, 0.9699, 0.9589, 0.9589, 0.9534, 0.937, 0.937, 0.9178, 0.7397, 0.5973, 0.3014, 0.3014, 0.2986, - 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2548, 0.1671, 0.1288, 0.1288, 0.1288, - 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, - 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.0904, 0.0466, 0.011, 0.011, 0.011, 0.011, 0.011, - 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, - 0.011, 0.011, 0.011, 0.0082, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0], - [1.0, 0.9781, 0.9699, 0.9644, 0.9479, 0.9452, 0.9288, 0.7397, 0.5973, 0.3014, 0.3014, 0.2986, 0.2986, - 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2548, 0.1671, 0.1288, 0.1288, 0.1288, 0.1288, - 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, - 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.0904, 0.0466, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, - 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, - 0.011, 0.011, 0.0082, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [1.0, 0.9863, 0.9726, 0.9534, 0.9507, 0.9315, 0.7397, 0.5973, 0.3014, 0.3014, 0.2986, 0.2986, 0.2986, - 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2548, 0.1671, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, - 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, - 0.1288, 0.1288, 0.1288, 0.1288, 0.0904, 0.0466, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, - 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, - 0.0082, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [1.0, 0.9836, 0.9616, 0.9589, 0.937, 0.7397, 0.5973, 0.3014, 0.3014, 0.2986, 0.2986, 0.2986, 0.2986, - 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2466, 0.1534, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, - 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, - 0.1151, 0.1151, 0.1151, 0.0795, 0.0438, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, - 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, - 0.0082, 0.0082, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [1.0, 0.9644, 0.9616, 0.9397, 0.7397, 0.5973, 0.3014, 0.3014, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, - 0.2986, 0.2986, 0.2959, 0.2932, 0.2438, 0.1507, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, - 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, - 0.1123, 0.1123, 0.0795, 0.0411, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, - 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, - 0.0055, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [1.0, 0.9726, 0.9479, 0.7397, 0.5973, 0.3014, 0.3014, 0.2986, 0.2986, 0.2986, 0.2986, 0.2959, 0.2932, - 0.2932, 0.2904, 0.2904, 0.2384, 0.1534, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, - 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, - 0.1151, 0.0822, 0.0411, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, - 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, - 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [1.0, 0.9562, 0.7452, 0.6027, 0.3014, 0.3014, 0.2986, 0.2986, 0.2932, 0.2932, 0.2932, 0.2904, 0.2904, - 0.2822, 0.2822, 0.2356, 0.1479, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, - 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, - 0.0822, 0.0411, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, - 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0027, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [1.0, 0.7479, 0.6, 0.3014, 0.3014, 0.2959, 0.2932, 0.2932, 0.2877, 0.2877, 0.2822, 0.2822, 0.2795, - 0.2575, 0.2082, 0.1397, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, - 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.0767, - 0.0411, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, - 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0027, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [1.0, 0.6137, 0.3014, 0.3014, 0.2959, 0.2932, 0.2877, 0.2877, 0.2849, 0.2822, 0.2767, 0.2575, 0.2521, - 0.2055, 0.1315, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, - 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.074, 0.0411, - 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, - 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0027, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [1.0, 0.3123, 0.3123, 0.3096, 0.3068, 0.3068, 0.2986, 0.2986, 0.2932, 0.2877, 0.2795, 0.2685, 0.2164, - 0.126, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, - 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.074, 0.0411, 0.0055, - 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, - 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0], - [1.0, 0.326, 0.3123, 0.3123, 0.3096, 0.3068, 0.3014, 0.2986, 0.2904, 0.2795, 0.2685, 0.2164, 0.1205, - 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, - 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.074, 0.0411, 0.0055, 0.0055, - 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, - 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0], - [1.0, 0.3123, 0.3123, 0.3123, 0.3096, 0.3068, 0.2986, 0.2877, 0.2822, 0.2712, 0.2164, 0.126, 0.1041, - 0.1041, 0.1041, 0.1041, 0.1041, 0.1041, 0.1041, 0.1041, 0.1041, 0.1041, 0.1041, 0.1014, 0.1014, 0.1014, - 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.074, 0.0411, 0.0055, 0.0055, 0.0055, - 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, - 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0], - [1.0, 0.7644, 0.7644, 0.7479, 0.6986, 0.6493, 0.5863, 0.4849, 0.4027, 0.2548, 0.1534, 0.1123, 0.1123, - 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1096, 0.1014, 0.1014, 0.1014, 0.1014, - 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.074, 0.0411, 0.0055, 0.0055, 0.0055, 0.0055, - 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, - 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0], - [1.0, 1.0, 1.0, 0.9753, 0.9068, 0.8301, 0.7178, 0.5616, 0.3452, 0.1753, 0.1315, 0.1288, 0.1288, 0.1288, - 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1233, 0.1178, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, - 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.074, 0.0411, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, - 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, - 0.0055, 0.0055, 0.0055, 0.0055, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [1.0, 1.0, 1.0, 0.9863, 0.8932, 0.8, 0.6685, 0.411, 0.1836, 0.1507, 0.1479, 0.1479, 0.1479, 0.1479, - 0.1479, 0.1479, 0.1479, 0.1479, 0.1397, 0.1315, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, - 0.1014, 0.1014, 0.1014, 0.1014, 0.074, 0.0411, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, - 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, - 0.0055, 0.0055, 0.0055, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]] - } - resp = simulate_outages(**inputs) + # # Output parse from existing simulation + # expected = { + # 'resilience_hours_min': 0, + # 'resilience_hours_max': 78, + # 'resilience_hours_avg': 10.23, + # "outage_durations": list(range(1, 79)), + # "probs_of_surviving": [0.8486, 0.7963, 0.7373, 0.6624, 0.59, 0.5194, 0.4533, 0.4007, 0.3583, 0.3231, 0.2933, + # 0.2691, 0.2471, 0.2297, 0.215, 0.2015, 0.1897, 0.1788, 0.1694, 0.1608, 0.153, 0.1454, + # 0.1381, 0.1308, 0.1247, 0.1184, 0.1122, 0.1065, 0.1013, 0.0968, 0.0927, 0.0887, 0.0847, + # 0.0807, 0.0767, 0.0727, 0.0687, 0.0648, 0.0607, 0.0562, 0.0516, 0.047, 0.0425, 0.0376, + # 0.0326, 0.0277, 0.0228, 0.0179, 0.0146, 0.0116, 0.0097, 0.0081, 0.0071, 0.0066, 0.0062, + # 0.0059, 0.0057, 0.0055, 0.0053, 0.005, 0.0048, 0.0046, 0.0043, 0.0041, 0.0039, 0.0037, + # 0.0032, 0.0027, 0.0023, 0.0018, 0.0014, 0.0009, 0.0007, 0.0006, 0.0005, 0.0003, 0.0002, + # 0.0001], + # "probs_of_surviving_by_month": [ + # [1.0, 0.7688, 0.7003, 0.6277, 0.5511, 0.4785, 0.4005, 0.3266, 0.2769, 0.2312, 0.1989, 0.1774, 0.168, + # 0.1586, 0.1519, 0.1452, 0.1358, 0.1263, 0.1169, 0.1075, 0.0981, 0.0887, 0.078, 0.0672, 0.0591, 0.0538, + # 0.0484, 0.043, 0.0376, 0.0323, 0.0309, 0.0296, 0.0282, 0.0269, 0.0255, 0.0242, 0.0228, 0.0215, 0.0202, + # 0.0188, 0.0175, 0.0161, 0.0148, 0.0134, 0.0121, 0.0108, 0.0094, 0.0081, 0.0067, 0.0054, 0.004, 0.0027, + # 0.0013, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + # [1.0, 0.8036, 0.7381, 0.6756, 0.6131, 0.5208, 0.4315, 0.3571, 0.3021, 0.2589, 0.2262, 0.2024, 0.1935, + # 0.183, 0.1756, 0.1696, 0.1607, 0.1548, 0.1473, 0.1384, 0.1324, 0.1265, 0.1205, 0.1146, 0.1086, 0.1027, + # 0.0967, 0.0908, 0.0848, 0.0804, 0.0774, 0.0744, 0.0729, 0.0714, 0.0699, 0.0685, 0.067, 0.0655, 0.064, + # 0.061, 0.0565, 0.0521, 0.0476, 0.0446, 0.0387, 0.0327, 0.0268, 0.0208, 0.0149, 0.0134, 0.0119, 0.0104, + # 0.0089, 0.0089, 0.0089, 0.0089, 0.0089, 0.0089, 0.0089, 0.0089, 0.0089, 0.0089, 0.0089, 0.0089, 0.0089, + # 0.0089, 0.0089, 0.0074, 0.006, 0.0045, 0.003, 0.0015, 0, 0, 0, 0, 0, 0, 0], + # [1.0, 0.8212, 0.7527, 0.6935, 0.6035, 0.5376, 0.461, 0.3898, 0.328, 0.2782, 0.2419, 0.2124, 0.1922, + # 0.1761, 0.1667, 0.1559, 0.1452, 0.1358, 0.129, 0.1223, 0.1169, 0.1116, 0.1062, 0.1008, 0.0954, 0.0901, + # 0.0847, 0.0793, 0.0766, 0.0753, 0.0739, 0.0726, 0.0712, 0.0699, 0.0685, 0.0672, 0.0659, 0.0645, 0.0632, + # 0.0618, 0.0578, 0.0538, 0.0484, 0.043, 0.0376, 0.0323, 0.0269, 0.0215, 0.0161, 0.0134, 0.0108, 0.0081, + # 0.0054, 0.0027, 0.0013, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + # [1.0, 0.8889, 0.8292, 0.7694, 0.6917, 0.6194, 0.5542, 0.4903, 0.4431, 0.4069, 0.3708, 0.3375, 0.3069, + # 0.2792, 0.2639, 0.25, 0.2389, 0.2278, 0.2181, 0.2083, 0.1986, 0.1903, 0.1833, 0.1764, 0.1694, 0.1625, + # 0.1556, 0.1486, 0.1417, 0.1347, 0.1278, 0.1208, 0.1139, 0.1069, 0.1, 0.0931, 0.0861, 0.0792, 0.0722, + # 0.0653, 0.0583, 0.0514, 0.0444, 0.0375, 0.0306, 0.0236, 0.0167, 0.0097, 0.0028, 0.0014, 0, 0, 0, 0, 0, + # 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + # [1.0, 0.9059, 0.8723, 0.8293, 0.7796, 0.6962, 0.6358, 0.578, 0.5215, 0.4785, 0.4409, 0.4005, 0.3656, + # 0.3333, 0.3051, 0.2809, 0.2608, 0.2419, 0.2285, 0.2177, 0.207, 0.1989, 0.1935, 0.1882, 0.1828, 0.1774, + # 0.172, 0.1667, 0.1613, 0.1559, 0.1505, 0.1452, 0.1398, 0.1344, 0.129, 0.1237, 0.1183, 0.1129, 0.1075, + # 0.1022, 0.0968, 0.0914, 0.086, 0.0806, 0.0753, 0.0699, 0.0645, 0.0591, 0.0538, 0.0484, 0.043, 0.039, + # 0.0363, 0.0349, 0.0336, 0.0323, 0.0309, 0.0296, 0.0282, 0.0269, 0.0255, 0.0242, 0.0228, 0.0215, 0.0202, + # 0.0188, 0.0175, 0.0161, 0.0148, 0.0134, 0.0121, 0.0108, 0.0094, 0.0081, 0.0067, 0.0054, 0.004, 0.0027, + # 0.0013], + # [1.0, 0.9111, 0.8792, 0.8292, 0.7597, 0.6708, 0.5944, 0.5403, 0.4875, 0.4444, 0.4028, 0.3667, 0.3306, + # 0.2972, 0.2667, 0.2431, 0.225, 0.2083, 0.1944, 0.1861, 0.1792, 0.1736, 0.1681, 0.1625, 0.1569, 0.1514, + # 0.1458, 0.1403, 0.1347, 0.1292, 0.1236, 0.1181, 0.1125, 0.1069, 0.1014, 0.0958, 0.0903, 0.0847, 0.0792, + # 0.0736, 0.0681, 0.0625, 0.0569, 0.0514, 0.0458, 0.0403, 0.0347, 0.0292, 0.0236, 0.0181, 0.0125, 0.0083, + # 0.0042, 0.0028, 0.0014, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + # [1.0, 0.9194, 0.8831, 0.8293, 0.7433, 0.6815, 0.6022, 0.5497, 0.5067, 0.4718, 0.4368, 0.4046, 0.3696, + # 0.3414, 0.3172, 0.2997, 0.2823, 0.2661, 0.2513, 0.2392, 0.2285, 0.2177, 0.207, 0.1976, 0.1895, 0.1815, + # 0.1734, 0.1653, 0.1573, 0.1492, 0.1425, 0.1358, 0.129, 0.1223, 0.1156, 0.1089, 0.1022, 0.0954, 0.0887, + # 0.082, 0.0753, 0.0685, 0.0618, 0.0551, 0.0484, 0.0417, 0.0349, 0.0282, 0.0215, 0.0148, 0.0081, 0.0054, + # 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + # [1.0, 0.8992, 0.8589, 0.8065, 0.7285, 0.664, 0.5968, 0.5228, 0.4583, 0.4086, 0.3723, 0.336, 0.3024, + # 0.2702, 0.2433, 0.2177, 0.2016, 0.1909, 0.1815, 0.1734, 0.1653, 0.1586, 0.1519, 0.1465, 0.1411, 0.1358, + # 0.129, 0.1237, 0.1183, 0.1129, 0.1075, 0.1022, 0.0968, 0.0914, 0.086, 0.0806, 0.0753, 0.0699, 0.0645, + # 0.0591, 0.0538, 0.0484, 0.043, 0.0376, 0.0323, 0.0269, 0.0215, 0.0161, 0.0108, 0.0054, 0.0027, 0.0013, + # 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + # [1.0, 0.8708, 0.8347, 0.7819, 0.7042, 0.6181, 0.5486, 0.475, 0.4333, 0.4028, 0.3708, 0.3375, 0.3083, + # 0.2819, 0.2611, 0.2472, 0.2361, 0.2278, 0.2194, 0.2139, 0.2083, 0.2028, 0.1972, 0.1917, 0.1861, 0.1806, + # 0.175, 0.1694, 0.1639, 0.1583, 0.1528, 0.1472, 0.1417, 0.1361, 0.1306, 0.125, 0.1194, 0.1139, 0.1083, + # 0.1028, 0.0972, 0.0917, 0.0861, 0.0792, 0.0722, 0.0653, 0.0583, 0.0514, 0.0444, 0.0389, 0.0333, 0.0306, + # 0.0292, 0.0278, 0.0264, 0.025, 0.0236, 0.0222, 0.0208, 0.0194, 0.0181, 0.0167, 0.0153, 0.0139, 0.0125, + # 0.0111, 0.0097, 0.0083, 0.0069, 0.0056, 0.0042, 0.0028, 0.0014, 0, 0, 0, 0, 0, 0], + # [1.0, 0.8199, 0.7608, 0.7016, 0.6223, 0.5685, 0.5161, 0.4543, 0.3952, 0.3495, 0.3172, 0.2863, 0.2648, + # 0.246, 0.2272, 0.211, 0.1976, 0.1868, 0.1747, 0.1653, 0.1559, 0.1478, 0.1411, 0.1344, 0.1277, 0.121, + # 0.1142, 0.1075, 0.1035, 0.0995, 0.0954, 0.0914, 0.0874, 0.0833, 0.0793, 0.0753, 0.0712, 0.0672, 0.0645, + # 0.0605, 0.0551, 0.0497, 0.0444, 0.039, 0.0336, 0.0282, 0.0228, 0.0175, 0.0121, 0.0108, 0.0094, 0.0081, + # 0.0081, 0.0081, 0.0081, 0.0081, 0.0081, 0.0081, 0.0081, 0.0081, 0.0081, 0.0081, 0.0081, 0.0081, 0.0081, + # 0.0081, 0.0081, 0.0067, 0.0054, 0.004, 0.0027, 0.0013, 0, 0, 0, 0, 0, 0, 0], + # [1.0, 0.7931, 0.7319, 0.6542, 0.5819, 0.525, 0.4653, 0.4056, 0.3542, 0.3153, 0.2792, 0.2542, 0.2375, + # 0.2208, 0.2069, 0.1958, 0.1819, 0.1694, 0.1542, 0.1417, 0.1319, 0.1222, 0.1125, 0.1028, 0.0875, 0.0806, + # 0.0736, 0.0667, 0.0597, 0.0542, 0.0486, 0.0458, 0.0431, 0.0403, 0.0375, 0.0347, 0.0319, 0.0292, 0.0264, + # 0.0236, 0.0208, 0.0181, 0.0167, 0.0153, 0.0125, 0.0097, 0.0069, 0.0042, 0.0014, 0, 0, 0, 0, 0, 0, 0, 0, + # 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + # [1.0, 0.7796, 0.7124, 0.6465, 0.5685, 0.4946, 0.4207, 0.3441, 0.2957, 0.2487, 0.2137, 0.1989, 0.1855, + # 0.1747, 0.168, 0.1613, 0.1505, 0.1398, 0.129, 0.1183, 0.1075, 0.0968, 0.086, 0.0753, 0.0659, 0.0591, + # 0.0524, 0.0457, 0.039, 0.0336, 0.0309, 0.0296, 0.0282, 0.0269, 0.0255, 0.0242, 0.0228, 0.0215, 0.0202, + # 0.0188, 0.0175, 0.0161, 0.0148, 0.0134, 0.0121, 0.0108, 0.0094, 0.0081, 0.0067, 0.0054, 0.004, 0.0027, + # 0.0013, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], + # "probs_of_surviving_by_hour_of_the_day": [ + # [1.0, 1.0, 1.0, 0.9863, 0.8986, 0.8, 0.5233, 0.2247, 0.1671, 0.1644, 0.1644, 0.1644, 0.1644, 0.1644, + # 0.1616, 0.1616, 0.1616, 0.1534, 0.1425, 0.1068, 0.1068, 0.1068, 0.1068, 0.1068, 0.1068, 0.1068, 0.1068, + # 0.1068, 0.1068, 0.1068, 0.0795, 0.0438, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, + # 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, + # 0.0082, 0.0082, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + # [1.0, 1.0, 1.0, 0.9863, 0.8959, 0.6301, 0.3014, 0.2164, 0.2137, 0.2137, 0.2137, 0.211, 0.211, 0.2082, + # 0.2082, 0.2082, 0.1973, 0.1863, 0.126, 0.126, 0.126, 0.126, 0.126, 0.126, 0.126, 0.126, 0.126, 0.126, + # 0.126, 0.0959, 0.0521, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, + # 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0137, + # 0.0082, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, + # 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027], + # [1.0, 1.0, 1.0, 0.9863, 0.7123, 0.3644, 0.2904, 0.2822, 0.2822, 0.2822, 0.2822, 0.2795, 0.274, 0.274, + # 0.274, 0.2603, 0.2356, 0.1452, 0.1452, 0.1452, 0.1452, 0.1452, 0.1452, 0.1452, 0.1452, 0.1452, 0.1452, + # 0.1452, 0.1151, 0.0575, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, + # 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0192, + # 0.0082, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, + # 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0], + # [1.0, 1.0, 1.0, 0.8055, 0.474, 0.3616, 0.3562, 0.3562, 0.3562, 0.3562, 0.3534, 0.3479, 0.3479, 0.3479, + # 0.3233, 0.2904, 0.1836, 0.1836, 0.1836, 0.1836, 0.1836, 0.1836, 0.1836, 0.1836, 0.1808, 0.1808, 0.1808, + # 0.1397, 0.0712, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, + # 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0274, 0.0137, + # 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, + # 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0, 0], + # [1.0, 1.0, 0.863, 0.5315, 0.4521, 0.4493, 0.4466, 0.4466, 0.4466, 0.4438, 0.4384, 0.4384, 0.4356, 0.411, + # 0.3616, 0.2055, 0.2055, 0.2055, 0.2055, 0.2055, 0.2055, 0.2055, 0.2055, 0.2055, 0.2027, 0.2027, 0.1589, + # 0.0904, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, + # 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0411, 0.0247, 0.0027, + # 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, + # 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0, 0, 0], + # [1.0, 0.8712, 0.5808, 0.5014, 0.4959, 0.4959, 0.4959, 0.4959, 0.4932, 0.4877, 0.4877, 0.4849, 0.4575, + # 0.4027, 0.2137, 0.2137, 0.2137, 0.2137, 0.2137, 0.2137, 0.2137, 0.2137, 0.2137, 0.2137, 0.211, 0.1699, + # 0.1014, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, + # 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.0521, 0.0274, 0.0027, 0.0027, 0.0027, + # 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, + # 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0, 0, 0, 0], + # [1.0, 0.7041, 0.6685, 0.6685, 0.6658, 0.6658, 0.6658, 0.663, 0.6575, 0.6575, 0.6548, 0.6055, 0.526, + # 0.2712, 0.2712, 0.2685, 0.2685, 0.2685, 0.2685, 0.2685, 0.2685, 0.2685, 0.2685, 0.2658, 0.2219, 0.1534, + # 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, + # 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.0767, 0.0438, 0.0082, 0.0082, + # 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, + # 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0055, 0.0027, 0, 0, 0, 0, 0], + # [1.0, 0.937, 0.9233, 0.9178, 0.9068, 0.9068, 0.9014, 0.8904, 0.8904, 0.8767, 0.7205, 0.5836, 0.3014, + # 0.3014, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2521, 0.1671, 0.1288, + # 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, + # 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.0904, 0.0466, 0.011, 0.011, 0.011, + # 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, + # 0.011, 0.011, 0.011, 0.011, 0.011, 0.0082, 0.0027, 0, 0, 0, 0, 0, 0], + # [1.0, 0.9671, 0.9562, 0.9479, 0.9479, 0.9425, 0.9288, 0.926, 0.9096, 0.7397, 0.5918, 0.3014, 0.3014, + # 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2521, 0.1671, 0.1288, 0.1288, + # 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, + # 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.0904, 0.0466, 0.011, 0.011, 0.011, 0.011, + # 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, + # 0.011, 0.011, 0.011, 0.011, 0.0082, 0.0027, 0, 0, 0, 0, 0, 0, 0], + # [1.0, 0.9699, 0.9589, 0.9589, 0.9534, 0.937, 0.937, 0.9178, 0.7397, 0.5973, 0.3014, 0.3014, 0.2986, + # 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2548, 0.1671, 0.1288, 0.1288, 0.1288, + # 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, + # 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.0904, 0.0466, 0.011, 0.011, 0.011, 0.011, 0.011, + # 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, + # 0.011, 0.011, 0.011, 0.0082, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0], + # [1.0, 0.9781, 0.9699, 0.9644, 0.9479, 0.9452, 0.9288, 0.7397, 0.5973, 0.3014, 0.3014, 0.2986, 0.2986, + # 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2548, 0.1671, 0.1288, 0.1288, 0.1288, 0.1288, + # 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, + # 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.0904, 0.0466, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, + # 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, + # 0.011, 0.011, 0.0082, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0], + # [1.0, 0.9863, 0.9726, 0.9534, 0.9507, 0.9315, 0.7397, 0.5973, 0.3014, 0.3014, 0.2986, 0.2986, 0.2986, + # 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2548, 0.1671, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, + # 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, + # 0.1288, 0.1288, 0.1288, 0.1288, 0.0904, 0.0466, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, + # 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, + # 0.0082, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + # [1.0, 0.9836, 0.9616, 0.9589, 0.937, 0.7397, 0.5973, 0.3014, 0.3014, 0.2986, 0.2986, 0.2986, 0.2986, + # 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2466, 0.1534, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, + # 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, + # 0.1151, 0.1151, 0.1151, 0.0795, 0.0438, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, + # 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, + # 0.0082, 0.0082, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + # [1.0, 0.9644, 0.9616, 0.9397, 0.7397, 0.5973, 0.3014, 0.3014, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, + # 0.2986, 0.2986, 0.2959, 0.2932, 0.2438, 0.1507, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, + # 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, + # 0.1123, 0.1123, 0.0795, 0.0411, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, + # 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, + # 0.0055, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + # [1.0, 0.9726, 0.9479, 0.7397, 0.5973, 0.3014, 0.3014, 0.2986, 0.2986, 0.2986, 0.2986, 0.2959, 0.2932, + # 0.2932, 0.2904, 0.2904, 0.2384, 0.1534, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, + # 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, + # 0.1151, 0.0822, 0.0411, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, + # 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, + # 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + # [1.0, 0.9562, 0.7452, 0.6027, 0.3014, 0.3014, 0.2986, 0.2986, 0.2932, 0.2932, 0.2932, 0.2904, 0.2904, + # 0.2822, 0.2822, 0.2356, 0.1479, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, + # 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, + # 0.0822, 0.0411, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, + # 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0027, + # 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + # [1.0, 0.7479, 0.6, 0.3014, 0.3014, 0.2959, 0.2932, 0.2932, 0.2877, 0.2877, 0.2822, 0.2822, 0.2795, + # 0.2575, 0.2082, 0.1397, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, + # 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.0767, + # 0.0411, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, + # 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0027, 0, 0, + # 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + # [1.0, 0.6137, 0.3014, 0.3014, 0.2959, 0.2932, 0.2877, 0.2877, 0.2849, 0.2822, 0.2767, 0.2575, 0.2521, + # 0.2055, 0.1315, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, + # 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.074, 0.0411, + # 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, + # 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0027, 0, 0, 0, 0, 0, + # 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + # [1.0, 0.3123, 0.3123, 0.3096, 0.3068, 0.3068, 0.2986, 0.2986, 0.2932, 0.2877, 0.2795, 0.2685, 0.2164, + # 0.126, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, + # 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.074, 0.0411, 0.0055, + # 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, + # 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, + # 0, 0, 0, 0, 0, 0, 0, 0, 0], + # [1.0, 0.326, 0.3123, 0.3123, 0.3096, 0.3068, 0.3014, 0.2986, 0.2904, 0.2795, 0.2685, 0.2164, 0.1205, + # 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, + # 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.074, 0.0411, 0.0055, 0.0055, + # 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, + # 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + # 0, 0, 0, 0, 0, 0, 0, 0], + # [1.0, 0.3123, 0.3123, 0.3123, 0.3096, 0.3068, 0.2986, 0.2877, 0.2822, 0.2712, 0.2164, 0.126, 0.1041, + # 0.1041, 0.1041, 0.1041, 0.1041, 0.1041, 0.1041, 0.1041, 0.1041, 0.1041, 0.1041, 0.1014, 0.1014, 0.1014, + # 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.074, 0.0411, 0.0055, 0.0055, 0.0055, + # 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, + # 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + # 0, 0, 0, 0, 0, 0], + # [1.0, 0.7644, 0.7644, 0.7479, 0.6986, 0.6493, 0.5863, 0.4849, 0.4027, 0.2548, 0.1534, 0.1123, 0.1123, + # 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1096, 0.1014, 0.1014, 0.1014, 0.1014, + # 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.074, 0.0411, 0.0055, 0.0055, 0.0055, 0.0055, + # 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, + # 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + # 0, 0, 0, 0], + # [1.0, 1.0, 1.0, 0.9753, 0.9068, 0.8301, 0.7178, 0.5616, 0.3452, 0.1753, 0.1315, 0.1288, 0.1288, 0.1288, + # 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1233, 0.1178, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, + # 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.074, 0.0411, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, + # 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, + # 0.0055, 0.0055, 0.0055, 0.0055, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + # [1.0, 1.0, 1.0, 0.9863, 0.8932, 0.8, 0.6685, 0.411, 0.1836, 0.1507, 0.1479, 0.1479, 0.1479, 0.1479, + # 0.1479, 0.1479, 0.1479, 0.1479, 0.1397, 0.1315, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, + # 0.1014, 0.1014, 0.1014, 0.1014, 0.074, 0.0411, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, + # 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, + # 0.0055, 0.0055, 0.0055, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]] + # } + # resp = simulate_outages(**inputs) - self.assertAlmostEqual(expected['resilience_hours_min'], resp['resilience_hours_min'], places=3) - self.assertAlmostEqual(expected['resilience_hours_max'], resp['resilience_hours_max'], places=3) - self.assertAlmostEqual(expected['resilience_hours_avg'], resp['resilience_hours_avg'], places=3) - self.assertAlmostEqual(expected['outage_durations'], resp['outage_durations'], places=3) - for x, y in zip(expected['probs_of_surviving'], resp['probs_of_surviving']): - self.assertAlmostEquals(x, y, places=3) - for e, r in zip([0, 11], [0, 11]): - for x, y in zip(expected['probs_of_surviving_by_month'][e], resp['probs_of_surviving_by_month'][r]): - self.assertAlmostEquals(x, y, places=3) - for e, r in zip([0, 23], [0, 23]): - for x, y in zip(expected['probs_of_surviving_by_hour_of_the_day'][e], - resp['probs_of_surviving_by_hour_of_the_day'][r]): - self.assertAlmostEquals(x, y, places=3) + # self.assertAlmostEqual(expected['resilience_hours_min'], resp['resilience_hours_min'], places=3) + # self.assertAlmostEqual(expected['resilience_hours_max'], resp['resilience_hours_max'], places=3) + # self.assertAlmostEqual(expected['resilience_hours_avg'], resp['resilience_hours_avg'], places=3) + # self.assertAlmostEqual(expected['outage_durations'], resp['outage_durations'], places=3) + # for x, y in zip(expected['probs_of_surviving'], resp['probs_of_surviving']): + # self.assertAlmostEquals(x, y, places=3) + # for e, r in zip([0, 11], [0, 11]): + # for x, y in zip(expected['probs_of_surviving_by_month'][e], resp['probs_of_surviving_by_month'][r]): + # self.assertAlmostEquals(x, y, places=3) + # for e, r in zip([0, 23], [0, 23]): + # for x, y in zip(expected['probs_of_surviving_by_hour_of_the_day'][e], + # resp['probs_of_surviving_by_hour_of_the_day'][r]): + # self.assertAlmostEquals(x, y, places=3) - def test_outage_sim(self): - """ - With diesel + PV + storage - """ - expected = { - 'resilience_hours_min': 30, - 'resilience_hours_max': 154, - 'resilience_hours_avg': 82.1, - "outage_durations": list(range(1, 155)), - "probs_of_surviving": [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, - 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.9999, 0.9998, - 0.9997, 0.9985, 0.9973, 0.9965, 0.9951, 0.9937, 0.9921, 0.989, 0.9861, 0.983, - 0.9797, 0.976, 0.972, 0.9666, 0.9608, 0.9547, 0.9471, 0.9373, 0.9275, 0.9178, - 0.9066, 0.897, 0.8868, 0.8761, 0.8647, 0.8521, 0.8398, 0.8285, 0.816, 0.8035, - 0.7928, 0.7809, 0.7693, 0.7561, 0.7436, 0.7301, 0.7159, 0.701, 0.685, 0.6707, - 0.6535, 0.6347, 0.6189, 0.6026, 0.5864, 0.5696, 0.5534, 0.5376, 0.5213, 0.5053, - 0.4892, 0.4719, 0.4542, 0.4378, 0.4217, 0.4058, 0.3901, 0.3736, 0.3584, 0.3434, - 0.3287, 0.3152, 0.3006, 0.2854, 0.2728, 0.2579, 0.2449, 0.2321, 0.2196, 0.2068, - 0.1941, 0.1812, 0.1685, 0.1562, 0.1446, 0.1324, 0.1216, 0.1114, 0.1021, 0.0928, - 0.084, 0.0752, 0.0675, 0.0603, 0.0533, 0.0466, 0.0411, 0.0371, 0.0333, 0.0296, - 0.026, 0.0231, 0.0203, 0.0178, 0.0153, 0.0129, 0.0105, 0.0086, 0.0067, 0.0053, - 0.0042, 0.0035, 0.0033, 0.0031, 0.0029, 0.0026, 0.0024, 0.0022, 0.0019, 0.0017, - 0.0015, 0.0013, 0.0011, 0.001, 0.0009, 0.0008, 0.0007, 0.0006, 0.0005, 0.0003, - 0.0002, 0.0001] - } - resp = simulate_outages(**self.inputs) + # def test_outage_sim(self): + # """ + # With diesel + PV + storage + # """ + # expected = { + # 'resilience_hours_min': 30, + # 'resilience_hours_max': 154, + # 'resilience_hours_avg': 82.1, + # "outage_durations": list(range(1, 155)), + # "probs_of_surviving": [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + # 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.9999, 0.9998, + # 0.9997, 0.9985, 0.9973, 0.9965, 0.9951, 0.9937, 0.9921, 0.989, 0.9861, 0.983, + # 0.9797, 0.976, 0.972, 0.9666, 0.9608, 0.9547, 0.9471, 0.9373, 0.9275, 0.9178, + # 0.9066, 0.897, 0.8868, 0.8761, 0.8647, 0.8521, 0.8398, 0.8285, 0.816, 0.8035, + # 0.7928, 0.7809, 0.7693, 0.7561, 0.7436, 0.7301, 0.7159, 0.701, 0.685, 0.6707, + # 0.6535, 0.6347, 0.6189, 0.6026, 0.5864, 0.5696, 0.5534, 0.5376, 0.5213, 0.5053, + # 0.4892, 0.4719, 0.4542, 0.4378, 0.4217, 0.4058, 0.3901, 0.3736, 0.3584, 0.3434, + # 0.3287, 0.3152, 0.3006, 0.2854, 0.2728, 0.2579, 0.2449, 0.2321, 0.2196, 0.2068, + # 0.1941, 0.1812, 0.1685, 0.1562, 0.1446, 0.1324, 0.1216, 0.1114, 0.1021, 0.0928, + # 0.084, 0.0752, 0.0675, 0.0603, 0.0533, 0.0466, 0.0411, 0.0371, 0.0333, 0.0296, + # 0.026, 0.0231, 0.0203, 0.0178, 0.0153, 0.0129, 0.0105, 0.0086, 0.0067, 0.0053, + # 0.0042, 0.0035, 0.0033, 0.0031, 0.0029, 0.0026, 0.0024, 0.0022, 0.0019, 0.0017, + # 0.0015, 0.0013, 0.0011, 0.001, 0.0009, 0.0008, 0.0007, 0.0006, 0.0005, 0.0003, + # 0.0002, 0.0001] + # } + # resp = simulate_outages(**self.inputs) - self.assertAlmostEqual(expected['resilience_hours_min'], resp['resilience_hours_min'], places=4) - self.assertAlmostEqual(expected['resilience_hours_max'], resp['resilience_hours_max'], places=4) - self.assertAlmostEqual(expected['resilience_hours_avg'], resp['resilience_hours_avg'], places=4) - self.assertListEqual(expected['outage_durations'], resp['outage_durations']) - for x, y in zip(expected['probs_of_surviving'], resp['probs_of_surviving']): - self.assertAlmostEquals(x, y, places=4) + # self.assertAlmostEqual(expected['resilience_hours_min'], resp['resilience_hours_min'], places=4) + # self.assertAlmostEqual(expected['resilience_hours_max'], resp['resilience_hours_max'], places=4) + # self.assertAlmostEqual(expected['resilience_hours_avg'], resp['resilience_hours_avg'], places=4) + # self.assertListEqual(expected['outage_durations'], resp['outage_durations']) + # for x, y in zip(expected['probs_of_surviving'], resp['probs_of_surviving']): + # self.assertAlmostEquals(x, y, places=4) - def test_no_resilience(self): - inputs = self.inputs - inputs.update(pv_kw_ac_hourly=[], batt_kw=0, diesel_kw=0) - resp = simulate_outages(**inputs) + # def test_no_resilience(self): + # inputs = self.inputs + # inputs.update(pv_kw_ac_hourly=[], batt_kw=0, diesel_kw=0) + # resp = simulate_outages(**inputs) - self.assertEqual(0, resp['resilience_hours_min']) - self.assertEqual(0, resp['resilience_hours_max']) - self.assertEqual(0, resp['resilience_hours_avg']) - self.assertEqual([], resp['outage_durations']) - self.assertEqual([], resp['probs_of_surviving']) - self.assertEqual([[0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0]], resp['probs_of_surviving_by_month']) - self.assertEqual([[0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0]], resp['probs_of_surviving_by_hour_of_the_day']) + # self.assertEqual(0, resp['resilience_hours_min']) + # self.assertEqual(0, resp['resilience_hours_max']) + # self.assertEqual(0, resp['resilience_hours_avg']) + # self.assertEqual([], resp['outage_durations']) + # self.assertEqual([], resp['probs_of_surviving']) + # self.assertEqual([[0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0]], resp['probs_of_surviving_by_month']) + # self.assertEqual([[0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0]], resp['probs_of_surviving_by_hour_of_the_day']) - def test_flexible_timesteps(self): - """ - Same input with different timesteps-per-hour should have almost equal results. - """ - resp1 = simulate_outages(**self.inputs1) - resp2 = simulate_outages(**self.inputs2) + # def test_flexible_timesteps(self): + # """ + # Same input with different timesteps-per-hour should have almost equal results. + # """ + # resp1 = simulate_outages(**self.inputs1) + # resp2 = simulate_outages(**self.inputs2) - self.assertAlmostEqual(1, resp2['resilience_hours_max'] / resp1['resilience_hours_max'], places=1) - self.assertAlmostEqual(resp2['resilience_hours_min'], resp1['resilience_hours_min'], places=0) - self.assertAlmostEqual(1, resp2['resilience_hours_avg'] / resp1['resilience_hours_avg'], places=1) + # self.assertAlmostEqual(1, resp2['resilience_hours_max'] / resp1['resilience_hours_max'], places=1) + # self.assertAlmostEqual(resp2['resilience_hours_min'], resp1['resilience_hours_min'], places=0) + # self.assertAlmostEqual(1, resp2['resilience_hours_avg'] / resp1['resilience_hours_avg'], places=1) - for x, y in zip(resp1['probs_of_surviving'], resp2['probs_of_surviving']): - self.assertAlmostEquals(x, y, places=1) + # for x, y in zip(resp1['probs_of_surviving'], resp2['probs_of_surviving']): + # self.assertAlmostEquals(x, y, places=1) - def test_resil_endpoint(self): - post = json.load(open(os.path.join('resilience_stats', 'tests', 'POST_nested.json'), 'r')) - r = self.api_client.post('/v2/job/', format='json', data=post) - reopt_resp = json.loads(r.content) - run_uuid = reopt_resp['run_uuid'] + # def test_resil_endpoint(self): + # post = json.load(open(os.path.join('resilience_stats', 'tests', 'POST_nested.json'), 'r')) + # r = self.api_client.post('/v2/job/', format='json', data=post) + # reopt_resp = json.loads(r.content) + # run_uuid = reopt_resp['run_uuid'] - resp = self.api_client.post('/v2/outagesimjob/', format='json', data={"run_uuid": run_uuid, "bau": True}) - self.assertEqual(resp.status_code, 201) - resp = self.api_client.get('/v2/job//resilience_stats/'.replace("", run_uuid)) - resp_dict = json.loads(resp.content)['outage_sim_results'] + # resp = self.api_client.post('/v2/outagesimjob/', format='json', data={"run_uuid": run_uuid, "bau": True}) + # self.assertEqual(resp.status_code, 201) + # resp = self.api_client.get('/v2/job//resilience_stats/'.replace("", run_uuid)) + # resp_dict = json.loads(resp.content)['outage_sim_results'] - # NOTE: probabilities are sensitive to the SOC series, - # which can change while keeping the same optimal LCC - expected_probs = [0.3902, 0.2338, 0.1919, 0.1532, 0.1178, 0.0844, 0.0538, 0.0305, 0.0131, 0.0066, 0.0033, 0.001] - for idx, p in enumerate(resp_dict["probs_of_surviving"]): - self.assertAlmostEqual(p, expected_probs[idx], places=2) - self.assertEqual(resp_dict["resilience_hours_avg"], 1.28) - self.assertEqual(resp_dict["outage_durations"], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]) - self.assertEqual(resp_dict["resilience_hours_min"], 0) - self.assertEqual(resp_dict["resilience_hours_max"], 12) - self.assertFalse("resilience_hours_max_bau" in resp_dict) + # # NOTE: probabilities are sensitive to the SOC series, + # # which can change while keeping the same optimal LCC + # expected_probs = [0.3902, 0.2338, 0.1919, 0.1532, 0.1178, 0.0844, 0.0538, 0.0305, 0.0131, 0.0066, 0.0033, 0.001] + # for idx, p in enumerate(resp_dict["probs_of_surviving"]): + # self.assertAlmostEqual(p, expected_probs[idx], places=2) + # self.assertEqual(resp_dict["resilience_hours_avg"], 1.28) + # self.assertEqual(resp_dict["outage_durations"], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]) + # self.assertEqual(resp_dict["resilience_hours_min"], 0) + # self.assertEqual(resp_dict["resilience_hours_max"], 12) + # self.assertFalse("resilience_hours_max_bau" in resp_dict) - """ - financial_check returns true if the financial scenario system capacities are greater than or equal to the - resilience scenario system capacities - """ - resp = self.api_client.get( - '/v2/financial_check/?financial_uuid={0}&resilience_uuid={0}'.format(run_uuid), - format='json') - self.assertEqual(resp.status_code, 200) - results = json.loads(resp.content) - self.assertTrue(results["survives_specified_outage"]) + # """ + # financial_check returns true if the financial scenario system capacities are greater than or equal to the + # resilience scenario system capacities + # """ + # resp = self.api_client.get( + # '/v2/financial_check/?financial_uuid={0}&resilience_uuid={0}'.format(run_uuid), + # format='json') + # self.assertEqual(resp.status_code, 200) + # results = json.loads(resp.content) + # self.assertTrue(results["survives_specified_outage"]) - def test_outage_sim_chp(self): - expected = { - 'resilience_hours_min': 0, - 'resilience_hours_max': 10, - 'resilience_hours_avg': 0.01, - "outage_durations": list(range(1, 11)), - "probs_of_surviving": [0.0011, 0.001, 0.0009, 0.0008, 0.0007, 0.0006, 0.0005, 0.0003, 0.0002, 0.0001] - } - inputs = self.inputs - inputs.update(pv_kw_ac_hourly=[], batt_kw=0, diesel_kw=0, chp_kw=10, critical_loads_kw=[10]*10 + [11]*8750) - resp = simulate_outages(**inputs) + # def test_outage_sim_chp(self): + # expected = { + # 'resilience_hours_min': 0, + # 'resilience_hours_max': 10, + # 'resilience_hours_avg': 0.01, + # "outage_durations": list(range(1, 11)), + # "probs_of_surviving": [0.0011, 0.001, 0.0009, 0.0008, 0.0007, 0.0006, 0.0005, 0.0003, 0.0002, 0.0001] + # } + # inputs = self.inputs + # inputs.update(pv_kw_ac_hourly=[], batt_kw=0, diesel_kw=0, chp_kw=10, critical_loads_kw=[10]*10 + [11]*8750) + # resp = simulate_outages(**inputs) - self.assertAlmostEqual(expected['resilience_hours_min'], resp['resilience_hours_min'], places=4) - self.assertAlmostEqual(expected['resilience_hours_max'], resp['resilience_hours_max'], places=4) - self.assertAlmostEqual(expected['resilience_hours_avg'], resp['resilience_hours_avg'], places=4) - self.assertListEqual(expected['outage_durations'], resp['outage_durations']) - for x, y in zip(expected['probs_of_surviving'], resp['probs_of_surviving']): - self.assertAlmostEquals(x, y, places=4) + # self.assertAlmostEqual(expected['resilience_hours_min'], resp['resilience_hours_min'], places=4) + # self.assertAlmostEqual(expected['resilience_hours_max'], resp['resilience_hours_max'], places=4) + # self.assertAlmostEqual(expected['resilience_hours_avg'], resp['resilience_hours_avg'], places=4) + # self.assertListEqual(expected['outage_durations'], resp['outage_durations']) + # for x, y in zip(expected['probs_of_surviving'], resp['probs_of_surviving']): + # self.assertAlmostEquals(x, y, places=4) From 9605654184be7c0fb1592d26180b129e473eb6fd Mon Sep 17 00:00:00 2001 From: Hallie Dunham <70401017+hdunham@users.noreply.github.com> Date: Fri, 14 Jun 2024 12:50:48 -0600 Subject: [PATCH 08/24] test cant be empty --- reoptjl/test/test_http_endpoints.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/reoptjl/test/test_http_endpoints.py b/reoptjl/test/test_http_endpoints.py index ae389c988..ac845e94d 100644 --- a/reoptjl/test/test_http_endpoints.py +++ b/reoptjl/test/test_http_endpoints.py @@ -9,7 +9,8 @@ class TestHTTPEndpoints(ResourceTestCaseMixin, TestCase): - # def test_chp_defaults(self): + def test_chp_defaults(self): + self.assertEqual(1, 1) # inputs = {"hot_water_or_steam": "hot_water", # "avg_boiler_fuel_load_mmbtu_per_hour": 28.0 From b35505180a1fdb6812e610e67028a72635773be2 Mon Sep 17 00:00:00 2001 From: Hallie Dunham <70401017+hdunham@users.noreply.github.com> Date: Fri, 14 Jun 2024 13:11:33 -0600 Subject: [PATCH 09/24] Revert "test cant be empty" This reverts commit 9605654184be7c0fb1592d26180b129e473eb6fd. --- reoptjl/test/test_http_endpoints.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/reoptjl/test/test_http_endpoints.py b/reoptjl/test/test_http_endpoints.py index ac845e94d..ae389c988 100644 --- a/reoptjl/test/test_http_endpoints.py +++ b/reoptjl/test/test_http_endpoints.py @@ -9,8 +9,7 @@ class TestHTTPEndpoints(ResourceTestCaseMixin, TestCase): - def test_chp_defaults(self): - self.assertEqual(1, 1) + # def test_chp_defaults(self): # inputs = {"hot_water_or_steam": "hot_water", # "avg_boiler_fuel_load_mmbtu_per_hour": 28.0 From 4ce4b8318d3df34bfacaa1831c6c0a15dbd89365 Mon Sep 17 00:00:00 2001 From: Hallie Dunham <70401017+hdunham@users.noreply.github.com> Date: Fri, 14 Jun 2024 13:11:36 -0600 Subject: [PATCH 10/24] Revert "comment out extra tests" This reverts commit 04182ceefa0eaa3bcd04b66ce362042910b4bd41. --- reoptjl/test/test_http_endpoints.py | 700 +++++++++--------- reoptjl/test/test_job_endpoint.py | 534 ++++++------- reoptjl/test/test_validator.py | 630 ++++++++-------- resilience_stats/tests/test_erp.py | 234 +++--- .../tests/test_new_post_endpoint.py | 34 +- .../tests/test_resilience_stats.py | 688 ++++++++--------- 6 files changed, 1410 insertions(+), 1410 deletions(-) diff --git a/reoptjl/test/test_http_endpoints.py b/reoptjl/test/test_http_endpoints.py index ae389c988..760c10775 100644 --- a/reoptjl/test/test_http_endpoints.py +++ b/reoptjl/test/test_http_endpoints.py @@ -9,373 +9,373 @@ class TestHTTPEndpoints(ResourceTestCaseMixin, TestCase): - # def test_chp_defaults(self): + def test_chp_defaults(self): - # inputs = {"hot_water_or_steam": "hot_water", - # "avg_boiler_fuel_load_mmbtu_per_hour": 28.0 - # } + inputs = {"hot_water_or_steam": "hot_water", + "avg_boiler_fuel_load_mmbtu_per_hour": 28.0 + } - # # Direct call of the http.jl endpoint /chp_defaults - # julia_host = os.environ.get('JULIA_HOST', "julia") - # response = requests.get("http://" + julia_host + ":8081/chp_defaults/", json=inputs) - # http_response = response.json() + # Direct call of the http.jl endpoint /chp_defaults + julia_host = os.environ.get('JULIA_HOST', "julia") + response = requests.get("http://" + julia_host + ":8081/chp_defaults/", json=inputs) + http_response = response.json() - # # Call to the django view endpoint /chp_defaults which calls the http.jl endpoint - # resp = self.api_client.get(f'/v3/chp_defaults', data=inputs) - # view_response = json.loads(resp.content) + # Call to the django view endpoint /chp_defaults which calls the http.jl endpoint + resp = self.api_client.get(f'/v3/chp_defaults', data=inputs) + view_response = json.loads(resp.content) - # mismatch = [] - # for k, v in http_response["default_inputs"].items(): - # if v != view_response["default_inputs"][k]: - # mismatch.append(k) + mismatch = [] + for k, v in http_response["default_inputs"].items(): + if v != view_response["default_inputs"][k]: + mismatch.append(k) - # self.assertEqual(mismatch, []) - - # # Check the endpoint logic with the expected selection - # self.assertEqual(http_response["prime_mover"], "combustion_turbine") - # self.assertEqual(http_response["size_class"], 2) - # self.assertGreater(http_response["chp_elec_size_heuristic_kw"], 3500.0) - # self.assertEqual(http_response["default_inputs"]["federal_itc_fraction"], 0.3) - - # inputs = { - # "prime_mover": "micro_turbine", - # "avg_electric_load_kw": 885.0247784246575, - # "max_electric_load_kw": 1427.334, - # "is_electric_only": "true" - # } - - # # Direct call of the http.jl endpoint /chp_defaults - # julia_host = os.environ.get('JULIA_HOST', "julia") - # response = requests.get("http://" + julia_host + ":8081/chp_defaults/", json=inputs) - # http_response = response.json() - - # # Check the endpoint logic with the expected selection - # self.assertEqual(http_response["default_inputs"]["federal_itc_fraction"], 0.3) - - # inputs = { - # "prime_mover": "combustion_turbine", - # "size_class": 4, - # "is_electric_only": "true", - # "avg_electric_load_kw": 885.0247784246575, - # "max_electric_load_kw": 1427.334 - # } - - # # Direct call of the http.jl endpoint /chp_defaults - # julia_host = os.environ.get('JULIA_HOST', "julia") - # response = requests.get("http://" + julia_host + ":8081/chp_defaults/", json=inputs) - # http_response = response.json() - - # # Check the endpoint logic with the expected selection - # self.assertEqual(http_response["default_inputs"]["federal_itc_fraction"], 0.0) - - # inputs = { - # "prime_mover": "recip_engine", - # "size_class": 4, - # "is_electric_only": "true", - # "avg_electric_load_kw": 885.0247784246575, - # "max_electric_load_kw": 1427.334 - # } - - # # Call to the django view endpoint /chp_defaults which calls the http.jl endpoint - # resp = self.api_client.get(f'/v3/chp_defaults', data=inputs) - # view_response = json.loads(resp.content) - - # # Check the endpoint logic with the expected selection - # self.assertEqual(http_response["default_inputs"]["federal_itc_fraction"], 0.0) + self.assertEqual(mismatch, []) + + # Check the endpoint logic with the expected selection + self.assertEqual(http_response["prime_mover"], "combustion_turbine") + self.assertEqual(http_response["size_class"], 2) + self.assertGreater(http_response["chp_elec_size_heuristic_kw"], 3500.0) + self.assertEqual(http_response["default_inputs"]["federal_itc_fraction"], 0.3) + + inputs = { + "prime_mover": "micro_turbine", + "avg_electric_load_kw": 885.0247784246575, + "max_electric_load_kw": 1427.334, + "is_electric_only": "true" + } + + # Direct call of the http.jl endpoint /chp_defaults + julia_host = os.environ.get('JULIA_HOST', "julia") + response = requests.get("http://" + julia_host + ":8081/chp_defaults/", json=inputs) + http_response = response.json() + + # Check the endpoint logic with the expected selection + self.assertEqual(http_response["default_inputs"]["federal_itc_fraction"], 0.3) + + inputs = { + "prime_mover": "combustion_turbine", + "size_class": 4, + "is_electric_only": "true", + "avg_electric_load_kw": 885.0247784246575, + "max_electric_load_kw": 1427.334 + } + + # Direct call of the http.jl endpoint /chp_defaults + julia_host = os.environ.get('JULIA_HOST', "julia") + response = requests.get("http://" + julia_host + ":8081/chp_defaults/", json=inputs) + http_response = response.json() + + # Check the endpoint logic with the expected selection + self.assertEqual(http_response["default_inputs"]["federal_itc_fraction"], 0.0) + + inputs = { + "prime_mover": "recip_engine", + "size_class": 4, + "is_electric_only": "true", + "avg_electric_load_kw": 885.0247784246575, + "max_electric_load_kw": 1427.334 + } + + # Call to the django view endpoint /chp_defaults which calls the http.jl endpoint + resp = self.api_client.get(f'/v3/chp_defaults', data=inputs) + view_response = json.loads(resp.content) + + # Check the endpoint logic with the expected selection + self.assertEqual(http_response["default_inputs"]["federal_itc_fraction"], 0.0) - # def test_steamturbine_defaults(self): - - # inputs = { - # "prime_mover": "steam_turbine", - # "avg_boiler_fuel_load_mmbtu_per_hour": 28.0 - # } - - # # Direct call of the http.jl endpoint /chp_defaults - # julia_host = os.environ.get('JULIA_HOST', "julia") - # response = requests.get("http://" + julia_host + ":8081/chp_defaults/", json=inputs) - # http_response = response.json() - - # # Call to the django view endpoint /chp_defaults which calls the http.jl endpoint - # resp = self.api_client.get(f'/v3/chp_defaults', data=inputs) - # view_response = json.loads(resp.content) - - # mismatch = [] - # for k, v in http_response["default_inputs"].items(): - # if v != view_response["default_inputs"][k]: - # mismatch.append(k) + def test_steamturbine_defaults(self): + + inputs = { + "prime_mover": "steam_turbine", + "avg_boiler_fuel_load_mmbtu_per_hour": 28.0 + } + + # Direct call of the http.jl endpoint /chp_defaults + julia_host = os.environ.get('JULIA_HOST', "julia") + response = requests.get("http://" + julia_host + ":8081/chp_defaults/", json=inputs) + http_response = response.json() + + # Call to the django view endpoint /chp_defaults which calls the http.jl endpoint + resp = self.api_client.get(f'/v3/chp_defaults', data=inputs) + view_response = json.loads(resp.content) + + mismatch = [] + for k, v in http_response["default_inputs"].items(): + if v != view_response["default_inputs"][k]: + mismatch.append(k) - # self.assertEqual(mismatch, []) + self.assertEqual(mismatch, []) - # # Check the endpoint logic with the expected selection - # self.assertEqual(http_response["prime_mover"], "steam_turbine") - # self.assertEqual(http_response["size_class"], 1) - # self.assertGreater(http_response["chp_elec_size_heuristic_kw"], 574.419) + # Check the endpoint logic with the expected selection + self.assertEqual(http_response["prime_mover"], "steam_turbine") + self.assertEqual(http_response["size_class"], 1) + self.assertGreater(http_response["chp_elec_size_heuristic_kw"], 574.419) - # def test_absorption_chiller_defaults(self): + def test_absorption_chiller_defaults(self): - # inputs = {"thermal_consumption_hot_water_or_steam": "hot_water", - # "load_max_tons": 50 - # } + inputs = {"thermal_consumption_hot_water_or_steam": "hot_water", + "load_max_tons": 50 + } - # # Direct call of the http.jl endpoint /absorption_chiller_defaults - # julia_host = os.environ.get('JULIA_HOST', "julia") - # response = requests.get("http://" + julia_host + ":8081/absorption_chiller_defaults/", json=inputs) - # http_response = response.json() + # Direct call of the http.jl endpoint /absorption_chiller_defaults + julia_host = os.environ.get('JULIA_HOST', "julia") + response = requests.get("http://" + julia_host + ":8081/absorption_chiller_defaults/", json=inputs) + http_response = response.json() - # # Call to the django view endpoint /absorption_chiller_defaults which calls the http.jl endpoint - # resp = self.api_client.get(f'/v3/absorption_chiller_defaults', data=inputs) - # view_response = json.loads(resp.content) + # Call to the django view endpoint /absorption_chiller_defaults which calls the http.jl endpoint + resp = self.api_client.get(f'/v3/absorption_chiller_defaults', data=inputs) + view_response = json.loads(resp.content) - # mismatch = [] - # for k, v in http_response["default_inputs"].items(): - # if v != view_response["default_inputs"][k]: - # mismatch.append(k) + mismatch = [] + for k, v in http_response["default_inputs"].items(): + if v != view_response["default_inputs"][k]: + mismatch.append(k) - # self.assertEqual(mismatch, []) - - # # Check the endpoint logic with the expected selection - # self.assertEqual(http_response["thermal_consumption_hot_water_or_steam"], "hot_water") - # self.assertEqual(http_response["default_inputs"]["om_cost_per_ton"], 80.0) - # self.assertEqual(http_response["default_inputs"]["installed_cost_per_ton"], 3066.0) - # self.assertEqual(http_response["default_inputs"]["cop_thermal"], 0.74) - # self.assertNotIn("thermal_consumption_hot_water_or_steam", http_response["default_inputs"].keys()) + self.assertEqual(mismatch, []) + + # Check the endpoint logic with the expected selection + self.assertEqual(http_response["thermal_consumption_hot_water_or_steam"], "hot_water") + self.assertEqual(http_response["default_inputs"]["om_cost_per_ton"], 80.0) + self.assertEqual(http_response["default_inputs"]["installed_cost_per_ton"], 3066.0) + self.assertEqual(http_response["default_inputs"]["cop_thermal"], 0.74) + self.assertNotIn("thermal_consumption_hot_water_or_steam", http_response["default_inputs"].keys()) - # def test_simulated_load(self): - - # # Test heating load because REopt.jl separates SpaceHeating and DHW, so had to aggregate for this endpoint - # inputs = {"load_type": "heating", - # "doe_reference_name": "Hospital", - # "latitude": 36.12, - # "longitude": -115.5 - # } - - # # The /v3/simulated_load endpoint calls the http.jl /simulated_load endpoint - # response = self.api_client.get(f'/v3/simulated_load', data=inputs) - # http_response = json.loads(response.content) - - # # Call to the v2 /simulated_load to check for consistency - # resp = self.api_client.get(f'/v2/simulated_load', data=inputs) - # v2_response = json.loads(resp.content) - # self.assertAlmostEqual(http_response["annual_mmbtu"], v2_response["annual_mmbtu"], delta=1.0) - - # # Test blended/hybrid buildings - # inputs["load_type"] = "electric" - # inputs["annual_kwh"] = 1.5E7 - # inputs["doe_reference_name[0]"] = "Hospital" - # inputs["doe_reference_name[1]"] = "LargeOffice" - # inputs["percent_share[0]"] = 0.25 - # inputs["percent_share[1]"] = 1.0 - inputs["percent_share[0]"] + def test_simulated_load(self): + + # Test heating load because REopt.jl separates SpaceHeating and DHW, so had to aggregate for this endpoint + inputs = {"load_type": "heating", + "doe_reference_name": "Hospital", + "latitude": 36.12, + "longitude": -115.5 + } + + # The /v3/simulated_load endpoint calls the http.jl /simulated_load endpoint + response = self.api_client.get(f'/v3/simulated_load', data=inputs) + http_response = json.loads(response.content) + + # Call to the v2 /simulated_load to check for consistency + resp = self.api_client.get(f'/v2/simulated_load', data=inputs) + v2_response = json.loads(resp.content) + self.assertAlmostEqual(http_response["annual_mmbtu"], v2_response["annual_mmbtu"], delta=1.0) + + # Test blended/hybrid buildings + inputs["load_type"] = "electric" + inputs["annual_kwh"] = 1.5E7 + inputs["doe_reference_name[0]"] = "Hospital" + inputs["doe_reference_name[1]"] = "LargeOffice" + inputs["percent_share[0]"] = 0.25 + inputs["percent_share[1]"] = 1.0 - inputs["percent_share[0]"] - # # The /v3/simulated_load endpoint calls the http.jl /simulated_load endpoint - # response = self.api_client.get(f'/v3/simulated_load', data=inputs) - # http_response = json.loads(response.content) - - # # Call to the v2 /simulated_load to check for consistency - # resp = self.api_client.get(f'/v2/simulated_load', data=inputs) - # v2_response = json.loads(resp.content) - # self.assertAlmostEqual(http_response["annual_kwh"], v2_response["annual_kwh"], delta=1.0) - - # # Test bad inputs - # inputs["invalid_key"] = "invalid_val" - # resp = self.api_client.get(f'/v2/simulated_load', data=inputs) - # v2_response = json.loads(resp.content) - # assert("Error" in v2_response.keys()) - - # def test_avert_emissions_profile_endpoint(self): - # # Call to the django view endpoint dev/avert_emissions_profile which calls the http.jl endpoint - # #case 1: location in CONUS (Seattle, WA) - # inputs = { - # "latitude": 47.606211, - # "longitude": -122.336052, - # "load_year": 2021 - # } - # resp = self.api_client.get(f'/v3/avert_emissions_profile', data=inputs) - # self.assertHttpOK(resp) - # view_response = json.loads(resp.content) - # self.assertEquals(view_response["avert_meters_to_region"], 0.0) - # self.assertEquals(view_response["avert_region"], "Northwest") - # self.assertEquals(len(view_response["emissions_factor_series_lb_NOx_per_kwh"]), 8760) - # #case 2: location off shore of NJ (works for AVERT, not Cambium) - # inputs = { - # "latitude": 39.034417, - # "longitude": -74.759292, - # "load_year": 2021 - # } - # resp = self.api_client.get(f'/v3/avert_emissions_profile', data=inputs) - # self.assertHttpOK(resp) - # view_response = json.loads(resp.content) - # self.assertAlmostEqual(view_response["avert_meters_to_region"], 760.62, delta=1.0) - # self.assertEquals(view_response["avert_region"], "Mid-Atlantic") - # self.assertEquals(len(view_response["emissions_factor_series_lb_NOx_per_kwh"]), 8760) - # #case 3: Honolulu, HI (works for AVERT but not Cambium) - # inputs = { - # "latitude": 21.3099, - # "longitude": -157.8581, - # "load_year": 2021 - # } - # resp = self.api_client.get(f'/v3/avert_emissions_profile', data=inputs) - # self.assertHttpOK(resp) - # view_response = json.loads(resp.content) - # self.assertEquals(view_response["avert_meters_to_region"], 0.0) - # self.assertEquals(view_response["avert_region"], "Hawaii (Oahu)") - # self.assertEquals(len(view_response["emissions_factor_series_lb_NOx_per_kwh"]), 8760) - # #case 4: location well outside of US (does not work) - # inputs = { - # "latitude": 0.0, - # "longitude": 0.0, - # "load_year": 2022 - # } - # resp = self.api_client.get(f'/v3/avert_emissions_profile', data=inputs) - # self.assertHttpBadRequest(resp) - # view_response = json.loads(resp.content) - # self.assertTrue("error" in view_response) + # The /v3/simulated_load endpoint calls the http.jl /simulated_load endpoint + response = self.api_client.get(f'/v3/simulated_load', data=inputs) + http_response = json.loads(response.content) + + # Call to the v2 /simulated_load to check for consistency + resp = self.api_client.get(f'/v2/simulated_load', data=inputs) + v2_response = json.loads(resp.content) + self.assertAlmostEqual(http_response["annual_kwh"], v2_response["annual_kwh"], delta=1.0) + + # Test bad inputs + inputs["invalid_key"] = "invalid_val" + resp = self.api_client.get(f'/v2/simulated_load', data=inputs) + v2_response = json.loads(resp.content) + assert("Error" in v2_response.keys()) + + def test_avert_emissions_profile_endpoint(self): + # Call to the django view endpoint dev/avert_emissions_profile which calls the http.jl endpoint + #case 1: location in CONUS (Seattle, WA) + inputs = { + "latitude": 47.606211, + "longitude": -122.336052, + "load_year": 2021 + } + resp = self.api_client.get(f'/v3/avert_emissions_profile', data=inputs) + self.assertHttpOK(resp) + view_response = json.loads(resp.content) + self.assertEquals(view_response["avert_meters_to_region"], 0.0) + self.assertEquals(view_response["avert_region"], "Northwest") + self.assertEquals(len(view_response["emissions_factor_series_lb_NOx_per_kwh"]), 8760) + #case 2: location off shore of NJ (works for AVERT, not Cambium) + inputs = { + "latitude": 39.034417, + "longitude": -74.759292, + "load_year": 2021 + } + resp = self.api_client.get(f'/v3/avert_emissions_profile', data=inputs) + self.assertHttpOK(resp) + view_response = json.loads(resp.content) + self.assertAlmostEqual(view_response["avert_meters_to_region"], 760.62, delta=1.0) + self.assertEquals(view_response["avert_region"], "Mid-Atlantic") + self.assertEquals(len(view_response["emissions_factor_series_lb_NOx_per_kwh"]), 8760) + #case 3: Honolulu, HI (works for AVERT but not Cambium) + inputs = { + "latitude": 21.3099, + "longitude": -157.8581, + "load_year": 2021 + } + resp = self.api_client.get(f'/v3/avert_emissions_profile', data=inputs) + self.assertHttpOK(resp) + view_response = json.loads(resp.content) + self.assertEquals(view_response["avert_meters_to_region"], 0.0) + self.assertEquals(view_response["avert_region"], "Hawaii (Oahu)") + self.assertEquals(len(view_response["emissions_factor_series_lb_NOx_per_kwh"]), 8760) + #case 4: location well outside of US (does not work) + inputs = { + "latitude": 0.0, + "longitude": 0.0, + "load_year": 2022 + } + resp = self.api_client.get(f'/v3/avert_emissions_profile', data=inputs) + self.assertHttpBadRequest(resp) + view_response = json.loads(resp.content) + self.assertTrue("error" in view_response) - # def test_cambium_emissions_profile_endpoint(self): - # # Call to the django view endpoint v3/cambium_emissions_profile which calls the http.jl endpoint - # #case 1: location in CONUS (Seattle, WA) - # inputs = { - # "load_year": 2021, - # "scenario": "Mid-case", - # "location_type": "States", - # "latitude": 47.606211, # Seattle - # "longitude": -122.336052, # Seattle - # "start_year": 2024, - # "lifetime": 25, - # "metric_col": "lrmer_co2e", - # "grid_level": "enduse" - # } - # resp = self.api_client.get(f'/v3/cambium_emissions_profile', data=inputs) - # self.assertHttpOK(resp) - # view_response = json.loads(resp.content) - # self.assertEquals(view_response["metric_col"], "lrmer_co2e") - # self.assertEquals(view_response["location"], "Washington") - # self.assertEquals(len(view_response["emissions_factor_series_lb_CO2_per_kwh"]), 8760) - # #case 2: location off shore of NJ (works for AVERT, not Cambium) - # inputs["latitude"] = 39.034417 - # inputs["longitude"] = -74.759292 - # resp = self.api_client.get(f'/v3/cambium_emissions_profile', data=inputs) - # self.assertHttpBadRequest(resp) - # view_response = json.loads(resp.content) - # self.assertTrue("error" in view_response) - # #case 3: Honolulu, HI (works for AVERT but not Cambium) - # inputs["latitude"] = 21.3099 - # inputs["longitude"] = -157.8581 - # resp = self.api_client.get(f'/v3/cambium_emissions_profile', data=inputs) - # self.assertHttpBadRequest(resp) - # view_response = json.loads(resp.content) - # self.assertTrue("error" in view_response) - # #case 4: location well outside of US (does not work) - # inputs["latitude"] = 0.0 - # inputs["longitude"] = 0.0 - # resp = self.api_client.get(f'/v3/cambium_emissions_profile', data=inputs) - # self.assertHttpBadRequest(resp) - # view_response = json.loads(resp.content) - # self.assertTrue("error" in view_response) - - # def test_easiur_endpoint(self): - # # Call to the django view endpoint dev/easiur_costs which calls the http.jl endpoint - # inputs = { - # "latitude": 47.606211, - # "longitude": -122.336052, - # "inflation": 0.025 - # } - # resp = self.api_client.get(f'/v3/easiur_costs', data=inputs) - # self.assertHttpOK(resp) - # view_response = json.loads(resp.content) - # for ekey in ["NOx", "SO2", "PM25"]: - # for key_format in ["{}_grid_cost_per_tonne", "{}_onsite_fuelburn_cost_per_tonne", "{}_cost_escalation_rate_fraction"]: - # self.assertTrue(type(view_response[key_format.format(ekey)]) == float) - # inputs = { - # "latitude": 47.606211, - # "longitude": 122.336052, - # "inflation": 0.025 - # } - # resp = self.api_client.get(f'/v3/easiur_costs', data=inputs) - # self.assertHttpBadRequest(resp) - # view_response = json.loads(resp.content) - # self.assertTrue("error" in view_response) - - # def test_ghp_endpoints(self): - # # Test /ghp_efficiency_thermal_factors - # inputs_dict = {"latitude": 37.78, - # "longitude": -122.45, - # "doe_reference_name": "MediumOffice"} - - # # Call to the django view endpoint /ghp_efficiency_thermal_factors which calls the http.jl endpoint - # resp = self.api_client.get(f'/v3/ghp_efficiency_thermal_factors', data=inputs_dict) - # view_response = json.loads(resp.content) - - # self.assertEqual(view_response["cooling_efficiency_thermal_factor"], 0.43) - # self.assertEqual(view_response["space_heating_efficiency_thermal_factor"], 0.46) - - # # Test /ghpghx/ground_conductivity - # inputs_dict = {"latitude": 37.78, - # "longitude": -122.45} - - # # Call to the django view endpoint /ghpghx/ground_conductivity which calls the http.jl endpoint - # resp = self.api_client.get(f'/v3/ghpghx/ground_conductivity', data=inputs_dict) - # view_response = json.loads(resp.content) - - # self.assertEqual(view_response["thermal_conductivity"], 1.117) - - # def test_default_existing_chiller_cop(self): - # # Test 1: full dictionary - # inputs_dict = { - # "existing_chiller_max_thermal_factor_on_peak_load":1.25, - # "max_load_kw": 50, - # "max_load_ton":10 - # } - - # # Call to the django view endpoint /get_existing_chiller_default_cop which calls the http.jl endpoint - # resp = self.api_client.get(f'/v3/get_existing_chiller_default_cop', data=inputs_dict) - # view_response = json.loads(resp.content) - - # self.assertEqual(view_response["existing_chiller_cop"], 4.4) - - # # Test 2: empty dictionary, which should return unknown value - # inputs_dict = {} - - # # Call to the django view endpoint /get_existing_chiller_default_cop which calls the http.jl endpoint - # resp = self.api_client.get(f'/v3/get_existing_chiller_default_cop', data=inputs_dict) - # view_response = json.loads(resp.content) - - # self.assertEqual(view_response["existing_chiller_cop"], 4.545) - - # # Test 3: Check that "existing_chiller_max_thermal_factor_on_peak_load" can influence the COP; accept max_load_ton empty string - # inputs_dict = { - # "existing_chiller_max_thermal_factor_on_peak_load":1000, - # "max_load_kw": 5, - # "max_load_ton":"" - # } - - # # Call to the django view endpoint /get_existing_chiller_default_cop which calls the http.jl endpoint - # resp = self.api_client.get(f'/v3/get_existing_chiller_default_cop', data=inputs_dict) - # view_response = json.loads(resp.content) - - # self.assertEqual(view_response["existing_chiller_cop"], 4.69) - - # # Test 4: max_load_ton empty string - # inputs_dict = { - # "max_load_ton":90, - # "max_load_kw":"" - # } - - # # Call to the django view endpoint /get_existing_chiller_default_cop which calls the http.jl endpoint - # resp = self.api_client.get(f'/v3/get_existing_chiller_default_cop', data=inputs_dict) - # view_response = json.loads(resp.content) - - # self.assertEqual(view_response["existing_chiller_cop"], 4.69) - - # #Test 5: max_load_kw only, small value yields low COP - # inputs_dict = { - # "max_load_kw":1 - # } - - # # Call to the django view endpoint /get_existing_chiller_default_cop which calls the http.jl endpoint - # resp = self.api_client.get(f'/v3/get_existing_chiller_default_cop', data=inputs_dict) - # view_response = json.loads(resp.content) - - # self.assertEqual(view_response["existing_chiller_cop"], 4.4) + def test_cambium_emissions_profile_endpoint(self): + # Call to the django view endpoint v3/cambium_emissions_profile which calls the http.jl endpoint + #case 1: location in CONUS (Seattle, WA) + inputs = { + "load_year": 2021, + "scenario": "Mid-case", + "location_type": "States", + "latitude": 47.606211, # Seattle + "longitude": -122.336052, # Seattle + "start_year": 2024, + "lifetime": 25, + "metric_col": "lrmer_co2e", + "grid_level": "enduse" + } + resp = self.api_client.get(f'/v3/cambium_emissions_profile', data=inputs) + self.assertHttpOK(resp) + view_response = json.loads(resp.content) + self.assertEquals(view_response["metric_col"], "lrmer_co2e") + self.assertEquals(view_response["location"], "Washington") + self.assertEquals(len(view_response["emissions_factor_series_lb_CO2_per_kwh"]), 8760) + #case 2: location off shore of NJ (works for AVERT, not Cambium) + inputs["latitude"] = 39.034417 + inputs["longitude"] = -74.759292 + resp = self.api_client.get(f'/v3/cambium_emissions_profile', data=inputs) + self.assertHttpBadRequest(resp) + view_response = json.loads(resp.content) + self.assertTrue("error" in view_response) + #case 3: Honolulu, HI (works for AVERT but not Cambium) + inputs["latitude"] = 21.3099 + inputs["longitude"] = -157.8581 + resp = self.api_client.get(f'/v3/cambium_emissions_profile', data=inputs) + self.assertHttpBadRequest(resp) + view_response = json.loads(resp.content) + self.assertTrue("error" in view_response) + #case 4: location well outside of US (does not work) + inputs["latitude"] = 0.0 + inputs["longitude"] = 0.0 + resp = self.api_client.get(f'/v3/cambium_emissions_profile', data=inputs) + self.assertHttpBadRequest(resp) + view_response = json.loads(resp.content) + self.assertTrue("error" in view_response) + + def test_easiur_endpoint(self): + # Call to the django view endpoint dev/easiur_costs which calls the http.jl endpoint + inputs = { + "latitude": 47.606211, + "longitude": -122.336052, + "inflation": 0.025 + } + resp = self.api_client.get(f'/v3/easiur_costs', data=inputs) + self.assertHttpOK(resp) + view_response = json.loads(resp.content) + for ekey in ["NOx", "SO2", "PM25"]: + for key_format in ["{}_grid_cost_per_tonne", "{}_onsite_fuelburn_cost_per_tonne", "{}_cost_escalation_rate_fraction"]: + self.assertTrue(type(view_response[key_format.format(ekey)]) == float) + inputs = { + "latitude": 47.606211, + "longitude": 122.336052, + "inflation": 0.025 + } + resp = self.api_client.get(f'/v3/easiur_costs', data=inputs) + self.assertHttpBadRequest(resp) + view_response = json.loads(resp.content) + self.assertTrue("error" in view_response) + + def test_ghp_endpoints(self): + # Test /ghp_efficiency_thermal_factors + inputs_dict = {"latitude": 37.78, + "longitude": -122.45, + "doe_reference_name": "MediumOffice"} + + # Call to the django view endpoint /ghp_efficiency_thermal_factors which calls the http.jl endpoint + resp = self.api_client.get(f'/v3/ghp_efficiency_thermal_factors', data=inputs_dict) + view_response = json.loads(resp.content) + + self.assertEqual(view_response["cooling_efficiency_thermal_factor"], 0.43) + self.assertEqual(view_response["space_heating_efficiency_thermal_factor"], 0.46) + + # Test /ghpghx/ground_conductivity + inputs_dict = {"latitude": 37.78, + "longitude": -122.45} + + # Call to the django view endpoint /ghpghx/ground_conductivity which calls the http.jl endpoint + resp = self.api_client.get(f'/v3/ghpghx/ground_conductivity', data=inputs_dict) + view_response = json.loads(resp.content) + + self.assertEqual(view_response["thermal_conductivity"], 1.117) + + def test_default_existing_chiller_cop(self): + # Test 1: full dictionary + inputs_dict = { + "existing_chiller_max_thermal_factor_on_peak_load":1.25, + "max_load_kw": 50, + "max_load_ton":10 + } + + # Call to the django view endpoint /get_existing_chiller_default_cop which calls the http.jl endpoint + resp = self.api_client.get(f'/v3/get_existing_chiller_default_cop', data=inputs_dict) + view_response = json.loads(resp.content) + + self.assertEqual(view_response["existing_chiller_cop"], 4.4) + + # Test 2: empty dictionary, which should return unknown value + inputs_dict = {} + + # Call to the django view endpoint /get_existing_chiller_default_cop which calls the http.jl endpoint + resp = self.api_client.get(f'/v3/get_existing_chiller_default_cop', data=inputs_dict) + view_response = json.loads(resp.content) + + self.assertEqual(view_response["existing_chiller_cop"], 4.545) + + # Test 3: Check that "existing_chiller_max_thermal_factor_on_peak_load" can influence the COP; accept max_load_ton empty string + inputs_dict = { + "existing_chiller_max_thermal_factor_on_peak_load":1000, + "max_load_kw": 5, + "max_load_ton":"" + } + + # Call to the django view endpoint /get_existing_chiller_default_cop which calls the http.jl endpoint + resp = self.api_client.get(f'/v3/get_existing_chiller_default_cop', data=inputs_dict) + view_response = json.loads(resp.content) + + self.assertEqual(view_response["existing_chiller_cop"], 4.69) + + # Test 4: max_load_ton empty string + inputs_dict = { + "max_load_ton":90, + "max_load_kw":"" + } + + # Call to the django view endpoint /get_existing_chiller_default_cop which calls the http.jl endpoint + resp = self.api_client.get(f'/v3/get_existing_chiller_default_cop', data=inputs_dict) + view_response = json.loads(resp.content) + + self.assertEqual(view_response["existing_chiller_cop"], 4.69) + + #Test 5: max_load_kw only, small value yields low COP + inputs_dict = { + "max_load_kw":1 + } + + # Call to the django view endpoint /get_existing_chiller_default_cop which calls the http.jl endpoint + resp = self.api_client.get(f'/v3/get_existing_chiller_default_cop', data=inputs_dict) + view_response = json.loads(resp.content) + + self.assertEqual(view_response["existing_chiller_cop"], 4.4) diff --git a/reoptjl/test/test_job_endpoint.py b/reoptjl/test/test_job_endpoint.py index 9b234508b..407e1333e 100644 --- a/reoptjl/test/test_job_endpoint.py +++ b/reoptjl/test/test_job_endpoint.py @@ -36,275 +36,275 @@ def test_multiple_outages(self): self.assertAlmostEqual(results["Outages"]["microgrid_upgrade_capital_cost"], 1974429.4, delta=5000.0) self.assertAlmostEqual(results["Financial"]["lcc"], 59865240.0, delta=0.01*results["Financial"]["lcc"]) - # def test_pv_battery_and_emissions_defaults_from_julia(self): - # """ - # Same test post as "Solar and ElectricStorage w/BAU" in the Julia package. Used in development of v3. - # Also tests that inputs with defaults determined in the REopt julia package get updated in the database. - # """ - # post_file = os.path.join('reoptjl', 'test', 'posts', 'pv_batt_emissions.json') - # post = json.load(open(post_file, 'r')) - - # resp = self.api_client.post('/v3/job/', format='json', data=post) - # self.assertHttpCreated(resp) - # r = json.loads(resp.content) - # run_uuid = r.get('run_uuid') - - # resp = self.api_client.get(f'/v3/job/{run_uuid}/results') - # r = json.loads(resp.content) - # results = r["outputs"] - - # self.assertAlmostEqual(results["Financial"]["lcc"], 12391786, places=-3) - # self.assertAlmostEqual(results["Financial"]["lcc_bau"], 12766397, places=-3) - # self.assertAlmostEqual(results["PV"]["size_kw"], 216.667, places=1) - # self.assertAlmostEqual(results["ElectricStorage"]["size_kw"], 49.05, places=1) - # self.assertAlmostEqual(results["ElectricStorage"]["size_kwh"], 83.32, places=1) + def test_pv_battery_and_emissions_defaults_from_julia(self): + """ + Same test post as "Solar and ElectricStorage w/BAU" in the Julia package. Used in development of v3. + Also tests that inputs with defaults determined in the REopt julia package get updated in the database. + """ + post_file = os.path.join('reoptjl', 'test', 'posts', 'pv_batt_emissions.json') + post = json.load(open(post_file, 'r')) + + resp = self.api_client.post('/v3/job/', format='json', data=post) + self.assertHttpCreated(resp) + r = json.loads(resp.content) + run_uuid = r.get('run_uuid') + + resp = self.api_client.get(f'/v3/job/{run_uuid}/results') + r = json.loads(resp.content) + results = r["outputs"] + + self.assertAlmostEqual(results["Financial"]["lcc"], 12391786, places=-3) + self.assertAlmostEqual(results["Financial"]["lcc_bau"], 12766397, places=-3) + self.assertAlmostEqual(results["PV"]["size_kw"], 216.667, places=1) + self.assertAlmostEqual(results["ElectricStorage"]["size_kw"], 49.05, places=1) + self.assertAlmostEqual(results["ElectricStorage"]["size_kwh"], 83.32, places=1) - # self.assertIsNotNone(results["Site"]["total_renewable_energy_fraction"]) - # self.assertIsNotNone(results["Site"]["annual_emissions_tonnes_CO2"]) - # self.assertIsNotNone(results["Site"]["lifecycle_emissions_tonnes_NOx"]) - - # #test that emissions inputs got updated in the database with the defaults determined in REopt julia package - # updated_inputs = r["inputs"] - # self.assertIsNotNone(updated_inputs["ElectricUtility"]["emissions_factor_series_lb_CO2_per_kwh"]) - # self.assertIsNotNone(updated_inputs["Financial"]["NOx_grid_cost_per_tonne"]) - # self.assertIsNotNone(updated_inputs["Financial"]["SO2_onsite_fuelburn_cost_per_tonne"]) - # self.assertIsNotNone(updated_inputs["Financial"]["PM25_cost_escalation_rate_fraction"]) - - # def test_off_grid_defaults(self): - # """ - # Purpose of this test is to validate off-grid functionality and defaults in the API. - # """ - # post_file = os.path.join('reoptjl', 'test', 'posts', 'off_grid_defaults.json') - # post = json.load(open(post_file, 'r')) - - # resp = self.api_client.post('/v3/job/', format='json', data=post) - # self.assertHttpCreated(resp) - # r = json.loads(resp.content) - # run_uuid = r.get('run_uuid') - - # resp = self.api_client.get(f'/v3/job/{run_uuid}/results') - # r = json.loads(resp.content) - # results = r["outputs"] + self.assertIsNotNone(results["Site"]["total_renewable_energy_fraction"]) + self.assertIsNotNone(results["Site"]["annual_emissions_tonnes_CO2"]) + self.assertIsNotNone(results["Site"]["lifecycle_emissions_tonnes_NOx"]) + + #test that emissions inputs got updated in the database with the defaults determined in REopt julia package + updated_inputs = r["inputs"] + self.assertIsNotNone(updated_inputs["ElectricUtility"]["emissions_factor_series_lb_CO2_per_kwh"]) + self.assertIsNotNone(updated_inputs["Financial"]["NOx_grid_cost_per_tonne"]) + self.assertIsNotNone(updated_inputs["Financial"]["SO2_onsite_fuelburn_cost_per_tonne"]) + self.assertIsNotNone(updated_inputs["Financial"]["PM25_cost_escalation_rate_fraction"]) + + def test_off_grid_defaults(self): + """ + Purpose of this test is to validate off-grid functionality and defaults in the API. + """ + post_file = os.path.join('reoptjl', 'test', 'posts', 'off_grid_defaults.json') + post = json.load(open(post_file, 'r')) + + resp = self.api_client.post('/v3/job/', format='json', data=post) + self.assertHttpCreated(resp) + r = json.loads(resp.content) + run_uuid = r.get('run_uuid') + + resp = self.api_client.get(f'/v3/job/{run_uuid}/results') + r = json.loads(resp.content) + results = r["outputs"] - # # Validate that we got off-grid response fields - # self.assertAlmostEqual(results["Financial"]["offgrid_microgrid_lcoe_dollars_per_kwh"], 0.337, places=-3) - # self.assertAlmostEqual(results["ElectricTariff"]["year_one_bill_before_tax"], 0.0) - # self.assertAlmostEqual(results["ElectricLoad"]["offgrid_load_met_fraction"], 0.99999, places=-2) - # self.assertAlmostEqual(sum(results["ElectricLoad"]["offgrid_load_met_series_kw"]), 8760.0, places=-1) - # self.assertAlmostEqual(results["Financial"]["lifecycle_offgrid_other_annual_costs_after_tax"], 0.0, places=-2) + # Validate that we got off-grid response fields + self.assertAlmostEqual(results["Financial"]["offgrid_microgrid_lcoe_dollars_per_kwh"], 0.337, places=-3) + self.assertAlmostEqual(results["ElectricTariff"]["year_one_bill_before_tax"], 0.0) + self.assertAlmostEqual(results["ElectricLoad"]["offgrid_load_met_fraction"], 0.99999, places=-2) + self.assertAlmostEqual(sum(results["ElectricLoad"]["offgrid_load_met_series_kw"]), 8760.0, places=-1) + self.assertAlmostEqual(results["Financial"]["lifecycle_offgrid_other_annual_costs_after_tax"], 0.0, places=-2) - # def test_process_reopt_error(self): - # """ - # Purpose of this test is to ensure REopt status 400 is returned using the reoptjl endpoint - # """ - - # post_file = os.path.join('reoptjl', 'test', 'posts', 'handle_reopt_error.json') - # post = json.load(open(post_file, 'r')) - - # resp = self.api_client.post('/v3/job/', format='json', data=post) - # self.assertHttpCreated(resp) - # r = json.loads(resp.content) - # run_uuid = r.get('run_uuid') - - # resp = self.api_client.get(f'/v3/job/{run_uuid}/results') - # r = json.loads(resp.content) - # assert('errors' in r["messages"].keys()) - # assert('warnings' in r["messages"].keys()) - # assert(r['messages']['has_stacktrace']==True) - # assert(resp.status_code==400) - - - # def test_thermal_in_results(self): - # """ - # Purpose of this test is to check that the expected thermal loads, techs, and storage are included in the results - # """ - - # post_file = os.path.join('reoptjl', 'test', 'posts', 'test_thermal_in_results.json') #includes GhpGhx responses - # post = json.load(open(post_file, 'r')) - - # resp = self.api_client.post('/v3/job/', format='json', data=post) - # self.assertHttpCreated(resp) - # r = json.loads(resp.content) - # run_uuid = r.get('run_uuid') + def test_process_reopt_error(self): + """ + Purpose of this test is to ensure REopt status 400 is returned using the reoptjl endpoint + """ + + post_file = os.path.join('reoptjl', 'test', 'posts', 'handle_reopt_error.json') + post = json.load(open(post_file, 'r')) + + resp = self.api_client.post('/v3/job/', format='json', data=post) + self.assertHttpCreated(resp) + r = json.loads(resp.content) + run_uuid = r.get('run_uuid') + + resp = self.api_client.get(f'/v3/job/{run_uuid}/results') + r = json.loads(resp.content) + assert('errors' in r["messages"].keys()) + assert('warnings' in r["messages"].keys()) + assert(r['messages']['has_stacktrace']==True) + assert(resp.status_code==400) + + + def test_thermal_in_results(self): + """ + Purpose of this test is to check that the expected thermal loads, techs, and storage are included in the results + """ + + post_file = os.path.join('reoptjl', 'test', 'posts', 'test_thermal_in_results.json') #includes GhpGhx responses + post = json.load(open(post_file, 'r')) + + resp = self.api_client.post('/v3/job/', format='json', data=post) + self.assertHttpCreated(resp) + r = json.loads(resp.content) + run_uuid = r.get('run_uuid') - # resp = self.api_client.get(f'/v3/job/{run_uuid}/results') - # r = json.loads(resp.content) - # inputs = r["inputs"] - # results = r["outputs"] - # self.assertIn("CoolingLoad", list(inputs.keys())) - # self.assertIn("CoolingLoad", list(results.keys())) - # self.assertIn("CHP", list(results.keys())) - # self.assertIn("thermal_to_dhw_load_series_mmbtu_per_hour", list(results["CHP"].keys())) - # self.assertIn("thermal_to_space_heating_load_series_mmbtu_per_hour", list(results["CHP"].keys())) - # self.assertIn("thermal_to_dhw_load_series_mmbtu_per_hour", list(results["CHP"].keys())) - # self.assertIn("ExistingChiller",list(results.keys())) - # self.assertIn("ExistingBoiler", list(results.keys())) - # self.assertIn("HeatingLoad", list(results.keys())) - # self.assertIn("process_heat_thermal_load_series_mmbtu_per_hour", list(results["HeatingLoad"].keys())) - # self.assertIn("process_heat_boiler_fuel_load_series_mmbtu_per_hour", list(results["HeatingLoad"].keys())) - # self.assertIn("HotThermalStorage", list(results.keys())) - # self.assertIn("storage_to_dhw_load_series_mmbtu_per_hour", list(results["HotThermalStorage"].keys())) - # self.assertIn("storage_to_space_heating_load_series_mmbtu_per_hour", list(results["HotThermalStorage"].keys())) - # self.assertIn("storage_to_dhw_load_series_mmbtu_per_hour", list(results["HotThermalStorage"].keys())) - # self.assertIn("ColdThermalStorage", list(results.keys())) - # self.assertIn("AbsorptionChiller", list(results.keys())) - # self.assertIn("GHP", list(results.keys())) - - - # def test_chp_defaults_from_julia(self): - # # Test that the inputs_with_defaults_set_in_julia feature worked for CHP, consistent with /chp_defaults - # post_file = os.path.join('reoptjl', 'test', 'posts', 'chp_defaults_post.json') - # post = json.load(open(post_file, 'r')) - # # Make average MMBtu/hr thermal steam greater than 7 MMBtu/hr threshold for combustion_turbine to be chosen - # # Default ExistingBoiler efficiency for production_type = steam is 0.75 - # post["SpaceHeatingLoad"]["annual_mmbtu"] = 8760 * 8 / 0.75 - # post["DomesticHotWaterLoad"]["annual_mmbtu"] = 8760 * 8 / 0.75 - # resp = self.api_client.post('/v3/job/', format='json', data=post) - # self.assertHttpCreated(resp) - # r = json.loads(resp.content) - # run_uuid = r.get('run_uuid') - - # resp = self.api_client.get(f'/v3/job/{run_uuid}/results') - # r = json.loads(resp.content) - - # inputs_chp = r["inputs"]["CHP"] - - # avg_fuel_load = (post["SpaceHeatingLoad"]["annual_mmbtu"] + - # post["DomesticHotWaterLoad"]["annual_mmbtu"]) / 8760.0 - # inputs_chp_defaults = {"hot_water_or_steam": post["ExistingBoiler"]["production_type"], - # "avg_boiler_fuel_load_mmbtu_per_hour": avg_fuel_load - # } - - # # Call to the django view endpoint /chp_defaults which calls the http.jl endpoint - # resp = self.api_client.get(f'/v3/chp_defaults', data=inputs_chp_defaults) - # view_response = json.loads(resp.content) - - # for key in view_response["default_inputs"].keys(): - # if post["CHP"].get(key) is None: # Check that default got assigned consistent with /chp_defaults - # if key == "max_kw": - # self.assertEquals(inputs_chp[key], view_response["chp_max_size_kw"]) - # else: - # self.assertEquals(inputs_chp[key], view_response["default_inputs"][key]) - # else: # Make sure we didn't overwrite user-input - # self.assertEquals(inputs_chp[key], post["CHP"][key]) - - # def test_peak_load_outage_times(self): - # """ - # Purpose of this test is to test the endpoint /peak_load_outage_times - # """ - - # load = [100]*8760 - # load[40*24] = 200 - # load[50*24-1] = 300 - # load[70*24+13] = 300 - # load[170*24] = 300 - # load[243*24] = 400 - # outage_inputs = {"seasonal_peaks": True, - # "outage_duration": 95, - # "critical_load": load, - # "start_not_center_on_peaks": False - # } - # expected_time_steps = [50*24-1-47+1, 70*24+13-47+1, 170*24-47+1, 243*24-47+1] - # resp = self.api_client.post(f'/v3/peak_load_outage_times', data=outage_inputs) - # self.assertHttpOK(resp) - # resp = json.loads(resp.content) - # self.assertEquals(resp["outage_start_time_steps"], expected_time_steps) - - # outage_inputs["seasonal_peaks"] = False - # outage_inputs["start_not_center_on_peaks"] = True - # expected_time_steps = [243*24+1] - # resp = self.api_client.post(f'/v3/peak_load_outage_times', data=outage_inputs) - # self.assertHttpOK(resp) - # resp = json.loads(resp.content) - # self.assertEquals(resp["outage_start_time_steps"], expected_time_steps) - - # def test_superset_input_fields(self): - # """ - # Purpose of this test is to test the API's ability to accept all relevant - # input fields and send to REopt, ensuring name input consistency with REopt.jl. - # Note: Does not currently test CHP inputs - # """ - # post_file = os.path.join('reoptjl', 'test', 'posts', 'all_inputs_test.json') - # post = json.load(open(post_file, 'r')) - - # resp = self.api_client.post('/v3/job/', format='json', data=post) - # self.assertHttpCreated(resp) - # r = json.loads(resp.content) - # run_uuid = r.get('run_uuid') - - # resp = self.api_client.get(f'/v3/job/{run_uuid}/results') - # r = json.loads(resp.content) - # results = r["outputs"] - # self.assertAlmostEqual(results["Financial"]["npv"], -258533.19, delta=0.01*results["Financial"]["lcc"]) - # assert(resp.status_code==200) - - # def test_steamturbine_defaults_from_julia(self): - # # Test that the inputs_with_defaults_set_in_julia feature worked for SteamTurbine, consistent with /chp_defaults - # post_file = os.path.join('reoptjl', 'test', 'posts', 'steamturbine_defaults_post.json') - # post = json.load(open(post_file, 'r')) - - # # Call http.jl /reopt to run SteamTurbine scenario and get results for defaults from julia checking - # resp = self.api_client.post('/v3/job/', format='json', data=post) - # self.assertHttpCreated(resp) - # r = json.loads(resp.content) - # run_uuid = r.get('run_uuid') - - # resp = self.api_client.get(f'/v3/job/{run_uuid}/results') - # r = json.loads(resp.content) - - # inputs_steamturbine = r["inputs"]["SteamTurbine"] - - # avg_fuel_load = (post["SpaceHeatingLoad"]["annual_mmbtu"] + - # post["DomesticHotWaterLoad"]["annual_mmbtu"]) / 8760.0 + resp = self.api_client.get(f'/v3/job/{run_uuid}/results') + r = json.loads(resp.content) + inputs = r["inputs"] + results = r["outputs"] + self.assertIn("CoolingLoad", list(inputs.keys())) + self.assertIn("CoolingLoad", list(results.keys())) + self.assertIn("CHP", list(results.keys())) + self.assertIn("thermal_to_dhw_load_series_mmbtu_per_hour", list(results["CHP"].keys())) + self.assertIn("thermal_to_space_heating_load_series_mmbtu_per_hour", list(results["CHP"].keys())) + self.assertIn("thermal_to_dhw_load_series_mmbtu_per_hour", list(results["CHP"].keys())) + self.assertIn("ExistingChiller",list(results.keys())) + self.assertIn("ExistingBoiler", list(results.keys())) + self.assertIn("HeatingLoad", list(results.keys())) + self.assertIn("process_heat_thermal_load_series_mmbtu_per_hour", list(results["HeatingLoad"].keys())) + self.assertIn("process_heat_boiler_fuel_load_series_mmbtu_per_hour", list(results["HeatingLoad"].keys())) + self.assertIn("HotThermalStorage", list(results.keys())) + self.assertIn("storage_to_dhw_load_series_mmbtu_per_hour", list(results["HotThermalStorage"].keys())) + self.assertIn("storage_to_space_heating_load_series_mmbtu_per_hour", list(results["HotThermalStorage"].keys())) + self.assertIn("storage_to_dhw_load_series_mmbtu_per_hour", list(results["HotThermalStorage"].keys())) + self.assertIn("ColdThermalStorage", list(results.keys())) + self.assertIn("AbsorptionChiller", list(results.keys())) + self.assertIn("GHP", list(results.keys())) + + + def test_chp_defaults_from_julia(self): + # Test that the inputs_with_defaults_set_in_julia feature worked for CHP, consistent with /chp_defaults + post_file = os.path.join('reoptjl', 'test', 'posts', 'chp_defaults_post.json') + post = json.load(open(post_file, 'r')) + # Make average MMBtu/hr thermal steam greater than 7 MMBtu/hr threshold for combustion_turbine to be chosen + # Default ExistingBoiler efficiency for production_type = steam is 0.75 + post["SpaceHeatingLoad"]["annual_mmbtu"] = 8760 * 8 / 0.75 + post["DomesticHotWaterLoad"]["annual_mmbtu"] = 8760 * 8 / 0.75 + resp = self.api_client.post('/v3/job/', format='json', data=post) + self.assertHttpCreated(resp) + r = json.loads(resp.content) + run_uuid = r.get('run_uuid') + + resp = self.api_client.get(f'/v3/job/{run_uuid}/results') + r = json.loads(resp.content) + + inputs_chp = r["inputs"]["CHP"] + + avg_fuel_load = (post["SpaceHeatingLoad"]["annual_mmbtu"] + + post["DomesticHotWaterLoad"]["annual_mmbtu"]) / 8760.0 + inputs_chp_defaults = {"hot_water_or_steam": post["ExistingBoiler"]["production_type"], + "avg_boiler_fuel_load_mmbtu_per_hour": avg_fuel_load + } + + # Call to the django view endpoint /chp_defaults which calls the http.jl endpoint + resp = self.api_client.get(f'/v3/chp_defaults', data=inputs_chp_defaults) + view_response = json.loads(resp.content) + + for key in view_response["default_inputs"].keys(): + if post["CHP"].get(key) is None: # Check that default got assigned consistent with /chp_defaults + if key == "max_kw": + self.assertEquals(inputs_chp[key], view_response["chp_max_size_kw"]) + else: + self.assertEquals(inputs_chp[key], view_response["default_inputs"][key]) + else: # Make sure we didn't overwrite user-input + self.assertEquals(inputs_chp[key], post["CHP"][key]) + + def test_peak_load_outage_times(self): + """ + Purpose of this test is to test the endpoint /peak_load_outage_times + """ + + load = [100]*8760 + load[40*24] = 200 + load[50*24-1] = 300 + load[70*24+13] = 300 + load[170*24] = 300 + load[243*24] = 400 + outage_inputs = {"seasonal_peaks": True, + "outage_duration": 95, + "critical_load": load, + "start_not_center_on_peaks": False + } + expected_time_steps = [50*24-1-47+1, 70*24+13-47+1, 170*24-47+1, 243*24-47+1] + resp = self.api_client.post(f'/v3/peak_load_outage_times', data=outage_inputs) + self.assertHttpOK(resp) + resp = json.loads(resp.content) + self.assertEquals(resp["outage_start_time_steps"], expected_time_steps) + + outage_inputs["seasonal_peaks"] = False + outage_inputs["start_not_center_on_peaks"] = True + expected_time_steps = [243*24+1] + resp = self.api_client.post(f'/v3/peak_load_outage_times', data=outage_inputs) + self.assertHttpOK(resp) + resp = json.loads(resp.content) + self.assertEquals(resp["outage_start_time_steps"], expected_time_steps) + + def test_superset_input_fields(self): + """ + Purpose of this test is to test the API's ability to accept all relevant + input fields and send to REopt, ensuring name input consistency with REopt.jl. + Note: Does not currently test CHP inputs + """ + post_file = os.path.join('reoptjl', 'test', 'posts', 'all_inputs_test.json') + post = json.load(open(post_file, 'r')) + + resp = self.api_client.post('/v3/job/', format='json', data=post) + self.assertHttpCreated(resp) + r = json.loads(resp.content) + run_uuid = r.get('run_uuid') + + resp = self.api_client.get(f'/v3/job/{run_uuid}/results') + r = json.loads(resp.content) + results = r["outputs"] + self.assertAlmostEqual(results["Financial"]["npv"], -258533.19, delta=0.01*results["Financial"]["lcc"]) + assert(resp.status_code==200) + + def test_steamturbine_defaults_from_julia(self): + # Test that the inputs_with_defaults_set_in_julia feature worked for SteamTurbine, consistent with /chp_defaults + post_file = os.path.join('reoptjl', 'test', 'posts', 'steamturbine_defaults_post.json') + post = json.load(open(post_file, 'r')) + + # Call http.jl /reopt to run SteamTurbine scenario and get results for defaults from julia checking + resp = self.api_client.post('/v3/job/', format='json', data=post) + self.assertHttpCreated(resp) + r = json.loads(resp.content) + run_uuid = r.get('run_uuid') + + resp = self.api_client.get(f'/v3/job/{run_uuid}/results') + r = json.loads(resp.content) + + inputs_steamturbine = r["inputs"]["SteamTurbine"] + + avg_fuel_load = (post["SpaceHeatingLoad"]["annual_mmbtu"] + + post["DomesticHotWaterLoad"]["annual_mmbtu"]) / 8760.0 - # inputs_steamturbine_defaults = { - # "prime_mover": "steam_turbine", - # "avg_boiler_fuel_load_mmbtu_per_hour": avg_fuel_load - # } - - # # Call to the django view endpoint /chp_defaults which calls the http.jl endpoint - # resp = self.api_client.get(f'/v3/chp_defaults', data=inputs_steamturbine_defaults) - # view_response = json.loads(resp.content) - - # for key in view_response["default_inputs"].keys(): - # # skip this key because its NaN in REoptInputs but is populated in /chp_defaults response. - # if key != "inlet_steam_temperature_degF": - # if post["SteamTurbine"].get(key) is None: # Check that default got assigned consistent with /chp_defaults - # self.assertEquals(inputs_steamturbine[key], view_response["default_inputs"][key]) - # else: # Make sure we didn't overwrite user-input - # self.assertEquals(inputs_steamturbine[key], post["SteamTurbine"][key]) - - # def test_hybridghp(self): - # post_file = os.path.join('reoptjl', 'test', 'posts', 'hybrid_ghp.json') - # post = json.load(open(post_file, 'r')) - - # post['GHP']['ghpghx_inputs'][0]['hybrid_ghx_sizing_method'] = 'Automatic' - # post['GHP']['avoided_capex_by_ghp_present_value'] = 1.0e6 - # post['GHP']['ghx_useful_life_years'] = 35 - - # # Call http.jl /reopt to run SteamTurbine scenario and get results for defaults from julia checking - # resp = self.api_client.post('/v3/job/', format='json', data=post) - # self.assertHttpCreated(resp) - # r = json.loads(resp.content) - # run_uuid = r.get('run_uuid') - - # resp = self.api_client.get(f'/v3/job/{run_uuid}/results') - # r = json.loads(resp.content) - - # # calculated_ghx_residual_value 117065.83 - # self.assertAlmostEqual(r["outputs"]["GHP"]["ghx_residual_value_present_value"], 117065.83, delta=500) - - # def test_centralghp(self): - # post_file = os.path.join('reoptjl', 'test', 'posts', 'central_plant_ghp.json') - # post = json.load(open(post_file, 'r')) - - # # Call http.jl /reopt to run the central plant GHP scenario and get results for defaults from julia checking - # resp = self.api_client.post('/v3/job/', format='json', data=post) - # self.assertHttpCreated(resp) - # r = json.loads(resp.content) - # run_uuid = r.get('run_uuid') - - # resp = self.api_client.get(f'/v3/job/{run_uuid}/results') - # r = json.loads(resp.content) - - # self.assertAlmostEqual(r["outputs"]["Financial"]["lifecycle_capital_costs"], 1046066.8, delta=1000) \ No newline at end of file + inputs_steamturbine_defaults = { + "prime_mover": "steam_turbine", + "avg_boiler_fuel_load_mmbtu_per_hour": avg_fuel_load + } + + # Call to the django view endpoint /chp_defaults which calls the http.jl endpoint + resp = self.api_client.get(f'/v3/chp_defaults', data=inputs_steamturbine_defaults) + view_response = json.loads(resp.content) + + for key in view_response["default_inputs"].keys(): + # skip this key because its NaN in REoptInputs but is populated in /chp_defaults response. + if key != "inlet_steam_temperature_degF": + if post["SteamTurbine"].get(key) is None: # Check that default got assigned consistent with /chp_defaults + self.assertEquals(inputs_steamturbine[key], view_response["default_inputs"][key]) + else: # Make sure we didn't overwrite user-input + self.assertEquals(inputs_steamturbine[key], post["SteamTurbine"][key]) + + def test_hybridghp(self): + post_file = os.path.join('reoptjl', 'test', 'posts', 'hybrid_ghp.json') + post = json.load(open(post_file, 'r')) + + post['GHP']['ghpghx_inputs'][0]['hybrid_ghx_sizing_method'] = 'Automatic' + post['GHP']['avoided_capex_by_ghp_present_value'] = 1.0e6 + post['GHP']['ghx_useful_life_years'] = 35 + + # Call http.jl /reopt to run SteamTurbine scenario and get results for defaults from julia checking + resp = self.api_client.post('/v3/job/', format='json', data=post) + self.assertHttpCreated(resp) + r = json.loads(resp.content) + run_uuid = r.get('run_uuid') + + resp = self.api_client.get(f'/v3/job/{run_uuid}/results') + r = json.loads(resp.content) + + # calculated_ghx_residual_value 117065.83 + self.assertAlmostEqual(r["outputs"]["GHP"]["ghx_residual_value_present_value"], 117065.83, delta=500) + + def test_centralghp(self): + post_file = os.path.join('reoptjl', 'test', 'posts', 'central_plant_ghp.json') + post = json.load(open(post_file, 'r')) + + # Call http.jl /reopt to run the central plant GHP scenario and get results for defaults from julia checking + resp = self.api_client.post('/v3/job/', format='json', data=post) + self.assertHttpCreated(resp) + r = json.loads(resp.content) + run_uuid = r.get('run_uuid') + + resp = self.api_client.get(f'/v3/job/{run_uuid}/results') + r = json.loads(resp.content) + + self.assertAlmostEqual(r["outputs"]["Financial"]["lifecycle_capital_costs"], 1046066.8, delta=1000) \ No newline at end of file diff --git a/reoptjl/test/test_validator.py b/reoptjl/test/test_validator.py index 0554e2005..7f73fdc06 100644 --- a/reoptjl/test/test_validator.py +++ b/reoptjl/test/test_validator.py @@ -13,334 +13,334 @@ def setUp(self): post_file = os.path.join('reoptjl', 'test', 'posts', 'validator_post.json') self.post = json.load(open(post_file, 'r')) - # def test_elec_load_profile_length_validation_and_resampling(self): - # """ - # try different lengths of load profiles, where the following are valid: - # - 8760 (hourly) - # - 17520 (30 min) - # - 35040 (15 min) - # also confirm that up/down-sampling is working. - # :return: None - # """ - # good_lengths = [8760, 17520, 35040] - # bad_lengths = [8759, 17521] - - # for length in bad_lengths + good_lengths: - # post = copy.deepcopy(self.post) - # post["APIMeta"]["run_uuid"] = uuid.uuid4() - # post['ElectricLoad']['loads_kw'] = list(range(length)) - # post['ElectricLoad']['critical_loads_kw'] = list(range(length)) - # validator = InputValidator(post) - # validator.clean() - # validator.clean_fields() - # validator.cross_clean() - - # if length in good_lengths: - # self.assertEquals(validator.is_valid, True) - - # if length > 8760: # check downsampling - # self.assertEquals(len(validator.models["ElectricLoad"].loads_kw), 8760) - # self.assertEquals(len(validator.models["ElectricLoad"].critical_loads_kw), 8760) - # assert("resampled inputs" in validator.messages) - - # elif length in bad_lengths: - # self.assertEquals(validator.is_valid, False) - # assert('Invalid length' in validator.validation_errors['ElectricLoad']['loads_kw']) - # assert('Invalid length' in validator.validation_errors['ElectricLoad']['critical_loads_kw']) - - # # check upsampling - # for time_steps_per_hour in [2, 4]: - # post = copy.deepcopy(self.post) - # post["APIMeta"]["run_uuid"] = uuid.uuid4() - # post['ElectricLoad']['loads_kw'] = list(range(8760)) - # post['ElectricLoad']['critical_loads_kw'] = list(range(8760)) - # post['Settings']['time_steps_per_hour'] = time_steps_per_hour - # validator = InputValidator(post) - # validator.clean() - # validator.clean_fields() - # validator.cross_clean() - # self.assertEquals(validator.is_valid, True) - # self.assertEquals(len(validator.models["ElectricLoad"].loads_kw), time_steps_per_hour*8760) - # self.assertEquals(len(validator.models["ElectricLoad"].critical_loads_kw), time_steps_per_hour*8760) - - # def test_bad_blended_profile_inputs(self): - # post = copy.deepcopy(self.post) - # del(post["ElectricLoad"]["doe_reference_name"]) - # post["ElectricLoad"]["blended_doe_reference_names"] = ["badname", "LargeOffice"] - # post["ElectricLoad"]["blended_doe_reference_percents"] = [1.5] - # validator = InputValidator(post) - # validator.clean_fields() - - # assert("'badname' is not a valid choice" - # in validator.validation_errors['ElectricLoad']['blended_doe_reference_names'][0]) - # assert("Ensure this value is less than or equal to 1.0" in - # validator.validation_errors['ElectricLoad']['blended_doe_reference_percents'][0]) - - # post["ElectricLoad"]["blended_doe_reference_names"] = ["MediumOffice", "LargeOffice"] - # post["ElectricLoad"]["blended_doe_reference_percents"] = [0.5] - # post["APIMeta"]["run_uuid"] = uuid.uuid4() - # validator = InputValidator(post) - # validator.clean_fields() - # validator.clean() - - # assert("The number of blended_doe_reference_names must equal the number of blended_doe_reference_percents" in - # validator.validation_errors['ElectricLoad']['blended_doe_reference_names'][0]) - # assert("Sum must = 1.0." in - # validator.validation_errors['ElectricLoad']['blended_doe_reference_percents'][0]) - - # def test_off_grid_defaults_overrides(self): - # post_file = os.path.join('reoptjl', 'test', 'posts', 'off_grid_validations.json') - # post = json.load(open(post_file, 'r')) + def test_elec_load_profile_length_validation_and_resampling(self): + """ + try different lengths of load profiles, where the following are valid: + - 8760 (hourly) + - 17520 (30 min) + - 35040 (15 min) + also confirm that up/down-sampling is working. + :return: None + """ + good_lengths = [8760, 17520, 35040] + bad_lengths = [8759, 17521] + + for length in bad_lengths + good_lengths: + post = copy.deepcopy(self.post) + post["APIMeta"]["run_uuid"] = uuid.uuid4() + post['ElectricLoad']['loads_kw'] = list(range(length)) + post['ElectricLoad']['critical_loads_kw'] = list(range(length)) + validator = InputValidator(post) + validator.clean() + validator.clean_fields() + validator.cross_clean() + + if length in good_lengths: + self.assertEquals(validator.is_valid, True) + + if length > 8760: # check downsampling + self.assertEquals(len(validator.models["ElectricLoad"].loads_kw), 8760) + self.assertEquals(len(validator.models["ElectricLoad"].critical_loads_kw), 8760) + assert("resampled inputs" in validator.messages) + + elif length in bad_lengths: + self.assertEquals(validator.is_valid, False) + assert('Invalid length' in validator.validation_errors['ElectricLoad']['loads_kw']) + assert('Invalid length' in validator.validation_errors['ElectricLoad']['critical_loads_kw']) + + # check upsampling + for time_steps_per_hour in [2, 4]: + post = copy.deepcopy(self.post) + post["APIMeta"]["run_uuid"] = uuid.uuid4() + post['ElectricLoad']['loads_kw'] = list(range(8760)) + post['ElectricLoad']['critical_loads_kw'] = list(range(8760)) + post['Settings']['time_steps_per_hour'] = time_steps_per_hour + validator = InputValidator(post) + validator.clean() + validator.clean_fields() + validator.cross_clean() + self.assertEquals(validator.is_valid, True) + self.assertEquals(len(validator.models["ElectricLoad"].loads_kw), time_steps_per_hour*8760) + self.assertEquals(len(validator.models["ElectricLoad"].critical_loads_kw), time_steps_per_hour*8760) + + def test_bad_blended_profile_inputs(self): + post = copy.deepcopy(self.post) + del(post["ElectricLoad"]["doe_reference_name"]) + post["ElectricLoad"]["blended_doe_reference_names"] = ["badname", "LargeOffice"] + post["ElectricLoad"]["blended_doe_reference_percents"] = [1.5] + validator = InputValidator(post) + validator.clean_fields() + + assert("'badname' is not a valid choice" + in validator.validation_errors['ElectricLoad']['blended_doe_reference_names'][0]) + assert("Ensure this value is less than or equal to 1.0" in + validator.validation_errors['ElectricLoad']['blended_doe_reference_percents'][0]) + + post["ElectricLoad"]["blended_doe_reference_names"] = ["MediumOffice", "LargeOffice"] + post["ElectricLoad"]["blended_doe_reference_percents"] = [0.5] + post["APIMeta"]["run_uuid"] = uuid.uuid4() + validator = InputValidator(post) + validator.clean_fields() + validator.clean() + + assert("The number of blended_doe_reference_names must equal the number of blended_doe_reference_percents" in + validator.validation_errors['ElectricLoad']['blended_doe_reference_names'][0]) + assert("Sum must = 1.0." in + validator.validation_errors['ElectricLoad']['blended_doe_reference_percents'][0]) + + def test_off_grid_defaults_overrides(self): + post_file = os.path.join('reoptjl', 'test', 'posts', 'off_grid_validations.json') + post = json.load(open(post_file, 'r')) - # post["APIMeta"]["run_uuid"] = uuid.uuid4() - - # validator = InputValidator(post) - # validator.clean_fields() - # validator.clean() - # validator.cross_clean() - # self.assertEquals(validator.is_valid, True) - - # self.assertAlmostEqual(validator.models["Wind"].operating_reserve_required_fraction, 0.5) - # self.assertAlmostEqual(validator.models["PV"].operating_reserve_required_fraction, 0.25) - # self.assertEqual(validator.models["Wind"].installed_cost_per_kw, 4760.0) # set based on size_class - - # self.assertAlmostEqual(validator.models["ElectricLoad"].operating_reserve_required_fraction, 0.1) - # self.assertAlmostEqual(validator.models["ElectricLoad"].critical_load_fraction, 1.0) - # self.assertAlmostEqual(validator.models["ElectricLoad"].min_load_met_annual_fraction, 0.99999) - - # self.assertAlmostEqual(validator.models["Generator"].installed_cost_per_kw, 880) - # self.assertAlmostEqual(validator.models["Generator"].om_cost_per_kw, 10) - # self.assertAlmostEqual(validator.models["Generator"].fuel_avail_gal, 1.0e9) - # self.assertAlmostEqual(validator.models["Generator"].min_turn_down_fraction, 0.15) - # self.assertAlmostEqual(validator.models["Generator"].replacement_year, 10) - # self.assertAlmostEqual(validator.models["Generator"].replace_cost_per_kw, validator.models["Generator"].installed_cost_per_kw) - - # ## Test that some defaults can be overriden below - - # post["ElectricLoad"]["operating_reserve_required_fraction"] = 0.2 - # post["ElectricLoad"]["critical_load_fraction"] = 0.95 # cant override - # post["ElectricLoad"]["min_load_met_annual_fraction"] = 0.95 + post["APIMeta"]["run_uuid"] = uuid.uuid4() + + validator = InputValidator(post) + validator.clean_fields() + validator.clean() + validator.cross_clean() + self.assertEquals(validator.is_valid, True) + + self.assertAlmostEqual(validator.models["Wind"].operating_reserve_required_fraction, 0.5) + self.assertAlmostEqual(validator.models["PV"].operating_reserve_required_fraction, 0.25) + self.assertEqual(validator.models["Wind"].installed_cost_per_kw, 4760.0) # set based on size_class + + self.assertAlmostEqual(validator.models["ElectricLoad"].operating_reserve_required_fraction, 0.1) + self.assertAlmostEqual(validator.models["ElectricLoad"].critical_load_fraction, 1.0) + self.assertAlmostEqual(validator.models["ElectricLoad"].min_load_met_annual_fraction, 0.99999) + + self.assertAlmostEqual(validator.models["Generator"].installed_cost_per_kw, 880) + self.assertAlmostEqual(validator.models["Generator"].om_cost_per_kw, 10) + self.assertAlmostEqual(validator.models["Generator"].fuel_avail_gal, 1.0e9) + self.assertAlmostEqual(validator.models["Generator"].min_turn_down_fraction, 0.15) + self.assertAlmostEqual(validator.models["Generator"].replacement_year, 10) + self.assertAlmostEqual(validator.models["Generator"].replace_cost_per_kw, validator.models["Generator"].installed_cost_per_kw) + + ## Test that some defaults can be overriden below + + post["ElectricLoad"]["operating_reserve_required_fraction"] = 0.2 + post["ElectricLoad"]["critical_load_fraction"] = 0.95 # cant override + post["ElectricLoad"]["min_load_met_annual_fraction"] = 0.95 - # post["Generator"]["om_cost_per_kw"] = 21 - # post["Generator"]["fuel_avail_gal"] = 10000 - # post["Generator"]["min_turn_down_fraction"] = 0.14 - # post["Generator"]["replacement_year"] = 7 - # post["Generator"]["replace_cost_per_kw"] = 200 - - # post["Wind"]["operating_reserve_required_fraction"] = 0.35 - # post["PV"]["operating_reserve_required_fraction"] = 0.35 + post["Generator"]["om_cost_per_kw"] = 21 + post["Generator"]["fuel_avail_gal"] = 10000 + post["Generator"]["min_turn_down_fraction"] = 0.14 + post["Generator"]["replacement_year"] = 7 + post["Generator"]["replace_cost_per_kw"] = 200 + + post["Wind"]["operating_reserve_required_fraction"] = 0.35 + post["PV"]["operating_reserve_required_fraction"] = 0.35 - # post["APIMeta"]["run_uuid"] = uuid.uuid4() + post["APIMeta"]["run_uuid"] = uuid.uuid4() - # validator = InputValidator(post) - # validator.clean_fields() - # validator.clean() - # validator.cross_clean() - # self.assertEquals(validator.is_valid, True) + validator = InputValidator(post) + validator.clean_fields() + validator.clean() + validator.cross_clean() + self.assertEquals(validator.is_valid, True) - # self.assertAlmostEqual(validator.models["PV"].operating_reserve_required_fraction, 0.35) - # self.assertAlmostEqual(validator.models["Wind"].operating_reserve_required_fraction, 0.35) + self.assertAlmostEqual(validator.models["PV"].operating_reserve_required_fraction, 0.35) + self.assertAlmostEqual(validator.models["Wind"].operating_reserve_required_fraction, 0.35) - # self.assertAlmostEqual(validator.models["ElectricLoad"].operating_reserve_required_fraction, 0.2) - # self.assertAlmostEqual(validator.models["ElectricLoad"].critical_load_fraction, 1.0) # cant override - # self.assertAlmostEqual(validator.models["ElectricLoad"].min_load_met_annual_fraction, 0.95) + self.assertAlmostEqual(validator.models["ElectricLoad"].operating_reserve_required_fraction, 0.2) + self.assertAlmostEqual(validator.models["ElectricLoad"].critical_load_fraction, 1.0) # cant override + self.assertAlmostEqual(validator.models["ElectricLoad"].min_load_met_annual_fraction, 0.95) - # self.assertAlmostEqual(validator.models["Generator"].om_cost_per_kw, 21) - # self.assertAlmostEqual(validator.models["Generator"].fuel_avail_gal, 10000) - # self.assertAlmostEqual(validator.models["Generator"].min_turn_down_fraction, 0.14) - # self.assertAlmostEqual(validator.models["Generator"].replacement_year, 7) - # self.assertAlmostEqual(validator.models["Generator"].replace_cost_per_kw, 200.0) + self.assertAlmostEqual(validator.models["Generator"].om_cost_per_kw, 21) + self.assertAlmostEqual(validator.models["Generator"].fuel_avail_gal, 10000) + self.assertAlmostEqual(validator.models["Generator"].min_turn_down_fraction, 0.14) + self.assertAlmostEqual(validator.models["Generator"].replacement_year, 7) + self.assertAlmostEqual(validator.models["Generator"].replace_cost_per_kw, 200.0) - # def existingboiler_boiler_validation(self): + def existingboiler_boiler_validation(self): - # """ - # Validate clean, cross-clean methods are working as expected - # """ - # post_file = os.path.join('reoptjl', 'test', 'posts', 'existing_boiler.json') - # post = json.load(open(post_file, 'r')) + """ + Validate clean, cross-clean methods are working as expected + """ + post_file = os.path.join('reoptjl', 'test', 'posts', 'existing_boiler.json') + post = json.load(open(post_file, 'r')) - # post["APIMeta"]["run_uuid"] = uuid.uuid4() + post["APIMeta"]["run_uuid"] = uuid.uuid4() - # validator = InputValidator(post) - # validator.clean_fields() - # validator.clean() - # validator.cross_clean() - # self.assertEquals(validator.is_valid, True) + validator = InputValidator(post) + validator.clean_fields() + validator.clean() + validator.cross_clean() + self.assertEquals(validator.is_valid, True) - # self.assertAlmostEqual(validator.models["ExistingBoiler"].emissions_factor_lb_CO2_per_mmbtu, 117, places=-1) - # self.assertAlmostEqual(len(validator.models["ExistingBoiler"].fuel_cost_per_mmbtu), 8760) - # self.assertAlmostEqual(sum(validator.models["ExistingBoiler"].fuel_cost_per_mmbtu), 8760*0.5) + self.assertAlmostEqual(validator.models["ExistingBoiler"].emissions_factor_lb_CO2_per_mmbtu, 117, places=-1) + self.assertAlmostEqual(len(validator.models["ExistingBoiler"].fuel_cost_per_mmbtu), 8760) + self.assertAlmostEqual(sum(validator.models["ExistingBoiler"].fuel_cost_per_mmbtu), 8760*0.5) - # self.assertAlmostEqual(len(validator.models["Boiler"].fuel_cost_per_mmbtu), 8760) - # self.assertAlmostEqual(sum(validator.models["Boiler"].fuel_cost_per_mmbtu), 8760*0.25) + self.assertAlmostEqual(len(validator.models["Boiler"].fuel_cost_per_mmbtu), 8760) + self.assertAlmostEqual(sum(validator.models["Boiler"].fuel_cost_per_mmbtu), 8760*0.25) - # # Ensure Hot Thermal Storage System parameter is loaded from json - # self.assertAlmostEqual(validator.models["HotThermalStorage"].max_gal, 2500.0) - - # # Validate 12 month fuel cost vector gets scaled correctly - - # post["ExistingBoiler"]["fuel_cost_per_mmbtu"] = [1,2,1,1,1,1,1,1,1,1,1,1] - # post["Boiler"]["fuel_cost_per_mmbtu"] = [0.5,1,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5] - - # post["APIMeta"]["run_uuid"] = uuid.uuid4() - - # validator = InputValidator(post) - # validator.clean_fields() - # validator.clean() - # validator.cross_clean() - # self.assertEquals(validator.is_valid, True) - - # self.assertAlmostEqual(len(validator.models["ExistingBoiler"].fuel_cost_per_mmbtu), 8760) - # self.assertEqual(sum(validator.models["ExistingBoiler"].fuel_cost_per_mmbtu), 9432.0) - # self.assertAlmostEqual(len(validator.models["Boiler"].fuel_cost_per_mmbtu), 8760) - # self.assertEqual(sum(validator.models["Boiler"].fuel_cost_per_mmbtu), 9432.0*0.5) - - # def test_missing_required_keys(self): - # #start with on_grid, and keep all keys - # required_ongrid_object_names = [ - # "Site", "ElectricLoad", "ElectricTariff" - # ] - # required_offgrid_object_names = [ - # "Site", "ElectricLoad" - # ] - # #prior to removal of keys, test lack or errors of validator with full inputs - # post = copy.deepcopy(self.post) - # post["APIMeta"]["run_uuid"] = uuid.uuid4() - # post["Settings"]["off_grid_flag"] = False - # validator = InputValidator(post) - # for key in required_ongrid_object_names: - # assert (key not in validator.validation_errors.keys()) - # post = copy.deepcopy(self.post) - # post["APIMeta"]["run_uuid"] = uuid.uuid4() - # post["Settings"]["off_grid_flag"] = True - # validator = InputValidator(post) - # for key in required_ongrid_object_names: - # assert (key not in validator.validation_errors.keys()) - # #test ongrid removal of all ongrid keys, trigger an error for all required ongrid inputs - # post = copy.deepcopy(self.post) - # post["APIMeta"]["run_uuid"] = uuid.uuid4() - # post["Settings"]["off_grid_flag"] = False - # for key in required_ongrid_object_names: - # del post[key] - # validator = InputValidator(post) - # for key in required_ongrid_object_names: - # assert("Missing required inputs." in validator.validation_errors[key]) - # #test offgrid removal of all offgrid keys, should trigger an error for required offgrid but not ongrid requirements - # post = copy.deepcopy(self.post) - # post["APIMeta"]["run_uuid"] = uuid.uuid4() - # post["Settings"]["off_grid_flag"] = True - # for key in required_ongrid_object_names: - # del post[key] - # validator = InputValidator(post) - # for key in required_ongrid_object_names: - # if key in required_offgrid_object_names: - # assert("Missing required inputs." in validator.validation_errors[key]) - # else: - # assert(key not in validator.validation_errors.keys()) - - # # check for missing CHP inputs - # post = copy.deepcopy(self.post) - # post["APIMeta"]["run_uuid"] = uuid.uuid4() - # post["CHP"].pop("fuel_cost_per_mmbtu") - # validator = InputValidator(post) - # validator.clean_fields() - # validator.clean() - # validator.cross_clean() - # assert("required inputs" in validator.validation_errors["CHP"].keys()) - - # def test_multiple_outages_validation(self): - # """ - # ensure that validation of multiple outages post works as expected and catches errors - # """ - # post_file = os.path.join('reoptjl', 'test', 'posts', 'outage.json') - # outage_post = json.load(open(post_file, 'r')) - # outage_post["APIMeta"] = {"run_uuid": uuid.uuid4()} - # outage_post["Meta"] = { - # "description": "test description", - # "address": "test address" - # } - # validator = InputValidator(outage_post) - # validator.clean_fields() - # validator.clean() - # validator.cross_clean() - # self.assertEquals(validator.is_valid, True) - - # # test mismatched length - # post = copy.deepcopy(outage_post) - # post["ElectricUtility"]["outage_durations"] = [10,20,30,40] - # post["ElectricUtility"]["outage_probabilities"] = [0.8,0.2] - # post["APIMeta"]["run_uuid"] = uuid.uuid4() - # validator = InputValidator(post) - # validator.clean() - # assert("mismatched length" in validator.validation_errors["ElectricUtility"].keys()) - - # # test missing outage_durations - # post = copy.deepcopy(outage_post) - # post["ElectricUtility"].pop("outage_durations") - # post["APIMeta"]["run_uuid"] = uuid.uuid4() - # validator = InputValidator(post) - # validator.clean() - # assert("missing required inputs" in validator.validation_errors["ElectricUtility"].keys()) - - # # test sum of outage_probabilities != 1 - # post = copy.deepcopy(outage_post) - # post["ElectricUtility"]["outage_durations"] = [10,20] - # post["ElectricUtility"]["outage_probabilities"] = [0.5,0.6] - # post["APIMeta"]["run_uuid"] = uuid.uuid4() - # validator = InputValidator(post) - # validator.clean() - # assert("outage_probabilities" in validator.validation_errors["ElectricUtility"].keys()) - - # # test missing outage_probabilities - # post = copy.deepcopy(outage_post) - # post["ElectricUtility"]["outage_durations"] = [10,20] - # post["ElectricUtility"].pop("outage_probabilities") - # post["APIMeta"]["run_uuid"] = uuid.uuid4() - # validator = InputValidator(post) - # validator.clean_fields() - # validator.clean() - # validator.cross_clean() - # self.assertEquals(validator.models["ElectricUtility"].outage_probabilities, [0.5, 0.5]) - # self.assertEquals(validator.is_valid, True) - - # def test_pv_tilt_defaults(self): - # post = copy.deepcopy(self.post) - # post["APIMeta"]["run_uuid"] = uuid.uuid4() - # del(post["ElectricStorage"]) - # del(post["CHP"]) - # del(post["PV"]["tilt"]) - # validator = InputValidator(post) - # validator.clean_fields() - # validator.clean() - # validator.cross_clean() - # self.assertEquals(validator.models["PV"].tilt, 20) - - # post["APIMeta"]["run_uuid"] = uuid.uuid4() - # post["PV"]["array_type"] = 2 - # validator = InputValidator(post) - # validator.clean_fields() - # validator.clean() - # validator.cross_clean() - # self.assertAlmostEquals(validator.models["PV"].tilt, 0) - - # def boiler_validation(self): - # """ - # Validate clean, cross-clean methods are working as expected - # """ - # post_file = os.path.join('job', 'test', 'posts', 'boiler_test.json') - # post = json.load(open(post_file, 'r')) - - # post["APIMeta"]["run_uuid"] = uuid.uuid4() - - # validator = InputValidator(post) - # validator.clean_fields() - # validator.clean() - # validator.cross_clean() - # self.assertEquals(validator.is_valid, True) - - # # Update with Boiler test fields - # # self.assertAlmostEqual(validator.models["ExistingBoiler"].emissions_factor_lb_CO2_per_mmbtu, 117, places=-1) - # # self.assertAlmostEqual(len(validator.models["ExistingBoiler"].fuel_cost_per_mmbtu), 8760) - # # self.assertAlmostEqual(sum(validator.models["ExistingBoiler"].fuel_cost_per_mmbtu), 8760*0.5) + # Ensure Hot Thermal Storage System parameter is loaded from json + self.assertAlmostEqual(validator.models["HotThermalStorage"].max_gal, 2500.0) + + # Validate 12 month fuel cost vector gets scaled correctly + + post["ExistingBoiler"]["fuel_cost_per_mmbtu"] = [1,2,1,1,1,1,1,1,1,1,1,1] + post["Boiler"]["fuel_cost_per_mmbtu"] = [0.5,1,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5] + + post["APIMeta"]["run_uuid"] = uuid.uuid4() + + validator = InputValidator(post) + validator.clean_fields() + validator.clean() + validator.cross_clean() + self.assertEquals(validator.is_valid, True) + + self.assertAlmostEqual(len(validator.models["ExistingBoiler"].fuel_cost_per_mmbtu), 8760) + self.assertEqual(sum(validator.models["ExistingBoiler"].fuel_cost_per_mmbtu), 9432.0) + self.assertAlmostEqual(len(validator.models["Boiler"].fuel_cost_per_mmbtu), 8760) + self.assertEqual(sum(validator.models["Boiler"].fuel_cost_per_mmbtu), 9432.0*0.5) + + def test_missing_required_keys(self): + #start with on_grid, and keep all keys + required_ongrid_object_names = [ + "Site", "ElectricLoad", "ElectricTariff" + ] + required_offgrid_object_names = [ + "Site", "ElectricLoad" + ] + #prior to removal of keys, test lack or errors of validator with full inputs + post = copy.deepcopy(self.post) + post["APIMeta"]["run_uuid"] = uuid.uuid4() + post["Settings"]["off_grid_flag"] = False + validator = InputValidator(post) + for key in required_ongrid_object_names: + assert (key not in validator.validation_errors.keys()) + post = copy.deepcopy(self.post) + post["APIMeta"]["run_uuid"] = uuid.uuid4() + post["Settings"]["off_grid_flag"] = True + validator = InputValidator(post) + for key in required_ongrid_object_names: + assert (key not in validator.validation_errors.keys()) + #test ongrid removal of all ongrid keys, trigger an error for all required ongrid inputs + post = copy.deepcopy(self.post) + post["APIMeta"]["run_uuid"] = uuid.uuid4() + post["Settings"]["off_grid_flag"] = False + for key in required_ongrid_object_names: + del post[key] + validator = InputValidator(post) + for key in required_ongrid_object_names: + assert("Missing required inputs." in validator.validation_errors[key]) + #test offgrid removal of all offgrid keys, should trigger an error for required offgrid but not ongrid requirements + post = copy.deepcopy(self.post) + post["APIMeta"]["run_uuid"] = uuid.uuid4() + post["Settings"]["off_grid_flag"] = True + for key in required_ongrid_object_names: + del post[key] + validator = InputValidator(post) + for key in required_ongrid_object_names: + if key in required_offgrid_object_names: + assert("Missing required inputs." in validator.validation_errors[key]) + else: + assert(key not in validator.validation_errors.keys()) + + # check for missing CHP inputs + post = copy.deepcopy(self.post) + post["APIMeta"]["run_uuid"] = uuid.uuid4() + post["CHP"].pop("fuel_cost_per_mmbtu") + validator = InputValidator(post) + validator.clean_fields() + validator.clean() + validator.cross_clean() + assert("required inputs" in validator.validation_errors["CHP"].keys()) + + def test_multiple_outages_validation(self): + """ + ensure that validation of multiple outages post works as expected and catches errors + """ + post_file = os.path.join('reoptjl', 'test', 'posts', 'outage.json') + outage_post = json.load(open(post_file, 'r')) + outage_post["APIMeta"] = {"run_uuid": uuid.uuid4()} + outage_post["Meta"] = { + "description": "test description", + "address": "test address" + } + validator = InputValidator(outage_post) + validator.clean_fields() + validator.clean() + validator.cross_clean() + self.assertEquals(validator.is_valid, True) + + # test mismatched length + post = copy.deepcopy(outage_post) + post["ElectricUtility"]["outage_durations"] = [10,20,30,40] + post["ElectricUtility"]["outage_probabilities"] = [0.8,0.2] + post["APIMeta"]["run_uuid"] = uuid.uuid4() + validator = InputValidator(post) + validator.clean() + assert("mismatched length" in validator.validation_errors["ElectricUtility"].keys()) + + # test missing outage_durations + post = copy.deepcopy(outage_post) + post["ElectricUtility"].pop("outage_durations") + post["APIMeta"]["run_uuid"] = uuid.uuid4() + validator = InputValidator(post) + validator.clean() + assert("missing required inputs" in validator.validation_errors["ElectricUtility"].keys()) + + # test sum of outage_probabilities != 1 + post = copy.deepcopy(outage_post) + post["ElectricUtility"]["outage_durations"] = [10,20] + post["ElectricUtility"]["outage_probabilities"] = [0.5,0.6] + post["APIMeta"]["run_uuid"] = uuid.uuid4() + validator = InputValidator(post) + validator.clean() + assert("outage_probabilities" in validator.validation_errors["ElectricUtility"].keys()) + + # test missing outage_probabilities + post = copy.deepcopy(outage_post) + post["ElectricUtility"]["outage_durations"] = [10,20] + post["ElectricUtility"].pop("outage_probabilities") + post["APIMeta"]["run_uuid"] = uuid.uuid4() + validator = InputValidator(post) + validator.clean_fields() + validator.clean() + validator.cross_clean() + self.assertEquals(validator.models["ElectricUtility"].outage_probabilities, [0.5, 0.5]) + self.assertEquals(validator.is_valid, True) + + def test_pv_tilt_defaults(self): + post = copy.deepcopy(self.post) + post["APIMeta"]["run_uuid"] = uuid.uuid4() + del(post["ElectricStorage"]) + del(post["CHP"]) + del(post["PV"]["tilt"]) + validator = InputValidator(post) + validator.clean_fields() + validator.clean() + validator.cross_clean() + self.assertEquals(validator.models["PV"].tilt, 20) + + post["APIMeta"]["run_uuid"] = uuid.uuid4() + post["PV"]["array_type"] = 2 + validator = InputValidator(post) + validator.clean_fields() + validator.clean() + validator.cross_clean() + self.assertAlmostEquals(validator.models["PV"].tilt, 0) + + def boiler_validation(self): + """ + Validate clean, cross-clean methods are working as expected + """ + post_file = os.path.join('job', 'test', 'posts', 'boiler_test.json') + post = json.load(open(post_file, 'r')) + + post["APIMeta"]["run_uuid"] = uuid.uuid4() + + validator = InputValidator(post) + validator.clean_fields() + validator.clean() + validator.cross_clean() + self.assertEquals(validator.is_valid, True) + + # Update with Boiler test fields + # self.assertAlmostEqual(validator.models["ExistingBoiler"].emissions_factor_lb_CO2_per_mmbtu, 117, places=-1) + # self.assertAlmostEqual(len(validator.models["ExistingBoiler"].fuel_cost_per_mmbtu), 8760) + # self.assertAlmostEqual(sum(validator.models["ExistingBoiler"].fuel_cost_per_mmbtu), 8760*0.5) diff --git a/resilience_stats/tests/test_erp.py b/resilience_stats/tests/test_erp.py index b25871776..8181f4290 100644 --- a/resilience_stats/tests/test_erp.py +++ b/resilience_stats/tests/test_erp.py @@ -74,125 +74,125 @@ def test_erp_long_duration_battery(self): for i in range(99):#min(length(simresults["probs_of_surviving"]), reliability_inputs["max_outage_duration"]) self.assertAlmostEqual(results_sim["mean_cumulative_survival_by_duration"][i], expected_result[i], places=4) - # def test_erp_large_battery(self): - # """ - # Tests calling ERP with PV, a small generator, and very large battery such that final survivial should be 1. - # This is the same as the first test in the "Backup Generator Reliability" testset in the REopt Julia package. - # """ - # post_sim_large_stor = json.load(open(self.post_sim_large_stor, 'rb')) - - # resp = self.get_response_sim(post_sim_large_stor) - # self.assertHttpCreated(resp) - # r_sim = json.loads(resp.content) - # erp_run_uuid = r_sim.get('run_uuid') - - # resp = self.get_results_sim(erp_run_uuid) - # results = json.loads(resp.content) - - # self.assertAlmostEqual(results["outputs"]["mean_cumulative_survival_final_time_step"], 1.0, places=4) - - # def test_erp_with_reopt_run_uuid(self): - # """ - # Tests calling ERP with a REopt run_uuid provided, but all inputs from REopt results overrided. - # This ends up being the same as the second to last test in the "Backup Generator Reliability" testset in the REopt Julia package. - # Then tests calling ERP with the same REopt run_uuid provided, but only the necessary additional ERP inputs provided. - # This used to be the same as the last test in the "Backup Generator Reliability" testset in the REopt Julia package, but need to make consistent again. - # """ - - # data = json.load(open(self.post_opt_gens_batt_pv_wind, 'rb')) - # data["Settings"]["optimality_tolerance"] = 1.0e-2 # REopt_tol per line 38. - # resp = self.get_response_opt(data) - # self.assertHttpCreated(resp) - # r_opt = json.loads(resp.content) - # reopt_run_uuid = r_opt.get('run_uuid') - - # assert(reopt_run_uuid is not None) - # post_sim = json.load(open(self.post_sim_gens_batt_pv_wind, 'rb')) - # post_sim["reopt_run_uuid"] = reopt_run_uuid - # post_sim["ElectricStorage"]["starting_soc_series_fraction"] = 8760 * [1] - - # resp = self.get_response_sim(post_sim) - # self.assertHttpCreated(resp) - # r_sim = json.loads(resp.content) - # erp_run_uuid = r_sim.get('run_uuid') - - # resp = self.get_results_sim(erp_run_uuid) - # results = json.loads(resp.content) - # self.assertAlmostEqual(results["outputs"]["unlimited_fuel_cumulative_survival_final_time_step"][0], 0.858756, places=4) - # self.assertAlmostEqual(results["outputs"]["cumulative_survival_final_time_step"][0], 0.858756, places=4) - # self.assertAlmostEqual(results["outputs"]["mean_cumulative_survival_final_time_step"], 0.904242, places=4) - - # #remove inputs that override REopt results and run again - # for model, field in [ - # ("Generator","size_kw"), - # ("PrimeGenerator","size_kw"), - # ("ElectricStorage","size_kw"), - # ("ElectricStorage","size_kwh"), - # ("ElectricStorage","charge_efficiency"), - # ("ElectricStorage","discharge_efficiency"), - # ("ElectricStorage","starting_soc_series_fraction"), - # ("PV","size_kw"), - # ("PV","production_factor_series"), - # ("Wind","size_kw"), - # ("Wind","production_factor_series"), - # ("Outage","critical_loads_kw"), - # ("Outage","max_outage_duration") - # ]: - # post_sim.get(model,{}).pop(field, None) - - # # add minimum_soc_fraction input to be consistent with test in REopt.jl - # post_sim["ElectricStorage"]["minimum_soc_fraction"] = 0.2 + def test_erp_large_battery(self): + """ + Tests calling ERP with PV, a small generator, and very large battery such that final survivial should be 1. + This is the same as the first test in the "Backup Generator Reliability" testset in the REopt Julia package. + """ + post_sim_large_stor = json.load(open(self.post_sim_large_stor, 'rb')) + + resp = self.get_response_sim(post_sim_large_stor) + self.assertHttpCreated(resp) + r_sim = json.loads(resp.content) + erp_run_uuid = r_sim.get('run_uuid') + + resp = self.get_results_sim(erp_run_uuid) + results = json.loads(resp.content) + + self.assertAlmostEqual(results["outputs"]["mean_cumulative_survival_final_time_step"], 1.0, places=4) + + def test_erp_with_reopt_run_uuid(self): + """ + Tests calling ERP with a REopt run_uuid provided, but all inputs from REopt results overrided. + This ends up being the same as the second to last test in the "Backup Generator Reliability" testset in the REopt Julia package. + Then tests calling ERP with the same REopt run_uuid provided, but only the necessary additional ERP inputs provided. + This used to be the same as the last test in the "Backup Generator Reliability" testset in the REopt Julia package, but need to make consistent again. + """ + + data = json.load(open(self.post_opt_gens_batt_pv_wind, 'rb')) + data["Settings"]["optimality_tolerance"] = 1.0e-2 # REopt_tol per line 38. + resp = self.get_response_opt(data) + self.assertHttpCreated(resp) + r_opt = json.loads(resp.content) + reopt_run_uuid = r_opt.get('run_uuid') + + assert(reopt_run_uuid is not None) + post_sim = json.load(open(self.post_sim_gens_batt_pv_wind, 'rb')) + post_sim["reopt_run_uuid"] = reopt_run_uuid + post_sim["ElectricStorage"]["starting_soc_series_fraction"] = 8760 * [1] + + resp = self.get_response_sim(post_sim) + self.assertHttpCreated(resp) + r_sim = json.loads(resp.content) + erp_run_uuid = r_sim.get('run_uuid') + + resp = self.get_results_sim(erp_run_uuid) + results = json.loads(resp.content) + self.assertAlmostEqual(results["outputs"]["unlimited_fuel_cumulative_survival_final_time_step"][0], 0.858756, places=4) + self.assertAlmostEqual(results["outputs"]["cumulative_survival_final_time_step"][0], 0.858756, places=4) + self.assertAlmostEqual(results["outputs"]["mean_cumulative_survival_final_time_step"], 0.904242, places=4) + + #remove inputs that override REopt results and run again + for model, field in [ + ("Generator","size_kw"), + ("PrimeGenerator","size_kw"), + ("ElectricStorage","size_kw"), + ("ElectricStorage","size_kwh"), + ("ElectricStorage","charge_efficiency"), + ("ElectricStorage","discharge_efficiency"), + ("ElectricStorage","starting_soc_series_fraction"), + ("PV","size_kw"), + ("PV","production_factor_series"), + ("Wind","size_kw"), + ("Wind","production_factor_series"), + ("Outage","critical_loads_kw"), + ("Outage","max_outage_duration") + ]: + post_sim.get(model,{}).pop(field, None) + + # add minimum_soc_fraction input to be consistent with test in REopt.jl + post_sim["ElectricStorage"]["minimum_soc_fraction"] = 0.2 - # resp = self.get_response_sim(post_sim) - # self.assertHttpCreated(resp) - # r_sim = json.loads(resp.content) - # erp_run_uuid = r_sim.get('run_uuid') - - # resp = self.get_results_sim(erp_run_uuid) - # results = json.loads(resp.content) - # self.assertAlmostEqual(results["outputs"]["mean_cumulative_survival_by_duration"][23], 0.9661, delta=0.0015) - # self.assertAlmostEqual(results["outputs"]["cumulative_survival_final_time_step"][0], 0.962327, places=3) - # self.assertAlmostEqual(results["outputs"]["mean_cumulative_survival_final_time_step"], 0.9661, places=2) - - # def test_erp_with_no_opt(self): - # """ - # Tests calling ERP on it's own without providing a REopt run_uuid. - # Same as the second to last test in the "Backup Generator Reliability" testset in the REopt Julia package. - # """ + resp = self.get_response_sim(post_sim) + self.assertHttpCreated(resp) + r_sim = json.loads(resp.content) + erp_run_uuid = r_sim.get('run_uuid') + + resp = self.get_results_sim(erp_run_uuid) + results = json.loads(resp.content) + self.assertAlmostEqual(results["outputs"]["mean_cumulative_survival_by_duration"][23], 0.9661, delta=0.0015) + self.assertAlmostEqual(results["outputs"]["cumulative_survival_final_time_step"][0], 0.962327, places=3) + self.assertAlmostEqual(results["outputs"]["mean_cumulative_survival_final_time_step"], 0.9661, places=2) + + def test_erp_with_no_opt(self): + """ + Tests calling ERP on it's own without providing a REopt run_uuid. + Same as the second to last test in the "Backup Generator Reliability" testset in the REopt Julia package. + """ - # post_sim = json.load(open(self.post_sim_only, 'rb')) - - # resp = self.get_response_sim(post_sim) - # self.assertHttpCreated(resp) - # r_sim = json.loads(resp.content) - # #TODO: don't return run_uuid when there's a REoptFailedToStartError - # erp_run_uuid = r_sim.get('run_uuid') - - # resp = self.get_results_sim(erp_run_uuid) - # results = json.loads(resp.content) - - # self.assertAlmostEqual(results["outputs"]["mean_cumulative_survival_final_time_step"], 0.904242, places=4) #0.990784, places=4) - - # # Test that zero battery doesn't cause error - # post_sim["ElectricStorage"]["size_kwh"] = 0 - # post_sim["ElectricStorage"]["size_kwh"] = 0 - # resp = self.get_response_sim(post_sim) - # self.assertHttpCreated(resp) - # r_sim = json.loads(resp.content) - # erp_run_uuid = r_sim.get('run_uuid') - # resp = self.get_results_sim(erp_run_uuid) - # self.assertHttpOK(resp) - - # def test_erp_help_view(self): - # """ - # Tests hiting the erp/help url to get defaults and other info about inputs - # """ + post_sim = json.load(open(self.post_sim_only, 'rb')) + + resp = self.get_response_sim(post_sim) + self.assertHttpCreated(resp) + r_sim = json.loads(resp.content) + #TODO: don't return run_uuid when there's a REoptFailedToStartError + erp_run_uuid = r_sim.get('run_uuid') + + resp = self.get_results_sim(erp_run_uuid) + results = json.loads(resp.content) + + self.assertAlmostEqual(results["outputs"]["mean_cumulative_survival_final_time_step"], 0.904242, places=4) #0.990784, places=4) + + # Test that zero battery doesn't cause error + post_sim["ElectricStorage"]["size_kwh"] = 0 + post_sim["ElectricStorage"]["size_kwh"] = 0 + resp = self.get_response_sim(post_sim) + self.assertHttpCreated(resp) + r_sim = json.loads(resp.content) + erp_run_uuid = r_sim.get('run_uuid') + resp = self.get_results_sim(erp_run_uuid) + self.assertHttpOK(resp) + + def test_erp_help_view(self): + """ + Tests hiting the erp/help url to get defaults and other info about inputs + """ - # resp = self.get_help() - # self.assertHttpOK(resp) - # resp = json.loads(resp.content) + resp = self.get_help() + self.assertHttpOK(resp) + resp = json.loads(resp.content) - # resp = self.get_chp_defaults("recip_engine", True, 10000) - # self.assertHttpOK(resp) - # resp = json.loads(resp.content) + resp = self.get_chp_defaults("recip_engine", True, 10000) + self.assertHttpOK(resp) + resp = json.loads(resp.content) \ No newline at end of file diff --git a/resilience_stats/tests/test_new_post_endpoint.py b/resilience_stats/tests/test_new_post_endpoint.py index 291e6b05c..69262b687 100644 --- a/resilience_stats/tests/test_new_post_endpoint.py +++ b/resilience_stats/tests/test_new_post_endpoint.py @@ -27,20 +27,20 @@ def get_response_sim(self, data_sim): return self.api_client.post(self.reopt_base_sim, format='json', data=data_sim) - # def test_both_modules(self): - # """ - # temporary test setup for running an optimizatin problem that precedes the following test for new endpoint for outage simulation module. - # :return: - # """ - # data = json.load(open(self.post_opt, 'rb')) - # resp = self.get_response_opt(data) - # self.assertHttpCreated(resp) - # r_opt = json.loads(resp.content) - # run_uuid = r_opt.get('run_uuid') - - # assert(run_uuid is not None) - # post_sim = {"run_uuid": run_uuid, "bau": True} - # resp = self.get_response_sim(data_sim=post_sim) - # self.assertHttpCreated(resp) - # r_sim = json.loads(resp.content) - # # print("Response from outagesimjob:", r_sim) + def test_both_modules(self): + """ + temporary test setup for running an optimizatin problem that precedes the following test for new endpoint for outage simulation module. + :return: + """ + data = json.load(open(self.post_opt, 'rb')) + resp = self.get_response_opt(data) + self.assertHttpCreated(resp) + r_opt = json.loads(resp.content) + run_uuid = r_opt.get('run_uuid') + + assert(run_uuid is not None) + post_sim = {"run_uuid": run_uuid, "bau": True} + resp = self.get_response_sim(data_sim=post_sim) + self.assertHttpCreated(resp) + r_sim = json.loads(resp.content) + # print("Response from outagesimjob:", r_sim) diff --git a/resilience_stats/tests/test_resilience_stats.py b/resilience_stats/tests/test_resilience_stats.py index a0d495ff0..0023ff6f0 100644 --- a/resilience_stats/tests/test_resilience_stats.py +++ b/resilience_stats/tests/test_resilience_stats.py @@ -55,362 +55,362 @@ def readFiles(path): self.inputs1 = readFiles('timestep_1') self.inputs2 = readFiles('timestep_2') - # def test_outage_sim_no_diesel(self): - # """ - # For the case that no diesel generator is on site, outage simulation with load following strategy should have the - # same results as existing simulation's results. - # """ - # inputs = self.inputs - # inputs.update(diesel_kw=0, fuel_available=0, m=0, b=0) + def test_outage_sim_no_diesel(self): + """ + For the case that no diesel generator is on site, outage simulation with load following strategy should have the + same results as existing simulation's results. + """ + inputs = self.inputs + inputs.update(diesel_kw=0, fuel_available=0, m=0, b=0) - # # Output parse from existing simulation - # expected = { - # 'resilience_hours_min': 0, - # 'resilience_hours_max': 78, - # 'resilience_hours_avg': 10.23, - # "outage_durations": list(range(1, 79)), - # "probs_of_surviving": [0.8486, 0.7963, 0.7373, 0.6624, 0.59, 0.5194, 0.4533, 0.4007, 0.3583, 0.3231, 0.2933, - # 0.2691, 0.2471, 0.2297, 0.215, 0.2015, 0.1897, 0.1788, 0.1694, 0.1608, 0.153, 0.1454, - # 0.1381, 0.1308, 0.1247, 0.1184, 0.1122, 0.1065, 0.1013, 0.0968, 0.0927, 0.0887, 0.0847, - # 0.0807, 0.0767, 0.0727, 0.0687, 0.0648, 0.0607, 0.0562, 0.0516, 0.047, 0.0425, 0.0376, - # 0.0326, 0.0277, 0.0228, 0.0179, 0.0146, 0.0116, 0.0097, 0.0081, 0.0071, 0.0066, 0.0062, - # 0.0059, 0.0057, 0.0055, 0.0053, 0.005, 0.0048, 0.0046, 0.0043, 0.0041, 0.0039, 0.0037, - # 0.0032, 0.0027, 0.0023, 0.0018, 0.0014, 0.0009, 0.0007, 0.0006, 0.0005, 0.0003, 0.0002, - # 0.0001], - # "probs_of_surviving_by_month": [ - # [1.0, 0.7688, 0.7003, 0.6277, 0.5511, 0.4785, 0.4005, 0.3266, 0.2769, 0.2312, 0.1989, 0.1774, 0.168, - # 0.1586, 0.1519, 0.1452, 0.1358, 0.1263, 0.1169, 0.1075, 0.0981, 0.0887, 0.078, 0.0672, 0.0591, 0.0538, - # 0.0484, 0.043, 0.0376, 0.0323, 0.0309, 0.0296, 0.0282, 0.0269, 0.0255, 0.0242, 0.0228, 0.0215, 0.0202, - # 0.0188, 0.0175, 0.0161, 0.0148, 0.0134, 0.0121, 0.0108, 0.0094, 0.0081, 0.0067, 0.0054, 0.004, 0.0027, - # 0.0013, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - # [1.0, 0.8036, 0.7381, 0.6756, 0.6131, 0.5208, 0.4315, 0.3571, 0.3021, 0.2589, 0.2262, 0.2024, 0.1935, - # 0.183, 0.1756, 0.1696, 0.1607, 0.1548, 0.1473, 0.1384, 0.1324, 0.1265, 0.1205, 0.1146, 0.1086, 0.1027, - # 0.0967, 0.0908, 0.0848, 0.0804, 0.0774, 0.0744, 0.0729, 0.0714, 0.0699, 0.0685, 0.067, 0.0655, 0.064, - # 0.061, 0.0565, 0.0521, 0.0476, 0.0446, 0.0387, 0.0327, 0.0268, 0.0208, 0.0149, 0.0134, 0.0119, 0.0104, - # 0.0089, 0.0089, 0.0089, 0.0089, 0.0089, 0.0089, 0.0089, 0.0089, 0.0089, 0.0089, 0.0089, 0.0089, 0.0089, - # 0.0089, 0.0089, 0.0074, 0.006, 0.0045, 0.003, 0.0015, 0, 0, 0, 0, 0, 0, 0], - # [1.0, 0.8212, 0.7527, 0.6935, 0.6035, 0.5376, 0.461, 0.3898, 0.328, 0.2782, 0.2419, 0.2124, 0.1922, - # 0.1761, 0.1667, 0.1559, 0.1452, 0.1358, 0.129, 0.1223, 0.1169, 0.1116, 0.1062, 0.1008, 0.0954, 0.0901, - # 0.0847, 0.0793, 0.0766, 0.0753, 0.0739, 0.0726, 0.0712, 0.0699, 0.0685, 0.0672, 0.0659, 0.0645, 0.0632, - # 0.0618, 0.0578, 0.0538, 0.0484, 0.043, 0.0376, 0.0323, 0.0269, 0.0215, 0.0161, 0.0134, 0.0108, 0.0081, - # 0.0054, 0.0027, 0.0013, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - # [1.0, 0.8889, 0.8292, 0.7694, 0.6917, 0.6194, 0.5542, 0.4903, 0.4431, 0.4069, 0.3708, 0.3375, 0.3069, - # 0.2792, 0.2639, 0.25, 0.2389, 0.2278, 0.2181, 0.2083, 0.1986, 0.1903, 0.1833, 0.1764, 0.1694, 0.1625, - # 0.1556, 0.1486, 0.1417, 0.1347, 0.1278, 0.1208, 0.1139, 0.1069, 0.1, 0.0931, 0.0861, 0.0792, 0.0722, - # 0.0653, 0.0583, 0.0514, 0.0444, 0.0375, 0.0306, 0.0236, 0.0167, 0.0097, 0.0028, 0.0014, 0, 0, 0, 0, 0, - # 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - # [1.0, 0.9059, 0.8723, 0.8293, 0.7796, 0.6962, 0.6358, 0.578, 0.5215, 0.4785, 0.4409, 0.4005, 0.3656, - # 0.3333, 0.3051, 0.2809, 0.2608, 0.2419, 0.2285, 0.2177, 0.207, 0.1989, 0.1935, 0.1882, 0.1828, 0.1774, - # 0.172, 0.1667, 0.1613, 0.1559, 0.1505, 0.1452, 0.1398, 0.1344, 0.129, 0.1237, 0.1183, 0.1129, 0.1075, - # 0.1022, 0.0968, 0.0914, 0.086, 0.0806, 0.0753, 0.0699, 0.0645, 0.0591, 0.0538, 0.0484, 0.043, 0.039, - # 0.0363, 0.0349, 0.0336, 0.0323, 0.0309, 0.0296, 0.0282, 0.0269, 0.0255, 0.0242, 0.0228, 0.0215, 0.0202, - # 0.0188, 0.0175, 0.0161, 0.0148, 0.0134, 0.0121, 0.0108, 0.0094, 0.0081, 0.0067, 0.0054, 0.004, 0.0027, - # 0.0013], - # [1.0, 0.9111, 0.8792, 0.8292, 0.7597, 0.6708, 0.5944, 0.5403, 0.4875, 0.4444, 0.4028, 0.3667, 0.3306, - # 0.2972, 0.2667, 0.2431, 0.225, 0.2083, 0.1944, 0.1861, 0.1792, 0.1736, 0.1681, 0.1625, 0.1569, 0.1514, - # 0.1458, 0.1403, 0.1347, 0.1292, 0.1236, 0.1181, 0.1125, 0.1069, 0.1014, 0.0958, 0.0903, 0.0847, 0.0792, - # 0.0736, 0.0681, 0.0625, 0.0569, 0.0514, 0.0458, 0.0403, 0.0347, 0.0292, 0.0236, 0.0181, 0.0125, 0.0083, - # 0.0042, 0.0028, 0.0014, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - # [1.0, 0.9194, 0.8831, 0.8293, 0.7433, 0.6815, 0.6022, 0.5497, 0.5067, 0.4718, 0.4368, 0.4046, 0.3696, - # 0.3414, 0.3172, 0.2997, 0.2823, 0.2661, 0.2513, 0.2392, 0.2285, 0.2177, 0.207, 0.1976, 0.1895, 0.1815, - # 0.1734, 0.1653, 0.1573, 0.1492, 0.1425, 0.1358, 0.129, 0.1223, 0.1156, 0.1089, 0.1022, 0.0954, 0.0887, - # 0.082, 0.0753, 0.0685, 0.0618, 0.0551, 0.0484, 0.0417, 0.0349, 0.0282, 0.0215, 0.0148, 0.0081, 0.0054, - # 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - # [1.0, 0.8992, 0.8589, 0.8065, 0.7285, 0.664, 0.5968, 0.5228, 0.4583, 0.4086, 0.3723, 0.336, 0.3024, - # 0.2702, 0.2433, 0.2177, 0.2016, 0.1909, 0.1815, 0.1734, 0.1653, 0.1586, 0.1519, 0.1465, 0.1411, 0.1358, - # 0.129, 0.1237, 0.1183, 0.1129, 0.1075, 0.1022, 0.0968, 0.0914, 0.086, 0.0806, 0.0753, 0.0699, 0.0645, - # 0.0591, 0.0538, 0.0484, 0.043, 0.0376, 0.0323, 0.0269, 0.0215, 0.0161, 0.0108, 0.0054, 0.0027, 0.0013, - # 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - # [1.0, 0.8708, 0.8347, 0.7819, 0.7042, 0.6181, 0.5486, 0.475, 0.4333, 0.4028, 0.3708, 0.3375, 0.3083, - # 0.2819, 0.2611, 0.2472, 0.2361, 0.2278, 0.2194, 0.2139, 0.2083, 0.2028, 0.1972, 0.1917, 0.1861, 0.1806, - # 0.175, 0.1694, 0.1639, 0.1583, 0.1528, 0.1472, 0.1417, 0.1361, 0.1306, 0.125, 0.1194, 0.1139, 0.1083, - # 0.1028, 0.0972, 0.0917, 0.0861, 0.0792, 0.0722, 0.0653, 0.0583, 0.0514, 0.0444, 0.0389, 0.0333, 0.0306, - # 0.0292, 0.0278, 0.0264, 0.025, 0.0236, 0.0222, 0.0208, 0.0194, 0.0181, 0.0167, 0.0153, 0.0139, 0.0125, - # 0.0111, 0.0097, 0.0083, 0.0069, 0.0056, 0.0042, 0.0028, 0.0014, 0, 0, 0, 0, 0, 0], - # [1.0, 0.8199, 0.7608, 0.7016, 0.6223, 0.5685, 0.5161, 0.4543, 0.3952, 0.3495, 0.3172, 0.2863, 0.2648, - # 0.246, 0.2272, 0.211, 0.1976, 0.1868, 0.1747, 0.1653, 0.1559, 0.1478, 0.1411, 0.1344, 0.1277, 0.121, - # 0.1142, 0.1075, 0.1035, 0.0995, 0.0954, 0.0914, 0.0874, 0.0833, 0.0793, 0.0753, 0.0712, 0.0672, 0.0645, - # 0.0605, 0.0551, 0.0497, 0.0444, 0.039, 0.0336, 0.0282, 0.0228, 0.0175, 0.0121, 0.0108, 0.0094, 0.0081, - # 0.0081, 0.0081, 0.0081, 0.0081, 0.0081, 0.0081, 0.0081, 0.0081, 0.0081, 0.0081, 0.0081, 0.0081, 0.0081, - # 0.0081, 0.0081, 0.0067, 0.0054, 0.004, 0.0027, 0.0013, 0, 0, 0, 0, 0, 0, 0], - # [1.0, 0.7931, 0.7319, 0.6542, 0.5819, 0.525, 0.4653, 0.4056, 0.3542, 0.3153, 0.2792, 0.2542, 0.2375, - # 0.2208, 0.2069, 0.1958, 0.1819, 0.1694, 0.1542, 0.1417, 0.1319, 0.1222, 0.1125, 0.1028, 0.0875, 0.0806, - # 0.0736, 0.0667, 0.0597, 0.0542, 0.0486, 0.0458, 0.0431, 0.0403, 0.0375, 0.0347, 0.0319, 0.0292, 0.0264, - # 0.0236, 0.0208, 0.0181, 0.0167, 0.0153, 0.0125, 0.0097, 0.0069, 0.0042, 0.0014, 0, 0, 0, 0, 0, 0, 0, 0, - # 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - # [1.0, 0.7796, 0.7124, 0.6465, 0.5685, 0.4946, 0.4207, 0.3441, 0.2957, 0.2487, 0.2137, 0.1989, 0.1855, - # 0.1747, 0.168, 0.1613, 0.1505, 0.1398, 0.129, 0.1183, 0.1075, 0.0968, 0.086, 0.0753, 0.0659, 0.0591, - # 0.0524, 0.0457, 0.039, 0.0336, 0.0309, 0.0296, 0.0282, 0.0269, 0.0255, 0.0242, 0.0228, 0.0215, 0.0202, - # 0.0188, 0.0175, 0.0161, 0.0148, 0.0134, 0.0121, 0.0108, 0.0094, 0.0081, 0.0067, 0.0054, 0.004, 0.0027, - # 0.0013, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], - # "probs_of_surviving_by_hour_of_the_day": [ - # [1.0, 1.0, 1.0, 0.9863, 0.8986, 0.8, 0.5233, 0.2247, 0.1671, 0.1644, 0.1644, 0.1644, 0.1644, 0.1644, - # 0.1616, 0.1616, 0.1616, 0.1534, 0.1425, 0.1068, 0.1068, 0.1068, 0.1068, 0.1068, 0.1068, 0.1068, 0.1068, - # 0.1068, 0.1068, 0.1068, 0.0795, 0.0438, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, - # 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, - # 0.0082, 0.0082, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - # [1.0, 1.0, 1.0, 0.9863, 0.8959, 0.6301, 0.3014, 0.2164, 0.2137, 0.2137, 0.2137, 0.211, 0.211, 0.2082, - # 0.2082, 0.2082, 0.1973, 0.1863, 0.126, 0.126, 0.126, 0.126, 0.126, 0.126, 0.126, 0.126, 0.126, 0.126, - # 0.126, 0.0959, 0.0521, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, - # 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0137, - # 0.0082, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, - # 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027], - # [1.0, 1.0, 1.0, 0.9863, 0.7123, 0.3644, 0.2904, 0.2822, 0.2822, 0.2822, 0.2822, 0.2795, 0.274, 0.274, - # 0.274, 0.2603, 0.2356, 0.1452, 0.1452, 0.1452, 0.1452, 0.1452, 0.1452, 0.1452, 0.1452, 0.1452, 0.1452, - # 0.1452, 0.1151, 0.0575, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, - # 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0192, - # 0.0082, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, - # 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0], - # [1.0, 1.0, 1.0, 0.8055, 0.474, 0.3616, 0.3562, 0.3562, 0.3562, 0.3562, 0.3534, 0.3479, 0.3479, 0.3479, - # 0.3233, 0.2904, 0.1836, 0.1836, 0.1836, 0.1836, 0.1836, 0.1836, 0.1836, 0.1836, 0.1808, 0.1808, 0.1808, - # 0.1397, 0.0712, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, - # 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0274, 0.0137, - # 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, - # 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0, 0], - # [1.0, 1.0, 0.863, 0.5315, 0.4521, 0.4493, 0.4466, 0.4466, 0.4466, 0.4438, 0.4384, 0.4384, 0.4356, 0.411, - # 0.3616, 0.2055, 0.2055, 0.2055, 0.2055, 0.2055, 0.2055, 0.2055, 0.2055, 0.2055, 0.2027, 0.2027, 0.1589, - # 0.0904, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, - # 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0411, 0.0247, 0.0027, - # 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, - # 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0, 0, 0], - # [1.0, 0.8712, 0.5808, 0.5014, 0.4959, 0.4959, 0.4959, 0.4959, 0.4932, 0.4877, 0.4877, 0.4849, 0.4575, - # 0.4027, 0.2137, 0.2137, 0.2137, 0.2137, 0.2137, 0.2137, 0.2137, 0.2137, 0.2137, 0.2137, 0.211, 0.1699, - # 0.1014, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, - # 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.0521, 0.0274, 0.0027, 0.0027, 0.0027, - # 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, - # 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0, 0, 0, 0], - # [1.0, 0.7041, 0.6685, 0.6685, 0.6658, 0.6658, 0.6658, 0.663, 0.6575, 0.6575, 0.6548, 0.6055, 0.526, - # 0.2712, 0.2712, 0.2685, 0.2685, 0.2685, 0.2685, 0.2685, 0.2685, 0.2685, 0.2685, 0.2658, 0.2219, 0.1534, - # 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, - # 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.0767, 0.0438, 0.0082, 0.0082, - # 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, - # 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0055, 0.0027, 0, 0, 0, 0, 0], - # [1.0, 0.937, 0.9233, 0.9178, 0.9068, 0.9068, 0.9014, 0.8904, 0.8904, 0.8767, 0.7205, 0.5836, 0.3014, - # 0.3014, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2521, 0.1671, 0.1288, - # 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, - # 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.0904, 0.0466, 0.011, 0.011, 0.011, - # 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, - # 0.011, 0.011, 0.011, 0.011, 0.011, 0.0082, 0.0027, 0, 0, 0, 0, 0, 0], - # [1.0, 0.9671, 0.9562, 0.9479, 0.9479, 0.9425, 0.9288, 0.926, 0.9096, 0.7397, 0.5918, 0.3014, 0.3014, - # 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2521, 0.1671, 0.1288, 0.1288, - # 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, - # 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.0904, 0.0466, 0.011, 0.011, 0.011, 0.011, - # 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, - # 0.011, 0.011, 0.011, 0.011, 0.0082, 0.0027, 0, 0, 0, 0, 0, 0, 0], - # [1.0, 0.9699, 0.9589, 0.9589, 0.9534, 0.937, 0.937, 0.9178, 0.7397, 0.5973, 0.3014, 0.3014, 0.2986, - # 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2548, 0.1671, 0.1288, 0.1288, 0.1288, - # 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, - # 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.0904, 0.0466, 0.011, 0.011, 0.011, 0.011, 0.011, - # 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, - # 0.011, 0.011, 0.011, 0.0082, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0], - # [1.0, 0.9781, 0.9699, 0.9644, 0.9479, 0.9452, 0.9288, 0.7397, 0.5973, 0.3014, 0.3014, 0.2986, 0.2986, - # 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2548, 0.1671, 0.1288, 0.1288, 0.1288, 0.1288, - # 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, - # 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.0904, 0.0466, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, - # 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, - # 0.011, 0.011, 0.0082, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0], - # [1.0, 0.9863, 0.9726, 0.9534, 0.9507, 0.9315, 0.7397, 0.5973, 0.3014, 0.3014, 0.2986, 0.2986, 0.2986, - # 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2548, 0.1671, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, - # 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, - # 0.1288, 0.1288, 0.1288, 0.1288, 0.0904, 0.0466, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, - # 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, - # 0.0082, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - # [1.0, 0.9836, 0.9616, 0.9589, 0.937, 0.7397, 0.5973, 0.3014, 0.3014, 0.2986, 0.2986, 0.2986, 0.2986, - # 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2466, 0.1534, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, - # 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, - # 0.1151, 0.1151, 0.1151, 0.0795, 0.0438, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, - # 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, - # 0.0082, 0.0082, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - # [1.0, 0.9644, 0.9616, 0.9397, 0.7397, 0.5973, 0.3014, 0.3014, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, - # 0.2986, 0.2986, 0.2959, 0.2932, 0.2438, 0.1507, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, - # 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, - # 0.1123, 0.1123, 0.0795, 0.0411, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, - # 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, - # 0.0055, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - # [1.0, 0.9726, 0.9479, 0.7397, 0.5973, 0.3014, 0.3014, 0.2986, 0.2986, 0.2986, 0.2986, 0.2959, 0.2932, - # 0.2932, 0.2904, 0.2904, 0.2384, 0.1534, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, - # 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, - # 0.1151, 0.0822, 0.0411, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, - # 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, - # 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - # [1.0, 0.9562, 0.7452, 0.6027, 0.3014, 0.3014, 0.2986, 0.2986, 0.2932, 0.2932, 0.2932, 0.2904, 0.2904, - # 0.2822, 0.2822, 0.2356, 0.1479, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, - # 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, - # 0.0822, 0.0411, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, - # 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0027, - # 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - # [1.0, 0.7479, 0.6, 0.3014, 0.3014, 0.2959, 0.2932, 0.2932, 0.2877, 0.2877, 0.2822, 0.2822, 0.2795, - # 0.2575, 0.2082, 0.1397, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, - # 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.0767, - # 0.0411, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, - # 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0027, 0, 0, - # 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - # [1.0, 0.6137, 0.3014, 0.3014, 0.2959, 0.2932, 0.2877, 0.2877, 0.2849, 0.2822, 0.2767, 0.2575, 0.2521, - # 0.2055, 0.1315, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, - # 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.074, 0.0411, - # 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, - # 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0027, 0, 0, 0, 0, 0, - # 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - # [1.0, 0.3123, 0.3123, 0.3096, 0.3068, 0.3068, 0.2986, 0.2986, 0.2932, 0.2877, 0.2795, 0.2685, 0.2164, - # 0.126, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, - # 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.074, 0.0411, 0.0055, - # 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, - # 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, - # 0, 0, 0, 0, 0, 0, 0, 0, 0], - # [1.0, 0.326, 0.3123, 0.3123, 0.3096, 0.3068, 0.3014, 0.2986, 0.2904, 0.2795, 0.2685, 0.2164, 0.1205, - # 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, - # 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.074, 0.0411, 0.0055, 0.0055, - # 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, - # 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - # 0, 0, 0, 0, 0, 0, 0, 0], - # [1.0, 0.3123, 0.3123, 0.3123, 0.3096, 0.3068, 0.2986, 0.2877, 0.2822, 0.2712, 0.2164, 0.126, 0.1041, - # 0.1041, 0.1041, 0.1041, 0.1041, 0.1041, 0.1041, 0.1041, 0.1041, 0.1041, 0.1041, 0.1014, 0.1014, 0.1014, - # 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.074, 0.0411, 0.0055, 0.0055, 0.0055, - # 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, - # 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - # 0, 0, 0, 0, 0, 0], - # [1.0, 0.7644, 0.7644, 0.7479, 0.6986, 0.6493, 0.5863, 0.4849, 0.4027, 0.2548, 0.1534, 0.1123, 0.1123, - # 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1096, 0.1014, 0.1014, 0.1014, 0.1014, - # 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.074, 0.0411, 0.0055, 0.0055, 0.0055, 0.0055, - # 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, - # 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - # 0, 0, 0, 0], - # [1.0, 1.0, 1.0, 0.9753, 0.9068, 0.8301, 0.7178, 0.5616, 0.3452, 0.1753, 0.1315, 0.1288, 0.1288, 0.1288, - # 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1233, 0.1178, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, - # 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.074, 0.0411, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, - # 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, - # 0.0055, 0.0055, 0.0055, 0.0055, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - # [1.0, 1.0, 1.0, 0.9863, 0.8932, 0.8, 0.6685, 0.411, 0.1836, 0.1507, 0.1479, 0.1479, 0.1479, 0.1479, - # 0.1479, 0.1479, 0.1479, 0.1479, 0.1397, 0.1315, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, - # 0.1014, 0.1014, 0.1014, 0.1014, 0.074, 0.0411, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, - # 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, - # 0.0055, 0.0055, 0.0055, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]] - # } - # resp = simulate_outages(**inputs) + # Output parse from existing simulation + expected = { + 'resilience_hours_min': 0, + 'resilience_hours_max': 78, + 'resilience_hours_avg': 10.23, + "outage_durations": list(range(1, 79)), + "probs_of_surviving": [0.8486, 0.7963, 0.7373, 0.6624, 0.59, 0.5194, 0.4533, 0.4007, 0.3583, 0.3231, 0.2933, + 0.2691, 0.2471, 0.2297, 0.215, 0.2015, 0.1897, 0.1788, 0.1694, 0.1608, 0.153, 0.1454, + 0.1381, 0.1308, 0.1247, 0.1184, 0.1122, 0.1065, 0.1013, 0.0968, 0.0927, 0.0887, 0.0847, + 0.0807, 0.0767, 0.0727, 0.0687, 0.0648, 0.0607, 0.0562, 0.0516, 0.047, 0.0425, 0.0376, + 0.0326, 0.0277, 0.0228, 0.0179, 0.0146, 0.0116, 0.0097, 0.0081, 0.0071, 0.0066, 0.0062, + 0.0059, 0.0057, 0.0055, 0.0053, 0.005, 0.0048, 0.0046, 0.0043, 0.0041, 0.0039, 0.0037, + 0.0032, 0.0027, 0.0023, 0.0018, 0.0014, 0.0009, 0.0007, 0.0006, 0.0005, 0.0003, 0.0002, + 0.0001], + "probs_of_surviving_by_month": [ + [1.0, 0.7688, 0.7003, 0.6277, 0.5511, 0.4785, 0.4005, 0.3266, 0.2769, 0.2312, 0.1989, 0.1774, 0.168, + 0.1586, 0.1519, 0.1452, 0.1358, 0.1263, 0.1169, 0.1075, 0.0981, 0.0887, 0.078, 0.0672, 0.0591, 0.0538, + 0.0484, 0.043, 0.0376, 0.0323, 0.0309, 0.0296, 0.0282, 0.0269, 0.0255, 0.0242, 0.0228, 0.0215, 0.0202, + 0.0188, 0.0175, 0.0161, 0.0148, 0.0134, 0.0121, 0.0108, 0.0094, 0.0081, 0.0067, 0.0054, 0.004, 0.0027, + 0.0013, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [1.0, 0.8036, 0.7381, 0.6756, 0.6131, 0.5208, 0.4315, 0.3571, 0.3021, 0.2589, 0.2262, 0.2024, 0.1935, + 0.183, 0.1756, 0.1696, 0.1607, 0.1548, 0.1473, 0.1384, 0.1324, 0.1265, 0.1205, 0.1146, 0.1086, 0.1027, + 0.0967, 0.0908, 0.0848, 0.0804, 0.0774, 0.0744, 0.0729, 0.0714, 0.0699, 0.0685, 0.067, 0.0655, 0.064, + 0.061, 0.0565, 0.0521, 0.0476, 0.0446, 0.0387, 0.0327, 0.0268, 0.0208, 0.0149, 0.0134, 0.0119, 0.0104, + 0.0089, 0.0089, 0.0089, 0.0089, 0.0089, 0.0089, 0.0089, 0.0089, 0.0089, 0.0089, 0.0089, 0.0089, 0.0089, + 0.0089, 0.0089, 0.0074, 0.006, 0.0045, 0.003, 0.0015, 0, 0, 0, 0, 0, 0, 0], + [1.0, 0.8212, 0.7527, 0.6935, 0.6035, 0.5376, 0.461, 0.3898, 0.328, 0.2782, 0.2419, 0.2124, 0.1922, + 0.1761, 0.1667, 0.1559, 0.1452, 0.1358, 0.129, 0.1223, 0.1169, 0.1116, 0.1062, 0.1008, 0.0954, 0.0901, + 0.0847, 0.0793, 0.0766, 0.0753, 0.0739, 0.0726, 0.0712, 0.0699, 0.0685, 0.0672, 0.0659, 0.0645, 0.0632, + 0.0618, 0.0578, 0.0538, 0.0484, 0.043, 0.0376, 0.0323, 0.0269, 0.0215, 0.0161, 0.0134, 0.0108, 0.0081, + 0.0054, 0.0027, 0.0013, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [1.0, 0.8889, 0.8292, 0.7694, 0.6917, 0.6194, 0.5542, 0.4903, 0.4431, 0.4069, 0.3708, 0.3375, 0.3069, + 0.2792, 0.2639, 0.25, 0.2389, 0.2278, 0.2181, 0.2083, 0.1986, 0.1903, 0.1833, 0.1764, 0.1694, 0.1625, + 0.1556, 0.1486, 0.1417, 0.1347, 0.1278, 0.1208, 0.1139, 0.1069, 0.1, 0.0931, 0.0861, 0.0792, 0.0722, + 0.0653, 0.0583, 0.0514, 0.0444, 0.0375, 0.0306, 0.0236, 0.0167, 0.0097, 0.0028, 0.0014, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [1.0, 0.9059, 0.8723, 0.8293, 0.7796, 0.6962, 0.6358, 0.578, 0.5215, 0.4785, 0.4409, 0.4005, 0.3656, + 0.3333, 0.3051, 0.2809, 0.2608, 0.2419, 0.2285, 0.2177, 0.207, 0.1989, 0.1935, 0.1882, 0.1828, 0.1774, + 0.172, 0.1667, 0.1613, 0.1559, 0.1505, 0.1452, 0.1398, 0.1344, 0.129, 0.1237, 0.1183, 0.1129, 0.1075, + 0.1022, 0.0968, 0.0914, 0.086, 0.0806, 0.0753, 0.0699, 0.0645, 0.0591, 0.0538, 0.0484, 0.043, 0.039, + 0.0363, 0.0349, 0.0336, 0.0323, 0.0309, 0.0296, 0.0282, 0.0269, 0.0255, 0.0242, 0.0228, 0.0215, 0.0202, + 0.0188, 0.0175, 0.0161, 0.0148, 0.0134, 0.0121, 0.0108, 0.0094, 0.0081, 0.0067, 0.0054, 0.004, 0.0027, + 0.0013], + [1.0, 0.9111, 0.8792, 0.8292, 0.7597, 0.6708, 0.5944, 0.5403, 0.4875, 0.4444, 0.4028, 0.3667, 0.3306, + 0.2972, 0.2667, 0.2431, 0.225, 0.2083, 0.1944, 0.1861, 0.1792, 0.1736, 0.1681, 0.1625, 0.1569, 0.1514, + 0.1458, 0.1403, 0.1347, 0.1292, 0.1236, 0.1181, 0.1125, 0.1069, 0.1014, 0.0958, 0.0903, 0.0847, 0.0792, + 0.0736, 0.0681, 0.0625, 0.0569, 0.0514, 0.0458, 0.0403, 0.0347, 0.0292, 0.0236, 0.0181, 0.0125, 0.0083, + 0.0042, 0.0028, 0.0014, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [1.0, 0.9194, 0.8831, 0.8293, 0.7433, 0.6815, 0.6022, 0.5497, 0.5067, 0.4718, 0.4368, 0.4046, 0.3696, + 0.3414, 0.3172, 0.2997, 0.2823, 0.2661, 0.2513, 0.2392, 0.2285, 0.2177, 0.207, 0.1976, 0.1895, 0.1815, + 0.1734, 0.1653, 0.1573, 0.1492, 0.1425, 0.1358, 0.129, 0.1223, 0.1156, 0.1089, 0.1022, 0.0954, 0.0887, + 0.082, 0.0753, 0.0685, 0.0618, 0.0551, 0.0484, 0.0417, 0.0349, 0.0282, 0.0215, 0.0148, 0.0081, 0.0054, + 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [1.0, 0.8992, 0.8589, 0.8065, 0.7285, 0.664, 0.5968, 0.5228, 0.4583, 0.4086, 0.3723, 0.336, 0.3024, + 0.2702, 0.2433, 0.2177, 0.2016, 0.1909, 0.1815, 0.1734, 0.1653, 0.1586, 0.1519, 0.1465, 0.1411, 0.1358, + 0.129, 0.1237, 0.1183, 0.1129, 0.1075, 0.1022, 0.0968, 0.0914, 0.086, 0.0806, 0.0753, 0.0699, 0.0645, + 0.0591, 0.0538, 0.0484, 0.043, 0.0376, 0.0323, 0.0269, 0.0215, 0.0161, 0.0108, 0.0054, 0.0027, 0.0013, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [1.0, 0.8708, 0.8347, 0.7819, 0.7042, 0.6181, 0.5486, 0.475, 0.4333, 0.4028, 0.3708, 0.3375, 0.3083, + 0.2819, 0.2611, 0.2472, 0.2361, 0.2278, 0.2194, 0.2139, 0.2083, 0.2028, 0.1972, 0.1917, 0.1861, 0.1806, + 0.175, 0.1694, 0.1639, 0.1583, 0.1528, 0.1472, 0.1417, 0.1361, 0.1306, 0.125, 0.1194, 0.1139, 0.1083, + 0.1028, 0.0972, 0.0917, 0.0861, 0.0792, 0.0722, 0.0653, 0.0583, 0.0514, 0.0444, 0.0389, 0.0333, 0.0306, + 0.0292, 0.0278, 0.0264, 0.025, 0.0236, 0.0222, 0.0208, 0.0194, 0.0181, 0.0167, 0.0153, 0.0139, 0.0125, + 0.0111, 0.0097, 0.0083, 0.0069, 0.0056, 0.0042, 0.0028, 0.0014, 0, 0, 0, 0, 0, 0], + [1.0, 0.8199, 0.7608, 0.7016, 0.6223, 0.5685, 0.5161, 0.4543, 0.3952, 0.3495, 0.3172, 0.2863, 0.2648, + 0.246, 0.2272, 0.211, 0.1976, 0.1868, 0.1747, 0.1653, 0.1559, 0.1478, 0.1411, 0.1344, 0.1277, 0.121, + 0.1142, 0.1075, 0.1035, 0.0995, 0.0954, 0.0914, 0.0874, 0.0833, 0.0793, 0.0753, 0.0712, 0.0672, 0.0645, + 0.0605, 0.0551, 0.0497, 0.0444, 0.039, 0.0336, 0.0282, 0.0228, 0.0175, 0.0121, 0.0108, 0.0094, 0.0081, + 0.0081, 0.0081, 0.0081, 0.0081, 0.0081, 0.0081, 0.0081, 0.0081, 0.0081, 0.0081, 0.0081, 0.0081, 0.0081, + 0.0081, 0.0081, 0.0067, 0.0054, 0.004, 0.0027, 0.0013, 0, 0, 0, 0, 0, 0, 0], + [1.0, 0.7931, 0.7319, 0.6542, 0.5819, 0.525, 0.4653, 0.4056, 0.3542, 0.3153, 0.2792, 0.2542, 0.2375, + 0.2208, 0.2069, 0.1958, 0.1819, 0.1694, 0.1542, 0.1417, 0.1319, 0.1222, 0.1125, 0.1028, 0.0875, 0.0806, + 0.0736, 0.0667, 0.0597, 0.0542, 0.0486, 0.0458, 0.0431, 0.0403, 0.0375, 0.0347, 0.0319, 0.0292, 0.0264, + 0.0236, 0.0208, 0.0181, 0.0167, 0.0153, 0.0125, 0.0097, 0.0069, 0.0042, 0.0014, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [1.0, 0.7796, 0.7124, 0.6465, 0.5685, 0.4946, 0.4207, 0.3441, 0.2957, 0.2487, 0.2137, 0.1989, 0.1855, + 0.1747, 0.168, 0.1613, 0.1505, 0.1398, 0.129, 0.1183, 0.1075, 0.0968, 0.086, 0.0753, 0.0659, 0.0591, + 0.0524, 0.0457, 0.039, 0.0336, 0.0309, 0.0296, 0.0282, 0.0269, 0.0255, 0.0242, 0.0228, 0.0215, 0.0202, + 0.0188, 0.0175, 0.0161, 0.0148, 0.0134, 0.0121, 0.0108, 0.0094, 0.0081, 0.0067, 0.0054, 0.004, 0.0027, + 0.0013, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], + "probs_of_surviving_by_hour_of_the_day": [ + [1.0, 1.0, 1.0, 0.9863, 0.8986, 0.8, 0.5233, 0.2247, 0.1671, 0.1644, 0.1644, 0.1644, 0.1644, 0.1644, + 0.1616, 0.1616, 0.1616, 0.1534, 0.1425, 0.1068, 0.1068, 0.1068, 0.1068, 0.1068, 0.1068, 0.1068, 0.1068, + 0.1068, 0.1068, 0.1068, 0.0795, 0.0438, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, + 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, + 0.0082, 0.0082, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [1.0, 1.0, 1.0, 0.9863, 0.8959, 0.6301, 0.3014, 0.2164, 0.2137, 0.2137, 0.2137, 0.211, 0.211, 0.2082, + 0.2082, 0.2082, 0.1973, 0.1863, 0.126, 0.126, 0.126, 0.126, 0.126, 0.126, 0.126, 0.126, 0.126, 0.126, + 0.126, 0.0959, 0.0521, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, + 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0164, 0.0137, + 0.0082, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, + 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027], + [1.0, 1.0, 1.0, 0.9863, 0.7123, 0.3644, 0.2904, 0.2822, 0.2822, 0.2822, 0.2822, 0.2795, 0.274, 0.274, + 0.274, 0.2603, 0.2356, 0.1452, 0.1452, 0.1452, 0.1452, 0.1452, 0.1452, 0.1452, 0.1452, 0.1452, 0.1452, + 0.1452, 0.1151, 0.0575, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, + 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0219, 0.0192, + 0.0082, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, + 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0], + [1.0, 1.0, 1.0, 0.8055, 0.474, 0.3616, 0.3562, 0.3562, 0.3562, 0.3562, 0.3534, 0.3479, 0.3479, 0.3479, + 0.3233, 0.2904, 0.1836, 0.1836, 0.1836, 0.1836, 0.1836, 0.1836, 0.1836, 0.1836, 0.1808, 0.1808, 0.1808, + 0.1397, 0.0712, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, + 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0329, 0.0274, 0.0137, + 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, + 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0, 0], + [1.0, 1.0, 0.863, 0.5315, 0.4521, 0.4493, 0.4466, 0.4466, 0.4466, 0.4438, 0.4384, 0.4384, 0.4356, 0.411, + 0.3616, 0.2055, 0.2055, 0.2055, 0.2055, 0.2055, 0.2055, 0.2055, 0.2055, 0.2055, 0.2027, 0.2027, 0.1589, + 0.0904, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, + 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0521, 0.0411, 0.0247, 0.0027, + 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, + 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0, 0, 0], + [1.0, 0.8712, 0.5808, 0.5014, 0.4959, 0.4959, 0.4959, 0.4959, 0.4932, 0.4877, 0.4877, 0.4849, 0.4575, + 0.4027, 0.2137, 0.2137, 0.2137, 0.2137, 0.2137, 0.2137, 0.2137, 0.2137, 0.2137, 0.2137, 0.211, 0.1699, + 0.1014, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, + 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.063, 0.0521, 0.0274, 0.0027, 0.0027, 0.0027, + 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, + 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0.0027, 0, 0, 0, 0], + [1.0, 0.7041, 0.6685, 0.6685, 0.6658, 0.6658, 0.6658, 0.663, 0.6575, 0.6575, 0.6548, 0.6055, 0.526, + 0.2712, 0.2712, 0.2685, 0.2685, 0.2685, 0.2685, 0.2685, 0.2685, 0.2685, 0.2685, 0.2658, 0.2219, 0.1534, + 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, + 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.0767, 0.0438, 0.0082, 0.0082, + 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, + 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0055, 0.0027, 0, 0, 0, 0, 0], + [1.0, 0.937, 0.9233, 0.9178, 0.9068, 0.9068, 0.9014, 0.8904, 0.8904, 0.8767, 0.7205, 0.5836, 0.3014, + 0.3014, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2521, 0.1671, 0.1288, + 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, + 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.0904, 0.0466, 0.011, 0.011, 0.011, + 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, + 0.011, 0.011, 0.011, 0.011, 0.011, 0.0082, 0.0027, 0, 0, 0, 0, 0, 0], + [1.0, 0.9671, 0.9562, 0.9479, 0.9479, 0.9425, 0.9288, 0.926, 0.9096, 0.7397, 0.5918, 0.3014, 0.3014, + 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2521, 0.1671, 0.1288, 0.1288, + 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, + 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.0904, 0.0466, 0.011, 0.011, 0.011, 0.011, + 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, + 0.011, 0.011, 0.011, 0.011, 0.0082, 0.0027, 0, 0, 0, 0, 0, 0, 0], + [1.0, 0.9699, 0.9589, 0.9589, 0.9534, 0.937, 0.937, 0.9178, 0.7397, 0.5973, 0.3014, 0.3014, 0.2986, + 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2548, 0.1671, 0.1288, 0.1288, 0.1288, + 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, + 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.0904, 0.0466, 0.011, 0.011, 0.011, 0.011, 0.011, + 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, + 0.011, 0.011, 0.011, 0.0082, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0], + [1.0, 0.9781, 0.9699, 0.9644, 0.9479, 0.9452, 0.9288, 0.7397, 0.5973, 0.3014, 0.3014, 0.2986, 0.2986, + 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2548, 0.1671, 0.1288, 0.1288, 0.1288, 0.1288, + 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, + 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.0904, 0.0466, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, + 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, + 0.011, 0.011, 0.0082, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [1.0, 0.9863, 0.9726, 0.9534, 0.9507, 0.9315, 0.7397, 0.5973, 0.3014, 0.3014, 0.2986, 0.2986, 0.2986, + 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2548, 0.1671, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, + 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, + 0.1288, 0.1288, 0.1288, 0.1288, 0.0904, 0.0466, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, + 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, 0.011, + 0.0082, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [1.0, 0.9836, 0.9616, 0.9589, 0.937, 0.7397, 0.5973, 0.3014, 0.3014, 0.2986, 0.2986, 0.2986, 0.2986, + 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, 0.2466, 0.1534, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, + 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, + 0.1151, 0.1151, 0.1151, 0.0795, 0.0438, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, + 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, 0.0082, + 0.0082, 0.0082, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [1.0, 0.9644, 0.9616, 0.9397, 0.7397, 0.5973, 0.3014, 0.3014, 0.2986, 0.2986, 0.2986, 0.2986, 0.2986, + 0.2986, 0.2986, 0.2959, 0.2932, 0.2438, 0.1507, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, + 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, + 0.1123, 0.1123, 0.0795, 0.0411, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, + 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, + 0.0055, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [1.0, 0.9726, 0.9479, 0.7397, 0.5973, 0.3014, 0.3014, 0.2986, 0.2986, 0.2986, 0.2986, 0.2959, 0.2932, + 0.2932, 0.2904, 0.2904, 0.2384, 0.1534, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, + 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, 0.1151, + 0.1151, 0.0822, 0.0411, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, + 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, + 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [1.0, 0.9562, 0.7452, 0.6027, 0.3014, 0.3014, 0.2986, 0.2986, 0.2932, 0.2932, 0.2932, 0.2904, 0.2904, + 0.2822, 0.2822, 0.2356, 0.1479, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, + 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, 0.1096, + 0.0822, 0.0411, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, + 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0027, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [1.0, 0.7479, 0.6, 0.3014, 0.3014, 0.2959, 0.2932, 0.2932, 0.2877, 0.2877, 0.2822, 0.2822, 0.2795, + 0.2575, 0.2082, 0.1397, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, + 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.0767, + 0.0411, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, + 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0027, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [1.0, 0.6137, 0.3014, 0.3014, 0.2959, 0.2932, 0.2877, 0.2877, 0.2849, 0.2822, 0.2767, 0.2575, 0.2521, + 0.2055, 0.1315, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, + 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.074, 0.0411, + 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, + 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0027, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [1.0, 0.3123, 0.3123, 0.3096, 0.3068, 0.3068, 0.2986, 0.2986, 0.2932, 0.2877, 0.2795, 0.2685, 0.2164, + 0.126, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, + 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.074, 0.0411, 0.0055, + 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, + 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0], + [1.0, 0.326, 0.3123, 0.3123, 0.3096, 0.3068, 0.3014, 0.2986, 0.2904, 0.2795, 0.2685, 0.2164, 0.1205, + 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, + 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.074, 0.0411, 0.0055, 0.0055, + 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, + 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0], + [1.0, 0.3123, 0.3123, 0.3123, 0.3096, 0.3068, 0.2986, 0.2877, 0.2822, 0.2712, 0.2164, 0.126, 0.1041, + 0.1041, 0.1041, 0.1041, 0.1041, 0.1041, 0.1041, 0.1041, 0.1041, 0.1041, 0.1041, 0.1014, 0.1014, 0.1014, + 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.074, 0.0411, 0.0055, 0.0055, 0.0055, + 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, + 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0], + [1.0, 0.7644, 0.7644, 0.7479, 0.6986, 0.6493, 0.5863, 0.4849, 0.4027, 0.2548, 0.1534, 0.1123, 0.1123, + 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1123, 0.1096, 0.1014, 0.1014, 0.1014, 0.1014, + 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.074, 0.0411, 0.0055, 0.0055, 0.0055, 0.0055, + 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, + 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0], + [1.0, 1.0, 1.0, 0.9753, 0.9068, 0.8301, 0.7178, 0.5616, 0.3452, 0.1753, 0.1315, 0.1288, 0.1288, 0.1288, + 0.1288, 0.1288, 0.1288, 0.1288, 0.1288, 0.1233, 0.1178, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, + 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.074, 0.0411, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, + 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, + 0.0055, 0.0055, 0.0055, 0.0055, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [1.0, 1.0, 1.0, 0.9863, 0.8932, 0.8, 0.6685, 0.411, 0.1836, 0.1507, 0.1479, 0.1479, 0.1479, 0.1479, + 0.1479, 0.1479, 0.1479, 0.1479, 0.1397, 0.1315, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, 0.1014, + 0.1014, 0.1014, 0.1014, 0.1014, 0.074, 0.0411, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, + 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, 0.0055, + 0.0055, 0.0055, 0.0055, 0.0027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]] + } + resp = simulate_outages(**inputs) - # self.assertAlmostEqual(expected['resilience_hours_min'], resp['resilience_hours_min'], places=3) - # self.assertAlmostEqual(expected['resilience_hours_max'], resp['resilience_hours_max'], places=3) - # self.assertAlmostEqual(expected['resilience_hours_avg'], resp['resilience_hours_avg'], places=3) - # self.assertAlmostEqual(expected['outage_durations'], resp['outage_durations'], places=3) - # for x, y in zip(expected['probs_of_surviving'], resp['probs_of_surviving']): - # self.assertAlmostEquals(x, y, places=3) - # for e, r in zip([0, 11], [0, 11]): - # for x, y in zip(expected['probs_of_surviving_by_month'][e], resp['probs_of_surviving_by_month'][r]): - # self.assertAlmostEquals(x, y, places=3) - # for e, r in zip([0, 23], [0, 23]): - # for x, y in zip(expected['probs_of_surviving_by_hour_of_the_day'][e], - # resp['probs_of_surviving_by_hour_of_the_day'][r]): - # self.assertAlmostEquals(x, y, places=3) + self.assertAlmostEqual(expected['resilience_hours_min'], resp['resilience_hours_min'], places=3) + self.assertAlmostEqual(expected['resilience_hours_max'], resp['resilience_hours_max'], places=3) + self.assertAlmostEqual(expected['resilience_hours_avg'], resp['resilience_hours_avg'], places=3) + self.assertAlmostEqual(expected['outage_durations'], resp['outage_durations'], places=3) + for x, y in zip(expected['probs_of_surviving'], resp['probs_of_surviving']): + self.assertAlmostEquals(x, y, places=3) + for e, r in zip([0, 11], [0, 11]): + for x, y in zip(expected['probs_of_surviving_by_month'][e], resp['probs_of_surviving_by_month'][r]): + self.assertAlmostEquals(x, y, places=3) + for e, r in zip([0, 23], [0, 23]): + for x, y in zip(expected['probs_of_surviving_by_hour_of_the_day'][e], + resp['probs_of_surviving_by_hour_of_the_day'][r]): + self.assertAlmostEquals(x, y, places=3) - # def test_outage_sim(self): - # """ - # With diesel + PV + storage - # """ - # expected = { - # 'resilience_hours_min': 30, - # 'resilience_hours_max': 154, - # 'resilience_hours_avg': 82.1, - # "outage_durations": list(range(1, 155)), - # "probs_of_surviving": [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, - # 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.9999, 0.9998, - # 0.9997, 0.9985, 0.9973, 0.9965, 0.9951, 0.9937, 0.9921, 0.989, 0.9861, 0.983, - # 0.9797, 0.976, 0.972, 0.9666, 0.9608, 0.9547, 0.9471, 0.9373, 0.9275, 0.9178, - # 0.9066, 0.897, 0.8868, 0.8761, 0.8647, 0.8521, 0.8398, 0.8285, 0.816, 0.8035, - # 0.7928, 0.7809, 0.7693, 0.7561, 0.7436, 0.7301, 0.7159, 0.701, 0.685, 0.6707, - # 0.6535, 0.6347, 0.6189, 0.6026, 0.5864, 0.5696, 0.5534, 0.5376, 0.5213, 0.5053, - # 0.4892, 0.4719, 0.4542, 0.4378, 0.4217, 0.4058, 0.3901, 0.3736, 0.3584, 0.3434, - # 0.3287, 0.3152, 0.3006, 0.2854, 0.2728, 0.2579, 0.2449, 0.2321, 0.2196, 0.2068, - # 0.1941, 0.1812, 0.1685, 0.1562, 0.1446, 0.1324, 0.1216, 0.1114, 0.1021, 0.0928, - # 0.084, 0.0752, 0.0675, 0.0603, 0.0533, 0.0466, 0.0411, 0.0371, 0.0333, 0.0296, - # 0.026, 0.0231, 0.0203, 0.0178, 0.0153, 0.0129, 0.0105, 0.0086, 0.0067, 0.0053, - # 0.0042, 0.0035, 0.0033, 0.0031, 0.0029, 0.0026, 0.0024, 0.0022, 0.0019, 0.0017, - # 0.0015, 0.0013, 0.0011, 0.001, 0.0009, 0.0008, 0.0007, 0.0006, 0.0005, 0.0003, - # 0.0002, 0.0001] - # } - # resp = simulate_outages(**self.inputs) + def test_outage_sim(self): + """ + With diesel + PV + storage + """ + expected = { + 'resilience_hours_min': 30, + 'resilience_hours_max': 154, + 'resilience_hours_avg': 82.1, + "outage_durations": list(range(1, 155)), + "probs_of_surviving": [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.9999, 0.9998, + 0.9997, 0.9985, 0.9973, 0.9965, 0.9951, 0.9937, 0.9921, 0.989, 0.9861, 0.983, + 0.9797, 0.976, 0.972, 0.9666, 0.9608, 0.9547, 0.9471, 0.9373, 0.9275, 0.9178, + 0.9066, 0.897, 0.8868, 0.8761, 0.8647, 0.8521, 0.8398, 0.8285, 0.816, 0.8035, + 0.7928, 0.7809, 0.7693, 0.7561, 0.7436, 0.7301, 0.7159, 0.701, 0.685, 0.6707, + 0.6535, 0.6347, 0.6189, 0.6026, 0.5864, 0.5696, 0.5534, 0.5376, 0.5213, 0.5053, + 0.4892, 0.4719, 0.4542, 0.4378, 0.4217, 0.4058, 0.3901, 0.3736, 0.3584, 0.3434, + 0.3287, 0.3152, 0.3006, 0.2854, 0.2728, 0.2579, 0.2449, 0.2321, 0.2196, 0.2068, + 0.1941, 0.1812, 0.1685, 0.1562, 0.1446, 0.1324, 0.1216, 0.1114, 0.1021, 0.0928, + 0.084, 0.0752, 0.0675, 0.0603, 0.0533, 0.0466, 0.0411, 0.0371, 0.0333, 0.0296, + 0.026, 0.0231, 0.0203, 0.0178, 0.0153, 0.0129, 0.0105, 0.0086, 0.0067, 0.0053, + 0.0042, 0.0035, 0.0033, 0.0031, 0.0029, 0.0026, 0.0024, 0.0022, 0.0019, 0.0017, + 0.0015, 0.0013, 0.0011, 0.001, 0.0009, 0.0008, 0.0007, 0.0006, 0.0005, 0.0003, + 0.0002, 0.0001] + } + resp = simulate_outages(**self.inputs) - # self.assertAlmostEqual(expected['resilience_hours_min'], resp['resilience_hours_min'], places=4) - # self.assertAlmostEqual(expected['resilience_hours_max'], resp['resilience_hours_max'], places=4) - # self.assertAlmostEqual(expected['resilience_hours_avg'], resp['resilience_hours_avg'], places=4) - # self.assertListEqual(expected['outage_durations'], resp['outage_durations']) - # for x, y in zip(expected['probs_of_surviving'], resp['probs_of_surviving']): - # self.assertAlmostEquals(x, y, places=4) + self.assertAlmostEqual(expected['resilience_hours_min'], resp['resilience_hours_min'], places=4) + self.assertAlmostEqual(expected['resilience_hours_max'], resp['resilience_hours_max'], places=4) + self.assertAlmostEqual(expected['resilience_hours_avg'], resp['resilience_hours_avg'], places=4) + self.assertListEqual(expected['outage_durations'], resp['outage_durations']) + for x, y in zip(expected['probs_of_surviving'], resp['probs_of_surviving']): + self.assertAlmostEquals(x, y, places=4) - # def test_no_resilience(self): - # inputs = self.inputs - # inputs.update(pv_kw_ac_hourly=[], batt_kw=0, diesel_kw=0) - # resp = simulate_outages(**inputs) + def test_no_resilience(self): + inputs = self.inputs + inputs.update(pv_kw_ac_hourly=[], batt_kw=0, diesel_kw=0) + resp = simulate_outages(**inputs) - # self.assertEqual(0, resp['resilience_hours_min']) - # self.assertEqual(0, resp['resilience_hours_max']) - # self.assertEqual(0, resp['resilience_hours_avg']) - # self.assertEqual([], resp['outage_durations']) - # self.assertEqual([], resp['probs_of_surviving']) - # self.assertEqual([[0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0]], resp['probs_of_surviving_by_month']) - # self.assertEqual([[0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0]], resp['probs_of_surviving_by_hour_of_the_day']) + self.assertEqual(0, resp['resilience_hours_min']) + self.assertEqual(0, resp['resilience_hours_max']) + self.assertEqual(0, resp['resilience_hours_avg']) + self.assertEqual([], resp['outage_durations']) + self.assertEqual([], resp['probs_of_surviving']) + self.assertEqual([[0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0]], resp['probs_of_surviving_by_month']) + self.assertEqual([[0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0], [0]], resp['probs_of_surviving_by_hour_of_the_day']) - # def test_flexible_timesteps(self): - # """ - # Same input with different timesteps-per-hour should have almost equal results. - # """ - # resp1 = simulate_outages(**self.inputs1) - # resp2 = simulate_outages(**self.inputs2) + def test_flexible_timesteps(self): + """ + Same input with different timesteps-per-hour should have almost equal results. + """ + resp1 = simulate_outages(**self.inputs1) + resp2 = simulate_outages(**self.inputs2) - # self.assertAlmostEqual(1, resp2['resilience_hours_max'] / resp1['resilience_hours_max'], places=1) - # self.assertAlmostEqual(resp2['resilience_hours_min'], resp1['resilience_hours_min'], places=0) - # self.assertAlmostEqual(1, resp2['resilience_hours_avg'] / resp1['resilience_hours_avg'], places=1) + self.assertAlmostEqual(1, resp2['resilience_hours_max'] / resp1['resilience_hours_max'], places=1) + self.assertAlmostEqual(resp2['resilience_hours_min'], resp1['resilience_hours_min'], places=0) + self.assertAlmostEqual(1, resp2['resilience_hours_avg'] / resp1['resilience_hours_avg'], places=1) - # for x, y in zip(resp1['probs_of_surviving'], resp2['probs_of_surviving']): - # self.assertAlmostEquals(x, y, places=1) + for x, y in zip(resp1['probs_of_surviving'], resp2['probs_of_surviving']): + self.assertAlmostEquals(x, y, places=1) - # def test_resil_endpoint(self): - # post = json.load(open(os.path.join('resilience_stats', 'tests', 'POST_nested.json'), 'r')) - # r = self.api_client.post('/v2/job/', format='json', data=post) - # reopt_resp = json.loads(r.content) - # run_uuid = reopt_resp['run_uuid'] + def test_resil_endpoint(self): + post = json.load(open(os.path.join('resilience_stats', 'tests', 'POST_nested.json'), 'r')) + r = self.api_client.post('/v2/job/', format='json', data=post) + reopt_resp = json.loads(r.content) + run_uuid = reopt_resp['run_uuid'] - # resp = self.api_client.post('/v2/outagesimjob/', format='json', data={"run_uuid": run_uuid, "bau": True}) - # self.assertEqual(resp.status_code, 201) - # resp = self.api_client.get('/v2/job//resilience_stats/'.replace("", run_uuid)) - # resp_dict = json.loads(resp.content)['outage_sim_results'] + resp = self.api_client.post('/v2/outagesimjob/', format='json', data={"run_uuid": run_uuid, "bau": True}) + self.assertEqual(resp.status_code, 201) + resp = self.api_client.get('/v2/job//resilience_stats/'.replace("", run_uuid)) + resp_dict = json.loads(resp.content)['outage_sim_results'] - # # NOTE: probabilities are sensitive to the SOC series, - # # which can change while keeping the same optimal LCC - # expected_probs = [0.3902, 0.2338, 0.1919, 0.1532, 0.1178, 0.0844, 0.0538, 0.0305, 0.0131, 0.0066, 0.0033, 0.001] - # for idx, p in enumerate(resp_dict["probs_of_surviving"]): - # self.assertAlmostEqual(p, expected_probs[idx], places=2) - # self.assertEqual(resp_dict["resilience_hours_avg"], 1.28) - # self.assertEqual(resp_dict["outage_durations"], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]) - # self.assertEqual(resp_dict["resilience_hours_min"], 0) - # self.assertEqual(resp_dict["resilience_hours_max"], 12) - # self.assertFalse("resilience_hours_max_bau" in resp_dict) + # NOTE: probabilities are sensitive to the SOC series, + # which can change while keeping the same optimal LCC + expected_probs = [0.3902, 0.2338, 0.1919, 0.1532, 0.1178, 0.0844, 0.0538, 0.0305, 0.0131, 0.0066, 0.0033, 0.001] + for idx, p in enumerate(resp_dict["probs_of_surviving"]): + self.assertAlmostEqual(p, expected_probs[idx], places=2) + self.assertEqual(resp_dict["resilience_hours_avg"], 1.28) + self.assertEqual(resp_dict["outage_durations"], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]) + self.assertEqual(resp_dict["resilience_hours_min"], 0) + self.assertEqual(resp_dict["resilience_hours_max"], 12) + self.assertFalse("resilience_hours_max_bau" in resp_dict) - # """ - # financial_check returns true if the financial scenario system capacities are greater than or equal to the - # resilience scenario system capacities - # """ - # resp = self.api_client.get( - # '/v2/financial_check/?financial_uuid={0}&resilience_uuid={0}'.format(run_uuid), - # format='json') - # self.assertEqual(resp.status_code, 200) - # results = json.loads(resp.content) - # self.assertTrue(results["survives_specified_outage"]) + """ + financial_check returns true if the financial scenario system capacities are greater than or equal to the + resilience scenario system capacities + """ + resp = self.api_client.get( + '/v2/financial_check/?financial_uuid={0}&resilience_uuid={0}'.format(run_uuid), + format='json') + self.assertEqual(resp.status_code, 200) + results = json.loads(resp.content) + self.assertTrue(results["survives_specified_outage"]) - # def test_outage_sim_chp(self): - # expected = { - # 'resilience_hours_min': 0, - # 'resilience_hours_max': 10, - # 'resilience_hours_avg': 0.01, - # "outage_durations": list(range(1, 11)), - # "probs_of_surviving": [0.0011, 0.001, 0.0009, 0.0008, 0.0007, 0.0006, 0.0005, 0.0003, 0.0002, 0.0001] - # } - # inputs = self.inputs - # inputs.update(pv_kw_ac_hourly=[], batt_kw=0, diesel_kw=0, chp_kw=10, critical_loads_kw=[10]*10 + [11]*8750) - # resp = simulate_outages(**inputs) + def test_outage_sim_chp(self): + expected = { + 'resilience_hours_min': 0, + 'resilience_hours_max': 10, + 'resilience_hours_avg': 0.01, + "outage_durations": list(range(1, 11)), + "probs_of_surviving": [0.0011, 0.001, 0.0009, 0.0008, 0.0007, 0.0006, 0.0005, 0.0003, 0.0002, 0.0001] + } + inputs = self.inputs + inputs.update(pv_kw_ac_hourly=[], batt_kw=0, diesel_kw=0, chp_kw=10, critical_loads_kw=[10]*10 + [11]*8750) + resp = simulate_outages(**inputs) - # self.assertAlmostEqual(expected['resilience_hours_min'], resp['resilience_hours_min'], places=4) - # self.assertAlmostEqual(expected['resilience_hours_max'], resp['resilience_hours_max'], places=4) - # self.assertAlmostEqual(expected['resilience_hours_avg'], resp['resilience_hours_avg'], places=4) - # self.assertListEqual(expected['outage_durations'], resp['outage_durations']) - # for x, y in zip(expected['probs_of_surviving'], resp['probs_of_surviving']): - # self.assertAlmostEquals(x, y, places=4) + self.assertAlmostEqual(expected['resilience_hours_min'], resp['resilience_hours_min'], places=4) + self.assertAlmostEqual(expected['resilience_hours_max'], resp['resilience_hours_max'], places=4) + self.assertAlmostEqual(expected['resilience_hours_avg'], resp['resilience_hours_avg'], places=4) + self.assertListEqual(expected['outage_durations'], resp['outage_durations']) + for x, y in zip(expected['probs_of_surviving'], resp['probs_of_surviving']): + self.assertAlmostEquals(x, y, places=4) From a5ceff1cd3ba9c1a8bb9efe016b17e62896ec3ba Mon Sep 17 00:00:00 2001 From: Hallie Dunham <70401017+hdunham@users.noreply.github.com> Date: Mon, 1 Jul 2024 17:21:34 -0600 Subject: [PATCH 11/24] add erp_outputs view --- resilience_stats/views.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/resilience_stats/views.py b/resilience_stats/views.py index 468a68453..75ae42814 100644 --- a/resilience_stats/views.py +++ b/resilience_stats/views.py @@ -105,6 +105,11 @@ def erp_results(request, run_uuid): return JsonResponse(resp, status=500) def erp_help(request): + """ + Served at host/erp/help + :param request: + :return: JSON response with all erp inputs + """ try: d = dict() d["reopt_run_uuid"] = ERPMeta.info_dict(ERPMeta)["reopt_run_uuid"] @@ -121,6 +126,19 @@ def erp_help(request): except Exception as e: return JsonResponse({"Error": "Unexpected error in ERP help endpoint: {}".format(e.args[0])}, status=500) +def erp_outputs(request): + """ + Served at host/erp/outputs + :return: JSON response with all erp outputs + """ + + try: + d = ERPOutputs.info_dict(ERPOutputs) + return JsonResponse(d) + + except Exception as e: + return JsonResponse({"Error": "Unexpected error in ERP help endpoint: {}".format(e.args[0])}, status=500) + def erp_chp_prime_gen_defaults(request): prime_mover = str(request.GET.get('prime_mover')) is_chp = bool(request.GET.get('is_chp')) From 9e1dc879eff4b0b1c7f6d053413ba6d7861297d7 Mon Sep 17 00:00:00 2001 From: Hallie Dunham <70401017+hdunham@users.noreply.github.com> Date: Mon, 1 Jul 2024 17:22:14 -0600 Subject: [PATCH 12/24] add erp/inputs and erp/outputs urls --- resilience_stats/urls_v3plus.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/resilience_stats/urls_v3plus.py b/resilience_stats/urls_v3plus.py index 023b43161..977b28031 100644 --- a/resilience_stats/urls_v3plus.py +++ b/resilience_stats/urls_v3plus.py @@ -6,5 +6,7 @@ urlpatterns = [ re_path(r'^erp/(?P[0-9a-f-]+)/results/?$', views.erp_results), re_path(r'^erp/help/?$', views.erp_help), + re_path(r'^erp/inputs/?$', views.erp_help), + re_path(r'^erp/outputs/?$', views.erp_outputs), re_path(r'^erp/chp_defaults/?$', views.erp_chp_prime_gen_defaults), ] From d09ee96f16bda6be89c708c99b8ed64ebd947e92 Mon Sep 17 00:00:00 2001 From: Hallie Dunham <70401017+hdunham@users.noreply.github.com> Date: Mon, 1 Jul 2024 18:11:37 -0600 Subject: [PATCH 13/24] Update CHANGELOG.md --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36949fa54..344dc2341 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,12 @@ Classify the change according to the following categories: ##### Removed ### Patches +## Develop +### Minor Updates +#### Added +- `/erp/inputs` endpoint (calls `erp_help()`, same as `/erp/help`) +- `/erp/outputs` endpoint that GETs the ERP output field info (calls `erp_outputs()`) + ## v3.9.1 ### Minor Updates #### Added From 7c3a4ab1e2b77381ddae78d38f4bc79e652a6690 Mon Sep 17 00:00:00 2001 From: Hallie Dunham <70401017+hdunham@users.noreply.github.com> Date: Tue, 9 Jul 2024 19:53:21 -0700 Subject: [PATCH 14/24] delete commented out reopt_version --- reoptjl/api.py | 1 - resilience_stats/api.py | 1 - 2 files changed, 2 deletions(-) diff --git a/reoptjl/api.py b/reoptjl/api.py index 480460721..e2a6264b7 100644 --- a/reoptjl/api.py +++ b/reoptjl/api.py @@ -98,7 +98,6 @@ def obj_create(self, bundle, **kwargs): meta = { "run_uuid": run_uuid, "api_version": 3, - # "reopt_version": "0.47.1", "status": "Validating..." } bundle.data.update({"APIMeta": meta}) diff --git a/resilience_stats/api.py b/resilience_stats/api.py index c92a9f9d2..754247fe4 100644 --- a/resilience_stats/api.py +++ b/resilience_stats/api.py @@ -62,7 +62,6 @@ def obj_create(self, bundle, **kwargs): meta_dict = { "run_uuid": erp_run_uuid, - # "reopt_version": "0.45.0", "status": "Validating..." } From c8338485b3ac8b2b50e4ca1cd25c9be785bf2ab5 Mon Sep 17 00:00:00 2001 From: Hallie Dunham <70401017+hdunham@users.noreply.github.com> Date: Tue, 9 Jul 2024 20:00:40 -0700 Subject: [PATCH 15/24] Update CHANGELOG.md --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e7df597c..4b96257ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,11 @@ Classify the change according to the following categories: ##### Removed ### Patches +## v3.9.3 +### Minor Updates +#### Changed +- Set **reopt_version** in **APIMeta** and **ERPMeta** programatically based on actual REopt.jl package version in Julia environment instead of hardcoded so doesn't need to be updated by hand + ## v3.9.2 #### Added - Added attribute `thermal_efficiency` to the arguments of http endpoint `chp_defaults` From 6e36f1609704156660bbf0d6aa34f896b4d416b8 Mon Sep 17 00:00:00 2001 From: Hallie Dunham <70401017+hdunham@users.noreply.github.com> Date: Wed, 10 Jul 2024 08:50:16 -0700 Subject: [PATCH 16/24] update reopt_version check in tests to not be specific number --- reoptjl/test/test_job_endpoint.py | 2 +- resilience_stats/tests/test_erp.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/reoptjl/test/test_job_endpoint.py b/reoptjl/test/test_job_endpoint.py index 407e1333e..0ecb02a09 100644 --- a/reoptjl/test/test_job_endpoint.py +++ b/reoptjl/test/test_job_endpoint.py @@ -25,7 +25,7 @@ def test_multiple_outages(self): run_uuid = r.get('run_uuid') resp = self.api_client.get(f'/v3/job/{run_uuid}/results') r = json.loads(resp.content) - self.assertEqual(r["reopt_version"], "0.47.1") + self.assertIn("reopt_version", resp.keys()) results = r["outputs"] self.assertEqual(np.array(results["Outages"]["unserved_load_series_kw"]).shape, (1,2,5)) self.assertEqual(np.array(results["Outages"]["generator_fuel_used_per_outage_gal"]).shape, (1,2)) diff --git a/resilience_stats/tests/test_erp.py b/resilience_stats/tests/test_erp.py index 8181f4290..d6dce6d8e 100644 --- a/resilience_stats/tests/test_erp.py +++ b/resilience_stats/tests/test_erp.py @@ -66,7 +66,7 @@ def test_erp_long_duration_battery(self): r_sim = json.loads(resp.content) erp_run_uuid = r_sim.get('run_uuid') resp = self.get_results_sim(erp_run_uuid) - self.assertEqual(resp["reopt_version"], "0.47.1") + self.assertIn("reopt_version", resp.keys()) results_sim = json.loads(resp.content)["outputs"] expected_result = ([1]*79)+[0.999543,0.994178,0.9871,0.97774,0.965753,0.949429,0.926712,0.899543,0.863584,0.826712,0.785616,0.736416,0.683105,0.626256,0.571005,0.519064,0.47226,0.429909,0.391553,0.357306,0] From 4535484cf27ff313be92582e0332533e7a072dd2 Mon Sep 17 00:00:00 2001 From: Hallie Dunham <70401017+hdunham@users.noreply.github.com> Date: Wed, 10 Jul 2024 09:10:23 -0700 Subject: [PATCH 17/24] convert JsonResponse to Dict before keys --- reoptjl/test/test_job_endpoint.py | 2 +- resilience_stats/tests/test_erp.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/reoptjl/test/test_job_endpoint.py b/reoptjl/test/test_job_endpoint.py index 0ecb02a09..8a41b24ce 100644 --- a/reoptjl/test/test_job_endpoint.py +++ b/reoptjl/test/test_job_endpoint.py @@ -25,7 +25,7 @@ def test_multiple_outages(self): run_uuid = r.get('run_uuid') resp = self.api_client.get(f'/v3/job/{run_uuid}/results') r = json.loads(resp.content) - self.assertIn("reopt_version", resp.keys()) + self.assertIn("reopt_version", r.keys()) results = r["outputs"] self.assertEqual(np.array(results["Outages"]["unserved_load_series_kw"]).shape, (1,2,5)) self.assertEqual(np.array(results["Outages"]["generator_fuel_used_per_outage_gal"]).shape, (1,2)) diff --git a/resilience_stats/tests/test_erp.py b/resilience_stats/tests/test_erp.py index d6dce6d8e..c9730e5a4 100644 --- a/resilience_stats/tests/test_erp.py +++ b/resilience_stats/tests/test_erp.py @@ -66,8 +66,9 @@ def test_erp_long_duration_battery(self): r_sim = json.loads(resp.content) erp_run_uuid = r_sim.get('run_uuid') resp = self.get_results_sim(erp_run_uuid) - self.assertIn("reopt_version", resp.keys()) - results_sim = json.loads(resp.content)["outputs"] + r = json.loads(resp.content) + self.assertIn("reopt_version", r.keys()) + results_sim = r["outputs"] expected_result = ([1]*79)+[0.999543,0.994178,0.9871,0.97774,0.965753,0.949429,0.926712,0.899543,0.863584,0.826712,0.785616,0.736416,0.683105,0.626256,0.571005,0.519064,0.47226,0.429909,0.391553,0.357306,0] #TODO: resolve bug where unlimted fuel markov portion of results goes to zero 1 timestep early From e618edcf9bb3cd90dad4862d189890ad72a50088 Mon Sep 17 00:00:00 2001 From: Hallie Dunham <70401017+hdunham@users.noreply.github.com> Date: Wed, 10 Jul 2024 09:19:13 -0700 Subject: [PATCH 18/24] test erp/inputs and erp/outputs endpoints --- resilience_stats/tests/test_erp.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/resilience_stats/tests/test_erp.py b/resilience_stats/tests/test_erp.py index fcd284360..3da388dce 100644 --- a/resilience_stats/tests/test_erp.py +++ b/resilience_stats/tests/test_erp.py @@ -16,6 +16,8 @@ def setUp(self): self.reopt_base_erp = '/v3/erp/' self.reopt_base_erp_results = '/v3/erp/{}/results/' self.reopt_base_erp_help = '/v3/erp/help/' + self.reopt_base_erp_inputs = '/v3/erp/inputs/' + self.reopt_base_erp_outputs = '/v3/erp/outputs/' self.reopt_base_erp_chp_defaults = '/v3/erp/chp_defaults/?prime_mover={0}&is_chp={1}&size_kw={2}' self.post_sim_gens_batt_pv_wind = os.path.join('resilience_stats', 'tests', 'ERP_sim_gens_batt_pv_wind_post.json') self.post_sim_large_stor = os.path.join('resilience_stats', 'tests', 'ERP_sim_large_stor_post.json') @@ -42,6 +44,12 @@ def get_results_sim(self, run_uuid): def get_help(self): return self.api_client.get(self.reopt_base_erp_help) + def get_inputs(self): + return self.api_client.get(self.reopt_base_erp_inputs) + + def get_outputs(self): + return self.api_client.get(self.reopt_base_erp_outputs) + def get_chp_defaults(self, prime_mover, is_chp, size_kw): return self.api_client.get( self.reopt_base_erp_chp_defaults.format(prime_mover, is_chp, size_kw), @@ -182,7 +190,7 @@ def test_erp_with_no_opt(self): resp = self.get_results_sim(erp_run_uuid) self.assertHttpOK(resp) - def test_erp_help_view(self): + def test_erp_help_views(self): """ Tests hiting the erp/help url to get defaults and other info about inputs """ @@ -190,6 +198,14 @@ def test_erp_help_view(self): resp = self.get_help() self.assertHttpOK(resp) resp = json.loads(resp.content) + + resp = self.get_inputs() + self.assertHttpOK(resp) + resp = json.loads(resp.content) + + resp = self.get_outputs() + self.assertHttpOK(resp) + resp = json.loads(resp.content) resp = self.get_chp_defaults("recip_engine", True, 10000) self.assertHttpOK(resp) From 1efd3fbe826aee162a7f3f0772650e10bec75a2a Mon Sep 17 00:00:00 2001 From: Hallie Dunham <70401017+hdunham@users.noreply.github.com> Date: Thu, 18 Jul 2024 15:14:26 -0600 Subject: [PATCH 19/24] Update views.py --- resilience_stats/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resilience_stats/views.py b/resilience_stats/views.py index 75ae42814..5719567d5 100644 --- a/resilience_stats/views.py +++ b/resilience_stats/views.py @@ -106,7 +106,7 @@ def erp_results(request, run_uuid): def erp_help(request): """ - Served at host/erp/help + Served at host/erp/help and host/erp/inputs :param request: :return: JSON response with all erp inputs """ From 9a5ec88c52dba3cbf6b63c772930de9b22dd65aa Mon Sep 17 00:00:00 2001 From: Hallie Dunham <70401017+hdunham@users.noreply.github.com> Date: Mon, 22 Jul 2024 11:35:44 -0600 Subject: [PATCH 20/24] make error messages from erp help, inputs, and outputs endpoints align with url name --- resilience_stats/urls_v3plus.py | 2 +- resilience_stats/views.py | 43 +++++++++++++++++++++++---------- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/resilience_stats/urls_v3plus.py b/resilience_stats/urls_v3plus.py index 977b28031..9438b8960 100644 --- a/resilience_stats/urls_v3plus.py +++ b/resilience_stats/urls_v3plus.py @@ -6,7 +6,7 @@ urlpatterns = [ re_path(r'^erp/(?P[0-9a-f-]+)/results/?$', views.erp_results), re_path(r'^erp/help/?$', views.erp_help), - re_path(r'^erp/inputs/?$', views.erp_help), + re_path(r'^erp/inputs/?$', views.erp_inputs), re_path(r'^erp/outputs/?$', views.erp_outputs), re_path(r'^erp/chp_defaults/?$', views.erp_chp_prime_gen_defaults), ] diff --git a/resilience_stats/views.py b/resilience_stats/views.py index 5719567d5..d5d3b7add 100644 --- a/resilience_stats/views.py +++ b/resilience_stats/views.py @@ -104,28 +104,45 @@ def erp_results(request, run_uuid): resp['status'] = 'Error' return JsonResponse(resp, status=500) +def get_erp_inputs_info(): + d = dict() + d["reopt_run_uuid"] = ERPMeta.info_dict(ERPMeta)["reopt_run_uuid"] + # do models need to be passed in as arg? + d[ERPOutageInputs.key] = ERPOutageInputs.info_dict(ERPOutageInputs) + d[ERPPVInputs.key] = ERPPVInputs.info_dict(ERPPVInputs) + d[ERPWindInputs.key] = ERPWindInputs.info_dict(ERPWindInputs) + d[ERPElectricStorageInputs.key] = ERPElectricStorageInputs.info_dict(ERPElectricStorageInputs) + d[ERPGeneratorInputs.key] = ERPGeneratorInputs.info_dict(ERPGeneratorInputs) + d[ERPPrimeGeneratorInputs.key] = ERPPrimeGeneratorInputs.info_dict(ERPPrimeGeneratorInputs) + #TODO: add wind once implemented + return JsonResponse(d) + def erp_help(request): """ - Served at host/erp/help and host/erp/inputs + Served at host/erp/help :param request: :return: JSON response with all erp inputs """ try: - d = dict() - d["reopt_run_uuid"] = ERPMeta.info_dict(ERPMeta)["reopt_run_uuid"] - # do models need to be passed in as arg? - d[ERPOutageInputs.key] = ERPOutageInputs.info_dict(ERPOutageInputs) - d[ERPPVInputs.key] = ERPPVInputs.info_dict(ERPPVInputs) - d[ERPWindInputs.key] = ERPWindInputs.info_dict(ERPWindInputs) - d[ERPElectricStorageInputs.key] = ERPElectricStorageInputs.info_dict(ERPElectricStorageInputs) - d[ERPGeneratorInputs.key] = ERPGeneratorInputs.info_dict(ERPGeneratorInputs) - d[ERPPrimeGeneratorInputs.key] = ERPPrimeGeneratorInputs.info_dict(ERPPrimeGeneratorInputs) - #TODO: add wind once implemented - return JsonResponse(d) + resp = get_erp_inputs_info() + return resp except Exception as e: return JsonResponse({"Error": "Unexpected error in ERP help endpoint: {}".format(e.args[0])}, status=500) +def erp_inputs(request): + """ + Served at host/erp/inputs + :param request: + :return: JSON response with all erp inputs + """ + try: + resp = get_erp_inputs_info() + return resp + + except Exception as e: + return JsonResponse({"Error": "Unexpected error in ERP inputs endpoint: {}".format(e.args[0])}, status=500) + def erp_outputs(request): """ Served at host/erp/outputs @@ -137,7 +154,7 @@ def erp_outputs(request): return JsonResponse(d) except Exception as e: - return JsonResponse({"Error": "Unexpected error in ERP help endpoint: {}".format(e.args[0])}, status=500) + return JsonResponse({"Error": "Unexpected error in ERP outputs endpoint: {}".format(e.args[0])}, status=500) def erp_chp_prime_gen_defaults(request): prime_mover = str(request.GET.get('prime_mover')) From 4ac54c46740c65027685a69642d7cf4f30d0e80d Mon Sep 17 00:00:00 2001 From: Hallie Dunham <70401017+hdunham@users.noreply.github.com> Date: Mon, 22 Jul 2024 11:39:30 -0600 Subject: [PATCH 21/24] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b96257ff..e48cbdd7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ Classify the change according to the following categories: ##### Removed ### Patches -## v3.9.3 +## Develop ### Minor Updates #### Changed - Set **reopt_version** in **APIMeta** and **ERPMeta** programatically based on actual REopt.jl package version in Julia environment instead of hardcoded so doesn't need to be updated by hand From 1abc1d7c1fd4a038adc000613a32f646fbbd0135 Mon Sep 17 00:00:00 2001 From: Hallie Dunham <70401017+hdunham@users.noreply.github.com> Date: Mon, 22 Jul 2024 14:15:34 -0600 Subject: [PATCH 22/24] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aca87568b..6fa40b7fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ Classify the change according to the following categories: ##### Removed ### Patches -## Develop +## v3.9.3 ### Minor Updates #### Added - `/erp/inputs` endpoint (calls `erp_help()`, same as `/erp/help`) From 3710afff685149f5340508033d537bb64b0544e2 Mon Sep 17 00:00:00 2001 From: Hallie Dunham <70401017+hdunham@users.noreply.github.com> Date: Mon, 29 Jul 2024 14:03:36 -0700 Subject: [PATCH 23/24] Revert "Update CHANGELOG.md" This reverts commit 1abc1d7c1fd4a038adc000613a32f646fbbd0135. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fa40b7fa..aca87568b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ Classify the change according to the following categories: ##### Removed ### Patches -## v3.9.3 +## Develop ### Minor Updates #### Added - `/erp/inputs` endpoint (calls `erp_help()`, same as `/erp/help`) From 4ec5df99b8fd68bf462d9809b1e088c860c9ba8b Mon Sep 17 00:00:00 2001 From: Hallie Dunham <70401017+hdunham@users.noreply.github.com> Date: Mon, 29 Jul 2024 16:56:33 -0700 Subject: [PATCH 24/24] Reapply "Update CHANGELOG.md" This reverts commit 3710afff685149f5340508033d537bb64b0544e2. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aca87568b..6fa40b7fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ Classify the change according to the following categories: ##### Removed ### Patches -## Develop +## v3.9.3 ### Minor Updates #### Added - `/erp/inputs` endpoint (calls `erp_help()`, same as `/erp/help`)