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 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: diff --git a/julia_src/Manifest.toml b/julia_src/Manifest.toml index 6729da562..fc3e1581e 100644 --- a/julia_src/Manifest.toml +++ b/julia_src/Manifest.toml @@ -922,9 +922,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 = "24f902b6f67ed1d4389d21b5d42f820036b182b4" +git-tree-sha1 = "9946abe774e30d82f786e68296ad1fdf8bb7dba4" uuid = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6" -version = "0.51.0" +version = "0.51.1" [[deps.Random]] deps = ["SHA"] diff --git a/julia_src/http.jl b/julia_src/http.jl index 5e2a75d94..ebbfe6ea2 100644 --- a/julia_src/http.jl +++ b/julia_src/http.jl @@ -420,7 +420,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]) diff --git a/reoptjl/custom_table_config.py b/reoptjl/custom_table_config.py index c363e6c43..34131f514 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") @@ -276,11 +276,17 @@ "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 ($/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") + }, { "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 (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") @@ -311,20 +317,20 @@ { "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 ($)", "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 ($)", "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_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 ($)", @@ -335,11 +341,11 @@ { "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)", + "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") @@ -361,86 +367,122 @@ ############################ 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: "" }, { - "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" : "Purchased Electricity Cost ($)", + "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 ($/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 ($/yr)", "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" : "Electricity Cost Savings ($)", + "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 ($/yr)", "key" : "electricity_cost_savings", "bau_value" : lambda df: "", "scenario_value": lambda df: "" }, + { + "label" : "Electricity Cost Savings, After Tax ($/yr)", + "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: "" }, { - "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 ($/yr)", "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_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", + "label" : "Fuel Cost Savings ($/yr)", + "key" : "fuel_cost_savings", "bau_value" : lambda df: "", "scenario_value": lambda df: "" }, + { + "label" : "Fuel Cost Savings, After Tax ($/yr)", + "key" : "fuel_cost_savings_after_tax", + "bau_value" : lambda df: "", + "scenario_value": lambda df: "" + }, ##################################################################################################### ############################ Renewable Energy & Emissions ########################### ##################################################################################################### @@ -451,63 +493,138 @@ "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 CO2 Emissions (tonnes)", + "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 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 CO2 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 CO2 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") }, { - "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: "" }, #################################################################################################################################### + ##################### 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 ############################## ##################################################################################################### { - "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_non_discounted_incentives_without_macrs_bau"), + "scenario_value": lambda df: safe_get(df, "outputs.Financial.capital_costs_after_non_discounted_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 ($/yr)", + "key": "total_year_one_savings_after_tax", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" }, { "label": "Additional Upfront Incentive ($)", @@ -522,53 +639,50 @@ "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: "" }, { - "label": "Modified Net Upfront Capital Cost ($)", - "key": "modified_net_upfront_capital_cost", - "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": "Modified Simple Payback Period (years)", - "key": "modified_simple_payback_period", + "label": "Unaddressable Fuel Yearly Cost ($/yr)", + "key": "unaddressable_fuel_yearly_cost", "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", + "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: "" - }, + }, { - "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": "Modified Total Capital Cost ($)", + "key": "modified_net_upfront_capital_cost", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" }, { - "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": "Modified Simple Payback Period Without Incentives (yrs)", + "key": "modified_simple_payback_period_without_incentives", + "bau_value": lambda df: "", + "scenario_value": lambda df: "" + }, { - "label": "CO2 Savings Including Unaddressable (%)", - "key": "co2_savings_including_unaddressable", + "label": "Modified Simple Payback Period (yrs)", + "key": "modified_simple_payback_period", "bau_value": lambda df: "", "scenario_value": lambda df: "" }, @@ -583,151 +697,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)", - "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)", + "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") + "bau_value" : lambda df: "", + "scenario_value": lambda df: "" }, { - "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") @@ -742,115 +856,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"), + "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") }, { - "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") @@ -866,59 +980,75 @@ "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") }, + ##################################################################################################### + ############################ 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") + }, ] ''' @@ -942,14 +1072,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_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 ($)", - "total_co2_emission_value" : "Total CO2 Emissions (tonnes)", + "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 CO2 Emissions (tonnes)" + "annual_co2_emissions_value": "Annual CO2e Emissions (tonnes/yr)" } ''' @@ -982,31 +1115,74 @@ "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 ($/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 ($/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": "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": "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 ($/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 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})' + "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": "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": "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 ($/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 ($/yr)"] + 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 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)", "formula": lambda col, bau, headers: ( @@ -1044,12 +1220,12 @@ "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", - "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 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/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/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/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/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 = [ + ] 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/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 47d088f7a..6a33b9daa 100644 --- a/reoptjl/models.py +++ b/reoptjl/models.py @@ -978,18 +978,18 @@ 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_non_discounted_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, 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, 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." - ) lifecycle_om_costs_before_tax = models.FloatField( null=True, blank=True, help_text="Life cycle operations and maintenance cost over analysis period before tax." @@ -1002,6 +1002,62 @@ 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." + ) + 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." + ) + 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_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_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_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_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_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_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.") + ) simple_payback_years = models.FloatField( null=True, blank=True, help_text=("Number of years until the cumulative annual cashflows turn positive. " @@ -1096,12 +1152,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.") @@ -2109,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" @@ -2544,6 +2617,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." @@ -2552,6 +2633,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" @@ -3960,6 +4049,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) @@ -4581,6 +4672,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 [\$]" @@ -4589,6 +4684,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." @@ -5162,7 +5261,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), @@ -6147,6 +6248,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, @@ -8303,7 +8408,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): """ diff --git a/reoptjl/test/posts/central_plant_ghp.json b/reoptjl/test/posts/central_plant_ghp.json index 4aa266c7a..d9c63ad25 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, diff --git a/reoptjl/views.py b/reoptjl/views.py index 566733f5e..b8b8901fb 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'] @@ -1765,13 +1766,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,17 +1810,25 @@ def get_combined_format(label, row_color, is_formula=False): return formula_currency_format elif '%' in label: return formula_percent_format + 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 '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 @@ -1982,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 }) @@ -2005,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) @@ -2015,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 = [ @@ -2031,49 +2044,54 @@ 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 = [ - "- 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) 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 = ( - "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) - 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)