diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f12af413..84146c243 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,27 @@ 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 ## v2.5.1 ### Minor Updates @@ -74,6 +95,7 @@ In `job/views.py`: ### Minor Updates ##### Fixed Lookback charge parameters expected from the URDB API call were changed to the non-caplitalized format, so they are now used properly. + ## v2.3.0 ##### Changed The following name changes were made in the `job/` endpoint and `julia_src/http.jl`: diff --git a/job/migrations/0016_reoptjlmessageoutputs_and_more.py b/job/migrations/0016_reoptjlmessageoutputs_and_more.py new file mode 100644 index 000000000..61a8d9a6f --- /dev/null +++ b/job/migrations/0016_reoptjlmessageoutputs_and_more.py @@ -0,0 +1,81 @@ +# Generated by Django 4.0.7 on 2022-12-19 15:24 + +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', '0015_coolingloadinputs_coolingloadoutputs_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='REoptjlMessageOutputs', + fields=[ + ('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='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', + 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_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_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_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( + 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/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 = [ + ] diff --git a/job/models.py b/job/models.py index 5d1a2fab7..21f7fb4d6 100644 --- a/job/models.py +++ b/job/models.py @@ -568,38 +568,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" @@ -620,7 +588,7 @@ class FinancialInputs(BaseModel, models.Model): help_text="Analysis period in years. Must be integer." ) elec_cost_escalation_rate_fraction = models.FloatField( - default=0.023, + default=0.019, validators=[ MinValueValidator(-1), MaxValueValidator(1) @@ -629,7 +597,7 @@ class FinancialInputs(BaseModel, models.Model): help_text="Annual nominal utility electricity cost escalation rate." ) offtaker_discount_rate_fraction = models.FloatField( - default=0.083, + default=0.0564, validators=[ MinValueValidator(0), MaxValueValidator(1) @@ -657,7 +625,7 @@ class FinancialInputs(BaseModel, models.Model): help_text="Annual nominal O&M cost escalation rate" ) owner_discount_rate_fraction = models.FloatField( - default=0.083, + default=0.0564, validators=[ MinValueValidator(0), MaxValueValidator(1) @@ -2051,7 +2019,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) @@ -2060,7 +2028,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) @@ -2478,7 +2446,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) @@ -2806,7 +2774,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) @@ -2815,7 +2783,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) @@ -2824,7 +2792,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) @@ -2833,7 +2801,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) @@ -4415,7 +4383,23 @@ class ExistingBoilerOutputs(BaseModel, models.Model): lifecycle_fuel_cost_after_tax_bau = models.FloatField(null=True, blank=True) year_one_thermal_production_mmbtu = models.FloatField(null=True, blank=True) year_one_fuel_cost_before_tax = models.FloatField(null=True, blank=True) - year_one_thermal_to_tes_series_mmbtu_per_hour = ArrayField( + 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, ) @@ -4435,10 +4419,38 @@ 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 +class REoptjlMessageOutputs(BaseModel, models.Model): + + key = "Messages" + meta = models.OneToOneField( + APIMeta, + on_delete=models.CASCADE, + related_name="REoptjlMessageOutputs", + 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" diff --git a/job/src/process_results.py b/job/src/process_results.py index 60dbbc4d8..1d44ce60a 100644 --- a/job/src/process_results.py +++ b/job/src/process_results.py @@ -27,13 +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 job.models import FinancialOutputs, APIMeta, PVOutputs, ElectricStorageOutputs, ElectricTariffOutputs, SiteOutputs,\ - ElectricUtilityOutputs, GeneratorOutputs, ElectricLoadOutputs, WindOutputs, FinancialInputs, ElectricUtilityInputs,\ - ExistingBoilerOutputs, CHPOutputs, CHPInputs, ExistingChillerOutputs, CoolingLoadOutputs, HeatingLoadOutputs,\ - HotThermalStorageOutputs, ColdThermalStorageOutputs +from job.models import REoptjlMessageOutputs, FinancialOutputs, APIMeta, PVOutputs, ElectricStorageOutputs, ElectricTariffOutputs, SiteOutputs,\ + ElectricUtilityOutputs, GeneratorOutputs, ElectricLoadOutputs, WindOutputs, FinancialInputs, ElectricUtilityInputs, ExistingBoilerOutputs,\ + CHPInputs, CHPOutputs, ExistingChillerOutputs, CoolingLoadOutputs, HeatingLoadOutputs, HotThermalStorageOutputs, ColdThermalStorageOutputs import logging log = logging.getLogger(__name__) +import sys +import traceback def process_results(results: dict, run_uuid: str) -> None: """ @@ -45,41 +45,44 @@ 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 "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 "ExistingChiller" in results.keys(): - ExistingChillerOutputs.create(meta=meta, **results["ExistingChiller"]).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 "HotThermalStorage" in results.keys(): - HotThermalStorageOutputs.create(meta=meta, **results["HotThermalStorage"]).save() - if "ColdThermalStorage" in results.keys(): - ColdThermalStorageOutputs.create(meta=meta, **results["ColdThermalStorage"]).save() - if "HeatingLoad" in results.keys(): - HeatingLoadOutputs.create(meta=meta, **results["HeatingLoad"]).save() - if "CoolingLoad" in results.keys(): - CoolingLoadOutputs.create(meta=meta, **results["CoolingLoad"]).save() - if "CHP" in results.keys(): - CHPOutputs.create(meta=meta, **results["CHP"]).save() - # TODO process rest of results + 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() + ElectricLoadOutputs.create(meta=meta, **results["ElectricLoad"]).save() + 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 "ExistingChiller" in results.keys(): + ExistingChillerOutputs.create(meta=meta, **results["ExistingChiller"]).save() + if "HotThermalStorage" in results.keys(): + HotThermalStorageOutputs.create(meta=meta, **results["HotThermalStorage"]).save() + if "ColdThermalStorage" in results.keys(): + ColdThermalStorageOutputs.create(meta=meta, **results["ColdThermalStorage"]).save() + if "HeatingLoad" in results.keys(): + HeatingLoadOutputs.create(meta=meta, **results["HeatingLoad"]).save() + if "CoolingLoad" in results.keys(): + CoolingLoadOutputs.create(meta=meta, **results["CoolingLoad"]).save() + if "CHP" in results.keys(): + CHPOutputs.create(meta=meta, **results["CHP"]).save() + # TODO process rest of results def update_inputs_in_database(inputs_to_update: dict, run_uuid: str) -> None: """ @@ -100,7 +103,7 @@ 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) diff --git a/job/src/run_jump_model.py b/job/src/run_jump_model.py index 46a8faf9d..507de352d 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) @@ -110,23 +111,27 @@ 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: 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"]) logger.info(msg) raise OptimizationTimeout(task=name, message=msg, run_uuid=run_uuid, user_uuid=user_uuid) + elif status.strip().lower() == 'error': + 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.") 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) + 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/test/posts/all_inputs_test.json b/job/test/posts/all_inputs_test.json new file mode 100644 index 000000000..d7f5ed65e --- /dev/null +++ b/job/test/posts/all_inputs_test.json @@ -0,0 +1,270 @@ +{ + "Financial": { + "analysis_years": 25, + "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_fraction": 0.3, + "offgrid_other_capital_costs": 0.0, + "offgrid_other_annual_costs": 0.0, + "CO2_cost_per_tonne": 51.0, + "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_rate_fraction": null, + "SO2_cost_escalation_rate_fraction": null, + "PM25_cost_escalation_rate_fraction": null + }, + "ElectricLoad": { + "annual_kwh": 190000.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_fraction": 0.5, + "operating_reserve_required_fraction": 0.0, + "min_load_met_annual_fraction": 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_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 + }, + "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_fraction": 1.0, + "macrs_itc_reduction": 0.5, + "federal_itc_fraction": 0.26, + "state_ibi_fraction": 0.0, + "state_ibi_max": 10000000000.0, + "utility_ibi_fraction": 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_fraction": 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", + "production_factor_series": [], + "can_net_meter": true, + "can_wholesale": true, + "can_export_beyond_nem_limit": true, + "can_curtail": true, + "operating_reserve_required_fraction": 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_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_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, + "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_fraction": 1.0, + "macrs_itc_reduction": 0.5, + "total_itc_fraction": 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_fraction": 0.0, + "only_runs_during_grid_outage": true, + "sells_energy_back_to_grid": false, + "macrs_option_years": 0, + "macrs_bonus_fraction": 1.0, + "macrs_itc_reduction": 0.0, + "federal_itc_fraction": 0.0, + "state_ibi_fraction": 0.0, + "state_ibi_max": 10000000000.0, + "utility_ibi_fraction": 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_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, + "emissions_factor_lb_PM25_per_gal": 0.0, + "replacement_year": 25, + "replace_cost_per_kw": 0.0 + }, + "Wind": { + "size_class": "medium", + "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_fraction": 1.0, + "macrs_itc_reduction": 0.5, + "federal_itc_fraction": 0.26, + "state_ibi_fraction": 0.0, + "state_ibi_max": 10000000000.0, + "utility_ibi_fraction": 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, + "production_factor_series": [], + "can_net_meter": true, + "can_wholesale": true, + "can_export_beyond_nem_limit": true, + "can_curtail": true, + "operating_reserve_required_fraction": 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": [0.0], + "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/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 9ff2861b4..f6d73aaad 100644 --- a/job/test/test_job_endpoint.py +++ b/job/test/test_job_endpoint.py @@ -27,10 +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) import os @@ -42,62 +45,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 - } - } + 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=scenario) + 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 +77,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 - } - } + 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=scenario) + 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') @@ -160,6 +95,26 @@ def test_off_grid_defaults(self): self.assertAlmostEqual(results["ElectricLoad"]["offgrid_load_met_fraction"], 0.99999, places=-2) self.assertAlmostEqual(sum(results["ElectricLoad"]["offgrid_load_met_series_kw"]), 8760.0, places=-1) self.assertAlmostEqual(results["Financial"]["lifecycle_offgrid_other_annual_costs_after_tax"], 0.0, places=-2) + + def test_process_reopt_error(self): + """ + Purpose of this test is to ensure REopt status 400 is returned using the 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) + assert('errors' in r["messages"].keys()) + assert('warnings' in r["messages"].keys()) + assert(resp.status_code==400) + def test_thermal_in_results(self): """ @@ -248,6 +203,7 @@ def test_chp_defaults_from_julia(self): resp = self.api_client.get(f'/dev/job/{run_uuid}/results') r = json.loads(resp.content) + inputs_chp = r["inputs"]["CHP"] avg_fuel_load = (post["SpaceHeatingLoad"]["annual_mmbtu"] + @@ -266,3 +222,22 @@ def test_chp_defaults_from_julia(self): else: # Make sure we didn't overwrite user-input self.assertEquals(inputs_chp[key], post["CHP"][key]) + def test_superset_input_fields(self): + """ + Purpose of this test is to test the API's ability to accept all relevant + input fields and send to REopt, ensuring name input consistency with REopt.jl. + """ + 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"] + + self.assertAlmostEqual(results["Financial"]["npv"], 165.21, places=-2) + assert(resp.status_code==200) diff --git a/job/validators.py b/job/validators.py index 37db60e1b..65fba3499 100644 --- a/job/validators.py +++ b/job/validators.py @@ -173,7 +173,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: """ @@ -508,14 +508,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") diff --git a/job/views.py b/job/views.py index 4d109b190..ad77e8dcd 100644 --- a/job/views.py +++ b/job/views.py @@ -31,14 +31,15 @@ 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,\ ElectricTariffInputs, ElectricUtilityInputs, SpaceHeatingLoadInputs, PVOutputs, ElectricStorageOutputs,\ - WindOutputs, ExistingBoilerInputs, GeneratorOutputs, ElectricTariffOutputs, ElectricUtilityOutputs,\ - ElectricLoadOutputs, ExistingBoilerOutputs, DomesticHotWaterLoadInputs, SiteInputs, SiteOutputs, APIMeta,\ + WindOutputs, ExistingBoilerInputs, GeneratorOutputs, ElectricTariffOutputs, ElectricUtilityOutputs, \ + ElectricLoadOutputs, ExistingBoilerOutputs, DomesticHotWaterLoadInputs, SiteInputs, SiteOutputs, APIMeta, \ UserProvidedMeta, CHPInputs, CHPOutputs, CoolingLoadInputs, ExistingChillerInputs, ExistingChillerOutputs,\ - CoolingLoadOutputs, HeatingLoadOutputs, HotThermalStorageInputs, HotThermalStorageOutputs,\ + CoolingLoadOutputs, HeatingLoadOutputs, REoptjlMessageOutputs, HotThermalStorageInputs, HotThermalStorageOutputs,\ ColdThermalStorageInputs, ColdThermalStorageOutputs import os import requests @@ -119,6 +120,7 @@ def outputs(request): d["HeatingLoad"] = HeatingLoadOutputs.info_dict(HeatingLoadOutputs) d["CoolingLoad"] = CoolingLoadOutputs.info_dict(CoolingLoadOutputs) d["CHP"] = CHPOutputs.info_dict(CHPOutputs) + d["Messages"] = REoptjlMessageOutputs.info_dict(REoptjlMessageOutputs) return JsonResponse(d) except Exception as e: @@ -153,7 +155,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, " @@ -238,6 +240,20 @@ def results(request, run_uuid): msgs = meta.Message.all() 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"]: + 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: @@ -298,6 +314,9 @@ 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 591975272..88492d891 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"] @@ -72,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"] @@ -115,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" @@ -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"] @@ -159,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"] @@ -206,13 +209,14 @@ 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", "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" @@ -251,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"] @@ -358,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"] @@ -385,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"] @@ -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,15 +461,16 @@ 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" [[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"] @@ -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,9 +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 = "92e8055867af9460faba381c44778d7634d54b7b" uuid = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6" -version = "0.22.0" +version = "0.23.0" [[deps.Random]] deps = ["SHA", "Serialization"] @@ -687,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" @@ -710,12 +726,13 @@ 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"] -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"] @@ -758,9 +775,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 +791,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 +808,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 +822,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"] @@ -815,14 +834,14 @@ 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 = "e59ecc5a41b000fa94423a578d29290c7266fc10" +git-tree-sha1 = "ac00576f90d8a259f2c9d823e91d1de3fd44d348" uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4" -version = "1.4.0" +version = "1.4.1" [[deps.UUIDs]] deps = ["Random", "SHA"] @@ -833,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"] @@ -843,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" @@ -858,6 +882,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 +899,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 +916,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"] 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" 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 5e4258e16..2bff440aa 100644 --- a/julia_src/http.jl +++ b/julia_src/http.jl @@ -64,41 +64,55 @@ function reopt(req::HTTP.Request) ) ) end - @info "Starting REopt..." + @info "Starting REopt..." error_response = Dict() results = Dict() inputs_with_defaults_set_in_julia = Dict() - try + model_inputs = nothing + # Catch handled/unhandled exceptions in data pre-processing, JuMP setup + try 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_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, - :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), - "CHP" => chp_dict - ) - catch e - @error "Something went wrong in the Julia code!" exception=(e, catch_backtrace()) + catch e + @error "Something went wrong during REopt inputs processing!" exception=(e, catch_backtrace()) error_response["error"] = sprint(showerror, e) - end + 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_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, + :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), + "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? + end + end + if typeof(ms) <: AbstractArray finalize(backend(ms[1])) finalize(backend(ms[2])) @@ -106,19 +120,31 @@ function reopt(req::HTTP.Request) finalize(backend(ms)) 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 - ) - return HTTP.Response(200, JSON.json(response)) + 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 + response = Dict( + "results" => results, + "inputs_with_defaults_set_in_julia" => inputs_with_defaults_set_in_julia + ) + return HTTP.Response(200, JSON.json(response)) + end else @info "An error occured in the Julia code." 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..."