From 002fef7aaf41a40319a18b80ddcfc1ba2666a452 Mon Sep 17 00:00:00 2001 From: "Rathod, Bhavesh" Date: Mon, 26 Sep 2022 09:54:06 -0400 Subject: [PATCH 01/26] 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 996ce723a5cf1e7e781e1fe68a73fbf2f3761150 Mon Sep 17 00:00:00 2001 From: "Rathod, Bhavesh" Date: Wed, 28 Sep 2022 10:35:42 -0400 Subject: [PATCH 02/26] 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 20a1d7ab162780a0e2e04cefb177b8af06df5184 Mon Sep 17 00:00:00 2001 From: "Rathod, Bhavesh" Date: Mon, 10 Oct 2022 15:06:39 -0400 Subject: [PATCH 03/26] 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 66d79ecd987789d0f825201444bac0beafffa3e2 Mon Sep 17 00:00:00 2001 From: "Rathod, Bhavesh" Date: Mon, 10 Oct 2022 21:41:23 -0400 Subject: [PATCH 04/26] 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 a983ffcfc60c7f1967e285e6d2e7135c3cc262bd Mon Sep 17 00:00:00 2001 From: "Rathod, Bhavesh" Date: Tue, 11 Oct 2022 15:18:50 -0400 Subject: [PATCH 05/26] 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 df75108c86484902127aaa10fdbe2650c425aa89 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 06/26] 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 71844b7941cc231c49f999e2443b291f8d3a370a 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 07/26] 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 6133a3a917480fbdad1d920639ddac291146c4ad 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 08/26] 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 922fb2a271f72d1bad56dbe383230beeb451792a 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 09/26] 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 b438249b22de14565d7015bca4bfa0464d8a46e6 Mon Sep 17 00:00:00 2001 From: "Rathod, Bhavesh" Date: Tue, 22 Nov 2022 18:19:42 -0500 Subject: [PATCH 10/26] 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 ffaa095ea34e213f32f530375af8953887acea51 Mon Sep 17 00:00:00 2001 From: "Rathod, Bhavesh" Date: Wed, 23 Nov 2022 10:09:00 -0500 Subject: [PATCH 11/26] 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 41ab7e6b960e556c0c65a7009c5b71ad9c137245 Mon Sep 17 00:00:00 2001 From: "Rathod, Bhavesh" Date: Tue, 29 Nov 2022 13:42:50 -0500 Subject: [PATCH 12/26] 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 3baea3adb389aa4459780bf48a6221f13615974f Mon Sep 17 00:00:00 2001 From: "Rathod, Bhavesh" Date: Tue, 29 Nov 2022 13:58:58 -0500 Subject: [PATCH 13/26] 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 56438e92ceb70f3b365cbca382f465e599713269 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Sun, 4 Dec 2022 21:05:31 -0700 Subject: [PATCH 14/26] 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 6368475b12285374e03ab585c3310561edafbd3b Mon Sep 17 00:00:00 2001 From: bill-becker Date: Sun, 4 Dec 2022 21:22:30 -0700 Subject: [PATCH 15/26] 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 b2f777189f97c0d669fe48b2d68fa699f71c8af6 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Sun, 4 Dec 2022 21:50:46 -0700 Subject: [PATCH 16/26] 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 1ccb1be8728216b6a8df909810013cea33ca9395 Mon Sep 17 00:00:00 2001 From: "Rathod, Bhavesh" Date: Mon, 19 Dec 2022 10:26:43 -0500 Subject: [PATCH 17/26] 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 b52c77cbcd83d456353f35c928e11fb0d5b3a6e4 Mon Sep 17 00:00:00 2001 From: zolanaj Date: Mon, 19 Dec 2022 14:18:33 -0700 Subject: [PATCH 18/26] 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 2db3d4ba94767c65e38ea0f82a84a440d4f9fa94 Mon Sep 17 00:00:00 2001 From: Rathod Date: Tue, 20 Dec 2022 09:44:14 -0500 Subject: [PATCH 19/26] 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 7bab86b1e85b275e7fc59dc7b122b7fb0c436f56 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 20/26] 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 84aa2c1bc6081e53e28c5e8bcb5f2e847b094c97 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 21/26] 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 a58256040916cdafab3fd49223e6e4256624a2b1 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 22/26] 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 df8ec6a704422c4ecfa2ac0ab8bdf9ddb2ae3128 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 23/26] 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 0232e39bbf4f7fb866d56ee94e07724ce7aa202d 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 24/26] 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 b03bd81fad31e47666e282693f9ebf6e33fa928c 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 25/26] Revert "simplify conditions" This reverts commit 84aa2c1bc6081e53e28c5e8bcb5f2e847b094c97. --- 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 fdc46acbb876a74c3ad46bf9184e9cc0eb2d4e45 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 26/26] 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 = [ + ]