From 518d9507a215ceb10f75a61d7d6a34d6238919d7 Mon Sep 17 00:00:00 2001 From: "Rathod, Bhavesh" Date: Mon, 29 Sep 2025 13:26:55 -0400 Subject: [PATCH 01/63] Updated model with outputs for testing --- reoptjl/models.py | 152 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) diff --git a/reoptjl/models.py b/reoptjl/models.py index 2d48782ea..f1a3e925b 100644 --- a/reoptjl/models.py +++ b/reoptjl/models.py @@ -1506,6 +1506,24 @@ class ElectricLoadOutputs(BaseModel, models.Model): null=True, blank=True, help_text="Annual energy consumption calculated by summing up load_series_kw. Does not include electric load for any new heating or cooling techs." ) + monthly_calculated_kwh = ArrayField( + models.FloatField( + null=True, blank=True + ), + default=list, + help_text="Monthly energy consumption calculated by summing up load_series_kw. Does not include electric load for any new heating or cooling techs." + ) + monthly_peak_kw = ArrayField( + models.FloatField( + null=True, blank=True + ), + default=list, + help_text="Monthly peak energy demand determined from load_series_kw. Does not include electric load for any new heating or cooling techs." + ) + annual_peak_kw = models.FloatField( + null=True, blank=True, + help_text="Annual peak energy demand determined from load_series_kw. Does not include electric load for any new heating or cooling techs." + ) annual_electric_load_with_thermal_conversions_kwh = models.FloatField( null=True, blank=True, help_text="Total end-use electrical load, including electrified heating and cooling end-use load." @@ -2563,6 +2581,140 @@ class ElectricTariffOutputs(BaseModel, models.Model): related_name="ElectricTariffOutputs", primary_key=True ) + year_one_monthly_fixed_cost = ArrayField( + models.FloatField( + null=True, blank=True + ), + default=list, + help_text="Year one fixed utility costs for each month." + ) + + year_one_electric_to_load_energy_cost_series_before_tax = ArrayField( + models.FloatField( + null=True, blank=True + ), + default=list, + help_text="Series of cost of power purchased from grid to serve load in each timestep." + ) + monthly_electric_to_load_energy_cost_series_before_tax = ArrayField( + models.FloatField( + null=True, blank=True + ), + default=list, + help_text="Series of monthly cost of power purchased from grid to serve loads." + ) + year_one_electric_to_storage_energy_cost_series_before_tax = ArrayField( + models.FloatField( + null=True, blank=True + ), + default=list, + help_text="Series of cost of power purchased from grid to charge battery storage system in each timestep." + ) + monthly_electric_to_storage_energy_cost_series_before_tax = ArrayField( + models.FloatField( + null=True, blank=True + ), + default=list, + help_text="Series of monthly cost of power purchased from grid to charge battery storage system." + ) + monthly_facility_demand_cost_series_before_tax = ArrayField( + models.FloatField( + null=True, blank=True + ), + default=list, + help_text="Series of total monthly facility demand charges by month." + ) + monthly_gross_tou_demand_cost_series_before_tax = ArrayField( + models.FloatField( + null=True, blank=True + ), + default=list, + help_text="Series of total time of use demand charges by month." + ) + NEM_export_rate_series = ArrayField( + models.FloatField( + null=True, blank=True + ), + default=list, + help_text="Series of compensation rate in each timestep for exporting power to grid using systems sized up to net metering limit." + ) + NEM_electric_to_grid_series_kw = ArrayField( + models.FloatField( + null=True, blank=True + ), + default=list, + help_text="Series of power exports to the grid up to net metering limit for each timestep." + ) + NEM_monthly_export_series_kwh = ArrayField( + models.FloatField( + null=True, blank=True + ), + default=list, + help_text="Year one cost of electricity consumed in each month." + ) + NEM_monthly_export_cost_benefit_before_tax = ArrayField( + models.FloatField( + null=True, blank=True + ), + default=list, + help_text="Year one cost of electricity consumed in each month." + ) + WHL_export_rate_series = ArrayField( + models.FloatField( + null=True, blank=True + ), + default=list, + help_text="Series of compensation rate in each timestep for exporting power to grid for wholesale." + ) + WHL_electric_to_grid_series_kw = ArrayField( + models.FloatField( + null=True, blank=True + ), + default=list, + help_text="Series of power exports to the grid for wholesale in each timestep." + ) + WHL_monthly_export_series_kwh = ArrayField( + models.FloatField( + null=True, blank=True + ), + default=list, + help_text="Series of monthly energy exports to grid under wholesale benefit." + ) + WHL_monthly_export_cost_benefit_before_tax = ArrayField( + models.FloatField( + null=True, blank=True + ), + default=list, + help_text="Series of monthly monetary benefit from exporting power to grid at wholesale rate." + ) + EXC_export_rate_series = ArrayField( + models.FloatField( + null=True, blank=True + ), + default=list, + help_text="Series of compensation rate in each timestep for exporting power to grid beyond net metering limit." + ) + EXC_electric_to_grid_series_kw = ArrayField( + models.FloatField( + null=True, blank=True + ), + default=list, + help_text="Series of power exports to the grid above net metering limit for each timestep." + ) + EXC_monthly_export_series_kwh = ArrayField( + models.FloatField( + null=True, blank=True + ), + default=list, + help_text="Series of monthly energy exports to grid above net metering limit." + ) + EXC_monthly_export_cost_benefit_before_tax = ArrayField( + models.FloatField( + null=True, blank=True + ), + default=list, + help_text="Series of monthly monetary benefit from exporting power to grid above net metering limit." + ) year_one_energy_cost_before_tax = models.FloatField( null=True, blank=True, help_text="Optimal year one utility energy cost" From ea88d75f73f42b42e9a0efce1938fc35b87af24b Mon Sep 17 00:00:00 2001 From: "Rathod, Bhavesh" Date: Tue, 30 Sep 2025 12:25:09 -0400 Subject: [PATCH 02/63] Update julia version and make migrations --- julia_src/Manifest.toml | 6 +- ...tricloadoutputs_annual_peak_kw_and_more.py | 124 ++++++++++++++++++ 2 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 reoptjl/migrations/0093_electricloadoutputs_annual_peak_kw_and_more.py diff --git a/julia_src/Manifest.toml b/julia_src/Manifest.toml index 499e0c00a..e0ad46b68 100644 --- a/julia_src/Manifest.toml +++ b/julia_src/Manifest.toml @@ -922,9 +922,11 @@ uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" [[deps.REopt]] deps = ["ArchGDAL", "CSV", "CoolProp", "DataFrames", "Dates", "DelimitedFiles", "HTTP", "JLD", "JSON", "JuMP", "LinDistFlow", "LinearAlgebra", "Logging", "MathOptInterface", "Requires", "Roots", "Statistics", "TestEnv"] -git-tree-sha1 = "4db74039055f6084d8bd45304de9de856462d917" +git-tree-sha1 = "1283214f265ecc53d02b4bc9318676a9f02dadcb" +repo-rev = "elec_util_usage" +repo-url = "https://github.com/NREL/REopt.jl.git" uuid = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6" -version = "0.53.2" +version = "0.54.0" [[deps.Random]] deps = ["SHA"] diff --git a/reoptjl/migrations/0093_electricloadoutputs_annual_peak_kw_and_more.py b/reoptjl/migrations/0093_electricloadoutputs_annual_peak_kw_and_more.py new file mode 100644 index 000000000..42a5beb83 --- /dev/null +++ b/reoptjl/migrations/0093_electricloadoutputs_annual_peak_kw_and_more.py @@ -0,0 +1,124 @@ +# Generated by Django 4.0.7 on 2025-09-30 14:52 + +import django.contrib.postgres.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('reoptjl', '0092_merge_20250613_0525'), + ] + + operations = [ + migrations.AddField( + model_name='electricloadoutputs', + name='annual_peak_kw', + field=models.FloatField(blank=True, help_text='Annual peak energy demand determined from load_series_kw. Does not include electric load for any new heating or cooling techs.', null=True), + ), + migrations.AddField( + model_name='electricloadoutputs', + name='monthly_calculated_kwh', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Monthly energy consumption calculated by summing up load_series_kw. Does not include electric load for any new heating or cooling techs.', size=None), + ), + migrations.AddField( + model_name='electricloadoutputs', + name='monthly_peak_kw', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Monthly peak energy demand determined from load_series_kw. Does not include electric load for any new heating or cooling techs.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='EXC_electric_to_grid_series_kw', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of power exports to the grid above net metering limit for each timestep.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='EXC_export_rate_series', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of compensation rate in each timestep for exporting power to grid beyond net metering limit.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='EXC_monthly_export_cost_benefit_before_tax', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of monthly monetary benefit from exporting power to grid above net metering limit.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='EXC_monthly_export_series_kwh', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of monthly energy exports to grid above net metering limit.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='NEM_electric_to_grid_series_kw', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of power exports to the grid up to net metering limit for each timestep.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='NEM_export_rate_series', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of compensation rate in each timestep for exporting power to grid using systems sized up to net metering limit.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='NEM_monthly_export_cost_benefit_before_tax', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Year one cost of electricity consumed in each month.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='NEM_monthly_export_series_kwh', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Year one cost of electricity consumed in each month.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='WHL_electric_to_grid_series_kw', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of power exports to the grid for wholesale in each timestep.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='WHL_export_rate_series', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of compensation rate in each timestep for exporting power to grid for wholesale.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='WHL_monthly_export_cost_benefit_before_tax', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of monthly monetary benefit from exporting power to grid at wholesale rate.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='WHL_monthly_export_series_kwh', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of monthly energy exports to grid under wholesale benefit.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='monthly_electric_to_load_energy_cost_series_before_tax', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of monthly cost of power purchased from grid to serve loads.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='monthly_electric_to_storage_energy_cost_series_before_tax', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of monthly cost of power purchased from grid to charge battery storage system.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='monthly_facility_demand_cost_series_before_tax', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of total monthly facility demand charges by month.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='monthly_gross_tou_demand_cost_series_before_tax', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of total time of use demand charges by month.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='year_one_electric_to_load_energy_cost_series_before_tax', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of cost of power purchased from grid to serve load in each timestep.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='year_one_electric_to_storage_energy_cost_series_before_tax', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of cost of power purchased from grid to charge battery storage system in each timestep.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='year_one_monthly_fixed_cost', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Year one fixed utility costs for each month.', size=None), + ), + ] From 08a9ccf13e2af054663c678b2a65b0a843a609a6 Mon Sep 17 00:00:00 2001 From: Rathod Date: Thu, 2 Oct 2025 14:35:57 -0400 Subject: [PATCH 03/63] Remove migration 0093 and add billed energy rate field Deleted the migration file 0093_electricloadoutputs_annual_peak_kw_and_more.py and added the year_one_billed_energy_rate_series ArrayField to the ElectricTariffOutputs model. This change likely reflects a schema update and migration reset to accommodate the new field. --- ...tricloadoutputs_annual_peak_kw_and_more.py | 124 ------------------ reoptjl/models.py | 9 ++ 2 files changed, 9 insertions(+), 124 deletions(-) delete mode 100644 reoptjl/migrations/0093_electricloadoutputs_annual_peak_kw_and_more.py diff --git a/reoptjl/migrations/0093_electricloadoutputs_annual_peak_kw_and_more.py b/reoptjl/migrations/0093_electricloadoutputs_annual_peak_kw_and_more.py deleted file mode 100644 index 42a5beb83..000000000 --- a/reoptjl/migrations/0093_electricloadoutputs_annual_peak_kw_and_more.py +++ /dev/null @@ -1,124 +0,0 @@ -# Generated by Django 4.0.7 on 2025-09-30 14:52 - -import django.contrib.postgres.fields -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('reoptjl', '0092_merge_20250613_0525'), - ] - - operations = [ - migrations.AddField( - model_name='electricloadoutputs', - name='annual_peak_kw', - field=models.FloatField(blank=True, help_text='Annual peak energy demand determined from load_series_kw. Does not include electric load for any new heating or cooling techs.', null=True), - ), - migrations.AddField( - model_name='electricloadoutputs', - name='monthly_calculated_kwh', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Monthly energy consumption calculated by summing up load_series_kw. Does not include electric load for any new heating or cooling techs.', size=None), - ), - migrations.AddField( - model_name='electricloadoutputs', - name='monthly_peak_kw', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Monthly peak energy demand determined from load_series_kw. Does not include electric load for any new heating or cooling techs.', size=None), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='EXC_electric_to_grid_series_kw', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of power exports to the grid above net metering limit for each timestep.', size=None), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='EXC_export_rate_series', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of compensation rate in each timestep for exporting power to grid beyond net metering limit.', size=None), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='EXC_monthly_export_cost_benefit_before_tax', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of monthly monetary benefit from exporting power to grid above net metering limit.', size=None), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='EXC_monthly_export_series_kwh', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of monthly energy exports to grid above net metering limit.', size=None), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='NEM_electric_to_grid_series_kw', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of power exports to the grid up to net metering limit for each timestep.', size=None), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='NEM_export_rate_series', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of compensation rate in each timestep for exporting power to grid using systems sized up to net metering limit.', size=None), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='NEM_monthly_export_cost_benefit_before_tax', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Year one cost of electricity consumed in each month.', size=None), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='NEM_monthly_export_series_kwh', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Year one cost of electricity consumed in each month.', size=None), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='WHL_electric_to_grid_series_kw', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of power exports to the grid for wholesale in each timestep.', size=None), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='WHL_export_rate_series', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of compensation rate in each timestep for exporting power to grid for wholesale.', size=None), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='WHL_monthly_export_cost_benefit_before_tax', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of monthly monetary benefit from exporting power to grid at wholesale rate.', size=None), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='WHL_monthly_export_series_kwh', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of monthly energy exports to grid under wholesale benefit.', size=None), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='monthly_electric_to_load_energy_cost_series_before_tax', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of monthly cost of power purchased from grid to serve loads.', size=None), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='monthly_electric_to_storage_energy_cost_series_before_tax', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of monthly cost of power purchased from grid to charge battery storage system.', size=None), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='monthly_facility_demand_cost_series_before_tax', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of total monthly facility demand charges by month.', size=None), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='monthly_gross_tou_demand_cost_series_before_tax', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of total time of use demand charges by month.', size=None), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='year_one_electric_to_load_energy_cost_series_before_tax', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of cost of power purchased from grid to serve load in each timestep.', size=None), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='year_one_electric_to_storage_energy_cost_series_before_tax', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of cost of power purchased from grid to charge battery storage system in each timestep.', size=None), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='year_one_monthly_fixed_cost', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Year one fixed utility costs for each month.', size=None), - ), - ] diff --git a/reoptjl/models.py b/reoptjl/models.py index f1a3e925b..f58eb3cf0 100644 --- a/reoptjl/models.py +++ b/reoptjl/models.py @@ -2715,6 +2715,15 @@ class ElectricTariffOutputs(BaseModel, models.Model): default=list, help_text="Series of monthly monetary benefit from exporting power to grid above net metering limit." ) + + year_one_billed_energy_rate_series = ArrayField( + models.FloatField( + null=True, blank=True + ), + default=list, + help_text="Series of billed energy rates for each timestep in year one." + ) + year_one_energy_cost_before_tax = models.FloatField( null=True, blank=True, help_text="Optimal year one utility energy cost" From 39c7b2b4bff250c920e3e788377fc9a2f5be2554 Mon Sep 17 00:00:00 2001 From: "Rathod, Bhavesh" Date: Wed, 8 Oct 2025 19:44:40 -0400 Subject: [PATCH 04/63] Add new electric load and tariff output fields Introduces multiple new fields to ElectricLoadOutputs and ElectricTariffOutputs models for tracking annual peak kW, monthly energy and peak demand, export series, and various billed rate series. Updates Julia HTTP handler to log processing steps and ensures certain result matrices are converted to vectors before response. --- julia_src/http.jl | 5 + ...tricloadoutputs_annual_peak_kw_and_more.py | 164 ++++++++++++++++++ reoptjl/models.py | 39 ++++- 3 files changed, 206 insertions(+), 2 deletions(-) create mode 100644 reoptjl/migrations/0106_electricloadoutputs_annual_peak_kw_and_more.py diff --git a/julia_src/http.jl b/julia_src/http.jl index 8e4b0a4fc..a25671938 100644 --- a/julia_src/http.jl +++ b/julia_src/http.jl @@ -56,6 +56,7 @@ function reopt(req::HTTP.Request) # Catch handled/unhandled exceptions in data pre-processing, JuMP setup try model_inputs = reoptjl.REoptInputs(d) + @info "Successfully processed REopt inputs." catch e @error "Something went wrong during REopt inputs processing!" exception=(e, catch_backtrace()) error_response["error"] = sprint(showerror, e) @@ -67,6 +68,7 @@ function reopt(req::HTTP.Request) # Catch handled/unhandled exceptions in optimization try results = reoptjl.run_reopt(ms, model_inputs) + @info "Successfully ran REopt optimization." 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, @@ -185,6 +187,9 @@ function reopt(req::HTTP.Request) if isempty(error_response) @info "REopt model solved with status $(results["status"])." + # these are matrices that need to be vector. + results["ElectricTariff"]["year_one_electric_to_load_energy_cost_series_before_tax"] = results["ElectricTariff"]["year_one_electric_to_load_energy_cost_series_before_tax"][:,1] + results["ElectricTariff"]["monthly_facility_demand_cost_series_before_tax"] = results["ElectricTariff"]["monthly_facility_demand_cost_series_before_tax"][:,1] response = Dict( "results" => results, "reopt_version" => string(pkgversion(reoptjl)) diff --git a/reoptjl/migrations/0106_electricloadoutputs_annual_peak_kw_and_more.py b/reoptjl/migrations/0106_electricloadoutputs_annual_peak_kw_and_more.py new file mode 100644 index 000000000..8add1d9cb --- /dev/null +++ b/reoptjl/migrations/0106_electricloadoutputs_annual_peak_kw_and_more.py @@ -0,0 +1,164 @@ +# Generated by Django 4.2.24 on 2025-10-06 17:06 + +import django.contrib.postgres.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('reoptjl', '0105_alter_chpoutputs_initial_capital_costs_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='electricloadoutputs', + name='annual_peak_kw', + field=models.FloatField(blank=True, help_text='Annual peak energy demand determined from load_series_kw. Does not include electric load for any new heating or cooling techs.', null=True), + ), + migrations.AddField( + model_name='electricloadoutputs', + name='monthly_calculated_kwh', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Monthly energy consumption calculated by summing up load_series_kw. Does not include electric load for any new heating or cooling techs.', size=None), + ), + migrations.AddField( + model_name='electricloadoutputs', + name='monthly_peak_kw', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Monthly peak energy demand determined from load_series_kw. Does not include electric load for any new heating or cooling techs.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='EXC_electric_to_grid_series_kw', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of power exports to the grid above net metering limit for each timestep.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='EXC_export_rate_series', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of compensation rate in each timestep for exporting power to grid beyond net metering limit.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='EXC_monthly_export_cost_benefit_before_tax', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of monthly monetary benefit from exporting power to grid above net metering limit.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='EXC_monthly_export_series_kwh', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of monthly energy exports to grid above net metering limit.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='NEM_electric_to_grid_series_kw', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of power exports to the grid up to net metering limit for each timestep.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='NEM_export_rate_series', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of compensation rate in each timestep for exporting power to grid using systems sized up to net metering limit.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='NEM_monthly_export_cost_benefit_before_tax', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Year one cost of electricity consumed in each month.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='NEM_monthly_export_series_kwh', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Year one cost of electricity consumed in each month.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='WHL_electric_to_grid_series_kw', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of power exports to the grid for wholesale in each timestep.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='WHL_export_rate_series', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of compensation rate in each timestep for exporting power to grid for wholesale.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='WHL_monthly_export_cost_benefit_before_tax', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of monthly monetary benefit from exporting power to grid at wholesale rate.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='WHL_monthly_export_series_kwh', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of monthly energy exports to grid under wholesale benefit.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='monthly_electric_to_load_energy_cost_series_before_tax', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of monthly cost of power purchased from grid to serve loads.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='monthly_electric_to_storage_energy_cost_series_before_tax', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of monthly cost of power purchased from grid to charge battery storage system.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='monthly_facility_demand_cost_series_before_tax', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of total monthly facility demand charges by month.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='monthly_gross_tou_demand_cost_series_before_tax', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of total time of use demand charges by month.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='monthly_tou_demand_cost_series_before_tax', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of total time of use demand charges for each month.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='tou_demand_metrics', + field=models.JSONField(blank=True, null=True), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='year_one_billed_demand_rate_series', + field=models.JSONField(blank=True, null=True), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='year_one_billed_energy_rate_series', + field=models.JSONField(blank=True, help_text='Series of billed energy rates for each timestep in year one.', null=True), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='year_one_billed_energy_rate_tier_limits', + field=models.JSONField(blank=True, null=True), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='year_one_billed_facilitydemand_monthly_rate_series', + field=models.JSONField(blank=True, null=True), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='year_one_billed_facilitydemand_monthly_rate_tier_limits', + field=models.JSONField(blank=True, null=True), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='year_one_billed_tou_demand_rate_tier_limits', + field=models.JSONField(blank=True, null=True), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='year_one_electric_to_load_energy_cost_series_before_tax', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of cost of power purchased from grid to serve load in each timestep.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='year_one_electric_to_storage_energy_cost_series_before_tax', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of cost of power purchased from grid to charge battery storage system in each timestep.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='year_one_monthly_fixed_cost', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Year one fixed utility costs for each month.', size=None), + ), + ] diff --git a/reoptjl/models.py b/reoptjl/models.py index 0229e0026..c068360d1 100644 --- a/reoptjl/models.py +++ b/reoptjl/models.py @@ -2716,12 +2716,47 @@ class ElectricTariffOutputs(BaseModel, models.Model): help_text="Series of monthly monetary benefit from exporting power to grid above net metering limit." ) - year_one_billed_energy_rate_series = ArrayField( + year_one_billed_energy_rate_series = models.JSONField( + null=True, blank=True, + help_text="Series of billed energy rates for each timestep in year one." + ) + + monthly_tou_demand_cost_series_before_tax = ArrayField( models.FloatField( null=True, blank=True ), default=list, - help_text="Series of billed energy rates for each timestep in year one." + help_text="Series of total time of use demand charges for each month." + ) + + tou_demand_metrics = models.JSONField( + null=True, blank=True, + help_text="" + ) + + year_one_billed_facilitydemand_monthly_rate_tier_limits = models.JSONField( + null=True, blank=True, + help_text="" + ) + + year_one_billed_facilitydemand_monthly_rate_series = models.JSONField( + null=True, blank=True, + help_text="" + ) + + year_one_billed_tou_demand_rate_tier_limits = models.JSONField( + null=True, blank=True, + help_text="" + ) + + year_one_billed_energy_rate_tier_limits = models.JSONField( + null=True, blank=True, + help_text="" + ) + + year_one_billed_demand_rate_series = models.JSONField( + null=True, blank=True, + help_text="" ) year_one_energy_cost_before_tax = models.FloatField( From a8dcb7bbbfa77016facb0f00cb80a69a450ca123 Mon Sep 17 00:00:00 2001 From: Grant Ellwood <120485824+gellwood@users.noreply.github.com> Date: Fri, 10 Oct 2025 11:26:23 -0700 Subject: [PATCH 05/63] Added custom ANCCR table draft --- reoptjl/custom_table_config.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/reoptjl/custom_table_config.py b/reoptjl/custom_table_config.py index f12854ceb..c678cdb84 100644 --- a/reoptjl/custom_table_config.py +++ b/reoptjl/custom_table_config.py @@ -110,6 +110,22 @@ } ] + +custom_table_anccr = [ + { + "key": "site", + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "inputs.Meta.description", "None provided") + }, + { + "label": "Site Address", + "key": "site_address", + "bau_value": lambda df: safe_get(df, "inputs.Meta.address", "None provided"), + "scenario_value": lambda df: safe_get(df, "inputs.Meta.address", "None provided") + }, + + + # Webtool table configuration custom_table_webtool = [ ##################################################################################################### From 84956395b6706938e6bd2228616afa7556a6c951 Mon Sep 17 00:00:00 2001 From: "Rathod, Bhavesh" Date: Thu, 16 Oct 2025 16:54:49 -0400 Subject: [PATCH 06/63] Add URDB fields to ElectricTariffOutputs model Added multiple new fields to the ElectricTariffOutputs model and migration to store URDB-related metadata, including label, rate name, utility, effective date, voltage level, rate description, peak kW capacity min/max, additional info, energy and demand comments, and URL link. These changes support enhanced storage and retrieval of utility rate information. --- ...ffoutputs_urdb_demand_comments_and_more.py | 73 +++++++++++++++++++ reoptjl/models.py | 49 +++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 reoptjl/migrations/0107_electrictariffoutputs_urdb_demand_comments_and_more.py diff --git a/reoptjl/migrations/0107_electrictariffoutputs_urdb_demand_comments_and_more.py b/reoptjl/migrations/0107_electrictariffoutputs_urdb_demand_comments_and_more.py new file mode 100644 index 000000000..7dddf6eb4 --- /dev/null +++ b/reoptjl/migrations/0107_electrictariffoutputs_urdb_demand_comments_and_more.py @@ -0,0 +1,73 @@ +# Generated by Django 4.2.24 on 2025-10-16 20:43 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('reoptjl', '0106_electricloadoutputs_annual_peak_kw_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='electrictariffoutputs', + name='urdb_demand_comments', + field=models.TextField(blank=True, help_text='Business as usual life cycle utility minimum charge adder, after-tax', null=True), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='urdb_energy_comments', + field=models.TextField(blank=True, help_text='Business as usual life cycle utility minimum charge adder, after-tax', null=True), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='urdb_label', + field=models.TextField(blank=True, help_text='Business as usual life cycle utility minimum charge adder, after-tax', null=True), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='urdb_peak_kw_capacity_max', + field=models.FloatField(blank=True, help_text='Business as usual life cycle utility minimum charge adder, after-tax', null=True), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='urdb_peak_kw_capacity_min', + field=models.FloatField(blank=True, help_text='Business as usual life cycle utility minimum charge adder, after-tax', null=True), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='urdb_rate_additional_info', + field=models.TextField(blank=True, help_text='Business as usual life cycle utility minimum charge adder, after-tax', null=True), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='urdb_rate_description', + field=models.TextField(blank=True, help_text='Business as usual life cycle utility minimum charge adder, after-tax', null=True), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='urdb_rate_effective_date', + field=models.TextField(blank=True, help_text='Business as usual life cycle utility minimum charge adder, after-tax', null=True), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='urdb_rate_name', + field=models.TextField(blank=True, help_text='Business as usual life cycle utility minimum charge adder, after-tax', null=True), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='urdb_url_link', + field=models.TextField(blank=True, help_text='Business as usual life cycle utility minimum charge adder, after-tax', null=True), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='urdb_utility', + field=models.TextField(blank=True, help_text='Business as usual life cycle utility minimum charge adder, after-tax', null=True), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='urdb_voltage_level', + field=models.TextField(blank=True, help_text='Business as usual life cycle utility minimum charge adder, after-tax', null=True), + ), + ] diff --git a/reoptjl/models.py b/reoptjl/models.py index c068360d1..d6c421075 100644 --- a/reoptjl/models.py +++ b/reoptjl/models.py @@ -2759,6 +2759,55 @@ class ElectricTariffOutputs(BaseModel, models.Model): help_text="" ) + urdb_label = models.TextField( + null=True, blank=True, + help_text="Business as usual life cycle utility minimum charge adder, after-tax" + ) + urdb_rate_name = models.TextField( + null=True, blank=True, + help_text="Business as usual life cycle utility minimum charge adder, after-tax" + ) + urdb_utility = models.TextField( + null=True, blank=True, + help_text="Business as usual life cycle utility minimum charge adder, after-tax" + ) + urdb_rate_effective_date = models.TextField( + null=True, blank=True, + help_text="Business as usual life cycle utility minimum charge adder, after-tax" + ) + urdb_voltage_level = models.TextField( + null=True, blank=True, + help_text="Business as usual life cycle utility minimum charge adder, after-tax" + ) + urdb_rate_description = models.TextField( + null=True, blank=True, + help_text="Business as usual life cycle utility minimum charge adder, after-tax" + ) + urdb_peak_kw_capacity_min = models.FloatField( + null=True, blank=True, + help_text="Business as usual life cycle utility minimum charge adder, after-tax" + ) + urdb_peak_kw_capacity_max = models.FloatField( + null=True, blank=True, + help_text="Business as usual life cycle utility minimum charge adder, after-tax" + ) + urdb_rate_additional_info = models.TextField( + null=True, blank=True, + help_text="Business as usual life cycle utility minimum charge adder, after-tax" + ) + urdb_energy_comments = models.TextField( + null=True, blank=True, + help_text="Business as usual life cycle utility minimum charge adder, after-tax" + ) + urdb_demand_comments = models.TextField( + null=True, blank=True, + help_text="Business as usual life cycle utility minimum charge adder, after-tax" + ) + urdb_url_link = models.TextField( + null=True, blank=True, + help_text="Business as usual life cycle utility minimum charge adder, after-tax" + ) + year_one_energy_cost_before_tax = models.FloatField( null=True, blank=True, help_text="Optimal year one utility energy cost" From 4b3078a0a02720f98ddb97722a3eb9548def464a Mon Sep 17 00:00:00 2001 From: Grant Ellwood <120485824+gellwood@users.noreply.github.com> Date: Tue, 21 Oct 2025 12:20:29 -0700 Subject: [PATCH 07/63] Created custom table for ANCCR results Adds custom_table_anccr with a comprehensive configuration including general info, rate analysis, annual and monthly costs, load metrics, URDB rate info, and detailed monthly breakdowns. --- reoptjl/custom_table_config.py | 705 ++++++++++++++++++++++++++++++++- 1 file changed, 688 insertions(+), 17 deletions(-) diff --git a/reoptjl/custom_table_config.py b/reoptjl/custom_table_config.py index c678cdb84..3beb1d4b7 100644 --- a/reoptjl/custom_table_config.py +++ b/reoptjl/custom_table_config.py @@ -110,22 +110,6 @@ } ] - -custom_table_anccr = [ - { - "key": "site", - "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "inputs.Meta.description", "None provided") - }, - { - "label": "Site Address", - "key": "site_address", - "bau_value": lambda df: safe_get(df, "inputs.Meta.address", "None provided"), - "scenario_value": lambda df: safe_get(df, "inputs.Meta.address", "None provided") - }, - - - # Webtool table configuration custom_table_webtool = [ ##################################################################################################### @@ -1257,4 +1241,691 @@ "formula": lambda col, bau, headers: f'=({bau["placeholder1_value"]}-{col}{headers["Placeholder2"] + 2})/{bau["placeholder1_value"]}' # This formula calculates the percentage change of Placeholder2 using Placeholder1's BAU value as the reference. } -] \ No newline at end of file +] + + + +custom_table_anccr = [ +##################################################################################################### +################################ Need to get the RATE NAME to appear in header ################ +##################################################################################################### + + # { + # "label": "Rate Headers", + # "key": "site", + # "bau_value": lambda df: "", + # "scenario_value": lambda df: safe_get(df, "", "") + # }, + +##################################################################################################### +################################ General Information ################################ +##################################################################################################### + + { + "label": "Installation Name", # Not REopt result; Name based on UTRMS Data + "key": "installation_name", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + { + "label": "Site Location", # Not REopt result; Name based on User input + "key": "site_location", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + { + "label": "Utility Name", # Not REopt result; Name based on User input + "key": "utility_name", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + +##################################################################################################### +################################ Rate Analysis Summary ################################ +##################################################################################################### + + { + "label": "Rate Analysis Summary", + "key": "rate_analysis_summary_separator", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + { + "label": "Rate Name", # Not REopt result; Name based on User input + "key": "rate_name", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + { + "label": "Voltage Level", # Not REopt result; Name based on User input + "key": "voltage_level", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + { + "label": "Year 1 Fixed Charges ($)", + "key": "year_1_fixed_charges", + "bau_value": lambda df: safe_get(df, "ElectricTariff.year_one_fixed_cost_before_tax_bau"), + "scenario_value": lambda df: "" + }, + { + "label": "Year 1 Energy Charges ($)", + "key": "year_1_energy_charges", + "bau_value": lambda df: safe_get(df, "ElectricTariff.year_one_energy_cost_before_tax_bau"), + "scenario_value": lambda df: "" + }, + { + "label": "Year 1 Demand Charges ($)", + "key": "year_1_demand_charges", + "bau_value": lambda df: safe_get(df, "ElectricTariff.year_one_demand_cost_before_tax_bau"), + "scenario_value": lambda df: "" + }, + { + "label": "Year 1 Total Bill Charges ($)", + "key": "year_1_total_bill_charges", + "bau_value": lambda df: safe_get(df, "ElectricTariff.year_one_bill_before_tax_bau"), + "scenario_value": lambda df: "" + }, + { + "label": "Change in Year 1 Charges ($)", # this value will need to be calculated compared to the current rate + "key": "change_in_year_1_charges", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + +##################################################################################################### +################################ Year 1 Annual Costs ################################ +##################################################################################################### + + { + "label": "Year 1 Annual Costs", + "key": "year_1_annual_costs_separator", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + { + "label": "Year 1 Fixed Charges ($)", + "key": "year_1_fixed_charges", + "bau_value": lambda df: safe_get(df, "ElectricTariff.year_one_fixed_cost_before_tax_bau"), + "scenario_value": lambda df: "" + }, + { + "label": "Year 1 Fixed Charges Percent Change (%)", # this value will need to be calculated compared to the current rate + "key": "year_1_fixed_charges_percent_change", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + { + "label": "Year 1 Energy Charges ($)", + "key": "year_1_energy_charges", + "bau_value": lambda df: safe_get(df, "ElectricTariff.year_one_energy_cost_before_tax_bau"), + "scenario_value": lambda df: "" + }, + { + "label": "Year 1 Energy Charges Percent Change (%)", # this value will need to be calculated compared to the current rate + "key": "year_1_energy_charges_percent_change", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + { + "label": "Year 1 Demand Charges ($)", + "key": "year_1_demand_charges", + "bau_value": lambda df: safe_get(df, "ElectricTariff.year_one_demand_cost_before_tax_bau"), + "scenario_value": lambda df: "" + }, + { + "label": "Year 1 Demand Charges Percent Change (%)", # this value will need to be calculated compared to the current rate + "key": "year_1_demand_charges_percent_change", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + { + "label": "Year 1 Total Bill Charges ($)", + "key": "year_1_total_bill_charges", + "bau_value": lambda df: safe_get(df, "ElectricTariff.year_one_bill_before_tax_bau"), + "scenario_value": lambda df: "" + }, + { + "label": "Year 1 Total Bill Charges Percent Change (%)", # this value will need to be calculated compared to the current rate + "key": "year_1_total_bill_charges_percent_change", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + +##################################################################################################### +################################ Year 1 Annual Costs as a Percent of Total Bill ################################ +##################################################################################################### + + { + "label": "Year 1 Annual Costs as a Percent of Total Bill", + "key": "year_1_annual_costs_percent_of_total_bill_separator", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + { + "label": "Year 1 Fixed Charges Percent of Total Bill (%)", # value will be a calculation + "key": "year_1_fixed_charges_percent_of_total_bill", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + { + "label": "Year 1 Energy Charges Percent of Total Bill (%)", # value will be a calculation + "key": "year_1_energy_charges_percent_of_total_bill", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + { + "label": "Year 1 Demand Charges Percent of Total Bill (%)", # value will be a calculation + "key": "year_1_demand_charges_percent_of_total_bill", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + +##################################################################################################### +################################ Load Metrics ################################ +##################################################################################################### + + { + "label": "Load Metrics", + "key": "year_1_load_metrics_separator", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + { + "label": "Annual Grid Purchases (kWh)", + "key": "annual_grid_purchases_kwh", + "bau_value": lambda df: safe_get(df, "ElectricLoad.annual_calculated_kwh"), + "scenario_value": lambda df: "" + }, + { + "label": "Year 1 Peak Load (kW)", + "key": "year_1_peak_load_kw", + "bau_value": lambda df: safe_get(df, "ElectricLoad.annual_peak_kw"), + "scenario_value": lambda df: "" + }, + +##################################################################################################### +################################ URDB Rate Information ################################ +##################################################################################################### + { + "label": "URDB Rate Information", + "key": "urdb_rate_information_separator", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + { + "label": "URDB Label", # will need to pull from URDB + "key": "urdb_label", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + { + "label": "Rate Effective Date (latest_update)", # will need to pull from URDB + "key": "rate_effective_date", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + { + "label": "Voltage Level (voltagecategory)", # will need to pull from URDB + "key": "voltage_level_urdb", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + { + "label": "Peak kW Capacity Min (peakkwcapacitymin)", # will need to pull from URDB + "key": "peak_kw_capacity_min", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + { + "label": "Peak kW Capacity Max (peakkwcapacitymax)", # will need to pull from URDB + "key": "peak_kw_capacity_max", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + { + "label": "Rate Description (description)", # will need to pull from URDB + "key": "rate_description", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + { + "label": "Additional Information (basicinformationcomments)", # will need to pull from URDB + "key": "additional_information", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + { + "label": "Energy Comments (energycomments)", # will need to pull from URDB + "key": "energy_comments", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + { + "label": "Demand Comments (demandcomments)", # will need to pull from URDB + "key": "demand_comments", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + { + "label": "URDB Link", # will need to pull from URDB + "key": "urdb_link", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + +##################################################################################################### +################################ Year 1 Monthly Energy Costs ################################ +##################################################################################################### + + { + "label": "Year 1 Monthly Energy Costs ($)", + "key": "year_1_monthly_energy_costs_separator", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + { + "label": "January Energy Cost ($)", + "key": "january_energy_cost", + "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.0"), + "scenario_value": lambda df: "" + }, + { + "label": "February Energy Cost ($)", + "key": "february_energy_cost", + "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.1"), + "scenario_value": lambda df: "" + }, + { + "label": "March Energy Cost ($)", + "key": "march_energy_cost", + "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.2"), + "scenario_value": lambda df: "" + }, + { + "label": "April Energy Cost ($)", + "key": "april_energy_cost", + "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.3"), + "scenario_value": lambda df: "" + }, + { + "label": "May Energy Cost ($)", + "key": "may_energy_cost", + "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.4"), + "scenario_value": lambda df: "" + }, + { + "label": "June Energy Cost ($)", + "key": "june_energy_cost", + "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.5"), + "scenario_value": lambda df: "" + }, + { + "label": "July Energy Cost ($)", + "key": "july_energy_cost", + "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.6"), + "scenario_value": lambda df: "" + }, + { + "label": "August Energy Cost ($)", + "key": "august_energy_cost", + "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.7"), + "scenario_value": lambda df: "" + }, + { + "label": "September Energy Cost ($)", + "key": "september_energy_cost", + "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.8"), + "scenario_value": lambda df: "" + }, + { + "label": "October Energy Cost ($)", + "key": "october_energy_cost", + "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.9"), + "scenario_value": lambda df: "" + }, + { + "label": "November Energy Cost ($)", + "key": "november_energy_cost", + "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.10"), + "scenario_value": lambda df: "" + }, + { + "label": "December Energy Cost ($)", + "key": "december_energy_cost", + "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.11"), + "scenario_value": lambda df: "" + }, + +##################################################################################################### +################################ Year 1 Monthly Demand Costs ################################ +##################################################################################################### + + { + "label": "Year 1 Monthly Demand Costs ($)", + "key": "year_1_monthly_demand_costs_separator", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + { + "label": "January Demand Cost ($)", + "key": "january_demand_cost", + "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.0") + safe_get(df, "ElectricTariff.monthly_facility_demand_cost_series_before_tax.0"), + "scenario_value": lambda df: "" + }, + { + "label": "February Demand Cost ($)", + "key": "february_demand_cost", + "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.1") + safe_get(df, "ElectricTariff.monthly_facility_demand_cost_series_before_tax.1"), + "scenario_value": lambda df: "" + }, + { + "label": "March Demand Cost ($)", + "key": "march_demand_cost", + "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.2") + safe_get(df, "ElectricTariff.monthly_facility_demand_cost_series_before_tax.2"), + "scenario_value": lambda df: "" + }, + { + "label": "April Demand Cost ($)", + "key": "april_demand_cost", + "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.3") + safe_get(df, "ElectricTariff.monthly_facility_demand_cost_series_before_tax.3"), + "scenario_value": lambda df: "" + }, + { + "label": "May Demand Cost ($)", + "key": "may_demand_cost", + "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.4") + safe_get(df, "ElectricTariff.monthly_facility_demand_cost_series_before_tax.4"), + "scenario_value": lambda df: "" + }, + { + "label": "June Demand Cost ($)", + "key": "june_demand_cost", + "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.5") + safe_get(df, "ElectricTariff.monthly_facility_demand_cost_series_before_tax.5"), + "scenario_value": lambda df: "" + }, + { + "label": "July Demand Cost ($)", + "key": "july_demand_cost", + "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.6") + safe_get(df, "ElectricTariff.monthly_facility_demand_cost_series_before_tax.6"), + "scenario_value": lambda df: "" + }, + { + "label": "August Demand Cost ($)", + "key": "august_demand_cost", + "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.7") + safe_get(df, "ElectricTariff.monthly_facility_demand_cost_series_before_tax.7"), + "scenario_value": lambda df: "" + }, + { + "label": "September Demand Cost ($)", + "key": "september_demand_cost", + "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.8") + safe_get(df, "ElectricTariff.monthly_facility_demand_cost_series_before_tax.8"), + "scenario_value": lambda df: "" + }, + { + "label": "October Demand Cost ($)", + "key": "october_demand_cost", + "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.9") + safe_get(df, "ElectricTariff.monthly_facility_demand_cost_series_before_tax.9"), + "scenario_value": lambda df: "" + }, + { + "label": "November Demand Cost ($)", + "key": "november_demand_cost", + "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.10") + safe_get(df, "ElectricTariff.monthly_facility_demand_cost_series_before_tax.10"), + "scenario_value": lambda df: "" + }, + { + "label": "December Demand Cost ($)", + "key": "december_demand_cost", + "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.11") + safe_get(df, "ElectricTariff.monthly_facility_demand_cost_series_before_tax.11"), + "scenario_value": lambda df: "" + }, + +##################################################################################################### +################################ Year 1 Monthly Total Bill Costs ################################ +##################################################################################################### + + { + "label": "Year 1 Monthly Total Bill Costs ($)", + "key": "year_1_monthly_total_bill_costs_separator", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + { + "label": "January Total Bill Cost ($)", # not sure if we have a monthly total bill cost output in REopt -- need to ask Bhavesh + "key": "january_total_bill_cost", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + { + "label": "February Total Bill Cost ($)", + "key": "february_total_bill_cost", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + { + "label": "March Total Bill Cost ($)", + "key": "march_total_bill_cost", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + { + "label": "April Total Bill Cost ($)", + "key": "april_total_bill_cost", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + { + "label": "May Total Bill Cost ($)", + "key": "may_total_bill_cost", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + { + "label": "June Total Bill Cost ($)", + "key": "june_total_bill_cost", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + { + "label": "July Total Bill Cost ($)", + "key": "july_total_bill_cost", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + { + "label": "August Total Bill Cost ($)", + "key": "august_total_bill_cost", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + { + "label": "September Total Bill Cost ($)", + "key": "september_total_bill_cost", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + { + "label": "October Total Bill Cost ($)", + "key": "october_total_bill_cost", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + { + "label": "November Total Bill Cost ($)", + "key": "november_total_bill_cost", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + { + "label": "December Total Bill Cost ($)", + "key": "december_total_bill_cost", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + +##################################################################################################### +################################ Monthly Energy Consumption (kWh) ################################ +##################################################################################################### + + { + "label": "Monthly Energy Consumption (kWh)", + "key": "monthly_energy_consumption_separator", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + { + "label": "January Energy Consumption (kWh)", + "key": "january_energy_consumption_kwh", + "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_calculated_kwh.0"), + "scenario_value": lambda df: "" + }, + { + "label": "February Energy Consumption (kWh)", + "key": "february_energy_consumption_kwh", + "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_calculated_kwh.1"), + "scenario_value": lambda df: "" + }, + { + "label": "March Energy Consumption (kWh)", + "key": "march_energy_consumption_kwh", + "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_calculated_kwh.2"), + "scenario_value": lambda df: "" + }, + { + "label": "April Energy Consumption (kWh)", + "key": "april_energy_consumption_kwh", + "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_calculated_kwh.3"), + "scenario_value": lambda df: "" + }, + { + "label": "May Energy Consumption (kWh)", + "key": "may_energy_consumption_kwh", + "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_calculated_kwh.4"), + "scenario_value": lambda df: "" + }, + { + "label": "June Energy Consumption (kWh)", + "key": "june_energy_consumption_kwh", + "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_calculated_kwh.5"), + "scenario_value": lambda df: "" + }, + { + "label": "July Energy Consumption (kWh)", + "key": "july_energy_consumption_kwh", + "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_calculated_kwh.6"), + "scenario_value": lambda df: "" + }, + { + "label": "August Energy Consumption (kWh)", + "key": "august_energy_consumption_kwh", + "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_calculated_kwh.7"), + "scenario_value": lambda df: "" + }, + { + "label": "September Energy Consumption (kWh)", + "key": "september_energy_consumption_kwh", + "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_calculated_kwh.8"), + "scenario_value": lambda df: "" + }, + { + "label": "October Energy Consumption (kWh)", + "key": "october_energy_consumption_kwh", + "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_calculated_kwh.9"), + "scenario_value": lambda df: "" + }, + { + "label": "November Energy Consumption (kWh)", + "key": "november_energy_consumption_kwh", + "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_calculated_kwh.10"), + "scenario_value": lambda df: "" + }, + { + "label": "December Energy Consumption (kWh)", + "key": "december_energy_consumption_kwh", + "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_calculated_kwh.11"), + "scenario_value": lambda df: "" + }, + +##################################################################################################### +################################ Monthly Peak Load (kW) ################################ +##################################################################################################### + + { + "label": "Monthly Peak Load (kW)", + "key": "monthly_peak_load_separator", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + { + "label": "January Peak Load (kW)", + "key": "january_peak_load_kw", + "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_peak_kw.0"), + "scenario_value": lambda df: "" + }, + { + "label": "February Peak Load (kW)", + "key": "february_peak_load_kw", + "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_peak_kw.1"), + "scenario_value": lambda df: "" + }, + { + "label": "March Peak Load (kW)", + "key": "march_peak_load_kw", + "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_peak_kw.2"), + "scenario_value": lambda df: "" + }, + { + "label": "April Peak Load (kW)", + "key": "april_peak_load_kw", + "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_peak_kw.3"), + "scenario_value": lambda df: "" + }, + { + "label": "May Peak Load (kW)", + "key": "may_peak_load_kw", + "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_peak_kw.4"), + "scenario_value": lambda df: "" + }, + { + "label": "June Peak Load (kW)", + "key": "june_peak_load_kw", + "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_peak_kw.5"), + "scenario_value": lambda df: "" + }, + { + "label": "July Peak Load (kW)", + "key": "july_peak_load_kw", + "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_peak_kw.6"), + "scenario_value": lambda df: "" + }, + { + "label": "August Peak Load (kW)", + "key": "august_peak_load_kw", + "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_peak_kw.7"), + "scenario_value": lambda df: "" + }, + { + "label": "September Peak Load (kW)", + "key": "september_peak_load_kw", + "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_peak_kw.8"), + "scenario_value": lambda df: "" + }, + { + "label": "October Peak Load (kW)", + "key": "october_peak_load_kw", + "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_peak_kw.9"), + "scenario_value": lambda df: "" + }, + { + "label": "November Peak Load (kW)", + "key": "november_peak_load_kw", + "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_peak_kw.10"), + "scenario_value": lambda df: "" + }, + { + "label": "December Peak Load (kW)", + "key": "december_peak_load_kw", + "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_peak_kw.11"), + "scenario_value": lambda df: "" + } +] From aebd41ffd88fe8494670a322f8d66d6d7c2fd367 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Wed, 22 Oct 2025 12:16:36 -0600 Subject: [PATCH 08/63] Update REopt#elec_util_usage after develop merge --- julia_src/Manifest.toml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/julia_src/Manifest.toml b/julia_src/Manifest.toml index 196aae6bd..794ce8c5e 100644 --- a/julia_src/Manifest.toml +++ b/julia_src/Manifest.toml @@ -948,9 +948,11 @@ version = "1.11.0" [[deps.REopt]] deps = ["ArchGDAL", "CSV", "CoolProp", "DataFrames", "Dates", "DelimitedFiles", "HTTP", "JLD", "JSON", "JuMP", "LinDistFlow", "LinearAlgebra", "Logging", "MathOptInterface", "Requires", "Roots", "Statistics", "TestEnv"] -git-tree-sha1 = "103761fa0f7447377726347af656cde6ab1160cc" +git-tree-sha1 = "dc4c262d7bab3c195154f48daa92451cf8769fea" +repo-rev = "elec_util_usage" +repo-url = "https://github.com/NREL/REopt.jl.git" uuid = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6" -version = "0.55.1" +version = "0.55.0" [[deps.Random]] deps = ["SHA"] From a7fdea9b9f1fcc3125aa55a57b9e9cefbbcc6f24 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Wed, 22 Oct 2025 12:17:05 -0600 Subject: [PATCH 09/63] Migration --merge after merging develop --- reoptjl/migrations/0110_merge_20251022_1752.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 reoptjl/migrations/0110_merge_20251022_1752.py diff --git a/reoptjl/migrations/0110_merge_20251022_1752.py b/reoptjl/migrations/0110_merge_20251022_1752.py new file mode 100644 index 000000000..cb76a4e89 --- /dev/null +++ b/reoptjl/migrations/0110_merge_20251022_1752.py @@ -0,0 +1,14 @@ +# Generated by Django 4.2.25 on 2025-10-22 17:52 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('reoptjl', '0107_electrictariffoutputs_urdb_demand_comments_and_more'), + ('reoptjl', '0109_remove_ghpoutputs_iterations_auto_guess_and_more'), + ] + + operations = [ + ] From 19d2bb2e1ca3527e4f47f12b19d63baf7aaf7377 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Wed, 22 Oct 2025 17:08:40 -0600 Subject: [PATCH 10/63] Move urdb_metadata into ElectricTariffInputs and group into single json field instead of separate --- ...ffoutputs_urdb_demand_comments_and_more.py | 66 +++++++++++++++++++ reoptjl/models.py | 53 ++------------- 2 files changed, 70 insertions(+), 49 deletions(-) create mode 100644 reoptjl/migrations/0111_remove_electrictariffoutputs_urdb_demand_comments_and_more.py diff --git a/reoptjl/migrations/0111_remove_electrictariffoutputs_urdb_demand_comments_and_more.py b/reoptjl/migrations/0111_remove_electrictariffoutputs_urdb_demand_comments_and_more.py new file mode 100644 index 000000000..f4eeb281d --- /dev/null +++ b/reoptjl/migrations/0111_remove_electrictariffoutputs_urdb_demand_comments_and_more.py @@ -0,0 +1,66 @@ +# Generated by Django 4.2.25 on 2025-10-22 22:17 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('reoptjl', '0110_merge_20251022_1752'), + ] + + operations = [ + migrations.RemoveField( + model_name='electrictariffoutputs', + name='urdb_demand_comments', + ), + migrations.RemoveField( + model_name='electrictariffoutputs', + name='urdb_energy_comments', + ), + migrations.RemoveField( + model_name='electrictariffoutputs', + name='urdb_label', + ), + migrations.RemoveField( + model_name='electrictariffoutputs', + name='urdb_peak_kw_capacity_max', + ), + migrations.RemoveField( + model_name='electrictariffoutputs', + name='urdb_peak_kw_capacity_min', + ), + migrations.RemoveField( + model_name='electrictariffoutputs', + name='urdb_rate_additional_info', + ), + migrations.RemoveField( + model_name='electrictariffoutputs', + name='urdb_rate_description', + ), + migrations.RemoveField( + model_name='electrictariffoutputs', + name='urdb_rate_effective_date', + ), + migrations.RemoveField( + model_name='electrictariffoutputs', + name='urdb_rate_name', + ), + migrations.RemoveField( + model_name='electrictariffoutputs', + name='urdb_url_link', + ), + migrations.RemoveField( + model_name='electrictariffoutputs', + name='urdb_utility', + ), + migrations.RemoveField( + model_name='electrictariffoutputs', + name='urdb_voltage_level', + ), + migrations.AddField( + model_name='electrictariffinputs', + name='urdb_metadata', + field=models.JSONField(blank=True, help_text='Utility rate meta data from Utility Rate Database API', null=True), + ), + ] diff --git a/reoptjl/models.py b/reoptjl/models.py index 38ec9f3be..5e01d86f5 100644 --- a/reoptjl/models.py +++ b/reoptjl/models.py @@ -1738,6 +1738,10 @@ class ElectricTariffInputs(BaseModel, models.Model): help_text=("Optional coincident peak demand charge that is applied to the max load during the time_steps " "specified in coincident_peak_load_active_time_steps") ) + urdb_metadata = models.JSONField( + null=True, blank=True, + help_text=("Utility rate meta data from Utility Rate Database API") + ) def clean(self): error_messages = {} @@ -2799,55 +2803,6 @@ class ElectricTariffOutputs(BaseModel, models.Model): help_text="" ) - urdb_label = models.TextField( - null=True, blank=True, - help_text="Business as usual life cycle utility minimum charge adder, after-tax" - ) - urdb_rate_name = models.TextField( - null=True, blank=True, - help_text="Business as usual life cycle utility minimum charge adder, after-tax" - ) - urdb_utility = models.TextField( - null=True, blank=True, - help_text="Business as usual life cycle utility minimum charge adder, after-tax" - ) - urdb_rate_effective_date = models.TextField( - null=True, blank=True, - help_text="Business as usual life cycle utility minimum charge adder, after-tax" - ) - urdb_voltage_level = models.TextField( - null=True, blank=True, - help_text="Business as usual life cycle utility minimum charge adder, after-tax" - ) - urdb_rate_description = models.TextField( - null=True, blank=True, - help_text="Business as usual life cycle utility minimum charge adder, after-tax" - ) - urdb_peak_kw_capacity_min = models.FloatField( - null=True, blank=True, - help_text="Business as usual life cycle utility minimum charge adder, after-tax" - ) - urdb_peak_kw_capacity_max = models.FloatField( - null=True, blank=True, - help_text="Business as usual life cycle utility minimum charge adder, after-tax" - ) - urdb_rate_additional_info = models.TextField( - null=True, blank=True, - help_text="Business as usual life cycle utility minimum charge adder, after-tax" - ) - urdb_energy_comments = models.TextField( - null=True, blank=True, - help_text="Business as usual life cycle utility minimum charge adder, after-tax" - ) - urdb_demand_comments = models.TextField( - null=True, blank=True, - help_text="Business as usual life cycle utility minimum charge adder, after-tax" - ) - urdb_url_link = models.TextField( - null=True, blank=True, - help_text="Business as usual life cycle utility minimum charge adder, after-tax" - ) - year_one_energy_cost_before_tax = models.FloatField( null=True, blank=True, help_text="Optimal year one utility energy cost" From b2a3b6bebbb1c31ce576bb8955893f92cf0cb71b Mon Sep 17 00:00:00 2001 From: bill-becker Date: Wed, 22 Oct 2025 17:11:06 -0600 Subject: [PATCH 11/63] Rename export types to whole words --- ...et_metering_electric_to_grid_series_kw_.py | 73 +++++++++++++++++++ reoptjl/models.py | 24 +++--- 2 files changed, 85 insertions(+), 12 deletions(-) create mode 100644 reoptjl/migrations/0112_rename_nem_electric_to_grid_series_kw_electrictariffoutputs_net_metering_electric_to_grid_series_kw_.py diff --git a/reoptjl/migrations/0112_rename_nem_electric_to_grid_series_kw_electrictariffoutputs_net_metering_electric_to_grid_series_kw_.py b/reoptjl/migrations/0112_rename_nem_electric_to_grid_series_kw_electrictariffoutputs_net_metering_electric_to_grid_series_kw_.py new file mode 100644 index 000000000..5b99f0d3c --- /dev/null +++ b/reoptjl/migrations/0112_rename_nem_electric_to_grid_series_kw_electrictariffoutputs_net_metering_electric_to_grid_series_kw_.py @@ -0,0 +1,73 @@ +# Generated by Django 4.2.25 on 2025-10-22 22:21 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('reoptjl', '0111_remove_electrictariffoutputs_urdb_demand_comments_and_more'), + ] + + operations = [ + migrations.RenameField( + model_name='electrictariffoutputs', + old_name='NEM_electric_to_grid_series_kw', + new_name='net_metering_electric_to_grid_series_kw', + ), + migrations.RenameField( + model_name='electrictariffoutputs', + old_name='EXC_electric_to_grid_series_kw', + new_name='net_metering_excess_electric_to_grid_series_kw', + ), + migrations.RenameField( + model_name='electrictariffoutputs', + old_name='EXC_export_rate_series', + new_name='net_metering_excess_export_rate_series', + ), + migrations.RenameField( + model_name='electrictariffoutputs', + old_name='EXC_monthly_export_cost_benefit_before_tax', + new_name='net_metering_excess_monthly_export_cost_benefit_before_tax', + ), + migrations.RenameField( + model_name='electrictariffoutputs', + old_name='EXC_monthly_export_series_kwh', + new_name='net_metering_excess_monthly_export_series_kwh', + ), + migrations.RenameField( + model_name='electrictariffoutputs', + old_name='NEM_export_rate_series', + new_name='net_metering_export_rate_series', + ), + migrations.RenameField( + model_name='electrictariffoutputs', + old_name='NEM_monthly_export_cost_benefit_before_tax', + new_name='net_metering_monthly_export_cost_benefit_before_tax', + ), + migrations.RenameField( + model_name='electrictariffoutputs', + old_name='NEM_monthly_export_series_kwh', + new_name='net_metering_monthly_export_series_kwh', + ), + migrations.RenameField( + model_name='electrictariffoutputs', + old_name='WHL_electric_to_grid_series_kw', + new_name='wholesale_electric_to_grid_series_kw', + ), + migrations.RenameField( + model_name='electrictariffoutputs', + old_name='WHL_export_rate_series', + new_name='wholesale_export_rate_series', + ), + migrations.RenameField( + model_name='electrictariffoutputs', + old_name='WHL_monthly_export_cost_benefit_before_tax', + new_name='wholesale_monthly_export_cost_benefit_before_tax', + ), + migrations.RenameField( + model_name='electrictariffoutputs', + old_name='WHL_monthly_export_series_kwh', + new_name='wholesale_monthly_export_series_kwh', + ), + ] diff --git a/reoptjl/models.py b/reoptjl/models.py index 5e01d86f5..59786030a 100644 --- a/reoptjl/models.py +++ b/reoptjl/models.py @@ -2675,84 +2675,84 @@ class ElectricTariffOutputs(BaseModel, models.Model): default=list, help_text="Series of total time of use demand charges by month." ) - NEM_export_rate_series = ArrayField( + net_metering_export_rate_series = ArrayField( models.FloatField( null=True, blank=True ), default=list, help_text="Series of compensation rate in each timestep for exporting power to grid using systems sized up to net metering limit." ) - NEM_electric_to_grid_series_kw = ArrayField( + net_metering_electric_to_grid_series_kw = ArrayField( models.FloatField( null=True, blank=True ), default=list, help_text="Series of power exports to the grid up to net metering limit for each timestep." ) - NEM_monthly_export_series_kwh = ArrayField( + net_metering_monthly_export_series_kwh = ArrayField( models.FloatField( null=True, blank=True ), default=list, help_text="Year one cost of electricity consumed in each month." ) - NEM_monthly_export_cost_benefit_before_tax = ArrayField( + net_metering_monthly_export_cost_benefit_before_tax = ArrayField( models.FloatField( null=True, blank=True ), default=list, help_text="Year one cost of electricity consumed in each month." ) - WHL_export_rate_series = ArrayField( + wholesale_export_rate_series = ArrayField( models.FloatField( null=True, blank=True ), default=list, help_text="Series of compensation rate in each timestep for exporting power to grid for wholesale." ) - WHL_electric_to_grid_series_kw = ArrayField( + wholesale_electric_to_grid_series_kw = ArrayField( models.FloatField( null=True, blank=True ), default=list, help_text="Series of power exports to the grid for wholesale in each timestep." ) - WHL_monthly_export_series_kwh = ArrayField( + wholesale_monthly_export_series_kwh = ArrayField( models.FloatField( null=True, blank=True ), default=list, help_text="Series of monthly energy exports to grid under wholesale benefit." ) - WHL_monthly_export_cost_benefit_before_tax = ArrayField( + wholesale_monthly_export_cost_benefit_before_tax = ArrayField( models.FloatField( null=True, blank=True ), default=list, help_text="Series of monthly monetary benefit from exporting power to grid at wholesale rate." ) - EXC_export_rate_series = ArrayField( + net_metering_excess_export_rate_series = ArrayField( models.FloatField( null=True, blank=True ), default=list, help_text="Series of compensation rate in each timestep for exporting power to grid beyond net metering limit." ) - EXC_electric_to_grid_series_kw = ArrayField( + net_metering_excess_electric_to_grid_series_kw = ArrayField( models.FloatField( null=True, blank=True ), default=list, help_text="Series of power exports to the grid above net metering limit for each timestep." ) - EXC_monthly_export_series_kwh = ArrayField( + net_metering_excess_monthly_export_series_kwh = ArrayField( models.FloatField( null=True, blank=True ), default=list, help_text="Series of monthly energy exports to grid above net metering limit." ) - EXC_monthly_export_cost_benefit_before_tax = ArrayField( + net_metering_excess_monthly_export_cost_benefit_before_tax = ArrayField( models.FloatField( null=True, blank=True ), From 1a3e4fd84dada8ab012ee2fcffca42a52621d134 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Wed, 22 Oct 2025 17:24:28 -0600 Subject: [PATCH 12/63] Update ElectricTariffInputs urdb_metadata from Julia in process_results --- julia_src/Manifest.toml | 4 ++-- julia_src/http.jl | 11 ++++++++++- reoptjl/src/process_results.py | 5 ++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/julia_src/Manifest.toml b/julia_src/Manifest.toml index 794ce8c5e..88fc351af 100644 --- a/julia_src/Manifest.toml +++ b/julia_src/Manifest.toml @@ -948,11 +948,11 @@ version = "1.11.0" [[deps.REopt]] deps = ["ArchGDAL", "CSV", "CoolProp", "DataFrames", "Dates", "DelimitedFiles", "HTTP", "JLD", "JSON", "JuMP", "LinDistFlow", "LinearAlgebra", "Logging", "MathOptInterface", "Requires", "Roots", "Statistics", "TestEnv"] -git-tree-sha1 = "dc4c262d7bab3c195154f48daa92451cf8769fea" +git-tree-sha1 = "18d4e3f1c74fa7804716f9fedf53f9c0755c7248" repo-rev = "elec_util_usage" repo-url = "https://github.com/NREL/REopt.jl.git" uuid = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6" -version = "0.55.0" +version = "0.55.1" [[deps.Random]] deps = ["SHA"] diff --git a/julia_src/http.jl b/julia_src/http.jl index 817b8837d..4674fd24e 100644 --- a/julia_src/http.jl +++ b/julia_src/http.jl @@ -237,6 +237,14 @@ function reopt(req::HTTP.Request) high_temp_storage_dict = Dict(key=>getfield(model_inputs.s.storage.attr["HighTempThermalStorage"], key) for key in inputs_with_defaults_from_julia_high_temp_storage) else high_temp_storage_dict = Dict() + end + if haskey(d, "ElectricTariff") && !isempty(get(d["ElectricTariff"], "urdb_metadata", Dict())) + inputs_from_julia_electric_tariff = [ + :urdb_metadata + ] + electric_tariff_dict = Dict(key=>getfield(model_inputs.s.electric_tariff, key) for key in inputs_from_julia_electric_tariff) + else + electric_tariff_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_julia_financial), @@ -253,7 +261,8 @@ function reopt(req::HTTP.Request) "ElectricStorage" => electric_storage_dict, "ColdThermalStorage" => cold_storage_dict, "HotThermalStorage" => hot_storage_dict, - "HighTempThermalStorage" => high_temp_storage_dict + "HighTempThermalStorage" => high_temp_storage_dict, + "ElectricTariff" => electric_tariff_dict ) catch e @error "Something went wrong in REopt optimization!" exception=(e, catch_backtrace()) diff --git a/reoptjl/src/process_results.py b/reoptjl/src/process_results.py index 20b26e209..22bf0a292 100644 --- a/reoptjl/src/process_results.py +++ b/reoptjl/src/process_results.py @@ -9,7 +9,7 @@ SteamTurbineOutputs, GHPInputs, GHPOutputs, ExistingChillerInputs, \ ElectricHeaterOutputs, ASHPSpaceHeaterOutputs, ASHPWaterHeaterOutputs, \ SiteInputs, ASHPSpaceHeaterInputs, ASHPWaterHeaterInputs, CSTInputs, CSTOutputs, PVInputs, \ - HighTempThermalStorageInputs, HighTempThermalStorageOutputs + HighTempThermalStorageInputs, HighTempThermalStorageOutputs, ElectricTariffInputs import numpy as np import sys import traceback as tb @@ -179,6 +179,9 @@ def update_inputs_in_database(inputs_to_update: dict, run_uuid: str) -> None: if inputs_to_update.get("HighTempThermalStorage") is not None: prune_update_fields(HighTempThermalStorageInputs, inputs_to_update["HighTempThermalStorage"]) HighTempThermalStorageInputs.objects.filter(meta__run_uuid=run_uuid).update(**inputs_to_update["HighTempThermalStorage"]) + if inputs_to_update.get("ElectricTariff"): + prune_update_fields(ElectricTariffInputs, inputs_to_update["ElectricTariff"]) + ElectricTariffInputs.objects.filter(meta__run_uuid=run_uuid).update(**inputs_to_update["ElectricTariff"]) except Exception as e: exc_type, exc_value, exc_traceback = sys.exc_info() debug_msg = "exc_type: {}; exc_value: {}; exc_traceback: {}".format( From 242dede222411baeefdc5ccdc67e1b8db39cb1d9 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Wed, 22 Oct 2025 20:37:18 -0600 Subject: [PATCH 13/63] Avoid error handling breakdown if ElectricTariff is not present, and fix check for urdb_metadata --- julia_src/http.jl | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/julia_src/http.jl b/julia_src/http.jl index 4674fd24e..b59e8f418 100644 --- a/julia_src/http.jl +++ b/julia_src/http.jl @@ -238,7 +238,7 @@ function reopt(req::HTTP.Request) else high_temp_storage_dict = Dict() end - if haskey(d, "ElectricTariff") && !isempty(get(d["ElectricTariff"], "urdb_metadata", Dict())) + if haskey(d, "ElectricTariff") && !isempty(model_inputs.s.electric_tariff.urdb_metadata) inputs_from_julia_electric_tariff = [ :urdb_metadata ] @@ -283,9 +283,11 @@ function reopt(req::HTTP.Request) if isempty(error_response) @info "REopt model solved with status $(results["status"])." - # these are matrices that need to be vector. - results["ElectricTariff"]["year_one_electric_to_load_energy_cost_series_before_tax"] = results["ElectricTariff"]["year_one_electric_to_load_energy_cost_series_before_tax"][:,1] - results["ElectricTariff"]["monthly_facility_demand_cost_series_before_tax"] = results["ElectricTariff"]["monthly_facility_demand_cost_series_before_tax"][:,1] + # These are matrices that need to be vector. + if haskey(results, "ElectricTariff") + results["ElectricTariff"]["year_one_electric_to_load_energy_cost_series_before_tax"] = results["ElectricTariff"]["year_one_electric_to_load_energy_cost_series_before_tax"][:,1] + results["ElectricTariff"]["monthly_facility_demand_cost_series_before_tax"] = results["ElectricTariff"]["monthly_facility_demand_cost_series_before_tax"][:,1] + end response = Dict( "results" => results, "reopt_version" => string(pkgversion(reoptjl)) From 8260d010970ea1d98c659d2857c0f0e34d3a7f3a Mon Sep 17 00:00:00 2001 From: bill-becker Date: Wed, 22 Oct 2025 20:37:52 -0600 Subject: [PATCH 14/63] Update to latest REopt#elec_util_usage --- julia_src/Manifest.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/julia_src/Manifest.toml b/julia_src/Manifest.toml index 88fc351af..a9dd913a1 100644 --- a/julia_src/Manifest.toml +++ b/julia_src/Manifest.toml @@ -948,7 +948,7 @@ version = "1.11.0" [[deps.REopt]] deps = ["ArchGDAL", "CSV", "CoolProp", "DataFrames", "Dates", "DelimitedFiles", "HTTP", "JLD", "JSON", "JuMP", "LinDistFlow", "LinearAlgebra", "Logging", "MathOptInterface", "Requires", "Roots", "Statistics", "TestEnv"] -git-tree-sha1 = "18d4e3f1c74fa7804716f9fedf53f9c0755c7248" +git-tree-sha1 = "0a5d1d2a086cd400d9b0e4758b34da51a07d6dbd" repo-rev = "elec_util_usage" repo-url = "https://github.com/NREL/REopt.jl.git" uuid = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6" From 60d90fe9c5430b8b01a6a0f5c23de85625ba6661 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Wed, 22 Oct 2025 20:38:53 -0600 Subject: [PATCH 15/63] Reduce complexity of long-solving sector defaults test --- reoptjl/test/posts/sector_defaults_post.json | 40 +++++--------------- reoptjl/test/test_job_endpoint.py | 2 - 2 files changed, 10 insertions(+), 32 deletions(-) diff --git a/reoptjl/test/posts/sector_defaults_post.json b/reoptjl/test/posts/sector_defaults_post.json index 1df5abbae..ca174468f 100644 --- a/reoptjl/test/posts/sector_defaults_post.json +++ b/reoptjl/test/posts/sector_defaults_post.json @@ -23,44 +23,24 @@ "ExistingBoiler": { "fuel_cost_per_mmbtu": 10.0 }, - "CHP": { - "prime_mover": "recip_engine", - "max_kw": 100, - "fuel_cost_per_mmbtu": 10.0, - "can_supply_steam_turbine": true - }, "PV": { "min_kw": 1000.0, "max_kw": 1000.0, "federal_itc_fraction": 0.2 }, - "SteamTurbine":{ - "min_kw": 100, - "max_kw": 100 - }, - "HighTempThermalStorage": { - "min_kwh": 10, - "max_kwh": 10, - "thermal_decay_rate_fraction": 0.0 - }, "Wind": { "min_kw": 100, "max_kw": 100 }, - "GHP": { - "require_ghp_purchase": true, - "building_sqft": 50000.0, - "can_serve_dhw": false, - "space_heating_efficiency_thermal_factor": 0.85, - "cooling_efficiency_thermal_factor": 0.6, - "ghpghx_inputs": [{ - "borehole_depth_ft": 400.0, - "simulation_years": 20, - "solver_eft_tolerance_f": 2.0, - "ghx_model": "TESS", - "tess_ghx_minimum_timesteps_per_hour": 1, - "max_sizing_iterations": 10, - "init_sizing_factor_ft_per_peak_ton": 300.0 - }] + "ElectricStorage": { + "min_kw": 50, + "max_kw": 50, + "min_kwh": 100, + "max_kwh": 100 + }, + "Boiler": { + "fuel_cost_per_mmbtu": 10.0, + "min_mmbtu_per_hour": 0.5, + "max_mmbtu_per_hour": 0.5 } } \ No newline at end of file diff --git a/reoptjl/test/test_job_endpoint.py b/reoptjl/test/test_job_endpoint.py index 6f74663aa..f880ba921 100644 --- a/reoptjl/test/test_job_endpoint.py +++ b/reoptjl/test/test_job_endpoint.py @@ -178,8 +178,6 @@ def test_sector_defaults_from_julia(self): self.assertEqual(saved_model_inputs.get(input_key), post[model_name][input_key]) else: # Check that default got assigned consistent with /sector_defaults - if model_name == "SteamTurbine" and input_key == "federal_itc_fraction": - continue #ST doesn't have federal_itc_fraction input self.assertEqual(saved_model_inputs.get(input_key), default_input_val) def test_chp_defaults_from_julia(self): From c5238caaa6904f652a8322517c0d6a988c903e25 Mon Sep 17 00:00:00 2001 From: adfarth Date: Thu, 23 Oct 2025 13:51:04 -0700 Subject: [PATCH 16/63] Update http.jl --- 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 1c9e36d99..44aa77e86 100644 --- a/julia_src/http.jl +++ b/julia_src/http.jl @@ -555,7 +555,7 @@ function simulated_load(req::HTTP.Request) end end - @info "Getting CRB Loads..." + @info "Getting CRB Loads..." # TODO: update this b/c it could be for custom loads too? data = Dict() error_response = Dict() try From 1393f17855a01c737f3f19ab87ab27afe786027f Mon Sep 17 00:00:00 2001 From: adfarth Date: Thu, 23 Oct 2025 14:14:20 -0700 Subject: [PATCH 17/63] add the input --- CHANGELOG.md | 6 ++++++ julia_src/Manifest.toml | 6 ++++-- reoptjl/models.py | 20 +++++++++++++++++--- reoptjl/views.py | 4 ++-- 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9581b3d90..66fead55c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,12 @@ Classify the change according to the following categories: ##### Removed ### Patches +## peak-scaling +### Minor Updates +##### Added +- **ElectricLoad** input **monthly_peaks_kw**. Can be used to scale loads_kw or doe_reference loads to monthly peaks while maintaining monthly energy. + + ## v3.16.2 ### Patches - Added `CST` and `HighTempThermalStorage` to all/superset inputs test. diff --git a/julia_src/Manifest.toml b/julia_src/Manifest.toml index 196aae6bd..9cabcb1f8 100644 --- a/julia_src/Manifest.toml +++ b/julia_src/Manifest.toml @@ -948,9 +948,11 @@ version = "1.11.0" [[deps.REopt]] deps = ["ArchGDAL", "CSV", "CoolProp", "DataFrames", "Dates", "DelimitedFiles", "HTTP", "JLD", "JSON", "JuMP", "LinDistFlow", "LinearAlgebra", "Logging", "MathOptInterface", "Requires", "Roots", "Statistics", "TestEnv"] -git-tree-sha1 = "103761fa0f7447377726347af656cde6ab1160cc" +git-tree-sha1 = "b08ad59633ffcda75f037e377775a95822e6694b" +repo-rev = "peak-scaling" +repo-url = "https://github.com/NREL/REopt.jl.git" uuid = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6" -version = "0.55.1" +version = "0.55.0" [[deps.Random]] deps = ["SHA"] diff --git a/reoptjl/models.py b/reoptjl/models.py index d535fe252..822df77b3 100644 --- a/reoptjl/models.py +++ b/reoptjl/models.py @@ -1387,8 +1387,22 @@ class ElectricLoadInputs(BaseModel, models.Model): blank=True ), default=list, blank=True, - help_text=("Monthly site energy consumption from electricity series (an array 12 entries long), in kWh, used " - "to scale simulated default building load profile for the site's climate zone") + help_text=("Monthly site energy consumption (an array 12 entries long), in kWh, used to scale either loads_kw series " + "(with normalize_and_scale_load_profile_input) or the simulated default building load profile for the site's climate zone") + ) + monthly_peaks_kw = ArrayField( + models.FloatField( + validators=[ + MinValueValidator(0), + MaxValueValidator(1.0e8) + ], + blank=True + ), + default=list, blank=True, + help_text=("Monthly peak power consumption (an array 12 entries long), in kW, used to scale either loads_kw series " + "(with normalize_and_scale_load_profile_input) or the simulated default building load profile for the site's climate zone." + "Monthly energy is maintained while scaling to the monthly peaks." + ) ) loads_kw = ArrayField( models.FloatField(blank=True), @@ -1401,7 +1415,7 @@ class ElectricLoadInputs(BaseModel, models.Model): normalize_and_scale_load_profile_input = models.BooleanField( blank=True, default=False, - help_text=("Takes the input loads_kw and normalizes and scales it to annual or monthly energy inputs.") + help_text=("Takes the input loads_kw and normalizes and scales it to the inputs annual_kwh, monthly_totals_kwh, and/or monthly_peaks_kw.") ) critical_loads_kw = ArrayField( models.FloatField(blank=True), diff --git a/reoptjl/views.py b/reoptjl/views.py index 38556999b..d0e02304e 100644 --- a/reoptjl/views.py +++ b/reoptjl/views.py @@ -582,7 +582,7 @@ def simulated_load(request): # Required for GET - will throw a Missing Error if not included if request.method == "GET": valid_keys = ["doe_reference_name","industrial_reference_name","latitude","longitude","load_type","percent_share","annual_kwh", - "monthly_totals_kwh","annual_mmbtu","annual_fraction","annual_tonhour","monthly_tonhour", + "monthly_totals_kwh","monthly_peaks_kw","annual_mmbtu","annual_fraction","annual_tonhour","monthly_tonhour", "monthly_mmbtu","monthly_fraction","max_thermal_factor_on_peak_load","chiller_cop", "addressable_load_fraction", "cooling_doe_ref_name", "cooling_pct_share", "boiler_efficiency", "normalize_and_scale_load_profile_input", "year"] @@ -658,7 +658,7 @@ def simulated_load(request): # TODO make year optional? inputs[field] = data[field] if inputs["load_type"] == "electric": - for energy in ["annual_kwh", "monthly_totals_kwh"]: + for energy in ["annual_kwh", "monthly_totals_kwh", "monthly_peaks_kw"]: if data.get(energy) is not None: inputs[energy] = data.get(energy) elif inputs["load_type"] in ["space_heating", "domestic_hot_water", "process_heat"]: From b77bdcbf75a6be8de5e4507189a4f03a4a4e71f4 Mon Sep 17 00:00:00 2001 From: adfarth Date: Thu, 23 Oct 2025 14:19:41 -0700 Subject: [PATCH 18/63] add to test posts --- reoptjl/test/posts/all_inputs_test.json | 1 + reoptjl/test/posts/validator_post.json | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/reoptjl/test/posts/all_inputs_test.json b/reoptjl/test/posts/all_inputs_test.json index 8381b01b5..a2355ef95 100644 --- a/reoptjl/test/posts/all_inputs_test.json +++ b/reoptjl/test/posts/all_inputs_test.json @@ -35,6 +35,7 @@ "doe_reference_name": "MidriseApartment", "year": 2017, "monthly_totals_kwh": [], + "monthly_peaks_kw": [], "loads_kw": [], "critical_loads_kw": [], "loads_kw_is_net": true, diff --git a/reoptjl/test/posts/validator_post.json b/reoptjl/test/posts/validator_post.json index d4c8ce524..05f4e78ab 100644 --- a/reoptjl/test/posts/validator_post.json +++ b/reoptjl/test/posts/validator_post.json @@ -44,6 +44,20 @@ 1200, 1200 ], + "monthly_peaks_kw": [ + 1.5, + 1.5, + 1.5, + 1.5, + 1.5, + 1.5, + 1.5, + 1.5, + 1.5, + 1.5, + 1.5, + 1.5 + ], "outage_start_time_step": 11, "outage_end_time_step": 501, "critical_load_fraction": 0.5, From 12a359c648871fef866cc4e0342e8bd871299ccf Mon Sep 17 00:00:00 2001 From: adfarth Date: Thu, 23 Oct 2025 14:21:25 -0700 Subject: [PATCH 19/63] Create 0110_electricloadinputs_monthly_peaks_kw_and_more.py --- ...ricloadinputs_monthly_peaks_kw_and_more.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 reoptjl/migrations/0110_electricloadinputs_monthly_peaks_kw_and_more.py diff --git a/reoptjl/migrations/0110_electricloadinputs_monthly_peaks_kw_and_more.py b/reoptjl/migrations/0110_electricloadinputs_monthly_peaks_kw_and_more.py new file mode 100644 index 000000000..3ad7ed1b0 --- /dev/null +++ b/reoptjl/migrations/0110_electricloadinputs_monthly_peaks_kw_and_more.py @@ -0,0 +1,30 @@ +# Generated by Django 4.2.24 on 2025-10-23 21:21 + +import django.contrib.postgres.fields +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('reoptjl', '0109_remove_ghpoutputs_iterations_auto_guess_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='electricloadinputs', + name='monthly_peaks_kw', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100000000.0)]), blank=True, default=list, help_text="Monthly peak power consumption (an array 12 entries long), in kW, used to scale either loads_kw series (with normalize_and_scale_load_profile_input) or the simulated default building load profile for the site's climate zone.Monthly energy is maintained while scaling to the monthly peaks.", size=None), + ), + migrations.AlterField( + model_name='electricloadinputs', + name='monthly_totals_kwh', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100000000.0)]), blank=True, default=list, help_text="Monthly site energy consumption (an array 12 entries long), in kWh, used to scale either loads_kw series (with normalize_and_scale_load_profile_input) or the simulated default building load profile for the site's climate zone", size=None), + ), + migrations.AlterField( + model_name='electricloadinputs', + name='normalize_and_scale_load_profile_input', + field=models.BooleanField(blank=True, default=False, help_text='Takes the input loads_kw and normalizes and scales it to the inputs annual_kwh, monthly_totals_kwh, and/or monthly_peaks_kw.'), + ), + ] From 5a42adde59aa9c55c8da98b9f4fa7d88b6dcf03d Mon Sep 17 00:00:00 2001 From: adfarth Date: Thu, 23 Oct 2025 14:28:27 -0700 Subject: [PATCH 20/63] Update http.jl --- julia_src/http.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/julia_src/http.jl b/julia_src/http.jl index 44aa77e86..7d6914165 100644 --- a/julia_src/http.jl +++ b/julia_src/http.jl @@ -544,7 +544,7 @@ function simulated_load(req::HTTP.Request) end # Convert vectors which come in as Vector{Any} to Vector{Float} (within Vector{<:Real}) - vector_types = ["percent_share", "cooling_pct_share", "monthly_totals_kwh", "monthly_mmbtu", + vector_types = ["percent_share", "cooling_pct_share", "monthly_totals_kwh", "monthly_peaks_kw", "monthly_mmbtu", "monthly_tonhour", "monthly_fraction", "addressable_load_fraction", "load_profile"] for key in vector_types if key in keys(d) && typeof(d[key]) <: Vector{} @@ -555,7 +555,7 @@ function simulated_load(req::HTTP.Request) end end - @info "Getting CRB Loads..." # TODO: update this b/c it could be for custom loads too? + @info "Getting Loads..." data = Dict() error_response = Dict() try From 6cadf5b798c41dfcd7cf615f2837707eae434c8f Mon Sep 17 00:00:00 2001 From: bill-becker Date: Thu, 23 Oct 2025 16:35:33 -0600 Subject: [PATCH 21/63] Make output.ElectricLoad.monthly_peak_kw consistent with upcoming new input monthly_peaks_kw --- ..._kw_electricloadoutputs_monthly_peaks_kw.py | 18 ++++++++++++++++++ reoptjl/models.py | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 reoptjl/migrations/0113_rename_monthly_peak_kw_electricloadoutputs_monthly_peaks_kw.py diff --git a/reoptjl/migrations/0113_rename_monthly_peak_kw_electricloadoutputs_monthly_peaks_kw.py b/reoptjl/migrations/0113_rename_monthly_peak_kw_electricloadoutputs_monthly_peaks_kw.py new file mode 100644 index 000000000..625c6323c --- /dev/null +++ b/reoptjl/migrations/0113_rename_monthly_peak_kw_electricloadoutputs_monthly_peaks_kw.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.25 on 2025-10-23 22:33 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('reoptjl', '0112_rename_nem_electric_to_grid_series_kw_electrictariffoutputs_net_metering_electric_to_grid_series_kw_'), + ] + + operations = [ + migrations.RenameField( + model_name='electricloadoutputs', + old_name='monthly_peak_kw', + new_name='monthly_peaks_kw', + ), + ] diff --git a/reoptjl/models.py b/reoptjl/models.py index 59786030a..b46c33482 100644 --- a/reoptjl/models.py +++ b/reoptjl/models.py @@ -1553,7 +1553,7 @@ class ElectricLoadOutputs(BaseModel, models.Model): default=list, help_text="Monthly energy consumption calculated by summing up load_series_kw. Does not include electric load for any new heating or cooling techs." ) - monthly_peak_kw = ArrayField( + monthly_peaks_kw = ArrayField( models.FloatField( null=True, blank=True ), From 08f8e1e12fcfee902bf7d7d2ac93f65bc4da5231 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Thu, 23 Oct 2025 19:01:49 -0600 Subject: [PATCH 22/63] Update REopt#elec_util_usage for updated monthly_peaks_kw name --- julia_src/Manifest.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/julia_src/Manifest.toml b/julia_src/Manifest.toml index a9dd913a1..16e510202 100644 --- a/julia_src/Manifest.toml +++ b/julia_src/Manifest.toml @@ -948,7 +948,7 @@ version = "1.11.0" [[deps.REopt]] deps = ["ArchGDAL", "CSV", "CoolProp", "DataFrames", "Dates", "DelimitedFiles", "HTTP", "JLD", "JSON", "JuMP", "LinDistFlow", "LinearAlgebra", "Logging", "MathOptInterface", "Requires", "Roots", "Statistics", "TestEnv"] -git-tree-sha1 = "0a5d1d2a086cd400d9b0e4758b34da51a07d6dbd" +git-tree-sha1 = "277df4545cd30a5fc227bf982897ef65d5ea3520" repo-rev = "elec_util_usage" repo-url = "https://github.com/NREL/REopt.jl.git" uuid = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6" From 6611829dbf77b7064efb89a0141561597ec1d550 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Fri, 24 Oct 2025 06:34:23 -0600 Subject: [PATCH 23/63] Updates to anccr tables config and helpers --- reoptjl/custom_table_config.py | 268 ++++++++++++++++---------------- reoptjl/custom_table_helpers.py | 30 +++- reoptjl/views.py | 22 +-- 3 files changed, 174 insertions(+), 146 deletions(-) diff --git a/reoptjl/custom_table_config.py b/reoptjl/custom_table_config.py index 849501740..de50c9660 100644 --- a/reoptjl/custom_table_config.py +++ b/reoptjl/custom_table_config.py @@ -1331,7 +1331,7 @@ "label": "Utility Name", # Not REopt result; Name based on User input "key": "utility_name", "bau_value": lambda df: "", - "scenario_value": lambda df: "" + "scenario_value": lambda df: safe_get(df, "inputs.ElectricTariff.urdb_metadata.utility") }, ##################################################################################################### @@ -1359,26 +1359,26 @@ { "label": "Year 1 Fixed Charges ($)", "key": "year_1_fixed_charges", - "bau_value": lambda df: safe_get(df, "ElectricTariff.year_one_fixed_cost_before_tax_bau"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.year_one_fixed_cost_before_tax") }, { "label": "Year 1 Energy Charges ($)", "key": "year_1_energy_charges", - "bau_value": lambda df: safe_get(df, "ElectricTariff.year_one_energy_cost_before_tax_bau"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.year_one_energy_cost_before_tax") }, { "label": "Year 1 Demand Charges ($)", "key": "year_1_demand_charges", - "bau_value": lambda df: safe_get(df, "ElectricTariff.year_one_demand_cost_before_tax_bau"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.year_one_demand_cost_before_tax") }, { "label": "Year 1 Total Bill Charges ($)", "key": "year_1_total_bill_charges", - "bau_value": lambda df: safe_get(df, "ElectricTariff.year_one_bill_before_tax_bau"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.year_one_bill_before_tax") }, { "label": "Change in Year 1 Charges ($)", # this value will need to be calculated compared to the current rate @@ -1398,10 +1398,10 @@ "scenario_value": lambda df: "" }, { - "label": "Year 1 Fixed Charges ($)", + "label": "Year 1 Annual Fixed Charges ($)", "key": "year_1_fixed_charges", - "bau_value": lambda df: safe_get(df, "ElectricTariff.year_one_fixed_cost_before_tax_bau"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.year_one_fixed_cost_before_tax") }, { "label": "Year 1 Fixed Charges Percent Change (%)", # this value will need to be calculated compared to the current rate @@ -1410,10 +1410,10 @@ "scenario_value": lambda df: "" }, { - "label": "Year 1 Energy Charges ($)", + "label": "Year 1 Annual Energy Charges ($)", "key": "year_1_energy_charges", - "bau_value": lambda df: safe_get(df, "ElectricTariff.year_one_energy_cost_before_tax_bau"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.year_one_energy_cost_before_tax") }, { "label": "Year 1 Energy Charges Percent Change (%)", # this value will need to be calculated compared to the current rate @@ -1422,10 +1422,10 @@ "scenario_value": lambda df: "" }, { - "label": "Year 1 Demand Charges ($)", + "label": "Year 1 Annual Demand Charges ($)", "key": "year_1_demand_charges", - "bau_value": lambda df: safe_get(df, "ElectricTariff.year_one_demand_cost_before_tax_bau"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.year_one_demand_cost_before_tax") }, { "label": "Year 1 Demand Charges Percent Change (%)", # this value will need to be calculated compared to the current rate @@ -1434,10 +1434,10 @@ "scenario_value": lambda df: "" }, { - "label": "Year 1 Total Bill Charges ($)", + "label": "Year 1 Annual Total Bill Charges ($)", "key": "year_1_total_bill_charges", - "bau_value": lambda df: safe_get(df, "ElectricTariff.year_one_bill_before_tax_bau"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.year_one_bill_before_tax") }, { "label": "Year 1 Total Bill Charges Percent Change (%)", # this value will need to be calculated compared to the current rate @@ -1488,14 +1488,14 @@ { "label": "Annual Grid Purchases (kWh)", "key": "annual_grid_purchases_kwh", - "bau_value": lambda df: safe_get(df, "ElectricLoad.annual_calculated_kwh"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricLoad.annual_calculated_kwh") }, { "label": "Year 1 Peak Load (kW)", "key": "year_1_peak_load_kw", - "bau_value": lambda df: safe_get(df, "ElectricLoad.annual_peak_kw"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricLoad.annual_peak_kw") }, ##################################################################################################### @@ -1507,65 +1507,71 @@ "bau_value": lambda df: "", "scenario_value": lambda df: "" }, + { + "label": "URDB Rate Name", # will need to pull from URDB + "key": "urdb_rate_name", + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "inputs.ElectricTariff.urdb_metadata.rate_name") + }, { "label": "URDB Label", # will need to pull from URDB "key": "urdb_label", "bau_value": lambda df: "", - "scenario_value": lambda df: "" + "scenario_value": lambda df: safe_get(df, "inputs.ElectricTariff.urdb_metadata.label") }, { "label": "Rate Effective Date (latest_update)", # will need to pull from URDB "key": "rate_effective_date", "bau_value": lambda df: "", - "scenario_value": lambda df: "" + "scenario_value": lambda df: safe_get(df, "inputs.ElectricTariff.urdb_metadata.rate_effective_date") }, { "label": "Voltage Level (voltagecategory)", # will need to pull from URDB "key": "voltage_level_urdb", "bau_value": lambda df: "", - "scenario_value": lambda df: "" + "scenario_value": lambda df: safe_get(df, "inputs.ElectricTariff.urdb_metadata.voltage_level") }, { "label": "Peak kW Capacity Min (peakkwcapacitymin)", # will need to pull from URDB "key": "peak_kw_capacity_min", "bau_value": lambda df: "", - "scenario_value": lambda df: "" + "scenario_value": lambda df: safe_get(df, "inputs.ElectricTariff.urdb_metadata.peak_kw_capacity_min") }, { "label": "Peak kW Capacity Max (peakkwcapacitymax)", # will need to pull from URDB "key": "peak_kw_capacity_max", "bau_value": lambda df: "", - "scenario_value": lambda df: "" + "scenario_value": lambda df: safe_get(df, "inputs.ElectricTariff.urdb_metadata.peak_kw_capacity_max") }, { "label": "Rate Description (description)", # will need to pull from URDB "key": "rate_description", "bau_value": lambda df: "", - "scenario_value": lambda df: "" + "scenario_value": lambda df: safe_get(df, "inputs.ElectricTariff.urdb_metadata.rate_description") }, { "label": "Additional Information (basicinformationcomments)", # will need to pull from URDB "key": "additional_information", "bau_value": lambda df: "", - "scenario_value": lambda df: "" + "scenario_value": lambda df: safe_get(df, "inputs.ElectricTariff.urdb_metadata.rate_additional_info") }, { "label": "Energy Comments (energycomments)", # will need to pull from URDB "key": "energy_comments", "bau_value": lambda df: "", - "scenario_value": lambda df: "" + "scenario_value": lambda df: safe_get(df, "inputs.ElectricTariff.urdb_metadata.energy_comments") }, { "label": "Demand Comments (demandcomments)", # will need to pull from URDB "key": "demand_comments", "bau_value": lambda df: "", - "scenario_value": lambda df: "" + "scenario_value": lambda df: safe_get(df, "inputs.ElectricTariff.urdb_metadata.demand_comments") }, { "label": "URDB Link", # will need to pull from URDB "key": "urdb_link", "bau_value": lambda df: "", - "scenario_value": lambda df: "" + "scenario_value": lambda df: safe_get(df, "inputs.ElectricTariff.urdb_metadata.url_link") }, ##################################################################################################### @@ -1581,74 +1587,74 @@ { "label": "January Energy Cost ($)", "key": "january_energy_cost", - "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.0"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.0") # January }, { "label": "February Energy Cost ($)", "key": "february_energy_cost", - "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.1"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.1") }, { "label": "March Energy Cost ($)", "key": "march_energy_cost", - "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.2"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.2") }, { "label": "April Energy Cost ($)", "key": "april_energy_cost", - "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.3"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.3") }, { "label": "May Energy Cost ($)", "key": "may_energy_cost", - "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.4"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.4") }, { "label": "June Energy Cost ($)", "key": "june_energy_cost", - "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.5"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.5") }, { "label": "July Energy Cost ($)", "key": "july_energy_cost", - "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.6"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.6") }, { "label": "August Energy Cost ($)", "key": "august_energy_cost", - "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.7"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.7") }, { "label": "September Energy Cost ($)", "key": "september_energy_cost", - "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.8"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.8") }, { "label": "October Energy Cost ($)", "key": "october_energy_cost", - "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.9"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.9") }, { "label": "November Energy Cost ($)", "key": "november_energy_cost", - "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.10"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.10") }, { "label": "December Energy Cost ($)", "key": "december_energy_cost", - "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.11"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.11") }, ##################################################################################################### @@ -1664,74 +1670,74 @@ { "label": "January Demand Cost ($)", "key": "january_demand_cost", - "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.0") + safe_get(df, "ElectricTariff.monthly_facility_demand_cost_series_before_tax.0"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.0") + safe_get(df, "outputs.ElectricTariff.monthly_facility_demand_cost_series_before_tax.0") }, { "label": "February Demand Cost ($)", "key": "february_demand_cost", - "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.1") + safe_get(df, "ElectricTariff.monthly_facility_demand_cost_series_before_tax.1"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.1") + safe_get(df, "outputs.ElectricTariff.monthly_facility_demand_cost_series_before_tax.1") }, { "label": "March Demand Cost ($)", "key": "march_demand_cost", - "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.2") + safe_get(df, "ElectricTariff.monthly_facility_demand_cost_series_before_tax.2"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.2") + safe_get(df, "outputs.ElectricTariff.monthly_facility_demand_cost_series_before_tax.2") }, { "label": "April Demand Cost ($)", "key": "april_demand_cost", - "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.3") + safe_get(df, "ElectricTariff.monthly_facility_demand_cost_series_before_tax.3"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.3") + safe_get(df, "outputs.ElectricTariff.monthly_facility_demand_cost_series_before_tax.3") }, { "label": "May Demand Cost ($)", "key": "may_demand_cost", - "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.4") + safe_get(df, "ElectricTariff.monthly_facility_demand_cost_series_before_tax.4"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.4") + safe_get(df, "outputs.ElectricTariff.monthly_facility_demand_cost_series_before_tax.4") }, { "label": "June Demand Cost ($)", "key": "june_demand_cost", - "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.5") + safe_get(df, "ElectricTariff.monthly_facility_demand_cost_series_before_tax.5"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.5") + safe_get(df, "outputs.ElectricTariff.monthly_facility_demand_cost_series_before_tax.5") }, { "label": "July Demand Cost ($)", "key": "july_demand_cost", - "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.6") + safe_get(df, "ElectricTariff.monthly_facility_demand_cost_series_before_tax.6"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.6") + safe_get(df, "outputs.ElectricTariff.monthly_facility_demand_cost_series_before_tax.6") }, { "label": "August Demand Cost ($)", "key": "august_demand_cost", - "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.7") + safe_get(df, "ElectricTariff.monthly_facility_demand_cost_series_before_tax.7"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.7") + safe_get(df, "outputs.ElectricTariff.monthly_facility_demand_cost_series_before_tax.7") }, { "label": "September Demand Cost ($)", "key": "september_demand_cost", - "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.8") + safe_get(df, "ElectricTariff.monthly_facility_demand_cost_series_before_tax.8"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.8") + safe_get(df, "outputs.ElectricTariff.monthly_facility_demand_cost_series_before_tax.8") }, { "label": "October Demand Cost ($)", "key": "october_demand_cost", - "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.9") + safe_get(df, "ElectricTariff.monthly_facility_demand_cost_series_before_tax.9"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.9") + safe_get(df, "outputs.ElectricTariff.monthly_facility_demand_cost_series_before_tax.9") }, { "label": "November Demand Cost ($)", "key": "november_demand_cost", - "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.10") + safe_get(df, "ElectricTariff.monthly_facility_demand_cost_series_before_tax.10"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.10") + safe_get(df, "outputs.ElectricTariff.monthly_facility_demand_cost_series_before_tax.10") }, { "label": "December Demand Cost ($)", "key": "december_demand_cost", - "bau_value": lambda df: safe_get(df, "ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.11") + safe_get(df, "ElectricTariff.monthly_facility_demand_cost_series_before_tax.11"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.11") + safe_get(df, "outputs.ElectricTariff.monthly_facility_demand_cost_series_before_tax.11") }, ##################################################################################################### @@ -1830,74 +1836,74 @@ { "label": "January Energy Consumption (kWh)", "key": "january_energy_consumption_kwh", - "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_calculated_kwh.0"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricLoad.monthly_calculated_kwh.0") }, { "label": "February Energy Consumption (kWh)", "key": "february_energy_consumption_kwh", - "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_calculated_kwh.1"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricLoad.monthly_calculated_kwh.1") }, { "label": "March Energy Consumption (kWh)", "key": "march_energy_consumption_kwh", - "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_calculated_kwh.2"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricLoad.monthly_calculated_kwh.2") }, { "label": "April Energy Consumption (kWh)", "key": "april_energy_consumption_kwh", - "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_calculated_kwh.3"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricLoad.monthly_calculated_kwh.3") }, { "label": "May Energy Consumption (kWh)", "key": "may_energy_consumption_kwh", - "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_calculated_kwh.4"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricLoad.monthly_calculated_kwh.4") }, { "label": "June Energy Consumption (kWh)", "key": "june_energy_consumption_kwh", - "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_calculated_kwh.5"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricLoad.monthly_calculated_kwh.5") }, { "label": "July Energy Consumption (kWh)", "key": "july_energy_consumption_kwh", - "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_calculated_kwh.6"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricLoad.monthly_calculated_kwh.6") }, { "label": "August Energy Consumption (kWh)", "key": "august_energy_consumption_kwh", - "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_calculated_kwh.7"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricLoad.monthly_calculated_kwh.7") }, { "label": "September Energy Consumption (kWh)", "key": "september_energy_consumption_kwh", - "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_calculated_kwh.8"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricLoad.monthly_calculated_kwh.8") }, { "label": "October Energy Consumption (kWh)", "key": "october_energy_consumption_kwh", - "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_calculated_kwh.9"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricLoad.monthly_calculated_kwh.9") }, { "label": "November Energy Consumption (kWh)", "key": "november_energy_consumption_kwh", - "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_calculated_kwh.10"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricLoad.monthly_calculated_kwh.10") }, { "label": "December Energy Consumption (kWh)", "key": "december_energy_consumption_kwh", - "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_calculated_kwh.11"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricLoad.monthly_calculated_kwh.11") }, ##################################################################################################### @@ -1913,73 +1919,73 @@ { "label": "January Peak Load (kW)", "key": "january_peak_load_kw", - "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_peak_kw.0"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricLoad.monthly_peaks_kw.0") }, { "label": "February Peak Load (kW)", "key": "february_peak_load_kw", - "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_peak_kw.1"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricLoad.monthly_peaks_kw.1") }, { "label": "March Peak Load (kW)", "key": "march_peak_load_kw", - "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_peak_kw.2"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricLoad.monthly_peaks_kw.2") }, { "label": "April Peak Load (kW)", "key": "april_peak_load_kw", - "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_peak_kw.3"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricLoad.monthly_peaks_kw.3") }, { "label": "May Peak Load (kW)", "key": "may_peak_load_kw", - "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_peak_kw.4"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricLoad.monthly_peaks_kw.4") }, { "label": "June Peak Load (kW)", "key": "june_peak_load_kw", - "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_peak_kw.5"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricLoad.monthly_peaks_kw.5") }, { "label": "July Peak Load (kW)", "key": "july_peak_load_kw", - "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_peak_kw.6"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricLoad.monthly_peaks_kw.6") }, { "label": "August Peak Load (kW)", "key": "august_peak_load_kw", - "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_peak_kw.7"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricLoad.monthly_peaks_kw.7") }, { "label": "September Peak Load (kW)", "key": "september_peak_load_kw", - "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_peak_kw.8"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricLoad.monthly_peaks_kw.8") }, { "label": "October Peak Load (kW)", "key": "october_peak_load_kw", - "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_peak_kw.9"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricLoad.monthly_peaks_kw.9") }, { "label": "November Peak Load (kW)", "key": "november_peak_load_kw", - "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_peak_kw.10"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricLoad.monthly_peaks_kw.10") }, { "label": "December Peak Load (kW)", "key": "december_peak_load_kw", - "bau_value": lambda df: safe_get(df, "ElectricLoad.monthly_peak_kw.11"), - "scenario_value": lambda df: "" + "bau_value": lambda df: "", + "scenario_value": lambda df: safe_get(df, "outputs.ElectricLoad.monthly_peaks_kw.11") } ] diff --git a/reoptjl/custom_table_helpers.py b/reoptjl/custom_table_helpers.py index 5e56a9ca9..68286fd3d 100644 --- a/reoptjl/custom_table_helpers.py +++ b/reoptjl/custom_table_helpers.py @@ -2,12 +2,22 @@ from typing import Dict, Any, List, Union def flatten_dict(d: Dict[str, Any], parent_key: str = '', sep: str = '.') -> Dict[str, Any]: - """Flatten nested dictionary.""" + """Flatten nested dictionary and handle arrays by creating indexed keys.""" items = [] for k, v in d.items(): new_key = f"{parent_key}{sep}{k}" if parent_key else k if isinstance(v, dict): items.extend(flatten_dict(v, new_key, sep=sep).items()) + elif isinstance(v, list): + # Handle arrays by creating indexed keys (e.g., key.0, key.1, key.2, ...) + for i, item in enumerate(v): + indexed_key = f"{new_key}{sep}{i}" + if isinstance(item, dict): + # If array item is a dict, flatten it further + items.extend(flatten_dict(item, indexed_key, sep=sep).items()) + else: + # If array item is a scalar, add it directly + items.append((indexed_key, item)) else: items.append((new_key, v)) return dict(items) @@ -20,12 +30,19 @@ def clean_data_dict(data_dict: Dict[str, List[Any]]) -> Dict[str, List[Any]]: for key, value_array in data_dict.items() } -def sum_vectors(data: Union[Dict[str, Any], List[Any]]) -> Union[Dict[str, Any], List[Any], Any]: - """Sum numerical vectors within a nested data structure.""" +def sum_vectors(data: Union[Dict[str, Any], List[Any]], preserve_monthly: bool = True) -> Union[Dict[str, Any], List[Any], Any]: + """Sum numerical vectors within a nested data structure, but preserve monthly arrays.""" if isinstance(data, dict): - return {key: sum_vectors(value) for key, value in data.items()} + result = {} + for key, value in data.items(): + # Preserve monthly series arrays - don't sum them + if preserve_monthly and 'monthly_' in key and isinstance(value, list): + result[key] = value # Keep monthly data as arrays + else: + result[key] = sum_vectors(value, preserve_monthly) + return result elif isinstance(data, list): - return sum(data) if all(isinstance(item, (int, float)) for item in data) else [sum_vectors(item) for item in data] + return sum(data) if all(isinstance(item, (int, float)) for item in data) else [sum_vectors(item, preserve_monthly) for item in data] else: return data @@ -40,4 +57,5 @@ def colnum_string(n: int) -> str: def safe_get(df: Dict[str, Any], key: str, default: Any = 0) -> Any: """Safely get a value from a dictionary with a default fallback.""" value = df.get(key, default if default is not None else 0) - return value if value is not None else 0 \ No newline at end of file + return value if value is not None else 0 + \ No newline at end of file diff --git a/reoptjl/views.py b/reoptjl/views.py index 38556999b..096d7156f 100644 --- a/reoptjl/views.py +++ b/reoptjl/views.py @@ -1747,17 +1747,17 @@ def summarize_vector_data(request: Any, run_uuid: str) -> Dict[str, Any]: try: response = results(request, run_uuid) if response.status_code == 200: - return sum_vectors(json.loads(response.content)) + return sum_vectors(json.loads(response.content), preserve_monthly=True) return {"error": f"Failed to fetch data for run_uuid {run_uuid}"} except Exception: log_and_raise_error('summarize_vector_data') -def generate_data_dict(config: List[Dict[str, Any]], df_gen: Dict[str, Any]) -> Dict[str, List[Any]]: +def generate_data_dict(config: List[Dict[str, Any]], df_gen: Dict[str, Any]) -> Dict[str, Any]: try: - data_dict = defaultdict(list) + data_dict = {} for entry in config: val = entry["scenario_value"](df_gen) - data_dict[entry["label"]].append(val) + data_dict[entry["label"]] = val return data_dict except Exception: log_and_raise_error('generate_data_dict') @@ -1767,9 +1767,11 @@ def generate_reopt_dataframe(data_f: Dict[str, Any], scenario_name: str, config: scenario_name_str = str(scenario_name) df_gen = flatten_dict(data_f) data_dict = generate_data_dict(config, df_gen) - data_dict["Scenario"] = [scenario_name_str] + data_dict["Scenario"] = scenario_name_str col_order = ["Scenario"] + [entry["label"] for entry in config] - return pd.DataFrame(data_dict)[col_order] + # Convert to single-row DataFrame by wrapping each value in a list + df_data = {key: [value] for key, value in data_dict.items()} + return pd.DataFrame(df_data)[col_order] except Exception: log_and_raise_error('generate_reopt_dataframe') @@ -1800,7 +1802,7 @@ def get_bau_values(scenarios: List[Dict[str, Any]], config: List[Dict[str, Any]] def process_scenarios(scenarios: List[Dict[str, Any]], reopt_data_config: List[Dict[str, Any]]) -> pd.DataFrame: try: bau_values_per_scenario = get_bau_values(scenarios, reopt_data_config) - combined_df = pd.DataFrame() + all_dataframes = [] for idx, scenario in enumerate(scenarios): run_uuid = scenario['run_uuid'] @@ -1811,9 +1813,11 @@ def process_scenarios(scenarios: List[Dict[str, Any]], reopt_data_config: List[D bau_data["Scenario"] = [f"BAU {idx + 1}"] df_bau = pd.DataFrame(bau_data) - combined_df = pd.concat([combined_df, df_bau, df_result], axis=0) if not combined_df.empty else pd.concat([df_bau, df_result], axis=0) + # Add both BAU and result dataframes to list + all_dataframes.extend([df_bau, df_result]) - combined_df.reset_index(drop=True, inplace=True) + # Concatenate all dataframes at once + combined_df = pd.concat(all_dataframes, axis=0, ignore_index=True) combined_df = pd.DataFrame(clean_data_dict(combined_df.to_dict(orient="list"))) return combined_df[["Scenario"] + [col for col in combined_df.columns if col != "Scenario"]] except Exception: From df4f35820db2f5fdd772492e2c5a87f7c16a187c Mon Sep 17 00:00:00 2001 From: bill-becker Date: Fri, 24 Oct 2025 09:22:20 -0600 Subject: [PATCH 24/63] Squash migrations for new ElectricTariff Inputs/Outputs --- ...ffoutputs_urdb_demand_comments_and_more.py | 73 ---------------- ...ricloadoutputs_annual_peak_kw_and_more.py} | 83 ++++++++++--------- .../migrations/0110_merge_20251022_1752.py | 14 ---- ...ffoutputs_urdb_demand_comments_and_more.py | 66 --------------- ...et_metering_electric_to_grid_series_kw_.py | 73 ---------------- ...kw_electricloadoutputs_monthly_peaks_kw.py | 18 ---- 6 files changed, 44 insertions(+), 283 deletions(-) delete mode 100644 reoptjl/migrations/0107_electrictariffoutputs_urdb_demand_comments_and_more.py rename reoptjl/migrations/{0106_electricloadoutputs_annual_peak_kw_and_more.py => 0110_electricloadoutputs_annual_peak_kw_and_more.py} (89%) delete mode 100644 reoptjl/migrations/0110_merge_20251022_1752.py delete mode 100644 reoptjl/migrations/0111_remove_electrictariffoutputs_urdb_demand_comments_and_more.py delete mode 100644 reoptjl/migrations/0112_rename_nem_electric_to_grid_series_kw_electrictariffoutputs_net_metering_electric_to_grid_series_kw_.py delete mode 100644 reoptjl/migrations/0113_rename_monthly_peak_kw_electricloadoutputs_monthly_peaks_kw.py diff --git a/reoptjl/migrations/0107_electrictariffoutputs_urdb_demand_comments_and_more.py b/reoptjl/migrations/0107_electrictariffoutputs_urdb_demand_comments_and_more.py deleted file mode 100644 index 7dddf6eb4..000000000 --- a/reoptjl/migrations/0107_electrictariffoutputs_urdb_demand_comments_and_more.py +++ /dev/null @@ -1,73 +0,0 @@ -# Generated by Django 4.2.24 on 2025-10-16 20:43 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('reoptjl', '0106_electricloadoutputs_annual_peak_kw_and_more'), - ] - - operations = [ - migrations.AddField( - model_name='electrictariffoutputs', - name='urdb_demand_comments', - field=models.TextField(blank=True, help_text='Business as usual life cycle utility minimum charge adder, after-tax', null=True), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='urdb_energy_comments', - field=models.TextField(blank=True, help_text='Business as usual life cycle utility minimum charge adder, after-tax', null=True), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='urdb_label', - field=models.TextField(blank=True, help_text='Business as usual life cycle utility minimum charge adder, after-tax', null=True), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='urdb_peak_kw_capacity_max', - field=models.FloatField(blank=True, help_text='Business as usual life cycle utility minimum charge adder, after-tax', null=True), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='urdb_peak_kw_capacity_min', - field=models.FloatField(blank=True, help_text='Business as usual life cycle utility minimum charge adder, after-tax', null=True), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='urdb_rate_additional_info', - field=models.TextField(blank=True, help_text='Business as usual life cycle utility minimum charge adder, after-tax', null=True), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='urdb_rate_description', - field=models.TextField(blank=True, help_text='Business as usual life cycle utility minimum charge adder, after-tax', null=True), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='urdb_rate_effective_date', - field=models.TextField(blank=True, help_text='Business as usual life cycle utility minimum charge adder, after-tax', null=True), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='urdb_rate_name', - field=models.TextField(blank=True, help_text='Business as usual life cycle utility minimum charge adder, after-tax', null=True), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='urdb_url_link', - field=models.TextField(blank=True, help_text='Business as usual life cycle utility minimum charge adder, after-tax', null=True), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='urdb_utility', - field=models.TextField(blank=True, help_text='Business as usual life cycle utility minimum charge adder, after-tax', null=True), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='urdb_voltage_level', - field=models.TextField(blank=True, help_text='Business as usual life cycle utility minimum charge adder, after-tax', null=True), - ), - ] diff --git a/reoptjl/migrations/0106_electricloadoutputs_annual_peak_kw_and_more.py b/reoptjl/migrations/0110_electricloadoutputs_annual_peak_kw_and_more.py similarity index 89% rename from reoptjl/migrations/0106_electricloadoutputs_annual_peak_kw_and_more.py rename to reoptjl/migrations/0110_electricloadoutputs_annual_peak_kw_and_more.py index 8add1d9cb..55aabc085 100644 --- a/reoptjl/migrations/0106_electricloadoutputs_annual_peak_kw_and_more.py +++ b/reoptjl/migrations/0110_electricloadoutputs_annual_peak_kw_and_more.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.24 on 2025-10-06 17:06 +# Generated by Django 4.2.25 on 2025-10-24 13:01 import django.contrib.postgres.fields from django.db import migrations, models @@ -7,7 +7,7 @@ class Migration(migrations.Migration): dependencies = [ - ('reoptjl', '0105_alter_chpoutputs_initial_capital_costs_and_more'), + ('reoptjl', '0109_remove_ghpoutputs_iterations_auto_guess_and_more'), ] operations = [ @@ -23,98 +23,103 @@ class Migration(migrations.Migration): ), migrations.AddField( model_name='electricloadoutputs', - name='monthly_peak_kw', + name='monthly_peaks_kw', field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Monthly peak energy demand determined from load_series_kw. Does not include electric load for any new heating or cooling techs.', size=None), ), + migrations.AddField( + model_name='electrictariffinputs', + name='urdb_metadata', + field=models.JSONField(blank=True, help_text='Utility rate meta data from Utility Rate Database API', null=True), + ), migrations.AddField( model_name='electrictariffoutputs', - name='EXC_electric_to_grid_series_kw', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of power exports to the grid above net metering limit for each timestep.', size=None), + name='monthly_electric_to_load_energy_cost_series_before_tax', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of monthly cost of power purchased from grid to serve loads.', size=None), ), migrations.AddField( model_name='electrictariffoutputs', - name='EXC_export_rate_series', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of compensation rate in each timestep for exporting power to grid beyond net metering limit.', size=None), + name='monthly_electric_to_storage_energy_cost_series_before_tax', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of monthly cost of power purchased from grid to charge battery storage system.', size=None), ), migrations.AddField( model_name='electrictariffoutputs', - name='EXC_monthly_export_cost_benefit_before_tax', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of monthly monetary benefit from exporting power to grid above net metering limit.', size=None), + name='monthly_facility_demand_cost_series_before_tax', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of total monthly facility demand charges by month.', size=None), ), migrations.AddField( model_name='electrictariffoutputs', - name='EXC_monthly_export_series_kwh', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of monthly energy exports to grid above net metering limit.', size=None), + name='monthly_gross_tou_demand_cost_series_before_tax', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of total time of use demand charges by month.', size=None), ), migrations.AddField( model_name='electrictariffoutputs', - name='NEM_electric_to_grid_series_kw', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of power exports to the grid up to net metering limit for each timestep.', size=None), + name='monthly_tou_demand_cost_series_before_tax', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of total time of use demand charges for each month.', size=None), ), migrations.AddField( model_name='electrictariffoutputs', - name='NEM_export_rate_series', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of compensation rate in each timestep for exporting power to grid using systems sized up to net metering limit.', size=None), + name='net_metering_electric_to_grid_series_kw', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of power exports to the grid up to net metering limit for each timestep.', size=None), ), migrations.AddField( model_name='electrictariffoutputs', - name='NEM_monthly_export_cost_benefit_before_tax', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Year one cost of electricity consumed in each month.', size=None), + name='net_metering_excess_electric_to_grid_series_kw', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of power exports to the grid above net metering limit for each timestep.', size=None), ), migrations.AddField( model_name='electrictariffoutputs', - name='NEM_monthly_export_series_kwh', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Year one cost of electricity consumed in each month.', size=None), + name='net_metering_excess_export_rate_series', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of compensation rate in each timestep for exporting power to grid beyond net metering limit.', size=None), ), migrations.AddField( model_name='electrictariffoutputs', - name='WHL_electric_to_grid_series_kw', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of power exports to the grid for wholesale in each timestep.', size=None), + name='net_metering_excess_monthly_export_cost_benefit_before_tax', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of monthly monetary benefit from exporting power to grid above net metering limit.', size=None), ), migrations.AddField( model_name='electrictariffoutputs', - name='WHL_export_rate_series', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of compensation rate in each timestep for exporting power to grid for wholesale.', size=None), + name='net_metering_excess_monthly_export_series_kwh', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of monthly energy exports to grid above net metering limit.', size=None), ), migrations.AddField( model_name='electrictariffoutputs', - name='WHL_monthly_export_cost_benefit_before_tax', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of monthly monetary benefit from exporting power to grid at wholesale rate.', size=None), + name='net_metering_export_rate_series', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of compensation rate in each timestep for exporting power to grid using systems sized up to net metering limit.', size=None), ), migrations.AddField( model_name='electrictariffoutputs', - name='WHL_monthly_export_series_kwh', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of monthly energy exports to grid under wholesale benefit.', size=None), + name='net_metering_monthly_export_cost_benefit_before_tax', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Year one cost of electricity consumed in each month.', size=None), ), migrations.AddField( model_name='electrictariffoutputs', - name='monthly_electric_to_load_energy_cost_series_before_tax', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of monthly cost of power purchased from grid to serve loads.', size=None), + name='net_metering_monthly_export_series_kwh', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Year one cost of electricity consumed in each month.', size=None), ), migrations.AddField( model_name='electrictariffoutputs', - name='monthly_electric_to_storage_energy_cost_series_before_tax', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of monthly cost of power purchased from grid to charge battery storage system.', size=None), + name='tou_demand_metrics', + field=models.JSONField(blank=True, null=True), ), migrations.AddField( model_name='electrictariffoutputs', - name='monthly_facility_demand_cost_series_before_tax', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of total monthly facility demand charges by month.', size=None), + name='wholesale_electric_to_grid_series_kw', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of power exports to the grid for wholesale in each timestep.', size=None), ), migrations.AddField( model_name='electrictariffoutputs', - name='monthly_gross_tou_demand_cost_series_before_tax', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of total time of use demand charges by month.', size=None), + name='wholesale_export_rate_series', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of compensation rate in each timestep for exporting power to grid for wholesale.', size=None), ), migrations.AddField( model_name='electrictariffoutputs', - name='monthly_tou_demand_cost_series_before_tax', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of total time of use demand charges for each month.', size=None), + name='wholesale_monthly_export_cost_benefit_before_tax', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of monthly monetary benefit from exporting power to grid at wholesale rate.', size=None), ), migrations.AddField( model_name='electrictariffoutputs', - name='tou_demand_metrics', - field=models.JSONField(blank=True, null=True), + name='wholesale_monthly_export_series_kwh', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of monthly energy exports to grid under wholesale benefit.', size=None), ), migrations.AddField( model_name='electrictariffoutputs', diff --git a/reoptjl/migrations/0110_merge_20251022_1752.py b/reoptjl/migrations/0110_merge_20251022_1752.py deleted file mode 100644 index cb76a4e89..000000000 --- a/reoptjl/migrations/0110_merge_20251022_1752.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 4.2.25 on 2025-10-22 17:52 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('reoptjl', '0107_electrictariffoutputs_urdb_demand_comments_and_more'), - ('reoptjl', '0109_remove_ghpoutputs_iterations_auto_guess_and_more'), - ] - - operations = [ - ] diff --git a/reoptjl/migrations/0111_remove_electrictariffoutputs_urdb_demand_comments_and_more.py b/reoptjl/migrations/0111_remove_electrictariffoutputs_urdb_demand_comments_and_more.py deleted file mode 100644 index f4eeb281d..000000000 --- a/reoptjl/migrations/0111_remove_electrictariffoutputs_urdb_demand_comments_and_more.py +++ /dev/null @@ -1,66 +0,0 @@ -# Generated by Django 4.2.25 on 2025-10-22 22:17 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('reoptjl', '0110_merge_20251022_1752'), - ] - - operations = [ - migrations.RemoveField( - model_name='electrictariffoutputs', - name='urdb_demand_comments', - ), - migrations.RemoveField( - model_name='electrictariffoutputs', - name='urdb_energy_comments', - ), - migrations.RemoveField( - model_name='electrictariffoutputs', - name='urdb_label', - ), - migrations.RemoveField( - model_name='electrictariffoutputs', - name='urdb_peak_kw_capacity_max', - ), - migrations.RemoveField( - model_name='electrictariffoutputs', - name='urdb_peak_kw_capacity_min', - ), - migrations.RemoveField( - model_name='electrictariffoutputs', - name='urdb_rate_additional_info', - ), - migrations.RemoveField( - model_name='electrictariffoutputs', - name='urdb_rate_description', - ), - migrations.RemoveField( - model_name='electrictariffoutputs', - name='urdb_rate_effective_date', - ), - migrations.RemoveField( - model_name='electrictariffoutputs', - name='urdb_rate_name', - ), - migrations.RemoveField( - model_name='electrictariffoutputs', - name='urdb_url_link', - ), - migrations.RemoveField( - model_name='electrictariffoutputs', - name='urdb_utility', - ), - migrations.RemoveField( - model_name='electrictariffoutputs', - name='urdb_voltage_level', - ), - migrations.AddField( - model_name='electrictariffinputs', - name='urdb_metadata', - field=models.JSONField(blank=True, help_text='Utility rate meta data from Utility Rate Database API', null=True), - ), - ] diff --git a/reoptjl/migrations/0112_rename_nem_electric_to_grid_series_kw_electrictariffoutputs_net_metering_electric_to_grid_series_kw_.py b/reoptjl/migrations/0112_rename_nem_electric_to_grid_series_kw_electrictariffoutputs_net_metering_electric_to_grid_series_kw_.py deleted file mode 100644 index 5b99f0d3c..000000000 --- a/reoptjl/migrations/0112_rename_nem_electric_to_grid_series_kw_electrictariffoutputs_net_metering_electric_to_grid_series_kw_.py +++ /dev/null @@ -1,73 +0,0 @@ -# Generated by Django 4.2.25 on 2025-10-22 22:21 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('reoptjl', '0111_remove_electrictariffoutputs_urdb_demand_comments_and_more'), - ] - - operations = [ - migrations.RenameField( - model_name='electrictariffoutputs', - old_name='NEM_electric_to_grid_series_kw', - new_name='net_metering_electric_to_grid_series_kw', - ), - migrations.RenameField( - model_name='electrictariffoutputs', - old_name='EXC_electric_to_grid_series_kw', - new_name='net_metering_excess_electric_to_grid_series_kw', - ), - migrations.RenameField( - model_name='electrictariffoutputs', - old_name='EXC_export_rate_series', - new_name='net_metering_excess_export_rate_series', - ), - migrations.RenameField( - model_name='electrictariffoutputs', - old_name='EXC_monthly_export_cost_benefit_before_tax', - new_name='net_metering_excess_monthly_export_cost_benefit_before_tax', - ), - migrations.RenameField( - model_name='electrictariffoutputs', - old_name='EXC_monthly_export_series_kwh', - new_name='net_metering_excess_monthly_export_series_kwh', - ), - migrations.RenameField( - model_name='electrictariffoutputs', - old_name='NEM_export_rate_series', - new_name='net_metering_export_rate_series', - ), - migrations.RenameField( - model_name='electrictariffoutputs', - old_name='NEM_monthly_export_cost_benefit_before_tax', - new_name='net_metering_monthly_export_cost_benefit_before_tax', - ), - migrations.RenameField( - model_name='electrictariffoutputs', - old_name='NEM_monthly_export_series_kwh', - new_name='net_metering_monthly_export_series_kwh', - ), - migrations.RenameField( - model_name='electrictariffoutputs', - old_name='WHL_electric_to_grid_series_kw', - new_name='wholesale_electric_to_grid_series_kw', - ), - migrations.RenameField( - model_name='electrictariffoutputs', - old_name='WHL_export_rate_series', - new_name='wholesale_export_rate_series', - ), - migrations.RenameField( - model_name='electrictariffoutputs', - old_name='WHL_monthly_export_cost_benefit_before_tax', - new_name='wholesale_monthly_export_cost_benefit_before_tax', - ), - migrations.RenameField( - model_name='electrictariffoutputs', - old_name='WHL_monthly_export_series_kwh', - new_name='wholesale_monthly_export_series_kwh', - ), - ] diff --git a/reoptjl/migrations/0113_rename_monthly_peak_kw_electricloadoutputs_monthly_peaks_kw.py b/reoptjl/migrations/0113_rename_monthly_peak_kw_electricloadoutputs_monthly_peaks_kw.py deleted file mode 100644 index 625c6323c..000000000 --- a/reoptjl/migrations/0113_rename_monthly_peak_kw_electricloadoutputs_monthly_peaks_kw.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 4.2.25 on 2025-10-23 22:33 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('reoptjl', '0112_rename_nem_electric_to_grid_series_kw_electrictariffoutputs_net_metering_electric_to_grid_series_kw_'), - ] - - operations = [ - migrations.RenameField( - model_name='electricloadoutputs', - old_name='monthly_peak_kw', - new_name='monthly_peaks_kw', - ), - ] From 0b766c8ae2f2de17ffa8dce9dd77007efea330e9 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Fri, 24 Oct 2025 10:24:02 -0600 Subject: [PATCH 25/63] Update REopt#peak-scaling branch for sim_load output names --- julia_src/Manifest.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/julia_src/Manifest.toml b/julia_src/Manifest.toml index 9cabcb1f8..6f16d2aa0 100644 --- a/julia_src/Manifest.toml +++ b/julia_src/Manifest.toml @@ -948,7 +948,7 @@ version = "1.11.0" [[deps.REopt]] deps = ["ArchGDAL", "CSV", "CoolProp", "DataFrames", "Dates", "DelimitedFiles", "HTTP", "JLD", "JSON", "JuMP", "LinDistFlow", "LinearAlgebra", "Logging", "MathOptInterface", "Requires", "Roots", "Statistics", "TestEnv"] -git-tree-sha1 = "b08ad59633ffcda75f037e377775a95822e6694b" +git-tree-sha1 = "016a24bc463c0b3dd261877efc2d7850de70fcc9" repo-rev = "peak-scaling" repo-url = "https://github.com/NREL/REopt.jl.git" uuid = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6" From 8243d341d80eaed69001bbf2c2512da2a8c16630 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Fri, 24 Oct 2025 10:24:55 -0600 Subject: [PATCH 26/63] Type conversions for simulated_load to be more specific, from Real to Float64 --- julia_src/http.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/julia_src/http.jl b/julia_src/http.jl index 7d6914165..85818357d 100644 --- a/julia_src/http.jl +++ b/julia_src/http.jl @@ -548,10 +548,10 @@ function simulated_load(req::HTTP.Request) "monthly_tonhour", "monthly_fraction", "addressable_load_fraction", "load_profile"] for key in vector_types if key in keys(d) && typeof(d[key]) <: Vector{} - d[key] = convert(Vector{Real}, d[key]) + d[key] = convert(Vector{Float64}, d[key]) elseif key in keys(d) && key == "addressable_load_fraction" # Scalar version of input, convert Any to Real - d[key] = convert(Real, d[key]) + d[key] = convert(Float64, d[key]) end end From 54696b28bb9788d9c8161a413617db7d6cbaaa9b Mon Sep 17 00:00:00 2001 From: bill-becker Date: Fri, 24 Oct 2025 10:25:36 -0600 Subject: [PATCH 27/63] Add time_steps_per_hour input to simulated_load for non-hourly scaling --- reoptjl/views.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/reoptjl/views.py b/reoptjl/views.py index d0e02304e..3832e36c0 100644 --- a/reoptjl/views.py +++ b/reoptjl/views.py @@ -585,7 +585,7 @@ def simulated_load(request): "monthly_totals_kwh","monthly_peaks_kw","annual_mmbtu","annual_fraction","annual_tonhour","monthly_tonhour", "monthly_mmbtu","monthly_fraction","max_thermal_factor_on_peak_load","chiller_cop", "addressable_load_fraction", "cooling_doe_ref_name", "cooling_pct_share", "boiler_efficiency", - "normalize_and_scale_load_profile_input", "year"] + "normalize_and_scale_load_profile_input", "year", "time_steps_per_hour"] for key in request.GET.keys(): k = key if "[" in key: @@ -669,7 +669,8 @@ def simulated_load(request): for energy in ["annual_tonhour", "monthly_tonhour"]: if data.get(energy) is not None: inputs[energy] = data.get(energy) - # TODO cooling, not in REopt.jl yet + if len(inputs["load_profile"]) != 8760: + inputs["time_steps_per_hour"] = data["time_steps_per_hour"] # TODO consider changing all requests to POST so that we don't have to do the weird array processing like percent_share[0], [1], etc? # json.dump(inputs, open("sim_load_post.json", "w")) From fcab6dcbf17c7291dc36a1fd8921c24c531b732d Mon Sep 17 00:00:00 2001 From: bill-becker Date: Fri, 24 Oct 2025 10:58:54 -0600 Subject: [PATCH 28/63] Merge migrations after merging scale-peak-loads --- reoptjl/migrations/0111_merge_20251024_1656.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 reoptjl/migrations/0111_merge_20251024_1656.py diff --git a/reoptjl/migrations/0111_merge_20251024_1656.py b/reoptjl/migrations/0111_merge_20251024_1656.py new file mode 100644 index 000000000..c2b3ee1e1 --- /dev/null +++ b/reoptjl/migrations/0111_merge_20251024_1656.py @@ -0,0 +1,14 @@ +# Generated by Django 4.2.25 on 2025-10-24 16:56 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('reoptjl', '0110_electricloadinputs_monthly_peaks_kw_and_more'), + ('reoptjl', '0110_electricloadoutputs_annual_peak_kw_and_more'), + ] + + operations = [ + ] From 127401bfe7c8e0aca7c001f49134c17006b6fa03 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Fri, 24 Oct 2025 15:24:53 -0600 Subject: [PATCH 29/63] Remove excessive outputs for ElectricTariff export rates/costs, squash migrations again --- julia_src/Manifest.toml | 4 +- julia_src/http.jl | 5 - ...ricloadinputs_monthly_peaks_kw_and_more.py | 97 +++++++++- ...tricloadoutputs_annual_peak_kw_and_more.py | 169 ------------------ .../migrations/0111_merge_20251024_1656.py | 14 -- reoptjl/models.py | 98 +--------- 6 files changed, 102 insertions(+), 285 deletions(-) delete mode 100644 reoptjl/migrations/0110_electricloadoutputs_annual_peak_kw_and_more.py delete mode 100644 reoptjl/migrations/0111_merge_20251024_1656.py diff --git a/julia_src/Manifest.toml b/julia_src/Manifest.toml index 9c4a2e4ea..2e42c79f9 100644 --- a/julia_src/Manifest.toml +++ b/julia_src/Manifest.toml @@ -948,11 +948,11 @@ version = "1.11.0" [[deps.REopt]] deps = ["ArchGDAL", "CSV", "CoolProp", "DataFrames", "Dates", "DelimitedFiles", "HTTP", "JLD", "JSON", "JuMP", "LinDistFlow", "LinearAlgebra", "Logging", "MathOptInterface", "Requires", "Roots", "Statistics", "TestEnv"] -git-tree-sha1 = "277df4545cd30a5fc227bf982897ef65d5ea3520" +git-tree-sha1 = "41fb68a9281a59c12731a5b21acb6d7d8018fd4e" repo-rev = "elec_util_usage" repo-url = "https://github.com/NREL/REopt.jl.git" uuid = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6" -version = "0.55.0" +version = "0.55.1" [[deps.Random]] deps = ["SHA"] diff --git a/julia_src/http.jl b/julia_src/http.jl index c78140dd8..7da731072 100644 --- a/julia_src/http.jl +++ b/julia_src/http.jl @@ -283,11 +283,6 @@ function reopt(req::HTTP.Request) if isempty(error_response) @info "REopt model solved with status $(results["status"])." - # These are matrices that need to be vector. - if haskey(results, "ElectricTariff") - results["ElectricTariff"]["year_one_electric_to_load_energy_cost_series_before_tax"] = results["ElectricTariff"]["year_one_electric_to_load_energy_cost_series_before_tax"][:,1] - results["ElectricTariff"]["monthly_facility_demand_cost_series_before_tax"] = results["ElectricTariff"]["monthly_facility_demand_cost_series_before_tax"][:,1] - end response = Dict( "results" => results, "reopt_version" => string(pkgversion(reoptjl)) diff --git a/reoptjl/migrations/0110_electricloadinputs_monthly_peaks_kw_and_more.py b/reoptjl/migrations/0110_electricloadinputs_monthly_peaks_kw_and_more.py index 3ad7ed1b0..0e2bba519 100644 --- a/reoptjl/migrations/0110_electricloadinputs_monthly_peaks_kw_and_more.py +++ b/reoptjl/migrations/0110_electricloadinputs_monthly_peaks_kw_and_more.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.24 on 2025-10-23 21:21 +# Generated by Django 4.2.25 on 2025-10-24 21:17 import django.contrib.postgres.fields import django.core.validators @@ -17,6 +17,101 @@ class Migration(migrations.Migration): name='monthly_peaks_kw', field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100000000.0)]), blank=True, default=list, help_text="Monthly peak power consumption (an array 12 entries long), in kW, used to scale either loads_kw series (with normalize_and_scale_load_profile_input) or the simulated default building load profile for the site's climate zone.Monthly energy is maintained while scaling to the monthly peaks.", size=None), ), + migrations.AddField( + model_name='electricloadoutputs', + name='annual_peak_kw', + field=models.FloatField(blank=True, help_text='Annual peak energy demand determined from load_series_kw. Does not include electric load for any new heating or cooling techs.', null=True), + ), + migrations.AddField( + model_name='electricloadoutputs', + name='monthly_calculated_kwh', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Monthly energy consumption calculated by summing up load_series_kw. Does not include electric load for any new heating or cooling techs.', size=None), + ), + migrations.AddField( + model_name='electricloadoutputs', + name='monthly_peaks_kw', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Monthly peak energy demand determined from load_series_kw. Does not include electric load for any new heating or cooling techs.', size=None), + ), + migrations.AddField( + model_name='electrictariffinputs', + name='urdb_metadata', + field=models.JSONField(blank=True, help_text='Utility rate meta data from Utility Rate Database API', null=True), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='monthly_electric_to_load_energy_cost_series_before_tax', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of monthly cost of power purchased from grid to serve loads.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='monthly_electric_to_storage_energy_cost_series_before_tax', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of monthly cost of power purchased from grid to charge battery storage system.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='monthly_facility_demand_cost_series_before_tax', + field=models.JSONField(blank=True, help_text='Series of total monthly facility demand charges by month.', null=True), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='monthly_gross_tou_demand_cost_series_before_tax', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of total time of use demand charges by month.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='monthly_tou_demand_cost_series_before_tax', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of total time of use demand charges for each month.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='tou_demand_metrics', + field=models.JSONField(blank=True, null=True), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='year_one_billed_demand_rate_series', + field=models.JSONField(blank=True, null=True), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='year_one_billed_energy_rate_series', + field=models.JSONField(blank=True, help_text='Series of billed energy rates for each timestep in year one.', null=True), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='year_one_billed_energy_rate_tier_limits', + field=models.JSONField(blank=True, null=True), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='year_one_billed_facilitydemand_monthly_rate_series', + field=models.JSONField(blank=True, null=True), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='year_one_billed_facilitydemand_monthly_rate_tier_limits', + field=models.JSONField(blank=True, null=True), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='year_one_billed_tou_demand_rate_tier_limits', + field=models.JSONField(blank=True, null=True), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='year_one_electric_to_load_energy_cost_series_before_tax', + field=models.JSONField(blank=True, help_text='Series of cost of power purchased from grid to serve load in each timestep.', null=True), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='year_one_electric_to_storage_energy_cost_series_before_tax', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of cost of power purchased from grid to charge battery storage system in each timestep.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='year_one_monthly_fixed_cost', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Year one fixed utility costs for each month.', size=None), + ), migrations.AlterField( model_name='electricloadinputs', name='monthly_totals_kwh', diff --git a/reoptjl/migrations/0110_electricloadoutputs_annual_peak_kw_and_more.py b/reoptjl/migrations/0110_electricloadoutputs_annual_peak_kw_and_more.py deleted file mode 100644 index 55aabc085..000000000 --- a/reoptjl/migrations/0110_electricloadoutputs_annual_peak_kw_and_more.py +++ /dev/null @@ -1,169 +0,0 @@ -# Generated by Django 4.2.25 on 2025-10-24 13:01 - -import django.contrib.postgres.fields -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('reoptjl', '0109_remove_ghpoutputs_iterations_auto_guess_and_more'), - ] - - operations = [ - migrations.AddField( - model_name='electricloadoutputs', - name='annual_peak_kw', - field=models.FloatField(blank=True, help_text='Annual peak energy demand determined from load_series_kw. Does not include electric load for any new heating or cooling techs.', null=True), - ), - migrations.AddField( - model_name='electricloadoutputs', - name='monthly_calculated_kwh', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Monthly energy consumption calculated by summing up load_series_kw. Does not include electric load for any new heating or cooling techs.', size=None), - ), - migrations.AddField( - model_name='electricloadoutputs', - name='monthly_peaks_kw', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Monthly peak energy demand determined from load_series_kw. Does not include electric load for any new heating or cooling techs.', size=None), - ), - migrations.AddField( - model_name='electrictariffinputs', - name='urdb_metadata', - field=models.JSONField(blank=True, help_text='Utility rate meta data from Utility Rate Database API', null=True), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='monthly_electric_to_load_energy_cost_series_before_tax', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of monthly cost of power purchased from grid to serve loads.', size=None), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='monthly_electric_to_storage_energy_cost_series_before_tax', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of monthly cost of power purchased from grid to charge battery storage system.', size=None), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='monthly_facility_demand_cost_series_before_tax', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of total monthly facility demand charges by month.', size=None), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='monthly_gross_tou_demand_cost_series_before_tax', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of total time of use demand charges by month.', size=None), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='monthly_tou_demand_cost_series_before_tax', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of total time of use demand charges for each month.', size=None), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='net_metering_electric_to_grid_series_kw', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of power exports to the grid up to net metering limit for each timestep.', size=None), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='net_metering_excess_electric_to_grid_series_kw', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of power exports to the grid above net metering limit for each timestep.', size=None), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='net_metering_excess_export_rate_series', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of compensation rate in each timestep for exporting power to grid beyond net metering limit.', size=None), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='net_metering_excess_monthly_export_cost_benefit_before_tax', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of monthly monetary benefit from exporting power to grid above net metering limit.', size=None), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='net_metering_excess_monthly_export_series_kwh', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of monthly energy exports to grid above net metering limit.', size=None), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='net_metering_export_rate_series', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of compensation rate in each timestep for exporting power to grid using systems sized up to net metering limit.', size=None), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='net_metering_monthly_export_cost_benefit_before_tax', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Year one cost of electricity consumed in each month.', size=None), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='net_metering_monthly_export_series_kwh', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Year one cost of electricity consumed in each month.', size=None), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='tou_demand_metrics', - field=models.JSONField(blank=True, null=True), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='wholesale_electric_to_grid_series_kw', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of power exports to the grid for wholesale in each timestep.', size=None), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='wholesale_export_rate_series', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of compensation rate in each timestep for exporting power to grid for wholesale.', size=None), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='wholesale_monthly_export_cost_benefit_before_tax', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of monthly monetary benefit from exporting power to grid at wholesale rate.', size=None), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='wholesale_monthly_export_series_kwh', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of monthly energy exports to grid under wholesale benefit.', size=None), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='year_one_billed_demand_rate_series', - field=models.JSONField(blank=True, null=True), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='year_one_billed_energy_rate_series', - field=models.JSONField(blank=True, help_text='Series of billed energy rates for each timestep in year one.', null=True), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='year_one_billed_energy_rate_tier_limits', - field=models.JSONField(blank=True, null=True), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='year_one_billed_facilitydemand_monthly_rate_series', - field=models.JSONField(blank=True, null=True), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='year_one_billed_facilitydemand_monthly_rate_tier_limits', - field=models.JSONField(blank=True, null=True), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='year_one_billed_tou_demand_rate_tier_limits', - field=models.JSONField(blank=True, null=True), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='year_one_electric_to_load_energy_cost_series_before_tax', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of cost of power purchased from grid to serve load in each timestep.', size=None), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='year_one_electric_to_storage_energy_cost_series_before_tax', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of cost of power purchased from grid to charge battery storage system in each timestep.', size=None), - ), - migrations.AddField( - model_name='electrictariffoutputs', - name='year_one_monthly_fixed_cost', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Year one fixed utility costs for each month.', size=None), - ), - ] diff --git a/reoptjl/migrations/0111_merge_20251024_1656.py b/reoptjl/migrations/0111_merge_20251024_1656.py deleted file mode 100644 index c2b3ee1e1..000000000 --- a/reoptjl/migrations/0111_merge_20251024_1656.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 4.2.25 on 2025-10-24 16:56 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('reoptjl', '0110_electricloadinputs_monthly_peaks_kw_and_more'), - ('reoptjl', '0110_electricloadoutputs_annual_peak_kw_and_more'), - ] - - operations = [ - ] diff --git a/reoptjl/models.py b/reoptjl/models.py index b4f0284f9..dae2d7039 100644 --- a/reoptjl/models.py +++ b/reoptjl/models.py @@ -2647,11 +2647,8 @@ class ElectricTariffOutputs(BaseModel, models.Model): help_text="Year one fixed utility costs for each month." ) - year_one_electric_to_load_energy_cost_series_before_tax = ArrayField( - models.FloatField( - null=True, blank=True - ), - default=list, + year_one_electric_to_load_energy_cost_series_before_tax = models.JSONField( + null=True, blank=True, help_text="Series of cost of power purchased from grid to serve load in each timestep." ) monthly_electric_to_load_energy_cost_series_before_tax = ArrayField( @@ -2675,11 +2672,8 @@ class ElectricTariffOutputs(BaseModel, models.Model): default=list, help_text="Series of monthly cost of power purchased from grid to charge battery storage system." ) - monthly_facility_demand_cost_series_before_tax = ArrayField( - models.FloatField( - null=True, blank=True - ), - default=list, + monthly_facility_demand_cost_series_before_tax = models.JSONField( + null=True, blank=True, help_text="Series of total monthly facility demand charges by month." ) monthly_gross_tou_demand_cost_series_before_tax = ArrayField( @@ -2689,90 +2683,6 @@ class ElectricTariffOutputs(BaseModel, models.Model): default=list, help_text="Series of total time of use demand charges by month." ) - net_metering_export_rate_series = ArrayField( - models.FloatField( - null=True, blank=True - ), - default=list, - help_text="Series of compensation rate in each timestep for exporting power to grid using systems sized up to net metering limit." - ) - net_metering_electric_to_grid_series_kw = ArrayField( - models.FloatField( - null=True, blank=True - ), - default=list, - help_text="Series of power exports to the grid up to net metering limit for each timestep." - ) - net_metering_monthly_export_series_kwh = ArrayField( - models.FloatField( - null=True, blank=True - ), - default=list, - help_text="Year one cost of electricity consumed in each month." - ) - net_metering_monthly_export_cost_benefit_before_tax = ArrayField( - models.FloatField( - null=True, blank=True - ), - default=list, - help_text="Year one cost of electricity consumed in each month." - ) - wholesale_export_rate_series = ArrayField( - models.FloatField( - null=True, blank=True - ), - default=list, - help_text="Series of compensation rate in each timestep for exporting power to grid for wholesale." - ) - wholesale_electric_to_grid_series_kw = ArrayField( - models.FloatField( - null=True, blank=True - ), - default=list, - help_text="Series of power exports to the grid for wholesale in each timestep." - ) - wholesale_monthly_export_series_kwh = ArrayField( - models.FloatField( - null=True, blank=True - ), - default=list, - help_text="Series of monthly energy exports to grid under wholesale benefit." - ) - wholesale_monthly_export_cost_benefit_before_tax = ArrayField( - models.FloatField( - null=True, blank=True - ), - default=list, - help_text="Series of monthly monetary benefit from exporting power to grid at wholesale rate." - ) - net_metering_excess_export_rate_series = ArrayField( - models.FloatField( - null=True, blank=True - ), - default=list, - help_text="Series of compensation rate in each timestep for exporting power to grid beyond net metering limit." - ) - net_metering_excess_electric_to_grid_series_kw = ArrayField( - models.FloatField( - null=True, blank=True - ), - default=list, - help_text="Series of power exports to the grid above net metering limit for each timestep." - ) - net_metering_excess_monthly_export_series_kwh = ArrayField( - models.FloatField( - null=True, blank=True - ), - default=list, - help_text="Series of monthly energy exports to grid above net metering limit." - ) - net_metering_excess_monthly_export_cost_benefit_before_tax = ArrayField( - models.FloatField( - null=True, blank=True - ), - default=list, - help_text="Series of monthly monetary benefit from exporting power to grid above net metering limit." - ) year_one_billed_energy_rate_series = models.JSONField( null=True, blank=True, From 4fe19389ecd1fd92ffabf1c087dedda623a81685 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Fri, 24 Oct 2025 15:28:29 -0600 Subject: [PATCH 30/63] Change REopt.jl back to #anccr --- julia_src/Manifest.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/julia_src/Manifest.toml b/julia_src/Manifest.toml index 2e42c79f9..b01b244d8 100644 --- a/julia_src/Manifest.toml +++ b/julia_src/Manifest.toml @@ -948,8 +948,8 @@ version = "1.11.0" [[deps.REopt]] deps = ["ArchGDAL", "CSV", "CoolProp", "DataFrames", "Dates", "DelimitedFiles", "HTTP", "JLD", "JSON", "JuMP", "LinDistFlow", "LinearAlgebra", "Logging", "MathOptInterface", "Requires", "Roots", "Statistics", "TestEnv"] -git-tree-sha1 = "41fb68a9281a59c12731a5b21acb6d7d8018fd4e" -repo-rev = "elec_util_usage" +git-tree-sha1 = "8d42aee5e9103e257a7a85b7549d1d316258b989" +repo-rev = "anccr" repo-url = "https://github.com/NREL/REopt.jl.git" uuid = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6" version = "0.55.1" From 08285f973461cf8d93028091561368abdd65b081 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Sun, 26 Oct 2025 08:12:11 -0600 Subject: [PATCH 31/63] Update REopt#anccr branch with 15-min scaling fix --- julia_src/Manifest.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/julia_src/Manifest.toml b/julia_src/Manifest.toml index b01b244d8..8285a59a9 100644 --- a/julia_src/Manifest.toml +++ b/julia_src/Manifest.toml @@ -948,7 +948,7 @@ version = "1.11.0" [[deps.REopt]] deps = ["ArchGDAL", "CSV", "CoolProp", "DataFrames", "Dates", "DelimitedFiles", "HTTP", "JLD", "JSON", "JuMP", "LinDistFlow", "LinearAlgebra", "Logging", "MathOptInterface", "Requires", "Roots", "Statistics", "TestEnv"] -git-tree-sha1 = "8d42aee5e9103e257a7a85b7549d1d316258b989" +git-tree-sha1 = "9429fda2ede90d7f1909f215c521fedbaad789f0" repo-rev = "anccr" repo-url = "https://github.com/NREL/REopt.jl.git" uuid = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6" From 18cadd71c6bc80e693e5b2f72b15e8f59a4ce9b1 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Tue, 28 Oct 2025 11:55:21 -0600 Subject: [PATCH 32/63] ElectricTariffOuputs Updates --- reoptjl/models.py | 107 ++++++++++++++++++++++++++++++---------------- 1 file changed, 70 insertions(+), 37 deletions(-) diff --git a/reoptjl/models.py b/reoptjl/models.py index dae2d7039..4bd8d63c8 100644 --- a/reoptjl/models.py +++ b/reoptjl/models.py @@ -2639,94 +2639,127 @@ class ElectricTariffOutputs(BaseModel, models.Model): related_name="ElectricTariffOutputs", primary_key=True ) - year_one_monthly_fixed_cost = ArrayField( + + monthly_fixed_cost = ArrayField( models.FloatField( null=True, blank=True ), default=list, help_text="Year one fixed utility costs for each month." ) - - year_one_electric_to_load_energy_cost_series_before_tax = models.JSONField( + monthly_fixed_cost_bau = ArrayField( + models.FloatField( + null=True, blank=True + ), + default=list, + help_text="Business as usual year one fixed utility costs for each month." + ) + energy_cost_series_before_tax = models.JSONField( null=True, blank=True, - help_text="Series of cost of power purchased from grid to serve load in each timestep." + help_text="Series of cost of power purchased from grid to serve load in each timestep, by Tier_i." ) - monthly_electric_to_load_energy_cost_series_before_tax = ArrayField( + energy_cost_series_before_tax_bau = models.JSONField( + null=True, blank=True, + help_text="Business as usual series of cost of power purchased from grid to serve load in each timestep, by Tier_i." + ) + monthly_energy_cost_series_before_tax = ArrayField( models.FloatField( null=True, blank=True ), default=list, help_text="Series of monthly cost of power purchased from grid to serve loads." ) - year_one_electric_to_storage_energy_cost_series_before_tax = ArrayField( + monthly_energy_cost_series_before_tax_bau = ArrayField( models.FloatField( null=True, blank=True ), default=list, - help_text="Series of cost of power purchased from grid to charge battery storage system in each timestep." + help_text="Business as usual series of monthly cost of power purchased from grid to serve loads." ) - monthly_electric_to_storage_energy_cost_series_before_tax = ArrayField( + monthly_facility_demand_cost_series_before_tax = ArrayField( models.FloatField( null=True, blank=True ), default=list, - help_text="Series of monthly cost of power purchased from grid to charge battery storage system." - ) - monthly_facility_demand_cost_series_before_tax = models.JSONField( - null=True, blank=True, - help_text="Series of total monthly facility demand charges by month." + help_text="Series of total (all tiers) monthly facility demand charges by month." ) - monthly_gross_tou_demand_cost_series_before_tax = ArrayField( + monthly_facility_demand_cost_series_before_tax_bau = ArrayField( models.FloatField( null=True, blank=True ), default=list, - help_text="Series of total time of use demand charges by month." + help_text="Business as usual series of total (all tiers) monthly facility demand charges by month." ) - - year_one_billed_energy_rate_series = models.JSONField( + energy_rate_series = models.JSONField( null=True, blank=True, help_text="Series of billed energy rates for each timestep in year one." ) - + energy_rate_average_series = ArrayField( + models.FloatField( + null=True, blank=True + ), + default=list, + help_text="Series of average (across tiers) energy rates for each timestep in year one." + ) monthly_tou_demand_cost_series_before_tax = ArrayField( models.FloatField( null=True, blank=True ), default=list, - help_text="Series of total time of use demand charges for each month." + help_text="Series of total time-of-use demand charges for each month." + ) + monthly_tou_demand_cost_series_before_tax_bau = ArrayField( + models.FloatField( + null=True, blank=True + ), + default=list, + help_text="Business as usual series of total time-of-use demand charges for each month." + ) + monthly_demand_cost_series_before_tax = ArrayField( + models.FloatField( + null=True, blank=True + ), + default=list, + help_text="Series of total (facility and TOU for all tiers) monthly demand charges for each month." + ) + monthly_demand_cost_series_before_tax_bau = ArrayField( + models.FloatField( + null=True, blank=True + ), + default=list, + help_text="Business as usual series of total (facility and TOU for all tiers) monthly demand charges for each month." ) - tou_demand_metrics = models.JSONField( null=True, blank=True, - help_text="" + help_text="Dictionary of TOU demand metrics, including month, tier, demand_rate, measured_tou_peak_demand, and demand_charge_before_tax" ) - - year_one_billed_facilitydemand_monthly_rate_tier_limits = models.JSONField( + facility_demand_monthly_rate_tier_limits = models.JSONField( null=True, blank=True, - help_text="" + help_text="Facility (not dependent on TOU) demand charge tier limits" ) - - year_one_billed_facilitydemand_monthly_rate_series = models.JSONField( + facility_demand_monthly_rate_series = models.JSONField( null=True, blank=True, - help_text="" + help_text="Facility (not dependent on TOU) demand charge rates by Tier_i" ) - - year_one_billed_tou_demand_rate_tier_limits = models.JSONField( + tou_demand_rate_tier_limits = models.JSONField( null=True, blank=True, - help_text="" + help_text="TOU demand rate tier limits" ) - - year_one_billed_energy_rate_tier_limits = models.JSONField( + energy_rate_tier_limits = models.JSONField( null=True, blank=True, - help_text="" + help_text="Energy rate tier limits" ) - - year_one_billed_demand_rate_series = models.JSONField( + tou_demand_rate_series = models.JSONField( null=True, blank=True, - help_text="" + help_text="Series of demand rates by Tier_i for each timestep." + ) + demand_rate_average_series = ArrayField( + models.FloatField( + null=True, blank=True + ), + default=list, + help_text="Series of average (across tiers) demand rates for each timestep in year one." ) - year_one_energy_cost_before_tax = models.FloatField( null=True, blank=True, help_text="Optimal year one utility energy cost" From 52f9d754ea0fe89284371c0539f744202139df5f Mon Sep 17 00:00:00 2001 From: bill-becker Date: Tue, 28 Oct 2025 15:33:32 -0600 Subject: [PATCH 33/63] Update squashed migration after ElectricTariffOutputs refactor --- ...ricloadinputs_monthly_peaks_kw_and_more.py | 90 ++++++++++++------- 1 file changed, 60 insertions(+), 30 deletions(-) diff --git a/reoptjl/migrations/0110_electricloadinputs_monthly_peaks_kw_and_more.py b/reoptjl/migrations/0110_electricloadinputs_monthly_peaks_kw_and_more.py index 0e2bba519..6e428cf8d 100644 --- a/reoptjl/migrations/0110_electricloadinputs_monthly_peaks_kw_and_more.py +++ b/reoptjl/migrations/0110_electricloadinputs_monthly_peaks_kw_and_more.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.25 on 2025-10-24 21:17 +# Generated by Django 4.2.25 on 2025-10-28 21:29 import django.contrib.postgres.fields import django.core.validators @@ -39,79 +39,109 @@ class Migration(migrations.Migration): ), migrations.AddField( model_name='electrictariffoutputs', - name='monthly_electric_to_load_energy_cost_series_before_tax', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of monthly cost of power purchased from grid to serve loads.', size=None), + name='demand_rate_average_series', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of average (across tiers) demand rates for each timestep in year one.', size=None), ), migrations.AddField( model_name='electrictariffoutputs', - name='monthly_electric_to_storage_energy_cost_series_before_tax', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of monthly cost of power purchased from grid to charge battery storage system.', size=None), + name='energy_cost_series_before_tax', + field=models.JSONField(blank=True, help_text='Series of cost of power purchased from grid to serve load in each timestep, by Tier_i.', null=True), ), migrations.AddField( model_name='electrictariffoutputs', - name='monthly_facility_demand_cost_series_before_tax', - field=models.JSONField(blank=True, help_text='Series of total monthly facility demand charges by month.', null=True), + name='energy_cost_series_before_tax_bau', + field=models.JSONField(blank=True, help_text='Business as usual series of cost of power purchased from grid to serve load in each timestep, by Tier_i.', null=True), ), migrations.AddField( model_name='electrictariffoutputs', - name='monthly_gross_tou_demand_cost_series_before_tax', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of total time of use demand charges by month.', size=None), + name='energy_rate_average_series', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of average (across tiers) energy rates for each timestep in year one.', size=None), ), migrations.AddField( model_name='electrictariffoutputs', - name='monthly_tou_demand_cost_series_before_tax', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of total time of use demand charges for each month.', size=None), + name='energy_rate_series', + field=models.JSONField(blank=True, help_text='Series of billed energy rates for each timestep in year one.', null=True), ), migrations.AddField( model_name='electrictariffoutputs', - name='tou_demand_metrics', - field=models.JSONField(blank=True, null=True), + name='energy_rate_tier_limits', + field=models.JSONField(blank=True, help_text='Energy rate tier limits', null=True), ), migrations.AddField( model_name='electrictariffoutputs', - name='year_one_billed_demand_rate_series', - field=models.JSONField(blank=True, null=True), + name='facility_demand_monthly_rate_series', + field=models.JSONField(blank=True, help_text='Facility (not dependent on TOU) demand charge rates by Tier_i', null=True), ), migrations.AddField( model_name='electrictariffoutputs', - name='year_one_billed_energy_rate_series', - field=models.JSONField(blank=True, help_text='Series of billed energy rates for each timestep in year one.', null=True), + name='facility_demand_monthly_rate_tier_limits', + field=models.JSONField(blank=True, help_text='Facility (not dependent on TOU) demand charge tier limits', null=True), ), migrations.AddField( model_name='electrictariffoutputs', - name='year_one_billed_energy_rate_tier_limits', - field=models.JSONField(blank=True, null=True), + name='monthly_demand_cost_series_before_tax', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of total (facility and TOU for all tiers) monthly demand charges for each month.', size=None), ), migrations.AddField( model_name='electrictariffoutputs', - name='year_one_billed_facilitydemand_monthly_rate_series', - field=models.JSONField(blank=True, null=True), + name='monthly_demand_cost_series_before_tax_bau', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Business as usual series of total (facility and TOU for all tiers) monthly demand charges for each month.', size=None), ), migrations.AddField( model_name='electrictariffoutputs', - name='year_one_billed_facilitydemand_monthly_rate_tier_limits', - field=models.JSONField(blank=True, null=True), + name='monthly_energy_cost_series_before_tax', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of monthly cost of power purchased from grid to serve loads.', size=None), ), migrations.AddField( model_name='electrictariffoutputs', - name='year_one_billed_tou_demand_rate_tier_limits', - field=models.JSONField(blank=True, null=True), + name='monthly_energy_cost_series_before_tax_bau', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Business as usual series of monthly cost of power purchased from grid to serve loads.', size=None), ), migrations.AddField( model_name='electrictariffoutputs', - name='year_one_electric_to_load_energy_cost_series_before_tax', - field=models.JSONField(blank=True, help_text='Series of cost of power purchased from grid to serve load in each timestep.', null=True), + name='monthly_facility_demand_cost_series_before_tax', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of total (all tiers) monthly facility demand charges by month.', size=None), ), migrations.AddField( model_name='electrictariffoutputs', - name='year_one_electric_to_storage_energy_cost_series_before_tax', - field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of cost of power purchased from grid to charge battery storage system in each timestep.', size=None), + name='monthly_facility_demand_cost_series_before_tax_bau', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Business as usual series of total (all tiers) monthly facility demand charges by month.', size=None), ), migrations.AddField( model_name='electrictariffoutputs', - name='year_one_monthly_fixed_cost', + name='monthly_fixed_cost', field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Year one fixed utility costs for each month.', size=None), ), + migrations.AddField( + model_name='electrictariffoutputs', + name='monthly_fixed_cost_bau', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Business as usual year one fixed utility costs for each month.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='monthly_tou_demand_cost_series_before_tax', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Series of total time-of-use demand charges for each month.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='monthly_tou_demand_cost_series_before_tax_bau', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Business as usual series of total time-of-use demand charges for each month.', size=None), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='tou_demand_metrics', + field=models.JSONField(blank=True, help_text='Dictionary of TOU demand metrics, including month, tier, demand_rate, measured_tou_peak_demand, and demand_charge_before_tax', null=True), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='tou_demand_rate_series', + field=models.JSONField(blank=True, help_text='Series of demand rates by Tier_i for each timestep.', null=True), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='tou_demand_rate_tier_limits', + field=models.JSONField(blank=True, help_text='TOU demand rate tier limits', null=True), + ), migrations.AlterField( model_name='electricloadinputs', name='monthly_totals_kwh', From 34528ce510aa8d66faf1696d2c5f57cb6d84f289 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Tue, 28 Oct 2025 15:33:56 -0600 Subject: [PATCH 34/63] Update REopt#anccr after ElectricTariff results refactor --- julia_src/Manifest.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/julia_src/Manifest.toml b/julia_src/Manifest.toml index 8285a59a9..e3e2d2ddb 100644 --- a/julia_src/Manifest.toml +++ b/julia_src/Manifest.toml @@ -948,7 +948,7 @@ version = "1.11.0" [[deps.REopt]] deps = ["ArchGDAL", "CSV", "CoolProp", "DataFrames", "Dates", "DelimitedFiles", "HTTP", "JLD", "JSON", "JuMP", "LinDistFlow", "LinearAlgebra", "Logging", "MathOptInterface", "Requires", "Roots", "Statistics", "TestEnv"] -git-tree-sha1 = "9429fda2ede90d7f1909f215c521fedbaad789f0" +git-tree-sha1 = "2a9a14bb8ff19b35c022bd92006e270e3e2978cf" repo-rev = "anccr" repo-url = "https://github.com/NREL/REopt.jl.git" uuid = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6" From cf4e1e9d2c0540da7aff78e8101288141e7a4c93 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Wed, 29 Oct 2025 10:30:57 -0600 Subject: [PATCH 35/63] Update REopt#anccr after get_load_metrics() finalize and export --- julia_src/Manifest.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/julia_src/Manifest.toml b/julia_src/Manifest.toml index e3e2d2ddb..81e4652cc 100644 --- a/julia_src/Manifest.toml +++ b/julia_src/Manifest.toml @@ -948,7 +948,7 @@ version = "1.11.0" [[deps.REopt]] deps = ["ArchGDAL", "CSV", "CoolProp", "DataFrames", "Dates", "DelimitedFiles", "HTTP", "JLD", "JSON", "JuMP", "LinDistFlow", "LinearAlgebra", "Logging", "MathOptInterface", "Requires", "Roots", "Statistics", "TestEnv"] -git-tree-sha1 = "2a9a14bb8ff19b35c022bd92006e270e3e2978cf" +git-tree-sha1 = "9f4d91d7f346773414cb21a40197fb9b860934d3" repo-rev = "anccr" repo-url = "https://github.com/NREL/REopt.jl.git" uuid = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6" From 506517218b091e9cb13ede561ef9f1c8d7e2f5c3 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Wed, 29 Oct 2025 15:15:06 -0600 Subject: [PATCH 36/63] Add new /get_load_metrics endpoint --- julia_src/Manifest.toml | 2 +- julia_src/http.jl | 30 ++++++++++++++++++++++ reoptjl/urls.py | 3 ++- reoptjl/views.py | 57 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 90 insertions(+), 2 deletions(-) diff --git a/julia_src/Manifest.toml b/julia_src/Manifest.toml index 81e4652cc..2a5332c3f 100644 --- a/julia_src/Manifest.toml +++ b/julia_src/Manifest.toml @@ -948,7 +948,7 @@ version = "1.11.0" [[deps.REopt]] deps = ["ArchGDAL", "CSV", "CoolProp", "DataFrames", "Dates", "DelimitedFiles", "HTTP", "JLD", "JSON", "JuMP", "LinDistFlow", "LinearAlgebra", "Logging", "MathOptInterface", "Requires", "Roots", "Statistics", "TestEnv"] -git-tree-sha1 = "9f4d91d7f346773414cb21a40197fb9b860934d3" +git-tree-sha1 = "dcf1738eccb7de29e10a6fd9ab12a185e4e040ed" repo-rev = "anccr" 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 7da731072..d2602379e 100644 --- a/julia_src/http.jl +++ b/julia_src/http.jl @@ -585,6 +585,35 @@ function simulated_load(req::HTTP.Request) end end +function get_load_metrics(req::HTTP.Request) + d = JSON.parse(String(req.body)) + + # Convert load_profile from Vector{Any} to Vector{Float64} + if "load_profile" in keys(d) && typeof(d["load_profile"]) <: Vector{} + d["load_profile"] = convert(Vector{Float64}, d["load_profile"]) + end + + @info "Getting load metrics..." + data = Dict() + error_response = Dict() + try + load_profile = pop!(d, "load_profile") + other_kwargs = reoptjl.dictkeys_tosymbols(d) + data = reoptjl.get_load_metrics(load_profile; other_kwargs...) + catch e + @error "Something went wrong in the get_load_metrics" exception=(e, catch_backtrace()) + error_response["error"] = sprint(showerror, e) + end + if isempty(error_response) + @info "Load metrics determined." + response = data + return HTTP.Response(200, JSON.json(response)) + else + @info "An error occured in the get_load_metrics endpoint" + return HTTP.Response(500, JSON.json(error_response)) + end +end + function ghp_efficiency_thermal_factors(req::HTTP.Request) d = JSON.parse(String(req.body)) @@ -790,4 +819,5 @@ HTTP.register!(ROUTER, "GET", "/health", health) HTTP.register!(ROUTER, "GET", "/get_existing_chiller_default_cop", get_existing_chiller_default_cop) HTTP.register!(ROUTER, "GET", "/get_ashp_defaults", get_ashp_defaults) HTTP.register!(ROUTER, "GET", "/pv_cost_defaults", pv_cost_defaults) +HTTP.register!(ROUTER, "GET", "/get_load_metrics", get_load_metrics) HTTP.serve(ROUTER, "0.0.0.0", 8081, reuseaddr=true) diff --git a/reoptjl/urls.py b/reoptjl/urls.py index eec71fae9..16e40949e 100644 --- a/reoptjl/urls.py +++ b/reoptjl/urls.py @@ -28,5 +28,6 @@ re_path(r'^get_ashp_defaults/?$', views.get_ashp_defaults), re_path(r'^pv_cost_defaults/?$', views.pv_cost_defaults), re_path(r'^summary_by_runuuids/?$', views.summary_by_runuuids), - re_path(r'^link_run_to_portfolios/?$', views.link_run_uuids_to_portfolio_uuid) + re_path(r'^link_run_to_portfolios/?$', views.link_run_uuids_to_portfolio_uuid), + re_path(r'^get_load_metrics/?$', views.get_load_metrics) ] diff --git a/reoptjl/views.py b/reoptjl/views.py index 418f45918..de551798f 100644 --- a/reoptjl/views.py +++ b/reoptjl/views.py @@ -1648,6 +1648,63 @@ def easiur_costs(request): log.error(debug_msg) return JsonResponse({"Error": "Unexpected Error. Please check your input parameters and contact reopt@nrel.gov if problems persist."}, status=500) +def get_load_metrics(request): + try: + if request.method == "POST": + post_body = json.loads(request.body) + load_profile = list(post_body.get("load_profile")) + + inputs = { + "load_profile": load_profile + } + + # Add optional parameters if provided + if post_body.get("time_steps_per_hour") is not None: + inputs["time_steps_per_hour"] = int(post_body.get("time_steps_per_hour")) + + if post_body.get("year") is not None: + inputs["year"] = int(post_body.get("year")) + + julia_host = os.environ.get('JULIA_HOST', "julia") + http_jl_response = requests.get("http://" + julia_host + ":8081/get_load_metrics/", json=inputs) + response_data = http_jl_response.json() + + # Round all numeric values in the response to 0 decimal places + def round_values(obj): + if isinstance(obj, dict): + return {key: round_values(value) for key, value in obj.items()} + elif isinstance(obj, list): + return [round_values(item) for item in obj] + elif isinstance(obj, float): + return int(round(obj, 0)) # Convert to int to remove decimal point + elif isinstance(obj, int): + return obj + else: + return obj + + rounded_response_data = round_values(response_data) + + response = JsonResponse( + rounded_response_data, + status=http_jl_response.status_code + ) + return response + else: + return JsonResponse({"Error": "Only POST method is supported for this endpoint"}, status=405) + + except ValueError as e: + return JsonResponse({"Error": str(e.args[0])}, status=400) + + except KeyError as e: + return JsonResponse({"Error. Missing": str(e.args[0])}, status=400) + + except Exception: + exc_type, exc_value, exc_traceback = sys.exc_info() + debug_msg = "exc_type: {}; exc_value: {}; exc_traceback: {}".format(exc_type, exc_value.args[0], + tb.format_tb(exc_traceback)) + log.debug(debug_msg) + return JsonResponse({"Error": "Unexpected error in get_load_metrics endpoint. Check log for more."}, status=500) + def sector_defaults(request): try: inputs = { From bd4907c1798883d673985c5a06b2d4a9d36d34a7 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Wed, 29 Oct 2025 15:22:34 -0600 Subject: [PATCH 37/63] Update CHANGELOG under the anccr branch heading --- CHANGELOG.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66fead55c..6b5050580 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,10 +26,12 @@ Classify the change according to the following categories: ##### Removed ### Patches -## peak-scaling +## anccr ### Minor Updates ##### Added -- **ElectricLoad** input **monthly_peaks_kw**. Can be used to scale loads_kw or doe_reference loads to monthly peaks while maintaining monthly energy. +- `ElectricLoad` input `monthly_peaks_kw`. Can be used to scale loads_kw or doe_reference loads to monthly peaks while maintaining monthly energy. +- `ElectricTariff` outputs: `demand_rate_average_series`, `energy_cost_series_before_tax`, `energy_cost_series_before_tax_bau`, `energy_rate_average_series`, `energy_rate_series`, `energy_rate_tier_limits`, `facility_demand_monthly_rate_series`, `facility_demand_monthly_rate_tier_limits`, `monthly_demand_cost_series_before_tax`, `monthly_demand_cost_series_before_tax_bau`, `monthly_energy_cost_series_before_tax`, `monthly_energy_cost_series_before_tax_bau`, `monthly_facility_demand_cost_series_before_tax`, `monthly_facility_demand_cost_series_before_tax_bau`, `monthly_fixed_cost`, `monthly_fixed_cost_bau`, `monthly_tou_demand_cost_series_before_tax`, `monthly_tou_demand_cost_series_before_tax_bau`, `tou_demand_metrics`, `tou_demand_rate_series`, `tou_demand_rate_tier_limits`. +- New endpoint `/get_load_metrics` for sending a timeseries `load_profile` and getting monthly and annual energy and peak loads. ## v3.16.2 From c54f8b8b0375d892b24f5e48c0721042881d6424 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Wed, 29 Oct 2025 15:23:08 -0600 Subject: [PATCH 38/63] Update ANCCR spreadsheet config to fix demand charges --- reoptjl/custom_table_config.py | 48 +++++++++++++++++----------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/reoptjl/custom_table_config.py b/reoptjl/custom_table_config.py index de50c9660..b7ba83ae5 100644 --- a/reoptjl/custom_table_config.py +++ b/reoptjl/custom_table_config.py @@ -1588,73 +1588,73 @@ "label": "January Energy Cost ($)", "key": "january_energy_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.0") # January + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.0") # January }, { "label": "February Energy Cost ($)", "key": "february_energy_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.1") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.1") }, { "label": "March Energy Cost ($)", "key": "march_energy_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.2") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.2") }, { "label": "April Energy Cost ($)", "key": "april_energy_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.3") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.3") }, { "label": "May Energy Cost ($)", "key": "may_energy_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.4") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.4") }, { "label": "June Energy Cost ($)", "key": "june_energy_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.5") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.5") }, { "label": "July Energy Cost ($)", "key": "july_energy_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.6") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.6") }, { "label": "August Energy Cost ($)", "key": "august_energy_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.7") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.7") }, { "label": "September Energy Cost ($)", "key": "september_energy_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.8") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.8") }, { "label": "October Energy Cost ($)", "key": "october_energy_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.9") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.9") }, { "label": "November Energy Cost ($)", "key": "november_energy_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.10") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.10") }, { "label": "December Energy Cost ($)", "key": "december_energy_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_electric_to_load_energy_cost_series_before_tax.11") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.11") }, ##################################################################################################### @@ -1671,73 +1671,73 @@ "label": "January Demand Cost ($)", "key": "january_demand_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.0") + safe_get(df, "outputs.ElectricTariff.monthly_facility_demand_cost_series_before_tax.0") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.0") }, { "label": "February Demand Cost ($)", "key": "february_demand_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.1") + safe_get(df, "outputs.ElectricTariff.monthly_facility_demand_cost_series_before_tax.1") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.1") }, { "label": "March Demand Cost ($)", "key": "march_demand_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.2") + safe_get(df, "outputs.ElectricTariff.monthly_facility_demand_cost_series_before_tax.2") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.2") }, { "label": "April Demand Cost ($)", "key": "april_demand_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.3") + safe_get(df, "outputs.ElectricTariff.monthly_facility_demand_cost_series_before_tax.3") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.3") }, { "label": "May Demand Cost ($)", "key": "may_demand_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.4") + safe_get(df, "outputs.ElectricTariff.monthly_facility_demand_cost_series_before_tax.4") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.4") }, { "label": "June Demand Cost ($)", "key": "june_demand_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.5") + safe_get(df, "outputs.ElectricTariff.monthly_facility_demand_cost_series_before_tax.5") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.5") }, { "label": "July Demand Cost ($)", "key": "july_demand_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.6") + safe_get(df, "outputs.ElectricTariff.monthly_facility_demand_cost_series_before_tax.6") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.6") }, { "label": "August Demand Cost ($)", "key": "august_demand_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.7") + safe_get(df, "outputs.ElectricTariff.monthly_facility_demand_cost_series_before_tax.7") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.7") }, { "label": "September Demand Cost ($)", "key": "september_demand_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.8") + safe_get(df, "outputs.ElectricTariff.monthly_facility_demand_cost_series_before_tax.8") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.8") }, { "label": "October Demand Cost ($)", "key": "october_demand_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.9") + safe_get(df, "outputs.ElectricTariff.monthly_facility_demand_cost_series_before_tax.9") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.9") }, { "label": "November Demand Cost ($)", "key": "november_demand_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.10") + safe_get(df, "outputs.ElectricTariff.monthly_facility_demand_cost_series_before_tax.10") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.10") }, { "label": "December Demand Cost ($)", "key": "december_demand_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_gross_tou_demand_cost_series_before_tax.11") + safe_get(df, "outputs.ElectricTariff.monthly_facility_demand_cost_series_before_tax.11") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.11") }, ##################################################################################################### From d3365c037efeee5967023bc1daae82928c9c94a1 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Thu, 30 Oct 2025 21:58:32 -0600 Subject: [PATCH 39/63] Enable POST for doe_reference_name input to /simulated_load --- reoptjl/views.py | 55 +++++++++++++++++++++++++++++++----------------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/reoptjl/views.py b/reoptjl/views.py index de551798f..fbdaa19e1 100644 --- a/reoptjl/views.py +++ b/reoptjl/views.py @@ -653,10 +653,24 @@ def simulated_load(request): if request.method == "POST": data = json.loads(request.body) - required_post_fields = ["load_type", "normalize_and_scale_load_profile_input", "load_profile", "year"] + required_post_fields = ["load_type", "year"] + either_required = ["normalize_and_scale_load_profile_input", "doe_reference_name"] + either_check = 0 + for either in either_required: + if data.get(either) is not None: + inputs[either] = data[either] + either_check += 1 + if either_check == 0: + return JsonResponse({"Error: Missing either of normalize_and_scale_load_profile_input or doe_reference_name."}, status=400) + elif either_check == 2: + return JsonResponse({"Error: Both normalize_and_scale_load_profile_input and doe_reference_name were input; only input one of these."}, status=400) for field in required_post_fields: - # TODO make year optional? + # TODO make year optional for doe_reference_name input inputs[field] = data[field] + if data.get("normalize_and_scale_load_profile_input") is not None: + inputs["load_profile"] = data["load_profile"] + if len(inputs["load_profile"]) != 8760: + inputs["time_steps_per_hour"] = data["time_steps_per_hour"] if inputs["load_type"] == "electric": for energy in ["annual_kwh", "monthly_totals_kwh", "monthly_peaks_kw"]: if data.get(energy) is not None: @@ -669,15 +683,18 @@ def simulated_load(request): for energy in ["annual_tonhour", "monthly_tonhour"]: if data.get(energy) is not None: inputs[energy] = data.get(energy) - if len(inputs["load_profile"]) != 8760: - inputs["time_steps_per_hour"] = data["time_steps_per_hour"] - # TODO consider changing all requests to POST so that we don't have to do the weird array processing like percent_share[0], [1], etc? # json.dump(inputs, open("sim_load_post.json", "w")) julia_host = os.environ.get('JULIA_HOST', "julia") http_jl_response = requests.get("http://" + julia_host + ":8081/simulated_load/", json=inputs) + response_data = http_jl_response.json() + # Round all non-loads_kw outputs to 2 decimal places + loads_kw = response_data.pop("loads_kw") + rounded_response_data = round_values(response_data) + rounded_response_data["loads_kw"] = loads_kw + response = JsonResponse( - http_jl_response.json(), + rounded_response_data, status=http_jl_response.status_code ) @@ -1668,19 +1685,6 @@ def get_load_metrics(request): julia_host = os.environ.get('JULIA_HOST', "julia") http_jl_response = requests.get("http://" + julia_host + ":8081/get_load_metrics/", json=inputs) response_data = http_jl_response.json() - - # Round all numeric values in the response to 0 decimal places - def round_values(obj): - if isinstance(obj, dict): - return {key: round_values(value) for key, value in obj.items()} - elif isinstance(obj, list): - return [round_values(item) for item in obj] - elif isinstance(obj, float): - return int(round(obj, 0)) # Convert to int to remove decimal point - elif isinstance(obj, int): - return obj - else: - return obj rounded_response_data = round_values(response_data) @@ -1705,6 +1709,19 @@ def round_values(obj): log.debug(debug_msg) return JsonResponse({"Error": "Unexpected error in get_load_metrics endpoint. Check log for more."}, status=500) +# Round all numeric values in the response to 0 decimal places +def round_values(obj): + if isinstance(obj, dict): + return {key: round_values(value) for key, value in obj.items()} + elif isinstance(obj, list): + return [round_values(item) for item in obj] + elif isinstance(obj, float): + return round(obj, 2) # Convert to int to remove decimal point + elif isinstance(obj, int): + return obj + else: + return obj + def sector_defaults(request): try: inputs = { From 169eedf439a385dd5f347861c782167a719e0db2 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Fri, 31 Oct 2025 08:51:47 -0600 Subject: [PATCH 40/63] Fix rounding for non-electric load_type --- reoptjl/views.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/reoptjl/views.py b/reoptjl/views.py index fbdaa19e1..829d61603 100644 --- a/reoptjl/views.py +++ b/reoptjl/views.py @@ -688,10 +688,11 @@ def simulated_load(request): julia_host = os.environ.get('JULIA_HOST', "julia") http_jl_response = requests.get("http://" + julia_host + ":8081/simulated_load/", json=inputs) response_data = http_jl_response.json() - # Round all non-loads_kw outputs to 2 decimal places - loads_kw = response_data.pop("loads_kw") + # Round all scalar/monthly outputs to 2 decimal places + load_profile_key = next((k for k in response_data if "loads_" in k), None) + load_profile = response_data.pop(load_profile_key) rounded_response_data = round_values(response_data) - rounded_response_data["loads_kw"] = loads_kw + rounded_response_data[load_profile_key] = load_profile response = JsonResponse( rounded_response_data, From 713a6a75c06c3e65ec5ca860b8a0405ba2bc7e29 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Wed, 5 Nov 2025 19:50:13 -0700 Subject: [PATCH 41/63] Update REopt.jl to develop branch --- julia_src/Manifest.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/julia_src/Manifest.toml b/julia_src/Manifest.toml index 2a5332c3f..d63ca5fbc 100644 --- a/julia_src/Manifest.toml +++ b/julia_src/Manifest.toml @@ -948,11 +948,11 @@ version = "1.11.0" [[deps.REopt]] deps = ["ArchGDAL", "CSV", "CoolProp", "DataFrames", "Dates", "DelimitedFiles", "HTTP", "JLD", "JSON", "JuMP", "LinDistFlow", "LinearAlgebra", "Logging", "MathOptInterface", "Requires", "Roots", "Statistics", "TestEnv"] -git-tree-sha1 = "dcf1738eccb7de29e10a6fd9ab12a185e4e040ed" -repo-rev = "anccr" +git-tree-sha1 = "f417499df09d366e58079aa3b4fd9897c37c2b61" +repo-rev = "develop" repo-url = "https://github.com/NREL/REopt.jl.git" uuid = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6" -version = "0.55.1" +version = "0.55.2" [[deps.Random]] deps = ["SHA"] From d7a79e64287c79d6914fd5e004bc02ab8e28be70 Mon Sep 17 00:00:00 2001 From: Grant Ellwood <120485824+gellwood@users.noreply.github.com> Date: Fri, 7 Nov 2025 07:27:14 -0700 Subject: [PATCH 42/63] Update ANCCR table config and add new calculations Added ANCCR-specific calculation formulas for year 1 charges and percent changes. Updated and standardized keys and labels for URDB-related fields. Modified scenario_value logic for installation, site, utility, and rate fields. Changed monthly energy and demand cost fields to use BAU series. Implemented monthly total bill cost calculations using fixed, energy, and demand BAU values. --- reoptjl/custom_table_config.py | 193 ++++++++++++++++++++------------- 1 file changed, 120 insertions(+), 73 deletions(-) diff --git a/reoptjl/custom_table_config.py b/reoptjl/custom_table_config.py index b7ba83ae5..c0fb6e986 100644 --- a/reoptjl/custom_table_config.py +++ b/reoptjl/custom_table_config.py @@ -1294,6 +1294,48 @@ "name": "Placeholder Calculation With BAU Reference", "formula": lambda col, bau, headers: f'=({bau["placeholder1_value"]}-{col}{headers["Placeholder2"] + 2})/{bau["placeholder1_value"]}' # This formula calculates the percentage change of Placeholder2 using Placeholder1's BAU value as the reference. + }, + + # ANCCR Specific Calculations + { + "name": "Change in Year 1 Total Charges ($)", + "formula": lambda col, bau, headers: f'={col}{headers["Year 1 Total Bill Charges ($)"] + 2}-{"C"}{headers["Year 1 Total Bill Charges ($)"] + 2}' + # This formula calculates the percentage change of scenario 2 vs. scenario 1. + }, + { + "name": "Year 1 Fixed Charges Percent Change (%)", + "formula": lambda col, bau, headers: f'=({col}{headers["Year 1 Annual Fixed Charges ($)"] + 2}-{"C"}{headers["Year 1 Annual Fixed Charges ($)"] + 2})/{"C"}{headers["Year 1 Annual Fixed Charges ($)"] + 2}' + # This formula calculates the percentage change of scenario 2 vs. scenario 1. + }, + { + "name": "Year 1 Energy Charges Percent Change (%)", + "formula": lambda col, bau, headers: f'=({col}{headers["Year 1 Annual Energy Charges ($)"] + 2}-{"C"}{headers["Year 1 Annual Energy Charges ($)"] + 2})/{"C"}{headers["Year 1 Annual Energy Charges ($)"] + 2}' + # This formula calculates the percentage change of scenario 2 vs. scenario 1. + }, + { + "name": "Year 1 Demand Charges Percent Change (%)", + "formula": lambda col, bau, headers: f'=({col}{headers["Year 1 Annual Demand Charges ($)"] + 2}-{"C"}{headers["Year 1 Annual Demand Charges ($)"] + 2})/{"C"}{headers["Year 1 Annual Demand Charges ($)"] + 2}' + # This formula calculates the percentage change of scenario 2 vs. scenario 1. + }, + { + "name": "Year 1 Total Bill Charges Percent Change (%)", + "formula": lambda col, bau, headers: f'=({col}{headers["Year 1 Annual Total Bill Charges ($)"] + 2}-{"C"}{headers["Year 1 Annual Total Bill Charges ($)"] + 2})/{"C"}{headers["Year 1 Annual Total Bill Charges ($)"] + 2}' + # This formula calculates the percentage change of scenario 2 vs. scenario 1. + }, + { + "name": "Year 1 Fixed Charges Percent of Total Bill (%)", + "formula": lambda col, bau, headers: f'=({col}{headers["Year 1 Annual Fixed Charges ($)"] + 2})/{col}{headers["Year 1 Annual Total Bill Charges ($)"] + 2}' + # This formula calculates the percentage change of scenario 2 vs. scenario 1. + }, + { + "name": "Year 1 Energy Charges Percent of Total Bill (%)", + "formula": lambda col, bau, headers: f'=({col}{headers["Year 1 Annual Energy Charges ($)"] + 2})/{col}{headers["Year 1 Annual Total Bill Charges ($)"] + 2}' + # This formula calculates the percentage change of scenario 2 vs. scenario 1. + }, + { + "name": "Year 1 Demand Charges Percent of Total Bill (%)", + "formula": lambda col, bau, headers: f'=({col}{headers["Year 1 Annual Demand Charges ($)"] + 2})/{col}{headers["Year 1 Annual Total Bill Charges ($)"] + 2}' + # This formula calculates the percentage change of scenario 2 vs. scenario 1. } ] @@ -1316,19 +1358,19 @@ ##################################################################################################### { - "label": "Installation Name", # Not REopt result; Name based on UTRMS Data + "label": "Installation Name", "key": "installation_name", "bau_value": lambda df: "", - "scenario_value": lambda df: "" + "scenario_value": lambda df: safe_get(df, "inputs.Meta.description") }, { - "label": "Site Location", # Not REopt result; Name based on User input + "label": "Site Location", "key": "site_location", "bau_value": lambda df: "", - "scenario_value": lambda df: "" + "scenario_value": lambda df: safe_get(df, "inputs.Meta.address") }, { - "label": "Utility Name", # Not REopt result; Name based on User input + "label": "Utility Name", "key": "utility_name", "bau_value": lambda df: "", "scenario_value": lambda df: safe_get(df, "inputs.ElectricTariff.urdb_metadata.utility") @@ -1345,16 +1387,16 @@ "scenario_value": lambda df: "" }, { - "label": "Rate Name", # Not REopt result; Name based on User input - "key": "rate_name", + "label": "Rate Name", + "key": "urdb_rate_name", "bau_value": lambda df: "", - "scenario_value": lambda df: "" + "scenario_value": lambda df: safe_get(df, "inputs.ElectricTariff.urdb_metadata.rate_name") }, { - "label": "Voltage Level", # Not REopt result; Name based on User input + "label": "Voltage Level", "key": "voltage_level", "bau_value": lambda df: "", - "scenario_value": lambda df: "" + "scenario_value": lambda df: safe_get(df, "inputs.ElectricTariff.urdb_metadata.voltage_level") }, { "label": "Year 1 Fixed Charges ($)", @@ -1380,9 +1422,10 @@ "bau_value": lambda df: "", "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.year_one_bill_before_tax") }, + # this value will need to be calculated compared to the current rate { - "label": "Change in Year 1 Charges ($)", # this value will need to be calculated compared to the current rate - "key": "change_in_year_1_charges", + "label": "Change in Year 1 Total Charges ($)", + "key": "change_in_year_1_total_charges", "bau_value": lambda df: "", "scenario_value": lambda df: "" }, @@ -1403,8 +1446,9 @@ "bau_value": lambda df: "", "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.year_one_fixed_cost_before_tax") }, + # this value will need to be calculated compared to the current rate { - "label": "Year 1 Fixed Charges Percent Change (%)", # this value will need to be calculated compared to the current rate + "label": "Year 1 Fixed Charges Percent Change (%)", "key": "year_1_fixed_charges_percent_change", "bau_value": lambda df: "", "scenario_value": lambda df: "" @@ -1415,8 +1459,9 @@ "bau_value": lambda df: "", "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.year_one_energy_cost_before_tax") }, + # this value will need to be calculated compared to the current rate { - "label": "Year 1 Energy Charges Percent Change (%)", # this value will need to be calculated compared to the current rate + "label": "Year 1 Energy Charges Percent Change (%)", "key": "year_1_energy_charges_percent_change", "bau_value": lambda df: "", "scenario_value": lambda df: "" @@ -1427,8 +1472,9 @@ "bau_value": lambda df: "", "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.year_one_demand_cost_before_tax") }, + # this value will need to be calculated compared to the current rate { - "label": "Year 1 Demand Charges Percent Change (%)", # this value will need to be calculated compared to the current rate + "label": "Year 1 Demand Charges Percent Change (%)", "key": "year_1_demand_charges_percent_change", "bau_value": lambda df: "", "scenario_value": lambda df: "" @@ -1439,8 +1485,9 @@ "bau_value": lambda df: "", "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.year_one_bill_before_tax") }, + # this value will need to be calculated compared to the current rate { - "label": "Year 1 Total Bill Charges Percent Change (%)", # this value will need to be calculated compared to the current rate + "label": "Year 1 Total Bill Charges Percent Change (%)", "key": "year_1_total_bill_charges_percent_change", "bau_value": lambda df: "", "scenario_value": lambda df: "" @@ -1508,70 +1555,70 @@ "scenario_value": lambda df: "" }, { - "label": "URDB Rate Name", # will need to pull from URDB + "label": "URDB Rate Name", "key": "urdb_rate_name", "bau_value": lambda df: "", "scenario_value": lambda df: safe_get(df, "inputs.ElectricTariff.urdb_metadata.rate_name") }, { - "label": "URDB Label", # will need to pull from URDB + "label": "URDB Label", "key": "urdb_label", "bau_value": lambda df: "", "scenario_value": lambda df: safe_get(df, "inputs.ElectricTariff.urdb_metadata.label") }, { - "label": "Rate Effective Date (latest_update)", # will need to pull from URDB - "key": "rate_effective_date", + "label": "URDB Rate Effective Date", + "key": "urdb_rate_effective_date", "bau_value": lambda df: "", "scenario_value": lambda df: safe_get(df, "inputs.ElectricTariff.urdb_metadata.rate_effective_date") }, { - "label": "Voltage Level (voltagecategory)", # will need to pull from URDB - "key": "voltage_level_urdb", + "label": "URDB Voltage Level", + "key": "urdb_voltage_level", "bau_value": lambda df: "", "scenario_value": lambda df: safe_get(df, "inputs.ElectricTariff.urdb_metadata.voltage_level") }, { - "label": "Peak kW Capacity Min (peakkwcapacitymin)", # will need to pull from URDB - "key": "peak_kw_capacity_min", + "label": "URDB Peak kW Capacity Min", + "key": "urdb_peak_kw_capacity_min", "bau_value": lambda df: "", "scenario_value": lambda df: safe_get(df, "inputs.ElectricTariff.urdb_metadata.peak_kw_capacity_min") }, { - "label": "Peak kW Capacity Max (peakkwcapacitymax)", # will need to pull from URDB - "key": "peak_kw_capacity_max", + "label": "URDB Peak kW Capacity Max", + "key": "urdb_peak_kw_capacity_max", "bau_value": lambda df: "", "scenario_value": lambda df: safe_get(df, "inputs.ElectricTariff.urdb_metadata.peak_kw_capacity_max") }, { - "label": "Rate Description (description)", # will need to pull from URDB - "key": "rate_description", + "label": "URDB Rate Description", + "key": "urdb_rate_description", "bau_value": lambda df: "", "scenario_value": lambda df: safe_get(df, "inputs.ElectricTariff.urdb_metadata.rate_description") }, { - "label": "Additional Information (basicinformationcomments)", # will need to pull from URDB - "key": "additional_information", + "label": "URDB Additional Information", + "key": "urdb_additional_information", "bau_value": lambda df: "", "scenario_value": lambda df: safe_get(df, "inputs.ElectricTariff.urdb_metadata.rate_additional_info") }, { - "label": "Energy Comments (energycomments)", # will need to pull from URDB - "key": "energy_comments", + "label": "URDB Energy Comments", + "key": "urdb_energy_comments", "bau_value": lambda df: "", "scenario_value": lambda df: safe_get(df, "inputs.ElectricTariff.urdb_metadata.energy_comments") }, { - "label": "Demand Comments (demandcomments)", # will need to pull from URDB - "key": "demand_comments", + "label": "URDB Demand Comments", + "key": "urdb_demand_comments", "bau_value": lambda df: "", "scenario_value": lambda df: safe_get(df, "inputs.ElectricTariff.urdb_metadata.demand_comments") }, { - "label": "URDB Link", # will need to pull from URDB - "key": "urdb_link", + "label": "URDB URL", + "key": "urdb_url", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "inputs.ElectricTariff.urdb_metadata.url_link") + "scenario_value": lambda df: f'=HYPERLINK("{safe_get(df, "inputs.ElectricTariff.urdb_metadata.url_link")}", "URDB Link")' }, ##################################################################################################### @@ -1588,73 +1635,73 @@ "label": "January Energy Cost ($)", "key": "january_energy_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.0") # January + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.0") # January }, { "label": "February Energy Cost ($)", "key": "february_energy_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.1") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.1") }, { "label": "March Energy Cost ($)", "key": "march_energy_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.2") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.2") }, { "label": "April Energy Cost ($)", "key": "april_energy_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.3") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.3") }, { "label": "May Energy Cost ($)", "key": "may_energy_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.4") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.4") }, { "label": "June Energy Cost ($)", "key": "june_energy_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.5") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.5") }, { "label": "July Energy Cost ($)", "key": "july_energy_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.6") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.6") }, { "label": "August Energy Cost ($)", "key": "august_energy_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.7") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.7") }, { "label": "September Energy Cost ($)", "key": "september_energy_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.8") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.8") }, { "label": "October Energy Cost ($)", "key": "october_energy_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.9") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.9") }, { "label": "November Energy Cost ($)", "key": "november_energy_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.10") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.10") }, { "label": "December Energy Cost ($)", "key": "december_energy_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.11") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.11") }, ##################################################################################################### @@ -1671,73 +1718,73 @@ "label": "January Demand Cost ($)", "key": "january_demand_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.0") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.0") }, { "label": "February Demand Cost ($)", "key": "february_demand_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.1") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.1") }, { "label": "March Demand Cost ($)", "key": "march_demand_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.2") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.2") }, { "label": "April Demand Cost ($)", "key": "april_demand_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.3") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.3") }, { "label": "May Demand Cost ($)", "key": "may_demand_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.4") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.4") }, { "label": "June Demand Cost ($)", "key": "june_demand_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.5") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.5") }, { "label": "July Demand Cost ($)", "key": "july_demand_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.6") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.6") }, { "label": "August Demand Cost ($)", "key": "august_demand_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.7") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.7") }, { "label": "September Demand Cost ($)", "key": "september_demand_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.8") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.8") }, { "label": "October Demand Cost ($)", "key": "october_demand_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.9") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.9") }, { "label": "November Demand Cost ($)", "key": "november_demand_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.10") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.10") }, { "label": "December Demand Cost ($)", "key": "december_demand_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.11") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.11") }, ##################################################################################################### @@ -1754,73 +1801,73 @@ "label": "January Total Bill Cost ($)", # not sure if we have a monthly total bill cost output in REopt -- need to ask Bhavesh "key": "january_total_bill_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: "" + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_fixed_cost_bau.0") + safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.0") + safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.0") }, { "label": "February Total Bill Cost ($)", "key": "february_total_bill_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: "" + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_fixed_cost_bau.1") + safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.1") + safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.1") }, { "label": "March Total Bill Cost ($)", "key": "march_total_bill_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: "" + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_fixed_cost_bau.2") + safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.2") + safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.2") }, { "label": "April Total Bill Cost ($)", "key": "april_total_bill_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: "" + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_fixed_cost_bau.3") + safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.3") + safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.3") }, { "label": "May Total Bill Cost ($)", "key": "may_total_bill_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: "" + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_fixed_cost_bau.4") + safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.4") + safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.4") }, { "label": "June Total Bill Cost ($)", "key": "june_total_bill_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: "" + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_fixed_cost_bau.5") + safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.5") + safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.5") }, { "label": "July Total Bill Cost ($)", "key": "july_total_bill_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: "" + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_fixed_cost_bau.6") + safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.6") + safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.6") }, { "label": "August Total Bill Cost ($)", "key": "august_total_bill_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: "" + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_fixed_cost_bau.7") + safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.7") + safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.7") }, { "label": "September Total Bill Cost ($)", "key": "september_total_bill_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: "" + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_fixed_cost_bau.8") + safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.8") + safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.8") }, { "label": "October Total Bill Cost ($)", "key": "october_total_bill_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: "" + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_fixed_cost_bau.9") + safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.9") + safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.9") }, { "label": "November Total Bill Cost ($)", "key": "november_total_bill_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: "" + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_fixed_cost_bau.10") + safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.10") + safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.10") }, { "label": "December Total Bill Cost ($)", "key": "december_total_bill_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: "" + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_fixed_cost_bau.11") + safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.11") + safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.11") }, ##################################################################################################### From 93deef9efe1d087cb944613d12e53dc99518a082 Mon Sep 17 00:00:00 2001 From: Grant Ellwood <120485824+gellwood@users.noreply.github.com> Date: Fri, 7 Nov 2025 09:41:19 -0700 Subject: [PATCH 43/63] Update ANCCR table logic and Excel export headers Modified custom_table_anccr to use scenario values instead of BAU for monthly cost fields. Updated Excel export logic to hide all BAU columns for ANCCR tables and use rate names as headers for scenario columns. Also updated the generate_excel_workbook function to accept scenarios and handle new header logic. --- reoptjl/custom_table_config.py | 72 +++++++++++++++++----------------- reoptjl/views.py | 72 +++++++++++++++++++++++++--------- 2 files changed, 89 insertions(+), 55 deletions(-) diff --git a/reoptjl/custom_table_config.py b/reoptjl/custom_table_config.py index c0fb6e986..f28fb4922 100644 --- a/reoptjl/custom_table_config.py +++ b/reoptjl/custom_table_config.py @@ -1635,73 +1635,73 @@ "label": "January Energy Cost ($)", "key": "january_energy_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.0") # January + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.0") # January }, { "label": "February Energy Cost ($)", "key": "february_energy_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.1") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.1") }, { "label": "March Energy Cost ($)", "key": "march_energy_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.2") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.2") }, { "label": "April Energy Cost ($)", "key": "april_energy_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.3") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.3") }, { "label": "May Energy Cost ($)", "key": "may_energy_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.4") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.4") }, { "label": "June Energy Cost ($)", "key": "june_energy_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.5") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.5") }, { "label": "July Energy Cost ($)", "key": "july_energy_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.6") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.6") }, { "label": "August Energy Cost ($)", "key": "august_energy_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.7") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.7") }, { "label": "September Energy Cost ($)", "key": "september_energy_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.8") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.8") }, { "label": "October Energy Cost ($)", "key": "october_energy_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.9") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.9") }, { "label": "November Energy Cost ($)", "key": "november_energy_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.10") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.10") }, { "label": "December Energy Cost ($)", "key": "december_energy_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.11") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.11") }, ##################################################################################################### @@ -1718,73 +1718,73 @@ "label": "January Demand Cost ($)", "key": "january_demand_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.0") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.0") }, { "label": "February Demand Cost ($)", "key": "february_demand_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.1") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.1") }, { "label": "March Demand Cost ($)", "key": "march_demand_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.2") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.2") }, { "label": "April Demand Cost ($)", "key": "april_demand_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.3") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.3") }, { "label": "May Demand Cost ($)", "key": "may_demand_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.4") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.4") }, { "label": "June Demand Cost ($)", "key": "june_demand_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.5") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.5") }, { "label": "July Demand Cost ($)", "key": "july_demand_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.6") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.6") }, { "label": "August Demand Cost ($)", "key": "august_demand_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.7") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.7") }, { "label": "September Demand Cost ($)", "key": "september_demand_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.8") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.8") }, { "label": "October Demand Cost ($)", "key": "october_demand_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.9") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.9") }, { "label": "November Demand Cost ($)", "key": "november_demand_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.10") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.10") }, { "label": "December Demand Cost ($)", "key": "december_demand_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.11") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.11") }, ##################################################################################################### @@ -1801,73 +1801,73 @@ "label": "January Total Bill Cost ($)", # not sure if we have a monthly total bill cost output in REopt -- need to ask Bhavesh "key": "january_total_bill_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_fixed_cost_bau.0") + safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.0") + safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.0") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_fixed_cost.0") + safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.0") + safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.0") }, { "label": "February Total Bill Cost ($)", "key": "february_total_bill_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_fixed_cost_bau.1") + safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.1") + safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.1") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_fixed_cost.1") + safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.1") + safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.1") }, { "label": "March Total Bill Cost ($)", "key": "march_total_bill_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_fixed_cost_bau.2") + safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.2") + safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.2") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_fixed_cost.2") + safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.2") + safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.2") }, { "label": "April Total Bill Cost ($)", "key": "april_total_bill_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_fixed_cost_bau.3") + safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.3") + safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.3") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_fixed_cost.3") + safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.3") + safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.3") }, { "label": "May Total Bill Cost ($)", "key": "may_total_bill_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_fixed_cost_bau.4") + safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.4") + safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.4") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_fixed_cost.4") + safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.4") + safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.4") }, { "label": "June Total Bill Cost ($)", "key": "june_total_bill_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_fixed_cost_bau.5") + safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.5") + safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.5") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_fixed_cost.5") + safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.5") + safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.5") }, { "label": "July Total Bill Cost ($)", "key": "july_total_bill_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_fixed_cost_bau.6") + safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.6") + safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.6") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_fixed_cost.6") + safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.6") + safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.6") }, { "label": "August Total Bill Cost ($)", "key": "august_total_bill_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_fixed_cost_bau.7") + safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.7") + safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.7") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_fixed_cost.7") + safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.7") + safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.7") }, { "label": "September Total Bill Cost ($)", "key": "september_total_bill_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_fixed_cost_bau.8") + safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.8") + safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.8") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_fixed_cost.8") + safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.8") + safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.8") }, { "label": "October Total Bill Cost ($)", "key": "october_total_bill_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_fixed_cost_bau.9") + safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.9") + safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.9") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_fixed_cost.9") + safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.9") + safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.9") }, { "label": "November Total Bill Cost ($)", "key": "november_total_bill_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_fixed_cost_bau.10") + safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.10") + safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.10") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_fixed_cost.10") + safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.10") + safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.10") }, { "label": "December Total Bill Cost ($)", "key": "december_total_bill_cost", "bau_value": lambda df: "", - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_fixed_cost_bau.11") + safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax_bau.11") + safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax_bau.11") + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_fixed_cost.11") + safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.11") + safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.11") }, ##################################################################################################### diff --git a/reoptjl/views.py b/reoptjl/views.py index 829d61603..2b556f06f 100644 --- a/reoptjl/views.py +++ b/reoptjl/views.py @@ -1926,7 +1926,7 @@ def generate_results_table(request: Any) -> HttpResponse: final_df_transpose = final_df_transpose.drop(final_df_transpose.index[0]) output = io.BytesIO() - generate_excel_workbook(final_df_transpose, target_custom_table, output) + generate_excel_workbook(final_df_transpose, target_custom_table, output, scenarios['scenarios']) output.seek(0) response = HttpResponse(output, content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') @@ -1938,7 +1938,7 @@ def generate_results_table(request: Any) -> HttpResponse: log.error(f"Unexpected error in generate_results_table: {e}") return JsonResponse({"Error": "An unexpected error occurred. Please try again later."}, status=500) -def generate_excel_workbook(df: pd.DataFrame, custom_table: List[Dict[str, Any]], output: io.BytesIO) -> None: +def generate_excel_workbook(df: pd.DataFrame, custom_table: List[Dict[str, Any]], output: io.BytesIO, scenarios: List[Dict[str, Any]] = None) -> None: try: workbook = xlsxwriter.Workbook(output, {'in_memory': True}) @@ -2026,31 +2026,46 @@ def get_combined_format(label, row_color, is_formula=False): column_width = 25 columns_to_hide = set() + # Check if using custom_table_anccr - if so, hide ALL BAU columns + is_anccr_table = (custom_table == custom_table_anccr) + + # Extract rate names for ANCCR table headers + rate_names = [] + if is_anccr_table and scenarios: + for scenario in scenarios: + rate_name = scenario.get('full_data', {}).get('inputs', {}).get('ElectricTariff', {}).get('urdb_metadata', {}).get('rate_name', None) + if rate_name: + rate_names.append(rate_name) + # Loop through BAU columns and check if all numerical values are identical across all BAU columns bau_columns = [i for i, header in enumerate(df.columns) if "BAU" in header] # Only proceed if there are BAU columns if bau_columns: - identical_bau_columns = True # Assume all BAU columns are identical unless proven otherwise + if is_anccr_table: + # For custom_table_anccr, hide ALL BAU columns + columns_to_hide.update(bau_columns) + else: + identical_bau_columns = True # Assume all BAU columns are identical unless proven otherwise - # Loop through each row and check the values across BAU columns - for row_num in range(len(df)): - row_values = df.iloc[row_num, bau_columns].values # Get all BAU values for this row + # Loop through each row and check the values across BAU columns + for row_num in range(len(df)): + row_values = df.iloc[row_num, bau_columns].values # Get all BAU values for this row - # Filter only numerical values for comparison - numerical_values = [value for value in row_values if isinstance(value, (int, float))] + # Filter only numerical values for comparison + numerical_values = [value for value in row_values if isinstance(value, (int, float))] - # Check if all numerical BAU values in this row are the same - if numerical_values: # Proceed only if there are numerical values to compare - first_bau_value = numerical_values[0] - if not all(value == first_bau_value for value in numerical_values): - identical_bau_columns = False - break # If any row has different BAU values, stop checking further + # Check if all numerical BAU values in this row are the same + if numerical_values: # Proceed only if there are numerical values to compare + first_bau_value = numerical_values[0] + if not all(value == first_bau_value for value in numerical_values): + identical_bau_columns = False + break # If any row has different BAU values, stop checking further - # If all BAU columns are identical across all rows, hide all but the first BAU column - if identical_bau_columns: - for col_num in bau_columns[1:]: - columns_to_hide.add(col_num) + # If all BAU columns are identical across all rows, hide all but the first BAU column + if identical_bau_columns: + for col_num in bau_columns[1:]: + columns_to_hide.add(col_num) # Now set the column properties for hiding BAU columns and leaving others unchanged for col_num, header in enumerate(df.columns): @@ -2063,8 +2078,27 @@ def get_combined_format(label, row_color, is_formula=False): # Write scenario headers worksheet.write('A1', 'Scenario', scenario_formats[0]) + + # Track non-BAU column index for rate name mapping (only for custom_table_anccr) + non_bau_index = 0 + for col_num, header in enumerate(df.columns): - worksheet.write(0, col_num + 1, header, scenario_formats[(col_num // 2) % (len(scenario_formats) - 1) + 1]) + # For custom_table_anccr, use rate names for non-BAU column headers + if is_anccr_table and rate_names: + if "BAU" not in header: + # This is a non-BAU column - use rate name as header + if non_bau_index < len(rate_names): + header_text = rate_names[non_bau_index] + else: + header_text = header + non_bau_index += 1 + else: + # This is a BAU column - keep original header (will be hidden) + header_text = header + else: + header_text = header + + worksheet.write(0, col_num + 1, header_text, scenario_formats[(col_num // 2) % (len(scenario_formats) - 1) + 1]) # Write variable names and data with full-row formatting row_offset = 0 # To keep track of the current row in the worksheet From e566415cedaa8c6e5ebafdfb644c481064037417 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Tue, 11 Nov 2025 21:12:26 -0700 Subject: [PATCH 44/63] Update REopt to v0.56.0 --- julia_src/Manifest.toml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/julia_src/Manifest.toml b/julia_src/Manifest.toml index d63ca5fbc..e06ec8559 100644 --- a/julia_src/Manifest.toml +++ b/julia_src/Manifest.toml @@ -948,11 +948,9 @@ version = "1.11.0" [[deps.REopt]] deps = ["ArchGDAL", "CSV", "CoolProp", "DataFrames", "Dates", "DelimitedFiles", "HTTP", "JLD", "JSON", "JuMP", "LinDistFlow", "LinearAlgebra", "Logging", "MathOptInterface", "Requires", "Roots", "Statistics", "TestEnv"] -git-tree-sha1 = "f417499df09d366e58079aa3b4fd9897c37c2b61" -repo-rev = "develop" -repo-url = "https://github.com/NREL/REopt.jl.git" +git-tree-sha1 = "476b384ce2b9b010dea363a5b79bfb4838def222" uuid = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6" -version = "0.55.2" +version = "0.56.0" [[deps.Random]] deps = ["SHA"] From 556de365e530e98e0503f621a907eedc567efbe8 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Tue, 11 Nov 2025 21:21:38 -0700 Subject: [PATCH 45/63] Update monthly_fixed_cost name and re-squash migrations Note, will have to delete deployed db associated with this API branch for the new monthly_fixed_cost_... name to work --- CHANGELOG.md | 2 +- .../0110_electricloadinputs_monthly_peaks_kw_and_more.py | 6 +++--- reoptjl/models.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b5050580..c4823a691 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,7 +30,7 @@ Classify the change according to the following categories: ### Minor Updates ##### Added - `ElectricLoad` input `monthly_peaks_kw`. Can be used to scale loads_kw or doe_reference loads to monthly peaks while maintaining monthly energy. -- `ElectricTariff` outputs: `demand_rate_average_series`, `energy_cost_series_before_tax`, `energy_cost_series_before_tax_bau`, `energy_rate_average_series`, `energy_rate_series`, `energy_rate_tier_limits`, `facility_demand_monthly_rate_series`, `facility_demand_monthly_rate_tier_limits`, `monthly_demand_cost_series_before_tax`, `monthly_demand_cost_series_before_tax_bau`, `monthly_energy_cost_series_before_tax`, `monthly_energy_cost_series_before_tax_bau`, `monthly_facility_demand_cost_series_before_tax`, `monthly_facility_demand_cost_series_before_tax_bau`, `monthly_fixed_cost`, `monthly_fixed_cost_bau`, `monthly_tou_demand_cost_series_before_tax`, `monthly_tou_demand_cost_series_before_tax_bau`, `tou_demand_metrics`, `tou_demand_rate_series`, `tou_demand_rate_tier_limits`. +- `ElectricTariff` outputs: `demand_rate_average_series`, `energy_cost_series_before_tax`, `energy_cost_series_before_tax_bau`, `energy_rate_average_series`, `energy_rate_series`, `energy_rate_tier_limits`, `facility_demand_monthly_rate_series`, `facility_demand_monthly_rate_tier_limits`, `monthly_demand_cost_series_before_tax`, `monthly_demand_cost_series_before_tax_bau`, `monthly_energy_cost_series_before_tax`, `monthly_energy_cost_series_before_tax_bau`, `monthly_facility_demand_cost_series_before_tax`, `monthly_facility_demand_cost_series_before_tax_bau`, `monthly_fixed_cost_series_before_tax`, `monthly_fixed_cost_before_tax_bau`, `monthly_tou_demand_cost_series_before_tax`, `monthly_tou_demand_cost_series_before_tax_bau`, `tou_demand_metrics`, `tou_demand_rate_series`, `tou_demand_rate_tier_limits`. - New endpoint `/get_load_metrics` for sending a timeseries `load_profile` and getting monthly and annual energy and peak loads. diff --git a/reoptjl/migrations/0110_electricloadinputs_monthly_peaks_kw_and_more.py b/reoptjl/migrations/0110_electricloadinputs_monthly_peaks_kw_and_more.py index 6e428cf8d..542a1b752 100644 --- a/reoptjl/migrations/0110_electricloadinputs_monthly_peaks_kw_and_more.py +++ b/reoptjl/migrations/0110_electricloadinputs_monthly_peaks_kw_and_more.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.25 on 2025-10-28 21:29 +# Generated by Django 4.2.26 on 2025-11-12 04:20 import django.contrib.postgres.fields import django.core.validators @@ -109,12 +109,12 @@ class Migration(migrations.Migration): ), migrations.AddField( model_name='electrictariffoutputs', - name='monthly_fixed_cost', + name='monthly_fixed_cost_series_before_tax', field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Year one fixed utility costs for each month.', size=None), ), migrations.AddField( model_name='electrictariffoutputs', - name='monthly_fixed_cost_bau', + name='monthly_fixed_cost_series_before_tax_bau', field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, help_text='Business as usual year one fixed utility costs for each month.', size=None), ), migrations.AddField( diff --git a/reoptjl/models.py b/reoptjl/models.py index 4bd8d63c8..4d134884e 100644 --- a/reoptjl/models.py +++ b/reoptjl/models.py @@ -2640,14 +2640,14 @@ class ElectricTariffOutputs(BaseModel, models.Model): primary_key=True ) - monthly_fixed_cost = ArrayField( + monthly_fixed_cost_series_before_tax = ArrayField( models.FloatField( null=True, blank=True ), default=list, help_text="Year one fixed utility costs for each month." ) - monthly_fixed_cost_bau = ArrayField( + monthly_fixed_cost_series_before_tax_bau = ArrayField( models.FloatField( null=True, blank=True ), From d154074ef780c1a2f5bec703e129bac11b31d061 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Tue, 11 Nov 2025 21:30:59 -0700 Subject: [PATCH 46/63] Replace 'anccr' with 'rates' to be more generic --- CHANGELOG.md | 1 + reoptjl/custom_table_config.py | 2 +- reoptjl/views.py | 10 +++++----- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4823a691..b7fdc14ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ Classify the change according to the following categories: - `ElectricLoad` input `monthly_peaks_kw`. Can be used to scale loads_kw or doe_reference loads to monthly peaks while maintaining monthly energy. - `ElectricTariff` outputs: `demand_rate_average_series`, `energy_cost_series_before_tax`, `energy_cost_series_before_tax_bau`, `energy_rate_average_series`, `energy_rate_series`, `energy_rate_tier_limits`, `facility_demand_monthly_rate_series`, `facility_demand_monthly_rate_tier_limits`, `monthly_demand_cost_series_before_tax`, `monthly_demand_cost_series_before_tax_bau`, `monthly_energy_cost_series_before_tax`, `monthly_energy_cost_series_before_tax_bau`, `monthly_facility_demand_cost_series_before_tax`, `monthly_facility_demand_cost_series_before_tax_bau`, `monthly_fixed_cost_series_before_tax`, `monthly_fixed_cost_before_tax_bau`, `monthly_tou_demand_cost_series_before_tax`, `monthly_tou_demand_cost_series_before_tax_bau`, `tou_demand_metrics`, `tou_demand_rate_series`, `tou_demand_rate_tier_limits`. - New endpoint `/get_load_metrics` for sending a timeseries `load_profile` and getting monthly and annual energy and peak loads. +- New custom table option `custom_table_rates` for endpoint `/job/generate_results_table` ## v3.16.2 diff --git a/reoptjl/custom_table_config.py b/reoptjl/custom_table_config.py index f28fb4922..a68522824 100644 --- a/reoptjl/custom_table_config.py +++ b/reoptjl/custom_table_config.py @@ -1341,7 +1341,7 @@ -custom_table_anccr = [ +custom_table_rates = [ ##################################################################################################### ################################ Need to get the RATE NAME to appear in header ################ ##################################################################################################### diff --git a/reoptjl/views.py b/reoptjl/views.py index 2b556f06f..941a195db 100644 --- a/reoptjl/views.py +++ b/reoptjl/views.py @@ -2026,8 +2026,8 @@ def get_combined_format(label, row_color, is_formula=False): column_width = 25 columns_to_hide = set() - # Check if using custom_table_anccr - if so, hide ALL BAU columns - is_anccr_table = (custom_table == custom_table_anccr) + # Check if using custom_table_rates - if so, hide ALL BAU columns + is_anccr_table = (custom_table == custom_table_rates) # Extract rate names for ANCCR table headers rate_names = [] @@ -2043,7 +2043,7 @@ def get_combined_format(label, row_color, is_formula=False): # Only proceed if there are BAU columns if bau_columns: if is_anccr_table: - # For custom_table_anccr, hide ALL BAU columns + # For custom_table_rates, hide ALL BAU columns columns_to_hide.update(bau_columns) else: identical_bau_columns = True # Assume all BAU columns are identical unless proven otherwise @@ -2079,11 +2079,11 @@ def get_combined_format(label, row_color, is_formula=False): # Write scenario headers worksheet.write('A1', 'Scenario', scenario_formats[0]) - # Track non-BAU column index for rate name mapping (only for custom_table_anccr) + # Track non-BAU column index for rate name mapping (only for custom_table_rates) non_bau_index = 0 for col_num, header in enumerate(df.columns): - # For custom_table_anccr, use rate names for non-BAU column headers + # For custom_table_rates, use rate names for non-BAU column headers if is_anccr_table and rate_names: if "BAU" not in header: # This is a non-BAU column - use rate name as header From d20186cceb562fde6231b2c9f927a3c43b73c8f3 Mon Sep 17 00:00:00 2001 From: Bill Becker <42586683+Bill-Becker@users.noreply.github.com> Date: Tue, 11 Nov 2025 21:39:19 -0700 Subject: [PATCH 47/63] Update reoptjl/views.py for syntax Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- reoptjl/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reoptjl/views.py b/reoptjl/views.py index 941a195db..25f19948e 100644 --- a/reoptjl/views.py +++ b/reoptjl/views.py @@ -661,7 +661,7 @@ def simulated_load(request): inputs[either] = data[either] either_check += 1 if either_check == 0: - return JsonResponse({"Error: Missing either of normalize_and_scale_load_profile_input or doe_reference_name."}, status=400) + return JsonResponse({"Error": "Missing either of normalize_and_scale_load_profile_input or doe_reference_name."}, status=400) elif either_check == 2: return JsonResponse({"Error: Both normalize_and_scale_load_profile_input and doe_reference_name were input; only input one of these."}, status=400) for field in required_post_fields: From 88f695cb7035a114014a4526dd3274ddf579750c Mon Sep 17 00:00:00 2001 From: Bill Becker <42586683+Bill-Becker@users.noreply.github.com> Date: Tue, 11 Nov 2025 21:39:59 -0700 Subject: [PATCH 48/63] Update reoptjl/views.py for Error syntax Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- reoptjl/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reoptjl/views.py b/reoptjl/views.py index 25f19948e..4174a95fe 100644 --- a/reoptjl/views.py +++ b/reoptjl/views.py @@ -663,7 +663,7 @@ def simulated_load(request): if either_check == 0: return JsonResponse({"Error": "Missing either of normalize_and_scale_load_profile_input or doe_reference_name."}, status=400) elif either_check == 2: - return JsonResponse({"Error: Both normalize_and_scale_load_profile_input and doe_reference_name were input; only input one of these."}, status=400) + return JsonResponse({"Error": "Both normalize_and_scale_load_profile_input and doe_reference_name were input; only input one of these."}, status=400) for field in required_post_fields: # TODO make year optional for doe_reference_name input inputs[field] = data[field] From dfb0b3d4f7c144cb757ea45d78b67709f69e073b Mon Sep 17 00:00:00 2001 From: Bill Becker <42586683+Bill-Becker@users.noreply.github.com> Date: Tue, 11 Nov 2025 21:40:30 -0700 Subject: [PATCH 49/63] Update reoptjl/views.py for Error syntax 3 Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- reoptjl/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reoptjl/views.py b/reoptjl/views.py index 4174a95fe..5af1441be 100644 --- a/reoptjl/views.py +++ b/reoptjl/views.py @@ -1701,7 +1701,7 @@ def get_load_metrics(request): return JsonResponse({"Error": str(e.args[0])}, status=400) except KeyError as e: - return JsonResponse({"Error. Missing": str(e.args[0])}, status=400) + return JsonResponse({"Error": str(e.args[0])}, status=400) except Exception: exc_type, exc_value, exc_traceback = sys.exc_info() From 6b0435aeb1fad0c0eef5fb47252399ee4fbced84 Mon Sep 17 00:00:00 2001 From: Bill Becker <42586683+Bill-Becker@users.noreply.github.com> Date: Tue, 11 Nov 2025 21:41:02 -0700 Subject: [PATCH 50/63] Make comment accurate with functionality Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- reoptjl/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reoptjl/views.py b/reoptjl/views.py index 5af1441be..a89fae554 100644 --- a/reoptjl/views.py +++ b/reoptjl/views.py @@ -1710,7 +1710,7 @@ def get_load_metrics(request): log.debug(debug_msg) return JsonResponse({"Error": "Unexpected error in get_load_metrics endpoint. Check log for more."}, status=500) -# Round all numeric values in the response to 0 decimal places +# Round all numeric values in the response to 2 decimal places def round_values(obj): if isinstance(obj, dict): return {key: round_values(value) for key, value in obj.items()} From 2192f7c8706b12016e28e817adad6ebef94704e3 Mon Sep 17 00:00:00 2001 From: Bill Becker <42586683+Bill-Becker@users.noreply.github.com> Date: Tue, 11 Nov 2025 21:42:52 -0700 Subject: [PATCH 51/63] Remove comment about asking Bhavesh Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- reoptjl/custom_table_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reoptjl/custom_table_config.py b/reoptjl/custom_table_config.py index a68522824..e86027e53 100644 --- a/reoptjl/custom_table_config.py +++ b/reoptjl/custom_table_config.py @@ -1798,7 +1798,7 @@ "scenario_value": lambda df: "" }, { - "label": "January Total Bill Cost ($)", # not sure if we have a monthly total bill cost output in REopt -- need to ask Bhavesh + "label": "January Total Bill Cost ($)", "key": "january_total_bill_cost", "bau_value": lambda df: "", "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.monthly_fixed_cost.0") + safe_get(df, "outputs.ElectricTariff.monthly_energy_cost_series_before_tax.0") + safe_get(df, "outputs.ElectricTariff.monthly_demand_cost_series_before_tax.0") From 9952837dd7519fa517b3cb56cc03de00103561b1 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Tue, 11 Nov 2025 21:50:49 -0700 Subject: [PATCH 52/63] Update rounding comments --- reoptjl/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/reoptjl/views.py b/reoptjl/views.py index a89fae554..b44076cf9 100644 --- a/reoptjl/views.py +++ b/reoptjl/views.py @@ -1710,14 +1710,14 @@ def get_load_metrics(request): log.debug(debug_msg) return JsonResponse({"Error": "Unexpected error in get_load_metrics endpoint. Check log for more."}, status=500) -# Round all numeric values in the response to 2 decimal places +# Round all numeric values in the response def round_values(obj): if isinstance(obj, dict): return {key: round_values(value) for key, value in obj.items()} elif isinstance(obj, list): return [round_values(item) for item in obj] elif isinstance(obj, float): - return round(obj, 2) # Convert to int to remove decimal point + return round(obj, 2) # Round to two decimal places elif isinstance(obj, int): return obj else: From 2c89f29fd3cc52b5dd463d0bb7eeff6877b5fe61 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Wed, 12 Nov 2025 08:50:42 -0700 Subject: [PATCH 53/63] Remove BAU columns from custom_table_rates --- reoptjl/custom_table_config.py | 12 ++-- reoptjl/views.py | 103 +++++++++++++++++++-------------- 2 files changed, 64 insertions(+), 51 deletions(-) diff --git a/reoptjl/custom_table_config.py b/reoptjl/custom_table_config.py index e86027e53..bea0a1075 100644 --- a/reoptjl/custom_table_config.py +++ b/reoptjl/custom_table_config.py @@ -1296,30 +1296,30 @@ # This formula calculates the percentage change of Placeholder2 using Placeholder1's BAU value as the reference. }, - # ANCCR Specific Calculations + # custom_table_rates Specific Calculations { "name": "Change in Year 1 Total Charges ($)", - "formula": lambda col, bau, headers: f'={col}{headers["Year 1 Total Bill Charges ($)"] + 2}-{"C"}{headers["Year 1 Total Bill Charges ($)"] + 2}' + "formula": lambda col, bau, headers: f'={col}{headers["Year 1 Total Bill Charges ($)"] + 2}-{"B"}{headers["Year 1 Total Bill Charges ($)"] + 2}' # This formula calculates the percentage change of scenario 2 vs. scenario 1. }, { "name": "Year 1 Fixed Charges Percent Change (%)", - "formula": lambda col, bau, headers: f'=({col}{headers["Year 1 Annual Fixed Charges ($)"] + 2}-{"C"}{headers["Year 1 Annual Fixed Charges ($)"] + 2})/{"C"}{headers["Year 1 Annual Fixed Charges ($)"] + 2}' + "formula": lambda col, bau, headers: f'=({col}{headers["Year 1 Annual Fixed Charges ($)"] + 2}-{"B"}{headers["Year 1 Annual Fixed Charges ($)"] + 2})/{"B"}{headers["Year 1 Annual Fixed Charges ($)"] + 2}' # This formula calculates the percentage change of scenario 2 vs. scenario 1. }, { "name": "Year 1 Energy Charges Percent Change (%)", - "formula": lambda col, bau, headers: f'=({col}{headers["Year 1 Annual Energy Charges ($)"] + 2}-{"C"}{headers["Year 1 Annual Energy Charges ($)"] + 2})/{"C"}{headers["Year 1 Annual Energy Charges ($)"] + 2}' + "formula": lambda col, bau, headers: f'=({col}{headers["Year 1 Annual Energy Charges ($)"] + 2}-{"B"}{headers["Year 1 Annual Energy Charges ($)"] + 2})/{"B"}{headers["Year 1 Annual Energy Charges ($)"] + 2}' # This formula calculates the percentage change of scenario 2 vs. scenario 1. }, { "name": "Year 1 Demand Charges Percent Change (%)", - "formula": lambda col, bau, headers: f'=({col}{headers["Year 1 Annual Demand Charges ($)"] + 2}-{"C"}{headers["Year 1 Annual Demand Charges ($)"] + 2})/{"C"}{headers["Year 1 Annual Demand Charges ($)"] + 2}' + "formula": lambda col, bau, headers: f'=({col}{headers["Year 1 Annual Demand Charges ($)"] + 2}-{"B"}{headers["Year 1 Annual Demand Charges ($)"] + 2})/{"B"}{headers["Year 1 Annual Demand Charges ($)"] + 2}' # This formula calculates the percentage change of scenario 2 vs. scenario 1. }, { "name": "Year 1 Total Bill Charges Percent Change (%)", - "formula": lambda col, bau, headers: f'=({col}{headers["Year 1 Annual Total Bill Charges ($)"] + 2}-{"C"}{headers["Year 1 Annual Total Bill Charges ($)"] + 2})/{"C"}{headers["Year 1 Annual Total Bill Charges ($)"] + 2}' + "formula": lambda col, bau, headers: f'=({col}{headers["Year 1 Annual Total Bill Charges ($)"] + 2}-{"B"}{headers["Year 1 Annual Total Bill Charges ($)"] + 2})/{"B"}{headers["Year 1 Annual Total Bill Charges ($)"] + 2}' # This formula calculates the percentage change of scenario 2 vs. scenario 1. }, { diff --git a/reoptjl/views.py b/reoptjl/views.py index b44076cf9..534de046e 100644 --- a/reoptjl/views.py +++ b/reoptjl/views.py @@ -1877,20 +1877,33 @@ def get_bau_values(scenarios: List[Dict[str, Any]], config: List[Dict[str, Any]] def process_scenarios(scenarios: List[Dict[str, Any]], reopt_data_config: List[Dict[str, Any]]) -> pd.DataFrame: try: - bau_values_per_scenario = get_bau_values(scenarios, reopt_data_config) - all_dataframes = [] + # Check if we're using custom_table_rates - if so, skip BAU data entirely + is_rates_table = (reopt_data_config == custom_table_rates) + + if is_rates_table: + # For custom_table_rates, only generate scenario data (no BAU) + all_dataframes = [] + for idx, scenario in enumerate(scenarios): + run_uuid = scenario['run_uuid'] + df_result = generate_reopt_dataframe(scenario['full_data'], run_uuid, reopt_data_config) + df_result["Scenario"] = run_uuid + all_dataframes.append(df_result) + else: + # For all other tables, generate both BAU and scenario data + bau_values_per_scenario = get_bau_values(scenarios, reopt_data_config) + all_dataframes = [] - for idx, scenario in enumerate(scenarios): - run_uuid = scenario['run_uuid'] - df_result = generate_reopt_dataframe(scenario['full_data'], run_uuid, reopt_data_config) - df_result["Scenario"] = run_uuid + for idx, scenario in enumerate(scenarios): + run_uuid = scenario['run_uuid'] + df_result = generate_reopt_dataframe(scenario['full_data'], run_uuid, reopt_data_config) + df_result["Scenario"] = run_uuid - bau_data = {key: [value] for key, value in bau_values_per_scenario[run_uuid].items()} - bau_data["Scenario"] = [f"BAU {idx + 1}"] - df_bau = pd.DataFrame(bau_data) + bau_data = {key: [value] for key, value in bau_values_per_scenario[run_uuid].items()} + bau_data["Scenario"] = [f"BAU {idx + 1}"] + df_bau = pd.DataFrame(bau_data) - # Add both BAU and result dataframes to list - all_dataframes.extend([df_bau, df_result]) + # Add both BAU and result dataframes to list + all_dataframes.extend([df_bau, df_result]) # Concatenate all dataframes at once combined_df = pd.concat(all_dataframes, axis=0, ignore_index=True) @@ -2026,26 +2039,24 @@ def get_combined_format(label, row_color, is_formula=False): column_width = 25 columns_to_hide = set() - # Check if using custom_table_rates - if so, hide ALL BAU columns - is_anccr_table = (custom_table == custom_table_rates) + # Check if using custom_table_rates + is_rates_table = (custom_table == custom_table_rates) - # Extract rate names for ANCCR table headers + # Extract rate names for rates table headers rate_names = [] - if is_anccr_table and scenarios: + if is_rates_table and scenarios: for scenario in scenarios: rate_name = scenario.get('full_data', {}).get('inputs', {}).get('ElectricTariff', {}).get('urdb_metadata', {}).get('rate_name', None) if rate_name: rate_names.append(rate_name) - # Loop through BAU columns and check if all numerical values are identical across all BAU columns - bau_columns = [i for i, header in enumerate(df.columns) if "BAU" in header] - - # Only proceed if there are BAU columns - if bau_columns: - if is_anccr_table: - # For custom_table_rates, hide ALL BAU columns - columns_to_hide.update(bau_columns) - else: + # For non-rates tables, handle BAU column logic + if not is_rates_table: + # Loop through BAU columns and check if all numerical values are identical across all BAU columns + bau_columns = [i for i, header in enumerate(df.columns) if "BAU" in header] + + # Only proceed if there are BAU columns + if bau_columns: identical_bau_columns = True # Assume all BAU columns are identical unless proven otherwise # Loop through each row and check the values across BAU columns @@ -2067,13 +2078,13 @@ def get_combined_format(label, row_color, is_formula=False): for col_num in bau_columns[1:]: columns_to_hide.add(col_num) - # Now set the column properties for hiding BAU columns and leaving others unchanged + # Now set the column properties - no BAU columns to hide for rates table for col_num, header in enumerate(df.columns): - if "BAU" in header and col_num in columns_to_hide: - # Hide the BAU columns that have been marked + if not is_rates_table and "BAU" in header and col_num in columns_to_hide: + # Hide the BAU columns that have been marked (only for non-rates tables) worksheet.set_column(col_num + 1, col_num + 1, column_width, None, {'hidden': True}) else: - # Set the normal column width for non-hidden columns + # Set the normal column width for all columns (rates table has no BAU columns to hide) worksheet.set_column(col_num + 1, col_num + 1, column_width) # Write scenario headers @@ -2083,18 +2094,14 @@ def get_combined_format(label, row_color, is_formula=False): non_bau_index = 0 for col_num, header in enumerate(df.columns): - # For custom_table_rates, use rate names for non-BAU column headers - if is_anccr_table and rate_names: - if "BAU" not in header: - # This is a non-BAU column - use rate name as header - if non_bau_index < len(rate_names): - header_text = rate_names[non_bau_index] - else: - header_text = header - non_bau_index += 1 + # For custom_table_rates, use rate names for column headers + if is_rates_table and rate_names: + # For rates table, all columns are scenario columns - use rate names + if non_bau_index < len(rate_names): + header_text = rate_names[non_bau_index] else: - # This is a BAU column - keep original header (will be hidden) header_text = header + non_bau_index += 1 else: header_text = header @@ -2168,15 +2175,21 @@ def get_bau_column(col): missing_entries = [] for col in range(2, len(df.columns) + 2): - # Skip BAU columns (BAU columns should not have formulas) - if col % 2 == 0: - continue # Skip the BAU column + # For rates table, apply formulas to all columns since there are no BAU columns + # For other tables, skip BAU columns (every other column) + if not is_rates_table and col % 2 == 0: + continue # Skip the BAU column for non-rates tables col_letter = colnum_string(col) - bau_col = get_bau_column(col) # Get the corresponding BAU column - bau_col_letter = colnum_string(bau_col) # Convert the column number to letter for Excel reference - - bau_cells = {cell_name: f'{bau_col_letter}{headers[header] + 2}' for cell_name, header in bau_cells_config.items() if header in headers} + + # For non-rates tables, get the corresponding BAU column + if not is_rates_table: + bau_col = get_bau_column(col) # Get the corresponding BAU column + bau_col_letter = colnum_string(bau_col) # Convert the column number to letter for Excel reference + bau_cells = {cell_name: f'{bau_col_letter}{headers[header] + 2}' for cell_name, header in bau_cells_config.items() if header in headers} + else: + # For rates table, no BAU cells since we don't have BAU columns + bau_cells = {} for calc in relevant_calculations: try: From 2875a7e0f7e66c174646a66f9a464875968a8a7c Mon Sep 17 00:00:00 2001 From: bill-becker Date: Wed, 12 Nov 2025 09:17:23 -0700 Subject: [PATCH 54/63] Wrap text for all cells; do not include instructions tab for custom_table_rates --- reoptjl/views.py | 244 ++++++++++++++++++++++++----------------------- 1 file changed, 126 insertions(+), 118 deletions(-) diff --git a/reoptjl/views.py b/reoptjl/views.py index 534de046e..b13aa7190 100644 --- a/reoptjl/views.py +++ b/reoptjl/views.py @@ -1958,28 +1958,32 @@ def generate_excel_workbook(df: pd.DataFrame, custom_table: List[Dict[str, Any]] # Add the 'Results Table' worksheet worksheet = workbook.add_worksheet('Results Table') - # Add the 'Instructions' worksheet - instructions_worksheet = workbook.add_worksheet('Instructions') + # Check if using custom_table_rates + is_rates_table = (custom_table == custom_table_rates) + + # Add the 'Instructions' worksheet only for non-rates tables + if not is_rates_table: + instructions_worksheet = workbook.add_worksheet('Instructions') # Scenario header formatting with colors scenario_colors = ['#0B5E90', '#00A4E4','#f46d43','#fdae61', '#66c2a5', '#d53e4f', '#3288bd'] - scenario_formats = [workbook.add_format({'bold': True, 'bg_color': color, 'border': 1, 'align': 'center', 'font_color': 'white', 'font_size': 12}) for color in scenario_colors] + scenario_formats = [workbook.add_format({'bold': True, 'bg_color': color, 'border': 1, 'align': 'center', 'font_color': 'white', 'font_size': 12, 'text_wrap': True}) for color in scenario_colors] # Row alternating colors row_colors = ['#d1d5d8', '#fafbfb'] # Base formats for errors, percentages, and currency values - error_format = workbook.add_format({'bg_color': '#FFC7CE', 'align': 'center', 'valign': 'center', 'border': 1, 'font_color': 'white', 'bold': True, 'font_size': 10}) - base_percent_format = {'num_format': '0.0%', 'align': 'center', 'valign': 'center', 'border': 1, 'font_size': 10} - base_currency_format = {'num_format': '$#,##0', 'align': 'center', 'valign': 'center', 'border': 1, 'font_size': 10} + error_format = workbook.add_format({'bg_color': '#FFC7CE', 'align': 'center', 'valign': 'center', 'border': 1, 'font_color': 'white', 'bold': True, 'font_size': 10, 'text_wrap': True}) + base_percent_format = {'num_format': '0.0%', 'align': 'center', 'valign': 'center', 'border': 1, 'font_size': 10, 'text_wrap': True} + base_currency_format = {'num_format': '$#,##0', 'align': 'center', 'valign': 'center', 'border': 1, 'font_size': 10, 'text_wrap': True} # Formula formats using dark blue background formula_color = '#F8F8FF' - formula_format = workbook.add_format({'num_format': '#,##0','bg_color': '#0B5E90', 'align': 'center', 'valign': 'center', 'border': 1, 'font_color': formula_color, 'font_size': 10, 'italic': True}) - formula_payback_format = workbook.add_format({'num_format': '0.0','bg_color': '#0B5E90', 'align': 'center', 'valign': 'center', 'border': 1, 'font_color': formula_color, 'font_size': 10, 'italic': True}) - formula_percent_format = workbook.add_format({'bg_color': '#0B5E90', 'num_format': '0.0%', 'align': 'center', 'valign': 'center', 'border': 1, 'font_color': formula_color, 'font_size': 10, 'italic': True}) - formula_currency_format = workbook.add_format({'bg_color': '#0B5E90', 'num_format': '$#,##0', 'align': 'center', 'valign': 'center', 'border': 1, 'font_color': formula_color, 'font_size': 10, 'italic': True}) + formula_format = workbook.add_format({'num_format': '#,##0','bg_color': '#0B5E90', 'align': 'center', 'valign': 'center', 'border': 1, 'font_color': formula_color, 'font_size': 10, 'italic': True, 'text_wrap': True}) + formula_payback_format = workbook.add_format({'num_format': '0.0','bg_color': '#0B5E90', 'align': 'center', 'valign': 'center', 'border': 1, 'font_color': formula_color, 'font_size': 10, 'italic': True, 'text_wrap': True}) + formula_percent_format = workbook.add_format({'bg_color': '#0B5E90', 'num_format': '0.0%', 'align': 'center', 'valign': 'center', 'border': 1, 'font_color': formula_color, 'font_size': 10, 'italic': True, 'text_wrap': True}) + formula_currency_format = workbook.add_format({'bg_color': '#0B5E90', 'num_format': '$#,##0', 'align': 'center', 'valign': 'center', 'border': 1, 'font_color': formula_color, 'font_size': 10, 'italic': True, 'text_wrap': True}) # Message format for formula cells (blue background with white text) formula_message_format = workbook.add_format({ @@ -1990,7 +1994,8 @@ def generate_excel_workbook(df: pd.DataFrame, custom_table: List[Dict[str, Any]] 'border': 1, 'bold': True, 'font_size': 12, - 'italic': True + 'italic': True, + 'text_wrap': True }) # Message format for input cells (yellow background) @@ -2000,13 +2005,14 @@ def generate_excel_workbook(df: pd.DataFrame, custom_table: List[Dict[str, Any]] 'valign': 'center', 'border': 1, 'bold': True, - 'font_size': 12 + 'font_size': 12, + 'text_wrap': True }) # Separator format for rows that act as visual dividers - separator_format = workbook.add_format({'bg_color': '#5D6A71', 'bold': True, 'border': 1,'font_size': 11,'font_color': 'white'}) + separator_format = workbook.add_format({'bg_color': '#5D6A71', 'bold': True, 'border': 1,'font_size': 11,'font_color': 'white', 'text_wrap': True}) - input_cell_format = workbook.add_format({'bg_color': '#FFFC79', 'align': 'center', 'valign': 'center', 'border': 1, 'font_size': 10}) + input_cell_format = workbook.add_format({'bg_color': '#FFFC79', 'align': 'center', 'valign': 'center', 'border': 1, 'font_size': 10, 'text_wrap': True}) # Combine row color with cell format, excluding formulas def get_combined_format(label, row_color, is_formula=False): @@ -2018,9 +2024,9 @@ def get_combined_format(label, row_color, is_formula=False): elif 'yrs' in label: return formula_payback_format return formula_format - base_data_format = {'num_format': '#,##0','bg_color': row_color, 'align': 'center', 'valign': 'center', 'border': 1, 'font_size': 10} - payback_data_format = {'num_format': '0.0','bg_color': row_color, 'align': 'center', 'valign': 'center', 'border': 1, 'font_size': 10} - blue_text_format = {'font_color': 'blue', 'bg_color': row_color, 'align': 'center', 'valign': 'center', 'border': 1, 'font_size': 10} + base_data_format = {'num_format': '#,##0','bg_color': row_color, 'align': 'center', 'valign': 'center', 'border': 1, 'font_size': 10, 'text_wrap': True} + payback_data_format = {'num_format': '0.0','bg_color': row_color, 'align': 'center', 'valign': 'center', 'border': 1, 'font_size': 10, 'text_wrap': True} + blue_text_format = {'font_color': 'blue', 'bg_color': row_color, 'align': 'center', 'valign': 'center', 'border': 1, 'font_size': 10, 'text_wrap': True} if label: if '$' in label: return workbook.add_format({**base_currency_format, 'bg_color': row_color}) @@ -2121,7 +2127,7 @@ def get_combined_format(label, row_color, is_formula=False): row_color = row_colors[(row_num + row_offset) % 2] # Alternating row colors # Write the label in the first column - worksheet.write(row_num + 1 + row_offset, 0, entry['label'], workbook.add_format({'bg_color': row_color, 'border': 1})) + worksheet.write(row_num + 1 + row_offset, 0, entry['label'], workbook.add_format({'bg_color': row_color, 'border': 1, 'text_wrap': True})) # Write the data for each column variable = entry['label'] # Assuming df index or columns match the label @@ -2224,115 +2230,117 @@ def get_bau_column(col): if missing_entries: print(f"missing_entries in the input table: {', '.join(set(missing_entries))}. Please update the configuration if necessary.") - # Formats for the instructions sheet - title_format = workbook.add_format({ - 'bold': True, 'font_size': 18, 'align': 'left', 'valign': 'top' - }) - subtitle_format = workbook.add_format({ - 'bold': True, 'font_size': 14, 'align': 'left', 'valign': 'top' - }) - subsubtitle_format = workbook.add_format({ - 'italic': True, 'font_size': 12, 'align': 'left', 'valign': 'top', 'text_wrap': True - }) - text_format = workbook.add_format({ - 'font_size': 12, 'align': 'left', 'valign': 'top', 'text_wrap': True - }) - bullet_format = workbook.add_format({ - 'font_size': 12, 'align': 'left', 'valign': 'top', 'text_wrap': True, 'indent': 1 - }) + # Add Instructions worksheet content only for non-rates tables + if not is_rates_table: + # Formats for the instructions sheet + title_format = workbook.add_format({ + 'bold': True, 'font_size': 18, 'align': 'left', 'valign': 'top', 'text_wrap': True + }) + subtitle_format = workbook.add_format({ + 'bold': True, 'font_size': 14, 'align': 'left', 'valign': 'top', 'text_wrap': True + }) + subsubtitle_format = workbook.add_format({ + 'italic': True, 'font_size': 12, 'align': 'left', 'valign': 'top', 'text_wrap': True + }) + text_format = workbook.add_format({ + 'font_size': 12, 'align': 'left', 'valign': 'top', 'text_wrap': True + }) + bullet_format = workbook.add_format({ + 'font_size': 12, 'align': 'left', 'valign': 'top', 'text_wrap': True, 'indent': 1 + }) + + # Set column width and default row height + instructions_worksheet.set_column(0, 0, 100) + instructions_worksheet.set_default_row(15) + + # Start writing instructions + row = 0 + instructions_worksheet.write(row, 0, "Instructions for Using the REopt Results Table Workbook", title_format) + row += 2 + + # General Introduction + general_instructions = ( + "Welcome to the REopt Results Table Workbook !\n\n" + "This workbook contains all of the results of your selected REopt analysis scenarios. " + "Please read the following instructions carefully to understand how to use this workbook effectively." + ) + instructions_worksheet.write(row, 0, general_instructions, text_format) + row += 3 - # Set column width and default row height - instructions_worksheet.set_column(0, 0, 100) - instructions_worksheet.set_default_row(15) + # Using the 'Results Table' Sheet with formula format + instructions_worksheet.write(row, 0, "Using the 'Results Table' Sheet", subtitle_format) + row += 1 - # Start writing instructions - row = 0 - instructions_worksheet.write(row, 0, "Instructions for Using the REopt Results Table Workbook", title_format) - row += 2 + custom_table_instructions = ( + "The 'Results Table' sheet displays the scenario results of your REopt analysis in a structured format. " + "Here's how to use it:" + ) + instructions_worksheet.write(row, 0, custom_table_instructions, subsubtitle_format) + row += 2 + + steps = [ + "1. Review the Results: Browse through the table to understand the system capacities, financial metrics, and energy production details.", + "2. Identify Editable Fields: Look for yellow cells in the 'Playground' section where you can input additional incentives or costs.", + "3. Avoid Editing Formulas: Do not edit cells with blue background and white text, as they contain important formulas.", + "4. Interpreting BAU and Optimal Scenarios: 'BAU' stands for 'Business as Usual' and represents the baseline scenario without any new investments. 'Optimal' scenarios show the results with optimized investments.", + "5. Hidden BAU Columns: If all scenarios are for a single site, identical BAU columns may be hidden except for the first one. For multiple sites where financials and energy consumption differ, all BAU columns will be visible." + ] + for step in steps: + instructions_worksheet.write(row, 0, step, bullet_format) + row += 1 + row += 2 - # General Introduction - general_instructions = ( - "Welcome to the REopt Results Table Workbook !\n\n" - "This workbook contains all of the results of your selected REopt analysis scenarios. " - "Please read the following instructions carefully to understand how to use this workbook effectively." - ) - instructions_worksheet.write(row, 0, general_instructions, text_format) - row += 3 + # Notes for the Playground Section + instructions_worksheet.write(row, 0, "Notes for the economic 'Playground' Section", subtitle_format) + row += 1 - # Using the 'Results Table' Sheet with formula format - instructions_worksheet.write(row, 0, "Using the 'Results Table' Sheet", subtitle_format) - row += 1 + playground_notes = ( + "The economic 'Playground' section allows you to explore the effects of additional incentives and costs and on your project's financial metrics, in particular the simple payback period." + ) + instructions_worksheet.write(row, 0, playground_notes, subsubtitle_format) + row += 2 + + playground_items = [ + "- Total Capital Cost Before Incentives ($): For reference, to view what the payback would be without incentives.", + "- Total Capital Cost After Incentives Without MACRS ($): Represents the capital cost after incentives, but excludes MACRS depreciation benefits.", + "- Total Capital Cost After Non-Discounted Incentives ($): Same as above, but includes non-discounted MACRS depreciation, which provides tax benefits over the first 5-7 years.", + "- Additional Upfront Incentive ($): Input any additional grants or incentives (e.g., state or local grants).", + "- Additional Upfront Cost ($): Input any extra upfront costs (e.g., interconnection upgrades, microgrid components).", + "- Additional Yearly Cost Savings ($/yr): Input any ongoing yearly savings (e.g., avoided cost of outages, improved productivity, product sales with ESG designation).", + "- Additional Yearly Cost ($/yr): Input any additional yearly costs (e.g., microgrid operation and maintenance).", + "- Modified Total Year One Savings, After Tax ($): Updated total yearly savings to include any user-input additional yearly savings and cost.", + "- Modified Total Capital Cost ($): Updated total cost to include any user-input additional incentive and cost.", + "- Modified Simple Payback Period Without Incentives (yrs): Uses Total Capital Cost Before Incentives ($) to calculate payback, for reference." + "- Modified Simple Payback Period (yrs): Calculates a simple payback period with Modified Total Year One Savings, After Tax ($) and Modified Total Capital Cost ($)." + ] + for item in playground_items: + instructions_worksheet.write(row, 0, item, bullet_format) + row += 1 + row += 1 - custom_table_instructions = ( - "The 'Results Table' sheet displays the scenario results of your REopt analysis in a structured format. " - "Here's how to use it:" - ) - instructions_worksheet.write(row, 0, custom_table_instructions, subsubtitle_format) - row += 2 - - steps = [ - "1. Review the Results: Browse through the table to understand the system capacities, financial metrics, and energy production details.", - "2. Identify Editable Fields: Look for yellow cells in the 'Playground' section where you can input additional incentives or costs.", - "3. Avoid Editing Formulas: Do not edit cells with blue background and white text, as they contain important formulas.", - "4. Interpreting BAU and Optimal Scenarios: 'BAU' stands for 'Business as Usual' and represents the baseline scenario without any new investments. 'Optimal' scenarios show the results with optimized investments.", - "5. Hidden BAU Columns: If all scenarios are for a single site, identical BAU columns may be hidden except for the first one. For multiple sites where financials and energy consumption differ, all BAU columns will be visible." - ] - for step in steps: - instructions_worksheet.write(row, 0, step, bullet_format) + # Unaddressable Heating Load and Emissions + instructions_worksheet.write(row, 0, "Notes for the emissions 'Playground' Section", subtitle_format) + row += 1 + + instructions_worksheet.write(row, 0, "The emissions 'Playground' section allows you to explore the effects of unaddressable fuel emissions on the total emissions reduction %.", subsubtitle_format) row += 1 - row += 2 - # Notes for the Playground Section - instructions_worksheet.write(row, 0, "Notes for the economic 'Playground' Section", subtitle_format) - row += 1 + unaddressable_notes = ( + "In scenarios where there is an unaddressable fuel load (e.g. heating demand that cannot be served by the technologies analyzed), " + "the associated fuel consumption and emissions are not accounted for in the standard REopt outputs.\n\n" + "The 'Unaddressable Fuel CO₂ Emissions' row in the 'Playground' section includes these emissions, providing a more comprehensive view of your site's total emissions. " + "Including unaddressable emissions results in a lower percentage reduction because the total emissions baseline is larger." + ) + instructions_worksheet.write(row, 0, unaddressable_notes, text_format) + row += 3 - playground_notes = ( - "The economic 'Playground' section allows you to explore the effects of additional incentives and costs and on your project's financial metrics, in particular the simple payback period." - ) - instructions_worksheet.write(row, 0, playground_notes, subsubtitle_format) - row += 2 - - playground_items = [ - "- Total Capital Cost Before Incentives ($): For reference, to view what the payback would be without incentives.", - "- Total Capital Cost After Incentives Without MACRS ($): Represents the capital cost after incentives, but excludes MACRS depreciation benefits.", - "- Total Capital Cost After Non-Discounted Incentives ($): Same as above, but includes non-discounted MACRS depreciation, which provides tax benefits over the first 5-7 years.", - "- Additional Upfront Incentive ($): Input any additional grants or incentives (e.g., state or local grants).", - "- Additional Upfront Cost ($): Input any extra upfront costs (e.g., interconnection upgrades, microgrid components).", - "- Additional Yearly Cost Savings ($/yr): Input any ongoing yearly savings (e.g., avoided cost of outages, improved productivity, product sales with ESG designation).", - "- Additional Yearly Cost ($/yr): Input any additional yearly costs (e.g., microgrid operation and maintenance).", - "- Modified Total Year One Savings, After Tax ($): Updated total yearly savings to include any user-input additional yearly savings and cost.", - "- Modified Total Capital Cost ($): Updated total cost to include any user-input additional incentive and cost.", - "- Modified Simple Payback Period Without Incentives (yrs): Uses Total Capital Cost Before Incentives ($) to calculate payback, for reference." - "- Modified Simple Payback Period (yrs): Calculates a simple payback period with Modified Total Year One Savings, After Tax ($) and Modified Total Capital Cost ($)." - ] - for item in playground_items: - instructions_worksheet.write(row, 0, item, bullet_format) + # Final Note and Contact Info + instructions_worksheet.write(row, 0, "Thank you for using the REopt Results Table Workbook!", subtitle_format) row += 1 - row += 1 - - # Unaddressable Heating Load and Emissions - instructions_worksheet.write(row, 0, "Notes for the emissions 'Playground' Section", subtitle_format) - row += 1 - - instructions_worksheet.write(row, 0, "The emissions 'Playground' section allows you to explore the effects of unaddressable fuel emissions on the total emissions reduction %.", subsubtitle_format) - row += 1 - - unaddressable_notes = ( - "In scenarios where there is an unaddressable fuel load (e.g. heating demand that cannot be served by the technologies analyzed), " - "the associated fuel consumption and emissions are not accounted for in the standard REopt outputs.\n\n" - "The 'Unaddressable Fuel CO₂ Emissions' row in the 'Playground' section includes these emissions, providing a more comprehensive view of your site's total emissions. " - "Including unaddressable emissions results in a lower percentage reduction because the total emissions baseline is larger." - ) - instructions_worksheet.write(row, 0, unaddressable_notes, text_format) - row += 3 - - # Final Note and Contact Info - instructions_worksheet.write(row, 0, "Thank you for using the REopt Results Table Workbook!", subtitle_format) - row += 1 - contact_info = "For support or feedback, please contact the REopt team at reopt@nrel.gov." - instructions_worksheet.write(row, 0, contact_info, subtitle_format) - # Freeze panes to keep the title visible - instructions_worksheet.freeze_panes(1, 0) + contact_info = "For support or feedback, please contact the REopt team at reopt@nrel.gov." + instructions_worksheet.write(row, 0, contact_info, subtitle_format) + # Freeze panes to keep the title visible + instructions_worksheet.freeze_panes(1, 0) # Close the workbook after all sheets are written workbook.close() From 1466582fc831402c53fabcdbf0befc79d12cf8ef Mon Sep 17 00:00:00 2001 From: Grant Ellwood <120485824+gellwood@users.noreply.github.com> Date: Thu, 13 Nov 2025 09:32:51 -0700 Subject: [PATCH 55/63] Updated comments for BAU columns for rate table Clarifies comments and logic for hiding BAU columns in non-rates tables within the Excel workbook generation. No functional changes, only improved code readability. --- reoptjl/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/reoptjl/views.py b/reoptjl/views.py index b13aa7190..09b060b93 100644 --- a/reoptjl/views.py +++ b/reoptjl/views.py @@ -2084,10 +2084,10 @@ def get_combined_format(label, row_color, is_formula=False): for col_num in bau_columns[1:]: columns_to_hide.add(col_num) - # Now set the column properties - no BAU columns to hide for rates table + # Now set the column properties - remove BAU columns for rates table for col_num, header in enumerate(df.columns): if not is_rates_table and "BAU" in header and col_num in columns_to_hide: - # Hide the BAU columns that have been marked (only for non-rates tables) + # Remove the BAU columns that have been marked (only for non-rates tables) worksheet.set_column(col_num + 1, col_num + 1, column_width, None, {'hidden': True}) else: # Set the normal column width for all columns (rates table has no BAU columns to hide) From bcfd5c24ac43bad5c3785384e1e29b56cfa84a63 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Mon, 17 Nov 2025 20:51:35 -0700 Subject: [PATCH 56/63] Convert type of monthly_peaks_load to avoid Any[] parse --- julia_src/http.jl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/julia_src/http.jl b/julia_src/http.jl index d2602379e..be47a6689 100644 --- a/julia_src/http.jl +++ b/julia_src/http.jl @@ -90,6 +90,10 @@ function reopt(req::HTTP.Request) model_inputs = nothing # Catch handled/unhandled exceptions in data pre-processing, JuMP setup try + # Convert monthly_peaks_kw to Vector{Float64} if present in ElectricLoad + if haskey(d["ElectricLoad"], "monthly_peaks_kw") + d["ElectricLoad"]["monthly_peaks_kw"] = convert(Vector{Float64}, d["ElectricLoad"]["monthly_peaks_kw"]) + end model_inputs = reoptjl.REoptInputs(d) @info "Successfully processed REopt inputs." catch e From 812e15f800f09cd975dd69d00e1825fc3420694b Mon Sep 17 00:00:00 2001 From: bill-becker Date: Tue, 18 Nov 2025 15:50:57 -0700 Subject: [PATCH 57/63] Update REopt.jl to v0.56.1 --- julia_src/Manifest.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/julia_src/Manifest.toml b/julia_src/Manifest.toml index e06ec8559..1b02d6e14 100644 --- a/julia_src/Manifest.toml +++ b/julia_src/Manifest.toml @@ -948,9 +948,9 @@ version = "1.11.0" [[deps.REopt]] deps = ["ArchGDAL", "CSV", "CoolProp", "DataFrames", "Dates", "DelimitedFiles", "HTTP", "JLD", "JSON", "JuMP", "LinDistFlow", "LinearAlgebra", "Logging", "MathOptInterface", "Requires", "Roots", "Statistics", "TestEnv"] -git-tree-sha1 = "476b384ce2b9b010dea363a5b79bfb4838def222" +git-tree-sha1 = "36b5e8592f1d5acf571d6e3e293534bd69529622" uuid = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6" -version = "0.56.0" +version = "0.56.1" [[deps.Random]] deps = ["SHA"] From 2e8a04304c4ae9fa72c78f4cc5e06d235d522285 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Tue, 18 Nov 2025 15:51:45 -0700 Subject: [PATCH 58/63] Revert "Convert type of monthly_peaks_load to avoid Any[] parse" This reverts commit bcfd5c24ac43bad5c3785384e1e29b56cfa84a63. --- julia_src/http.jl | 4 ---- 1 file changed, 4 deletions(-) diff --git a/julia_src/http.jl b/julia_src/http.jl index be47a6689..d2602379e 100644 --- a/julia_src/http.jl +++ b/julia_src/http.jl @@ -90,10 +90,6 @@ function reopt(req::HTTP.Request) model_inputs = nothing # Catch handled/unhandled exceptions in data pre-processing, JuMP setup try - # Convert monthly_peaks_kw to Vector{Float64} if present in ElectricLoad - if haskey(d["ElectricLoad"], "monthly_peaks_kw") - d["ElectricLoad"]["monthly_peaks_kw"] = convert(Vector{Float64}, d["ElectricLoad"]["monthly_peaks_kw"]) - end model_inputs = reoptjl.REoptInputs(d) @info "Successfully processed REopt inputs." catch e From ee1d57b266f9918ca6d34f59509c3d08512a10a6 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Tue, 18 Nov 2025 16:31:29 -0700 Subject: [PATCH 59/63] Reapply "Convert type of monthly_peaks_load to avoid Any[] parse" This reverts commit 2e8a04304c4ae9fa72c78f4cc5e06d235d522285. --- julia_src/http.jl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/julia_src/http.jl b/julia_src/http.jl index d2602379e..be47a6689 100644 --- a/julia_src/http.jl +++ b/julia_src/http.jl @@ -90,6 +90,10 @@ function reopt(req::HTTP.Request) model_inputs = nothing # Catch handled/unhandled exceptions in data pre-processing, JuMP setup try + # Convert monthly_peaks_kw to Vector{Float64} if present in ElectricLoad + if haskey(d["ElectricLoad"], "monthly_peaks_kw") + d["ElectricLoad"]["monthly_peaks_kw"] = convert(Vector{Float64}, d["ElectricLoad"]["monthly_peaks_kw"]) + end model_inputs = reoptjl.REoptInputs(d) @info "Successfully processed REopt inputs." catch e From 76a114c26dce83adc98ec9f444b4c23e9b4be522 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Tue, 18 Nov 2025 21:07:48 -0700 Subject: [PATCH 60/63] Bump REopt.jl version to 0.56.2 --- julia_src/Manifest.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/julia_src/Manifest.toml b/julia_src/Manifest.toml index 1b02d6e14..b0930d9ab 100644 --- a/julia_src/Manifest.toml +++ b/julia_src/Manifest.toml @@ -948,9 +948,9 @@ version = "1.11.0" [[deps.REopt]] deps = ["ArchGDAL", "CSV", "CoolProp", "DataFrames", "Dates", "DelimitedFiles", "HTTP", "JLD", "JSON", "JuMP", "LinDistFlow", "LinearAlgebra", "Logging", "MathOptInterface", "Requires", "Roots", "Statistics", "TestEnv"] -git-tree-sha1 = "36b5e8592f1d5acf571d6e3e293534bd69529622" +git-tree-sha1 = "00bb39c8f932a3320960f01adc139229c24e12b7" uuid = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6" -version = "0.56.1" +version = "0.56.2" [[deps.Random]] deps = ["SHA"] From c8ec874923172dae6d2ab6b38685a37db861dc55 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Tue, 18 Nov 2025 21:11:05 -0700 Subject: [PATCH 61/63] Finalize CHANGELOG for new version --- CHANGELOG.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7fdc14ed..13cade981 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,13 +26,15 @@ Classify the change according to the following categories: ##### Removed ### Patches -## anccr +## v3.17.0 ### Minor Updates ##### Added - `ElectricLoad` input `monthly_peaks_kw`. Can be used to scale loads_kw or doe_reference loads to monthly peaks while maintaining monthly energy. - `ElectricTariff` outputs: `demand_rate_average_series`, `energy_cost_series_before_tax`, `energy_cost_series_before_tax_bau`, `energy_rate_average_series`, `energy_rate_series`, `energy_rate_tier_limits`, `facility_demand_monthly_rate_series`, `facility_demand_monthly_rate_tier_limits`, `monthly_demand_cost_series_before_tax`, `monthly_demand_cost_series_before_tax_bau`, `monthly_energy_cost_series_before_tax`, `monthly_energy_cost_series_before_tax_bau`, `monthly_facility_demand_cost_series_before_tax`, `monthly_facility_demand_cost_series_before_tax_bau`, `monthly_fixed_cost_series_before_tax`, `monthly_fixed_cost_before_tax_bau`, `monthly_tou_demand_cost_series_before_tax`, `monthly_tou_demand_cost_series_before_tax_bau`, `tou_demand_metrics`, `tou_demand_rate_series`, `tou_demand_rate_tier_limits`. - New endpoint `/get_load_metrics` for sending a timeseries `load_profile` and getting monthly and annual energy and peak loads. -- New custom table option `custom_table_rates` for endpoint `/job/generate_results_table` +- New custom table option `custom_table_rates` for endpoint `/job/generate_results_table`. +##### Fixed +- Avoid `CST` bypassing non-servable heating loads by going through the `HighTempThermalStorage`. ## v3.16.2 From 85e37f6c1b806ff37c13f58d06ab80b44d602d3a Mon Sep 17 00:00:00 2001 From: bill-becker Date: Tue, 18 Nov 2025 21:12:26 -0700 Subject: [PATCH 62/63] Revert "Reapply "Convert type of monthly_peaks_load to avoid Any[] parse"" This reverts commit ee1d57b266f9918ca6d34f59509c3d08512a10a6. --- julia_src/http.jl | 4 ---- 1 file changed, 4 deletions(-) diff --git a/julia_src/http.jl b/julia_src/http.jl index be47a6689..d2602379e 100644 --- a/julia_src/http.jl +++ b/julia_src/http.jl @@ -90,10 +90,6 @@ function reopt(req::HTTP.Request) model_inputs = nothing # Catch handled/unhandled exceptions in data pre-processing, JuMP setup try - # Convert monthly_peaks_kw to Vector{Float64} if present in ElectricLoad - if haskey(d["ElectricLoad"], "monthly_peaks_kw") - d["ElectricLoad"]["monthly_peaks_kw"] = convert(Vector{Float64}, d["ElectricLoad"]["monthly_peaks_kw"]) - end model_inputs = reoptjl.REoptInputs(d) @info "Successfully processed REopt inputs." catch e From a851919cf21b5ee86a704b54caf99dd10d8a1b0c Mon Sep 17 00:00:00 2001 From: Bill Becker <42586683+Bill-Becker@users.noreply.github.com> Date: Tue, 18 Nov 2025 21:37:27 -0700 Subject: [PATCH 63/63] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- CHANGELOG.md | 2 +- reoptjl/views.py | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13cade981..c274b07fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,7 +30,7 @@ Classify the change according to the following categories: ### Minor Updates ##### Added - `ElectricLoad` input `monthly_peaks_kw`. Can be used to scale loads_kw or doe_reference loads to monthly peaks while maintaining monthly energy. -- `ElectricTariff` outputs: `demand_rate_average_series`, `energy_cost_series_before_tax`, `energy_cost_series_before_tax_bau`, `energy_rate_average_series`, `energy_rate_series`, `energy_rate_tier_limits`, `facility_demand_monthly_rate_series`, `facility_demand_monthly_rate_tier_limits`, `monthly_demand_cost_series_before_tax`, `monthly_demand_cost_series_before_tax_bau`, `monthly_energy_cost_series_before_tax`, `monthly_energy_cost_series_before_tax_bau`, `monthly_facility_demand_cost_series_before_tax`, `monthly_facility_demand_cost_series_before_tax_bau`, `monthly_fixed_cost_series_before_tax`, `monthly_fixed_cost_before_tax_bau`, `monthly_tou_demand_cost_series_before_tax`, `monthly_tou_demand_cost_series_before_tax_bau`, `tou_demand_metrics`, `tou_demand_rate_series`, `tou_demand_rate_tier_limits`. +- `ElectricTariff` outputs: `demand_rate_average_series`, `energy_cost_series_before_tax`, `energy_cost_series_before_tax_bau`, `energy_rate_average_series`, `energy_rate_series`, `energy_rate_tier_limits`, `facility_demand_monthly_rate_series`, `facility_demand_monthly_rate_tier_limits`, `monthly_demand_cost_series_before_tax`, `monthly_demand_cost_series_before_tax_bau`, `monthly_energy_cost_series_before_tax`, `monthly_energy_cost_series_before_tax_bau`, `monthly_facility_demand_cost_series_before_tax`, `monthly_facility_demand_cost_series_before_tax_bau`, `monthly_fixed_cost_series_before_tax`, `monthly_fixed_cost_series_before_tax_bau`, `monthly_tou_demand_cost_series_before_tax`, `monthly_tou_demand_cost_series_before_tax_bau`, `tou_demand_metrics`, `tou_demand_rate_series`, `tou_demand_rate_tier_limits`. - New endpoint `/get_load_metrics` for sending a timeseries `load_profile` and getting monthly and annual energy and peak loads. - New custom table option `custom_table_rates` for endpoint `/job/generate_results_table`. ##### Fixed diff --git a/reoptjl/views.py b/reoptjl/views.py index 09b060b93..ba3a3ab30 100644 --- a/reoptjl/views.py +++ b/reoptjl/views.py @@ -668,8 +668,12 @@ def simulated_load(request): # TODO make year optional for doe_reference_name input inputs[field] = data[field] if data.get("normalize_and_scale_load_profile_input") is not None: + if "load_profile" not in data: + return JsonResponse({"Error": "load_profile is required when normalize_and_scale_load_profile_input is provided."}, status=400) inputs["load_profile"] = data["load_profile"] if len(inputs["load_profile"]) != 8760: + if "time_steps_per_hour" not in data: + return JsonResponse({"Error": "time_steps_per_hour is required when load_profile length is not 8760."}, status=400) inputs["time_steps_per_hour"] = data["time_steps_per_hour"] if inputs["load_type"] == "electric": for energy in ["annual_kwh", "monthly_totals_kwh", "monthly_peaks_kw"]: @@ -1829,6 +1833,14 @@ def summarize_vector_data(request: Any, run_uuid: str) -> Dict[str, Any]: log_and_raise_error('summarize_vector_data') def generate_data_dict(config: List[Dict[str, Any]], df_gen: Dict[str, Any]) -> Dict[str, Any]: + """ + Generates a dictionary mapping labels to single values (not lists). + Args: + config: List of configuration dictionaries, each with a "label" and "scenario_value" callable. + df_gen: Dictionary of scenario data. + Returns: + Dict[str, Any]: Dictionary mapping labels to single values. + """ try: data_dict = {} for entry in config: @@ -2310,7 +2322,7 @@ def get_bau_column(col): "- Additional Yearly Cost ($/yr): Input any additional yearly costs (e.g., microgrid operation and maintenance).", "- Modified Total Year One Savings, After Tax ($): Updated total yearly savings to include any user-input additional yearly savings and cost.", "- Modified Total Capital Cost ($): Updated total cost to include any user-input additional incentive and cost.", - "- Modified Simple Payback Period Without Incentives (yrs): Uses Total Capital Cost Before Incentives ($) to calculate payback, for reference." + "- Modified Simple Payback Period Without Incentives (yrs): Uses Total Capital Cost Before Incentives ($) to calculate payback, for reference.", "- Modified Simple Payback Period (yrs): Calculates a simple payback period with Modified Total Year One Savings, After Tax ($) and Modified Total Capital Cost ($)." ] for item in playground_items: