From eb294df139c7f58b291e6293f58b5f3b4b544b31 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Mon, 17 Feb 2025 23:25:52 -0700 Subject: [PATCH 01/32] Temp change to REopt#after-tax-savings branch --- julia_src/Manifest.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/julia_src/Manifest.toml b/julia_src/Manifest.toml index 1803c1732..2d773aa2d 100644 --- a/julia_src/Manifest.toml +++ b/julia_src/Manifest.toml @@ -929,7 +929,9 @@ 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 = "324394f21cb7e2db3d9e7ebde19c4e83c5a64e0f" +git-tree-sha1 = "5e5c0f840afe0108d8de2c76d39b12627ab23730" +repo-rev = "after-tax-savings" +repo-url = "https://github.com/NREL/REopt.jl.git" uuid = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6" version = "0.50.0" From f8cbff850cbeb086ac6f441d7a0fcdd8c828003c Mon Sep 17 00:00:00 2001 From: bill-becker Date: Tue, 18 Feb 2025 22:34:30 -0700 Subject: [PATCH 02/32] Add new after tax fuel cost and other model fields --- julia_src/Manifest.toml | 2 +- ...s_year_one_fuel_cost_after_tax_and_more.py | 93 +++++++++++++++++++ reoptjl/models.py | 56 ++++++++++- 3 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 reoptjl/migrations/0077_boileroutputs_year_one_fuel_cost_after_tax_and_more.py diff --git a/julia_src/Manifest.toml b/julia_src/Manifest.toml index 2d773aa2d..5fdcc7f4b 100644 --- a/julia_src/Manifest.toml +++ b/julia_src/Manifest.toml @@ -929,7 +929,7 @@ 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 = "5e5c0f840afe0108d8de2c76d39b12627ab23730" +git-tree-sha1 = "f0a692c4ede1180273c926a8f0d2a8891a78f065" repo-rev = "after-tax-savings" repo-url = "https://github.com/NREL/REopt.jl.git" uuid = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6" diff --git a/reoptjl/migrations/0077_boileroutputs_year_one_fuel_cost_after_tax_and_more.py b/reoptjl/migrations/0077_boileroutputs_year_one_fuel_cost_after_tax_and_more.py new file mode 100644 index 000000000..21d677389 --- /dev/null +++ b/reoptjl/migrations/0077_boileroutputs_year_one_fuel_cost_after_tax_and_more.py @@ -0,0 +1,93 @@ +# Generated by Django 4.0.7 on 2025-02-19 05:32 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('reoptjl', '0076_ashpspaceheaterinputs_force_dispatch_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='boileroutputs', + name='year_one_fuel_cost_after_tax', + field=models.FloatField(blank=True, null=True), + ), + migrations.AddField( + model_name='chpoutputs', + name='year_one_fuel_cost_after_tax', + field=models.FloatField(blank=True, help_text='Cost of fuel consumed by the CHP system in year one, after tax [\\$]', null=True), + ), + migrations.AddField( + model_name='chpoutputs', + name='year_one_standby_cost_after_tax', + field=models.FloatField(blank=True, help_text='CHP standby charges in year one, after tax [\\$]', null=True), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='year_one_bill_after_tax', + field=models.FloatField(blank=True, help_text='Optimal year one utility bill, after tax', null=True), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='year_one_bill_after_tax_bau', + field=models.FloatField(blank=True, help_text='Business as usual year one utility bill, after tax', null=True), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='year_one_export_benefit_after_tax', + field=models.FloatField(blank=True, help_text='Optimal year one value of exported energy, after tax. A positive value indicates a benefit.', null=True), + ), + migrations.AddField( + model_name='electrictariffoutputs', + name='year_one_export_benefit_after_tax_bau', + field=models.FloatField(blank=True, help_text='Business as usual year one value of exported energy, after tax. A positive value indicates a benefit.', null=True), + ), + migrations.AddField( + model_name='existingboileroutputs', + name='year_one_fuel_cost_after_tax', + field=models.FloatField(blank=True, null=True), + ), + migrations.AddField( + model_name='existingboileroutputs', + name='year_one_fuel_cost_after_tax_bau', + field=models.FloatField(blank=True, null=True), + ), + migrations.AddField( + model_name='financialoutputs', + name='capital_costs_after_non_discounted_incentives', + field=models.FloatField(blank=True, help_text='Capital costs for all technologies, in present value, after all non-discounted incentives including MACRS, including present value of replacement costs', null=True), + ), + migrations.AddField( + model_name='financialoutputs', + name='year_one_fuel_cost_after_tax', + field=models.FloatField(blank=True, help_text='Year one fuel cost of all combined fuel-burning techs, after tax.', null=True), + ), + migrations.AddField( + model_name='financialoutputs', + name='year_one_fuel_cost_after_tax_bau', + field=models.FloatField(blank=True, help_text='Year one fuel cost of all combined fuel-burning techs, after tax in the BAU case.', null=True), + ), + migrations.AddField( + model_name='financialoutputs', + name='year_one_fuel_cost_before_tax', + field=models.FloatField(blank=True, help_text='Year one fuel cost of all combined fuel-burning techs, before tax.', null=True), + ), + migrations.AddField( + model_name='financialoutputs', + name='year_one_fuel_cost_before_tax_bau', + field=models.FloatField(blank=True, help_text='Year one fuel cost of all combined fuel-burning techs, before tax in the BAU case.', null=True), + ), + migrations.AddField( + model_name='financialoutputs', + name='year_one_om_costs_after_tax_bau', + field=models.FloatField(blank=True, help_text='Year one operations and maintenance cost after tax in the BAU case.', null=True), + ), + migrations.AddField( + model_name='ghpoutputs', + name='avoided_capex_by_ghp_present_value', + field=models.FloatField(blank=True, null=True), + ), + ] diff --git a/reoptjl/models.py b/reoptjl/models.py index f6a5680eb..093e51ae1 100644 --- a/reoptjl/models.py +++ b/reoptjl/models.py @@ -947,6 +947,10 @@ class FinancialOutputs(BaseModel, models.Model): null=True, blank=True, help_text="Up-front capital costs for all technologies, in present value, excluding replacement costs, including incentives except for MACRS." ) + capital_costs_after_non_discounted_incentives = models.FloatField( + null=True, blank=True, + help_text="Capital costs for all technologies, in present value, after all non-discounted incentives including MACRS, including present value of replacement costs" + ) om_and_replacement_present_cost_after_tax = models.FloatField( null=True, blank=True, help_text="Net O&M and replacement costs in present value, after-tax." @@ -955,6 +959,10 @@ class FinancialOutputs(BaseModel, models.Model): null=True, blank=True, help_text="Year one operations and maintenance cost after tax." ) + year_one_om_costs_after_tax_bau = models.FloatField( + null=True, blank=True, + help_text="Year one operations and maintenance cost after tax in the BAU case." + ) lifecycle_om_costs_before_tax = models.FloatField( null=True, blank=True, help_text="Life cycle operations and maintenance cost over analysis period before tax." @@ -967,6 +975,22 @@ class FinancialOutputs(BaseModel, models.Model): null=True, blank=True, help_text="Year one operations and maintenance cost before tax in the BAU case." ) + year_one_fuel_cost_before_tax = models.FloatField( + null=True, blank=True, + help_text="Year one fuel cost of all combined fuel-burning techs, before tax." + ) + year_one_fuel_cost_before_tax_bau = models.FloatField( + null=True, blank=True, + help_text="Year one fuel cost of all combined fuel-burning techs, before tax in the BAU case." + ) + year_one_fuel_cost_after_tax = models.FloatField( + null=True, blank=True, + help_text="Year one fuel cost of all combined fuel-burning techs, after tax." + ) + year_one_fuel_cost_after_tax_bau = models.FloatField( + null=True, blank=True, + help_text="Year one fuel cost of all combined fuel-burning techs, after tax in the BAU case." + ) simple_payback_years = models.FloatField( null=True, blank=True, help_text=("Number of years until the cumulative annual cashflows turn positive. " @@ -2483,6 +2507,14 @@ class ElectricTariffOutputs(BaseModel, models.Model): null=True, blank=True, help_text="Business as usual year one utility bill" ) + year_one_bill_after_tax = models.FloatField( + null=True, blank=True, + help_text="Optimal year one utility bill, after tax" + ) + year_one_bill_after_tax_bau = models.FloatField( + null=True, blank=True, + help_text="Business as usual year one utility bill, after tax" + ) year_one_export_benefit_before_tax = models.FloatField( null=True, blank=True, help_text="Optimal year one value of exported energy. A positive value indicates a benefit." @@ -2491,6 +2523,14 @@ class ElectricTariffOutputs(BaseModel, models.Model): null=True, blank=True, help_text="Business as usual year one value of exported energy. A positive value indicates a benefit." ) + year_one_export_benefit_after_tax = models.FloatField( + null=True, blank=True, + help_text="Optimal year one value of exported energy, after tax. A positive value indicates a benefit." + ) + year_one_export_benefit_after_tax_bau = models.FloatField( + null=True, blank=True, + help_text="Business as usual year one value of exported energy, after tax. A positive value indicates a benefit." + ) year_one_coincident_peak_cost_before_tax = models.FloatField( null=True, blank=True, help_text="Optimal year one coincident peak charges" @@ -4496,6 +4536,10 @@ class CHPOutputs(BaseModel, models.Model): null=True, blank=True, help_text="Cost of fuel consumed by the CHP system in year one [\$]" ) + year_one_fuel_cost_after_tax = models.FloatField( + null=True, blank=True, + help_text="Cost of fuel consumed by the CHP system in year one, after tax [\$]" + ) lifecycle_fuel_cost_after_tax = models.FloatField( null=True, blank=True, help_text="Present value of cost of fuel consumed by the CHP system, after tax [\$]" @@ -4504,6 +4548,10 @@ class CHPOutputs(BaseModel, models.Model): null=True, blank=True, help_text="CHP standby charges in year one [\$]" ) + year_one_standby_cost_after_tax = models.FloatField( + null=True, blank=True, + help_text="CHP standby charges in year one, after tax [\$]" + ) lifecycle_standby_cost_after_tax = models.FloatField( null=True, blank=True, help_text="Present value of all CHP standby charges, after tax." @@ -5077,7 +5125,9 @@ class ExistingBoilerOutputs(BaseModel, models.Model): annual_thermal_production_mmbtu = models.FloatField(null=True, blank=True) annual_thermal_production_mmbtu_bau = models.FloatField(null=True, blank=True) year_one_fuel_cost_before_tax = models.FloatField(null=True, blank=True) + year_one_fuel_cost_after_tax = models.FloatField(null=True, blank=True) year_one_fuel_cost_before_tax_bau = models.FloatField(null=True, blank=True) + year_one_fuel_cost_after_tax_bau = models.FloatField(null=True, blank=True) thermal_to_storage_series_mmbtu_per_hour = ArrayField( models.FloatField(null=True, blank=True), @@ -6062,6 +6112,10 @@ class BoilerOutputs(BaseModel, models.Model): null=True, blank=True ) + year_one_fuel_cost_after_tax = models.FloatField( + null=True, blank=True + ) + thermal_to_steamturbine_series_mmbtu_per_hour = ArrayField( models.FloatField(null=True, blank=True), default = list, @@ -8218,7 +8272,7 @@ class GHPOutputs(BaseModel, models.Model): thermal_to_space_heating_load_series_mmbtu_per_hour = ArrayField(models.FloatField(null=True, blank=True), default=list, null=True, blank=True) thermal_to_dhw_load_series_mmbtu_per_hour = ArrayField(models.FloatField(null=True, blank=True), default=list, null=True, blank=True) thermal_to_load_series_ton = ArrayField(models.FloatField(null=True, blank=True), default=list, null=True, blank=True) - + avoided_capex_by_ghp_present_value = models.FloatField(null=True, blank=True) def get_input_dict_from_run_uuid(run_uuid:str): """ From 6deb3c5afc7f65e874a694897262237af2e5e8d7 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Tue, 18 Feb 2025 22:55:06 -0700 Subject: [PATCH 03/32] Avoid lifecycle_capital_cost GHP test discrepancy from it now including the GHX residual value --- reoptjl/test/posts/central_plant_ghp.json | 1 + 1 file changed, 1 insertion(+) diff --git a/reoptjl/test/posts/central_plant_ghp.json b/reoptjl/test/posts/central_plant_ghp.json index 942942a22..dc12c0f4d 100644 --- a/reoptjl/test/posts/central_plant_ghp.json +++ b/reoptjl/test/posts/central_plant_ghp.json @@ -45,6 +45,7 @@ "macrs_bonus_fraction": 0.0, "macrs_itc_reduction": 0.5, "federal_itc_fraction": 0.3, + "ghx_useful_life_years": 25, "ghpghx_inputs": [{ "borehole_depth_ft": 400.0, "simulation_years": 20, From 7a9439d0316df6e69df9274a38d657c25b13496f Mon Sep 17 00:00:00 2001 From: bill-becker Date: Wed, 19 Feb 2025 08:51:10 -0700 Subject: [PATCH 04/32] Add after-tax fuel cost for Generator --- ...s_year_one_fuel_cost_after_tax_and_more.py | 23 +++++++++++++++++++ reoptjl/models.py | 2 ++ 2 files changed, 25 insertions(+) create mode 100644 reoptjl/migrations/0078_generatoroutputs_year_one_fuel_cost_after_tax_and_more.py diff --git a/reoptjl/migrations/0078_generatoroutputs_year_one_fuel_cost_after_tax_and_more.py b/reoptjl/migrations/0078_generatoroutputs_year_one_fuel_cost_after_tax_and_more.py new file mode 100644 index 000000000..2c7c0e941 --- /dev/null +++ b/reoptjl/migrations/0078_generatoroutputs_year_one_fuel_cost_after_tax_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 4.0.7 on 2025-02-19 14:15 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('reoptjl', '0077_boileroutputs_year_one_fuel_cost_after_tax_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='generatoroutputs', + name='year_one_fuel_cost_after_tax', + field=models.FloatField(blank=True, null=True), + ), + migrations.AddField( + model_name='generatoroutputs', + name='year_one_fuel_cost_after_tax_bau', + field=models.FloatField(blank=True, null=True), + ), + ] diff --git a/reoptjl/models.py b/reoptjl/models.py index 093e51ae1..2feb646c3 100644 --- a/reoptjl/models.py +++ b/reoptjl/models.py @@ -3915,6 +3915,8 @@ class GeneratorOutputs(BaseModel, models.Model): year_one_variable_om_cost_before_tax_bau = models.FloatField(null=True, blank=True) year_one_fuel_cost_before_tax = models.FloatField(null=True, blank=True) year_one_fuel_cost_before_tax_bau = models.FloatField(null=True, blank=True) + year_one_fuel_cost_after_tax = models.FloatField(null=True, blank=True) + year_one_fuel_cost_after_tax_bau = models.FloatField(null=True, blank=True) year_one_fixed_om_cost_before_tax = models.FloatField(null=True, blank=True) year_one_fixed_om_cost_before_tax_bau = models.FloatField(null=True, blank=True) lifecycle_variable_om_cost_after_tax = models.FloatField(null=True, blank=True) From d8799f3a130bc30e1d2073e1b515b2eb1f7143d4 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Thu, 20 Feb 2025 19:37:04 -0700 Subject: [PATCH 05/32] Convert monthly_fraction to expected type in /simulated_load --- 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 90a3df764..8da68214d 100644 --- a/julia_src/http.jl +++ b/julia_src/http.jl @@ -419,7 +419,7 @@ function simulated_load(req::HTTP.Request) # 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", - "monthly_tonhour", "addressable_load_fraction", "load_profile"] + "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]) From 761bc992c8c3f869b7baa21b8080d2959c28e629 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Thu, 20 Feb 2025 19:37:22 -0700 Subject: [PATCH 06/32] Remove deprecated version in docker-compose.yml file --- docker-compose.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index a3ebd222f..80cdda5b5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,3 @@ -version: "2.1" - services: redis: @@ -67,7 +65,8 @@ services: environment: - XPRESS_JL_SKIP_LIB_CHECK=True - XPRESS_INSTALLED=False - command: julia --project=/opt/julia_src http.jl + # command: julia --project=/opt/julia_src http.jl + command: julia --project=. -e 'using Pkg; Pkg.instantiate(); include("http.jl")' ports: - "8081:8081" volumes: From bba9e6992ec9dfb434eaa9ff6181ee4fba2d8556 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Thu, 20 Feb 2025 19:38:00 -0700 Subject: [PATCH 07/32] Update REopt#after-tax-savings with cooling 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 5fdcc7f4b..5960dcc06 100644 --- a/julia_src/Manifest.toml +++ b/julia_src/Manifest.toml @@ -929,7 +929,7 @@ 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 = "f0a692c4ede1180273c926a8f0d2a8891a78f065" +git-tree-sha1 = "03e51b11471e03b3a3427ff99aa7891772d524a5" repo-rev = "after-tax-savings" repo-url = "https://github.com/NREL/REopt.jl.git" uuid = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6" From 791ebbabab0e8fd247851b38a6709f723e8f02a7 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Fri, 21 Feb 2025 08:24:48 -0700 Subject: [PATCH 08/32] Fix CoolingLoad type conversions in REopt.jl branch --- 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 5960dcc06..f7581ee91 100644 --- a/julia_src/Manifest.toml +++ b/julia_src/Manifest.toml @@ -929,7 +929,7 @@ 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 = "03e51b11471e03b3a3427ff99aa7891772d524a5" +git-tree-sha1 = "3ae1cfd96f39ec65816432db27c92ea37e8a688c" repo-rev = "after-tax-savings" repo-url = "https://github.com/NREL/REopt.jl.git" uuid = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6" From 1ca2c7faa4d35aa0de6130bf2500bfabddf9be80 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Mon, 3 Mar 2025 10:05:59 -0700 Subject: [PATCH 09/32] Update capital cost model fields --- ...after_incentives_without_macrs_and_more.py | 27 +++++++++++++++++++ reoptjl/models.py | 6 ++--- 2 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 reoptjl/migrations/0079_remove_financialoutputs_initial_capital_costs_after_incentives_without_macrs_and_more.py diff --git a/reoptjl/migrations/0079_remove_financialoutputs_initial_capital_costs_after_incentives_without_macrs_and_more.py b/reoptjl/migrations/0079_remove_financialoutputs_initial_capital_costs_after_incentives_without_macrs_and_more.py new file mode 100644 index 000000000..366b2804d --- /dev/null +++ b/reoptjl/migrations/0079_remove_financialoutputs_initial_capital_costs_after_incentives_without_macrs_and_more.py @@ -0,0 +1,27 @@ +# Generated by Django 4.0.7 on 2025-02-28 23:10 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('reoptjl', '0078_generatoroutputs_year_one_fuel_cost_after_tax_and_more'), + ] + + operations = [ + migrations.RemoveField( + model_name='financialoutputs', + name='initial_capital_costs_after_incentives_without_macrs', + ), + migrations.AddField( + model_name='financialoutputs', + name='capital_costs_after_incentives_without_macrs', + field=models.FloatField(blank=True, help_text='Capital costs for all technologies, including present value of replacement costs and incentives except for MACRS.', null=True), + ), + migrations.AlterField( + model_name='financialoutputs', + name='capital_costs_after_non_discounted_incentives', + field=models.FloatField(blank=True, help_text='Capital costs for all technologies, including present value of replacement costs and all non-discounted incentives including MACRS.', null=True), + ), + ] diff --git a/reoptjl/models.py b/reoptjl/models.py index 2feb646c3..498584621 100644 --- a/reoptjl/models.py +++ b/reoptjl/models.py @@ -943,13 +943,13 @@ class FinancialOutputs(BaseModel, models.Model): null=True, blank=True, help_text="Up-front capital costs for all technologies, in present value, excluding replacement costs, including incentives." ) - initial_capital_costs_after_incentives_without_macrs = models.FloatField( + capital_costs_after_incentives_without_macrs = models.FloatField( null=True, blank=True, - help_text="Up-front capital costs for all technologies, in present value, excluding replacement costs, including incentives except for MACRS." + help_text="Capital costs for all technologies, including present value of replacement costs and incentives except for MACRS." ) capital_costs_after_non_discounted_incentives = models.FloatField( null=True, blank=True, - help_text="Capital costs for all technologies, in present value, after all non-discounted incentives including MACRS, including present value of replacement costs" + help_text="Capital costs for all technologies, including present value of replacement costs and all non-discounted incentives including MACRS." ) om_and_replacement_present_cost_after_tax = models.FloatField( null=True, blank=True, From f12cbd6a4b0a28a2e44e903b2b5b77e977dbda7d Mon Sep 17 00:00:00 2001 From: bill-becker Date: Mon, 3 Mar 2025 10:08:30 -0700 Subject: [PATCH 10/32] Add financial output model fields for total yearly costs and savings --- ...one_chp_standby_cost_after_tax_and_more.py | 53 +++++++++++++++++++ reoptjl/models.py | 52 ++++++++++++++---- 2 files changed, 94 insertions(+), 11 deletions(-) create mode 100644 reoptjl/migrations/0080_financialoutputs_year_one_chp_standby_cost_after_tax_and_more.py diff --git a/reoptjl/migrations/0080_financialoutputs_year_one_chp_standby_cost_after_tax_and_more.py b/reoptjl/migrations/0080_financialoutputs_year_one_chp_standby_cost_after_tax_and_more.py new file mode 100644 index 000000000..bff1bc5d8 --- /dev/null +++ b/reoptjl/migrations/0080_financialoutputs_year_one_chp_standby_cost_after_tax_and_more.py @@ -0,0 +1,53 @@ +# Generated by Django 4.0.7 on 2025-03-03 17:06 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('reoptjl', '0079_remove_financialoutputs_initial_capital_costs_after_incentives_without_macrs_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='financialoutputs', + name='year_one_chp_standby_cost_after_tax', + field=models.FloatField(blank=True, help_text='Year one CHP standby charges, after tax.', null=True), + ), + migrations.AddField( + model_name='financialoutputs', + name='year_one_chp_standby_cost_before_tax', + field=models.FloatField(blank=True, help_text='Year one CHP standby charges, before tax.', null=True), + ), + migrations.AddField( + model_name='financialoutputs', + name='year_one_total_cost_after_tax', + field=models.FloatField(blank=True, help_text='Year one total operating (electricity, fuel, O&M) costs, after tax.', null=True), + ), + migrations.AddField( + model_name='financialoutputs', + name='year_one_total_cost_after_tax_bau', + field=models.FloatField(blank=True, help_text='Year one total operating (electricity, fuel, O&M) costs, after tax in the BAU case.', null=True), + ), + migrations.AddField( + model_name='financialoutputs', + name='year_one_total_cost_before_tax', + field=models.FloatField(blank=True, help_text='Year one total operating (electricity, fuel, O&M) costs, before tax.', null=True), + ), + migrations.AddField( + model_name='financialoutputs', + name='year_one_total_cost_before_tax_bau', + field=models.FloatField(blank=True, help_text='Year one total operating (electricity, fuel, O&M) costs, before tax in the BAU case.', null=True), + ), + migrations.AddField( + model_name='financialoutputs', + name='year_one_total_cost_savings_after_tax', + field=models.FloatField(blank=True, help_text='Year one total operating (electricity, fuel, O&M) cost savings compared to BAU case, after tax.', null=True), + ), + migrations.AddField( + model_name='financialoutputs', + name='year_one_total_cost_savings_before_tax', + field=models.FloatField(blank=True, help_text='Year one total operating (electricity, fuel, O&M) cost savings compared to BAU case, before tax.', null=True), + ), + ] diff --git a/reoptjl/models.py b/reoptjl/models.py index 498584621..f9df419e2 100644 --- a/reoptjl/models.py +++ b/reoptjl/models.py @@ -955,14 +955,6 @@ class FinancialOutputs(BaseModel, models.Model): null=True, blank=True, help_text="Net O&M and replacement costs in present value, after-tax." ) - year_one_om_costs_after_tax = models.FloatField( - null=True, blank=True, - help_text="Year one operations and maintenance cost after tax." - ) - year_one_om_costs_after_tax_bau = models.FloatField( - null=True, blank=True, - help_text="Year one operations and maintenance cost after tax in the BAU case." - ) lifecycle_om_costs_before_tax = models.FloatField( null=True, blank=True, help_text="Life cycle operations and maintenance cost over analysis period before tax." @@ -975,6 +967,14 @@ class FinancialOutputs(BaseModel, models.Model): null=True, blank=True, help_text="Year one operations and maintenance cost before tax in the BAU case." ) + year_one_om_costs_after_tax = models.FloatField( + null=True, blank=True, + help_text="Year one operations and maintenance cost after tax." + ) + year_one_om_costs_after_tax_bau = models.FloatField( + null=True, blank=True, + help_text="Year one operations and maintenance cost after tax in the BAU case." + ) year_one_fuel_cost_before_tax = models.FloatField( null=True, blank=True, help_text="Year one fuel cost of all combined fuel-burning techs, before tax." @@ -990,7 +990,39 @@ class FinancialOutputs(BaseModel, models.Model): year_one_fuel_cost_after_tax_bau = models.FloatField( null=True, blank=True, help_text="Year one fuel cost of all combined fuel-burning techs, after tax in the BAU case." - ) + ) + year_one_chp_standby_cost_before_tax = models.FloatField( + null=True, blank=True, + help_text=("Year one CHP standby charges, before tax.") + ) + year_one_chp_standby_cost_after_tax = models.FloatField( + null=True, blank=True, + help_text=("Year one CHP standby charges, after tax.") + ) + year_one_total_cost_before_tax = models.FloatField( + null=True, blank=True, + help_text=("Year one total operating (electricity, fuel, O&M) costs, before tax.") + ) + year_one_total_cost_before_tax_bau = models.FloatField( + null=True, blank=True, + help_text=("Year one total operating (electricity, fuel, O&M) costs, before tax in the BAU case.") + ) + year_one_total_cost_after_tax = models.FloatField( + null=True, blank=True, + help_text=("Year one total operating (electricity, fuel, O&M) costs, after tax.") + ) + year_one_total_cost_after_tax_bau = models.FloatField( + null=True, blank=True, + help_text=("Year one total operating (electricity, fuel, O&M) costs, after tax in the BAU case.") + ) + year_one_total_cost_savings_before_tax = models.FloatField( + null=True, blank=True, + help_text=("Year one total operating (electricity, fuel, O&M) cost savings compared to BAU case, before tax.") + ) + year_one_total_cost_savings_after_tax = models.FloatField( + null=True, blank=True, + help_text=("Year one total operating (electricity, fuel, O&M) cost savings compared to BAU case, after tax.") + ) simple_payback_years = models.FloatField( null=True, blank=True, help_text=("Number of years until the cumulative annual cashflows turn positive. " @@ -1085,12 +1117,10 @@ class FinancialOutputs(BaseModel, models.Model): null=True, blank=True, help_text=("Component of lifecycle costs (LCC). This value is the present value of all fuel costs over the analysis period, after tax.") ) - lifecycle_fuel_costs_after_tax_bau = models.FloatField( null=True, blank=True, help_text=("Component of lifecycle costs (LCC). This value is the present value of all fuel costs over the analysis period, after tax in the BAU case.") ) - lifecycle_chp_standby_cost_after_tax = models.FloatField( null=True, blank=True, help_text=("Component of lifecycle costs (LCC). This value is the present value of all CHP standby charges, after tax.") From 4ff276097d305d95e5635eebac6e1551f9083f93 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Mon, 3 Mar 2025 10:09:29 -0700 Subject: [PATCH 11/32] Update REopt.jl#after-tax-savings branch --- 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 f7581ee91..193a2461e 100644 --- a/julia_src/Manifest.toml +++ b/julia_src/Manifest.toml @@ -929,7 +929,7 @@ 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 = "3ae1cfd96f39ec65816432db27c92ea37e8a688c" +git-tree-sha1 = "b7d1b5cba5145f62ec15adf4f47d754d43294a24" repo-rev = "after-tax-savings" repo-url = "https://github.com/NREL/REopt.jl.git" uuid = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6" From b7a3547f2ffe8b10a2f5127d1aaacc6b5bd76cbc Mon Sep 17 00:00:00 2001 From: bill-becker Date: Mon, 3 Mar 2025 10:10:13 -0700 Subject: [PATCH 12/32] Update custom_table_config and some formatting for spreadsheet --- reoptjl/custom_table_config.py | 141 ++++++++++++++++++++++++++------- reoptjl/views.py | 10 ++- 2 files changed, 122 insertions(+), 29 deletions(-) diff --git a/reoptjl/custom_table_config.py b/reoptjl/custom_table_config.py index bfafcaea5..5a0ecac84 100644 --- a/reoptjl/custom_table_config.py +++ b/reoptjl/custom_table_config.py @@ -281,6 +281,12 @@ "bau_value" : lambda df: safe_get(df, "outputs.Financial.year_one_om_costs_before_tax_bau"), "scenario_value": lambda df: safe_get(df, "outputs.Financial.year_one_om_costs_before_tax") }, + { + "label" : "Year 1 O&M Cost, After Tax ($)", + "key" : "year_1_om_cost_after_tax", + "bau_value" : lambda df: safe_get(df, "outputs.Financial.year_one_om_costs_after_tax_bau"), + "scenario_value": lambda df: safe_get(df, "outputs.Financial.year_one_om_costs_after_tax") + }, { "label" : "Net Present Value ($)", "key" : "npv", @@ -288,7 +294,7 @@ "scenario_value": lambda df: safe_get(df, "outputs.Financial.npv") }, { - "label" : "Payback Period (years)", + "label" : "Payback Period, with escalation and inflation (years)", "key" : "payback_period", "bau_value" : lambda df: safe_get(df, "outputs.Financial.simple_payback_years_bau"), "scenario_value": lambda df: safe_get(df, "outputs.Financial.simple_payback_years") @@ -311,8 +317,8 @@ { "label" : "Technology Capital Costs + Replacements, After Incentives ($)", "key" : "technology_capital_costs_after_incentives", - "bau_value" : lambda df: safe_get(df, "outputs.Financial.lifecycle_generation_tech_capital_costs_bau"), - "scenario_value": lambda df: safe_get(df, "outputs.Financial.lifecycle_generation_tech_capital_costs") + "bau_value" : lambda df: safe_get(df, "outputs.Financial.lifecycle_capital_costs_bau"), + "scenario_value": lambda df: safe_get(df, "outputs.Financial.lifecycle_capital_costs") }, { "label" : "O&M Costs ($)", @@ -323,8 +329,8 @@ { "label" : "Total Electric Costs ($)", "key" : "total_electric_utility_costs", - "bau_value" : lambda df: safe_get(df, "outputs.Financial.lifecycle_elecbill_after_tax_bau"), - "scenario_value": lambda df: safe_get(df, "outputs.Financial.lifecycle_elecbill_after_tax") + "bau_value" : lambda df: safe_get(df, "outputs.Financial.lifecycle_elecbill_after_tax_bau") - safe_get(df, "outputs.Financial.lifecycle_export_benefit_after_tax_bau") + safe_get(df, "outputs.CHP.lifecycle_chp_standby_cost_after_tax_bau"), + "scenario_value": lambda df: safe_get(df, "outputs.Financial.lifecycle_elecbill_after_tax") - safe_get(df, "outputs.Financial.lifecycle_export_benefit_after_tax") + safe_get(df, "outputs.CHP.lifecycle_chp_standby_cost_after_tax") }, { "label" : "Total Fuel Costs ($)", @@ -361,7 +367,7 @@ ############################ Year 1 Electric Bill ########################### ##################################################################################################### { - "label" : "Year 1 Electric Bill", + "label" : "Year 1 Electric Bill, Before Tax Unless Noted", "key" : "year_1_electric_bill_separator", "bau_value" : lambda df: "", "scenario_value": lambda df: "" @@ -390,23 +396,47 @@ "bau_value" : lambda df: safe_get(df, "outputs.ElectricTariff.year_one_fixed_cost_before_tax_bau"), "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.year_one_fixed_cost_before_tax") }, + { + "label" : "Standby Charges For CHP ($)", + "key" : "standby_charges_for_CHP", + "bau_value" : lambda df: safe_get(df, "outputs.CHP.year_one_standby_cost_before_tax_bau"), + "scenario_value": lambda df: safe_get(df, "outputs.CHP.year_one_standby_cost_before_tax") + }, + { + "label" : "Export Credits ($)", + "key" : "export_credits", + "bau_value" : lambda df: -1 * safe_get(df, "outputs.ElectricTariff.year_one_export_benefit_before_tax_bau"), + "scenario_value": lambda df: -1 * safe_get(df, "outputs.ElectricTariff.year_one_export_benefit_before_tax") + }, { "label" : "Purchased Electricity Cost ($)", "key" : "purchased_electricity_cost", - "bau_value" : lambda df: safe_get(df, "outputs.ElectricTariff.year_one_bill_before_tax_bau"), - "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.year_one_bill_before_tax") + "bau_value" : lambda df: safe_get(df, "outputs.ElectricTariff.year_one_bill_before_tax_bau") + safe_get(df, "outputs.CHP.year_one_standby_cost_before_tax_bau") - safe_get(df, "outputs.ElectricTariff.year_one_export_benefit_before_tax_bau"), + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.year_one_bill_before_tax") + safe_get(df, "outputs.CHP.year_one_standby_cost_before_tax") - safe_get(df, "outputs.ElectricTariff.year_one_export_benefit_before_tax") }, + { + "label" : "Purchased Electricity Cost, After Tax ($)", + "key" : "purchased_electricity_cost", + "bau_value" : lambda df: safe_get(df, "outputs.ElectricTariff.year_one_bill_after_tax_bau") + safe_get(df, "outputs.CHP.year_one_standby_cost_after_tax_bau") - safe_get(df, "outputs.ElectricTariff.year_one_export_benefit_after_tax_bau"), + "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.year_one_bill_after_tax") + safe_get(df, "outputs.CHP.year_one_standby_cost_after_tax") - safe_get(df, "outputs.ElectricTariff.year_one_export_benefit_after_tax") + }, { "label" : "Electricity Cost Savings ($)", "key" : "electricity_cost_savings", "bau_value" : lambda df: "", "scenario_value": lambda df: "" }, + { + "label" : "Electricity Cost Savings, After Tax ($)", + "key" : "electricity_cost_savings_after_tax", + "bau_value" : lambda df: "", + "scenario_value": lambda df: "" + }, ##################################################################################################### ############################ Year 1 Fuel Cost ########################### ##################################################################################################### { - "label" : "Year 1 Fuel Cost", + "label" : "Year 1 Fuel Cost, Before Tax Unless Noted", "key" : "year_1_fuel_cost_separator", "bau_value" : lambda df: "", "scenario_value": lambda df: "" @@ -432,15 +462,27 @@ { "label" : "Fuel Cost ($)", "key" : "fuel_cost", - "bau_value" : lambda df: safe_get(df, "outputs.ExistingBoiler.year_one_fuel_cost_before_tax_bau")+safe_get(df, "outputs.CHP.year_one_fuel_cost_before_tax_bau")+safe_get(df, "outputs.Generator.year_one_fuel_cost_before_tax_bau"), - "scenario_value": lambda df: safe_get(df, "outputs.ExistingBoiler.year_one_fuel_cost_before_tax")+safe_get(df, "outputs.CHP.year_one_fuel_cost_before_tax")+safe_get(df, "outputs.Generator.year_one_fuel_cost_before_tax") + "bau_value" : lambda df: safe_get(df, "outputs.Financial.year_one_fuel_cost_before_tax_bau"), + "scenario_value": lambda df: safe_get(df, "outputs.Financial.year_one_fuel_cost_before_tax") + }, + { + "label" : "Fuel Cost, After Tax ($)", + "key" : "fuel_cost", + "bau_value" : lambda df: safe_get(df, "outputs.Financial.year_one_fuel_cost_after_tax_bau"), + "scenario_value": lambda df: safe_get(df, "outputs.Financial.year_one_fuel_cost_after_tax") }, { "label" : "Fuel Cost Savings ($)", - "key" : "uel_cost_savings", + "key" : "fuel_cost_savings", "bau_value" : lambda df: "", "scenario_value": lambda df: "" }, + { + "label" : "Fuel Cost Savings, After Tax ($)", + "key" : "fuel_cost_savings_after_tax", + "bau_value" : lambda df: "", + "scenario_value": lambda df: "" + }, ##################################################################################################### ############################ Renewable Energy & Emissions ########################### ##################################################################################################### @@ -492,22 +534,40 @@ ##################### Playground - Explore Effect of Additional Incentives or Costs, outside of REopt ############################## ##################################################################################################### { - "label": "Playground - Explore Effect of Additional Incentives or Costs, outside of REopt", + "label": "Playground - Explore Effect of Additional Incentives or Costs, Outside of REopt", "key": "playground_separator", "bau_value": lambda df: "", "scenario_value": lambda df: "" + }, + { + "label": "Total Capital Cost Before Incentives ($)", + "key": "total_capital_cost_before_incentives", + "bau_value": lambda df: safe_get(df, "outputs.Financial.initial_capital_costs_bau") + safe_get(df, "outputs.Financial.replacements_present_cost_after_tax_bau"), + "scenario_value": lambda df: safe_get(df, "outputs.Financial.initial_capital_costs") + safe_get(df, "outputs.Financial.replacements_present_cost_after_tax") }, { - "label": "Net Upfront Capital Cost After Incentives but without MACRS ($)", - "key": "net_upfront_capital_cost_without_macrs", - "bau_value": lambda df: safe_get(df, "outputs.Financial.initial_capital_costs_after_incentives_without_macrs_bau"), - "scenario_value": lambda df: safe_get(df, "outputs.Financial.initial_capital_costs_after_incentives_without_macrs") + "label": "Total Capital Cost After Incentives Without MACRS ($)", + "key": "total_capital_cost_after_incentives_without_macrs", + "bau_value": lambda df: safe_get(df, "outputs.Financial.capital_costs_after_incentives_without_macrs_bau"), + "scenario_value": lambda df: safe_get(df, "outputs.Financial.capital_costs_after_incentives_without_macrs") }, { - "label": "Net Upfront Capital Cost After Incentives with MACRS ($)", - "key": "net_upfront_capital_cost_with_macrs", - "bau_value": lambda df: safe_get(df, "outputs.Financial.initial_capital_costs_after_incentives_bau"), - "scenario_value": lambda df: safe_get(df, "outputs.Financial.initial_capital_costs_after_incentives") + "label": "Total Capital Cost After Non-Discounted Incentives ($)", + "key": "total_capital_cost_after_non_discounted_incentives", + "bau_value": lambda df: safe_get(df, "outputs.Financial.capital_costs_after_non_discounted_incentives_bau"), + "scenario_value": lambda df: safe_get(df, "outputs.Financial.capital_costs_after_non_discounted_incentives") + }, + { + "label": "Tax Rate, For Reference (%)", + "key": "tax_rate", + "bau_value": lambda df: safe_get(df, "inputs.Financial.offtaker_tax_rate_fraction"), + "scenario_value": lambda df: safe_get(df, "inputs.Financial.offtaker_tax_rate_fraction") + }, + { + "label": "Total Year One Savings, After Tax ($)", + "key": "total_year_one_savings_after_tax", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" }, { "label": "Additional Upfront Incentive ($)", @@ -534,7 +594,13 @@ "scenario_value": lambda df: "" }, { - "label": "Modified Net Upfront Capital Cost ($)", + "label": "Modified Total Year One Savings, After Tax ($)", + "key": "modified_total_year_one_savings_after_tax", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + { + "label": "Modified Total Capital Cost ($)", "key": "modified_net_upfront_capital_cost", "bau_value": lambda df: "", "scenario_value": lambda df: "" @@ -943,9 +1009,12 @@ bau_cells_config = { "grid_value" : "Grid Purchased Electricity (kWh)", "elec_cost_value" : "Purchased Electricity Cost ($)", + "elec_cost_after_tax" : "Purchased Electricity Cost, After Tax ($)", "ng_reduction_value" : "Total Fuel (MMBtu)", "total_elec_costs" : "Total Electric Costs ($)", "fuel_costs" : "Fuel Cost ($)", + "fuel_costs_after_tax" : "Fuel Cost, After Tax ($)", + "om_costs_after_tax" : "Year 1 O&M Cost, After Tax ($)", "total_co2_emission_value" : "Total CO2 Emissions (tonnes)", "placeholder1_value" : "Placeholder1", "lcc_value" : "Lifecycle Costs ($)", @@ -985,6 +1054,10 @@ "name": "Electricity Cost Savings ($)", "formula": lambda col, bau, headers: f'={bau["elec_cost_value"]}-{col}{headers["Purchased Electricity Cost ($)"] + 2}' }, + { + "name": "Electricity Cost Savings, After Tax ($)", + "formula": lambda col, bau, headers: f'={bau["elec_cost_after_tax"]}-{col}{headers["Purchased Electricity Cost, After Tax ($)"] + 2}' + }, { "name": "NPV as a % of BAU LCC (%)", "formula": lambda col, bau, headers: f'=({col}{headers["Net Present Value ($)"] + 2}/{bau["lcc_value"]})' @@ -993,15 +1066,29 @@ "name": "Fuel Cost Savings ($)", "formula": lambda col, bau, headers: f'={bau["fuel_costs"]}-{col}{headers["Fuel Cost ($)"] + 2}' }, - { - "name": "Modified Net Upfront Capital Cost ($)", - "formula": lambda col, bau, headers: f'={col}{headers["Net Upfront Capital Cost After Incentives but without MACRS ($)"] + 2} - {col}{headers["Additional Upfront Incentive ($)"] + 2}+{col}{headers["Additional Upfront Cost ($)"] + 2}' + "name": "Fuel Cost Savings, After Tax ($)", + "formula": lambda col, bau, headers: f'={bau["fuel_costs_after_tax"]}-{col}{headers["Fuel Cost, After Tax ($)"] + 2}' + }, + { + "name": "Electricity Cost Savings, After Tax ($)", + "formula": lambda col, bau, headers: f'={bau["elec_cost_after_tax"]}-{col}{headers["Purchased Electricity Cost, After Tax ($)"] + 2}' + }, + { + "name": "Total Year One Savings, After Tax ($)", + "formula": lambda col, bau, headers: f'=({col}{headers["Electricity Cost Savings, After Tax ($)"] + 2}+{col}{headers["Fuel Cost Savings, After Tax ($)"] + 2}+({bau["om_costs_after_tax"]}-{col}{headers["Year 1 O&M Cost, After Tax ($)"] + 2}))' + }, + { + "name": "Modified Total Year One Savings, After Tax ($)", + "formula": lambda col, bau, headers: f'={col}{headers["Total Year One Savings, After Tax ($)"] + 2}+{col}{headers["Additional Yearly Cost Savings ($/Year)"] + 2}-{col}{headers["Additional Yearly Cost ($/Year)"] + 2}' + }, + { + "name": "Modified Total Capital Cost ($)", + "formula": lambda col, bau, headers: f'={col}{headers["Total Capital Cost After Non-Discounted Incentives ($)"] + 2}-{col}{headers["Additional Upfront Incentive ($)"] + 2}+{col}{headers["Additional Upfront Cost ($)"] + 2}' }, - { "name": "Modified Simple Payback Period (years)", - "formula": lambda col, bau, headers: f'=({col}{headers["Modified Net Upfront Capital Cost ($)"] + 2})/({col}{headers["Electricity Cost Savings ($)"] + 2}+{col}{headers["Fuel Cost Savings ($)"] + 2}+{col}{headers["Additional Yearly Cost Savings ($/Year)"] + 2}-{col}{headers["Year 1 O&M Cost, Before Tax ($)"] + 2}-{col}{headers["Additional Yearly Cost ($/Year)"] + 2})' + "formula": lambda col, bau, headers: f'={col}{headers["Modified Total Capital Cost ($)"] + 2}/{col}{headers["Modified Total Year One Savings, After Tax ($)"] + 2}' }, { "name": "CO2 Savings Including Unaddressable (%)", diff --git a/reoptjl/views.py b/reoptjl/views.py index 3dc915bc1..f56872a18 100644 --- a/reoptjl/views.py +++ b/reoptjl/views.py @@ -1765,13 +1765,14 @@ def generate_excel_workbook(df: pd.DataFrame, custom_table: List[Dict[str, Any]] # 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%', 'align': 'center', 'valign': 'center', 'border': 1, '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} # 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_percent_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_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}) # Message format for formula cells (blue background with white text) @@ -1808,13 +1809,18 @@ def get_combined_format(label, row_color, is_formula=False): return formula_currency_format elif '%' in label: return formula_percent_format + elif 'years' 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} if label: if '$' in label: return workbook.add_format({**base_currency_format, 'bg_color': row_color}) elif '%' in label: return workbook.add_format({**base_percent_format, 'bg_color': row_color}) + elif 'years' in label: + return workbook.add_format({**payback_data_format, 'bg_color': row_color}) return workbook.add_format(base_data_format) # Set column width for the first column (labels column) From 91ce0a672a773d900a08cbe1264b6abfc5b9c308 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Mon, 3 Mar 2025 10:44:06 -0700 Subject: [PATCH 13/32] Merge migrations after merging develop with emissions updates into after-tax --- reoptjl/migrations/0081_merge_20250303_1743.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 reoptjl/migrations/0081_merge_20250303_1743.py diff --git a/reoptjl/migrations/0081_merge_20250303_1743.py b/reoptjl/migrations/0081_merge_20250303_1743.py new file mode 100644 index 000000000..06f7f5c26 --- /dev/null +++ b/reoptjl/migrations/0081_merge_20250303_1743.py @@ -0,0 +1,14 @@ +# Generated by Django 4.0.7 on 2025-03-03 17:43 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('reoptjl', '0080_electricloadoutputs_annual_electric_load_with_thermal_conversions_kwh_and_more'), + ('reoptjl', '0080_financialoutputs_year_one_chp_standby_cost_after_tax_and_more'), + ] + + operations = [ + ] From ba15009b4ed676cd0564289c87c47e947b9ea386 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Mon, 3 Mar 2025 14:47:52 -0700 Subject: [PATCH 14/32] Merge REopt.jl develop into after-tax-savings --- 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 5fed6b542..b919a61c1 100644 --- a/julia_src/Manifest.toml +++ b/julia_src/Manifest.toml @@ -922,7 +922,7 @@ 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 = "b7d1b5cba5145f62ec15adf4f47d754d43294a24" +git-tree-sha1 = "43c36f1d4b30d7090d85382403b760236dfcfc15" repo-rev = "after-tax-savings" repo-url = "https://github.com/NREL/REopt.jl.git" uuid = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6" From 27c77e6fe95a7fa16a5c7e6c375cb662c79a6300 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Tue, 11 Mar 2025 21:28:34 -0600 Subject: [PATCH 15/32] Update some new field names per REopt.jl PR review --- julia_src/Manifest.toml | 2 +- reoptjl/custom_table_config.py | 4 +- ...outputs_capital_costs_after_non_discoun.py | 48 +++++++++++++++++++ reoptjl/models.py | 14 +++--- 4 files changed, 58 insertions(+), 10 deletions(-) create mode 100644 reoptjl/migrations/0082_rename_capital_costs_after_incentives_without_macrs_financialoutputs_capital_costs_after_non_discoun.py diff --git a/julia_src/Manifest.toml b/julia_src/Manifest.toml index b919a61c1..0f211040a 100644 --- a/julia_src/Manifest.toml +++ b/julia_src/Manifest.toml @@ -922,7 +922,7 @@ 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 = "43c36f1d4b30d7090d85382403b760236dfcfc15" +git-tree-sha1 = "09e1002f5093ce15d254e12fad04bdc220825085" repo-rev = "after-tax-savings" repo-url = "https://github.com/NREL/REopt.jl.git" uuid = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6" diff --git a/reoptjl/custom_table_config.py b/reoptjl/custom_table_config.py index 788bed8d6..971d1b834 100644 --- a/reoptjl/custom_table_config.py +++ b/reoptjl/custom_table_config.py @@ -548,8 +548,8 @@ { "label": "Total Capital Cost After Incentives Without MACRS ($)", "key": "total_capital_cost_after_incentives_without_macrs", - "bau_value": lambda df: safe_get(df, "outputs.Financial.capital_costs_after_incentives_without_macrs_bau"), - "scenario_value": lambda df: safe_get(df, "outputs.Financial.capital_costs_after_incentives_without_macrs") + "bau_value": lambda df: safe_get(df, "outputs.Financial.capital_costs_after_non_discounted_incentives_without_macrs_bau"), + "scenario_value": lambda df: safe_get(df, "outputs.Financial.capital_costs_after_non_discounted_incentives_without_macrs") }, { "label": "Total Capital Cost After Non-Discounted Incentives ($)", diff --git a/reoptjl/migrations/0082_rename_capital_costs_after_incentives_without_macrs_financialoutputs_capital_costs_after_non_discoun.py b/reoptjl/migrations/0082_rename_capital_costs_after_incentives_without_macrs_financialoutputs_capital_costs_after_non_discoun.py new file mode 100644 index 000000000..a934d8f77 --- /dev/null +++ b/reoptjl/migrations/0082_rename_capital_costs_after_incentives_without_macrs_financialoutputs_capital_costs_after_non_discoun.py @@ -0,0 +1,48 @@ +# Generated by Django 4.0.7 on 2025-03-12 03:14 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('reoptjl', '0081_merge_20250303_1743'), + ] + + operations = [ + migrations.RenameField( + model_name='financialoutputs', + old_name='capital_costs_after_incentives_without_macrs', + new_name='capital_costs_after_non_discounted_incentives_without_macrs', + ), + migrations.RenameField( + model_name='financialoutputs', + old_name='year_one_total_cost_after_tax', + new_name='year_one_total_operating_cost_after_tax', + ), + migrations.RenameField( + model_name='financialoutputs', + old_name='year_one_total_cost_after_tax_bau', + new_name='year_one_total_operating_cost_after_tax_bau', + ), + migrations.RenameField( + model_name='financialoutputs', + old_name='year_one_total_cost_before_tax', + new_name='year_one_total_operating_cost_before_tax', + ), + migrations.RenameField( + model_name='financialoutputs', + old_name='year_one_total_cost_before_tax_bau', + new_name='year_one_total_operating_cost_before_tax_bau', + ), + migrations.RenameField( + model_name='financialoutputs', + old_name='year_one_total_cost_savings_after_tax', + new_name='year_one_total_operating_cost_savings_after_tax', + ), + migrations.RenameField( + model_name='financialoutputs', + old_name='year_one_total_cost_savings_before_tax', + new_name='year_one_total_operating_cost_savings_before_tax', + ), + ] diff --git a/reoptjl/models.py b/reoptjl/models.py index 13ffee05d..51cc1b3a0 100644 --- a/reoptjl/models.py +++ b/reoptjl/models.py @@ -978,7 +978,7 @@ class FinancialOutputs(BaseModel, models.Model): null=True, blank=True, help_text="Up-front capital costs for all technologies, in present value, excluding replacement costs, including incentives." ) - capital_costs_after_incentives_without_macrs = models.FloatField( + capital_costs_after_non_discounted_incentives_without_macrs = models.FloatField( null=True, blank=True, help_text="Capital costs for all technologies, including present value of replacement costs and incentives except for MACRS." ) @@ -1034,27 +1034,27 @@ class FinancialOutputs(BaseModel, models.Model): null=True, blank=True, help_text=("Year one CHP standby charges, after tax.") ) - year_one_total_cost_before_tax = models.FloatField( + year_one_total_operating_cost_before_tax = models.FloatField( null=True, blank=True, help_text=("Year one total operating (electricity, fuel, O&M) costs, before tax.") ) - year_one_total_cost_before_tax_bau = models.FloatField( + year_one_total_operating_cost_before_tax_bau = models.FloatField( null=True, blank=True, help_text=("Year one total operating (electricity, fuel, O&M) costs, before tax in the BAU case.") ) - year_one_total_cost_after_tax = models.FloatField( + year_one_total_operating_cost_after_tax = models.FloatField( null=True, blank=True, help_text=("Year one total operating (electricity, fuel, O&M) costs, after tax.") ) - year_one_total_cost_after_tax_bau = models.FloatField( + year_one_total_operating_cost_after_tax_bau = models.FloatField( null=True, blank=True, help_text=("Year one total operating (electricity, fuel, O&M) costs, after tax in the BAU case.") ) - year_one_total_cost_savings_before_tax = models.FloatField( + year_one_total_operating_cost_savings_before_tax = models.FloatField( null=True, blank=True, help_text=("Year one total operating (electricity, fuel, O&M) cost savings compared to BAU case, before tax.") ) - year_one_total_cost_savings_after_tax = models.FloatField( + year_one_total_operating_cost_savings_after_tax = models.FloatField( null=True, blank=True, help_text=("Year one total operating (electricity, fuel, O&M) cost savings compared to BAU case, after tax.") ) From 47f5b46cc069ab6928a177fff2649f650585f0d5 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Wed, 12 Mar 2025 15:14:06 -0600 Subject: [PATCH 16/32] 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 0f211040a..f822a6ae3 100644 --- a/julia_src/Manifest.toml +++ b/julia_src/Manifest.toml @@ -922,11 +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 = "09e1002f5093ce15d254e12fad04bdc220825085" -repo-rev = "after-tax-savings" +git-tree-sha1 = "9946abe774e30d82f786e68296ad1fdf8bb7dba4" +repo-rev = "develop" repo-url = "https://github.com/NREL/REopt.jl.git" uuid = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6" -version = "0.51.0" +version = "0.51.1" [[deps.Random]] deps = ["SHA"] From 2c76556faa731d742d757500ed68ae53549ed8d8 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Fri, 21 Mar 2025 09:43:39 -0600 Subject: [PATCH 17/32] Update REopt.jl to latest registered version v0.51.1 --- julia_src/Manifest.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/julia_src/Manifest.toml b/julia_src/Manifest.toml index f822a6ae3..fc3e1581e 100644 --- a/julia_src/Manifest.toml +++ b/julia_src/Manifest.toml @@ -923,8 +923,6 @@ 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 = "9946abe774e30d82f786e68296ad1fdf8bb7dba4" -repo-rev = "develop" -repo-url = "https://github.com/NREL/REopt.jl.git" uuid = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6" version = "0.51.1" From 1a5d5288904de5168b5bf29cc3d28d9bbbdfc8c7 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Fri, 28 Mar 2025 21:11:23 -0600 Subject: [PATCH 18/32] Update unaddressable fuel emissions, Formatting --- reoptjl/custom_table_config.py | 126 +++++++++++++++++++++------------ reoptjl/views.py | 11 +-- 2 files changed, 89 insertions(+), 48 deletions(-) diff --git a/reoptjl/custom_table_config.py b/reoptjl/custom_table_config.py index 971d1b834..1380911b3 100644 --- a/reoptjl/custom_table_config.py +++ b/reoptjl/custom_table_config.py @@ -211,7 +211,7 @@ "scenario_value": lambda df: safe_get(df, "outputs.HotThermalStorage.size_gal") }, { - "label" : "Absorption Chiller Capacity (tons)", + "label" : "Absorption Chiller Capacity (ton)", "key" : "absorption_chiller_capacity", "bau_value" : lambda df: safe_get(df, "outputs.AbsorptionChiller.size_ton_bau"), "scenario_value": lambda df: safe_get(df, "outputs.AbsorptionChiller.size_ton") @@ -294,7 +294,7 @@ "scenario_value": lambda df: safe_get(df, "outputs.Financial.npv") }, { - "label" : "Payback Period, with escalation and inflation (years)", + "label" : "Payback Period, with escalation and inflation (yrs)", "key" : "payback_period", "bau_value" : lambda df: safe_get(df, "outputs.Financial.simple_payback_years_bau"), "scenario_value": lambda df: safe_get(df, "outputs.Financial.simple_payback_years") @@ -323,8 +323,8 @@ { "label" : "O&M Costs ($)", "key" : "om_costs", - "bau_value" : lambda df: safe_get(df, "outputs.Financial.om_and_replacement_present_cost_after_tax_bau"), - "scenario_value": lambda df: safe_get(df, "outputs.Financial.om_and_replacement_present_cost_after_tax") + "bau_value" : lambda df: safe_get(df, "outputs.Financial.lifecycle_om_costs_after_tax_bau"), + "scenario_value": lambda df: safe_get(df, "outputs.Financial.lifecycle_om_costs_after_tax") }, { "label" : "Total Electric Costs ($)", @@ -345,7 +345,7 @@ "scenario_value": lambda df: safe_get(df, "outputs.Financial.lifecycle_fuel_costs_after_tax")+ safe_get(df, "outputs.Financial.lifecycle_elecbill_after_tax") }, { - "label" : "Total Hypothetical Emissions Costs (not included in LCC)", + "label" : "Total Hypothetical Emissions Costs (not included in LCC) ($)", "key" : "total_emissions_costs", "bau_value" : lambda df: safe_get(df, "outputs.Financial.lifecycle_emissions_cost_climate_bau") + safe_get(df, "outputs.Financial.lifecycle_emissions_cost_health_bau"), "scenario_value": lambda df: safe_get(df, "outputs.Financial.lifecycle_emissions_cost_climate") + safe_get(df, "outputs.Financial.lifecycle_emissions_cost_health") @@ -493,11 +493,17 @@ "scenario_value": lambda df: "" }, { - "label" : "Annual % Renewable Electricity (%)", + "label" : "Annual % On-Site Renewable Electricity (%)", "key" : "annual_renewable_electricity", "bau_value" : lambda df: safe_get(df, "outputs.Site.onsite_renewable_electricity_fraction_of_elec_load_bau"), "scenario_value": lambda df: safe_get(df, "outputs.Site.onsite_renewable_electricity_fraction_of_elec_load") }, + { + "label" : "Annual % On-Site Renewable Energy (Elec+Fuel) (%)", + "key" : "annual_renewable_energy", + "bau_value" : lambda df: safe_get(df, "outputs.Site.onsite_renewable_energy_fraction_of_total_load_bau"), + "scenario_value": lambda df: safe_get(df, "outputs.Site.onsite_renewable_energy_fraction_of_total_load") + }, { "label" : "Annual CO2 Emissions (tonnes)", "key" : "annual_co2_emissions", @@ -523,14 +529,65 @@ "bau_value" : lambda df: safe_get(df, "outputs.Site.lifecycle_emissions_tonnes_CO2_bau"), "scenario_value": lambda df: safe_get(df, "outputs.Site.lifecycle_emissions_tonnes_CO2") }, - # CO2 (%) savings calculation + # CO2 savings (%) calculation { - "label" : "CO2 (%) savings", + "label" : "CO2 savings (%)", "key" : "co2_savings_percentage", "bau_value" : lambda df: "", "scenario_value": lambda df: "" }, #################################################################################################################################### + ##################### Playground - Consider Unaddressable Fuel Consumption in Emissions Reduction % Calculation #################### + ##################################################################################################################################### + { + "label": "Playground - Consider Unaddressable Fuel Consumption in Emissions Reduction % Calculation", + "key": "playground_emissions_reduction_separator", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + { + "label": "Unaddressable Heating Fuel from REopt Input (MMBtu/yr)", + "key": "unaddressable_heating_fuel_reopt", + "bau_value": lambda df: safe_get(df, "outputs.HeatingLoad.annual_total_unaddressable_heating_load_mmbtu"), + "scenario_value": lambda df: safe_get(df, "outputs.HeatingLoad.annual_total_unaddressable_heating_load_mmbtu") + }, + { + "label": "Unaddressable Heating Fuel CO2e Emissions from REopt Input (tonnes/yr)", + "key": "unaddressable_heating_fuel_co2_emissions_reopt", + "bau_value": lambda df: safe_get(df, "outputs.HeatingLoad.annual_emissions_from_unaddressable_heating_load_tonnes_CO2"), + "scenario_value": lambda df: safe_get(df, "outputs.HeatingLoad.annual_emissions_from_unaddressable_heating_load_tonnes_CO2") + }, + { + "label": "Additional Unaddressable Fuel Consumption (MMBtu/yr)", + "key": "additional_unaddressable_fuel_input", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + { + "label": "Additional Unaddressable Fuel Emissions Factor (lb-CO2e/MMBtu)", + "key": "additional_unaddressable_fuel_emissions_factor_input", + "bau_value": lambda df: safe_get(df, "inputs.ExistingBoiler.emissions_factor_lb_CO2_per_mmbtu"), + "scenario_value": lambda df: safe_get(df, "inputs.ExistingBoiler.emissions_factor_lb_CO2_per_mmbtu") + }, + { + "label": "Additional Unaddressable Fuel CO2e Emissions (tonnes/yr)", + "key": "additional_unaddressable_fuel_emissions", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + { + "label": "Total Unaddressable Fuel CO2e Emissions (tonnes/yr)", + "key": "total_unaddressable_co2_emissions", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + { + "label": "CO2e Savings Including Unaddressable Fuel (%)", + "key": "co2_savings_including_unaddressable", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, + #################################################################################################################################### ##################### Playground - Explore Effect of Additional Incentives or Costs, outside of REopt ############################## ##################################################################################################### { @@ -582,13 +639,13 @@ "scenario_value": lambda df: "" }, { - "label": "Additional Yearly Cost Savings ($/Year)", + "label": "Additional Yearly Cost Savings ($/yr)", "key": "additional_yearly_cost_savings_input", "bau_value": lambda df: "", "scenario_value": lambda df: "" }, { - "label": "Additional Yearly Cost ($/Year)", + "label": "Additional Yearly Cost ($/yr)", "key": "additional_yearly_cost_input", "bau_value": lambda df: "", "scenario_value": lambda df: "" @@ -606,38 +663,11 @@ "scenario_value": lambda df: "" }, { - "label": "Modified Simple Payback Period (years)", + "label": "Modified Simple Payback Period (yrs)", "key": "modified_simple_payback_period", "bau_value": lambda df: "", "scenario_value": lambda df: "" }, - #################################################################################################################################### - ##################### Playground - Consider Unaddressable Fuel Consumption in Emissions Reduction % Calculation #################### - ##################################################################################################################################### - { - "label": "Playground - Consider Unaddressable Fuel Consumption in Emissions Reduction % Calculation", - "key": "playground_emissions_reduction_separator", - "bau_value": lambda df: "", - "scenario_value": lambda df: "" - }, - { - "label": "Unaddressable Heating Load (Mmbtu/Year)", - "key": "unaddressable_heating_load", - "bau_value": lambda df: safe_get(df, "outputs.HeatingLoad.annual_total_unaddressable_heating_load_mmbtu"), - "scenario_value": lambda df: safe_get(df, "outputs.HeatingLoad.annual_total_unaddressable_heating_load_mmbtu") - }, - { - "label": "Unaddressable CO2 Emissions (tonnes)", - "key": "unaddressable_co2_emissions", - "bau_value": lambda df: safe_get(df, "outputs.HeatingLoad.annual_emissions_from_unaddressable_heating_load_tonnes_CO2"), - "scenario_value": lambda df: safe_get(df, "outputs.HeatingLoad.annual_emissions_from_unaddressable_heating_load_tonnes_CO2") - }, - { - "label": "CO2 Savings Including Unaddressable (%)", - "key": "co2_savings_including_unaddressable", - "bau_value": lambda df: "", - "scenario_value": lambda df: "" - }, ##################################################################################################### ############################# Annual Electric Production ############################# ##################################################################################################### @@ -1080,19 +1110,27 @@ }, { "name": "Modified Total Year One Savings, After Tax ($)", - "formula": lambda col, bau, headers: f'={col}{headers["Total Year One Savings, After Tax ($)"] + 2}+{col}{headers["Additional Yearly Cost Savings ($/Year)"] + 2}-{col}{headers["Additional Yearly Cost ($/Year)"] + 2}' + "formula": lambda col, bau, headers: f'={col}{headers["Total Year One Savings, After Tax ($)"] + 2}+{col}{headers["Additional Yearly Cost Savings ($/yr)"] + 2}-{col}{headers["Additional Yearly Cost ($/yr)"] + 2}' }, { "name": "Modified Total Capital Cost ($)", "formula": lambda col, bau, headers: f'={col}{headers["Total Capital Cost After Non-Discounted Incentives ($)"] + 2}-{col}{headers["Additional Upfront Incentive ($)"] + 2}+{col}{headers["Additional Upfront Cost ($)"] + 2}' }, { - "name": "Modified Simple Payback Period (years)", + "name": "Modified Simple Payback Period (yrs)", "formula": lambda col, bau, headers: f'={col}{headers["Modified Total Capital Cost ($)"] + 2}/{col}{headers["Modified Total Year One Savings, After Tax ($)"] + 2}' }, { - "name": "CO2 Savings Including Unaddressable (%)", - "formula": lambda col, bau, headers: f'=({bau["annual_co2_emissions_value"]}-{col}{headers["Annual CO2 Emissions (tonnes)"] + 2})/({bau["annual_co2_emissions_value"]}+{col}{headers["Unaddressable CO2 Emissions (tonnes)"] + 2})' + "name": "Additional Unaddressable Fuel CO2e Emissions (tonnes/yr)", + "formula": lambda col, bau, headers: f'={col}{headers["Additional Unaddressable Fuel Consumption (MMBtu/yr)"] + 2}*{col}{headers["Additional Unaddressable Fuel Emissions Factor (lb-CO2e/MMBtu)"] + 2}/2204.62' + }, + { + "name": "Total Unaddressable Fuel CO2e Emissions (tonnes/yr)", + "formula": lambda col, bau, headers: f'={col}{headers["Unaddressable Heating Fuel CO2e Emissions from REopt Input (tonnes/yr)"] + 2}+{col}{headers["Additional Unaddressable Fuel CO2e Emissions (tonnes/yr)"] + 2}' + }, + { + "name": "CO2e Savings Including Unaddressable Fuel (%)", + "formula": lambda col, bau, headers: f'=({bau["annual_co2_emissions_value"]}-{col}{headers["Annual CO2 Emissions (tonnes)"] + 2})/({bau["annual_co2_emissions_value"]}+{col}{headers["Total Unaddressable Fuel CO2e Emissions (tonnes/yr)"] + 2})' }, { "name": "Total Site Electricity Use (kWh)", @@ -1131,11 +1169,11 @@ "formula": lambda col, bau, headers: f'={bau["elec_cost_value"]}+-{col}{headers["Purchased Electricity Cost ($)"] + 2}' }, { - "name": "Simple Payback (years)", + "name": "Simple Payback (yrs)", "formula": lambda col, bau, headers: f'={col}{headers["Net Capital Cost ($)"] + 2}/{col}{headers["Annual Cost Savings ($)"] + 2}' }, { - "name": "CO2 (%) savings", + "name": "CO2 savings (%)", "formula": lambda col, bau, headers: f'=({bau["total_co2_emission_value"]}-{col}{headers["Total CO2 Emissions (tonnes)"] + 2})/{bau["total_co2_emission_value"]}' }, #Example Calculations diff --git a/reoptjl/views.py b/reoptjl/views.py index fb51f757a..6bbcf588c 100644 --- a/reoptjl/views.py +++ b/reoptjl/views.py @@ -1809,22 +1809,25 @@ def get_combined_format(label, row_color, is_formula=False): return formula_currency_format elif '%' in label: return formula_percent_format - elif 'years' in label: + 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} if label: if '$' in label: return workbook.add_format({**base_currency_format, 'bg_color': row_color}) elif '%' in label: return workbook.add_format({**base_percent_format, 'bg_color': row_color}) - elif 'years' in label: - return workbook.add_format({**payback_data_format, 'bg_color': row_color}) + elif 'yrs' in label: + return workbook.add_format({**payback_data_format, 'bg_color': row_color}) + elif 'URL' in label: + return workbook.add_format({**blue_text_format, 'bg_color': row_color}) return workbook.add_format(base_data_format) # Set column width for the first column (labels column) - worksheet.set_column(0, 0, 45) + worksheet.set_column(0, 0, 65) # Setting column widths and writing headers for other columns column_width = 25 From e9247e3b96691bde7d1d91604491b86b6abe5325 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Sat, 29 Mar 2025 08:01:13 -0600 Subject: [PATCH 19/32] Change CO2 labels to CO2e --- reoptjl/custom_table_config.py | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/reoptjl/custom_table_config.py b/reoptjl/custom_table_config.py index 1380911b3..622331704 100644 --- a/reoptjl/custom_table_config.py +++ b/reoptjl/custom_table_config.py @@ -505,33 +505,33 @@ "scenario_value": lambda df: safe_get(df, "outputs.Site.onsite_renewable_energy_fraction_of_total_load") }, { - "label" : "Annual CO2 Emissions (tonnes)", + "label" : "Annual CO2e Emissions (tonnes)", "key" : "annual_co2_emissions", "bau_value" : lambda df: safe_get(df, "outputs.Site.annual_emissions_tonnes_CO2_bau"), "scenario_value": lambda df: safe_get(df, "outputs.Site.annual_emissions_tonnes_CO2") }, # Added emissions from electricity and fuels { - "label" : "Annual CO2 Emissions from Electricity (tonnes)", + "label" : "Annual CO2e Emissions from Electricity (tonnes)", "key" : "annual_co2_emissions_electricity", "bau_value" : lambda df: safe_get(df, "outputs.ElectricUtility.annual_emissions_tonnes_CO2_bau"), "scenario_value": lambda df: safe_get(df, "outputs.ElectricUtility.annual_emissions_tonnes_CO2") }, { - "label" : "Annual CO2 Emissions from Fuel (tonnes)", + "label" : "Annual CO2e Emissions from Fuel (tonnes)", "key" : "annual_co2_emissions_fuel", "bau_value" : lambda df: safe_get(df, "outputs.Site.annual_emissions_from_fuelburn_tonnes_CO2_bau"), "scenario_value": lambda df: safe_get(df, "outputs.Site.annual_emissions_from_fuelburn_tonnes_CO2") }, { - "label" : "Total CO2 Emissions (tonnes)", + "label" : "Total CO2e Emissions (tonnes)", "key" : "co2_emissions", "bau_value" : lambda df: safe_get(df, "outputs.Site.lifecycle_emissions_tonnes_CO2_bau"), "scenario_value": lambda df: safe_get(df, "outputs.Site.lifecycle_emissions_tonnes_CO2") }, - # CO2 savings (%) calculation + # CO2e savings (%) calculation { - "label" : "CO2 savings (%)", + "label" : "CO2e savings (%)", "key" : "co2_savings_percentage", "bau_value" : lambda df: "", "scenario_value": lambda df: "" @@ -662,6 +662,12 @@ "bau_value": lambda df: "", "scenario_value": lambda df: "" }, + { + "label": "Modified Simple Payback Period Without Incentives (yrs)", + "key": "modified_simple_payback_period_without_incentives", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, { "label": "Modified Simple Payback Period (yrs)", "key": "modified_simple_payback_period", @@ -1045,10 +1051,10 @@ "fuel_costs" : "Fuel Cost ($)", "fuel_costs_after_tax" : "Fuel Cost, After Tax ($)", "om_costs_after_tax" : "Year 1 O&M Cost, After Tax ($)", - "total_co2_emission_value" : "Total CO2 Emissions (tonnes)", + "total_co2_emission_value" : "Total CO2e Emissions (tonnes)", "placeholder1_value" : "Placeholder1", "lcc_value" : "Lifecycle Costs ($)", - "annual_co2_emissions_value": "Annual CO2 Emissions (tonnes)" + "annual_co2_emissions_value": "Annual CO2e Emissions (tonnes)" } ''' @@ -1116,6 +1122,10 @@ "name": "Modified Total Capital Cost ($)", "formula": lambda col, bau, headers: f'={col}{headers["Total Capital Cost After Non-Discounted Incentives ($)"] + 2}-{col}{headers["Additional Upfront Incentive ($)"] + 2}+{col}{headers["Additional Upfront Cost ($)"] + 2}' }, + { + "name": "Modified Simple Payback Period Without Incentives (yrs)", + "formula": lambda col, bau, headers: f'={col}{headers["Total Capital Cost Before Incentives ($)"] + 2}/{col}{headers["Modified Total Year One Savings, After Tax ($)"] + 2}' + }, { "name": "Modified Simple Payback Period (yrs)", "formula": lambda col, bau, headers: f'={col}{headers["Modified Total Capital Cost ($)"] + 2}/{col}{headers["Modified Total Year One Savings, After Tax ($)"] + 2}' @@ -1130,7 +1140,7 @@ }, { "name": "CO2e Savings Including Unaddressable Fuel (%)", - "formula": lambda col, bau, headers: f'=({bau["annual_co2_emissions_value"]}-{col}{headers["Annual CO2 Emissions (tonnes)"] + 2})/({bau["annual_co2_emissions_value"]}+{col}{headers["Total Unaddressable Fuel CO2e Emissions (tonnes/yr)"] + 2})' + "formula": lambda col, bau, headers: f'=({bau["annual_co2_emissions_value"]}-{col}{headers["Annual CO2e Emissions (tonnes)"] + 2})/({bau["annual_co2_emissions_value"]}+{col}{headers["Total Unaddressable Fuel CO2e Emissions (tonnes/yr)"] + 2})' }, { "name": "Total Site Electricity Use (kWh)", @@ -1173,8 +1183,8 @@ "formula": lambda col, bau, headers: f'={col}{headers["Net Capital Cost ($)"] + 2}/{col}{headers["Annual Cost Savings ($)"] + 2}' }, { - "name": "CO2 savings (%)", - "formula": lambda col, bau, headers: f'=({bau["total_co2_emission_value"]}-{col}{headers["Total CO2 Emissions (tonnes)"] + 2})/{bau["total_co2_emission_value"]}' + "name": "CO2e savings (%)", + "formula": lambda col, bau, headers: f'=({bau["total_co2_emission_value"]}-{col}{headers["Total CO2e Emissions (tonnes)"] + 2})/{bau["total_co2_emission_value"]}' }, #Example Calculations # Calculation Without Reference to bau_cells From 66a03625dbd3c213242dc41b58780eb5d686fe4c Mon Sep 17 00:00:00 2001 From: bill-becker Date: Sat, 29 Mar 2025 08:08:27 -0600 Subject: [PATCH 20/32] Move Results Table to FIRST tab, Instructions to second tab --- reoptjl/views.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/reoptjl/views.py b/reoptjl/views.py index 6bbcf588c..7e7b3a830 100644 --- a/reoptjl/views.py +++ b/reoptjl/views.py @@ -1749,12 +1749,13 @@ def generate_results_table(request: Any) -> HttpResponse: def generate_excel_workbook(df: pd.DataFrame, custom_table: List[Dict[str, Any]], output: io.BytesIO) -> None: try: workbook = xlsxwriter.Workbook(output, {'in_memory': True}) - # Add the 'Instructions' worksheet - instructions_worksheet = workbook.add_worksheet('Instructions') - + # Add the 'Results Table' worksheet worksheet = workbook.add_worksheet('Results Table') + # Add the 'Instructions' worksheet + instructions_worksheet = workbook.add_worksheet('Instructions') + # Scenario header formatting with colors scenario_colors = ['#0B5E90', '#00A4E4','#f46d43','#fdae61', '#66c2a5', '#d53e4f', '#3288bd'] From 42b935ee95cb5d265c165d244d7354fedaee7c22 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Sat, 29 Mar 2025 08:27:05 -0600 Subject: [PATCH 21/32] Update instructions for playground updates --- reoptjl/views.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/reoptjl/views.py b/reoptjl/views.py index 7e7b3a830..5bdbc9e5b 100644 --- a/reoptjl/views.py +++ b/reoptjl/views.py @@ -2051,14 +2051,17 @@ def get_bau_column(col): row += 2 playground_items = [ - "- Net Upfront Capital Cost After Incentives but without MACRS ($): Represents the upfront cost after incentives, excluding MACRS depreciation benefits.", - "- Net Upfront Capital Cost After Incentives with MACRS ($): Includes MACRS depreciation, which provides tax benefits over the first 5-7 years.", - "- Additional Upfront Incentive ($): Input any additional grants or incentives (e.g., IAC grant, state or local grants).", + "- 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 ($/year): Input any ongoing yearly savings (e.g., improved productivity, product sales with ESG designation).", - "- Additional Yearly Cost ($/year): Input any additional yearly costs (e.g., microgrid operation and maintenance).", - "- Modified Net Upfront Capital Cost ($): This value recalculates based on your inputs.", - "- Modified Simple Payback Period (years): Recalculates the payback period based on your inputs, providing a more conventional 'simple' payback period." + "- 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) @@ -2070,9 +2073,9 @@ def get_bau_column(col): row += 1 unaddressable_notes = ( - "In scenarios where there is an unaddressable heating load (heating demand that cannot be served by the technologies analyzed), " + "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 CO₂ Emissions' row in the 'Playground' section includes these emissions, providing a more comprehensive view of your site's total emissions. " + "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) From 38a8b82905bef8b2bd1c347ce5abff9abc967460 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Sun, 30 Mar 2025 21:31:29 -0600 Subject: [PATCH 22/32] Fix lifecycle CHP standby charge reference --- reoptjl/custom_table_config.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/reoptjl/custom_table_config.py b/reoptjl/custom_table_config.py index 622331704..647a1b019 100644 --- a/reoptjl/custom_table_config.py +++ b/reoptjl/custom_table_config.py @@ -329,8 +329,8 @@ { "label" : "Total Electric Costs ($)", "key" : "total_electric_utility_costs", - "bau_value" : lambda df: safe_get(df, "outputs.Financial.lifecycle_elecbill_after_tax_bau") - safe_get(df, "outputs.Financial.lifecycle_export_benefit_after_tax_bau") + safe_get(df, "outputs.CHP.lifecycle_chp_standby_cost_after_tax_bau"), - "scenario_value": lambda df: safe_get(df, "outputs.Financial.lifecycle_elecbill_after_tax") - safe_get(df, "outputs.Financial.lifecycle_export_benefit_after_tax") + safe_get(df, "outputs.CHP.lifecycle_chp_standby_cost_after_tax") + "bau_value" : lambda df: safe_get(df, "outputs.Financial.lifecycle_elecbill_after_tax_bau") + safe_get(df, "outputs.Financial.lifecycle_chp_standby_cost_after_tax_bau"), + "scenario_value": lambda df: safe_get(df, "outputs.Financial.lifecycle_elecbill_after_tax") + safe_get(df, "outputs.Financial.lifecycle_chp_standby_cost_after_tax") }, { "label" : "Total Fuel Costs ($)", @@ -341,8 +341,8 @@ { "label" : "Total Utility Costs ($)", "key" : "total_utility_costs", - "bau_value" : lambda df: safe_get(df, "outputs.Financial.lifecycle_fuel_costs_after_tax_bau")+ safe_get(df, "outputs.Financial.lifecycle_elecbill_after_tax_bau"), - "scenario_value": lambda df: safe_get(df, "outputs.Financial.lifecycle_fuel_costs_after_tax")+ safe_get(df, "outputs.Financial.lifecycle_elecbill_after_tax") + "bau_value" : lambda df: safe_get(df, "outputs.Financial.lifecycle_elecbill_after_tax_bau") + safe_get(df, "outputs.Financial.lifecycle_chp_standby_cost_after_tax_bau") + safe_get(df, "outputs.Financial.lifecycle_fuel_costs_after_tax_bau"), + "scenario_value": lambda df: safe_get(df, "outputs.Financial.lifecycle_elecbill_after_tax") + safe_get(df, "outputs.Financial.lifecycle_chp_standby_cost_after_tax") + safe_get(df, "outputs.Financial.lifecycle_fuel_costs_after_tax") }, { "label" : "Total Hypothetical Emissions Costs (not included in LCC) ($)", @@ -1142,6 +1142,7 @@ "name": "CO2e Savings Including Unaddressable Fuel (%)", "formula": lambda col, bau, headers: f'=({bau["annual_co2_emissions_value"]}-{col}{headers["Annual CO2e Emissions (tonnes)"] + 2})/({bau["annual_co2_emissions_value"]}+{col}{headers["Total Unaddressable Fuel CO2e Emissions (tonnes/yr)"] + 2})' }, + # These below don't seem to be used currently { "name": "Total Site Electricity Use (kWh)", "formula": lambda col, bau, headers: ( From 442486ef406f84598a62f56eba392ba4e4b79ac0 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Sun, 30 Mar 2025 21:31:53 -0600 Subject: [PATCH 23/32] Improve description and formatting of playground sections --- reoptjl/views.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/reoptjl/views.py b/reoptjl/views.py index 5bdbc9e5b..fef600e5f 100644 --- a/reoptjl/views.py +++ b/reoptjl/views.py @@ -1992,6 +1992,9 @@ def get_bau_column(col): 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 }) @@ -2015,7 +2018,7 @@ def get_bau_column(col): "Please read the following instructions carefully to understand how to use this workbook effectively." ) instructions_worksheet.write(row, 0, general_instructions, text_format) - row += 4 + row += 3 # Using the 'Results Table' Sheet with formula format instructions_worksheet.write(row, 0, "Using the 'Results Table' Sheet", subtitle_format) @@ -2025,7 +2028,7 @@ def get_bau_column(col): "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, text_format) + instructions_worksheet.write(row, 0, custom_table_instructions, subsubtitle_format) row += 2 steps = [ @@ -2041,13 +2044,13 @@ def get_bau_column(col): row += 2 # Notes for the Playground Section - instructions_worksheet.write(row, 0, "Notes for the Playground Section", subtitle_format) + instructions_worksheet.write(row, 0, "Notes for the economic 'Playground' Section", subtitle_format) row += 1 playground_notes = ( - "The 'Playground' section allows you to explore the effects of additional incentives or costs on your project's financial metrics." + "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, text_format) + instructions_worksheet.write(row, 0, playground_notes, subsubtitle_format) row += 2 playground_items = [ @@ -2066,10 +2069,13 @@ def get_bau_column(col): for item in playground_items: instructions_worksheet.write(row, 0, item, bullet_format) row += 1 - row += 2 + row += 1 # Unaddressable Heating Load and Emissions - instructions_worksheet.write(row, 0, "Unaddressable Heating Load and Emissions", subtitle_format) + 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 = ( @@ -2079,14 +2085,13 @@ def get_bau_column(col): "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 += 2 + row += 3 # Final Note and Contact Info - instructions_worksheet.write(row, 0, "Thank you for using the REopt Results Table Workbook!", text_format) + 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, text_format) - + instructions_worksheet.write(row, 0, contact_info, subtitle_format) # Freeze panes to keep the title visible instructions_worksheet.freeze_panes(1, 0) From 601d92e0cb77439dcadeca1daf3f0d575886c979 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Sun, 30 Mar 2025 21:50:40 -0600 Subject: [PATCH 24/32] Update units label with "per year" where applicable --- reoptjl/custom_table_config.py | 154 ++++++++++++++++----------------- 1 file changed, 77 insertions(+), 77 deletions(-) diff --git a/reoptjl/custom_table_config.py b/reoptjl/custom_table_config.py index 647a1b019..389a1229f 100644 --- a/reoptjl/custom_table_config.py +++ b/reoptjl/custom_table_config.py @@ -276,13 +276,13 @@ "scenario_value": lambda df: "" }, { - "label" : "Year 1 O&M Cost, Before Tax ($)", + "label" : "Year 1 O&M Cost, Before Tax ($/yr)", "key" : "year_1_om_cost_before_tax", "bau_value" : lambda df: safe_get(df, "outputs.Financial.year_one_om_costs_before_tax_bau"), "scenario_value": lambda df: safe_get(df, "outputs.Financial.year_one_om_costs_before_tax") }, { - "label" : "Year 1 O&M Cost, After Tax ($)", + "label" : "Year 1 O&M Cost, After Tax ($/yr)", "key" : "year_1_om_cost_after_tax", "bau_value" : lambda df: safe_get(df, "outputs.Financial.year_one_om_costs_after_tax_bau"), "scenario_value": lambda df: safe_get(df, "outputs.Financial.year_one_om_costs_after_tax") @@ -373,61 +373,61 @@ "scenario_value": lambda df: "" }, { - "label" : "Electric Grid Purchases (kWh)", + "label" : "Electric Grid Purchases (kWh/yr)", "key" : "electric_grid_purchases", "bau_value" : lambda df: safe_get(df, "outputs.ElectricUtility.annual_energy_supplied_kwh_bau"), "scenario_value": lambda df: safe_get(df, "outputs.ElectricUtility.annual_energy_supplied_kwh") }, { - "label" : "Energy Charges ($)", + "label" : "Energy Charges ($/yr)", "key" : "electricity_energy_cost", "bau_value" : lambda df: safe_get(df, "outputs.ElectricTariff.year_one_energy_cost_before_tax_bau"), "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.year_one_energy_cost_before_tax") }, { - "label" : "Demand Charges ($)", + "label" : "Demand Charges ($/yr)", "key" : "electricity_demand_cost", "bau_value" : lambda df: safe_get(df, "outputs.ElectricTariff.year_one_demand_cost_before_tax_bau"), "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.year_one_demand_cost_before_tax") }, { - "label" : "Fixed Charges ($)", + "label" : "Fixed Charges ($/yr)", "key" : "utility_fixed_cost", "bau_value" : lambda df: safe_get(df, "outputs.ElectricTariff.year_one_fixed_cost_before_tax_bau"), "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.year_one_fixed_cost_before_tax") }, { - "label" : "Standby Charges For CHP ($)", + "label" : "Standby Charges For CHP ($/yr)", "key" : "standby_charges_for_CHP", "bau_value" : lambda df: safe_get(df, "outputs.CHP.year_one_standby_cost_before_tax_bau"), "scenario_value": lambda df: safe_get(df, "outputs.CHP.year_one_standby_cost_before_tax") }, { - "label" : "Export Credits ($)", + "label" : "Export Credits ($/yr)", "key" : "export_credits", "bau_value" : lambda df: -1 * safe_get(df, "outputs.ElectricTariff.year_one_export_benefit_before_tax_bau"), "scenario_value": lambda df: -1 * safe_get(df, "outputs.ElectricTariff.year_one_export_benefit_before_tax") }, { - "label" : "Purchased Electricity Cost ($)", + "label" : "Purchased Electricity Cost ($/yr)", "key" : "purchased_electricity_cost", "bau_value" : lambda df: safe_get(df, "outputs.ElectricTariff.year_one_bill_before_tax_bau") + safe_get(df, "outputs.CHP.year_one_standby_cost_before_tax_bau") - safe_get(df, "outputs.ElectricTariff.year_one_export_benefit_before_tax_bau"), "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.year_one_bill_before_tax") + safe_get(df, "outputs.CHP.year_one_standby_cost_before_tax") - safe_get(df, "outputs.ElectricTariff.year_one_export_benefit_before_tax") }, { - "label" : "Purchased Electricity Cost, After Tax ($)", + "label" : "Purchased Electricity Cost, After Tax ($/yr)", "key" : "purchased_electricity_cost", "bau_value" : lambda df: safe_get(df, "outputs.ElectricTariff.year_one_bill_after_tax_bau") + safe_get(df, "outputs.CHP.year_one_standby_cost_after_tax_bau") - safe_get(df, "outputs.ElectricTariff.year_one_export_benefit_after_tax_bau"), "scenario_value": lambda df: safe_get(df, "outputs.ElectricTariff.year_one_bill_after_tax") + safe_get(df, "outputs.CHP.year_one_standby_cost_after_tax") - safe_get(df, "outputs.ElectricTariff.year_one_export_benefit_after_tax") }, { - "label" : "Electricity Cost Savings ($)", + "label" : "Electricity Cost Savings ($/yr)", "key" : "electricity_cost_savings", "bau_value" : lambda df: "", "scenario_value": lambda df: "" }, { - "label" : "Electricity Cost Savings, After Tax ($)", + "label" : "Electricity Cost Savings, After Tax ($/yr)", "key" : "electricity_cost_savings_after_tax", "bau_value" : lambda df: "", "scenario_value": lambda df: "" @@ -442,43 +442,43 @@ "scenario_value": lambda df: "" }, { - "label" : "Boiler Fuel Cost ($)", + "label" : "Boiler Fuel Cost ($/yr)", "key" : "boiler_fuel_cost", "bau_value" : lambda df: safe_get(df, "outputs.ExistingBoiler.year_one_fuel_cost_before_tax_bau"), "scenario_value": lambda df: safe_get(df, "outputs.ExistingBoiler.year_one_fuel_cost_before_tax") }, { - "label" : "CHP Fuel Cost ($)", + "label" : "CHP Fuel Cost ($/yr)", "key" : "chp_fuel_cost", "bau_value" : lambda df : safe_get(df, "outputs.CHP.year_one_fuel_cost_before_tax_bau"), "scenario_value": lambda df : safe_get(df, "outputs.CHP.year_one_fuel_cost_before_tax") }, { - "label" : "Backup Generator Fuel Cost ($)", + "label" : "Backup Generator Fuel Cost ($/yr)", "key" : "backup_generator_fuel_cost", "bau_value" : lambda df: safe_get(df, "outputs.Generator.year_one_fuel_cost_before_tax_bau"), "scenario_value": lambda df: safe_get(df, "outputs.Generator.year_one_fuel_cost_before_tax") }, { - "label" : "Fuel Cost ($)", + "label" : "Fuel Cost ($/yr)", "key" : "fuel_cost", "bau_value" : lambda df: safe_get(df, "outputs.Financial.year_one_fuel_cost_before_tax_bau"), "scenario_value": lambda df: safe_get(df, "outputs.Financial.year_one_fuel_cost_before_tax") }, { - "label" : "Fuel Cost, After Tax ($)", + "label" : "Fuel Cost, After Tax ($/yr)", "key" : "fuel_cost", "bau_value" : lambda df: safe_get(df, "outputs.Financial.year_one_fuel_cost_after_tax_bau"), "scenario_value": lambda df: safe_get(df, "outputs.Financial.year_one_fuel_cost_after_tax") }, { - "label" : "Fuel Cost Savings ($)", + "label" : "Fuel Cost Savings ($/yr)", "key" : "fuel_cost_savings", "bau_value" : lambda df: "", "scenario_value": lambda df: "" }, { - "label" : "Fuel Cost Savings, After Tax ($)", + "label" : "Fuel Cost Savings, After Tax ($/yr)", "key" : "fuel_cost_savings_after_tax", "bau_value" : lambda df: "", "scenario_value": lambda df: "" @@ -505,20 +505,20 @@ "scenario_value": lambda df: safe_get(df, "outputs.Site.onsite_renewable_energy_fraction_of_total_load") }, { - "label" : "Annual CO2e Emissions (tonnes)", + "label" : "Annual CO2e Emissions (tonnes/yr)", "key" : "annual_co2_emissions", "bau_value" : lambda df: safe_get(df, "outputs.Site.annual_emissions_tonnes_CO2_bau"), "scenario_value": lambda df: safe_get(df, "outputs.Site.annual_emissions_tonnes_CO2") }, # Added emissions from electricity and fuels { - "label" : "Annual CO2e Emissions from Electricity (tonnes)", + "label" : "Annual CO2e Emissions from Electricity (tonnes/yr)", "key" : "annual_co2_emissions_electricity", "bau_value" : lambda df: safe_get(df, "outputs.ElectricUtility.annual_emissions_tonnes_CO2_bau"), "scenario_value": lambda df: safe_get(df, "outputs.ElectricUtility.annual_emissions_tonnes_CO2") }, { - "label" : "Annual CO2e Emissions from Fuel (tonnes)", + "label" : "Annual CO2e Emissions from Fuel (tonnes/yr)", "key" : "annual_co2_emissions_fuel", "bau_value" : lambda df: safe_get(df, "outputs.Site.annual_emissions_from_fuelburn_tonnes_CO2_bau"), "scenario_value": lambda df: safe_get(df, "outputs.Site.annual_emissions_from_fuelburn_tonnes_CO2") @@ -621,7 +621,7 @@ "scenario_value": lambda df: safe_get(df, "inputs.Financial.offtaker_tax_rate_fraction") }, { - "label": "Total Year One Savings, After Tax ($)", + "label": "Total Year One Savings, After Tax ($/yr)", "key": "total_year_one_savings_after_tax", "bau_value": lambda df: "", "scenario_value": lambda df: "" @@ -651,7 +651,7 @@ "scenario_value": lambda df: "" }, { - "label": "Modified Total Year One Savings, After Tax ($)", + "label": "Modified Total Year One Savings, After Tax ($/yr)", "key": "modified_total_year_one_savings_after_tax", "bau_value": lambda df: "", "scenario_value": lambda df: "" @@ -685,151 +685,151 @@ "comments" : "Split into Electric, Heating, and Cooling Sections" }, { - "label" : "Grid Serving Load (kWh)", + "label" : "Grid Serving Load (kWh/yr)", "key" : "grid_serving_load", "bau_value" : lambda df: safe_get(df, "outputs.ElectricUtility.electric_to_load_series_kw_bau"), "scenario_value": lambda df: safe_get(df, "outputs.ElectricUtility.electric_to_load_series_kw") }, { - "label" : "Grid Charging Battery (kWh)", + "label" : "Grid Charging Battery (kWh/yr)", "key" : "grid_charging_battery", "bau_value" : lambda df: safe_get(df, "outputs.ElectricUtility.electric_to_storage_series_kw_bau"), "scenario_value": lambda df: safe_get(df, "outputs.ElectricUtility.electric_to_storage_series_kw") }, { - "label" : "PV Serving Load (kWh)", + "label" : "PV Serving Load (kWh/yr)", "key" : "pv_serving_load", "bau_value" : lambda df: safe_get(df, "outputs.PV.electric_to_load_series_kw_bau"), "scenario_value": lambda df: safe_get(df, "outputs.PV.electric_to_load_series_kw") }, { - "label" : "PV Charging Battery (kWh)", + "label" : "PV Charging Battery (kWh/yr)", "key" : "pv_charging_battery", "bau_value" : lambda df: safe_get(df, "outputs.PV.electric_to_storage_series_kw_bau"), "scenario_value": lambda df: safe_get(df, "outputs.PV.electric_to_storage_series_kw") }, { - "label" : "PV Exported to Grid (kWh)", + "label" : "PV Exported to Grid (kWh/yr)", "key" : "pv_exported_to_grid", "bau_value" : lambda df: safe_get(df, "outputs.PV.electric_to_grid_series_kw_bau"), "scenario_value": lambda df: safe_get(df, "outputs.PV.electric_to_grid_series_kw") }, { - "label" : "PV Curtailment (kWh)", + "label" : "PV Curtailment (kWh/yr)", "key" : "pv_curtailment", "bau_value" : lambda df: safe_get(df, "outputs.PV.electric_curtailed_series_kw_bau"), "scenario_value": lambda df: safe_get(df, "outputs.PV.electric_curtailed_series_kw") }, { - "label" : "PV Year One Electricity Produced (kWh)", + "label" : "PV Year One Electricity Produced (kWh/yr)", "key" : "pv_year_one_electricity_produced", "bau_value" : lambda df: safe_get(df, "outputs.PV.year_one_energy_produced_kwh_bau"), "scenario_value": lambda df: safe_get(df, "outputs.PV.year_one_energy_produced_kwh") }, { - "label" : "Wind Serving Load (kWh)", + "label" : "Wind Serving Load (kWh/yr)", "key" : "wind_serving_load", "bau_value" : lambda df: safe_get(df, "outputs.Wind.electric_to_load_series_kw_bau"), "scenario_value": lambda df: safe_get(df, "outputs.Wind.electric_to_load_series_kw") }, { - "label" : "Wind Charging Battery (kWh)", + "label" : "Wind Charging Battery (kWh/yr)", "key" : "wind_charging_battery", "bau_value" : lambda df: safe_get(df, "outputs.Wind.electric_to_storage_series_kw_bau"), "scenario_value": lambda df: safe_get(df, "outputs.Wind.electric_to_storage_series_kw") }, { - "label" : "Wind Exported to Grid (kWh)", + "label" : "Wind Exported to Grid (kWh/yr)", "key" : "wind_exported_to_grid", "bau_value" : lambda df: safe_get(df, "outputs.Wind.electric_to_grid_series_kw_bau"), "scenario_value": lambda df: safe_get(df, "outputs.Wind.electric_to_grid_series_kw") }, { - "label" : "Wind Curtailment (kWh)", + "label" : "Wind Curtailment (kWh/yr)", "key" : "wind_curtailment", "bau_value" : lambda df: safe_get(df, "outputs.Wind.electric_curtailed_series_kw_bau"), "scenario_value": lambda df: safe_get(df, "outputs.Wind.electric_curtailed_series_kw") }, { - "label" : "Wind Total Electricity Produced (kWh)", + "label" : "Wind Total Electricity Produced (kWh/yr)", "key" : "wind_total_electricity_produced", "bau_value" : lambda df: safe_get(df, "outputs.Wind.annual_energy_produced_kwh_bau"), "scenario_value": lambda df: safe_get(df, "outputs.Wind.annual_energy_produced_kwh") }, { - "label" : "Battery Serving Load (kWh)", + "label" : "Battery Serving Load (kWh/yr)", "key" : "battery_serving_load", "bau_value" : lambda df: safe_get(df, "outputs.ElectricStorage.storage_to_load_series_kw_bau"), "scenario_value": lambda df: safe_get(df, "outputs.ElectricStorage.storage_to_load_series_kw") }, { - "label" : "Generator Serving Load (kWh)", + "label" : "Generator Serving Load (kWh/yr)", "key" : "generator_serving_load", "bau_value" : lambda df: safe_get(df, "outputs.Generator.electric_to_load_series_kw_bau"), "scenario_value": lambda df: safe_get(df, "outputs.Generator.electric_to_load_series_kw") }, { - "label" : "Generator Charging Battery (kWh)", + "label" : "Generator Charging Battery (kWh/yr)", "key" : "generator_charging_battery", "bau_value" : lambda df: safe_get(df, "outputs.Generator.electric_to_storage_series_kw_bau"), "scenario_value": lambda df: safe_get(df, "outputs.Generator.electric_to_storage_series_kw") }, { - "label" : "Generator Exported to Grid (kWh)", + "label" : "Generator Exported to Grid (kWh/yr)", "key" : "generator_exported_to_grid", "bau_value" : lambda df: safe_get(df, "outputs.Generator.electric_to_grid_series_kw_bau"), "scenario_value": lambda df: safe_get(df, "outputs.Generator.electric_to_grid_series_kw") }, { - "label" : "Generator Total Electricity Produced (kWh)", + "label" : "Generator Total Electricity Produced (kWh/yr)", "key" : "generator_total_electricity_produced", "bau_value" : lambda df: safe_get(df, "outputs.Generator.annual_energy_produced_kwh_bau"), "scenario_value": lambda df: safe_get(df, "outputs.Generator.annual_energy_produced_kwh") }, { - "label" : "CHP Serving Load (kWh)", + "label" : "CHP Serving Load (kWh/yr)", "key" : "chp_serving_load", "bau_value" : lambda df: safe_get(df, "outputs.CHP.electric_to_load_series_kw_bau"), "scenario_value": lambda df: safe_get(df, "outputs.CHP.electric_to_load_series_kw") }, { - "label" : "CHP Charging Battery (kWh)", + "label" : "CHP Charging Battery (kWh/yr)", "key" : "chp_charging_battery", "bau_value" : lambda df: safe_get(df, "outputs.CHP.electric_to_storage_series_kw_bau"), "scenario_value": lambda df: safe_get(df, "outputs.CHP.electric_to_storage_series_kw") }, { - "label" : "CHP Exported to Grid (kWh)", + "label" : "CHP Exported to Grid (kWh/yr)", "key" : "chp_exported_to_grid", "bau_value" : lambda df: safe_get(df, "outputs.CHP.electric_to_grid_series_kw_bau"), "scenario_value": lambda df: safe_get(df, "outputs.CHP.electric_to_grid_series_kw") }, { - "label" : "CHP Total Electricity Produced (kWh)", + "label" : "CHP Total Electricity Produced (kWh/yr)", "key" : "chp_total_electricity_produced", "bau_value" : lambda df: safe_get(df, "outputs.CHP.annual_electric_production_kwh_bau"), "scenario_value": lambda df: safe_get(df, "outputs.CHP.annual_electric_production_kwh") }, { - "label" : "Steam Turbine Serving Load (kWh)", + "label" : "Steam Turbine Serving Load (kWh/yr)", "key" : "steam_turbine_serving_load", "bau_value" : lambda df: safe_get(df, "outputs.SteamTurbine.electric_to_load_series_kw_bau"), "scenario_value": lambda df: safe_get(df, "outputs.SteamTurbine.electric_to_load_series_kw") }, { - "label" : "Steam Turbine Charging Battery (kWh)", + "label" : "Steam Turbine Charging Battery (kWh/yr)", "key" : "steam_turbine_charging_battery", "bau_value" : lambda df: safe_get(df, "outputs.SteamTurbine.electric_to_storage_series_kw_bau"), "scenario_value": lambda df: safe_get(df, "outputs.SteamTurbine.electric_to_storage_series_kw") }, { - "label" : "Steam Turbine Exported to Grid (kWh)", + "label" : "Steam Turbine Exported to Grid (kWh/yr)", "key" : "steam_turbine_exported_to_grid", "bau_value" : lambda df: safe_get(df, "outputs.SteamTurbine.electric_to_grid_series_kw_bau"), "scenario_value": lambda df: safe_get(df, "outputs.SteamTurbine.electric_to_grid_series_kw") }, { - "label" : "Steam Turbine Total Electricity Produced (kWh)", + "label" : "Steam Turbine Total Electricity Produced (kWh/yr)", "key" : "steam_turbine_total_electricity_produced", "bau_value" : lambda df: safe_get(df, "outputs.SteamTurbine.annual_electric_production_kwh_bau"), "scenario_value": lambda df: safe_get(df, "outputs.SteamTurbine.annual_electric_production_kwh") @@ -844,115 +844,115 @@ "scenario_value": lambda df: "" }, { - "label" : "Existing Heating System Serving Thermal Load (MMBtu)", + "label" : "Existing Heating System Serving Thermal Load (MMBtu/yr)", "key" : "existing_heating_system_serving_thermal_load", "bau_value" : lambda df: safe_get(df, "outputs.ExistingBoiler.thermal_to_load_series_mmbtu_per_hour_bau"), "scenario_value": lambda df: safe_get(df, "outputs.ExistingBoiler.thermal_to_load_series_mmbtu_per_hour") }, { - "label" : "Existing Heating System Thermal to Steam Turbine (MMBtu)", + "label" : "Existing Heating System Thermal to Steam Turbine (MMBtu/yr)", "key" : "existing_heating_system_thermal_to_steam_turbine", "bau_value" : lambda df: safe_get(df, "outputs.ExistingBoiler.thermal_to_steamturbine_series_mmbtu_per_hour_bau"), "scenario_value": lambda df: safe_get(df, "outputs.ExistingBoiler.thermal_to_steamturbine_series_mmbtu_per_hour") }, { - "label" : "Existing Heating System Charging Hot Water Storage (MMBtu)", + "label" : "Existing Heating System Charging Hot Water Storage (MMBtu/yr)", "key" : "existing_heating_system_charging_hot_water_storage", "bau_value" : lambda df: safe_get(df, "outputs.ExistingBoiler.thermal_to_storage_series_mmbtu_per_hour_bau"), "scenario_value": lambda df: safe_get(df, "outputs.ExistingBoiler.thermal_to_storage_series_mmbtu_per_hour") }, { - "label" : "Existing Heating System Total Thermal Produced (MMBtu)", + "label" : "Existing Heating System Total Thermal Produced (MMBtu/yr)", "key" : "existing_heating_system_total_thermal_produced", "bau_value" : lambda df : safe_get(df, "outputs.ExistingBoiler.annual_thermal_production_mmbtu_bau"), "scenario_value": lambda df : safe_get(df, "outputs.ExistingBoiler.annual_thermal_production_mmbtu") }, { - "label" : "CHP Serving Thermal Load (MMBtu)", + "label" : "CHP Serving Thermal Load (MMBtu/yr)", "key" : "chp_serving_thermal_load", "bau_value" : lambda df: safe_get(df, "outputs.CHP.thermal_to_load_series_mmbtu_per_hour_bau"), "scenario_value": lambda df: safe_get(df, "outputs.CHP.thermal_to_load_series_mmbtu_per_hour") }, { - "label" : "CHP Charging Hot Water Storage (MMBtu)", + "label" : "CHP Charging Hot Water Storage (MMBtu/yr)", "key" : "chp_charging_hot_water_storage", "bau_value" : lambda df: safe_get(df, "outputs.CHP.thermal_to_storage_series_mmbtu_per_hour_bau"), "scenario_value": lambda df: safe_get(df, "outputs.CHP.thermal_to_storage_series_mmbtu_per_hour") }, { - "label" : "CHP Thermal to Steam Turbine (MMBtu)", + "label" : "CHP Thermal to Steam Turbine (MMBtu/yr)", "key" : "chp_thermal_to_steam_turbine", "bau_value" : lambda df: safe_get(df, "outputs.CHP.thermal_to_steamturbine_series_mmbtu_per_hour_bau"), "scenario_value": lambda df: safe_get(df, "outputs.CHP.thermal_to_steamturbine_series_mmbtu_per_hour") }, { - "label" : "CHP Thermal Vented (MMBtu)", + "label" : "CHP Thermal Vented (MMBtu/yr)", "key" : "chp_thermal_vented", "bau_value" : lambda df: safe_get(df, "outputs.CHP.thermal_curtailed_series_mmbtu_per_hour_bau"), "scenario_value": lambda df: safe_get(df, "outputs.CHP.thermal_curtailed_series_mmbtu_per_hour") }, { - "label" : "CHP Total Thermal Produced (MMBtu)", + "label" : "CHP Total Thermal Produced (MMBtu/yr)", "key" : "chp_total_thermal_produced", "bau_value" : lambda df: safe_get(df, "outputs.CHP.annual_thermal_production_mmbtu_bau"), "scenario_value": lambda df: safe_get(df, "outputs.CHP.annual_thermal_production_mmbtu") }, { - "label" : "Steam Turbine Serving Thermal Load (MMBtu)", + "label" : "Steam Turbine Serving Thermal Load (MMBtu/yr)", "key" : "steam_turbine_serving_thermal_load", "bau_value" : lambda df: safe_get(df, "outputs.SteamTurbine.thermal_to_load_series_mmbtu_per_hour_bau"), "scenario_value": lambda df: safe_get(df, "outputs.SteamTurbine.thermal_to_load_series_mmbtu_per_hour") }, { - "label" : "Steam Turbine Charging Hot Water Storage (MMBtu)", + "label" : "Steam Turbine Charging Hot Water Storage (MMBtu/yr)", "key" : "steam_turbine_charging_hot_water_storage", "bau_value" : lambda df: safe_get(df, "outputs.SteamTurbine.thermal_to_storage_series_mmbtu_per_hour_bau"), "scenario_value": lambda df: safe_get(df, "outputs.SteamTurbine.thermal_to_storage_series_mmbtu_per_hour") }, { - "label" : "Steam Turbine Total Thermal Produced (MMBtu)", + "label" : "Steam Turbine Total Thermal Produced (MMBtu/yr)", "key" : "steam_turbine_total_thermal_produced", "bau_value" : lambda df : safe_get(df, "outputs.SteamTurbine.annual_thermal_production_mmbtu_bau"), "scenario_value": lambda df: safe_get(df, "outputs.SteamTurbine.annual_thermal_production_mmbtu") }, { - "label" : "GHP Reduction of Thermal Load (MMBtu)", + "label" : "GHP Reduction of Thermal Load (MMBtu/yr)", "key" : "ghp_reduction_of_thermal_load", "bau_value" : lambda df: safe_get(df, "outputs.GHP.space_heating_thermal_load_reduction_with_ghp_mmbtu_per_hour_bau"), "scenario_value": lambda df: safe_get(df, "outputs.GHP.space_heating_thermal_load_reduction_with_ghp_mmbtu_per_hour") }, { - "label" : "GHP Serving Thermal Load (MMBtu)", + "label" : "GHP Serving Thermal Load (MMBtu/yr)", "key" : "ghp_serving_thermal_load", "bau_value" : lambda df: safe_get(df, "outputs.GHP.thermal_to_space_heating_load_series_mmbtu_per_hour_bau") + safe_get(df, "outputs.GHP.thermal_to_dhw_load_series_mmbtu_per_hour_bau"), "scenario_value": lambda df: safe_get(df, "outputs.GHP.thermal_to_space_heating_load_series_mmbtu_per_hour") + safe_get(df, "outputs.GHP.thermal_to_dhw_load_series_mmbtu_per_hour") }, { - "label" : "ASHP Serving Thermal Load (MMBtu)", + "label" : "ASHP Serving Thermal Load (MMBtu/yr)", "key" : "ashp_serving_thermal_load", "bau_value" : lambda df : safe_get(df, "outputs.ASHPSpaceHeater.thermal_to_load_series_mmbtu_per_hour_bau"), "scenario_value": lambda df : safe_get(df, "outputs.ASHPSpaceHeater.thermal_to_load_series_mmbtu_per_hour") }, { - "label" : "ASHP Charging Hot Water Storage (MMBtu)", + "label" : "ASHP Charging Hot Water Storage (MMBtu/yr)", "key" : "ashp_charging_hot_water_storage", "bau_value" : lambda df: safe_get(df, "outputs.ASHPSpaceHeater.thermal_to_storage_series_mmbtu_per_hour_bau"), "scenario_value": lambda df: safe_get(df, "outputs.ASHPSpaceHeater.thermal_to_storage_series_mmbtu_per_hour") }, { - "label" : "ASHP Water Heater Serving Thermal Load (MMBtu)", + "label" : "ASHP Water Heater Serving Thermal Load (MMBtu/yr)", "key" : "ashp_water_heater_serving_thermal_load", "bau_value" : lambda df: safe_get(df, "outputs.ASHPWaterHeater.thermal_to_load_series_mmbtu_per_hour_bau"), "scenario_value": lambda df: safe_get(df, "outputs.ASHPWaterHeater.thermal_to_load_series_mmbtu_per_hour") }, { - "label" : "ASHP Water Heater Charging Hot Water Storage (MMBtu)", + "label" : "ASHP Water Heater Charging Hot Water Storage (MMBtu/yr)", "key" : "ashp_water_heater_charging_hot_water_storage", "bau_value" : lambda df: safe_get(df, "outputs.ASHPWaterHeater.thermal_to_storage_series_mmbtu_per_hour_bau"), "scenario_value": lambda df: safe_get(df, "outputs.ASHPWaterHeater.thermal_to_storage_series_mmbtu_per_hour") }, { - "label" : "Hot Water Storage Serving Thermal Load (MMBtu)", + "label" : "Hot Water Storage Serving Thermal Load (MMBtu/yr)", "key" : "hot_water_storage_serving_thermal_load", "bau_value" : lambda df: safe_get(df, "outputs.HotThermalStorage.storage_to_load_series_mmbtu_per_hour_bau"), "scenario_value": lambda df: safe_get(df, "outputs.HotThermalStorage.storage_to_load_series_mmbtu_per_hour") @@ -968,55 +968,55 @@ "scenario_value": lambda df: "" }, { - "label" : "Existing Cooling Plant Serving Thermal Load (ton-hr)", + "label" : "Existing Cooling Plant Serving Thermal Load (ton-hr/yr)", "key" : "existing_cooling_plant_serving_thermal_load", "bau_value" : lambda df: safe_get(df, "outputs.ExistingChiller.thermal_to_load_series_ton_bau"), "scenario_value": lambda df: safe_get(df, "outputs.ExistingChiller.thermal_to_load_series_ton") }, { - "label" : "Existing Cooling Plant Charging Chilled Water Storage (ton-hr)", + "label" : "Existing Cooling Plant Charging Chilled Water Storage (ton-hr/yr)", "key" : "existing_cooling_plant_charging_chilled_water_storage", "bau_value" : lambda df: safe_get(df, "outputs.ExistingChiller.thermal_to_storage_series_ton_bau"), "scenario_value": lambda df: safe_get(df, "outputs.ExistingChiller.thermal_to_storage_series_ton") }, { - "label" : "GHP Reduction of Thermal Load (ton-hr)", + "label" : "GHP Reduction of Thermal Load (ton-hr/yr)", "key" : "ghp_reduction_of_thermal_load_cooling", "bau_value" : lambda df: safe_get(df, "outputs.GHP.cooling_thermal_load_reduction_with_ghp_ton_bau"), "scenario_value": lambda df: safe_get(df, "outputs.GHP.cooling_thermal_load_reduction_with_ghp_ton") }, { - "label" : "GHP Serving Thermal Load (ton-hr)", + "label" : "GHP Serving Thermal Load (ton-hr/yr)", "key" : "ghp_serving_thermal_load_cooling", "bau_value" : lambda df: safe_get(df, "outputs.GHP.thermal_to_load_series_ton_bau"), "scenario_value": lambda df: safe_get(df, "outputs.GHP.thermal_to_load_series_ton") }, { - "label" : "ASHP Serving Thermal Load (ton-hr)", + "label" : "ASHP Serving Thermal Load (ton-hr/yr)", "key" : "ashp_serving_thermal_load_cooling", "bau_value" : lambda df: safe_get(df, "outputs.ASHPSpaceHeater.thermal_to_load_series_ton_bau"), "scenario_value": lambda df: safe_get(df, "outputs.ASHPSpaceHeater.thermal_to_load_series_ton") }, { - "label" : "ASHP Charging Chilled Water Storage (ton-hr)", + "label" : "ASHP Charging Chilled Water Storage (ton-hr/yr)", "key" : "ashp_charging_chilled_water_storage", "bau_value" : lambda df: safe_get(df, "outputs.ASHPSpaceHeater.thermal_to_storage_series_ton_bau"), "scenario_value": lambda df: safe_get(df, "outputs.ASHPSpaceHeater.thermal_to_storage_series_ton") }, { - "label" : "Absorption Chiller Serving Thermal Load (ton-hr)", + "label" : "Absorption Chiller Serving Thermal Load (ton-hr/yr)", "key" : "absorption_chiller_serving_thermal_load", "bau_value" : lambda df: safe_get(df, "outputs.AbsorptionChiller.thermal_to_load_series_ton_bau"), "scenario_value": lambda df: safe_get(df, "outputs.AbsorptionChiller.thermal_to_load_series_ton") }, { - "label" : "Absorption Chiller Charging Chilled Water Storage (ton-hr)", + "label" : "Absorption Chiller Charging Chilled Water Storage (ton-hr/yr)", "key" : "absorption_chiller_charging_chilled_water_storage", "bau_value" : lambda df: safe_get(df, "outputs.AbsorptionChiller.thermal_to_storage_series_ton_bau"), "scenario_value": lambda df: safe_get(df, "outputs.AbsorptionChiller.thermal_to_storage_series_ton") }, { - "label" : "Chilled Water Storage Serving Thermal Load (ton-hr)", + "label" : "Chilled Water Storage Serving Thermal Load (ton-hr/yr)", "key" : "chilled_water_storage_serving_thermal_load", "bau_value" : lambda df: safe_get(df, "outputs.ColdThermalStorage.storage_to_load_series_ton_bau"), "scenario_value": lambda df: safe_get(df, "outputs.ColdThermalStorage.storage_to_load_series_ton") From 28c83fc6aa57332abcb8ea1cc3db49b1b8578957 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Sun, 30 Mar 2025 22:24:31 -0600 Subject: [PATCH 25/32] Fix PV/Wind year one "production" and fix refactor "per year" name references --- reoptjl/custom_table_config.py | 66 +++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 29 deletions(-) diff --git a/reoptjl/custom_table_config.py b/reoptjl/custom_table_config.py index 389a1229f..735cfa912 100644 --- a/reoptjl/custom_table_config.py +++ b/reoptjl/custom_table_config.py @@ -721,10 +721,10 @@ "scenario_value": lambda df: safe_get(df, "outputs.PV.electric_curtailed_series_kw") }, { - "label" : "PV Year One Electricity Produced (kWh/yr)", - "key" : "pv_year_one_electricity_produced", - "bau_value" : lambda df: safe_get(df, "outputs.PV.year_one_energy_produced_kwh_bau"), - "scenario_value": lambda df: safe_get(df, "outputs.PV.year_one_energy_produced_kwh") + "label" : "PV Total Electricity Produced (kWh/yr)", + "key" : "pv_total_electricity_produced", + "bau_value" : lambda df: "", + "scenario_value": lambda df: "" }, { "label" : "Wind Serving Load (kWh/yr)", @@ -753,8 +753,8 @@ { "label" : "Wind Total Electricity Produced (kWh/yr)", "key" : "wind_total_electricity_produced", - "bau_value" : lambda df: safe_get(df, "outputs.Wind.annual_energy_produced_kwh_bau"), - "scenario_value": lambda df: safe_get(df, "outputs.Wind.annual_energy_produced_kwh") + "bau_value" : lambda df: "", + "scenario_value": lambda df: "" }, { "label" : "Battery Serving Load (kWh/yr)", @@ -1044,17 +1044,17 @@ # Define bau_cells configuration for calculations that reference bau cells, call these bau values within calculations bau_cells_config = { "grid_value" : "Grid Purchased Electricity (kWh)", - "elec_cost_value" : "Purchased Electricity Cost ($)", - "elec_cost_after_tax" : "Purchased Electricity Cost, After Tax ($)", + "elec_cost_value" : "Purchased Electricity Cost ($/yr)", + "elec_cost_after_tax" : "Purchased Electricity Cost, After Tax ($/yr)", "ng_reduction_value" : "Total Fuel (MMBtu)", "total_elec_costs" : "Total Electric Costs ($)", - "fuel_costs" : "Fuel Cost ($)", - "fuel_costs_after_tax" : "Fuel Cost, After Tax ($)", - "om_costs_after_tax" : "Year 1 O&M Cost, After Tax ($)", + "fuel_costs" : "Fuel Cost ($/yr)", + "fuel_costs_after_tax" : "Fuel Cost, After Tax ($/yr)", + "om_costs_after_tax" : "Year 1 O&M Cost, After Tax ($/yr)", "total_co2_emission_value" : "Total CO2e Emissions (tonnes)", "placeholder1_value" : "Placeholder1", "lcc_value" : "Lifecycle Costs ($)", - "annual_co2_emissions_value": "Annual CO2e Emissions (tonnes)" + "annual_co2_emissions_value": "Annual CO2e Emissions (tonnes/yr)" } ''' @@ -1087,36 +1087,36 @@ "formula": lambda col, bau, headers: f'={col}{headers["Gross Upfront Capital Costs, Before Incentives ($)"] + 2} - {col}{headers["Net Upfront Capital Cost, After Incentives ($)"] + 2}' }, { - "name": "Electricity Cost Savings ($)", - "formula": lambda col, bau, headers: f'={bau["elec_cost_value"]}-{col}{headers["Purchased Electricity Cost ($)"] + 2}' + "name": "Electricity Cost Savings ($/yr)", + "formula": lambda col, bau, headers: f'={bau["elec_cost_value"]}-{col}{headers["Purchased Electricity Cost ($/yr)"] + 2}' }, { - "name": "Electricity Cost Savings, After Tax ($)", - "formula": lambda col, bau, headers: f'={bau["elec_cost_after_tax"]}-{col}{headers["Purchased Electricity Cost, After Tax ($)"] + 2}' + "name": "Electricity Cost Savings, After Tax ($/yr)", + "formula": lambda col, bau, headers: f'={bau["elec_cost_after_tax"]}-{col}{headers["Purchased Electricity Cost, After Tax ($/yr)"] + 2}' }, { "name": "NPV as a % of BAU LCC (%)", "formula": lambda col, bau, headers: f'=({col}{headers["Net Present Value ($)"] + 2}/{bau["lcc_value"]})' }, { - "name": "Fuel Cost Savings ($)", - "formula": lambda col, bau, headers: f'={bau["fuel_costs"]}-{col}{headers["Fuel Cost ($)"] + 2}' + "name": "Fuel Cost Savings ($/yr)", + "formula": lambda col, bau, headers: f'={bau["fuel_costs"]}-{col}{headers["Fuel Cost ($/yr)"] + 2}' }, { - "name": "Fuel Cost Savings, After Tax ($)", - "formula": lambda col, bau, headers: f'={bau["fuel_costs_after_tax"]}-{col}{headers["Fuel Cost, After Tax ($)"] + 2}' + "name": "Fuel Cost Savings, After Tax ($/yr)", + "formula": lambda col, bau, headers: f'={bau["fuel_costs_after_tax"]}-{col}{headers["Fuel Cost, After Tax ($/yr)"] + 2}' }, { - "name": "Electricity Cost Savings, After Tax ($)", - "formula": lambda col, bau, headers: f'={bau["elec_cost_after_tax"]}-{col}{headers["Purchased Electricity Cost, After Tax ($)"] + 2}' + "name": "Electricity Cost Savings, After Tax ($/yr)", + "formula": lambda col, bau, headers: f'={bau["elec_cost_after_tax"]}-{col}{headers["Purchased Electricity Cost, After Tax ($/yr)"] + 2}' }, { - "name": "Total Year One Savings, After Tax ($)", - "formula": lambda col, bau, headers: f'=({col}{headers["Electricity Cost Savings, After Tax ($)"] + 2}+{col}{headers["Fuel Cost Savings, After Tax ($)"] + 2}+({bau["om_costs_after_tax"]}-{col}{headers["Year 1 O&M Cost, After Tax ($)"] + 2}))' + "name": "Total Year One Savings, After Tax ($/yr)", + "formula": lambda col, bau, headers: f'=({col}{headers["Electricity Cost Savings, After Tax ($/yr)"] + 2}+{col}{headers["Fuel Cost Savings, After Tax ($/yr)"] + 2}+({bau["om_costs_after_tax"]}-{col}{headers["Year 1 O&M Cost, After Tax ($/yr)"] + 2}))' }, { - "name": "Modified Total Year One Savings, After Tax ($)", - "formula": lambda col, bau, headers: f'={col}{headers["Total Year One Savings, After Tax ($)"] + 2}+{col}{headers["Additional Yearly Cost Savings ($/yr)"] + 2}-{col}{headers["Additional Yearly Cost ($/yr)"] + 2}' + "name": "Modified Total Year One Savings, After Tax ($/yr)", + "formula": lambda col, bau, headers: f'={col}{headers["Total Year One Savings, After Tax ($/yr)"] + 2}+{col}{headers["Additional Yearly Cost Savings ($/yr)"] + 2}-{col}{headers["Additional Yearly Cost ($/yr)"] + 2}' }, { "name": "Modified Total Capital Cost ($)", @@ -1124,11 +1124,11 @@ }, { "name": "Modified Simple Payback Period Without Incentives (yrs)", - "formula": lambda col, bau, headers: f'={col}{headers["Total Capital Cost Before Incentives ($)"] + 2}/{col}{headers["Modified Total Year One Savings, After Tax ($)"] + 2}' + "formula": lambda col, bau, headers: f'={col}{headers["Total Capital Cost Before Incentives ($)"] + 2}/{col}{headers["Modified Total Year One Savings, After Tax ($/yr)"] + 2}' }, { "name": "Modified Simple Payback Period (yrs)", - "formula": lambda col, bau, headers: f'={col}{headers["Modified Total Capital Cost ($)"] + 2}/{col}{headers["Modified Total Year One Savings, After Tax ($)"] + 2}' + "formula": lambda col, bau, headers: f'={col}{headers["Modified Total Capital Cost ($)"] + 2}/{col}{headers["Modified Total Year One Savings, After Tax ($/yr)"] + 2}' }, { "name": "Additional Unaddressable Fuel CO2e Emissions (tonnes/yr)", @@ -1140,8 +1140,16 @@ }, { "name": "CO2e Savings Including Unaddressable Fuel (%)", - "formula": lambda col, bau, headers: f'=({bau["annual_co2_emissions_value"]}-{col}{headers["Annual CO2e Emissions (tonnes)"] + 2})/({bau["annual_co2_emissions_value"]}+{col}{headers["Total Unaddressable Fuel CO2e Emissions (tonnes/yr)"] + 2})' + "formula": lambda col, bau, headers: f'=({bau["annual_co2_emissions_value"]}-{col}{headers["Annual CO2e Emissions (tonnes/yr)"] + 2})/({bau["annual_co2_emissions_value"]}+{col}{headers["Total Unaddressable Fuel CO2e Emissions (tonnes/yr)"] + 2})' }, + { + "name": "PV Total Electricity Produced (kWh/yr)", + "formula": lambda col, bau, headers: f'={col}{headers["PV Serving Load (kWh/yr)"] + 2}+{col}{headers["PV Charging Battery (kWh/yr)"] + 2}+{col}{headers["PV Exported to Grid (kWh/yr)"] + 2}' + }, + { + "name": "Wind Total Electricity Produced (kWh/yr)", + "formula": lambda col, bau, headers: f'={col}{headers["Wind Serving Load (kWh/yr)"] + 2}+{col}{headers["Wind Charging Battery (kWh/yr)"] + 2}+{col}{headers["Wind Exported to Grid (kWh/yr)"] + 2}' + }, # These below don't seem to be used currently { "name": "Total Site Electricity Use (kWh)", From 0efe3b8bcca9a122ae2c39791451b5fcd8ee8352 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Mon, 31 Mar 2025 08:40:41 -0600 Subject: [PATCH 26/32] Workaround for BAU ExistingBoiler to Load --- 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 735cfa912..4c3e45476 100644 --- a/reoptjl/custom_table_config.py +++ b/reoptjl/custom_table_config.py @@ -846,7 +846,7 @@ { "label" : "Existing Heating System Serving Thermal Load (MMBtu/yr)", "key" : "existing_heating_system_serving_thermal_load", - "bau_value" : lambda df: safe_get(df, "outputs.ExistingBoiler.thermal_to_load_series_mmbtu_per_hour_bau"), + "bau_value" : lambda df: safe_get(df, "outputs.ExistingBoiler.annual_thermal_production_mmbtu_bau"), "scenario_value": lambda df: safe_get(df, "outputs.ExistingBoiler.thermal_to_load_series_mmbtu_per_hour") }, { From 838c6e8af5751b766d6399f1da57973b6558a7c6 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Fri, 18 Apr 2025 15:44:30 -0600 Subject: [PATCH 27/32] Add peak_grid_demand_kw to ElectricUtility outputs --- ...ityoutputs_peak_grid_demand_kw_and_more.py | 23 +++++++++++++++++++ reoptjl/models.py | 19 +++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 reoptjl/migrations/0083_electricutilityoutputs_peak_grid_demand_kw_and_more.py diff --git a/reoptjl/migrations/0083_electricutilityoutputs_peak_grid_demand_kw_and_more.py b/reoptjl/migrations/0083_electricutilityoutputs_peak_grid_demand_kw_and_more.py new file mode 100644 index 000000000..07d4b091e --- /dev/null +++ b/reoptjl/migrations/0083_electricutilityoutputs_peak_grid_demand_kw_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 4.0.7 on 2025-04-18 21:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('reoptjl', '0082_rename_capital_costs_after_incentives_without_macrs_financialoutputs_capital_costs_after_non_discoun'), + ] + + operations = [ + migrations.AddField( + model_name='electricutilityoutputs', + name='peak_grid_demand_kw', + field=models.FloatField(blank=True, help_text='Maximum grid demand calculated as the maximum of (electric_to_load_series_kw .+ electric_to_storage_series_kw).', null=True), + ), + migrations.AddField( + model_name='electricutilityoutputs', + name='peak_grid_demand_kw_bau', + field=models.FloatField(blank=True, help_text='Maximum grid demand in the BAU case calculated as the maximum of electric_to_load_series_kw.', null=True), + ), + ] diff --git a/reoptjl/models.py b/reoptjl/models.py index 51cc1b3a0..6a33b9daa 100644 --- a/reoptjl/models.py +++ b/reoptjl/models.py @@ -2163,6 +2163,25 @@ class ElectricUtilityOutputs(BaseModel, models.Model): "Determined from site longitude and latitude and the cambium_location_type if " "custom emissions_factor_series_lb_CO2_per_kwh not provided and co2_from_avert is false.") ) + peak_grid_demand_kw = models.FloatField( + null=True, blank=True, + help_text="Maximum grid demand calculated as the maximum of (electric_to_load_series_kw .+ electric_to_storage_series_kw)." + ) + peak_grid_demand_kw_bau = models.FloatField( + null=True, blank=True, + help_text="Maximum grid demand in the BAU case calculated as the maximum of electric_to_load_series_kw." + ) + + def calculate_peak_demand(self): + if self.electric_to_load_series_kw and self.electric_to_storage_series_kw: + self.peak_grid_demand_kw = max(self.electric_to_load_series_kw + self.electric_to_storage_series_kw) + if self.electric_to_load_series_kw_bau: + self.peak_grid_demand_kw_bau = max(self.electric_to_load_series_kw_bau) + + def save(self, *args, **kwargs): + self.calculate_peak_demand() + super().save(*args, **kwargs) + class OutageOutputs(BaseModel, models.Model): key = "OutageOutputs" From e23c0f7d780366a11a8351b68d0392a8daa793d5 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Fri, 18 Apr 2025 15:44:52 -0600 Subject: [PATCH 28/32] Add unaddressable fuel cost and peak demand to results table --- reoptjl/custom_table_config.py | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/reoptjl/custom_table_config.py b/reoptjl/custom_table_config.py index 4c3e45476..34131f514 100644 --- a/reoptjl/custom_table_config.py +++ b/reoptjl/custom_table_config.py @@ -650,6 +650,18 @@ "bau_value": lambda df: "", "scenario_value": lambda df: "" }, + { + "label": "Unaddressable Fuel Cost ($/MMBtu)", + "key": "unaddressable_fuel_cost_input", + "bau_value": lambda df: safe_get(df, "inputs.ExistingBoiler.fuel_cost_per_mmbtu") / 8760, + "scenario_value": lambda df: safe_get(df, "inputs.ExistingBoiler.fuel_cost_per_mmbtu") / 8760 + }, + { + "label": "Unaddressable Fuel Yearly Cost ($/yr)", + "key": "unaddressable_fuel_yearly_cost", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, { "label": "Modified Total Year One Savings, After Tax ($/yr)", "key": "modified_total_year_one_savings_after_tax", @@ -1021,6 +1033,22 @@ "bau_value" : lambda df: safe_get(df, "outputs.ColdThermalStorage.storage_to_load_series_ton_bau"), "scenario_value": lambda df: safe_get(df, "outputs.ColdThermalStorage.storage_to_load_series_ton") }, + ##################################################################################################### + ############################ Other Metrics ############################ + ##################################################################################################### + + { + "label" : "Other Metrics", + "key" : "other_metrics_separator", + "bau_value" : lambda df: "", + "scenario_value": lambda df: "" + }, + { + "label" : "Peak Grid Demand (kW)", + "key" : "peak_grid_demand_max", + "bau_value" : lambda df: safe_get(df, "outputs.ElectricUtility.peak_grid_demand_kw_bau"), + "scenario_value": lambda df: safe_get(df, "outputs.ElectricUtility.peak_grid_demand_kw") + }, ] ''' @@ -1105,7 +1133,11 @@ { "name": "Fuel Cost Savings, After Tax ($/yr)", "formula": lambda col, bau, headers: f'={bau["fuel_costs_after_tax"]}-{col}{headers["Fuel Cost, After Tax ($/yr)"] + 2}' - }, + }, + { + "name": "Unaddressable Fuel Yearly Cost ($/yr)", + "formula": lambda col, bau, headers: f'={col}{headers["Unaddressable Fuel Cost ($/MMBtu)"] + 2} * ({col}{headers["Unaddressable Heating Fuel from REopt Input (MMBtu/yr)"] + 2} + {col}{headers["Additional Unaddressable Fuel Consumption (MMBtu/yr)"] + 2})' + }, { "name": "Electricity Cost Savings, After Tax ($/yr)", "formula": lambda col, bau, headers: f'={bau["elec_cost_after_tax"]}-{col}{headers["Purchased Electricity Cost, After Tax ($/yr)"] + 2}' From 3343a061a31e084529b52c3df17059c824e7d373 Mon Sep 17 00:00:00 2001 From: Bill Becker <42586683+Bill-Becker@users.noreply.github.com> Date: Fri, 18 Apr 2025 16:23:43 -0600 Subject: [PATCH 29/32] Update CHANGELOG.md --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d2b8ec87..133f9d65c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,18 @@ Classify the change according to the following categories: ##### Removed ### Patches +## v3.12.1 +### Minor Updates +### Added +- Added the following output fields: `year_one_fuel_cost_after_tax` for `ExistingBoiler`, `CHP`, `Generator`, and `Boiler`; `ElectricTariff`: `year_one_bill_after_tax` and `year_one_export_benefit_after_tax`, `Financial`: `capital_costs_after_non_discounted_incentives`, `year_one_total_operating_cost_savings_before_tax`, `year_one_total_operating_cost_savings_after_tax`, `year_one_total_operating_cost_before_tax`, `year_one_total_operating_cost_after_tax`, `year_one_fuel_cost_before_tax`, `year_one_fuel_cost_after_tax`, `year_one_chp_standby_cost_after_tax`, `year_one_chp_standby_cost_after_tax`, `GHP.avoided_capex_by_ghp_present_value`, and `ElectricUtility.peak_grid_demand_kw` +- Added a lot of the output fields above to the custom_table_config.py file for the `job/generate_results_table` endpoint for the results table downloadable spreadsheet. +### Changed +- Using latest registered REopt.jl version 0.51.1 +- For the results table downloadable spreadsheet, changed some labels to include units and made other improvements, in addition to mostly adding a bunch of the after-tax outputs described above +### Fixed +- Fixed a GHP test with the corrected `lifecycle_capital_cost` calculation to include the avoided HVAC cost and GHX residual value +- Fixed a type issue with the `/simulated_load` endpoing for cooling load with `monthly_fraction` input + ## v3.12.0 ### Major Updates ### Added From fa6b4cf4dc213c5614fc70e39380d2ec4b8c84ac Mon Sep 17 00:00:00 2001 From: Bill Becker <42586683+Bill-Becker@users.noreply.github.com> Date: Fri, 18 Apr 2025 16:27:39 -0600 Subject: [PATCH 30/32] Update reoptjl/views.py 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 fef600e5f..b8b8901fb 100644 --- a/reoptjl/views.py +++ b/reoptjl/views.py @@ -2061,7 +2061,7 @@ def get_bau_column(col): "- 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 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 ($)." From d314ec2971b6dcb98faed673c8ab65f0cc0db371 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Fri, 18 Apr 2025 16:32:34 -0600 Subject: [PATCH 31/32] Use single quotes for http.jl file name inside outer quotes in compose command --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 80cdda5b5..e27c691ca 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -66,7 +66,7 @@ services: - XPRESS_JL_SKIP_LIB_CHECK=True - XPRESS_INSTALLED=False # command: julia --project=/opt/julia_src http.jl - command: julia --project=. -e 'using Pkg; Pkg.instantiate(); include("http.jl")' + command: julia --project=. -e 'using Pkg; Pkg.instantiate(); include('http.jl')' ports: - "8081:8081" volumes: From a1c5f394f0e945ac0c0e24a939677a9a68616542 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Fri, 18 Apr 2025 19:54:19 -0600 Subject: [PATCH 32/32] Revert "Use single quotes for http.jl file name inside outer quotes in compose command" This reverts commit d314ec2971b6dcb98faed673c8ab65f0cc0db371. --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index e27c691ca..80cdda5b5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -66,7 +66,7 @@ services: - XPRESS_JL_SKIP_LIB_CHECK=True - XPRESS_INSTALLED=False # command: julia --project=/opt/julia_src http.jl - command: julia --project=. -e 'using Pkg; Pkg.instantiate(); include('http.jl')' + command: julia --project=. -e 'using Pkg; Pkg.instantiate(); include("http.jl")' ports: - "8081:8081" volumes: