From a7bea3f50bf05b103e059ddb410fb1213e5725e6 Mon Sep 17 00:00:00 2001 From: zolanaj Date: Fri, 19 Aug 2022 16:15:02 -0600 Subject: [PATCH 01/53] Add HotThermalStorageInputs to models.py --- job/models.py | 139 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) diff --git a/job/models.py b/job/models.py index da8336e8c..6c214e402 100644 --- a/job/models.py +++ b/job/models.py @@ -3175,6 +3175,145 @@ def clean(self): # default = list, # ) +class HotThermalStorageInputs(BaseModel, models.Model): + key = "HotThermalStorage" + + meta = models.OneToOneField( + APIMeta, + on_delete=models.CASCADE, + related_name="HotThermalStorageInputs", + primary_key=True + ) + + min_gal = models.FloatField( + validators=[ + MinValueValidator(0), + MaxValueValidator(1.0e9) + ], + null=True, + blank=True, + default=0.0, + help_text="Minimum TES volume (energy) size constraint for optimization" + ) + max_gal = models.FloatField( + validators=[ + MinValueValidator(0), + MaxValueValidator(1.0e9) + ], + blank=True, + default=0.0, + help_text="Maximum TES volume (energy) size constraint for optimization. Set to zero to disable storage" + ) + hot_supply_water_temp_degF = models.FloatField( + validators=[ + MinValueValidator(40.0), + MaxValueValidator(210.0) + ], + blank=True, + default=180.0, + help_text="Hot-side supply water temperature from HotTES (top of tank) to the heating load" + ) + cooled_return_water_temp_degF = models.FloatField( + validators=[ + MinValueValidator(33.0), + MaxValueValidator(200.0) + ], + blank=True, + default=160.0, + help_text="Cold-side return water temperature from the heating load to the HotTES (bottom of tank)" + ) + internal_efficiency_pct = models.FloatField( + validators=[ + MinValueValidator(0), + MaxValueValidator(1.0) + ], + blank=True, + default=0.999999, + help_text="Thermal losses due to mixing from thermal power entering or leaving tank" + ) + soc_min_pct = models.FloatField( + default=0.1, + validators=[ + MinValueValidator(0), + MaxValueValidator(1.0) + ], + blank=True, + help_text="Minimum allowable battery state of charge as fraction of energy capacity." + ) + soc_init_pct = models.FloatField( + validators=[ + MinValueValidator(0), + MaxValueValidator(1.0) + ], + default=0.5, + blank=True, + help_text="Battery state of charge at first hour of optimization as fraction of energy capacity." + ) + installed_cost_per_gal = models.FloatField( + default=1.5, + validators=[ + MinValueValidator(0), + MaxValueValidator(1.0e4) + ], + blank=True, + help_text="Installed Hot TES cost in $/gal" + ) + om_cost_per_gal = models.FloatField( + default=0.0, + validators=[ + MinValueValidator(0), + MaxValueValidator(1.0e3) + ], + blank=True, + help_text="Annual Hot TES operations and maintenance costs in $/gal" + ) + thermal_decay_rate_fraction = models.FloatField( + default=0.0004, + validators=[ + MinValueValidator(0), + MaxValueValidator(1.0) + ], + blank=True, + help_text="Thermal energy-based cost of TES (e.g. volume of the tank)" + ) + macrs_option_years = models.IntegerField( + default=MACRS_YEARS_CHOICES.FIVE, + choices=MACRS_YEARS_CHOICES.choices, + blank=True, + help_text="Duration over which accelerated depreciation will occur. Set to zero to disable" + ) + macrs_bonus_pct = models.FloatField( + default=1.0, + validators=[ + MinValueValidator(0), + MaxValueValidator(1) + ], + blank=True, + help_text="Percent of upfront project costs to depreciate in year one in addition to scheduled depreciation" + ) + total_itc_pct = models.FloatField( + default=0.0, + validators=[ + MinValueValidator(0), + MaxValueValidator(1) + ], + blank=True, + help_text="Total investment tax credit in percent applied toward capital costs" + ) + total_rebate_per_kwh = models.FloatField( + default=0.0, + validators=[ + MinValueValidator(0), + MaxValueValidator(1.0e9) + ], + blank=True, + help_text="Rebate based on installed energy capacity" + ) + + def clean(self): + # perform custom validation here. + pass + class SpaceHeatingLoadInputs(BaseModel, models.Model): From 36f11a67e6699e702e73f782a3e64863dd975b93 Mon Sep 17 00:00:00 2001 From: zolanaj Date: Thu, 1 Sep 2022 10:08:24 -0600 Subject: [PATCH 02/53] Add HotThermalStorageOutputs to models.py --- job/models.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/job/models.py b/job/models.py index 6c214e402..90630caf0 100644 --- a/job/models.py +++ b/job/models.py @@ -3314,6 +3314,28 @@ def clean(self): # perform custom validation here. pass +class HotThermalStorageOutputs(BaseModel, models.Model): + key = "HotThermalStorageOutputs" + + meta = models.OneToOneField( + APIMeta, + on_delete=models.CASCADE, + related_name="HotThermalStorageOutputs", + primary_key=True + ) + size_gal = models.FloatField(null=True, blank=True) + year_one_soc_series_pct = ArrayField( + models.FloatField(null=True, blank=True), + default = list, + ) + year_one_thermal_production_mmbtu_per_hour = ArrayField( + models.FloatField(null=True, blank=True), + default = list, + ) + + def clean(self): + # perform custom validation here. + pass class SpaceHeatingLoadInputs(BaseModel, models.Model): From 3d2e50dde9a1cb945b7bbc5c05443122257dacb7 Mon Sep 17 00:00:00 2001 From: zolanaj Date: Thu, 1 Sep 2022 21:09:15 -0600 Subject: [PATCH 03/53] fix merge bugs --- job/validators.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/job/validators.py b/job/validators.py index a52d7c209..091fcb88a 100644 --- a/job/validators.py +++ b/job/validators.py @@ -30,7 +30,8 @@ import logging import pandas as pd from job.models import MAX_BIG_NUMBER, APIMeta, ExistingBoilerInputs, UserProvidedMeta, SiteInputs, Settings, ElectricLoadInputs, ElectricTariffInputs, \ - FinancialInputs, BaseModel, Message, ElectricUtilityInputs, PVInputs, ElectricStorageInputs, GeneratorInputs, WindInputs, SpaceHeatingLoadInputs + FinancialInputs, BaseModel, Message, ElectricUtilityInputs, PVInputs, ElectricStorageInputs, GeneratorInputs, WindInputs, SpaceHeatingLoadInputs, \ + DomesticHotWaterLoadInputs from django.core.exceptions import ValidationError from pyproj import Proj from typing import Tuple @@ -260,18 +261,10 @@ def update_pv_defaults_offgrid(self): self.models["PV"].operating_reserve_required_pct = 0.0 else: self.models["PV"].operating_reserve_required_pct = 0.25 - - if self.models["Settings"].off_grid_flag==True: - pvmodel.__setattr__("can_net_meter", False) - pvmodel.__setattr__("can_wholesale", False) - pvmodel.__setattr__("can_export_beyond_nem_limit", False) - if pvmodel.__getattribute__("operating_reserve_required_pct") == None: # user provided no value - pvmodel.__setattr__("operating_reserve_required_pct", 0.25) - else: - pvmodel.__setattr__("operating_reserve_required_pct", 0.0) # override any user provided values if "PV" in self.models.keys(): # single PV cross_clean_pv(self.models["PV"]) + update_pv_defaults_offgrid(self) if len(self.pvnames) > 0: # multiple PV for pvname in self.pvnames: From 459eca03b8bc52d0ac27657e71990000c65c3d15 Mon Sep 17 00:00:00 2001 From: zolanaj Date: Thu, 1 Sep 2022 21:11:01 -0600 Subject: [PATCH 04/53] add wind to off_grid_validators.json post --- job/test/posts/off_grid_validations.json | 1 + job/test/test_validator.py | 23 ----------------------- 2 files changed, 1 insertion(+), 23 deletions(-) diff --git a/job/test/posts/off_grid_validations.json b/job/test/posts/off_grid_validations.json index 06e1e2d3c..b7498424b 100644 --- a/job/test/posts/off_grid_validations.json +++ b/job/test/posts/off_grid_validations.json @@ -7,6 +7,7 @@ "latitude": 34.5794343 }, "PV": {}, + "Wind": {}, "Generator": { "installed_cost_per_kw": 700, "min_kw": 100, diff --git a/job/test/test_validator.py b/job/test/test_validator.py index 5045055de..91340483c 100644 --- a/job/test/test_validator.py +++ b/job/test/test_validator.py @@ -117,29 +117,6 @@ def test_bad_blended_profile_inputs(self): validator.validation_errors['ElectricLoad']['blended_doe_reference_percents'][0]) def test_off_grid_defaults_overrides(self): - post = { - "Settings":{ - "off_grid_flag": True - }, - "Site": { - "longitude": -118.1164613, - "latitude": 34.5794343 - }, - "PV": {}, - "Generator": { - "min_kw": 100, - "max_kw": 100 - }, - "ElectricLoad": { - "doe_reference_name": "RetailStore", - "city": "LosAngeles" - }, - "Wind": {}, - "ElectricStorage": {}, - "Financial": {}, - "APIMeta": {} - } - post_file = os.path.join('job', 'test', 'posts', 'off_grid_validations.json') post = json.load(open(post_file, 'r')) From 05de23e73877687dc61f3d1061c1b68658362c4b Mon Sep 17 00:00:00 2001 From: zolanaj Date: Fri, 2 Sep 2022 16:20:26 -0600 Subject: [PATCH 05/53] Add HotThermalStorage to functions that create inputs/outputs --- job/models.py | 3 +++ job/src/process_results.py | 4 +++- job/validators.py | 5 +++-- job/views.py | 10 +++++++++- 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/job/models.py b/job/models.py index ca9159058..b5793efaa 100644 --- a/job/models.py +++ b/job/models.py @@ -4268,6 +4268,9 @@ def filter_none_and_empty_array(d:dict): try: d["DomesticHotWaterLoad"] = filter_none_and_empty_array(meta.DomesticHotWaterLoadInputs.dict) except: pass + try: d["HotThermalStorage"] = filter_none_and_empty_array(meta.HotThermalStorageInputs.dict) + except: pass + return d ''' diff --git a/job/src/process_results.py b/job/src/process_results.py index eb881a433..4f565ad4b 100644 --- a/job/src/process_results.py +++ b/job/src/process_results.py @@ -30,7 +30,7 @@ from job.models import FinancialOutputs, APIMeta, PVOutputs, ElectricStorageOutputs, ElectricTariffOutputs, SiteOutputs,\ ElectricUtilityOutputs, GeneratorOutputs, ElectricLoadOutputs, WindOutputs, FinancialInputs, ElectricUtilityInputs, \ - ExistingBoilerOutputs + ExistingBoilerOutputs, HotThermalStorageOutputs import logging log = logging.getLogger(__name__) @@ -64,6 +64,8 @@ def process_results(results: dict, run_uuid: str) -> None: # BoilerOutputs.create(meta=meta, **results["Boiler"]).save() if "ExistingBoiler" in results.keys(): ExistingBoilerOutputs.create(meta=meta, **results["ExistingBoiler"]).save() + if "HotThermalStorage" in results.keys(): + HotThermalStorageOutputs.create(meta=meta, **results["HotThermalStorage"]).save() # TODO process rest of results diff --git a/job/validators.py b/job/validators.py index 4af8ea90e..0829ed60e 100644 --- a/job/validators.py +++ b/job/validators.py @@ -31,7 +31,7 @@ import pandas as pd from job.models import MAX_BIG_NUMBER, APIMeta, ExistingBoilerInputs, UserProvidedMeta, SiteInputs, Settings, ElectricLoadInputs, ElectricTariffInputs, \ FinancialInputs, BaseModel, Message, ElectricUtilityInputs, PVInputs, ElectricStorageInputs, GeneratorInputs, WindInputs, SpaceHeatingLoadInputs, \ - DomesticHotWaterLoadInputs + DomesticHotWaterLoadInputs, HotThermalStorageInputs from django.core.exceptions import ValidationError from pyproj import Proj from typing import Tuple @@ -96,7 +96,8 @@ def __init__(self, raw_inputs: dict): WindInputs, ExistingBoilerInputs, SpaceHeatingLoadInputs, - DomesticHotWaterLoadInputs + DomesticHotWaterLoadInputs, + HotThermalStorageInputs ) self.pvnames = [] on_grid_required_object_names = [ diff --git a/job/views.py b/job/views.py index afe491f08..d384719a9 100644 --- a/job/views.py +++ b/job/views.py @@ -36,7 +36,8 @@ from job.models import Settings, PVInputs, ElectricStorageInputs, WindInputs, GeneratorInputs, ElectricLoadInputs,\ ElectricTariffInputs, ElectricUtilityInputs, SpaceHeatingLoadInputs, PVOutputs, ElectricStorageOutputs, WindOutputs, \ ExistingBoilerInputs, GeneratorOutputs, ElectricTariffOutputs, ElectricUtilityOutputs, ElectricLoadOutputs, \ - ExistingBoilerOutputs, DomesticHotWaterLoadInputs, SiteInputs, SiteOutputs, APIMeta, UserProvidedMeta + ExistingBoilerOutputs, DomesticHotWaterLoadInputs, SiteInputs, SiteOutputs, APIMeta, UserProvidedMeta,\ + HotThermalStorageInputs, HotThermalStorageOutputs def make_error_resp(msg): @@ -63,6 +64,7 @@ def help(request): d["Generator"] = GeneratorInputs.info_dict(GeneratorInputs) d["ExistingBoiler"] = ExistingBoilerInputs.info_dict(ExistingBoilerInputs) # d["Boiler"] = BoilerInputs.info_dict(BoilerInputs) + d["HotThermalStorage"] = HotThermalStorageInputs.info_dict(HotThermalStorageInputs) d["SpaceHeatingLoad"] = SpaceHeatingLoadInputs.info_dict(SpaceHeatingLoadInputs) d["DomesticHotWaterLoad"] = DomesticHotWaterLoadInputs.info_dict(DomesticHotWaterLoadInputs) d["Site"] = SiteInputs.info_dict(SiteInputs) @@ -101,6 +103,7 @@ def outputs(request): d["Generator"] = GeneratorOutputs.info_dict(GeneratorOutputs) d["ExistingBoiler"] = ExistingBoilerOutputs.info_dict(ExistingBoilerOutputs) # d["Boiler"] = BoilerOutputs.info_dict(BoilerOutputs) + d["HotThermalStorage"] = HotThermalStorageOutputs.info_dict(HotThermalStorageOutputs) d["Site"] = SiteOutputs.info_dict(SiteOutputs) return JsonResponse(d) @@ -193,6 +196,9 @@ def results(request, run_uuid): # try: r["inputs"]["Boiler"] = meta.BoilerInputs.dict # except: pass + try: r["inputs"]["HotThermalStorage"] = meta.HotThermalStorageInputs.dict + except: pass + try: r["inputs"]["SpaceHeatingLoad"] = meta.SpaceHeatingLoadInputs.dict except: pass @@ -235,6 +241,8 @@ def results(request, run_uuid): except: pass # try: r["outputs"]["Boiler"] = meta.BoilerOutputs.dict # except: pass + try: r["outputs"]["HotThermalStorage"] = meta.HotThermalStorageOutputs.dict + except: pass for d in r["outputs"].values(): if isinstance(d, dict): From e6035e97d3f529e17f8b29dc2c8a7ca82ca8cfcf Mon Sep 17 00:00:00 2001 From: zolanaj Date: Tue, 6 Sep 2022 13:29:03 -0600 Subject: [PATCH 06/53] add HotThermalStorage to .json --- job/test/posts/existing_boiler.json | 3 +++ job/test/test_validator.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/job/test/posts/existing_boiler.json b/job/test/posts/existing_boiler.json index 1738372d2..1626ac0be 100644 --- a/job/test/posts/existing_boiler.json +++ b/job/test/posts/existing_boiler.json @@ -6,6 +6,9 @@ "ExistingBoiler": { "fuel_cost_per_mmbtu": 0.5 }, + "HotThermalStorage":{ + "max_gal":2500 + }, "ElectricLoad": { "doe_reference_name": "Hospital", "annual_kwh": 8760000.0 diff --git a/job/test/test_validator.py b/job/test/test_validator.py index 91340483c..23ae427dc 100644 --- a/job/test/test_validator.py +++ b/job/test/test_validator.py @@ -197,6 +197,9 @@ def existing_boiler_validation(self): 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 From 1eb53c7a090413783bfbdcfd67eaa90311489356 Mon Sep 17 00:00:00 2001 From: zolanaj Date: Tue, 6 Sep 2022 13:43:41 -0600 Subject: [PATCH 07/53] Create 0009_hotthermalstorageinputs_hotthermalstorageoutputs.py --- ...lstorageinputs_hotthermalstorageoutputs.py | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 job/migrations/0009_hotthermalstorageinputs_hotthermalstorageoutputs.py diff --git a/job/migrations/0009_hotthermalstorageinputs_hotthermalstorageoutputs.py b/job/migrations/0009_hotthermalstorageinputs_hotthermalstorageoutputs.py new file mode 100644 index 000000000..6520dfa13 --- /dev/null +++ b/job/migrations/0009_hotthermalstorageinputs_hotthermalstorageoutputs.py @@ -0,0 +1,48 @@ +# Generated by Django 4.0.6 on 2022-09-06 19:40 + +import django.contrib.postgres.fields +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion +import job.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('job', '0008_domestichotwaterloadinputs_existingboilerinputs_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='HotThermalStorageInputs', + fields=[ + ('meta', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='HotThermalStorageInputs', serialize=False, to='job.apimeta')), + ('min_gal', models.FloatField(blank=True, default=0.0, help_text='Minimum TES volume (energy) size constraint for optimization', null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000000000.0)])), + ('max_gal', models.FloatField(blank=True, default=0.0, help_text='Maximum TES volume (energy) size constraint for optimization. Set to zero to disable storage', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000000000.0)])), + ('hot_supply_water_temp_degF', models.FloatField(blank=True, default=180.0, help_text='Hot-side supply water temperature from HotTES (top of tank) to the heating load', validators=[django.core.validators.MinValueValidator(40.0), django.core.validators.MaxValueValidator(210.0)])), + ('cooled_return_water_temp_degF', models.FloatField(blank=True, default=160.0, help_text='Cold-side return water temperature from the heating load to the HotTES (bottom of tank)', validators=[django.core.validators.MinValueValidator(33.0), django.core.validators.MaxValueValidator(200.0)])), + ('internal_efficiency_pct', models.FloatField(blank=True, default=0.999999, help_text='Thermal losses due to mixing from thermal power entering or leaving tank', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1.0)])), + ('soc_min_pct', models.FloatField(blank=True, default=0.1, help_text='Minimum allowable battery state of charge as fraction of energy capacity.', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1.0)])), + ('soc_init_pct', models.FloatField(blank=True, default=0.5, help_text='Battery state of charge at first hour of optimization as fraction of energy capacity.', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1.0)])), + ('installed_cost_per_gal', models.FloatField(blank=True, default=1.5, help_text='Installed Hot TES cost in $/gal', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10000.0)])), + ('om_cost_per_gal', models.FloatField(blank=True, default=0.0, help_text='Annual Hot TES operations and maintenance costs in $/gal', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000.0)])), + ('thermal_decay_rate_fraction', models.FloatField(blank=True, default=0.0004, help_text='Thermal energy-based cost of TES (e.g. volume of the tank)', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1.0)])), + ('macrs_option_years', models.IntegerField(blank=True, choices=[(0, 'Zero'), (5, 'Five'), (7, 'Seven')], default=5, help_text='Duration over which accelerated depreciation will occur. Set to zero to disable')), + ('macrs_bonus_pct', models.FloatField(blank=True, default=1.0, help_text='Percent of upfront project costs to depreciate in year one in addition to scheduled depreciation', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1)])), + ('total_itc_pct', models.FloatField(blank=True, default=0.0, help_text='Total investment tax credit in percent applied toward capital costs', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1)])), + ('total_rebate_per_kwh', models.FloatField(blank=True, default=0.0, help_text='Rebate based on installed energy capacity', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000000000.0)])), + ], + bases=(job.models.BaseModel, models.Model), + ), + migrations.CreateModel( + name='HotThermalStorageOutputs', + fields=[ + ('meta', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='HotThermalStorageOutputs', serialize=False, to='job.apimeta')), + ('size_gal', models.FloatField(blank=True, null=True)), + ('year_one_soc_series_pct', django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, size=None)), + ('year_one_thermal_production_mmbtu_per_hour', django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, size=None)), + ], + bases=(job.models.BaseModel, models.Model), + ), + ] From 0b15e53c6d6fe12a5e45773cacc7b33ca5369a42 Mon Sep 17 00:00:00 2001 From: zolanaj Date: Tue, 6 Sep 2022 13:44:58 -0600 Subject: [PATCH 08/53] Update CHANGELOG.md --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0528685c..a88b83c5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,7 +27,7 @@ Classify the change according to the following categories: ### Patches -## Develop - 2022-09-01 +## Develop - 2022-09-06 ### Minor Updates ##### Fixed - Require ElectricTariff key in inputs when **Settings.off_grid_flag** is false @@ -43,6 +43,8 @@ Classify the change according to the following categories: - `0005_boilerinputs....` file used to add new models to the db - `job/` endpoint: Add inputs and validation to model off-grid wind In `job/models.py`: +- added **HotThermalStorageInputs** model +- added **HotThermalStorageOutputs** model - added **ExistingBoilerInputs** model - added **ExistingBoilerOutputs** model - added **SpaceHeatingLoadInputs** model @@ -68,6 +70,7 @@ In `job/models.py`: - **FinancialOutputs** - add `breakeven_cost_of_emissions_reduction_per_tonnes_CO2` In `job/process_results.py`: +- add **HotThermalStorageOutputs** - add **ExistingBoilerOutputs** In `job/test/test_job_endpoint.py`: - test that AVERT and EASIUR defaults for emissions inputs not provided by user are passed back from REopt.jl and saved in database From fedb8f86aecb4c0275b92476403ed67460ff9f9d Mon Sep 17 00:00:00 2001 From: "Rathod, Bhavesh" Date: Mon, 26 Sep 2022 09:54:06 -0400 Subject: [PATCH 09/53] Return REopt errors warns back to user --- job/migrations/0009_messagesoutputs.py | 25 +++++++++++++ job/models.py | 25 +++++++++++-- job/src/process_results.py | 20 +++++++---- job/src/run_jump_model.py | 8 +++-- job/views.py | 5 ++- julia_src/Manifest.toml | 10 +++--- julia_src/http.jl | 50 ++++++++++++++++---------- 7 files changed, 109 insertions(+), 34 deletions(-) create mode 100644 job/migrations/0009_messagesoutputs.py diff --git a/job/migrations/0009_messagesoutputs.py b/job/migrations/0009_messagesoutputs.py new file mode 100644 index 000000000..d528e9597 --- /dev/null +++ b/job/migrations/0009_messagesoutputs.py @@ -0,0 +1,25 @@ +# Generated by Django 4.0.6 on 2022-09-20 01:03 + +import django.contrib.postgres.fields +from django.db import migrations, models +import django.db.models.deletion +import job.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('job', '0008_domestichotwaterloadinputs_existingboilerinputs_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='MessagesOutputs', + fields=[ + ('meta', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='MessagesOutputs', serialize=False, to='job.apimeta')), + ('errors', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(blank=True, null=True), default=list, size=None)), + ('warnings', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(blank=True, null=True), default=list, size=None)), + ], + bases=(job.models.BaseModel, models.Model), + ), + ] diff --git a/job/models.py b/job/models.py index 9597c49e9..0d0ac9516 100644 --- a/job/models.py +++ b/job/models.py @@ -3583,6 +3583,29 @@ def clean(self): # perform custom validation here. pass +class MessagesOutputs(BaseModel, models.Model): + + key = "Messages" + meta = models.OneToOneField( + APIMeta, + on_delete=models.CASCADE, + related_name="MessagesOutputs", + primary_key=True + ) + + errors = ArrayField( + models.TextField(null=True, blank=True), + default = list, + ) + + warnings = ArrayField( + models.TextField(null=True, blank=True), + default = list, + ) + + def clean(self): + pass + # # Uncomment to enable Boiler functionality # class BoilerInputs(BaseModel, models.Model): # key = "Boiler" @@ -4044,8 +4067,6 @@ def clean(self): pass -# TODO Add domestic hot water input model. - def get_input_dict_from_run_uuid(run_uuid:str): """ Construct the input dict for REopt.run_reopt diff --git a/job/src/process_results.py b/job/src/process_results.py index 449583608..26cd49e44 100644 --- a/job/src/process_results.py +++ b/job/src/process_results.py @@ -27,7 +27,7 @@ # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED # OF THE POSSIBILITY OF SUCH DAMAGE. # ********************************************************************************* -from job.models import FinancialOutputs, APIMeta, PVOutputs, ElectricStorageOutputs, ElectricTariffOutputs, SiteOutputs,\ +from job.models import MessagesOutputs, FinancialOutputs, APIMeta, PVOutputs, ElectricStorageOutputs, ElectricTariffOutputs, SiteOutputs,\ ElectricUtilityOutputs, GeneratorOutputs, ElectricLoadOutputs, WindOutputs, FinancialInputs, ElectricUtilityInputs, ExistingBoilerOutputs import logging log = logging.getLogger(__name__) @@ -41,11 +41,17 @@ def process_results(results: dict, run_uuid: str) -> None: meta = APIMeta.objects.get(run_uuid=run_uuid) meta.status = results.get("status") meta.save(update_fields=["status"]) - FinancialOutputs.create(meta=meta, **results["Financial"]).save() - ElectricTariffOutputs.create(meta=meta, **results["ElectricTariff"]).save() - ElectricUtilityOutputs.create(meta=meta, **results["ElectricUtility"]).save() - ElectricLoadOutputs.create(meta=meta, **results["ElectricLoad"]).save() - SiteOutputs.create(meta=meta, **results["Site"]).save() + + if "Financial" in results.keys(): + FinancialOutputs.create(meta=meta, **results["Financial"]).save() + if "ElectricTariff" in results.keys(): + ElectricTariffOutputs.create(meta=meta, **results["ElectricTariff"]).save() + if "ElectricUtility" in results.keys(): + ElectricUtilityOutputs.create(meta=meta, **results["ElectricUtility"]).save() + if "ElectricLoad" in results.keys(): + ElectricLoadOutputs.create(meta=meta, **results["ElectricLoad"]).save() + if "Site" in results.keys(): + SiteOutputs.create(meta=meta, **results["Site"]).save() if "PV" in results.keys(): if isinstance(results["PV"], dict): PVOutputs.create(meta=meta, **results["PV"]).save() @@ -62,6 +68,8 @@ def process_results(results: dict, run_uuid: str) -> None: # BoilerOutputs.create(meta=meta, **results["Boiler"]).save() if "ExistingBoiler" in results.keys(): ExistingBoilerOutputs.create(meta=meta, **results["ExistingBoiler"]).save() + if "Messages" in results.keys(): + MessagesOutputs.create(meta=meta, **results["Messages"]).save() # TODO process rest of results diff --git a/job/src/run_jump_model.py b/job/src/run_jump_model.py index 46a8faf9d..5b8f346d9 100644 --- a/job/src/run_jump_model.py +++ b/job/src/run_jump_model.py @@ -93,7 +93,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"] - inputs_with_defaults_set_in_julia = response_json["inputs_with_defaults_set_in_julia"] + # inputs_with_defaults_set_in_julia = response_json["inputs_with_defaults_set_in_julia"] time_dict["pyjulia_run_reopt_seconds"] = time.time() - t_start results.update(time_dict) @@ -121,12 +121,16 @@ def run_jump_model(run_uuid): msg = "Optimization exceeded timeout: {} seconds.".format(data["Settings"]["timeout_seconds"]) logger.info(msg) raise OptimizationTimeout(task=name, message=msg, run_uuid=run_uuid, user_uuid=user_uuid) + elif status.strip().lower() == 'error': + msg = "Optimization errored out." + logger.info(msg) elif status.strip().lower() != 'optimal': logger.error("REopt status not optimal. Raising NotOptimal Exception.") raise NotOptimal(task=name, run_uuid=run_uuid, status=status.strip(), user_uuid=user_uuid) profiler.profileEnd() # TODO save profile times - update_inputs_in_database(inputs_with_defaults_set_in_julia, run_uuid) + # update_inputs_in_database(inputs_with_defaults_set_in_julia, run_uuid) + print(results.keys()) process_results(results, run_uuid) return True diff --git a/job/views.py b/job/views.py index 381773650..1f1309903 100644 --- a/job/views.py +++ b/job/views.py @@ -35,7 +35,7 @@ from reo.exceptions import UnexpectedError from job.models import Settings, PVInputs, ElectricStorageInputs, WindInputs, GeneratorInputs, ElectricLoadInputs,\ ElectricTariffInputs, ElectricUtilityInputs, SpaceHeatingLoadInputs, PVOutputs, ElectricStorageOutputs, WindOutputs, ExistingBoilerInputs,\ - GeneratorOutputs, ElectricTariffOutputs, ElectricUtilityOutputs, ElectricLoadOutputs, ExistingBoilerOutputs, \ + GeneratorOutputs, ElectricTariffOutputs, ElectricUtilityOutputs, ElectricLoadOutputs, ExistingBoilerOutputs, MessagesOutputs, \ DomesticHotWaterLoadInputs, SiteInputs, SiteOutputs, APIMeta, UserProvidedMeta @@ -101,6 +101,7 @@ def outputs(request): d["Generator"] = GeneratorOutputs.info_dict(GeneratorOutputs) d["ExistingBoiler"] = ExistingBoilerOutputs.info_dict(ExistingBoilerOutputs) # d["Boiler"] = BoilerOutputs.info_dict(BoilerOutputs) + d["Messages"] = MessagesOutputs.info_dict(MessagesOutputs) return JsonResponse(d) except Exception as e: @@ -234,6 +235,8 @@ def results(request, run_uuid): except: pass # try: r["outputs"]["Boiler"] = meta.BoilerOutputs.dict # except: pass + try: r["outputs"]["Messages"] = meta.MessagesOutputs.dict + except: pass for d in r["outputs"].values(): if isinstance(d, dict): diff --git a/julia_src/Manifest.toml b/julia_src/Manifest.toml index 8ca3de58b..32cb012e9 100644 --- a/julia_src/Manifest.toml +++ b/julia_src/Manifest.toml @@ -1,6 +1,6 @@ # This file is machine-generated - editing it directly is not advised -julia_version = "1.7.2" +julia_version = "1.7.0" manifest_format = "2.0" [[deps.AbstractFFTs]] @@ -280,9 +280,9 @@ version = "0.16.10" [[deps.HDF5_jll]] deps = ["Artifacts", "JLLWrappers", "LibCURL_jll", "Libdl", "OpenSSL_jll", "Pkg", "Zlib_jll"] -git-tree-sha1 = "c003b31e2e818bc512b0ff99d7dce03b0c1359f5" +git-tree-sha1 = "4cc2bb72df6ff40b055295fdef6d92955f9dede8" uuid = "0234f1f7-429e-5d53-9886-15a909be8d59" -version = "1.12.2+1" +version = "1.12.2+2" [[deps.HTTP]] deps = ["Base64", "Dates", "IniFile", "Logging", "MbedTLS", "NetworkOptions", "Sockets", "URIs"] @@ -566,7 +566,9 @@ uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" [[deps.REopt]] deps = ["ArchGDAL", "Dates", "DelimitedFiles", "HTTP", "JLD", "JSON", "JuMP", "LinDistFlow", "LinearAlgebra", "Logging", "MathOptInterface", "Requires", "Roots", "Statistics", "TestEnv"] -git-tree-sha1 = "f20da365c1cf263e3bfe0b1453eb7c6212df30d1" +git-tree-sha1 = "ae218eb799031206600e2e7af89bf751586a0615" +repo-rev = "error-msgs" +repo-url = "https://github.com/NREL/REopt.jl.git" uuid = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6" version = "0.18.1" diff --git a/julia_src/http.jl b/julia_src/http.jl index 3e044b3c6..89b4a5f53 100644 --- a/julia_src/http.jl +++ b/julia_src/http.jl @@ -68,25 +68,33 @@ function reopt(req::HTTP.Request) error_response = Dict() results = Dict() inputs_with_defaults_set_in_julia = Dict() + model_inputs = nothing try + results = reoptjl.run_reopt(ms, d) model_inputs = reoptjl.REoptInputs(d) - results = reoptjl.run_reopt(ms, model_inputs) - inputs_with_defaults_from_easiur = [ - :NOx_grid_cost_per_tonne, :SO2_grid_cost_per_tonne, :PM25_grid_cost_per_tonne, - :NOx_onsite_fuelburn_cost_per_tonne, :SO2_onsite_fuelburn_cost_per_tonne, :PM25_onsite_fuelburn_cost_per_tonne, - :NOx_cost_escalation_pct, :SO2_cost_escalation_pct, :PM25_cost_escalation_pct - ] - inputs_with_defaults_from_avert = [ - :emissions_factor_series_lb_CO2_per_kwh, :emissions_factor_series_lb_NOx_per_kwh, - :emissions_factor_series_lb_SO2_per_kwh, :emissions_factor_series_lb_PM25_per_kwh - ] - inputs_with_defaults_set_in_julia = Dict( - "Financial" => Dict(key=>getfield(model_inputs.s.financial, key) for key in inputs_with_defaults_from_easiur), - "ElectricUtility" => Dict(key=>getfield(model_inputs.s.electric_utility, key) for key in inputs_with_defaults_from_avert) - ) + + if !isnothing(model_inputs) + inputs_with_defaults_from_easiur = [ + :NOx_grid_cost_per_tonne, :SO2_grid_cost_per_tonne, :PM25_grid_cost_per_tonne, + :NOx_onsite_fuelburn_cost_per_tonne, :SO2_onsite_fuelburn_cost_per_tonne, :PM25_onsite_fuelburn_cost_per_tonne, + :NOx_cost_escalation_pct, :SO2_cost_escalation_pct, :PM25_cost_escalation_pct + ] + inputs_with_defaults_from_avert = [ + :emissions_factor_series_lb_CO2_per_kwh, :emissions_factor_series_lb_NOx_per_kwh, + :emissions_factor_series_lb_SO2_per_kwh, :emissions_factor_series_lb_PM25_per_kwh + ] + inputs_with_defaults_set_in_julia = Dict( + "Financial" => Dict(key=>getfield(model_inputs.s.financial, key) for key in inputs_with_defaults_from_easiur), + "ElectricUtility" => Dict(key=>getfield(model_inputs.s.electric_utility, key) for key in inputs_with_defaults_from_avert) + ) + end catch e - @error "Something went wrong in the Julia code!" exception=(e, catch_backtrace()) - error_response["error"] = sprint(showerror, e) + if collect(keys(results)) == ["Messages","status"] + nothing + else + @error "Something went wrong in the Julia code!" exception=(e, catch_backtrace()) + error_response["error"] = sprint(showerror, e) + end end if typeof(ms) <: AbstractArray finalize(backend(ms[1])) @@ -96,11 +104,15 @@ function reopt(req::HTTP.Request) end GC.gc() if isempty(error_response) - @info "REopt model solved with status $(results["status"])." response = Dict( - "results" => results, - "inputs_with_defaults_set_in_julia" => inputs_with_defaults_set_in_julia + "results" => results ) + if collect(keys(results)) == ["Messages","status"] + @info "REopt model solved with an error, see Messages for more information." + else + @info "REopt model solved with status $(results["status"])." + response["inputs_with_defaults_set_in_julia"] = inputs_with_defaults_set_in_julia + end return HTTP.Response(200, JSON.json(response)) else @info "An error occured in the Julia code." From 83b5ea54662ee6d723543ce539d3cc89d9da23c0 Mon Sep 17 00:00:00 2001 From: "Rathod, Bhavesh" Date: Wed, 28 Sep 2022 10:35:42 -0400 Subject: [PATCH 10/53] Update lingering old default fields, rm print statements and uncomment needed functionality --- job/models.py | 25 +++++++++++++++---------- job/src/run_jump_model.py | 7 ++++--- job/validators.py | 3 --- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/job/models.py b/job/models.py index 0d0ac9516..ad9d529da 100644 --- a/job/models.py +++ b/job/models.py @@ -617,7 +617,7 @@ class FinancialInputs(BaseModel, models.Model): help_text="Analysis period in years. Must be integer." ) elec_cost_escalation_pct = models.FloatField( - default=0.023, + default=0.019, validators=[ MinValueValidator(-1), MaxValueValidator(1) @@ -626,7 +626,7 @@ class FinancialInputs(BaseModel, models.Model): help_text="Annual nominal utility electricity cost escalation rate." ) offtaker_discount_pct = models.FloatField( - default=0.083, + default=0.0564, validators=[ MinValueValidator(0), MaxValueValidator(1) @@ -654,7 +654,7 @@ class FinancialInputs(BaseModel, models.Model): help_text="Annual nominal O&M cost escalation rate" ) owner_discount_pct = models.FloatField( - default=0.083, + default=0.0564, validators=[ MinValueValidator(0), MaxValueValidator(1) @@ -2070,7 +2070,7 @@ class PV_LOCATION_CHOICES(models.TextChoices): help_text="Maximum PV size constraint for optimization (upper bound on additional capacity beyond existing_kw). Set to zero to disable PV" ) installed_cost_per_kw = models.FloatField( - default=1600, + default=1592, validators=[ MinValueValidator(0), MaxValueValidator(1.0e5) @@ -2079,7 +2079,7 @@ class PV_LOCATION_CHOICES(models.TextChoices): help_text="Installed PV cost in $/kW" ) om_cost_per_kw = models.FloatField( - default=16, + default=17, validators=[ MinValueValidator(0), MaxValueValidator(1.0e3) @@ -2497,7 +2497,7 @@ class WIND_SIZE_CLASS_CHOICES(models.TextChoices): help_text="Installed cost in $/kW" ) om_cost_per_kw = models.FloatField( - default=16, + default=35, validators=[ MinValueValidator(0), MaxValueValidator(1.0e3) @@ -2703,6 +2703,11 @@ class WIND_SIZE_CLASS_CHOICES(models.TextChoices): "Required operating reserves applied to each timestep as a fraction of wind generation serving load in that timestep." ) + # By default prodfactor is [], which causes errors in REopt. Should we either make prod factor not default to anything or handle it separately in REopt? + def clean(self): + if self.prod_factor_series == []: + self.prod_factor_series = scalar_to_vector([0.0]) + class WindOutputs(BaseModel, models.Model): key = "WindOutputs" @@ -2825,7 +2830,7 @@ class ElectricStorageInputs(BaseModel, models.Model): help_text="Flag to set whether the battery can be charged from the grid, or just onsite generation." ) installed_cost_per_kw = models.FloatField( - default=840.0, + default=775.0, validators=[ MinValueValidator(0), MaxValueValidator(1.0e4) @@ -2834,7 +2839,7 @@ class ElectricStorageInputs(BaseModel, models.Model): help_text="Total upfront battery power capacity costs (e.g. inverter and balance of power systems)" ) installed_cost_per_kwh = models.FloatField( - default=420.0, + default=388.0, validators=[ MinValueValidator(0), MaxValueValidator(1.0e4) @@ -2843,7 +2848,7 @@ class ElectricStorageInputs(BaseModel, models.Model): help_text="Total upfront battery costs" ) replace_cost_per_kw = models.FloatField( - default=410.0, + default=440.0, validators=[ MinValueValidator(0), MaxValueValidator(1.0e4) @@ -2852,7 +2857,7 @@ class ElectricStorageInputs(BaseModel, models.Model): help_text="Battery power capacity replacement cost at time of replacement year" ) replace_cost_per_kwh = models.FloatField( - default=200.0, + default=220.0, validators=[ MinValueValidator(0), MaxValueValidator(1.0e4) diff --git a/job/src/run_jump_model.py b/job/src/run_jump_model.py index 5b8f346d9..380a06c0e 100644 --- a/job/src/run_jump_model.py +++ b/job/src/run_jump_model.py @@ -93,7 +93,8 @@ 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"] - # inputs_with_defaults_set_in_julia = response_json["inputs_with_defaults_set_in_julia"] + 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 results.update(time_dict) @@ -130,7 +131,7 @@ def run_jump_model(run_uuid): profiler.profileEnd() # TODO save profile times - # update_inputs_in_database(inputs_with_defaults_set_in_julia, run_uuid) - print(results.keys()) + if status.strip().lower() != 'error': + update_inputs_in_database(inputs_with_defaults_set_in_julia, run_uuid) process_results(results, run_uuid) return True diff --git a/job/validators.py b/job/validators.py index 1c80bcbdb..0a2c221c7 100644 --- a/job/validators.py +++ b/job/validators.py @@ -499,14 +499,11 @@ def update_pv_defaults_offgrid(self): # If empty key is provided, then check if doe_reference_names are provided in ElectricLoad if self.models["SpaceHeatingLoad"].doe_reference_name == None and not self.models["SpaceHeatingLoad"].blended_doe_reference_names: if self.models["ElectricLoad"].doe_reference_name != "": - print("**check 1") self.models["SpaceHeatingLoad"].__setattr__("doe_reference_name", self.models["ElectricLoad"].__getattribute__("doe_reference_name")) elif len(self.models["ElectricLoad"].blended_doe_reference_names) > 0: - print("**check 2") self.models["SpaceHeatingLoad"].__setattr__("blended_doe_reference_names", self.models["ElectricLoad"].__getattribute__("blended_doe_reference_names")) self.models["SpaceHeatingLoad"].__setattr__("blended_doe_reference_percents", self.models["ElectricLoad"].__getattribute__("blended_doe_reference_percents")) else: - print("**check 3") self.add_validation_error("SpaceHeatingLoad", "doe_reference_name", f"Must provide DOE commercial reference building profiles either under SpaceHeatingLoad or ElectricLoad") From 3378d432182e58371b622c71fa15fcf52b871c04 Mon Sep 17 00:00:00 2001 From: "Rathod, Bhavesh" Date: Mon, 10 Oct 2022 15:06:39 -0400 Subject: [PATCH 11/53] add test for superset of inputs and update defaults --- ...geinputs_installed_cost_per_kw_and_more.py | 64 +++++ job/test/posts/all_inputs_test.json | 270 ++++++++++++++++++ job/test/test_job_endpoint.py | 19 ++ 3 files changed, 353 insertions(+) create mode 100644 job/migrations/0010_alter_electricstorageinputs_installed_cost_per_kw_and_more.py create mode 100644 job/test/posts/all_inputs_test.json diff --git a/job/migrations/0010_alter_electricstorageinputs_installed_cost_per_kw_and_more.py b/job/migrations/0010_alter_electricstorageinputs_installed_cost_per_kw_and_more.py new file mode 100644 index 000000000..7abb02b70 --- /dev/null +++ b/job/migrations/0010_alter_electricstorageinputs_installed_cost_per_kw_and_more.py @@ -0,0 +1,64 @@ +# Generated by Django 4.0.6 on 2022-10-10 18:46 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('job', '0009_messagesoutputs'), + ] + + operations = [ + migrations.AlterField( + model_name='electricstorageinputs', + name='installed_cost_per_kw', + field=models.FloatField(blank=True, default=775.0, help_text='Total upfront battery power capacity costs (e.g. inverter and balance of power systems)', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10000.0)]), + ), + migrations.AlterField( + model_name='electricstorageinputs', + name='installed_cost_per_kwh', + field=models.FloatField(blank=True, default=388.0, help_text='Total upfront battery costs', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10000.0)]), + ), + migrations.AlterField( + model_name='electricstorageinputs', + name='replace_cost_per_kw', + field=models.FloatField(blank=True, default=440.0, help_text='Battery power capacity replacement cost at time of replacement year', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10000.0)]), + ), + migrations.AlterField( + model_name='electricstorageinputs', + name='replace_cost_per_kwh', + field=models.FloatField(blank=True, default=220.0, help_text='Battery energy capacity replacement cost at time of replacement year', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10000.0)]), + ), + migrations.AlterField( + model_name='financialinputs', + name='elec_cost_escalation_pct', + field=models.FloatField(blank=True, default=0.019, help_text='Annual nominal utility electricity cost escalation rate.', validators=[django.core.validators.MinValueValidator(-1), django.core.validators.MaxValueValidator(1)]), + ), + migrations.AlterField( + model_name='financialinputs', + name='offtaker_discount_pct', + field=models.FloatField(blank=True, default=0.0564, help_text='Nominal energy offtaker discount rate. In single ownership model the offtaker is also the generation owner.', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1)]), + ), + migrations.AlterField( + model_name='financialinputs', + name='owner_discount_pct', + field=models.FloatField(blank=True, default=0.0564, help_text='Nominal generation owner discount rate. Used for two party financing model. In two party ownership model the offtaker does not own the generator(s).', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1)]), + ), + migrations.AlterField( + model_name='pvinputs', + name='installed_cost_per_kw', + field=models.FloatField(blank=True, default=1592, help_text='Installed PV cost in $/kW', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100000.0)]), + ), + migrations.AlterField( + model_name='pvinputs', + name='om_cost_per_kw', + field=models.FloatField(blank=True, default=17, help_text='Annual PV operations and maintenance costs in $/kW', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000.0)]), + ), + migrations.AlterField( + model_name='windinputs', + name='om_cost_per_kw', + field=models.FloatField(blank=True, default=35, help_text='Annual operations and maintenance costs in $/kW', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000.0)]), + ), + ] diff --git a/job/test/posts/all_inputs_test.json b/job/test/posts/all_inputs_test.json new file mode 100644 index 000000000..94fc52edb --- /dev/null +++ b/job/test/posts/all_inputs_test.json @@ -0,0 +1,270 @@ +{ + "Financial": { + "analysis_years": 25, + "elec_cost_escalation_pct": 0.023, + "offtaker_discount_pct": 0.083, + "offtaker_tax_pct": 0.26, + "om_cost_escalation_pct": 0.025, + "owner_discount_pct": 0.083, + "owner_tax_pct": 0.26, + "third_party_ownership": false, + "value_of_lost_load_per_kwh": 100.0, + "microgrid_upgrade_cost_pct": 0.3, + "offgrid_other_capital_costs": 0.0, + "offgrid_other_annual_costs": 0.0, + "CO2_cost_per_tonne": 51.0, + "CO2_cost_escalation_pct": 0.042173, + "NOx_grid_cost_per_tonne": null, + "SO2_grid_cost_per_tonne": null, + "PM25_grid_cost_per_tonne": null, + "NOx_onsite_fuelburn_cost_per_tonne": null, + "SO2_onsite_fuelburn_cost_per_tonne": null, + "PM25_onsite_fuelburn_cost_per_tonne": null, + "NOx_cost_escalation_pct": null, + "SO2_cost_escalation_pct": null, + "PM25_cost_escalation_pct": null + }, + "ElectricLoad": { + "annual_kwh": 100000.0, + "doe_reference_name": "MidriseApartment", + "year": 2017, + "monthly_totals_kwh": [], + "loads_kw": [], + "critical_loads_kw": [], + "loads_kw_is_net": true, + "critical_loads_kw_is_net": false, + "critical_load_pct": 0.5, + "operating_reserve_required_pct": 0.0, + "min_load_met_annual_pct": 1.0, + "blended_doe_reference_names": [], + "blended_doe_reference_percents": [] + }, + "Site": { + "latitude": 38.96345294964369, + "longitude": -77.38406208630109, + "land_acres": null, + "roof_squarefeet": null, + "CO2_emissions_reduction_min_pct": null, + "CO2_emissions_reduction_max_pct": null, + "renewable_electricity_min_pct": null, + "renewable_electricity_max_pct": null, + "include_exported_elec_emissions_in_total": true, + "include_exported_renewable_electricity_in_total": true + }, + "Settings": { + "timeout_seconds": 420, + "time_steps_per_hour": 1, + "optimality_tolerance": 0.001, + "add_soc_incentive": true, + "run_bau": true, + "include_climate_in_objective": false, + "include_health_in_objective": false, + "off_grid_flag": false + }, + "PV": { + "name": "PV", + "existing_kw": 0.0, + "min_kw": 0.0, + "max_kw": 1000000000.0, + "installed_cost_per_kw": 1600.0, + "om_cost_per_kw": 16.0, + "macrs_option_years": 5, + "macrs_bonus_pct": 1.0, + "macrs_itc_reduction": 0.5, + "federal_itc_pct": 0.26, + "state_ibi_pct": 0.0, + "state_ibi_max": 10000000000.0, + "utility_ibi_pct": 0.0, + "utility_ibi_max": 10000000000.0, + "federal_rebate_per_kw": 0.0, + "state_rebate_per_kw": 0.0, + "state_rebate_max": 10000000000.0, + "utility_rebate_per_kw": 0.0, + "utility_rebate_max": 10000000000.0, + "production_incentive_per_kwh": 0.0, + "production_incentive_max_benefit": 1000000000.0, + "production_incentive_years": 1, + "production_incentive_max_kw": 1000000000.0, + "degradation_pct": 0.005, + "azimuth": 180.0, + "losses": 0.14, + "array_type": 1, + "module_type": 0, + "gcr": 0.4, + "dc_ac_ratio": 1.2, + "inv_eff": 0.96, + "radius": 0, + "tilt": 38.96345294964369, + "location": "both", + "prod_factor_series": [], + "can_net_meter": true, + "can_wholesale": true, + "can_export_beyond_nem_limit": true, + "can_curtail": true, + "operating_reserve_required_pct": 0.0 + }, + "Meta": { + "description": "", + "address": "" + }, + "ElectricTariff": { + "monthly_demand_rates": [], + "monthly_energy_rates": [], + "urdb_label": "632b4e5279d29ba1130491d1", + "urdb_response": null, + "urdb_rate_name": "", + "urdb_utility_name": "", + "blended_annual_demand_rate": null, + "blended_annual_energy_rate": null, + "wholesale_rate": [], + "export_rate_beyond_net_metering_limit": [], + "tou_energy_rates_per_kwh": [], + "add_monthly_rates_to_urdb_rate": false, + "add_tou_energy_rates_to_urdb_rate": false, + "coincident_peak_load_active_time_steps": [], + "coincident_peak_load_charge_per_kw": [] + }, + "ElectricUtility": { + "outage_start_time_step": null, + "outage_end_time_step": null, + "interconnection_limit_kw": 1000000000.0, + "net_metering_limit_kw": 0.0, + "emissions_region": "", + "emissions_factor_series_lb_CO2_per_kwh": [], + "emissions_factor_series_lb_NOx_per_kwh": [], + "emissions_factor_series_lb_SO2_per_kwh": [], + "emissions_factor_series_lb_PM25_per_kwh": [], + "emissions_factor_CO2_decrease_pct": 0.01174, + "emissions_factor_NOx_decrease_pct": 0.01174, + "emissions_factor_SO2_decrease_pct": 0.01174, + "emissions_factor_PM25_decrease_pct": 0.01174 + }, + "ElectricStorage": { + "min_kw": 0.0, + "max_kw": 1000000000.0, + "min_kwh": 0.0, + "max_kwh": 1000000.0, + "internal_efficiency_pct": 0.975, + "inverter_efficiency_pct": 0.96, + "rectifier_efficiency_pct": 0.96, + "soc_min_pct": 0.2, + "soc_init_pct": 0.5, + "can_grid_charge": true, + "installed_cost_per_kw": 840.0, + "installed_cost_per_kwh": 420.0, + "replace_cost_per_kw": 410.0, + "replace_cost_per_kwh": 200.0, + "inverter_replacement_year": 10, + "battery_replacement_year": 10, + "macrs_option_years": 7, + "macrs_bonus_pct": 1.0, + "macrs_itc_reduction": 0.5, + "total_itc_pct": 0.0, + "total_rebate_per_kw": 0.0, + "total_rebate_per_kwh": 0.0 + }, + "Generator": { + "existing_kw": 0.0, + "min_kw": 0.0, + "max_kw": 1000000000.0, + "installed_cost_per_kw": 500.0, + "om_cost_per_kw": 10.0, + "om_cost_per_kwh": 0.0, + "fuel_cost_per_gallon": 3.0, + "fuel_slope_gal_per_kwh": 0.076, + "fuel_intercept_gal_per_hr": 0.0, + "fuel_avail_gal": 660.0, + "min_turn_down_pct": 0.0, + "only_runs_during_grid_outage": true, + "sells_energy_back_to_grid": false, + "macrs_option_years": 0, + "macrs_bonus_pct": 1.0, + "macrs_itc_reduction": 0.0, + "federal_itc_pct": 0.0, + "state_ibi_pct": 0.0, + "state_ibi_max": 10000000000.0, + "utility_ibi_pct": 0.0, + "utility_ibi_max": 10000000000.0, + "federal_rebate_per_kw": 0.0, + "state_rebate_per_kw": 0.0, + "state_rebate_max": 10000000000.0, + "utility_rebate_per_kw": 0.0, + "utility_rebate_max": 10000000000.0, + "production_incentive_per_kwh": 0.0, + "production_incentive_max_benefit": 1000000000.0, + "production_incentive_years": 0, + "production_incentive_max_kw": 0.0, + "can_net_meter": false, + "can_wholesale": false, + "can_export_beyond_nem_limit": false, + "can_curtail": false, + "fuel_renewable_energy_pct": 0.0, + "emissions_factor_lb_CO2_per_gal": 22.51, + "emissions_factor_lb_NOx_per_gal": 0.0775544, + "emissions_factor_lb_SO2_per_gal": 0.040020476, + "emissions_factor_lb_PM25_per_gal": 0.0, + "replacement_year": 25, + "replace_cost_per_kw": 0.0 + }, + "Wind": { + "size_class": "", + "wind_meters_per_sec": [], + "wind_direction_degrees": [], + "temperature_celsius": [], + "pressure_atmospheres": [], + "min_kw": 0.0, + "max_kw": 1000000000.0, + "installed_cost_per_kw": 1600.0, + "om_cost_per_kw": 16.0, + "macrs_option_years": 5, + "macrs_bonus_pct": 1.0, + "macrs_itc_reduction": 0.5, + "federal_itc_pct": 0.26, + "state_ibi_pct": 0.0, + "state_ibi_max": 10000000000.0, + "utility_ibi_pct": 0.0, + "utility_ibi_max": 10000000000.0, + "federal_rebate_per_kw": 0.0, + "state_rebate_per_kw": 0.0, + "state_rebate_max": 10000000000.0, + "utility_rebate_per_kw": 0.0, + "utility_rebate_max": 10000000000.0, + "production_incentive_per_kwh": 0.0, + "production_incentive_max_benefit": 1000000000.0, + "production_incentive_years": 1, + "production_incentive_max_kw": 1000000000.0, + "prod_factor_series": [], + "can_net_meter": true, + "can_wholesale": true, + "can_export_beyond_nem_limit": true, + "can_curtail": true, + "operating_reserve_required_pct": 0.0 + }, + "ExistingBoiler": { + "production_type": "hot_water", + "max_thermal_factor_on_peak_load": 1.25, + "efficiency": 0.8, + "emissions_factor_lb_CO2_per_mmbtu": 116.9, + "emissions_factor_lb_NOx_per_mmbtu": 0.09139, + "emissions_factor_lb_SO2_per_mmbtu": 0.000578592, + "emissions_factor_lb_PM25_per_mmbtu": 0.007328833, + "fuel_cost_per_mmbtu": [], + "fuel_type": "natural_gas" + }, + "SpaceHeatingLoad": { + "annual_mmbtu": null, + "doe_reference_name": "MidriseApartment", + "monthly_mmbtu": [], + "fuel_loads_mmbtu_per_hour": [], + "blended_doe_reference_names": [], + "blended_doe_reference_percents": [] + }, + "DomesticHotWaterLoad": { + "annual_mmbtu": null, + "doe_reference_name": "MidriseApartment", + "monthly_mmbtu": [], + "fuel_loads_mmbtu_per_hour": [], + "blended_doe_reference_names": [], + "blended_doe_reference_percents": [] + } +} \ No newline at end of file diff --git a/job/test/test_job_endpoint.py b/job/test/test_job_endpoint.py index caa4ad874..29d0ef8c3 100644 --- a/job/test/test_job_endpoint.py +++ b/job/test/test_job_endpoint.py @@ -30,6 +30,7 @@ import json from tastypie.test import ResourceTestCaseMixin from django.test import TestCase # have to use unittest.TestCase to get tests to store to database, django.test.TestCase flushes db +import os import logging logging.disable(logging.CRITICAL) @@ -159,3 +160,21 @@ def test_off_grid_defaults(self): self.assertAlmostEqual(results["ElectricLoad"]["offgrid_load_met_pct"], 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_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. + """ + post_file = os.path.join('job', 'test', 'posts', 'all_inputs_test.json') + post = json.load(open(post_file, 'r')) + + resp = self.api_client.post('/dev/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'/dev/job/{run_uuid}/results') + r = json.loads(resp.content) + results = r["outputs"] + + assert(results["status"]=="optimal") \ No newline at end of file From 97364f06791a99af10ec1943aa096ddada0ff635 Mon Sep 17 00:00:00 2001 From: "Rathod, Bhavesh" Date: Mon, 10 Oct 2022 21:41:23 -0400 Subject: [PATCH 12/53] Add REopt warns/errors for requestor Return status 400 when REopt passes back an error --- job/migrations/0009_messagesoutputs.py | 25 -------- ...re.py => 0011_messagesoutputs_and_more.py} | 22 +++++-- job/models.py | 6 +- job/views.py | 3 + julia_src/Manifest.toml | 6 +- julia_src/http.jl | 64 ++++++++++++------- 6 files changed, 67 insertions(+), 59 deletions(-) delete mode 100644 job/migrations/0009_messagesoutputs.py rename job/migrations/{0010_alter_electricstorageinputs_installed_cost_per_kw_and_more.py => 0011_messagesoutputs_and_more.py} (78%) diff --git a/job/migrations/0009_messagesoutputs.py b/job/migrations/0009_messagesoutputs.py deleted file mode 100644 index d528e9597..000000000 --- a/job/migrations/0009_messagesoutputs.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 4.0.6 on 2022-09-20 01:03 - -import django.contrib.postgres.fields -from django.db import migrations, models -import django.db.models.deletion -import job.models - - -class Migration(migrations.Migration): - - dependencies = [ - ('job', '0008_domestichotwaterloadinputs_existingboilerinputs_and_more'), - ] - - operations = [ - migrations.CreateModel( - name='MessagesOutputs', - fields=[ - ('meta', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='MessagesOutputs', serialize=False, to='job.apimeta')), - ('errors', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(blank=True, null=True), default=list, size=None)), - ('warnings', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(blank=True, null=True), default=list, size=None)), - ], - bases=(job.models.BaseModel, models.Model), - ), - ] diff --git a/job/migrations/0010_alter_electricstorageinputs_installed_cost_per_kw_and_more.py b/job/migrations/0011_messagesoutputs_and_more.py similarity index 78% rename from job/migrations/0010_alter_electricstorageinputs_installed_cost_per_kw_and_more.py rename to job/migrations/0011_messagesoutputs_and_more.py index 7abb02b70..02f466ee1 100644 --- a/job/migrations/0010_alter_electricstorageinputs_installed_cost_per_kw_and_more.py +++ b/job/migrations/0011_messagesoutputs_and_more.py @@ -1,16 +1,28 @@ -# Generated by Django 4.0.6 on 2022-10-10 18:46 +# Generated by Django 4.0.6 on 2022-10-10 23:36 +import django.contrib.postgres.fields import django.core.validators from django.db import migrations, models +import django.db.models.deletion +import job.models class Migration(migrations.Migration): dependencies = [ - ('job', '0009_messagesoutputs'), + ('job', '0010_rename_prod_factor_series_pvinputs_production_factor_series_and_more'), ] operations = [ + migrations.CreateModel( + name='MessagesOutputs', + fields=[ + ('meta', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='MessagesOutputs', serialize=False, to='job.apimeta')), + ('errors', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(blank=True, null=True), default=list, size=None)), + ('warnings', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(blank=True, null=True), default=list, size=None)), + ], + bases=(job.models.BaseModel, models.Model), + ), migrations.AlterField( model_name='electricstorageinputs', name='installed_cost_per_kw', @@ -33,17 +45,17 @@ class Migration(migrations.Migration): ), migrations.AlterField( model_name='financialinputs', - name='elec_cost_escalation_pct', + name='elec_cost_escalation_rate_fraction', field=models.FloatField(blank=True, default=0.019, help_text='Annual nominal utility electricity cost escalation rate.', validators=[django.core.validators.MinValueValidator(-1), django.core.validators.MaxValueValidator(1)]), ), migrations.AlterField( model_name='financialinputs', - name='offtaker_discount_pct', + name='offtaker_discount_rate_fraction', field=models.FloatField(blank=True, default=0.0564, help_text='Nominal energy offtaker discount rate. In single ownership model the offtaker is also the generation owner.', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1)]), ), migrations.AlterField( model_name='financialinputs', - name='owner_discount_pct', + name='owner_discount_rate_fraction', field=models.FloatField(blank=True, default=0.0564, help_text='Nominal generation owner discount rate. Used for two party financing model. In two party ownership model the offtaker does not own the generator(s).', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1)]), ), migrations.AlterField( diff --git a/job/models.py b/job/models.py index 3c9392d0c..e41fab279 100644 --- a/job/models.py +++ b/job/models.py @@ -616,7 +616,7 @@ class FinancialInputs(BaseModel, models.Model): blank=True, help_text="Analysis period in years. Must be integer." ) - elec_cost_escalation_pct = models.FloatField( + elec_cost_escalation_rate_fraction = models.FloatField( default=0.019, validators=[ MinValueValidator(-1), @@ -625,7 +625,7 @@ class FinancialInputs(BaseModel, models.Model): blank=True, help_text="Annual nominal utility electricity cost escalation rate." ) - offtaker_discount_pct = models.FloatField( + offtaker_discount_rate_fraction = models.FloatField( default=0.0564, validators=[ MinValueValidator(0), @@ -653,7 +653,7 @@ class FinancialInputs(BaseModel, models.Model): blank=True, help_text="Annual nominal O&M cost escalation rate" ) - owner_discount_pct = models.FloatField( + owner_discount_rate_fraction = models.FloatField( default=0.0564, validators=[ MinValueValidator(0), diff --git a/job/views.py b/job/views.py index 1f1309903..d2aedc383 100644 --- a/job/views.py +++ b/job/views.py @@ -255,5 +255,8 @@ def results(request, run_uuid): err.save_to_db() resp = make_error_resp(err.message) return JsonResponse(resp, status=500) + + if meta.status == "error": + return JsonResponse(r, status=400) return JsonResponse(r) diff --git a/julia_src/Manifest.toml b/julia_src/Manifest.toml index 061fca47c..82e158c5b 100644 --- a/julia_src/Manifest.toml +++ b/julia_src/Manifest.toml @@ -1,6 +1,6 @@ # This file is machine-generated - editing it directly is not advised -julia_version = "1.7.1" +julia_version = "1.7.0" manifest_format = "2.0" [[deps.AbstractFFTs]] @@ -577,8 +577,8 @@ deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"] uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" [[deps.REopt]] -deps = ["ArchGDAL", "Dates", "DelimitedFiles", "HTTP", "JLD", "JSON", "JuMP", "LinDistFlow", "LinearAlgebra", "Logging", "MathOptInterface", "Requires", "Roots", "Statistics", "TestEnv"] -git-tree-sha1 = "ae218eb799031206600e2e7af89bf751586a0615" +deps = ["ArchGDAL", "CoolProp", "Dates", "DelimitedFiles", "HTTP", "JLD", "JSON", "JuMP", "LinDistFlow", "LinearAlgebra", "Logging", "MathOptInterface", "Requires", "Roots", "Statistics", "TestEnv"] +git-tree-sha1 = "800250e105b6b76cd70136654d280f28d4bbf84b" repo-rev = "error-msgs" repo-url = "https://github.com/NREL/REopt.jl.git" uuid = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6" diff --git a/julia_src/http.jl b/julia_src/http.jl index 89b4a5f53..265c67900 100644 --- a/julia_src/http.jl +++ b/julia_src/http.jl @@ -25,7 +25,11 @@ function job(req::HTTP.Request) Xpress.postsolve(optimizer.inner) if isempty(error_response) @info "REopt model solved with status $(results["status"])." - return HTTP.Response(200, JSON.json(results)) + if results["status"] == "error" + return HTTP.Response(400, JSON.json(results)) + else + return HTTP.Response(200, JSON.json(results)) + end else @info "An error occured in the Julia code." return HTTP.Response(500, JSON.json(error_response)) @@ -64,20 +68,29 @@ function reopt(req::HTTP.Request) ) ) end - @info "Starting REopt..." + @info "Starting REopt..." error_response = Dict() results = Dict() inputs_with_defaults_set_in_julia = Dict() model_inputs = nothing - try - results = reoptjl.run_reopt(ms, d) + # Catch handled/unhandled exceptions in data pre-processing, JuMP setup + try model_inputs = reoptjl.REoptInputs(d) - - if !isnothing(model_inputs) + catch e + @error "Something went wrong in the Julia code!" exception=(e, catch_backtrace()) + error_response["error"] = sprint(showerror, e) + end + + if isa(model_inputs, Dict) && model_inputs["status"] == "error" + results = model_inputs + else + # Catch handled/unhandled exceptions in optimization + try + results = reoptjl.run_reopt(ms, model_inputs) inputs_with_defaults_from_easiur = [ :NOx_grid_cost_per_tonne, :SO2_grid_cost_per_tonne, :PM25_grid_cost_per_tonne, :NOx_onsite_fuelburn_cost_per_tonne, :SO2_onsite_fuelburn_cost_per_tonne, :PM25_onsite_fuelburn_cost_per_tonne, - :NOx_cost_escalation_pct, :SO2_cost_escalation_pct, :PM25_cost_escalation_pct + :NOx_cost_escalation_rate_fraction, :SO2_cost_escalation_rate_fraction, :PM25_cost_escalation_rate_fraction ] inputs_with_defaults_from_avert = [ :emissions_factor_series_lb_CO2_per_kwh, :emissions_factor_series_lb_NOx_per_kwh, @@ -87,15 +100,12 @@ function reopt(req::HTTP.Request) "Financial" => Dict(key=>getfield(model_inputs.s.financial, key) for key in inputs_with_defaults_from_easiur), "ElectricUtility" => Dict(key=>getfield(model_inputs.s.electric_utility, key) for key in inputs_with_defaults_from_avert) ) + catch e + @error "Something q wrong in the Julia code!" exception=(e, catch_backtrace()) + error_response["error"] = sprint(showerror, e) # append instead of rewrite? end - catch e - if collect(keys(results)) == ["Messages","status"] - nothing - else - @error "Something went wrong in the Julia code!" exception=(e, catch_backtrace()) - error_response["error"] = sprint(showerror, e) - end - end + end + if typeof(ms) <: AbstractArray finalize(backend(ms[1])) finalize(backend(ms[2])) @@ -103,23 +113,31 @@ function reopt(req::HTTP.Request) finalize(backend(ms)) end GC.gc() + if isempty(error_response) - response = Dict( - "results" => results - ) - if collect(keys(results)) == ["Messages","status"] - @info "REopt model solved with an error, see Messages for more information." + @info "REopt model solved with status $(results["status"])." + if results["status"] == "error" + response = Dict( + "results" => results + ) + 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 - @info "REopt model solved with status $(results["status"])." - response["inputs_with_defaults_set_in_julia"] = inputs_with_defaults_set_in_julia + response = Dict( + "results" => results, + "inputs_with_defaults_set_in_julia" => inputs_with_defaults_set_in_julia + ) + return HTTP.Response(200, JSON.json(response)) end - return HTTP.Response(200, JSON.json(response)) else @info "An error occured in the Julia code." return HTTP.Response(500, JSON.json(error_response)) end end + function ghpghx(req::HTTP.Request) inputs_dict = JSON.parse(String(req.body)) @info "Starting GHPGHX" #with timeout of $(timeout) seconds..." From 36a0591b80bf264a0b8501cb1ac3e8a0b9965a3c Mon Sep 17 00:00:00 2001 From: "Rathod, Bhavesh" Date: Tue, 11 Oct 2022 15:18:50 -0400 Subject: [PATCH 13/53] add missing fields, update tests return job() function to its initial state --- ...ne_thermal_to_tes_series_mmbtu_per_hour.py | 19 ++++ ..._fuel_consumption_series_mmbtu_per_hour.py | 19 ++++ ...hermal_production_series_mmbtu_per_hour.py | 19 ++++ ...l_to_steamturbine_series_mmbtu_per_hour.py | 19 ++++ job/models.py | 25 +++- job/test/posts/all_inputs_test.json | 106 ++++++++--------- job/test/posts/handle_reopt_error.json | 18 +++ job/test/posts/off_grid_defaults.json | 18 +++ job/test/posts/pv_batt_emissions.json | 54 +++++++++ job/test/test_job_endpoint.py | 107 +++++------------- julia_src/Manifest.toml | 2 +- julia_src/http.jl | 6 +- 12 files changed, 271 insertions(+), 141 deletions(-) create mode 100644 job/migrations/0012_existingboileroutputs_year_one_thermal_to_tes_series_mmbtu_per_hour.py create mode 100644 job/migrations/0013_existingboileroutputs_year_one_fuel_consumption_series_mmbtu_per_hour.py create mode 100644 job/migrations/0014_existingboileroutputs_year_one_thermal_production_series_mmbtu_per_hour.py create mode 100644 job/migrations/0015_existingboileroutputs_year_one_thermal_to_steamturbine_series_mmbtu_per_hour.py create mode 100644 job/test/posts/handle_reopt_error.json create mode 100644 job/test/posts/off_grid_defaults.json create mode 100644 job/test/posts/pv_batt_emissions.json diff --git a/job/migrations/0012_existingboileroutputs_year_one_thermal_to_tes_series_mmbtu_per_hour.py b/job/migrations/0012_existingboileroutputs_year_one_thermal_to_tes_series_mmbtu_per_hour.py new file mode 100644 index 000000000..868677991 --- /dev/null +++ b/job/migrations/0012_existingboileroutputs_year_one_thermal_to_tes_series_mmbtu_per_hour.py @@ -0,0 +1,19 @@ +# Generated by Django 4.0.6 on 2022-10-11 18:23 + +import django.contrib.postgres.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('job', '0011_messagesoutputs_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='existingboileroutputs', + name='year_one_thermal_to_tes_series_mmbtu_per_hour', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, size=None), + ), + ] diff --git a/job/migrations/0013_existingboileroutputs_year_one_fuel_consumption_series_mmbtu_per_hour.py b/job/migrations/0013_existingboileroutputs_year_one_fuel_consumption_series_mmbtu_per_hour.py new file mode 100644 index 000000000..a6d0292f2 --- /dev/null +++ b/job/migrations/0013_existingboileroutputs_year_one_fuel_consumption_series_mmbtu_per_hour.py @@ -0,0 +1,19 @@ +# Generated by Django 4.0.6 on 2022-10-11 18:26 + +import django.contrib.postgres.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('job', '0012_existingboileroutputs_year_one_thermal_to_tes_series_mmbtu_per_hour'), + ] + + operations = [ + migrations.AddField( + model_name='existingboileroutputs', + name='year_one_fuel_consumption_series_mmbtu_per_hour', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, size=None), + ), + ] diff --git a/job/migrations/0014_existingboileroutputs_year_one_thermal_production_series_mmbtu_per_hour.py b/job/migrations/0014_existingboileroutputs_year_one_thermal_production_series_mmbtu_per_hour.py new file mode 100644 index 000000000..3bdec5149 --- /dev/null +++ b/job/migrations/0014_existingboileroutputs_year_one_thermal_production_series_mmbtu_per_hour.py @@ -0,0 +1,19 @@ +# Generated by Django 4.0.6 on 2022-10-11 18:30 + +import django.contrib.postgres.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('job', '0013_existingboileroutputs_year_one_fuel_consumption_series_mmbtu_per_hour'), + ] + + operations = [ + migrations.AddField( + model_name='existingboileroutputs', + name='year_one_thermal_production_series_mmbtu_per_hour', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, size=None), + ), + ] diff --git a/job/migrations/0015_existingboileroutputs_year_one_thermal_to_steamturbine_series_mmbtu_per_hour.py b/job/migrations/0015_existingboileroutputs_year_one_thermal_to_steamturbine_series_mmbtu_per_hour.py new file mode 100644 index 000000000..3a3d6b8c4 --- /dev/null +++ b/job/migrations/0015_existingboileroutputs_year_one_thermal_to_steamturbine_series_mmbtu_per_hour.py @@ -0,0 +1,19 @@ +# Generated by Django 4.0.6 on 2022-10-11 18:47 + +import django.contrib.postgres.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('job', '0014_existingboileroutputs_year_one_thermal_production_series_mmbtu_per_hour'), + ] + + operations = [ + migrations.AddField( + model_name='existingboileroutputs', + name='year_one_thermal_to_steamturbine_series_mmbtu_per_hour', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, size=None), + ), + ] diff --git a/job/models.py b/job/models.py index e41fab279..6498b9d3f 100644 --- a/job/models.py +++ b/job/models.py @@ -2703,11 +2703,6 @@ class WIND_SIZE_CLASS_CHOICES(models.TextChoices): "Required operating reserves applied to each timestep as a fraction of wind generation serving load in that timestep." ) - # By default prodfactor is [], which causes errors in REopt. Should we either make prod factor not default to anything or handle it separately in REopt? - def clean(self): - if self.prod_factor_series == []: - self.prod_factor_series = scalar_to_vector([0.0]) - class WindOutputs(BaseModel, models.Model): key = "WindOutputs" @@ -3569,6 +3564,21 @@ class ExistingBoilerOutputs(BaseModel, models.Model): year_one_thermal_production_mmbtu = models.FloatField(null=True, blank=True) year_one_fuel_cost_before_tax = models.FloatField(null=True, blank=True) thermal_to_tes_series_mmbtu_per_hour = models.FloatField(null=True, blank=True) + + year_one_thermal_to_steamturbine_series_mmbtu_per_hour = ArrayField( + models.FloatField(null=True, blank=True), + default = list, + ) + + year_one_thermal_production_series_mmbtu_per_hour = ArrayField( + models.FloatField(null=True, blank=True), + default = list, + ) + + year_one_fuel_consumption_series_mmbtu_per_hour = ArrayField( + models.FloatField(null=True, blank=True), + default = list, + ) thermal_to_tes_series_mmbtu_per_hour = ArrayField( models.FloatField(null=True, blank=True), default = list, @@ -3584,6 +3594,11 @@ class ExistingBoilerOutputs(BaseModel, models.Model): default = list, ) + year_one_thermal_to_tes_series_mmbtu_per_hour = ArrayField( + models.FloatField(null=True, blank=True), + default = list, + ) + def clean(self): # perform custom validation here. pass diff --git a/job/test/posts/all_inputs_test.json b/job/test/posts/all_inputs_test.json index 94fc52edb..d7f5ed65e 100644 --- a/job/test/posts/all_inputs_test.json +++ b/job/test/posts/all_inputs_test.json @@ -1,31 +1,31 @@ { "Financial": { "analysis_years": 25, - "elec_cost_escalation_pct": 0.023, - "offtaker_discount_pct": 0.083, - "offtaker_tax_pct": 0.26, - "om_cost_escalation_pct": 0.025, - "owner_discount_pct": 0.083, - "owner_tax_pct": 0.26, + "elec_cost_escalation_rate_fraction": 0.023, + "offtaker_discount_rate_fraction": 0.083, + "offtaker_tax_rate_fraction": 0.26, + "om_cost_escalation_rate_fraction": 0.025, + "owner_discount_rate_fraction": 0.083, + "owner_tax_rate_fraction": 0.26, "third_party_ownership": false, "value_of_lost_load_per_kwh": 100.0, - "microgrid_upgrade_cost_pct": 0.3, + "microgrid_upgrade_cost_fraction": 0.3, "offgrid_other_capital_costs": 0.0, "offgrid_other_annual_costs": 0.0, "CO2_cost_per_tonne": 51.0, - "CO2_cost_escalation_pct": 0.042173, + "CO2_cost_escalation_rate_fraction": 0.042173, "NOx_grid_cost_per_tonne": null, "SO2_grid_cost_per_tonne": null, "PM25_grid_cost_per_tonne": null, "NOx_onsite_fuelburn_cost_per_tonne": null, "SO2_onsite_fuelburn_cost_per_tonne": null, "PM25_onsite_fuelburn_cost_per_tonne": null, - "NOx_cost_escalation_pct": null, - "SO2_cost_escalation_pct": null, - "PM25_cost_escalation_pct": null + "NOx_cost_escalation_rate_fraction": null, + "SO2_cost_escalation_rate_fraction": null, + "PM25_cost_escalation_rate_fraction": null }, "ElectricLoad": { - "annual_kwh": 100000.0, + "annual_kwh": 190000.0, "doe_reference_name": "MidriseApartment", "year": 2017, "monthly_totals_kwh": [], @@ -33,9 +33,9 @@ "critical_loads_kw": [], "loads_kw_is_net": true, "critical_loads_kw_is_net": false, - "critical_load_pct": 0.5, - "operating_reserve_required_pct": 0.0, - "min_load_met_annual_pct": 1.0, + "critical_load_fraction": 0.5, + "operating_reserve_required_fraction": 0.0, + "min_load_met_annual_fraction": 1.0, "blended_doe_reference_names": [], "blended_doe_reference_percents": [] }, @@ -44,10 +44,10 @@ "longitude": -77.38406208630109, "land_acres": null, "roof_squarefeet": null, - "CO2_emissions_reduction_min_pct": null, - "CO2_emissions_reduction_max_pct": null, - "renewable_electricity_min_pct": null, - "renewable_electricity_max_pct": null, + "CO2_emissions_reduction_min_fraction": null, + "CO2_emissions_reduction_max_fraction": null, + "renewable_electricity_min_fraction": null, + "renewable_electricity_max_fraction": null, "include_exported_elec_emissions_in_total": true, "include_exported_renewable_electricity_in_total": true }, @@ -69,12 +69,12 @@ "installed_cost_per_kw": 1600.0, "om_cost_per_kw": 16.0, "macrs_option_years": 5, - "macrs_bonus_pct": 1.0, + "macrs_bonus_fraction": 1.0, "macrs_itc_reduction": 0.5, - "federal_itc_pct": 0.26, - "state_ibi_pct": 0.0, + "federal_itc_fraction": 0.26, + "state_ibi_fraction": 0.0, "state_ibi_max": 10000000000.0, - "utility_ibi_pct": 0.0, + "utility_ibi_fraction": 0.0, "utility_ibi_max": 10000000000.0, "federal_rebate_per_kw": 0.0, "state_rebate_per_kw": 0.0, @@ -85,7 +85,7 @@ "production_incentive_max_benefit": 1000000000.0, "production_incentive_years": 1, "production_incentive_max_kw": 1000000000.0, - "degradation_pct": 0.005, + "degradation_fraction": 0.005, "azimuth": 180.0, "losses": 0.14, "array_type": 1, @@ -96,12 +96,12 @@ "radius": 0, "tilt": 38.96345294964369, "location": "both", - "prod_factor_series": [], + "production_factor_series": [], "can_net_meter": true, "can_wholesale": true, "can_export_beyond_nem_limit": true, "can_curtail": true, - "operating_reserve_required_pct": 0.0 + "operating_reserve_required_fraction": 0.0 }, "Meta": { "description": "", @@ -111,7 +111,7 @@ "monthly_demand_rates": [], "monthly_energy_rates": [], "urdb_label": "632b4e5279d29ba1130491d1", - "urdb_response": null, + "urdb_response":null, "urdb_rate_name": "", "urdb_utility_name": "", "blended_annual_demand_rate": null, @@ -121,7 +121,7 @@ "tou_energy_rates_per_kwh": [], "add_monthly_rates_to_urdb_rate": false, "add_tou_energy_rates_to_urdb_rate": false, - "coincident_peak_load_active_time_steps": [], + "coincident_peak_load_active_time_steps": [[]], "coincident_peak_load_charge_per_kw": [] }, "ElectricUtility": { @@ -134,21 +134,21 @@ "emissions_factor_series_lb_NOx_per_kwh": [], "emissions_factor_series_lb_SO2_per_kwh": [], "emissions_factor_series_lb_PM25_per_kwh": [], - "emissions_factor_CO2_decrease_pct": 0.01174, - "emissions_factor_NOx_decrease_pct": 0.01174, - "emissions_factor_SO2_decrease_pct": 0.01174, - "emissions_factor_PM25_decrease_pct": 0.01174 + "emissions_factor_CO2_decrease_fraction": 0.01174, + "emissions_factor_NOx_decrease_fraction": 0.01174, + "emissions_factor_SO2_decrease_fraction": 0.01174, + "emissions_factor_PM25_decrease_fraction": 0.01174 }, "ElectricStorage": { "min_kw": 0.0, "max_kw": 1000000000.0, "min_kwh": 0.0, "max_kwh": 1000000.0, - "internal_efficiency_pct": 0.975, - "inverter_efficiency_pct": 0.96, - "rectifier_efficiency_pct": 0.96, - "soc_min_pct": 0.2, - "soc_init_pct": 0.5, + "internal_efficiency_fraction": 0.975, + "inverter_efficiency_fraction": 0.96, + "rectifier_efficiency_fraction": 0.96, + "soc_min_fraction": 0.2, + "soc_init_fraction": 0.5, "can_grid_charge": true, "installed_cost_per_kw": 840.0, "installed_cost_per_kwh": 420.0, @@ -157,9 +157,9 @@ "inverter_replacement_year": 10, "battery_replacement_year": 10, "macrs_option_years": 7, - "macrs_bonus_pct": 1.0, + "macrs_bonus_fraction": 1.0, "macrs_itc_reduction": 0.5, - "total_itc_pct": 0.0, + "total_itc_fraction": 0.0, "total_rebate_per_kw": 0.0, "total_rebate_per_kwh": 0.0 }, @@ -174,16 +174,16 @@ "fuel_slope_gal_per_kwh": 0.076, "fuel_intercept_gal_per_hr": 0.0, "fuel_avail_gal": 660.0, - "min_turn_down_pct": 0.0, + "min_turn_down_fraction": 0.0, "only_runs_during_grid_outage": true, "sells_energy_back_to_grid": false, "macrs_option_years": 0, - "macrs_bonus_pct": 1.0, + "macrs_bonus_fraction": 1.0, "macrs_itc_reduction": 0.0, - "federal_itc_pct": 0.0, - "state_ibi_pct": 0.0, + "federal_itc_fraction": 0.0, + "state_ibi_fraction": 0.0, "state_ibi_max": 10000000000.0, - "utility_ibi_pct": 0.0, + "utility_ibi_fraction": 0.0, "utility_ibi_max": 10000000000.0, "federal_rebate_per_kw": 0.0, "state_rebate_per_kw": 0.0, @@ -198,7 +198,7 @@ "can_wholesale": false, "can_export_beyond_nem_limit": false, "can_curtail": false, - "fuel_renewable_energy_pct": 0.0, + "fuel_renewable_energy_fraction": 0.0, "emissions_factor_lb_CO2_per_gal": 22.51, "emissions_factor_lb_NOx_per_gal": 0.0775544, "emissions_factor_lb_SO2_per_gal": 0.040020476, @@ -207,7 +207,7 @@ "replace_cost_per_kw": 0.0 }, "Wind": { - "size_class": "", + "size_class": "medium", "wind_meters_per_sec": [], "wind_direction_degrees": [], "temperature_celsius": [], @@ -217,12 +217,12 @@ "installed_cost_per_kw": 1600.0, "om_cost_per_kw": 16.0, "macrs_option_years": 5, - "macrs_bonus_pct": 1.0, + "macrs_bonus_fraction": 1.0, "macrs_itc_reduction": 0.5, - "federal_itc_pct": 0.26, - "state_ibi_pct": 0.0, + "federal_itc_fraction": 0.26, + "state_ibi_fraction": 0.0, "state_ibi_max": 10000000000.0, - "utility_ibi_pct": 0.0, + "utility_ibi_fraction": 0.0, "utility_ibi_max": 10000000000.0, "federal_rebate_per_kw": 0.0, "state_rebate_per_kw": 0.0, @@ -233,12 +233,12 @@ "production_incentive_max_benefit": 1000000000.0, "production_incentive_years": 1, "production_incentive_max_kw": 1000000000.0, - "prod_factor_series": [], + "production_factor_series": [], "can_net_meter": true, "can_wholesale": true, "can_export_beyond_nem_limit": true, "can_curtail": true, - "operating_reserve_required_pct": 0.0 + "operating_reserve_required_fraction": 0.0 }, "ExistingBoiler": { "production_type": "hot_water", @@ -248,7 +248,7 @@ "emissions_factor_lb_NOx_per_mmbtu": 0.09139, "emissions_factor_lb_SO2_per_mmbtu": 0.000578592, "emissions_factor_lb_PM25_per_mmbtu": 0.007328833, - "fuel_cost_per_mmbtu": [], + "fuel_cost_per_mmbtu": [0.0], "fuel_type": "natural_gas" }, "SpaceHeatingLoad": { diff --git a/job/test/posts/handle_reopt_error.json b/job/test/posts/handle_reopt_error.json new file mode 100644 index 000000000..08516ce3f --- /dev/null +++ b/job/test/posts/handle_reopt_error.json @@ -0,0 +1,18 @@ +{ + "Settings":{ + "optimality_tolerance":0.005, + "timeout_seconds":420, + "run_bau":false + }, + "ElectricTariff": { + "urdb_label": "539fc1a6ec4f024c27d8a8f7" + }, + "Site": { + "latitude": 65.0, + "longitude": -155.8394336 + }, + "ElectricLoad": { + "annual_kwh": 100000.0, + "doe_reference_name": "MidriseApartment" + } +} \ No newline at end of file diff --git a/job/test/posts/off_grid_defaults.json b/job/test/posts/off_grid_defaults.json new file mode 100644 index 000000000..89cc5b62d --- /dev/null +++ b/job/test/posts/off_grid_defaults.json @@ -0,0 +1,18 @@ +{ + "Settings":{ + "off_grid_flag": true, + "optimality_tolerance":0.05 + }, + "Site": { + "longitude": -118.1164613, + "latitude": 34.5794343 + }, + "PV": {}, + "ElectricStorage":{}, + "ElectricLoad": { + "doe_reference_name": "FlatLoad", + "annual_kwh": 8760.0, + "city": "LosAngeles", + "year": 2017 + } +} \ No newline at end of file diff --git a/job/test/posts/pv_batt_emissions.json b/job/test/posts/pv_batt_emissions.json new file mode 100644 index 000000000..0a6ba4bed --- /dev/null +++ b/job/test/posts/pv_batt_emissions.json @@ -0,0 +1,54 @@ +{ + "Site": { + "longitude": -118.1164613, + "latitude": 34.5794343, + "roof_squarefeet": 5000.0, + "land_acres": 1.0, + "node": 3 + }, + "PV": { + "macrs_bonus_fraction": 0.4, + "installed_cost_per_kw": 2000.0, + "tilt": 34.579, + "degradation_fraction": 0.005, + "macrs_option_years": 5, + "federal_itc_fraction": 0.3, + "module_type": 0, + "array_type": 1, + "om_cost_per_kw": 16.0, + "macrs_itc_reduction": 0.5, + "azimuth": 180.0, + "federal_rebate_per_kw": 350.0 + }, + "ElectricLoad": { + "doe_reference_name": "RetailStore", + "annual_kwh": 10000000.0, + "year": 2017 + }, + "ElectricStorage": { + "total_rebate_per_kw": 100.0, + "macrs_option_years": 5, + "can_grid_charge": true, + "macrs_bonus_fraction": 0.4, + "replace_cost_per_kw": 460.0, + "replace_cost_per_kwh": 230.0, + "installed_cost_per_kw": 1000.0, + "installed_cost_per_kwh": 500.0, + "total_itc_fraction": 0.0 + }, + "ElectricTariff": { + "urdb_label": "5ed6c1a15457a3367add15ae" + }, + "ElectricUtility": { + "emissions_factor_series_lb_NOx_per_kwh": 1 + }, + "Financial": { + "elec_cost_escalation_rate_fraction": 0.026, + "offtaker_discount_rate_fraction": 0.081, + "owner_discount_rate_fraction": 0.081, + "analysis_years": 20, + "offtaker_tax_rate_fraction": 0.4, + "owner_tax_rate_fraction": 0.4, + "om_cost_escalation_rate_fraction": 0.025 + } +} \ No newline at end of file diff --git a/job/test/test_job_endpoint.py b/job/test/test_job_endpoint.py index 9ef7af635..8d2576f89 100644 --- a/job/test/test_job_endpoint.py +++ b/job/test/test_job_endpoint.py @@ -27,11 +27,13 @@ # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED # OF THE POSSIBILITY OF SUCH DAMAGE. # ********************************************************************************* +from cProfile import run import json from tastypie.test import ResourceTestCaseMixin from django.test import TestCase # have to use unittest.TestCase to get tests to store to database, django.test.TestCase flushes db import os import logging +import requests logging.disable(logging.CRITICAL) @@ -42,62 +44,10 @@ def test_pv_battery_and_emissions_defaults_from_julia(self): Same test post as"Solar and Storage 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. """ - scenario = { - "Site": { - "longitude": -118.1164613, - "latitude": 34.5794343, - "roof_squarefeet": 5000.0, - "land_acres": 1.0, - "node": 3 - }, - "PV": { - "macrs_bonus_fraction": 0.4, - "installed_cost_per_kw": 2000.0, - "tilt": 34.579, - "degradation_fraction": 0.005, - "macrs_option_years": 5, - "federal_itc_fraction": 0.3, - "module_type": 0, - "array_type": 1, - "om_cost_per_kw": 16.0, - "macrs_itc_reduction": 0.5, - "azimuth": 180.0, - "federal_rebate_per_kw": 350.0 - }, - "ElectricLoad": { - "doe_reference_name": "RetailStore", - "annual_kwh": 10000000.0, - "year": 2017 - }, - "ElectricStorage": { - "total_rebate_per_kw": 100.0, - "macrs_option_years": 5, - "can_grid_charge": True, - "macrs_bonus_fraction": 0.4, - "replace_cost_per_kw": 460.0, - "replace_cost_per_kwh": 230.0, - "installed_cost_per_kw": 1000.0, - "installed_cost_per_kwh": 500.0, - "total_itc_fraction": 0.0 - }, - "ElectricTariff": { - "urdb_label": "5ed6c1a15457a3367add15ae" - }, - "ElectricUtility": { - "emissions_factor_series_lb_NOx_per_kwh": 1 - }, - "Financial": { - "elec_cost_escalation_rate_fraction": 0.026, - "offtaker_discount_rate_fraction": 0.081, - "owner_discount_rate_fraction": 0.081, - "analysis_years": 20, - "offtaker_tax_rate_fraction": 0.4, - "owner_tax_rate_fraction": 0.4, - "om_cost_escalation_rate_fraction": 0.025 - } - } - - resp = self.api_client.post('/dev/job/', format='json', data=scenario) + post_file = os.path.join('job', 'test', 'posts', 'pv_batt_emissions.json') + post = json.load(open(post_file, 'r')) + + resp = self.api_client.post('/dev/job/', format='json', data=post) self.assertHttpCreated(resp) r = json.loads(resp.content) run_uuid = r.get('run_uuid') @@ -126,26 +76,10 @@ def test_off_grid_defaults(self): """ Purpose of this test is to validate off-grid functionality and defaults in the API. """ - scenario = { - "Settings":{ - "off_grid_flag": True, - "optimality_tolerance":0.05 - }, - "Site": { - "longitude": -118.1164613, - "latitude": 34.5794343 - }, - "PV": {}, - "ElectricStorage":{}, - "ElectricLoad": { - "doe_reference_name": "FlatLoad", - "annual_kwh": 8760.0, - "city": "LosAngeles", - "year": 2017 - } - } - - resp = self.api_client.post('/dev/job/', format='json', data=scenario) + post_file = os.path.join('job', 'test', 'posts', 'off_grid_defaults.json') + post = json.load(open(post_file, 'r')) + + resp = self.api_client.post('/dev/job/', format='json', data=post) self.assertHttpCreated(resp) r = json.loads(resp.content) run_uuid = r.get('run_uuid') @@ -161,6 +95,24 @@ def test_off_grid_defaults(self): 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 process_reopt_error(self): + """ + Purpose of this test is to ensure REopt status 400 is returned using the job endpoint + """ + + post_file = os.path.join('job', 'test', 'posts', 'handle_reopt_error.json') + post = json.load(open(post_file, 'r')) + + resp = self.api_client.post('/dev/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'/dev/job/{run_uuid}/results') + r = json.loads(resp.content) + results = r["outputs"] + assert(resp.status_code==400) + 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. @@ -177,4 +129,5 @@ def test_superset_input_fields(self): r = json.loads(resp.content) results = r["outputs"] - assert(results["status"]=="optimal") \ No newline at end of file + self.assertAlmostEqual(results["Financial"]["npv"], 165.21, places=-2) + assert(resp.status_code==200) \ No newline at end of file diff --git a/julia_src/Manifest.toml b/julia_src/Manifest.toml index 82e158c5b..bd509d6e8 100644 --- a/julia_src/Manifest.toml +++ b/julia_src/Manifest.toml @@ -578,7 +578,7 @@ uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" [[deps.REopt]] deps = ["ArchGDAL", "CoolProp", "Dates", "DelimitedFiles", "HTTP", "JLD", "JSON", "JuMP", "LinDistFlow", "LinearAlgebra", "Logging", "MathOptInterface", "Requires", "Roots", "Statistics", "TestEnv"] -git-tree-sha1 = "800250e105b6b76cd70136654d280f28d4bbf84b" +git-tree-sha1 = "f76411c1b0bfa3fe47774abe5892bd7406e12862" repo-rev = "error-msgs" repo-url = "https://github.com/NREL/REopt.jl.git" uuid = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6" diff --git a/julia_src/http.jl b/julia_src/http.jl index 265c67900..d629aeb20 100644 --- a/julia_src/http.jl +++ b/julia_src/http.jl @@ -25,11 +25,7 @@ function job(req::HTTP.Request) Xpress.postsolve(optimizer.inner) if isempty(error_response) @info "REopt model solved with status $(results["status"])." - if results["status"] == "error" - return HTTP.Response(400, JSON.json(results)) - else - return HTTP.Response(200, JSON.json(results)) - end + return HTTP.Response(200, JSON.json(results)) else @info "An error occured in the Julia code." return HTTP.Response(500, JSON.json(error_response)) From f3e6336422f1d2cb579ce9d1e80f30dfc40b9772 Mon Sep 17 00:00:00 2001 From: hdunham <70401017+hdunham@users.noreply.github.com> Date: Mon, 17 Oct 2022 22:15:17 -0600 Subject: [PATCH 14/53] update test name --- job/test/test_job_endpoint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/job/test/test_job_endpoint.py b/job/test/test_job_endpoint.py index 8d2576f89..7712a239e 100644 --- a/job/test/test_job_endpoint.py +++ b/job/test/test_job_endpoint.py @@ -95,7 +95,7 @@ def test_off_grid_defaults(self): 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 process_reopt_error(self): + def test_process_reopt_error(self): """ Purpose of this test is to ensure REopt status 400 is returned using the job endpoint """ From cf8d0ddda38f3589977556cf4d3ecd9373418686 Mon Sep 17 00:00:00 2001 From: hdunham <70401017+hdunham@users.noreply.github.com> Date: Mon, 17 Oct 2022 22:17:34 -0600 Subject: [PATCH 15/53] update test_superset_input_fields docstring --- job/test/test_job_endpoint.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/job/test/test_job_endpoint.py b/job/test/test_job_endpoint.py index 7712a239e..86686b211 100644 --- a/job/test/test_job_endpoint.py +++ b/job/test/test_job_endpoint.py @@ -115,7 +115,8 @@ def test_process_reopt_error(self): 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. + 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. """ post_file = os.path.join('job', 'test', 'posts', 'all_inputs_test.json') post = json.load(open(post_file, 'r')) From ab2bc81ee3cff49ad29a017da55bc31bc87972fb Mon Sep 17 00:00:00 2001 From: hdunham <70401017+hdunham@users.noreply.github.com> Date: Mon, 17 Oct 2022 22:30:07 -0600 Subject: [PATCH 16/53] remove outdated todo --- job/models.py | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/job/models.py b/job/models.py index 6498b9d3f..837f22070 100644 --- a/job/models.py +++ b/job/models.py @@ -565,38 +565,6 @@ class SiteOutputs(BaseModel, models.Model): help_text="Percent reduction in total pounds of carbon dioxide emissions in the optimal case relative to the BAU case" ) -""" -# TODO should we move the emissions_calculator to Julia? -# Or is it supplanted by new emissions capabilities (not in develop/master as of 21.09.02)? - -class SiteOutputs(BaseModel, models.Model): - key = "SiteOutputs" - - meta = models.OneToOneField( - APIMeta, - on_delete=models.CASCADE, - primary_key=True, - related_name="SiteOutputs" - ) - - year_one_emissions_lb_C02 = models.FloatField( - null=True, blank=True, - help_text="Total equivalent pounds of carbon dioxide emitted from the site in the first year." - ) - year_one_emissions_bau_lb_C02 = models.FloatField( - null=True, blank=True, - help_text="Total equivalent pounds of carbon dioxide emittedf rom the site use in the first year in the BAU case." - ) - renewable_electricity_energy_fraction = models.FloatField( - null=True, blank=True, - help_text=("Portion of electrictrity use that is derived from on-site " - "renewable resource generation in year one. Calculated as " - "total PV and Wind generation in year one (including exports), " - "divided by the total annual load in year one.") - ) -""" - - class FinancialInputs(BaseModel, models.Model): key = "Financial" From 654793a3d72fd6f6445388eab94348ae4895f8e1 Mon Sep 17 00:00:00 2001 From: hdunham <70401017+hdunham@users.noreply.github.com> Date: Mon, 17 Oct 2022 23:02:05 -0600 Subject: [PATCH 17/53] update error messages --- job/src/run_jump_model.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/job/src/run_jump_model.py b/job/src/run_jump_model.py index 380a06c0e..7b29e32c2 100644 --- a/job/src/run_jump_model.py +++ b/job/src/run_jump_model.py @@ -111,7 +111,7 @@ def run_jump_model(run_uuid): raise OptimizationTimeout(task=name, message=msg, run_uuid=run_uuid, user_uuid=user_uuid) exc_type, exc_value, exc_traceback = sys.exc_info() - logger.error("REopt.py raise unexpected error: UUID: " + str(run_uuid)) + logger.error("REopt.jl raised an unexpected error: UUID: " + str(run_uuid)) raise UnexpectedError(exc_type, exc_value, traceback.format_tb(exc_traceback), task=name, run_uuid=run_uuid, user_uuid=user_uuid) else: @@ -123,7 +123,7 @@ def run_jump_model(run_uuid): logger.info(msg) raise OptimizationTimeout(task=name, message=msg, run_uuid=run_uuid, user_uuid=user_uuid) elif status.strip().lower() == 'error': - msg = "Optimization errored out." + msg = "Optimization did not complete due to an error." logger.info(msg) elif status.strip().lower() != 'optimal': logger.error("REopt status not optimal. Raising NotOptimal Exception.") From 9e719783820b1386e5b9f8bfcbd13e5ea6ebb7d1 Mon Sep 17 00:00:00 2001 From: zolanaj Date: Thu, 10 Nov 2022 13:45:40 -0700 Subject: [PATCH 18/53] Update names of HotThermalStorageOutputs fields --- job/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/job/models.py b/job/models.py index 916090c17..13b2fe84d 100644 --- a/job/models.py +++ b/job/models.py @@ -3904,11 +3904,11 @@ class HotThermalStorageOutputs(BaseModel, models.Model): primary_key=True ) size_gal = models.FloatField(null=True, blank=True) - year_one_soc_series_pct = ArrayField( + year_one_soc_series_fraction = ArrayField( models.FloatField(null=True, blank=True), default = list, ) - year_one_thermal_production_mmbtu_per_hour = ArrayField( + year_one_to_load_series_mmbtu_per_hour = ArrayField( models.FloatField(null=True, blank=True), default = list, ) From 7df704c258f4e44bfb16ca6de90aa779c93b03fb Mon Sep 17 00:00:00 2001 From: zolanaj Date: Thu, 10 Nov 2022 13:53:59 -0700 Subject: [PATCH 19/53] update help text for hot TES tax rebate --- job/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/job/models.py b/job/models.py index 13b2fe84d..3a43ce2ad 100644 --- a/job/models.py +++ b/job/models.py @@ -3887,7 +3887,7 @@ class HotThermalStorageInputs(BaseModel, models.Model): MaxValueValidator(1.0e9) ], blank=True, - help_text="Rebate based on installed energy capacity" + help_text="Rebate per unit installed energy capacity" ) def clean(self): From 317924bc3778bae0c62a0fbba290b80008061c8f Mon Sep 17 00:00:00 2001 From: zolanaj Date: Thu, 10 Nov 2022 14:45:38 -0700 Subject: [PATCH 20/53] change _pct to _fraction and defaults in HotThermalStorageInputs --- job/models.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/job/models.py b/job/models.py index 3a43ce2ad..b9d99c4a0 100644 --- a/job/models.py +++ b/job/models.py @@ -3784,7 +3784,7 @@ class HotThermalStorageInputs(BaseModel, models.Model): default=0.0, help_text="Maximum TES volume (energy) size constraint for optimization. Set to zero to disable storage" ) - hot_supply_water_temp_degF = models.FloatField( + hot_water_temp_degF = models.FloatField( validators=[ MinValueValidator(40.0), MaxValueValidator(210.0) @@ -3793,7 +3793,7 @@ class HotThermalStorageInputs(BaseModel, models.Model): default=180.0, help_text="Hot-side supply water temperature from HotTES (top of tank) to the heating load" ) - cooled_return_water_temp_degF = models.FloatField( + cold_water_temp_degF = models.FloatField( validators=[ MinValueValidator(33.0), MaxValueValidator(200.0) @@ -3802,7 +3802,7 @@ class HotThermalStorageInputs(BaseModel, models.Model): default=160.0, help_text="Cold-side return water temperature from the heating load to the HotTES (bottom of tank)" ) - internal_efficiency_pct = models.FloatField( + internal_efficiency_fraction = models.FloatField( validators=[ MinValueValidator(0), MaxValueValidator(1.0) @@ -3811,7 +3811,7 @@ class HotThermalStorageInputs(BaseModel, models.Model): default=0.999999, help_text="Thermal losses due to mixing from thermal power entering or leaving tank" ) - soc_min_pct = models.FloatField( + soc_min_fraction = models.FloatField( default=0.1, validators=[ MinValueValidator(0), @@ -3820,7 +3820,7 @@ class HotThermalStorageInputs(BaseModel, models.Model): blank=True, help_text="Minimum allowable battery state of charge as fraction of energy capacity." ) - soc_init_pct = models.FloatField( + soc_init_fraction = models.FloatField( validators=[ MinValueValidator(0), MaxValueValidator(1.0) @@ -3857,13 +3857,13 @@ class HotThermalStorageInputs(BaseModel, models.Model): help_text="Thermal energy-based cost of TES (e.g. volume of the tank)" ) macrs_option_years = models.IntegerField( - default=MACRS_YEARS_CHOICES.FIVE, + default=MACRS_YEARS_CHOICES.ZERO, choices=MACRS_YEARS_CHOICES.choices, blank=True, help_text="Duration over which accelerated depreciation will occur. Set to zero to disable" ) - macrs_bonus_pct = models.FloatField( - default=1.0, + macrs_bonus_fraction = models.FloatField( + default=0.0, validators=[ MinValueValidator(0), MaxValueValidator(1) @@ -3871,7 +3871,7 @@ class HotThermalStorageInputs(BaseModel, models.Model): blank=True, help_text="Percent of upfront project costs to depreciate in year one in addition to scheduled depreciation" ) - total_itc_pct = models.FloatField( + total_itc_fraction = models.FloatField( default=0.0, validators=[ MinValueValidator(0), From 0e2f3ee05c74f479bee64edc4d8b23413beb66d5 Mon Sep 17 00:00:00 2001 From: zolanaj Date: Thu, 10 Nov 2022 14:46:00 -0700 Subject: [PATCH 21/53] add macrs_itc_reduction in HotThermalStorageInputs --- job/models.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/job/models.py b/job/models.py index b9d99c4a0..008ea33b5 100644 --- a/job/models.py +++ b/job/models.py @@ -3871,6 +3871,15 @@ class HotThermalStorageInputs(BaseModel, models.Model): blank=True, help_text="Percent of upfront project costs to depreciate in year one in addition to scheduled depreciation" ) + macrs_itc_reduction = models.FloatField( + default=0.0, + validators=[ + MinValueValidator(0), + MaxValueValidator(1) + ], + blank=True, + help_text="Percent of the ITC value by which depreciable basis is reduced" + ) total_itc_fraction = models.FloatField( default=0.0, validators=[ From 8a21f588b31821b0738c95e1809b359629ac1083 Mon Sep 17 00:00:00 2001 From: zolanaj Date: Thu, 10 Nov 2022 14:47:20 -0700 Subject: [PATCH 22/53] add ColdThermalStorageInputs, ColdThermalStorageOutputs to models.py --- job/models.py | 175 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 173 insertions(+), 2 deletions(-) diff --git a/job/models.py b/job/models.py index 008ea33b5..d09481824 100644 --- a/job/models.py +++ b/job/models.py @@ -3836,7 +3836,7 @@ class HotThermalStorageInputs(BaseModel, models.Model): MaxValueValidator(1.0e4) ], blank=True, - help_text="Installed Hot TES cost in $/gal" + help_text="Installed hot TES cost in $/gal" ) om_cost_per_gal = models.FloatField( default=0.0, @@ -3845,7 +3845,7 @@ class HotThermalStorageInputs(BaseModel, models.Model): MaxValueValidator(1.0e3) ], blank=True, - help_text="Annual Hot TES operations and maintenance costs in $/gal" + help_text="Annual hot TES operations and maintenance costs in $/gal" ) thermal_decay_rate_fraction = models.FloatField( default=0.0004, @@ -3926,6 +3926,177 @@ def clean(self): # perform custom validation here. pass +class ColdThermalStorageInputs(BaseModel, models.Model): + key = "ColdThermalStorage" + + meta = models.OneToOneField( + APIMeta, + on_delete=models.CASCADE, + related_name="ColdThermalStorageInputs", + primary_key=True + ) + + min_gal = models.FloatField( + validators=[ + MinValueValidator(0), + MaxValueValidator(1.0e9) + ], + null=True, + blank=True, + default=0.0, + help_text="Minimum TES volume (energy) size constraint for optimization" + ) + max_gal = models.FloatField( + validators=[ + MinValueValidator(0), + MaxValueValidator(1.0e9) + ], + blank=True, + default=0.0, + help_text="Maximum TES volume (energy) size constraint for optimization. Set to zero to disable storage" + ) + cool_water_temp_degF = models.FloatField( + validators=[ + MinValueValidator(33.0), + MaxValueValidator(200.0) + ], + blank=True, + default=44.0, + help_text="Cold-side supply water temperature from ColdTES (top of tank) to the heating load" + ) + hot_water_temp_degF = models.FloatField( + validators=[ + MinValueValidator(40.0), + MaxValueValidator(210.0) + ], + blank=True, + default=56.0, + help_text="Cold-side return water temperature from the heating load to the ColdTES (bottom of tank)" + ) + internal_efficiency_fraction = models.FloatField( + validators=[ + MinValueValidator(0), + MaxValueValidator(1.0) + ], + blank=True, + default=0.999999, + help_text="Thermal losses due to mixing from thermal power entering or leaving tank" + ) + soc_min_fraction = models.FloatField( + default=0.1, + validators=[ + MinValueValidator(0), + MaxValueValidator(1.0) + ], + blank=True, + help_text="Minimum allowable battery state of charge as fraction of energy capacity." + ) + soc_init_fraction = models.FloatField( + validators=[ + MinValueValidator(0), + MaxValueValidator(1.0) + ], + default=0.5, + blank=True, + help_text="Battery state of charge at first hour of optimization as fraction of energy capacity." + ) + installed_cost_per_gal = models.FloatField( + default=1.5, + validators=[ + MinValueValidator(0), + MaxValueValidator(1.0e4) + ], + blank=True, + help_text="Installed cold TES cost in $/gal" + ) + om_cost_per_gal = models.FloatField( + default=0.0, + validators=[ + MinValueValidator(0), + MaxValueValidator(1.0e3) + ], + blank=True, + help_text="Annual cold TES operations and maintenance costs in $/gal" + ) + thermal_decay_rate_fraction = models.FloatField( + default=0.0004, + validators=[ + MinValueValidator(0), + MaxValueValidator(1.0) + ], + blank=True, + help_text="Thermal energy-based cost of TES (e.g. volume of the tank)" + ) + macrs_option_years = models.IntegerField( + default=MACRS_YEARS_CHOICES.ZERO, + choices=MACRS_YEARS_CHOICES.choices, + blank=True, + help_text="Duration over which accelerated depreciation will occur. Set to zero to disable" + ) + macrs_bonus_fraction = models.FloatField( + default=0.0, + validators=[ + MinValueValidator(0), + MaxValueValidator(1) + ], + blank=True, + help_text="Percent of upfront project costs to depreciate in year one in addition to scheduled depreciation" + ) + macrs_itc_reduction = models.FloatField( + default=0.0, + validators=[ + MinValueValidator(0), + MaxValueValidator(1) + ], + blank=True, + help_text="Percent of the ITC value by which depreciable basis is reduced" + ) + total_itc_fraction = models.FloatField( + default=0.0, + validators=[ + MinValueValidator(0), + MaxValueValidator(1) + ], + blank=True, + help_text="Total investment tax credit in percent applied toward capital costs" + ) + total_rebate_per_kwh = models.FloatField( + default=0.0, + validators=[ + MinValueValidator(0), + MaxValueValidator(1.0e9) + ], + blank=True, + help_text="Rebate per unit installed energy capacity" + ) + + def clean(self): + # perform custom validation here. + pass + +class ColdThermalStorageOutputs(BaseModel, models.Model): + key = "ColdThermalStorageOutputs" + + meta = models.OneToOneField( + APIMeta, + on_delete=models.CASCADE, + related_name="ColdThermalStorageOutputs", + primary_key=True + ) + size_gal = models.FloatField(null=True, blank=True) + year_one_soc_series_fraction = ArrayField( + models.FloatField(null=True, blank=True), + default = list, + ) + year_one_to_load_series_mmbtu_per_hour = ArrayField( + models.FloatField(null=True, blank=True), + default = list, + ) + + def clean(self): + # perform custom validation here. + pass + class SpaceHeatingLoadInputs(BaseModel, models.Model): key = "SpaceHeatingLoad" From 7165bde5ef92b5e61a40b7fc340b4bbeaef195dd Mon Sep 17 00:00:00 2001 From: zolanaj Date: Thu, 10 Nov 2022 15:49:14 -0700 Subject: [PATCH 23/53] Add ColdThermalStorageOutputs to process_results --- job/src/process_results.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/job/src/process_results.py b/job/src/process_results.py index 4f565ad4b..8fe20ef39 100644 --- a/job/src/process_results.py +++ b/job/src/process_results.py @@ -30,7 +30,7 @@ from job.models import FinancialOutputs, APIMeta, PVOutputs, ElectricStorageOutputs, ElectricTariffOutputs, SiteOutputs,\ ElectricUtilityOutputs, GeneratorOutputs, ElectricLoadOutputs, WindOutputs, FinancialInputs, ElectricUtilityInputs, \ - ExistingBoilerOutputs, HotThermalStorageOutputs + ExistingBoilerOutputs, HotThermalStorageOutputs, ColdThermalStorageOutputs import logging log = logging.getLogger(__name__) @@ -66,6 +66,8 @@ def process_results(results: dict, run_uuid: str) -> None: ExistingBoilerOutputs.create(meta=meta, **results["ExistingBoiler"]).save() if "HotThermalStorage" in results.keys(): HotThermalStorageOutputs.create(meta=meta, **results["HotThermalStorage"]).save() + if "ColdThermalStorage" in results.keys(): + HotThermalStorageOutputs.create(meta=meta, **results["ColdThermalStorage"]).save() # TODO process rest of results From 90d7c03f44ff95ff63114e342def90c16b1dc931 Mon Sep 17 00:00:00 2001 From: zolanaj Date: Fri, 11 Nov 2022 15:37:45 -0700 Subject: [PATCH 24/53] add ColdThermalStorageInputs to validators.py --- job/validators.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/job/validators.py b/job/validators.py index daaa8fc96..555645269 100644 --- a/job/validators.py +++ b/job/validators.py @@ -32,7 +32,7 @@ from job.models import MAX_BIG_NUMBER, APIMeta, ExistingBoilerInputs, UserProvidedMeta, SiteInputs, Settings, \ ElectricLoadInputs, ElectricTariffInputs, FinancialInputs, BaseModel, Message, ElectricUtilityInputs, PVInputs, \ ElectricStorageInputs, GeneratorInputs, WindInputs, SpaceHeatingLoadInputs, DomesticHotWaterLoadInputs, \ - CoolingLoadInputs, ExistingChillerInputs, HotThermalStorageInputs + CoolingLoadInputs, ExistingChillerInputs, HotThermalStorageInputs, ColdThermalStorageInputs from django.core.exceptions import ValidationError from pyproj import Proj from typing import Tuple @@ -100,7 +100,8 @@ def __init__(self, raw_inputs: dict): ExistingBoilerInputs, SpaceHeatingLoadInputs, DomesticHotWaterLoadInputs, - HotThermalStorageInputs + HotThermalStorageInputs, + ColdThermalStorageInputs ) self.pvnames = [] on_grid_required_object_names = [ From 137f28f86d85065da904dc981a5f99da73506f89 Mon Sep 17 00:00:00 2001 From: zolanaj Date: Fri, 11 Nov 2022 15:38:31 -0700 Subject: [PATCH 25/53] add ColdThermalStorageInputs, ColdThermalStorageOutputs to views.py (job endpoint) --- job/views.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/job/views.py b/job/views.py index c0af450bc..68474f773 100644 --- a/job/views.py +++ b/job/views.py @@ -38,7 +38,8 @@ WindOutputs, ExistingBoilerInputs, GeneratorOutputs, ElectricTariffOutputs, ElectricUtilityOutputs,\ ElectricLoadOutputs, ExistingBoilerOutputs, DomesticHotWaterLoadInputs, SiteInputs, SiteOutputs, APIMeta,\ UserProvidedMeta, CoolingLoadInputs, ExistingChillerInputs, ExistingChillerOutputs, CoolingLoadOutputs,\ - HeatingLoadOutputs, HotThermalStorageInputs, HotThermalStorageOutputs + HeatingLoadOutputs, HotThermalStorageInputs, HotThermalStorageOutputs, ColdThermalStorageInputs,\ + ColdThermalStorageOutputs def make_error_resp(msg): @@ -68,6 +69,7 @@ def help(request): d["ExistingBoiler"] = ExistingBoilerInputs.info_dict(ExistingBoilerInputs) # d["Boiler"] = BoilerInputs.info_dict(BoilerInputs) d["HotThermalStorage"] = HotThermalStorageInputs.info_dict(HotThermalStorageInputs) + d["ColdThermalStorage"] = ColdThermalStorageInputs.info_dict(ColdThermalStorageInputs) d["SpaceHeatingLoad"] = SpaceHeatingLoadInputs.info_dict(SpaceHeatingLoadInputs) d["DomesticHotWaterLoad"] = DomesticHotWaterLoadInputs.info_dict(DomesticHotWaterLoadInputs) d["Site"] = SiteInputs.info_dict(SiteInputs) @@ -108,6 +110,7 @@ def outputs(request): d["ExistingBoiler"] = ExistingBoilerOutputs.info_dict(ExistingBoilerOutputs) # d["Boiler"] = BoilerOutputs.info_dict(BoilerOutputs) d["HotThermalStorage"] = HotThermalStorageOutputs.info_dict(HotThermalStorageOutputs) + d["ColdThermalStorage"] = ColdThermalStorageOutputs.info_dict(ColdThermalStorageOutputs) d["Site"] = SiteOutputs.info_dict(SiteOutputs) d["HeatingLoad"] = HeatingLoadOutputs.info_dict(HeatingLoadOutputs) d["CoolingLoad"] = CoolingLoadOutputs.info_dict(CoolingLoadOutputs) @@ -211,6 +214,9 @@ def results(request, run_uuid): try: r["inputs"]["HotThermalStorage"] = meta.HotThermalStorageInputs.dict except: pass + try: r["inputs"]["ColdThermalStorage"] = meta.ColdThermalStorageInputs.dict + except: pass + try: r["inputs"]["SpaceHeatingLoad"] = meta.SpaceHeatingLoadInputs.dict except: pass @@ -258,6 +264,8 @@ def results(request, run_uuid): try: r["outputs"]["HotThermalStorage"] = meta.HotThermalStorageOutputs.dict except: pass + try: r["outputs"]["ColdThermalStorage"] = meta.ColdThermalStorageOutputs.dict + except: pass try: r["outputs"]["HeatingLoad"] = meta.HeatingLoadOutputs.dict except: pass try: r["outputs"]["CoolingLoad"] = meta.CoolingLoadOutputs.dict From 9825e011d4faba5d9267bd5a944df7e8ca6d8ee2 Mon Sep 17 00:00:00 2001 From: zolanaj Date: Fri, 11 Nov 2022 15:40:18 -0700 Subject: [PATCH 26/53] update migrations --- ...lstorageinputs_hotthermalstorageoutputs.py | 48 ----------- ...puts_coldthermalstorageoutputs_and_more.py | 81 +++++++++++++++++++ 2 files changed, 81 insertions(+), 48 deletions(-) delete mode 100644 job/migrations/0009_hotthermalstorageinputs_hotthermalstorageoutputs.py create mode 100644 job/migrations/0012_coldthermalstorageinputs_coldthermalstorageoutputs_and_more.py diff --git a/job/migrations/0009_hotthermalstorageinputs_hotthermalstorageoutputs.py b/job/migrations/0009_hotthermalstorageinputs_hotthermalstorageoutputs.py deleted file mode 100644 index 6520dfa13..000000000 --- a/job/migrations/0009_hotthermalstorageinputs_hotthermalstorageoutputs.py +++ /dev/null @@ -1,48 +0,0 @@ -# Generated by Django 4.0.6 on 2022-09-06 19:40 - -import django.contrib.postgres.fields -import django.core.validators -from django.db import migrations, models -import django.db.models.deletion -import job.models - - -class Migration(migrations.Migration): - - dependencies = [ - ('job', '0008_domestichotwaterloadinputs_existingboilerinputs_and_more'), - ] - - operations = [ - migrations.CreateModel( - name='HotThermalStorageInputs', - fields=[ - ('meta', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='HotThermalStorageInputs', serialize=False, to='job.apimeta')), - ('min_gal', models.FloatField(blank=True, default=0.0, help_text='Minimum TES volume (energy) size constraint for optimization', null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000000000.0)])), - ('max_gal', models.FloatField(blank=True, default=0.0, help_text='Maximum TES volume (energy) size constraint for optimization. Set to zero to disable storage', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000000000.0)])), - ('hot_supply_water_temp_degF', models.FloatField(blank=True, default=180.0, help_text='Hot-side supply water temperature from HotTES (top of tank) to the heating load', validators=[django.core.validators.MinValueValidator(40.0), django.core.validators.MaxValueValidator(210.0)])), - ('cooled_return_water_temp_degF', models.FloatField(blank=True, default=160.0, help_text='Cold-side return water temperature from the heating load to the HotTES (bottom of tank)', validators=[django.core.validators.MinValueValidator(33.0), django.core.validators.MaxValueValidator(200.0)])), - ('internal_efficiency_pct', models.FloatField(blank=True, default=0.999999, help_text='Thermal losses due to mixing from thermal power entering or leaving tank', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1.0)])), - ('soc_min_pct', models.FloatField(blank=True, default=0.1, help_text='Minimum allowable battery state of charge as fraction of energy capacity.', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1.0)])), - ('soc_init_pct', models.FloatField(blank=True, default=0.5, help_text='Battery state of charge at first hour of optimization as fraction of energy capacity.', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1.0)])), - ('installed_cost_per_gal', models.FloatField(blank=True, default=1.5, help_text='Installed Hot TES cost in $/gal', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10000.0)])), - ('om_cost_per_gal', models.FloatField(blank=True, default=0.0, help_text='Annual Hot TES operations and maintenance costs in $/gal', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000.0)])), - ('thermal_decay_rate_fraction', models.FloatField(blank=True, default=0.0004, help_text='Thermal energy-based cost of TES (e.g. volume of the tank)', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1.0)])), - ('macrs_option_years', models.IntegerField(blank=True, choices=[(0, 'Zero'), (5, 'Five'), (7, 'Seven')], default=5, help_text='Duration over which accelerated depreciation will occur. Set to zero to disable')), - ('macrs_bonus_pct', models.FloatField(blank=True, default=1.0, help_text='Percent of upfront project costs to depreciate in year one in addition to scheduled depreciation', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1)])), - ('total_itc_pct', models.FloatField(blank=True, default=0.0, help_text='Total investment tax credit in percent applied toward capital costs', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1)])), - ('total_rebate_per_kwh', models.FloatField(blank=True, default=0.0, help_text='Rebate based on installed energy capacity', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000000000.0)])), - ], - bases=(job.models.BaseModel, models.Model), - ), - migrations.CreateModel( - name='HotThermalStorageOutputs', - fields=[ - ('meta', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='HotThermalStorageOutputs', serialize=False, to='job.apimeta')), - ('size_gal', models.FloatField(blank=True, null=True)), - ('year_one_soc_series_pct', django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, size=None)), - ('year_one_thermal_production_mmbtu_per_hour', django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, size=None)), - ], - bases=(job.models.BaseModel, models.Model), - ), - ] diff --git a/job/migrations/0012_coldthermalstorageinputs_coldthermalstorageoutputs_and_more.py b/job/migrations/0012_coldthermalstorageinputs_coldthermalstorageoutputs_and_more.py new file mode 100644 index 000000000..0dbd54a05 --- /dev/null +++ b/job/migrations/0012_coldthermalstorageinputs_coldthermalstorageoutputs_and_more.py @@ -0,0 +1,81 @@ +# Generated by Django 4.0.6 on 2022-11-11 22:40 + +import django.contrib.postgres.fields +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion +import job.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('job', '0011_coolingloadinputs_coolingloadoutputs_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='ColdThermalStorageInputs', + fields=[ + ('meta', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='ColdThermalStorageInputs', serialize=False, to='job.apimeta')), + ('min_gal', models.FloatField(blank=True, default=0.0, help_text='Minimum TES volume (energy) size constraint for optimization', null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000000000.0)])), + ('max_gal', models.FloatField(blank=True, default=0.0, help_text='Maximum TES volume (energy) size constraint for optimization. Set to zero to disable storage', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000000000.0)])), + ('cool_water_temp_degF', models.FloatField(blank=True, default=44.0, help_text='Cold-side supply water temperature from ColdTES (top of tank) to the heating load', validators=[django.core.validators.MinValueValidator(33.0), django.core.validators.MaxValueValidator(200.0)])), + ('hot_water_temp_degF', models.FloatField(blank=True, default=56.0, help_text='Cold-side return water temperature from the heating load to the ColdTES (bottom of tank)', validators=[django.core.validators.MinValueValidator(40.0), django.core.validators.MaxValueValidator(210.0)])), + ('internal_efficiency_fraction', models.FloatField(blank=True, default=0.999999, help_text='Thermal losses due to mixing from thermal power entering or leaving tank', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1.0)])), + ('soc_min_fraction', models.FloatField(blank=True, default=0.1, help_text='Minimum allowable battery state of charge as fraction of energy capacity.', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1.0)])), + ('soc_init_fraction', models.FloatField(blank=True, default=0.5, help_text='Battery state of charge at first hour of optimization as fraction of energy capacity.', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1.0)])), + ('installed_cost_per_gal', models.FloatField(blank=True, default=1.5, help_text='Installed cold TES cost in $/gal', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10000.0)])), + ('om_cost_per_gal', models.FloatField(blank=True, default=0.0, help_text='Annual cold TES operations and maintenance costs in $/gal', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000.0)])), + ('thermal_decay_rate_fraction', models.FloatField(blank=True, default=0.0004, help_text='Thermal energy-based cost of TES (e.g. volume of the tank)', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1.0)])), + ('macrs_option_years', models.IntegerField(blank=True, choices=[(0, 'Zero'), (5, 'Five'), (7, 'Seven')], default=0, help_text='Duration over which accelerated depreciation will occur. Set to zero to disable')), + ('macrs_bonus_fraction', models.FloatField(blank=True, default=0.0, help_text='Percent of upfront project costs to depreciate in year one in addition to scheduled depreciation', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1)])), + ('macrs_itc_reduction', models.FloatField(blank=True, default=0.0, help_text='Percent of the ITC value by which depreciable basis is reduced', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1)])), + ('total_itc_fraction', models.FloatField(blank=True, default=0.0, help_text='Total investment tax credit in percent applied toward capital costs', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1)])), + ('total_rebate_per_kwh', models.FloatField(blank=True, default=0.0, help_text='Rebate per unit installed energy capacity', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000000000.0)])), + ], + bases=(job.models.BaseModel, models.Model), + ), + migrations.CreateModel( + name='ColdThermalStorageOutputs', + fields=[ + ('meta', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='ColdThermalStorageOutputs', serialize=False, to='job.apimeta')), + ('size_gal', models.FloatField(blank=True, null=True)), + ('year_one_soc_series_fraction', django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, size=None)), + ('year_one_to_load_series_mmbtu_per_hour', django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, size=None)), + ], + bases=(job.models.BaseModel, models.Model), + ), + migrations.CreateModel( + name='HotThermalStorageInputs', + fields=[ + ('meta', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='HotThermalStorageInputs', serialize=False, to='job.apimeta')), + ('min_gal', models.FloatField(blank=True, default=0.0, help_text='Minimum TES volume (energy) size constraint for optimization', null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000000000.0)])), + ('max_gal', models.FloatField(blank=True, default=0.0, help_text='Maximum TES volume (energy) size constraint for optimization. Set to zero to disable storage', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000000000.0)])), + ('hot_water_temp_degF', models.FloatField(blank=True, default=180.0, help_text='Hot-side supply water temperature from HotTES (top of tank) to the heating load', validators=[django.core.validators.MinValueValidator(40.0), django.core.validators.MaxValueValidator(210.0)])), + ('cold_water_temp_degF', models.FloatField(blank=True, default=160.0, help_text='Cold-side return water temperature from the heating load to the HotTES (bottom of tank)', validators=[django.core.validators.MinValueValidator(33.0), django.core.validators.MaxValueValidator(200.0)])), + ('internal_efficiency_fraction', models.FloatField(blank=True, default=0.999999, help_text='Thermal losses due to mixing from thermal power entering or leaving tank', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1.0)])), + ('soc_min_fraction', models.FloatField(blank=True, default=0.1, help_text='Minimum allowable battery state of charge as fraction of energy capacity.', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1.0)])), + ('soc_init_fraction', models.FloatField(blank=True, default=0.5, help_text='Battery state of charge at first hour of optimization as fraction of energy capacity.', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1.0)])), + ('installed_cost_per_gal', models.FloatField(blank=True, default=1.5, help_text='Installed hot TES cost in $/gal', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10000.0)])), + ('om_cost_per_gal', models.FloatField(blank=True, default=0.0, help_text='Annual hot TES operations and maintenance costs in $/gal', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000.0)])), + ('thermal_decay_rate_fraction', models.FloatField(blank=True, default=0.0004, help_text='Thermal energy-based cost of TES (e.g. volume of the tank)', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1.0)])), + ('macrs_option_years', models.IntegerField(blank=True, choices=[(0, 'Zero'), (5, 'Five'), (7, 'Seven')], default=0, help_text='Duration over which accelerated depreciation will occur. Set to zero to disable')), + ('macrs_bonus_fraction', models.FloatField(blank=True, default=0.0, help_text='Percent of upfront project costs to depreciate in year one in addition to scheduled depreciation', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1)])), + ('macrs_itc_reduction', models.FloatField(blank=True, default=0.0, help_text='Percent of the ITC value by which depreciable basis is reduced', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1)])), + ('total_itc_fraction', models.FloatField(blank=True, default=0.0, help_text='Total investment tax credit in percent applied toward capital costs', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1)])), + ('total_rebate_per_kwh', models.FloatField(blank=True, default=0.0, help_text='Rebate per unit installed energy capacity', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000000000.0)])), + ], + bases=(job.models.BaseModel, models.Model), + ), + migrations.CreateModel( + name='HotThermalStorageOutputs', + fields=[ + ('meta', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='HotThermalStorageOutputs', serialize=False, to='job.apimeta')), + ('size_gal', models.FloatField(blank=True, null=True)), + ('year_one_soc_series_fraction', django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, size=None)), + ('year_one_to_load_series_mmbtu_per_hour', django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, size=None)), + ], + bases=(job.models.BaseModel, models.Model), + ), + ] From 225228a79061f47c91ad0b088ff13ae4e3ae46af Mon Sep 17 00:00:00 2001 From: zolanaj Date: Fri, 11 Nov 2022 15:41:26 -0700 Subject: [PATCH 27/53] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b724c7cc9..430b317b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ Classify the change according to the following categories: ### Minor Updates ##### Added - `0011_coolingloadinputs....` file used to add new models to the db +- `0012_coldthermalstorageinputs....` file used to add new models to the db In `job/models.py`: - added **ExistingChillerInputs** model - added **ExistingChillerOutputs** model @@ -38,11 +39,14 @@ In `job/models.py`: - added **HeatingLoadOutputs** model - added **HotThermalStorageInputs** model - added **HotThermalStorageOutputs** model +- added **ColdThermalStorageInputs** model +- added **ColdThermalStorageOutputs** model In `job/process_results.py`: - add **ExistingChillerOutputs** - add **CoolingLoadOutputs** - add **HeatingLoadOutputs** - add **HotThermalStorageOutputs** +- add **ColdThermalStorageOutputs** In `job/validators.py: - add time series length validation on **CoolingLoadInputs->thermal_loads_ton** and **CoolingLoadInputs->per_time_step_fractions_of_electric_load** In `job/views.py`: From 15453aa55789665e6a2848f51c4c9a4859a528da Mon Sep 17 00:00:00 2001 From: "Rathod, Bhavesh" Date: Tue, 22 Nov 2022 18:19:42 -0500 Subject: [PATCH 28/53] PR#370 comments, migration file merging, changelog updates Address PR#370 comments, update changelog, consolidate migration files, rename MessageOutputs to REoptjlMessageOutputs to differentiate. Update how errors and warns are returned to the user using existing Messages key, update error and informational text around error handling --- CHANGELOG.md | 22 +++++++++ ...=> 0011_reoptjlmessageoutputs_and_more.py} | 26 ++++++++-- ...ne_thermal_to_tes_series_mmbtu_per_hour.py | 19 ------- ..._fuel_consumption_series_mmbtu_per_hour.py | 19 ------- ...hermal_production_series_mmbtu_per_hour.py | 19 ------- ...l_to_steamturbine_series_mmbtu_per_hour.py | 19 ------- job/models.py | 4 +- job/src/process_results.py | 49 +++++++++---------- job/src/run_jump_model.py | 2 +- job/views.py | 11 +++-- julia_src/Manifest.toml | 4 +- julia_src/cbc/http.jl | 6 ++- julia_src/http.jl | 4 +- 13 files changed, 88 insertions(+), 116 deletions(-) rename job/migrations/{0011_messagesoutputs_and_more.py => 0011_reoptjlmessageoutputs_and_more.py} (76%) delete mode 100644 job/migrations/0012_existingboileroutputs_year_one_thermal_to_tes_series_mmbtu_per_hour.py delete mode 100644 job/migrations/0013_existingboileroutputs_year_one_fuel_consumption_series_mmbtu_per_hour.py delete mode 100644 job/migrations/0014_existingboileroutputs_year_one_thermal_production_series_mmbtu_per_hour.py delete mode 100644 job/migrations/0015_existingboileroutputs_year_one_thermal_to_steamturbine_series_mmbtu_per_hour.py diff --git a/CHANGELOG.md b/CHANGELOG.md index f442edec3..6577e5b8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,28 @@ Classify the change according to the following categories: ##### Removed ### Patches +## Develop - 2022-11-22 +### Minor Updates +#### Added +1. **REoptjlMessageOutputs** model to capture errors and warnings returned by REoptjl during input processing and post optimization +2. Missing output fields for **ExistingBoilerOutputs** model +3. API test `job\test\posts\all_inputs_test.json` to include all input models in a single API test + +#### Changed +1. Default values for the following fields were changed to align them with REopt API v2 (and REopt.jl) defaults. As-is, these values are aligned with REopt v1 defaults. Units were unchanged. +- **FinancialInputs.elec_cost_escalation_rate_fraction** from 0.023 to 0.019 +- **FinancialInputs.offtaker_discount_rate_fraction** from 0.083 to 0.0564 +- **FinancialInputs.owner_discount_rate_fraction** from 0.083 to 0.0564 +- **PVInputs.installed_cost_per_kw** from 1600 to 1592 +- **PVInputs.om_cost_per_kw** from 16 to 17 +- **WindInputs.om_cost_per_kw** from 16 to 35 +- **ElectricStorageInputs.installed_cost_per_kw** from 840 to 775 +- **ElectricStorageInputs.installed_cost_per_kwh** from 420 to 388 +- **ElectricStorageInputs.replace_cost_per_kw** from 410 to 440 +- **ElectricStorageInputs.replace_cost_per_kwh** from 200 to 220 +2. Modified `julia_src\http.jl` and `julia_src\cbc\http.jl` to return status 400 when REopt responds with an error +3. Updated `r["Messages"]` in `validators.py` to include **REoptjlMessageOutputs** errors and warnings + ## v2.3.0 ### Minor Updates ##### Changed diff --git a/job/migrations/0011_messagesoutputs_and_more.py b/job/migrations/0011_reoptjlmessageoutputs_and_more.py similarity index 76% rename from job/migrations/0011_messagesoutputs_and_more.py rename to job/migrations/0011_reoptjlmessageoutputs_and_more.py index 02f466ee1..6ac5b35e1 100644 --- a/job/migrations/0011_messagesoutputs_and_more.py +++ b/job/migrations/0011_reoptjlmessageoutputs_and_more.py @@ -1,4 +1,4 @@ -# Generated by Django 4.0.6 on 2022-10-10 23:36 +# Generated by Django 4.0.6 on 2022-11-22 23:15 import django.contrib.postgres.fields import django.core.validators @@ -15,14 +15,34 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name='MessagesOutputs', + name='REoptjlMessageOutputs', fields=[ - ('meta', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='MessagesOutputs', serialize=False, to='job.apimeta')), + ('meta', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='REoptjlMessageOutputs', serialize=False, to='job.apimeta')), ('errors', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(blank=True, null=True), default=list, size=None)), ('warnings', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(blank=True, null=True), default=list, size=None)), ], bases=(job.models.BaseModel, models.Model), ), + migrations.AddField( + model_name='existingboileroutputs', + name='year_one_fuel_consumption_series_mmbtu_per_hour', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, size=None), + ), + migrations.AddField( + model_name='existingboileroutputs', + name='year_one_thermal_production_series_mmbtu_per_hour', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, size=None), + ), + migrations.AddField( + model_name='existingboileroutputs', + name='year_one_thermal_to_steamturbine_series_mmbtu_per_hour', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, size=None), + ), + migrations.AddField( + model_name='existingboileroutputs', + name='year_one_thermal_to_tes_series_mmbtu_per_hour', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, size=None), + ), migrations.AlterField( model_name='electricstorageinputs', name='installed_cost_per_kw', diff --git a/job/migrations/0012_existingboileroutputs_year_one_thermal_to_tes_series_mmbtu_per_hour.py b/job/migrations/0012_existingboileroutputs_year_one_thermal_to_tes_series_mmbtu_per_hour.py deleted file mode 100644 index 868677991..000000000 --- a/job/migrations/0012_existingboileroutputs_year_one_thermal_to_tes_series_mmbtu_per_hour.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 4.0.6 on 2022-10-11 18:23 - -import django.contrib.postgres.fields -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('job', '0011_messagesoutputs_and_more'), - ] - - operations = [ - migrations.AddField( - model_name='existingboileroutputs', - name='year_one_thermal_to_tes_series_mmbtu_per_hour', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, size=None), - ), - ] diff --git a/job/migrations/0013_existingboileroutputs_year_one_fuel_consumption_series_mmbtu_per_hour.py b/job/migrations/0013_existingboileroutputs_year_one_fuel_consumption_series_mmbtu_per_hour.py deleted file mode 100644 index a6d0292f2..000000000 --- a/job/migrations/0013_existingboileroutputs_year_one_fuel_consumption_series_mmbtu_per_hour.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 4.0.6 on 2022-10-11 18:26 - -import django.contrib.postgres.fields -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('job', '0012_existingboileroutputs_year_one_thermal_to_tes_series_mmbtu_per_hour'), - ] - - operations = [ - migrations.AddField( - model_name='existingboileroutputs', - name='year_one_fuel_consumption_series_mmbtu_per_hour', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, size=None), - ), - ] diff --git a/job/migrations/0014_existingboileroutputs_year_one_thermal_production_series_mmbtu_per_hour.py b/job/migrations/0014_existingboileroutputs_year_one_thermal_production_series_mmbtu_per_hour.py deleted file mode 100644 index 3bdec5149..000000000 --- a/job/migrations/0014_existingboileroutputs_year_one_thermal_production_series_mmbtu_per_hour.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 4.0.6 on 2022-10-11 18:30 - -import django.contrib.postgres.fields -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('job', '0013_existingboileroutputs_year_one_fuel_consumption_series_mmbtu_per_hour'), - ] - - operations = [ - migrations.AddField( - model_name='existingboileroutputs', - name='year_one_thermal_production_series_mmbtu_per_hour', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, size=None), - ), - ] diff --git a/job/migrations/0015_existingboileroutputs_year_one_thermal_to_steamturbine_series_mmbtu_per_hour.py b/job/migrations/0015_existingboileroutputs_year_one_thermal_to_steamturbine_series_mmbtu_per_hour.py deleted file mode 100644 index 3a3d6b8c4..000000000 --- a/job/migrations/0015_existingboileroutputs_year_one_thermal_to_steamturbine_series_mmbtu_per_hour.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 4.0.6 on 2022-10-11 18:47 - -import django.contrib.postgres.fields -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('job', '0014_existingboileroutputs_year_one_thermal_production_series_mmbtu_per_hour'), - ] - - operations = [ - migrations.AddField( - model_name='existingboileroutputs', - name='year_one_thermal_to_steamturbine_series_mmbtu_per_hour', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, size=None), - ), - ] diff --git a/job/models.py b/job/models.py index 837f22070..0ab251c14 100644 --- a/job/models.py +++ b/job/models.py @@ -3571,13 +3571,13 @@ def clean(self): # perform custom validation here. pass -class MessagesOutputs(BaseModel, models.Model): +class REoptjlMessageOutputs(BaseModel, models.Model): key = "Messages" meta = models.OneToOneField( APIMeta, on_delete=models.CASCADE, - related_name="MessagesOutputs", + related_name="REoptjlMessageOutputs", primary_key=True ) diff --git a/job/src/process_results.py b/job/src/process_results.py index 26cd49e44..e2d073336 100644 --- a/job/src/process_results.py +++ b/job/src/process_results.py @@ -27,7 +27,7 @@ # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED # OF THE POSSIBILITY OF SUCH DAMAGE. # ********************************************************************************* -from job.models import MessagesOutputs, FinancialOutputs, APIMeta, PVOutputs, ElectricStorageOutputs, ElectricTariffOutputs, SiteOutputs,\ +from job.models import REoptjlMessageOutputs, FinancialOutputs, APIMeta, PVOutputs, ElectricStorageOutputs, ElectricTariffOutputs, SiteOutputs,\ ElectricUtilityOutputs, GeneratorOutputs, ElectricLoadOutputs, WindOutputs, FinancialInputs, ElectricUtilityInputs, ExistingBoilerOutputs import logging log = logging.getLogger(__name__) @@ -42,35 +42,34 @@ def process_results(results: dict, run_uuid: str) -> None: meta.status = results.get("status") meta.save(update_fields=["status"]) - if "Financial" in results.keys(): + if results.get("status") == "error": + if "Messages" in results.keys(): + REoptjlMessageOutputs.create(meta=meta, **results["Messages"]).save() + else: FinancialOutputs.create(meta=meta, **results["Financial"]).save() - if "ElectricTariff" in results.keys(): ElectricTariffOutputs.create(meta=meta, **results["ElectricTariff"]).save() - if "ElectricUtility" in results.keys(): ElectricUtilityOutputs.create(meta=meta, **results["ElectricUtility"]).save() - if "ElectricLoad" in results.keys(): ElectricLoadOutputs.create(meta=meta, **results["ElectricLoad"]).save() - if "Site" in results.keys(): SiteOutputs.create(meta=meta, **results["Site"]).save() - if "PV" in results.keys(): - if isinstance(results["PV"], dict): - PVOutputs.create(meta=meta, **results["PV"]).save() - elif isinstance(results["PV"], list): - for pvdict in results["PV"]: - PVOutputs.create(meta=meta, **pvdict).save() - if "ElectricStorage" in results.keys(): - ElectricStorageOutputs.create(meta=meta, **results["ElectricStorage"]).save() - if "Generator" in results.keys(): - GeneratorOutputs.create(meta=meta, **results["Generator"]).save() - if "Wind" in results.keys(): - WindOutputs.create(meta=meta, **results["Wind"]).save() - # if "Boiler" in results.keys(): - # BoilerOutputs.create(meta=meta, **results["Boiler"]).save() - if "ExistingBoiler" in results.keys(): - ExistingBoilerOutputs.create(meta=meta, **results["ExistingBoiler"]).save() - if "Messages" in results.keys(): - MessagesOutputs.create(meta=meta, **results["Messages"]).save() - # TODO process rest of results + if "PV" in results.keys(): + if isinstance(results["PV"], dict): + PVOutputs.create(meta=meta, **results["PV"]).save() + elif isinstance(results["PV"], list): + for pvdict in results["PV"]: + PVOutputs.create(meta=meta, **pvdict).save() + if "ElectricStorage" in results.keys(): + ElectricStorageOutputs.create(meta=meta, **results["ElectricStorage"]).save() + if "Generator" in results.keys(): + GeneratorOutputs.create(meta=meta, **results["Generator"]).save() + if "Wind" in results.keys(): + WindOutputs.create(meta=meta, **results["Wind"]).save() + # if "Boiler" in results.keys(): + # BoilerOutputs.create(meta=meta, **results["Boiler"]).save() + if "ExistingBoiler" in results.keys(): + ExistingBoilerOutputs.create(meta=meta, **results["ExistingBoiler"]).save() + if "Messages" in results.keys(): + REoptjlMessageOutputs.create(meta=meta, **results["Messages"]).save() + # TODO process rest of results def update_inputs_in_database(inputs_to_update: dict, run_uuid: str) -> None: diff --git a/job/src/run_jump_model.py b/job/src/run_jump_model.py index 7b29e32c2..507de352d 100644 --- a/job/src/run_jump_model.py +++ b/job/src/run_jump_model.py @@ -116,7 +116,7 @@ def run_jump_model(run_uuid): user_uuid=user_uuid) else: status = results["status"] - logger.info("REopt run successful. Status {}".format(status)) + logger.info("REopt run completed with status {}".format(status)) if status.strip().lower() == 'timed-out': msg = "Optimization exceeded timeout: {} seconds.".format(data["Settings"]["timeout_seconds"]) diff --git a/job/views.py b/job/views.py index d2aedc383..7eb1cb058 100644 --- a/job/views.py +++ b/job/views.py @@ -35,7 +35,7 @@ from reo.exceptions import UnexpectedError from job.models import Settings, PVInputs, ElectricStorageInputs, WindInputs, GeneratorInputs, ElectricLoadInputs,\ ElectricTariffInputs, ElectricUtilityInputs, SpaceHeatingLoadInputs, PVOutputs, ElectricStorageOutputs, WindOutputs, ExistingBoilerInputs,\ - GeneratorOutputs, ElectricTariffOutputs, ElectricUtilityOutputs, ElectricLoadOutputs, ExistingBoilerOutputs, MessagesOutputs, \ + GeneratorOutputs, ElectricTariffOutputs, ElectricUtilityOutputs, ElectricLoadOutputs, ExistingBoilerOutputs, REoptjlMessageOutputs, \ DomesticHotWaterLoadInputs, SiteInputs, SiteOutputs, APIMeta, UserProvidedMeta @@ -101,7 +101,7 @@ def outputs(request): d["Generator"] = GeneratorOutputs.info_dict(GeneratorOutputs) d["ExistingBoiler"] = ExistingBoilerOutputs.info_dict(ExistingBoilerOutputs) # d["Boiler"] = BoilerOutputs.info_dict(BoilerOutputs) - d["Messages"] = MessagesOutputs.info_dict(MessagesOutputs) + d["Messages"] = REoptjlMessageOutputs.info_dict(REoptjlMessageOutputs) return JsonResponse(d) except Exception as e: @@ -206,6 +206,11 @@ def results(request, run_uuid): msgs = meta.Message.all() for msg in msgs: r["messages"][msg.message_type] = msg.message + + reopt_messages = meta.REoptjlMessageOutputs.dict + for msg_type in ["errors","warnings"]: + for e in range(0,len(reopt_messages[msg_type])): + r["messages"][msg_type] = reopt_messages[msg_type][e] except: pass try: @@ -235,8 +240,6 @@ def results(request, run_uuid): except: pass # try: r["outputs"]["Boiler"] = meta.BoilerOutputs.dict # except: pass - try: r["outputs"]["Messages"] = meta.MessagesOutputs.dict - except: pass for d in r["outputs"].values(): if isinstance(d, dict): diff --git a/julia_src/Manifest.toml b/julia_src/Manifest.toml index bd509d6e8..0df8a6bfb 100644 --- a/julia_src/Manifest.toml +++ b/julia_src/Manifest.toml @@ -578,11 +578,11 @@ uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" [[deps.REopt]] deps = ["ArchGDAL", "CoolProp", "Dates", "DelimitedFiles", "HTTP", "JLD", "JSON", "JuMP", "LinDistFlow", "LinearAlgebra", "Logging", "MathOptInterface", "Requires", "Roots", "Statistics", "TestEnv"] -git-tree-sha1 = "f76411c1b0bfa3fe47774abe5892bd7406e12862" +git-tree-sha1 = "48d65155a002d044abbc87eac6e1c1420a115c53" repo-rev = "error-msgs" repo-url = "https://github.com/NREL/REopt.jl.git" uuid = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6" -version = "0.20.0" +version = "0.22.0" [[deps.Random]] deps = ["SHA", "Serialization"] diff --git a/julia_src/cbc/http.jl b/julia_src/cbc/http.jl index a2bc18bd5..e71706e69 100644 --- a/julia_src/cbc/http.jl +++ b/julia_src/cbc/http.jl @@ -21,7 +21,11 @@ function job(req::HTTP.Request) GC.gc() if isempty(error_response) @info "REopt model solved with status $(results["status"])." - return HTTP.Response(200, JSON.json(results)) + if results["status"] == "error" + return HTTP.Response(400, JSON.json(response)) + else + return HTTP.Response(200, JSON.json(response)) + end else @info "An error occured in the Julia code." return HTTP.Response(500, JSON.json(error_response)) diff --git a/julia_src/http.jl b/julia_src/http.jl index d629aeb20..e436d7d88 100644 --- a/julia_src/http.jl +++ b/julia_src/http.jl @@ -73,7 +73,7 @@ function reopt(req::HTTP.Request) try model_inputs = reoptjl.REoptInputs(d) catch e - @error "Something went wrong in the Julia code!" exception=(e, catch_backtrace()) + @error "Something went wrong in REopt inputs processing!" exception=(e, catch_backtrace()) error_response["error"] = sprint(showerror, e) end @@ -97,7 +97,7 @@ function reopt(req::HTTP.Request) "ElectricUtility" => Dict(key=>getfield(model_inputs.s.electric_utility, key) for key in inputs_with_defaults_from_avert) ) catch e - @error "Something q wrong in the Julia code!" exception=(e, catch_backtrace()) + @error "Something went wrong in REopt optimization!" exception=(e, catch_backtrace()) error_response["error"] = sprint(showerror, e) # append instead of rewrite? end end From 358e2897e89e1a5d6339150e4c6e8bc43fb32ca3 Mon Sep 17 00:00:00 2001 From: "Rathod, Bhavesh" Date: Wed, 23 Nov 2022 10:09:00 -0500 Subject: [PATCH 29/53] update message handling and tests Create a dictionary of errors and warnings to they are easy to process by API users. always return an empty dictionary if there are no errors/warnings. Update tests --- job/test/test_job_endpoint.py | 3 ++- job/views.py | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/job/test/test_job_endpoint.py b/job/test/test_job_endpoint.py index 86686b211..d0c498418 100644 --- a/job/test/test_job_endpoint.py +++ b/job/test/test_job_endpoint.py @@ -110,7 +110,8 @@ def test_process_reopt_error(self): resp = self.api_client.get(f'/dev/job/{run_uuid}/results') r = json.loads(resp.content) - results = r["outputs"] + assert('errors' in r["messages"].keys()) + assert('warnings' in r["messages"].keys()) assert(resp.status_code==400) def test_superset_input_fields(self): diff --git a/job/views.py b/job/views.py index 7eb1cb058..199b55cfd 100644 --- a/job/views.py +++ b/job/views.py @@ -31,6 +31,7 @@ import uuid import sys import traceback as tb +import re from django.http import JsonResponse from reo.exceptions import UnexpectedError from job.models import Settings, PVInputs, ElectricStorageInputs, WindInputs, GeneratorInputs, ElectricLoadInputs,\ @@ -207,10 +208,19 @@ def results(request, run_uuid): for msg in msgs: r["messages"][msg.message_type] = msg.message + # Add a dictionary of warnings and errors from REopt + # key = location of warning, error, or uncaught error + # value = vector of text from REopt + # In case of uncaught error, vector length > 1 reopt_messages = meta.REoptjlMessageOutputs.dict for msg_type in ["errors","warnings"]: - for e in range(0,len(reopt_messages[msg_type])): - r["messages"][msg_type] = reopt_messages[msg_type][e] + r["messages"][msg_type] = dict() + for m in range(0,len(reopt_messages[msg_type])): + txt = reopt_messages[msg_type][m] + txt = re.sub('[^0-9a-zA-Z_.,() ]+', '', txt) + k = txt.split(',')[0] + v = txt.split(',')[1:] + r["messages"][msg_type][k] = v except: pass try: From e5e7afadaab348a98eb59c0ab13207a4abf9576e Mon Sep 17 00:00:00 2001 From: "Rathod, Bhavesh" Date: Tue, 29 Nov 2022 13:42:50 -0500 Subject: [PATCH 30/53] Fix bugs from develop into branch merge --- ...=> 0015_reoptjlmessageoutputs_and_more.py} | 21 +------ job/src/process_results.py | 2 +- julia_src/Manifest.toml | 55 ++++++++++++++----- 3 files changed, 44 insertions(+), 34 deletions(-) rename job/migrations/{0011_reoptjlmessageoutputs_and_more.py => 0015_reoptjlmessageoutputs_and_more.py} (81%) diff --git a/job/migrations/0011_reoptjlmessageoutputs_and_more.py b/job/migrations/0015_reoptjlmessageoutputs_and_more.py similarity index 81% rename from job/migrations/0011_reoptjlmessageoutputs_and_more.py rename to job/migrations/0015_reoptjlmessageoutputs_and_more.py index 6ac5b35e1..ccb1f7154 100644 --- a/job/migrations/0011_reoptjlmessageoutputs_and_more.py +++ b/job/migrations/0015_reoptjlmessageoutputs_and_more.py @@ -1,4 +1,4 @@ -# Generated by Django 4.0.6 on 2022-11-22 23:15 +# Generated by Django 4.0.6 on 2022-11-29 18:15 import django.contrib.postgres.fields import django.core.validators @@ -10,7 +10,7 @@ class Migration(migrations.Migration): dependencies = [ - ('job', '0010_rename_prod_factor_series_pvinputs_production_factor_series_and_more'), + ('job', '0014_rename_thermal_to_tes_series_mmbtu_per_hour_existingboileroutputs_year_one_fuel_consumption_series_m'), ] operations = [ @@ -25,22 +25,7 @@ class Migration(migrations.Migration): ), migrations.AddField( model_name='existingboileroutputs', - name='year_one_fuel_consumption_series_mmbtu_per_hour', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, size=None), - ), - migrations.AddField( - model_name='existingboileroutputs', - name='year_one_thermal_production_series_mmbtu_per_hour', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, size=None), - ), - migrations.AddField( - model_name='existingboileroutputs', - name='year_one_thermal_to_steamturbine_series_mmbtu_per_hour', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, size=None), - ), - migrations.AddField( - model_name='existingboileroutputs', - name='year_one_thermal_to_tes_series_mmbtu_per_hour', + name='thermal_to_tes_series_mmbtu_per_hour', field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, size=None), ), migrations.AlterField( diff --git a/job/src/process_results.py b/job/src/process_results.py index 3f40a0533..8b960a704 100644 --- a/job/src/process_results.py +++ b/job/src/process_results.py @@ -28,7 +28,7 @@ # OF THE POSSIBILITY OF SUCH DAMAGE. # ********************************************************************************* from job.models import REoptjlMessageOutputs, FinancialOutputs, APIMeta, PVOutputs, ElectricStorageOutputs, ElectricTariffOutputs, SiteOutputs,\ - ElectricUtilityOutputs, GeneratorOutputs, ElectricLoadOutputs, WindOutputs, FinancialInputs, ElectricUtilityInputs, ExistingBoilerOutputs,\ + ElectricUtilityOutputs, GeneratorOutputs, ElectricLoadOutputs, WindOutputs, FinancialInputs, ElectricUtilityInputs, ExistingBoilerOutputs,\ CHPInputs, CHPOutputs import logging diff --git a/julia_src/Manifest.toml b/julia_src/Manifest.toml index 591975272..776f2c641 100644 --- a/julia_src/Manifest.toml +++ b/julia_src/Manifest.toml @@ -1,7 +1,8 @@ # This file is machine-generated - editing it directly is not advised -julia_version = "1.7.1" +julia_version = "1.8.3" manifest_format = "2.0" +project_hash = "c2cdadb4ef175368e043d03d8dd75e25e020ef11" [[deps.AbstractFFTs]] deps = ["ChainRulesCore", "LinearAlgebra"] @@ -23,6 +24,7 @@ version = "0.9.3" [[deps.ArgTools]] uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" +version = "1.1.1" [[deps.Arrow_jll]] deps = ["Artifacts", "Bzip2_jll", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Lz4_jll", "Pkg", "Thrift_jll", "Zlib_jll", "boost_jll", "snappy_jll"] @@ -132,13 +134,14 @@ version = "0.3.0" [[deps.Compat]] deps = ["Dates", "LinearAlgebra", "UUIDs"] -git-tree-sha1 = "aaabba4ce1b7f8a9b34c015053d3b1edf60fa49c" +git-tree-sha1 = "00a2cccc7f098ff3b66806862d275ca3db9e6e5a" uuid = "34da2185-b29b-5c13-b0c7-acf172513d20" -version = "4.4.0" +version = "4.5.0" [[deps.CompilerSupportLibraries_jll]] deps = ["Artifacts", "Libdl"] uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" +version = "0.5.2+0" [[deps.ConstructionBase]] deps = ["LinearAlgebra"] @@ -211,8 +214,9 @@ uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" version = "0.9.2" [[deps.Downloads]] -deps = ["ArgTools", "LibCURL", "NetworkOptions"] +deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"] uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" +version = "1.6.0" [[deps.Expat_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] @@ -243,6 +247,9 @@ git-tree-sha1 = "e27c4ebe80e8699540f2d6c805cc12203b614f12" uuid = "48062228-2e41-5def-b9a4-89aafe57970f" version = "0.9.20" +[[deps.FileWatching]] +uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" + [[deps.FixedPointNumbers]] deps = ["Statistics"] git-tree-sha1 = "335bfdceacc84c5cdf16aadc768aa5ddfc5383cc" @@ -434,10 +441,12 @@ version = "2.10.1+0" [[deps.LibCURL]] deps = ["LibCURL_jll", "MozillaCACerts_jll"] uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21" +version = "0.6.3" [[deps.LibCURL_jll]] deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"] uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0" +version = "7.84.0+0" [[deps.LibGit2]] deps = ["Base64", "NetworkOptions", "Printf", "SHA"] @@ -452,6 +461,7 @@ version = "14.3.0+1" [[deps.LibSSH2_jll]] deps = ["Artifacts", "Libdl", "MbedTLS_jll"] uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8" +version = "1.10.2+0" [[deps.Libdl]] uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" @@ -486,9 +496,9 @@ version = "2.12.0+0" [[deps.LogExpFunctions]] deps = ["ChainRulesCore", "ChangesOfVariables", "DocStringExtensions", "InverseFunctions", "IrrationalConstants", "LinearAlgebra"] -git-tree-sha1 = "94d9c52ca447e23eac0c0f074effbcd38830deb5" +git-tree-sha1 = "946607f84feb96220f480e0422d3484c49c00239" uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688" -version = "0.3.18" +version = "0.3.19" [[deps.Logging]] uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" @@ -535,6 +545,7 @@ version = "1.1.7" [[deps.MbedTLS_jll]] deps = ["Artifacts", "Libdl"] uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" +version = "2.28.0+0" [[deps.Memento]] deps = ["Dates", "Distributed", "Requires", "Serialization", "Sockets", "Test", "UUIDs"] @@ -547,12 +558,13 @@ uuid = "a63ad114-7e13-5084-954f-fe012c677804" [[deps.MosaicViews]] deps = ["MappedArrays", "OffsetArrays", "PaddedViews", "StackViews"] -git-tree-sha1 = "b34e3bc3ca7c94914418637cb10cc4d1d80d877d" +git-tree-sha1 = "7b86a5d4d70a9f5cdf2dacb3cbe6d251d1a61dbe" uuid = "e94cdb99-869f-56ef-bcf0-1ae2bcbe0389" -version = "0.3.3" +version = "0.3.4" [[deps.MozillaCACerts_jll]] uuid = "14a3606d-f60d-562e-9121-12d972cd8159" +version = "2022.2.1" [[deps.MutableArithmetics]] deps = ["LinearAlgebra", "SparseArrays", "Test"] @@ -574,6 +586,7 @@ version = "400.902.5+1" [[deps.NetworkOptions]] uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" +version = "1.2.0" [[deps.OffsetArrays]] deps = ["Adapt"] @@ -584,6 +597,7 @@ version = "1.12.8" [[deps.OpenBLAS_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" +version = "0.3.20+0" [[deps.OpenJpeg_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Libtiff_jll", "LittleCMS_jll", "Pkg", "libpng_jll"] @@ -594,6 +608,7 @@ version = "2.4.0+0" [[deps.OpenLibm_jll]] deps = ["Artifacts", "Libdl"] uuid = "05823500-19ac-5b8b-9628-191a04bc5112" +version = "0.8.1+0" [[deps.OpenSSL_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] @@ -633,6 +648,7 @@ version = "2.5.1" [[deps.Pkg]] deps = ["Artifacts", "Dates", "Downloads", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"] uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" +version = "1.8.0" [[deps.PolyhedralRelaxations]] deps = ["DataStructures", "ForwardDiff", "JuMP", "Logging", "LoggingExtras"] @@ -672,7 +688,9 @@ uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" [[deps.REopt]] deps = ["ArchGDAL", "CoolProp", "Dates", "DelimitedFiles", "HTTP", "JLD", "JSON", "JuMP", "LinDistFlow", "LinearAlgebra", "Logging", "MathOptInterface", "Requires", "Roots", "Statistics", "TestEnv"] -git-tree-sha1 = "01951cf0e5b4fbdfd9f3a63fb1076346c3fe3b69" +git-tree-sha1 = "48d65155a002d044abbc87eac6e1c1420a115c53" +repo-rev = "error-msgs" +repo-url = "https://github.com/NREL/REopt.jl.git" uuid = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6" version = "0.22.0" @@ -710,6 +728,7 @@ version = "2.0.8" [[deps.SHA]] uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" +version = "0.7.0" [[deps.SQLite_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"] @@ -758,9 +777,9 @@ version = "0.1.1" [[deps.StaticArrays]] deps = ["LinearAlgebra", "Random", "StaticArraysCore", "Statistics"] -git-tree-sha1 = "4e051b85454b4e4f66e6a6b7bdc452ad9da3dcf6" +git-tree-sha1 = "ffc098086f35909741f71ce21d03dadf0d2bfa76" uuid = "90137ffa-7385-5640-81b9-e52037218182" -version = "1.5.10" +version = "1.5.11" [[deps.StaticArraysCore]] git-tree-sha1 = "6b7ba252635a5eff6a0b0664a41ee140a1c9e72a" @@ -774,6 +793,7 @@ uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" [[deps.TOML]] deps = ["Dates"] uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76" +version = "1.0.0" [[deps.TableTraits]] deps = ["IteratorInterfaceExtensions"] @@ -790,6 +810,7 @@ version = "1.10.0" [[deps.Tar]] deps = ["ArgTools", "SHA"] uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" +version = "1.10.1" [[deps.TensorCore]] deps = ["LinearAlgebra"] @@ -803,9 +824,9 @@ uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [[deps.TestEnv]] deps = ["Pkg"] -git-tree-sha1 = "43519ae06e007949a0e889f3234cf85875ac7e34" +git-tree-sha1 = "e458e63fc410c725194e7705c0960bd3d910063a" uuid = "1e6cf692-eddd-4d53-88a5-2d735e33781b" -version = "1.7.3" +version = "1.8.1" [[deps.Thrift_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "boost_jll"] @@ -820,9 +841,9 @@ uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa" version = "0.9.9" [[deps.URIs]] -git-tree-sha1 = "e59ecc5a41b000fa94423a578d29290c7266fc10" +git-tree-sha1 = "ac00576f90d8a259f2c9d823e91d1de3fd44d348" uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4" -version = "1.4.0" +version = "1.4.1" [[deps.UUIDs]] deps = ["Random", "SHA"] @@ -858,6 +879,7 @@ version = "0.15.5" [[deps.Zlib_jll]] deps = ["Libdl"] uuid = "83775a58-1f1d-513f-b197-d71354ab007a" +version = "1.2.12+3" [[deps.Zstd_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] @@ -874,6 +896,7 @@ version = "1.76.0+1" [[deps.libblastrampoline_jll]] deps = ["Artifacts", "Libdl", "OpenBLAS_jll"] uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" +version = "5.1.1+0" [[deps.libgeotiff_jll]] deps = ["Artifacts", "JLLWrappers", "LibCURL_jll", "Libdl", "Libtiff_jll", "PROJ_jll", "Pkg"] @@ -890,10 +913,12 @@ version = "1.6.38+0" [[deps.nghttp2_jll]] deps = ["Artifacts", "Libdl"] uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" +version = "1.48.0+0" [[deps.p7zip_jll]] deps = ["Artifacts", "Libdl"] uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" +version = "17.4.0+0" [[deps.snappy_jll]] deps = ["Artifacts", "JLLWrappers", "LZO_jll", "Libdl", "Pkg", "Zlib_jll"] From 6e3b945005eda66b1e7e53699bc209521bf9b399 Mon Sep 17 00:00:00 2001 From: "Rathod, Bhavesh" Date: Tue, 29 Nov 2022 13:58:58 -0500 Subject: [PATCH 31/53] Update views.py Add missing imports --- job/views.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/job/views.py b/job/views.py index 877296c9f..62d9168cc 100644 --- a/job/views.py +++ b/job/views.py @@ -38,7 +38,10 @@ ElectricTariffInputs, ElectricUtilityInputs, SpaceHeatingLoadInputs, PVOutputs, ElectricStorageOutputs, WindOutputs, ExistingBoilerInputs,\ GeneratorOutputs, ElectricTariffOutputs, ElectricUtilityOutputs, ElectricLoadOutputs, ExistingBoilerOutputs, REoptjlMessageOutputs, \ DomesticHotWaterLoadInputs, SiteInputs, SiteOutputs, APIMeta, UserProvidedMeta, CHPInputs, CHPOutputs - +import os +import requests +import logging +log = logging.getLogger(__name__) def make_error_resp(msg): resp = dict() From 05a06a35e3fd6968e87df951c76fac73c15ca216 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Sun, 4 Dec 2022 21:05:31 -0700 Subject: [PATCH 32/53] Add imports for db error handling --- job/src/process_results.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/job/src/process_results.py b/job/src/process_results.py index 8b960a704..adc9f83fe 100644 --- a/job/src/process_results.py +++ b/job/src/process_results.py @@ -33,6 +33,8 @@ import logging log = logging.getLogger(__name__) +import sys +import traceback def process_results(results: dict, run_uuid: str) -> None: """ @@ -95,6 +97,6 @@ def update_inputs_in_database(inputs_to_update: dict, run_uuid: str) -> None: debug_msg = "exc_type: {}; exc_value: {}; exc_traceback: {}".format( exc_type, exc_value.args[0], - tb.format_tb(exc_traceback) + traceback.format_tb(exc_traceback) ) log.debug(debug_msg) From 978317f343e6d9a78bec61323031b71c196257e8 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Sun, 4 Dec 2022 21:22:30 -0700 Subject: [PATCH 33/53] Fix merge-deletion of CHP from inputs_with_defaults_set_in_julia --- julia_src/http.jl | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/julia_src/http.jl b/julia_src/http.jl index 5874fbc7b..2c6c4bc1e 100644 --- a/julia_src/http.jl +++ b/julia_src/http.jl @@ -92,10 +92,21 @@ function reopt(req::HTTP.Request) :emissions_factor_series_lb_CO2_per_kwh, :emissions_factor_series_lb_NOx_per_kwh, :emissions_factor_series_lb_SO2_per_kwh, :emissions_factor_series_lb_PM25_per_kwh ] + if haskey(d, "CHP") + inputs_with_defaults_from_chp = [ + :installed_cost_per_kw, :tech_sizes_for_cost_curve, :om_cost_per_kwh, + :electric_efficiency_full_load, :thermal_efficiency_full_load, :min_allowable_kw, + :cooling_thermal_factor, :min_turn_down_fraction, :unavailability_periods + ] + chp_dict = Dict(key=>getfield(model_inputs.s.chp, key) for key in inputs_with_defaults_from_chp) + else + chp_dict = Dict() + end inputs_with_defaults_set_in_julia = Dict( "Financial" => Dict(key=>getfield(model_inputs.s.financial, key) for key in inputs_with_defaults_from_easiur), - "ElectricUtility" => Dict(key=>getfield(model_inputs.s.electric_utility, key) for key in inputs_with_defaults_from_avert) - ) + "ElectricUtility" => Dict(key=>getfield(model_inputs.s.electric_utility, key) for key in inputs_with_defaults_from_avert), + "CHP" => chp_dict + ) catch e @error "Something went wrong in REopt optimization!" exception=(e, catch_backtrace()) error_response["error"] = sprint(showerror, e) # append instead of rewrite? From 5783db2be700b8049e243bf6d133ba5dafe4c947 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Sun, 4 Dec 2022 21:50:46 -0700 Subject: [PATCH 34/53] Attempt to fix assumed auto-merge deletion This test had a chunk missing from it --- job/test/test_job_endpoint.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/job/test/test_job_endpoint.py b/job/test/test_job_endpoint.py index 2bf1aef58..3a702327f 100644 --- a/job/test/test_job_endpoint.py +++ b/job/test/test_job_endpoint.py @@ -103,6 +103,14 @@ def test_process_reopt_error(self): post_file = os.path.join('job', 'test', 'posts', 'handle_reopt_error.json') post = json.load(open(post_file, 'r')) + + resp = self.api_client.post('/dev/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'/dev/job/{run_uuid}/results') + r = json.loads(resp.content) assert('errors' in r["messages"].keys()) assert('warnings' in r["messages"].keys()) assert(resp.status_code==400) @@ -160,4 +168,4 @@ def test_superset_input_fields(self): results = r["outputs"] self.assertAlmostEqual(results["Financial"]["npv"], 165.21, places=-2) - assert(resp.status_code==200) \ No newline at end of file + assert(resp.status_code==200) \ No newline at end of file From 2690866ee8fad4cd6e7f00c14f7e9d09485afeb0 Mon Sep 17 00:00:00 2001 From: "Rathod, Bhavesh" Date: Mon, 19 Dec 2022 10:26:43 -0500 Subject: [PATCH 35/53] Update TOML files and duplicate migration files --- ...=> 0016_reoptjlmessageoutputs_and_more.py} | 4 +- julia_src/Manifest.toml | 61 ++++++++++--------- julia_src/Project.toml | 4 +- 3 files changed, 36 insertions(+), 33 deletions(-) rename job/migrations/{0015_reoptjlmessageoutputs_and_more.py => 0016_reoptjlmessageoutputs_and_more.py} (96%) diff --git a/job/migrations/0015_reoptjlmessageoutputs_and_more.py b/job/migrations/0016_reoptjlmessageoutputs_and_more.py similarity index 96% rename from job/migrations/0015_reoptjlmessageoutputs_and_more.py rename to job/migrations/0016_reoptjlmessageoutputs_and_more.py index ccb1f7154..61a8d9a6f 100644 --- a/job/migrations/0015_reoptjlmessageoutputs_and_more.py +++ b/job/migrations/0016_reoptjlmessageoutputs_and_more.py @@ -1,4 +1,4 @@ -# Generated by Django 4.0.6 on 2022-11-29 18:15 +# Generated by Django 4.0.7 on 2022-12-19 15:24 import django.contrib.postgres.fields import django.core.validators @@ -10,7 +10,7 @@ class Migration(migrations.Migration): dependencies = [ - ('job', '0014_rename_thermal_to_tes_series_mmbtu_per_hour_existingboileroutputs_year_one_fuel_consumption_series_m'), + ('job', '0015_coolingloadinputs_coolingloadoutputs_and_more'), ] operations = [ diff --git a/julia_src/Manifest.toml b/julia_src/Manifest.toml index 776f2c641..88492d891 100644 --- a/julia_src/Manifest.toml +++ b/julia_src/Manifest.toml @@ -74,10 +74,10 @@ uuid = "fa961155-64e5-5f13-b03f-caf6b980ea82" version = "0.4.2" [[deps.CSV]] -deps = ["CodecZlib", "Dates", "FilePathsBase", "InlineStrings", "Mmap", "Parsers", "PooledArrays", "SentinelArrays", "Tables", "Unicode", "WeakRefStrings"] -git-tree-sha1 = "c5fd7cd27ac4aed0acf4b73948f0110ff2a854b2" +deps = ["CodecZlib", "Dates", "FilePathsBase", "InlineStrings", "Mmap", "Parsers", "PooledArrays", "SentinelArrays", "SnoopPrecompile", "Tables", "Unicode", "WeakRefStrings", "WorkerUtilities"] +git-tree-sha1 = "8c73e96bd6817c2597cfd5615b91fca5deccf1af" uuid = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" -version = "0.10.7" +version = "0.10.8" [[deps.ChainRulesCore]] deps = ["Compat", "LinearAlgebra", "SparseArrays"] @@ -117,9 +117,9 @@ version = "0.9.9" [[deps.Colors]] deps = ["ColorTypes", "FixedPointNumbers", "Reexport"] -git-tree-sha1 = "417b0ed7b8b838aa6ca0a87aadf1bb9eb111ce40" +git-tree-sha1 = "fc08e5930ee9a4e03f84bfb5211cb54e7769758a" uuid = "5ae59095-9a9b-59fe-a467-6f913c188581" -version = "0.12.8" +version = "0.12.10" [[deps.CommonSolve]] git-tree-sha1 = "9441451ee712d1aec22edad62db1a9af3dc8d852" @@ -162,9 +162,9 @@ uuid = "3351c21f-4feb-5f29-afb9-f4fcb0e27549" version = "6.4.2+0" [[deps.DataAPI]] -git-tree-sha1 = "e08915633fcb3ea83bf9d6126292e5bc5c739922" +git-tree-sha1 = "e8119c1a33d267e16108be441a287a6981ba1630" uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a" -version = "1.13.0" +version = "1.14.0" [[deps.DataStructures]] deps = ["Compat", "InteractiveUtils", "OrderedCollections"] @@ -209,9 +209,9 @@ uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b" [[deps.DocStringExtensions]] deps = ["LibGit2"] -git-tree-sha1 = "c36550cb29cbe373e95b3f40486b9a4148f89ffd" +git-tree-sha1 = "2fb1e02f2b635d0845df5d7c167fec4dd739b00d" uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" -version = "0.9.2" +version = "0.9.3" [[deps.Downloads]] deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"] @@ -258,9 +258,9 @@ version = "0.8.4" [[deps.ForwardDiff]] deps = ["CommonSubexpressions", "DiffResults", "DiffRules", "LinearAlgebra", "LogExpFunctions", "NaNMath", "Preferences", "Printf", "Random", "SpecialFunctions", "StaticArrays"] -git-tree-sha1 = "10fa12fe96e4d76acfa738f4df2126589a67374f" +git-tree-sha1 = "a69dd6db8a809f78846ff259298678f0d6212180" uuid = "f6369f11-7733-5829-9624-2563aa707210" -version = "0.10.33" +version = "0.10.34" [[deps.Future]] deps = ["Random"] @@ -365,9 +365,9 @@ uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" [[deps.IntervalSets]] deps = ["Dates", "Random", "Statistics"] -git-tree-sha1 = "3f91cd3f56ea48d4d2a75c2a65455c5fc74fa347" +git-tree-sha1 = "16c0cc91853084cb5f58a78bd209513900206ce6" uuid = "8197267c-284f-5f27-9208-e0e47529a953" -version = "0.7.3" +version = "0.7.4" [[deps.InverseFunctions]] deps = ["Test"] @@ -392,9 +392,9 @@ version = "1.0.0" [[deps.JLD]] deps = ["Compat", "FileIO", "H5Zblosc", "HDF5", "Printf"] -git-tree-sha1 = "cd46c18390e9bbc37a2098dfb355ec5f18931900" +git-tree-sha1 = "ec6afa4fd3402e4dd5b15b3e5dd2f7dd52043ce8" uuid = "4138dd39-2aa7-5051-a626-17a0bb65d9c8" -version = "0.13.2" +version = "0.13.3" [[deps.JLLWrappers]] deps = ["Preferences"] @@ -468,9 +468,9 @@ uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" [[deps.Libiconv_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] -git-tree-sha1 = "42b62845d70a619f063a7da093d995ec8e15e778" +git-tree-sha1 = "c7cb1f5d892775ba13767a87c7ada0b980ea0a71" uuid = "94ce4f54-9a6c-5748-9c1c-f9c7231a4531" -version = "1.16.1+1" +version = "1.16.1+2" [[deps.Libtiff_jll]] deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "LERC_jll", "Libdl", "Pkg", "Zlib_jll", "Zstd_jll"] @@ -688,11 +688,9 @@ uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" [[deps.REopt]] deps = ["ArchGDAL", "CoolProp", "Dates", "DelimitedFiles", "HTTP", "JLD", "JSON", "JuMP", "LinDistFlow", "LinearAlgebra", "Logging", "MathOptInterface", "Requires", "Roots", "Statistics", "TestEnv"] -git-tree-sha1 = "48d65155a002d044abbc87eac6e1c1420a115c53" -repo-rev = "error-msgs" -repo-url = "https://github.com/NREL/REopt.jl.git" +git-tree-sha1 = "92e8055867af9460faba381c44778d7634d54b7b" uuid = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6" -version = "0.22.0" +version = "0.23.0" [[deps.Random]] deps = ["SHA", "Serialization"] @@ -705,9 +703,9 @@ version = "0.3.2" [[deps.RecipesBase]] deps = ["SnoopPrecompile"] -git-tree-sha1 = "d12e612bba40d189cead6ff857ddb67bd2e6a387" +git-tree-sha1 = "18c35ed630d7229c5584b945641a73ca83fb5213" uuid = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" -version = "1.3.1" +version = "1.3.2" [[deps.Reexport]] git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b" @@ -732,9 +730,9 @@ version = "0.7.0" [[deps.SQLite_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"] -git-tree-sha1 = "b89fe49b6a19cde7aefa7e7cf013c1160367f37d" +git-tree-sha1 = "2c761a91fb503e94bd0130fcf4352166c3c555bc" uuid = "76ed43ae-9a5d-5a62-8c75-30186b810ce8" -version = "3.40.0+0" +version = "3.40.0+1" [[deps.SentinelArrays]] deps = ["Dates", "Random"] @@ -836,9 +834,9 @@ version = "0.16.0+0" [[deps.TranscodingStreams]] deps = ["Random", "Test"] -git-tree-sha1 = "8a75929dcd3c38611db2f8d08546decb514fcadf" +git-tree-sha1 = "e4bdc63f5c6d62e80eb1c0043fcc0360d5950ff7" uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa" -version = "0.9.9" +version = "0.9.10" [[deps.URIs]] git-tree-sha1 = "ac00576f90d8a259f2c9d823e91d1de3fd44d348" @@ -854,9 +852,9 @@ uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" [[deps.Unitful]] deps = ["ConstructionBase", "Dates", "LinearAlgebra", "Random"] -git-tree-sha1 = "bdc60e70c8a2f63626deeb5c177baacfc43f8a59" +git-tree-sha1 = "d670a70dd3cdbe1c1186f2f17c9a68a7ec24838c" uuid = "1986cc42-f94f-5a68-af5c-568840ba703d" -version = "1.12.1" +version = "1.12.2" [[deps.WeakRefStrings]] deps = ["DataAPI", "InlineStrings", "Parsers"] @@ -864,6 +862,11 @@ git-tree-sha1 = "b1be2855ed9ed8eac54e5caff2afcdb442d52c23" uuid = "ea10d353-3f73-51f8-a26c-33c1cb351aa5" version = "1.4.2" +[[deps.WorkerUtilities]] +git-tree-sha1 = "cd1659ba0d57b71a464a29e64dbc67cfe83d54e7" +uuid = "76eceee3-57b5-4d4a-8e66-0e911cebbf60" +version = "1.6.1" + [[deps.XML2_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Pkg", "Zlib_jll"] git-tree-sha1 = "58443b63fb7e465a8a7210828c91c08b92132dff" diff --git a/julia_src/Project.toml b/julia_src/Project.toml index 00197fe52..c05682dc6 100644 --- a/julia_src/Project.toml +++ b/julia_src/Project.toml @@ -5,9 +5,9 @@ JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" JuMP = "4076af6c-e467-56ae-b986-b466b2749572" MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" MutableArithmetics = "d8a4904e-b15c-11e9-3269-09a3773c0cb0" +PROJ_jll = "58948b4f-47e0-5654-a9ad-f609743f8632" REopt = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6" Xpress = "9e70acf3-d6c9-5be6-b5bd-4e2c73e3e054" -PROJ_jll = "58948b4f-47e0-5654-a9ad-f609743f8632" [compat] -PROJ_jll = "900.0.0" \ No newline at end of file +PROJ_jll = "900.0.0" From bdd3280ba5ea4f9a5705e2d18d007227354c5a01 Mon Sep 17 00:00:00 2001 From: zolanaj Date: Mon, 19 Dec 2022 14:18:33 -0700 Subject: [PATCH 36/53] Add ExistingChiller to process_results --- job/src/process_results.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/job/src/process_results.py b/job/src/process_results.py index 1ebd0791f..3a168df2f 100644 --- a/job/src/process_results.py +++ b/job/src/process_results.py @@ -72,6 +72,8 @@ def process_results(results: dict, run_uuid: str) -> None: # BoilerOutputs.create(meta=meta, **results["Boiler"]).save() if "ExistingBoiler" in results.keys(): ExistingBoilerOutputs.create(meta=meta, **results["ExistingBoiler"]).save() + if "ExistingChiller" in results.keys(): + ExistingChillerOutputs.create(meta=meta, **results["ExistingChiller"]).save() if "Messages" in results.keys(): REoptjlMessageOutputs.create(meta=meta, **results["Messages"]).save() if "HeatingLoad" in results.keys(): From 5a30f732672e94768fd1ad602456722092525103 Mon Sep 17 00:00:00 2001 From: zolanaj Date: Mon, 19 Dec 2022 14:42:09 -0700 Subject: [PATCH 37/53] update TES database migrations --- ...hermalstorageinputs_coldthermalstorageoutputs_and_more.py} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename job/migrations/{0012_coldthermalstorageinputs_coldthermalstorageoutputs_and_more.py => 0016_coldthermalstorageinputs_coldthermalstorageoutputs_and_more.py} (98%) diff --git a/job/migrations/0012_coldthermalstorageinputs_coldthermalstorageoutputs_and_more.py b/job/migrations/0016_coldthermalstorageinputs_coldthermalstorageoutputs_and_more.py similarity index 98% rename from job/migrations/0012_coldthermalstorageinputs_coldthermalstorageoutputs_and_more.py rename to job/migrations/0016_coldthermalstorageinputs_coldthermalstorageoutputs_and_more.py index 0dbd54a05..50c0d4b3c 100644 --- a/job/migrations/0012_coldthermalstorageinputs_coldthermalstorageoutputs_and_more.py +++ b/job/migrations/0016_coldthermalstorageinputs_coldthermalstorageoutputs_and_more.py @@ -1,4 +1,4 @@ -# Generated by Django 4.0.6 on 2022-11-11 22:40 +# Generated by Django 4.0.7 on 2022-12-19 21:41 import django.contrib.postgres.fields import django.core.validators @@ -10,7 +10,7 @@ class Migration(migrations.Migration): dependencies = [ - ('job', '0011_coolingloadinputs_coolingloadoutputs_and_more'), + ('job', '0015_coolingloadinputs_coolingloadoutputs_and_more'), ] operations = [ From 3c947ec2134747341a8bae2dbfe51ba928490017 Mon Sep 17 00:00:00 2001 From: Rathod Date: Tue, 20 Dec 2022 09:44:14 -0500 Subject: [PATCH 38/53] Update CHANGELOG.md --- CHANGELOG.md | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a29f86502..b5062bd9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,28 @@ Classify the change according to the following categories: ##### Removed ### Patches +## Develop - 2022-11-22 +### Minor Updates +#### Added +1. **REoptjlMessageOutputs** model to capture errors and warnings returned by REoptjl during input processing and post optimization +2. Missing output fields for **ExistingBoilerOutputs** model +3. API test `job\test\posts\all_inputs_test.json` to include all input models in a single API test + +#### Changed +1. Default values for the following fields were changed to align them with REopt API v2 (i.e. stable, and REopt.jl) defaults. As-is, these values are aligned with REopt v1 defaults. Units were unchanged. +- **FinancialInputs.elec_cost_escalation_rate_fraction** from 0.023 to 0.019 +- **FinancialInputs.offtaker_discount_rate_fraction** from 0.083 to 0.0564 +- **FinancialInputs.owner_discount_rate_fraction** from 0.083 to 0.0564 +- **PVInputs.installed_cost_per_kw** from 1600 to 1592 +- **PVInputs.om_cost_per_kw** from 16 to 17 +- **WindInputs.om_cost_per_kw** from 16 to 35 +- **ElectricStorageInputs.installed_cost_per_kw** from 840 to 775 +- **ElectricStorageInputs.installed_cost_per_kwh** from 420 to 388 +- **ElectricStorageInputs.replace_cost_per_kw** from 410 to 440 +- **ElectricStorageInputs.replace_cost_per_kwh** from 200 to 220 +2. Modified `julia_src\http.jl` and `julia_src\cbc\http.jl` to return status 400 when REopt responds with an error +3. Updated `r["Messages"]` in `views.py` to include **REoptjlMessageOutputs** errors and warnings + ## Develop - 2022-11-11 ### Minor Updates ##### Added @@ -63,28 +85,6 @@ In `job/views.py`: ##### Fixed Lookback charge parameters expected from the URDB API call were changed to the non-caplitalized format, so they are now used properly. -## Develop - 2022-11-22 -### Minor Updates -#### Added -1. **REoptjlMessageOutputs** model to capture errors and warnings returned by REoptjl during input processing and post optimization -2. Missing output fields for **ExistingBoilerOutputs** model -3. API test `job\test\posts\all_inputs_test.json` to include all input models in a single API test - -#### Changed -1. Default values for the following fields were changed to align them with REopt API v2 (and REopt.jl) defaults. As-is, these values are aligned with REopt v1 defaults. Units were unchanged. -- **FinancialInputs.elec_cost_escalation_rate_fraction** from 0.023 to 0.019 -- **FinancialInputs.offtaker_discount_rate_fraction** from 0.083 to 0.0564 -- **FinancialInputs.owner_discount_rate_fraction** from 0.083 to 0.0564 -- **PVInputs.installed_cost_per_kw** from 1600 to 1592 -- **PVInputs.om_cost_per_kw** from 16 to 17 -- **WindInputs.om_cost_per_kw** from 16 to 35 -- **ElectricStorageInputs.installed_cost_per_kw** from 840 to 775 -- **ElectricStorageInputs.installed_cost_per_kwh** from 420 to 388 -- **ElectricStorageInputs.replace_cost_per_kw** from 410 to 440 -- **ElectricStorageInputs.replace_cost_per_kwh** from 200 to 220 -2. Modified `julia_src\http.jl` and `julia_src\cbc\http.jl` to return status 400 when REopt responds with an error -3. Updated `r["Messages"]` in `validators.py` to include **REoptjlMessageOutputs** errors and warnings - ## v2.3.0 ##### Changed The following name changes were made in the `job/` endpoint and `julia_src/http.jl`: From 2143259bf5441da08ebf70a038b2e1f5d6d0f938 Mon Sep 17 00:00:00 2001 From: zolanaj Date: Wed, 21 Dec 2022 09:52:22 -0700 Subject: [PATCH 39/53] add therrmal storage to test_job_endpoint cooling and heating loads test --- job/test/test_job_endpoint.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/job/test/test_job_endpoint.py b/job/test/test_job_endpoint.py index 4ef75823a..3aff7a69c 100644 --- a/job/test/test_job_endpoint.py +++ b/job/test/test_job_endpoint.py @@ -203,6 +203,14 @@ def test_cooling_possible_sets_and_results(self): "min_turn_down_fraction": 0.1, "thermal_efficiency_full_load": 0.45, "thermal_efficiency_half_load": 0.45 + }, + "HotThermalStorage":{ + "min_gal":2500, + "max_gal":2500 + }, + "ColdThermalStorage":{ + "min_gal":2500, + "max_gal":2500 } } @@ -221,6 +229,8 @@ def test_cooling_possible_sets_and_results(self): self.assertIn("ExistingChiller",list(results.keys())) self.assertIn("ExistingBoiler", list(results.keys())) self.assertIn("HeatingLoad", list(results.keys())) + self.assertIn("HotThermalStorage", list(results.keys())) + self.assertIn("ColdThermalStorage", list(results.keys())) def test_chp_defaults_from_julia(self): From b5c0740a11e527bc2e85db8957c722067d5a8e00 Mon Sep 17 00:00:00 2001 From: zolanaj Date: Wed, 21 Dec 2022 09:53:56 -0700 Subject: [PATCH 40/53] fix time series SOC in ColdThermalStorageOutputs (units=ton) --- ...thermalstorageinputs_coldthermalstorageoutputs_and_more.py | 4 ++-- job/models.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/job/migrations/0016_coldthermalstorageinputs_coldthermalstorageoutputs_and_more.py b/job/migrations/0016_coldthermalstorageinputs_coldthermalstorageoutputs_and_more.py index 50c0d4b3c..c713d088d 100644 --- a/job/migrations/0016_coldthermalstorageinputs_coldthermalstorageoutputs_and_more.py +++ b/job/migrations/0016_coldthermalstorageinputs_coldthermalstorageoutputs_and_more.py @@ -1,4 +1,4 @@ -# Generated by Django 4.0.7 on 2022-12-19 21:41 +# Generated by Django 4.0.7 on 2022-12-21 16:52 import django.contrib.postgres.fields import django.core.validators @@ -42,7 +42,7 @@ class Migration(migrations.Migration): ('meta', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='ColdThermalStorageOutputs', serialize=False, to='job.apimeta')), ('size_gal', models.FloatField(blank=True, null=True)), ('year_one_soc_series_fraction', django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, size=None)), - ('year_one_to_load_series_mmbtu_per_hour', django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, size=None)), + ('year_one_to_load_series_ton', django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, size=None)), ], bases=(job.models.BaseModel, models.Model), ), diff --git a/job/models.py b/job/models.py index 75e360941..db3cbb396 100644 --- a/job/models.py +++ b/job/models.py @@ -4944,7 +4944,7 @@ class ColdThermalStorageOutputs(BaseModel, models.Model): models.FloatField(null=True, blank=True), default = list, ) - year_one_to_load_series_mmbtu_per_hour = ArrayField( + year_one_to_load_series_ton = ArrayField( models.FloatField(null=True, blank=True), default = list, ) From fa0e2c114307e785b6a4aa425842d0a94d4baa15 Mon Sep 17 00:00:00 2001 From: zolanaj Date: Wed, 21 Dec 2022 09:54:32 -0700 Subject: [PATCH 41/53] add ColdThermalStorageInputs read-in from run_uuid --- job/models.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/job/models.py b/job/models.py index db3cbb396..c01b12398 100644 --- a/job/models.py +++ b/job/models.py @@ -5518,6 +5518,9 @@ def filter_none_and_empty_array(d:dict): try: d["HotThermalStorage"] = filter_none_and_empty_array(meta.HotThermalStorageInputs.dict) except: pass + + try: d["ColdThermalStorage"] = filter_none_and_empty_array(meta.ColdThermalStorageInputs.dict) + except: pass try: d["CHP"] = filter_none_and_empty_array(meta.CHPInputs.dict) except: pass From 888df47a71cee5926018b95e9115b38851bba8ee Mon Sep 17 00:00:00 2001 From: zolanaj Date: Wed, 21 Dec 2022 10:56:07 -0700 Subject: [PATCH 42/53] fix: cold_water_temp_degF <-- cool_water_temp_degF in HotThermalStorageInputs --- ...thermalstorageinputs_coldthermalstorageoutputs_and_more.py | 4 ++-- job/models.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/job/migrations/0016_coldthermalstorageinputs_coldthermalstorageoutputs_and_more.py b/job/migrations/0016_coldthermalstorageinputs_coldthermalstorageoutputs_and_more.py index c713d088d..5a8d58e6b 100644 --- a/job/migrations/0016_coldthermalstorageinputs_coldthermalstorageoutputs_and_more.py +++ b/job/migrations/0016_coldthermalstorageinputs_coldthermalstorageoutputs_and_more.py @@ -1,4 +1,4 @@ -# Generated by Django 4.0.7 on 2022-12-21 16:52 +# Generated by Django 4.0.7 on 2022-12-21 17:55 import django.contrib.postgres.fields import django.core.validators @@ -53,7 +53,7 @@ class Migration(migrations.Migration): ('min_gal', models.FloatField(blank=True, default=0.0, help_text='Minimum TES volume (energy) size constraint for optimization', null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000000000.0)])), ('max_gal', models.FloatField(blank=True, default=0.0, help_text='Maximum TES volume (energy) size constraint for optimization. Set to zero to disable storage', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000000000.0)])), ('hot_water_temp_degF', models.FloatField(blank=True, default=180.0, help_text='Hot-side supply water temperature from HotTES (top of tank) to the heating load', validators=[django.core.validators.MinValueValidator(40.0), django.core.validators.MaxValueValidator(210.0)])), - ('cold_water_temp_degF', models.FloatField(blank=True, default=160.0, help_text='Cold-side return water temperature from the heating load to the HotTES (bottom of tank)', validators=[django.core.validators.MinValueValidator(33.0), django.core.validators.MaxValueValidator(200.0)])), + ('cool_water_temp_degF', models.FloatField(blank=True, default=160.0, help_text='Cold-side return water temperature from the heating load to the HotTES (bottom of tank)', validators=[django.core.validators.MinValueValidator(33.0), django.core.validators.MaxValueValidator(200.0)])), ('internal_efficiency_fraction', models.FloatField(blank=True, default=0.999999, help_text='Thermal losses due to mixing from thermal power entering or leaving tank', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1.0)])), ('soc_min_fraction', models.FloatField(blank=True, default=0.1, help_text='Minimum allowable battery state of charge as fraction of energy capacity.', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1.0)])), ('soc_init_fraction', models.FloatField(blank=True, default=0.5, help_text='Battery state of charge at first hour of optimization as fraction of energy capacity.', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1.0)])), diff --git a/job/models.py b/job/models.py index c01b12398..5d1a2fab7 100644 --- a/job/models.py +++ b/job/models.py @@ -4649,7 +4649,7 @@ class HotThermalStorageInputs(BaseModel, models.Model): default=180.0, help_text="Hot-side supply water temperature from HotTES (top of tank) to the heating load" ) - cold_water_temp_degF = models.FloatField( + cool_water_temp_degF = models.FloatField( validators=[ MinValueValidator(33.0), MaxValueValidator(200.0) From e1bb8bb0cf0858daea5606a71069f7b6f306df0c Mon Sep 17 00:00:00 2001 From: zolanaj Date: Wed, 21 Dec 2022 13:21:51 -0700 Subject: [PATCH 43/53] Fix ColdThermalStorageOutputs creation in process_results --- job/src/process_results.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/job/src/process_results.py b/job/src/process_results.py index 485ee4478..60dbbc4d8 100644 --- a/job/src/process_results.py +++ b/job/src/process_results.py @@ -71,7 +71,7 @@ def process_results(results: dict, run_uuid: str) -> None: if "HotThermalStorage" in results.keys(): HotThermalStorageOutputs.create(meta=meta, **results["HotThermalStorage"]).save() if "ColdThermalStorage" in results.keys(): - HotThermalStorageOutputs.create(meta=meta, **results["ColdThermalStorage"]).save() + ColdThermalStorageOutputs.create(meta=meta, **results["ColdThermalStorage"]).save() if "HeatingLoad" in results.keys(): HeatingLoadOutputs.create(meta=meta, **results["HeatingLoad"]).save() if "CoolingLoad" in results.keys(): From 4c741c100b583d1f69c75d06fac22af03b229f3c Mon Sep 17 00:00:00 2001 From: hdunham <70401017+hdunham@users.noreply.github.com> Date: Thu, 22 Dec 2022 12:52:12 -0700 Subject: [PATCH 44/53] simplify process_results --- job/src/process_results.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/job/src/process_results.py b/job/src/process_results.py index 3a168df2f..2b30f73be 100644 --- a/job/src/process_results.py +++ b/job/src/process_results.py @@ -47,10 +47,9 @@ def process_results(results: dict, run_uuid: str) -> None: meta.status = results.get("status") meta.save(update_fields=["status"]) - if results.get("status") == "error": - if "Messages" in results.keys(): - REoptjlMessageOutputs.create(meta=meta, **results["Messages"]).save() - else: + if "Messages" in results.keys(): + REoptjlMessageOutputs.create(meta=meta, **results["Messages"]).save() + if results.get("status") != "error": FinancialOutputs.create(meta=meta, **results["Financial"]).save() ElectricTariffOutputs.create(meta=meta, **results["ElectricTariff"]).save() ElectricUtilityOutputs.create(meta=meta, **results["ElectricUtility"]).save() @@ -74,8 +73,6 @@ def process_results(results: dict, run_uuid: str) -> None: ExistingBoilerOutputs.create(meta=meta, **results["ExistingBoiler"]).save() if "ExistingChiller" in results.keys(): ExistingChillerOutputs.create(meta=meta, **results["ExistingChiller"]).save() - if "Messages" in results.keys(): - REoptjlMessageOutputs.create(meta=meta, **results["Messages"]).save() if "HeatingLoad" in results.keys(): HeatingLoadOutputs.create(meta=meta, **results["HeatingLoad"]).save() if "CoolingLoad" in results.keys(): From 7a3af5b3f893d577b4889d967c26c40c6c30e146 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Fri, 23 Dec 2022 10:18:37 -0700 Subject: [PATCH 45/53] Rename test and description to reflect actual --- job/test/test_job_endpoint.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/job/test/test_job_endpoint.py b/job/test/test_job_endpoint.py index 3aff7a69c..9ff2861b4 100644 --- a/job/test/test_job_endpoint.py +++ b/job/test/test_job_endpoint.py @@ -161,9 +161,9 @@ def test_off_grid_defaults(self): 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_cooling_possible_sets_and_results(self): + def test_thermal_in_results(self): """ - Purpose of this test is to test the validity of Cooling Load possible_sets, in particular []/null and blend/hybrid + Purpose of this test is to check that the expected thermal loads, techs, and storage are included in the results """ scenario = { "Settings": {"run_bau": False}, From a9098960e4451b0a37cfaa34fd7ed1aeef6c596f Mon Sep 17 00:00:00 2001 From: hdunham <70401017+hdunham@users.noreply.github.com> Date: Fri, 23 Dec 2022 15:25:39 -0700 Subject: [PATCH 46/53] simplify conditions --- job/src/run_jump_model.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/job/src/run_jump_model.py b/job/src/run_jump_model.py index 507de352d..749bc9172 100644 --- a/job/src/run_jump_model.py +++ b/job/src/run_jump_model.py @@ -93,8 +93,6 @@ 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"] - 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 results.update(time_dict) @@ -132,6 +130,7 @@ def run_jump_model(run_uuid): profiler.profileEnd() # TODO save profile times if status.strip().lower() != 'error': + inputs_with_defaults_set_in_julia = response_json["inputs_with_defaults_set_in_julia"] update_inputs_in_database(inputs_with_defaults_set_in_julia, run_uuid) process_results(results, run_uuid) return True From e44cea6b2454ed81eb34318c01177e525bbca0a1 Mon Sep 17 00:00:00 2001 From: hdunham <70401017+hdunham@users.noreply.github.com> Date: Fri, 23 Dec 2022 15:25:53 -0700 Subject: [PATCH 47/53] edit error text --- julia_src/http.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/julia_src/http.jl b/julia_src/http.jl index 2c6c4bc1e..2bff440aa 100644 --- a/julia_src/http.jl +++ b/julia_src/http.jl @@ -73,7 +73,7 @@ function reopt(req::HTTP.Request) try model_inputs = reoptjl.REoptInputs(d) catch e - @error "Something went wrong in REopt inputs processing!" exception=(e, catch_backtrace()) + @error "Something went wrong during REopt inputs processing!" exception=(e, catch_backtrace()) error_response["error"] = sprint(showerror, e) end From a9ba7ef022fdb6f79ba685db81f230dcbdd2e96e Mon Sep 17 00:00:00 2001 From: hdunham <70401017+hdunham@users.noreply.github.com> Date: Sun, 25 Dec 2022 14:57:33 -0700 Subject: [PATCH 48/53] try to correct wording of a docstring --- job/validators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/job/validators.py b/job/validators.py index 172eba1d4..3a849d7f3 100644 --- a/job/validators.py +++ b/job/validators.py @@ -171,7 +171,7 @@ def messages(self): def validated_input_dict(self): """ Passed to the Julia package, which can handle unused top level keys (such as messages) but will error if - keys that do not align with the Scenario struct fields are provided. For example, if Site.address is passed to + there are incorrect keys in the next level. For example, if Site.address is passed to the Julia package then a method error will be raised in Julia because the Site struct has no address field. :return: """ From 21f979e84279c594e5a45d0e406523a502d9a820 Mon Sep 17 00:00:00 2001 From: hdunham <70401017+hdunham@users.noreply.github.com> Date: Tue, 27 Dec 2022 11:25:09 -0500 Subject: [PATCH 49/53] remove unused code --- job/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/job/views.py b/job/views.py index 03d90c9f5..1c845c51c 100644 --- a/job/views.py +++ b/job/views.py @@ -149,7 +149,7 @@ def results(request, run_uuid): ).get(run_uuid=run_uuid) except Exception as e: if isinstance(e, models.ObjectDoesNotExist): - resp = {"messages": {"error": ""}} + resp = {"messages": {}} resp['messages']['error'] = ( "run_uuid {} not in database. " "You may have hit the results endpoint too quickly after POST'ing scenario, " From 59a3ee906e06f81b1df9bd53aaaffb77284233de Mon Sep 17 00:00:00 2001 From: hdunham <70401017+hdunham@users.noreply.github.com> Date: Tue, 27 Dec 2022 11:27:59 -0500 Subject: [PATCH 50/53] Revert "simplify conditions" This reverts commit a9098960e4451b0a37cfaa34fd7ed1aeef6c596f. --- job/src/run_jump_model.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/job/src/run_jump_model.py b/job/src/run_jump_model.py index 749bc9172..507de352d 100644 --- a/job/src/run_jump_model.py +++ b/job/src/run_jump_model.py @@ -93,6 +93,8 @@ 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"] + 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 results.update(time_dict) @@ -130,7 +132,6 @@ def run_jump_model(run_uuid): profiler.profileEnd() # TODO save profile times if status.strip().lower() != 'error': - inputs_with_defaults_set_in_julia = response_json["inputs_with_defaults_set_in_julia"] update_inputs_in_database(inputs_with_defaults_set_in_julia, run_uuid) process_results(results, run_uuid) return True From 1a420c494c3e9523ae4f421fcdde640cb444f929 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Wed, 28 Dec 2022 21:21:06 -0700 Subject: [PATCH 51/53] Make new version tag for new changes --- CHANGELOG.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1081ee1f1..3f12af413 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,27 +27,31 @@ Classify the change according to the following categories: ### Patches +## v2.5.1 +### Minor Updates +#### Added +- added **HotThermalStorageInputs** model +- added **HotThermalStorageOutputs** model +- added **ColdThermalStorageInputs** model +- added **ColdThermalStorageOutputs** model +- add **HotThermalStorageOutputs** +- add **ColdThermalStorageOutputs** +- `0012_coldthermalstorageinputs....` file used to add new models to the db + ## v2.5.0 ### Minor Updates ##### Added - `0011_coolingloadinputs....` file used to add new models to the db -- `0012_coldthermalstorageinputs....` file used to add new models to the db In `job/models.py`: - added **ExistingChillerInputs** model - added **ExistingChillerOutputs** model - added **CoolingLoadInputs** model - added **CoolingLoadOutputs** model - added **HeatingLoadOutputs** model -- added **HotThermalStorageInputs** model -- added **HotThermalStorageOutputs** model -- added **ColdThermalStorageInputs** model -- added **ColdThermalStorageOutputs** model In `job/process_results.py`: - add **ExistingChillerOutputs** - add **CoolingLoadOutputs** - add **HeatingLoadOutputs** -- add **HotThermalStorageOutputs** -- add **ColdThermalStorageOutputs** In `job/validators.py: - add time series length validation on **CoolingLoadInputs->thermal_loads_ton** and **CoolingLoadInputs->per_time_step_fractions_of_electric_load** In `job/views.py`: From 735fac4376f7dc225d7b30ed777adc72c19fec4d Mon Sep 17 00:00:00 2001 From: hdunham <70401017+hdunham@users.noreply.github.com> Date: Mon, 2 Jan 2023 11:21:13 -0500 Subject: [PATCH 52/53] merge migrations --- job/migrations/0017_merge_20230102_1621.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 job/migrations/0017_merge_20230102_1621.py diff --git a/job/migrations/0017_merge_20230102_1621.py b/job/migrations/0017_merge_20230102_1621.py new file mode 100644 index 000000000..557decdf2 --- /dev/null +++ b/job/migrations/0017_merge_20230102_1621.py @@ -0,0 +1,14 @@ +# Generated by Django 4.0.4 on 2023-01-02 16:21 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('job', '0016_coldthermalstorageinputs_coldthermalstorageoutputs_and_more'), + ('job', '0016_reoptjlmessageoutputs_and_more'), + ] + + operations = [ + ] From 65610e970cad17b9b381cef342bfe07b345079d8 Mon Sep 17 00:00:00 2001 From: Bill Becker <42586683+Bill-Becker@users.noreply.github.com> Date: Tue, 3 Jan 2023 09:26:21 -0700 Subject: [PATCH 53/53] Merge 2 PR updates to CHANGELOG.md and new version --- CHANGELOG.md | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 84146c243..c0fd4f313 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,12 +26,19 @@ Classify the change according to the following categories: ##### Removed ### Patches -## Develop - 2022-11-22 +## v2.6.0 ### Minor Updates #### Added 1. **REoptjlMessageOutputs** model to capture errors and warnings returned by REoptjl during input processing and post optimization 2. Missing output fields for **ExistingBoilerOutputs** model 3. API test `job\test\posts\all_inputs_test.json` to include all input models in a single API test +- added **HotThermalStorageInputs** model +- added **HotThermalStorageOutputs** model +- added **ColdThermalStorageInputs** model +- added **ColdThermalStorageOutputs** model +- add **HotThermalStorageOutputs** +- add **ColdThermalStorageOutputs** +- `0012_coldthermalstorageinputs....` file used to add new models to the db #### Changed 1. Default values for the following fields were changed to align them with REopt API v2 (i.e. stable, and REopt.jl) defaults. As-is, these values are aligned with REopt v1 defaults. Units were unchanged. @@ -48,17 +55,6 @@ Classify the change according to the following categories: 2. Modified `julia_src\http.jl` and `julia_src\cbc\http.jl` to return status 400 when REopt responds with an error 3. Updated `r["Messages"]` in `views.py` to include **REoptjlMessageOutputs** errors and warnings -## v2.5.1 -### Minor Updates -#### Added -- added **HotThermalStorageInputs** model -- added **HotThermalStorageOutputs** model -- added **ColdThermalStorageInputs** model -- added **ColdThermalStorageOutputs** model -- add **HotThermalStorageOutputs** -- add **ColdThermalStorageOutputs** -- `0012_coldthermalstorageinputs....` file used to add new models to the db - ## v2.5.0 ### Minor Updates ##### Added