diff --git a/CHANGELOG.md b/CHANGELOG.md index 3abbbb7fc..497d194a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,12 @@ Classify the change according to the following categories: ##### Removed ### Patches +## add-capex-constraint +### Minor Updates +#### Added +- Add **Financial** inputs **min_initial_capital_costs_before_incentives** and **max_initial_capital_costs_before_incentives** +- Add **CHP** output **initial_capital_costs** + ## v3.12.3 ### Minor Updates ### Added @@ -49,7 +55,7 @@ Classify the change according to the following categories: ## v3.12.0 ### Major Updates -### Added +#### Added - Add inputs: - **ElectricUtility.cambium_cef_metric** to utilize clean energy data from NREL's Cambium database - **ElectricUtility.renewable_energy_fraction_series** to supply a custom grid clean or renewable energy scalar or series diff --git a/julia_src/Manifest.toml b/julia_src/Manifest.toml index fc3e1581e..b6a488653 100644 --- a/julia_src/Manifest.toml +++ b/julia_src/Manifest.toml @@ -922,9 +922,11 @@ uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" [[deps.REopt]] deps = ["ArchGDAL", "CSV", "CoolProp", "DataFrames", "Dates", "DelimitedFiles", "HTTP", "JLD", "JSON", "JuMP", "LinDistFlow", "LinearAlgebra", "Logging", "MathOptInterface", "Requires", "Roots", "Statistics", "TestEnv"] -git-tree-sha1 = "9946abe774e30d82f786e68296ad1fdf8bb7dba4" +git-tree-sha1 = "7f41842737ce902942d5b1291f777ce55ef9adec" +repo-rev = "storage-cost-constraints-version2" +repo-url = "https://github.com/NREL/REopt.jl.git" uuid = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6" -version = "0.51.1" +version = "0.52.0" [[deps.Random]] deps = ["SHA"] diff --git a/reoptjl/migrations/0081_ghpinputs_load_served_by_ghp_and_more.py b/reoptjl/migrations/0081_ghpinputs_load_served_by_ghp_and_more.py new file mode 100644 index 000000000..d6a2f6a59 --- /dev/null +++ b/reoptjl/migrations/0081_ghpinputs_load_served_by_ghp_and_more.py @@ -0,0 +1,29 @@ +# Generated by Django 4.0.7 on 2025-04-22 16:44 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('reoptjl', '0080_electricloadoutputs_annual_electric_load_with_thermal_conversions_kwh_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='ghpinputs', + name='load_served_by_ghp', + field=models.TextField(blank=True, default='nonpeak', help_text='How to split between load served by GHP and load served by backup system'), + ), + migrations.AddField( + model_name='ghpinputs', + name='max_number_of_boreholes', + field=models.FloatField(blank=True, help_text='Maximum number of boreholes for GHX', null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100000000.0)]), + ), + migrations.AddField( + model_name='ghpinputs', + name='max_ton', + field=models.FloatField(blank=True, help_text='Maximum thermal power size constraint for GHP [ton]', null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100000000.0)]), + ), + ] diff --git a/reoptjl/migrations/0084_merge_20250423_2110.py b/reoptjl/migrations/0084_merge_20250423_2110.py new file mode 100644 index 000000000..8ea012a9b --- /dev/null +++ b/reoptjl/migrations/0084_merge_20250423_2110.py @@ -0,0 +1,14 @@ +# Generated by Django 4.0.7 on 2025-04-23 21:10 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('reoptjl', '0081_ghpinputs_load_served_by_ghp_and_more'), + ('reoptjl', '0083_electricutilityoutputs_peak_grid_demand_kw_and_more'), + ] + + operations = [ + ] diff --git a/reoptjl/migrations/0085_electricstorageinputs_cost_constant_replacement_year_and_more.py b/reoptjl/migrations/0085_electricstorageinputs_cost_constant_replacement_year_and_more.py new file mode 100644 index 000000000..e7ef9d064 --- /dev/null +++ b/reoptjl/migrations/0085_electricstorageinputs_cost_constant_replacement_year_and_more.py @@ -0,0 +1,29 @@ +# Generated by Django 4.0.7 on 2025-05-19 20:33 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('reoptjl', '0084_merge_20250424_1814'), + ] + + operations = [ + migrations.AddField( + model_name='electricstorageinputs', + name='cost_constant_replacement_year', + field=models.IntegerField(blank=True, default=10, help_text='Number of years from start of analysis period to apply replace_cost_constant.', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(75)]), + ), + migrations.AddField( + model_name='electricstorageinputs', + name='installed_cost_constant', + field=models.FloatField(blank=True, default=0.0, help_text='Fixed upfront cost for battery installation, independent of size.', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000000000.0)]), + ), + migrations.AddField( + model_name='electricstorageinputs', + name='replace_cost_constant', + field=models.FloatField(blank=True, default=0.0, help_text='Fixed replacement cost for battery, independent of size.', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000000000.0)]), + ), + ] diff --git a/reoptjl/migrations/0085_financialinputs_max_initial_capital_costs_before_incentives_and_more.py b/reoptjl/migrations/0085_financialinputs_max_initial_capital_costs_before_incentives_and_more.py new file mode 100644 index 000000000..5a02b6559 --- /dev/null +++ b/reoptjl/migrations/0085_financialinputs_max_initial_capital_costs_before_incentives_and_more.py @@ -0,0 +1,24 @@ +# Generated by Django 4.0.7 on 2025-05-08 04:49 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('reoptjl', '0084_merge_20250424_1814'), + ] + + operations = [ + migrations.AddField( + model_name='financialinputs', + name='max_initial_capital_costs_before_incentives', + field=models.FloatField(blank=True, help_text='Maximum up-front capital cost for all technologies, excluding replacement costs and incentives [\$].', null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000000000000.0)]), + ), + migrations.AddField( + model_name='financialinputs', + name='min_initial_capital_costs_before_incentives', + field=models.FloatField(blank=True, help_text='Minimum up-front capital cost for all technologies, excluding replacement costs and incentives [\$].', null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000000000000.0)]), + ), + ] diff --git a/reoptjl/migrations/0085_ghpoutputs_annual_thermal_production_mmbtu_and_more.py b/reoptjl/migrations/0085_ghpoutputs_annual_thermal_production_mmbtu_and_more.py new file mode 100644 index 000000000..fe4a5e62b --- /dev/null +++ b/reoptjl/migrations/0085_ghpoutputs_annual_thermal_production_mmbtu_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 4.0.7 on 2025-04-24 14:32 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('reoptjl', '0084_merge_20250423_2110'), + ] + + operations = [ + migrations.AddField( + model_name='ghpoutputs', + name='annual_thermal_production_mmbtu', + field=models.FloatField(blank=True, null=True), + ), + migrations.AddField( + model_name='ghpoutputs', + name='annual_thermal_production_tonhour', + field=models.FloatField(blank=True, null=True), + ), + ] diff --git a/reoptjl/migrations/0086_chpoutputs_initial_capital_costs.py b/reoptjl/migrations/0086_chpoutputs_initial_capital_costs.py new file mode 100644 index 000000000..5107d9a19 --- /dev/null +++ b/reoptjl/migrations/0086_chpoutputs_initial_capital_costs.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0.7 on 2025-05-08 18:51 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('reoptjl', '0085_financialinputs_max_initial_capital_costs_before_incentives_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='chpoutputs', + name='initial_capital_costs', + field=models.FloatField(blank=True, help_text='Initial capital costs of the CHP system, before incentives [\\$]', null=True), + ), + ] diff --git a/reoptjl/migrations/0086_electricstorageinputs_om_cost_fraction_of_installed_cost_and_more.py b/reoptjl/migrations/0086_electricstorageinputs_om_cost_fraction_of_installed_cost_and_more.py new file mode 100644 index 000000000..22718b0c6 --- /dev/null +++ b/reoptjl/migrations/0086_electricstorageinputs_om_cost_fraction_of_installed_cost_and_more.py @@ -0,0 +1,44 @@ +# Generated by Django 4.0.7 on 2025-06-04 00:05 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('reoptjl', '0085_electricstorageinputs_cost_constant_replacement_year_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='electricstorageinputs', + name='om_cost_fraction_of_installed_cost', + field=models.FloatField(blank=True, default=0.025, help_text='Annual O&M cost as a fraction of installed cost.', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1.0)]), + ), + migrations.AlterField( + model_name='electricstorageinputs', + name='installed_cost_constant', + field=models.FloatField(blank=True, default=222115.0, help_text='Fixed upfront cost for battery installation, independent of size.', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000000000.0)]), + ), + migrations.AlterField( + model_name='electricstorageinputs', + name='installed_cost_per_kw', + field=models.FloatField(blank=True, default=905.0, help_text='Total upfront battery power capacity costs (e.g. inverter and balance of power systems)', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10000.0)]), + ), + migrations.AlterField( + model_name='electricstorageinputs', + name='installed_cost_per_kwh', + field=models.FloatField(blank=True, default=237.0, help_text='Total upfront battery costs', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10000.0)]), + ), + migrations.AlterField( + model_name='electricstorageinputs', + name='replace_cost_per_kw', + field=models.FloatField(blank=True, default=0.0, help_text='Battery power capacity replacement cost at time of replacement year', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10000.0)]), + ), + migrations.AlterField( + model_name='electricstorageinputs', + name='replace_cost_per_kwh', + field=models.FloatField(blank=True, default=0.0, help_text='Battery energy capacity replacement cost at time of replacement year', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10000.0)]), + ), + ] diff --git a/reoptjl/migrations/0087_merge_20250604_0342.py b/reoptjl/migrations/0087_merge_20250604_0342.py new file mode 100644 index 000000000..edafd4e60 --- /dev/null +++ b/reoptjl/migrations/0087_merge_20250604_0342.py @@ -0,0 +1,14 @@ +# Generated by Django 4.0.7 on 2025-06-04 03:42 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('reoptjl', '0086_chpoutputs_initial_capital_costs'), + ('reoptjl', '0086_electricstorageinputs_om_cost_fraction_of_installed_cost_and_more'), + ] + + operations = [ + ] diff --git a/reoptjl/migrations/0088_financialoutputs_initial_capital_costs_after_incentives_bau_and_more.py b/reoptjl/migrations/0088_financialoutputs_initial_capital_costs_after_incentives_bau_and_more.py new file mode 100644 index 000000000..65c442ae1 --- /dev/null +++ b/reoptjl/migrations/0088_financialoutputs_initial_capital_costs_after_incentives_bau_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 4.0.7 on 2025-06-04 04:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('reoptjl', '0087_merge_20250604_0342'), + ] + + operations = [ + migrations.AddField( + model_name='financialoutputs', + name='initial_capital_costs_after_incentives_bau', + field=models.FloatField(blank=True, help_text='Up-front capital costs for BAU technologies such as ExistingBoiler and ExistingChiller, in present value.', null=True), + ), + migrations.AddField( + model_name='financialoutputs', + name='lifecycle_capital_costs_bau', + field=models.FloatField(blank=True, help_text='Net capital costs for BAU technologies such as ExistingBoiler and ExistingChiller, in present value.', null=True), + ), + ] diff --git a/reoptjl/migrations/0089_alter_electricstorageinputs_installed_cost_per_kw_and_more.py b/reoptjl/migrations/0089_alter_electricstorageinputs_installed_cost_per_kw_and_more.py new file mode 100644 index 000000000..54f0e618c --- /dev/null +++ b/reoptjl/migrations/0089_alter_electricstorageinputs_installed_cost_per_kw_and_more.py @@ -0,0 +1,24 @@ +# Generated by Django 4.0.7 on 2025-06-04 16:59 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('reoptjl', '0088_financialoutputs_initial_capital_costs_after_incentives_bau_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='electricstorageinputs', + name='installed_cost_per_kw', + field=models.FloatField(blank=True, default=968.0, help_text='Total upfront battery power capacity costs (e.g. inverter and balance of power systems)', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10000.0)]), + ), + migrations.AlterField( + model_name='electricstorageinputs', + name='installed_cost_per_kwh', + field=models.FloatField(blank=True, default=253.0, help_text='Total upfront battery costs', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10000.0)]), + ), + ] diff --git a/reoptjl/migrations/0090_existingboilerinputs_installed_cost_dollars_and_more.py b/reoptjl/migrations/0090_existingboilerinputs_installed_cost_dollars_and_more.py new file mode 100644 index 000000000..8d6357e79 --- /dev/null +++ b/reoptjl/migrations/0090_existingboilerinputs_installed_cost_dollars_and_more.py @@ -0,0 +1,49 @@ +# Generated by Django 4.0.7 on 2025-06-04 19:28 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('reoptjl', '0089_alter_electricstorageinputs_installed_cost_per_kw_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='existingboilerinputs', + name='installed_cost_dollars', + field=models.FloatField(blank=True, default=0.0, help_text='Cost incurred in BAU scenario, as well as Optimal if needed still, in dollars', null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100000000.0)]), + ), + migrations.AddField( + model_name='existingboilerinputs', + name='installed_cost_per_mmbtu_per_hour', + field=models.FloatField(blank=True, default=0.0, help_text="Thermal power capacity-based cost incurred in BAU and only based on what's needed in Optimal scenario", null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100000000.0)]), + ), + migrations.AddField( + model_name='existingboileroutputs', + name='size_mmbtu_per_hour_bau', + field=models.FloatField(blank=True, null=True), + ), + migrations.AddField( + model_name='existingchillerinputs', + name='installed_cost_dollars', + field=models.FloatField(blank=True, default=0.0, help_text='Cost incurred in BAU scenario, as well as Optimal if needed still, in dollars', null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100000000.0)]), + ), + migrations.AddField( + model_name='existingchillerinputs', + name='installed_cost_per_ton', + field=models.FloatField(blank=True, default=0.0, help_text="Thermal power capacity-based cost incurred in BAU and only based on what's needed in Optimal scenario", null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100000000.0)]), + ), + migrations.AddField( + model_name='existingchilleroutputs', + name='size_ton', + field=models.FloatField(blank=True, null=True), + ), + migrations.AddField( + model_name='existingchilleroutputs', + name='size_ton_bau', + field=models.FloatField(blank=True, null=True), + ), + ] diff --git a/reoptjl/migrations/0091_merge_20250604_2023.py b/reoptjl/migrations/0091_merge_20250604_2023.py new file mode 100644 index 000000000..8c83be0da --- /dev/null +++ b/reoptjl/migrations/0091_merge_20250604_2023.py @@ -0,0 +1,14 @@ +# Generated by Django 4.0.7 on 2025-06-04 20:23 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('reoptjl', '0085_ghpoutputs_annual_thermal_production_mmbtu_and_more'), + ('reoptjl', '0090_existingboilerinputs_installed_cost_dollars_and_more'), + ] + + operations = [ + ] diff --git a/reoptjl/models.py b/reoptjl/models.py index fdef159a8..b403bab80 100644 --- a/reoptjl/models.py +++ b/reoptjl/models.py @@ -786,6 +786,24 @@ class FinancialInputs(BaseModel, models.Model): default=0.0, help_text=("Only applicable when off_grid_flag is true. These per year costs are considered tax deductible for owner.") ) + min_initial_capital_costs_before_incentives = models.FloatField( + validators=[ + MinValueValidator(0), + MaxValueValidator(1e12) + ], + blank=True, + null=True, + help_text=("Minimum up-front capital cost for all technologies, excluding replacement costs and incentives [\$].") + ) + max_initial_capital_costs_before_incentives = models.FloatField( + validators=[ + MinValueValidator(0), + MaxValueValidator(1e12) + ], + blank=True, + null=True, + help_text=("Maximum up-front capital cost for all technologies, excluding replacement costs and incentives [\$].") + ) CO2_cost_per_tonne = models.FloatField( validators=[ MinValueValidator(0), @@ -964,6 +982,10 @@ class FinancialOutputs(BaseModel, models.Model): null=True, blank=True, help_text="Net capital costs for all technologies, in present value, including replacement costs and incentives." ) + lifecycle_capital_costs_bau = models.FloatField( + null=True, blank=True, + help_text="Net capital costs for BAU technologies such as ExistingBoiler and ExistingChiller, in present value." + ) microgrid_upgrade_cost = models.FloatField( null=True, blank=True, help_text=("Cost to make a distributed energy system islandable from the grid. Determined by multiplying the " @@ -978,6 +1000,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." ) + initial_capital_costs_after_incentives_bau = models.FloatField( + null=True, blank=True, + help_text="Up-front capital costs for BAU technologies such as ExistingBoiler and ExistingChiller, in present value." + ) 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." @@ -3515,7 +3541,7 @@ class ElectricStorageInputs(BaseModel, models.Model): help_text="Flag to set whether the battery can be charged from the grid, or just onsite generation." ) installed_cost_per_kw = models.FloatField( - default=910.0, + default=968.0, validators=[ MinValueValidator(0), MaxValueValidator(1.0e4) @@ -3524,7 +3550,7 @@ class ElectricStorageInputs(BaseModel, models.Model): help_text="Total upfront battery power capacity costs (e.g. inverter and balance of power systems)" ) installed_cost_per_kwh = models.FloatField( - default=455.0, + default=253.0, validators=[ MinValueValidator(0), MaxValueValidator(1.0e4) @@ -3532,8 +3558,17 @@ class ElectricStorageInputs(BaseModel, models.Model): blank=True, help_text="Total upfront battery costs" ) + installed_cost_constant = models.FloatField( + default=222115.0, + validators=[ + MinValueValidator(0), + MaxValueValidator(1.0e9) + ], + blank=True, + help_text="Fixed upfront cost for battery installation, independent of size." + ) replace_cost_per_kw = models.FloatField( - default=715.0, + default=0.0, validators=[ MinValueValidator(0), MaxValueValidator(1.0e4) @@ -3542,7 +3577,7 @@ class ElectricStorageInputs(BaseModel, models.Model): help_text="Battery power capacity replacement cost at time of replacement year" ) replace_cost_per_kwh = models.FloatField( - default=318.0, + default=0.0, validators=[ MinValueValidator(0), MaxValueValidator(1.0e4) @@ -3550,6 +3585,15 @@ class ElectricStorageInputs(BaseModel, models.Model): blank=True, help_text="Battery energy capacity replacement cost at time of replacement year" ) + replace_cost_constant = models.FloatField( + default=0.0, + validators=[ + MinValueValidator(0), + MaxValueValidator(1.0e9) + ], + blank=True, + help_text="Fixed replacement cost for battery, independent of size." + ) inverter_replacement_year = models.IntegerField( default=10, validators=[ @@ -3568,6 +3612,24 @@ class ElectricStorageInputs(BaseModel, models.Model): blank=True, help_text="Number of years from start of analysis period to replace battery" ) + cost_constant_replacement_year = models.IntegerField( + default=10, + validators=[ + MinValueValidator(0), + MaxValueValidator(MAX_YEARS) + ], + blank=True, + help_text="Number of years from start of analysis period to apply replace_cost_constant." + ) + om_cost_fraction_of_installed_cost = models.FloatField( + default=0.025, + validators=[ + MinValueValidator(0), + MaxValueValidator(1.0) + ], + blank=True, + help_text="Annual O&M cost as a fraction of installed cost." + ) macrs_option_years = models.IntegerField( default=MACRS_YEARS_CHOICES.SEVEN, choices=MACRS_YEARS_CHOICES.choices, @@ -4728,6 +4790,10 @@ class CHPOutputs(BaseModel, models.Model): models.FloatField(null=True, blank=True), default = list, ) + initial_capital_costs = models.FloatField( + null=True, blank=True, + help_text="Initial capital costs of the CHP system, before incentives [\$]" + ) def clean(): pass @@ -4987,6 +5053,28 @@ class ExistingChillerInputs(BaseModel, models.Model): help_text="Boolean indicator if the existing chiller is unavailable in the optimal case (still used in BAU)" ) + installed_cost_per_ton = models.FloatField( + default=0.0, + null=True, + blank=True, + validators=[ + MinValueValidator(0), + MaxValueValidator(MAX_BIG_NUMBER) + ], + help_text="Thermal power capacity-based cost incurred in BAU and only based on what's needed in Optimal scenario" + ) + + installed_cost_dollars = models.FloatField( + default=0.0, + null=True, + blank=True, + validators=[ + MinValueValidator(0), + MaxValueValidator(MAX_BIG_NUMBER) + ], + help_text="Cost incurred in BAU scenario, as well as Optimal if needed still, in dollars" + ) + def clean(self): pass @@ -5002,6 +5090,9 @@ class ExistingChillerOutputs(BaseModel, models.Model): primary_key=True ) + size_ton = models.FloatField(null=True, blank=True) + size_ton_bau = models.FloatField(null=True, blank=True) + thermal_to_storage_series_ton = ArrayField( models.FloatField( blank=True @@ -5202,6 +5293,29 @@ class ExistingBoilerInputs(BaseModel, models.Model): help_text="Existing boiler fuel type, one of natural_gas, landfill_bio_gas, propane, diesel_oil" ) + + installed_cost_per_mmbtu_per_hour = models.FloatField( + default=0.0, + null=True, + blank=True, + validators=[ + MinValueValidator(0), + MaxValueValidator(MAX_BIG_NUMBER) + ], + help_text="Thermal power capacity-based cost incurred in BAU and only based on what's needed in Optimal scenario" + ) + + installed_cost_dollars = models.FloatField( + default=0.0, + null=True, + blank=True, + validators=[ + MinValueValidator(0), + MaxValueValidator(MAX_BIG_NUMBER) + ], + help_text="Cost incurred in BAU scenario, as well as Optimal if needed still, in dollars" + ) + can_supply_steam_turbine = models.BooleanField( default=False, blank=True, @@ -5268,6 +5382,7 @@ class ExistingBoilerOutputs(BaseModel, models.Model): ) size_mmbtu_per_hour = models.FloatField(null=True, blank=True) + size_mmbtu_per_hour_bau = models.FloatField(null=True, blank=True) annual_fuel_consumption_mmbtu = models.FloatField(null=True, blank=True) annual_fuel_consumption_mmbtu_bau = models.FloatField(null=True, blank=True) @@ -8386,6 +8501,32 @@ class GHPInputs(BaseModel, models.Model): blank=True, help_text="Maximum utility rebate" ) + max_ton = models.FloatField( + validators=[ + MinValueValidator(0), + MaxValueValidator(MAX_BIG_NUMBER) + ], + null=True, + blank=True, + help_text=("Maximum thermal power size constraint for GHP [ton]") + ) + + max_number_of_boreholes = models.FloatField( + validators=[ + MinValueValidator(0), + MaxValueValidator(MAX_BIG_NUMBER) + ], + null=True, + blank=True, + help_text=("Maximum number of boreholes for GHX") + ) + + load_served_by_ghp = models.TextField( + null=False, + blank=True, + default="nonpeak", + help_text="How to split between load served by GHP and load served by backup system" + ) def clean(self): @@ -8429,6 +8570,8 @@ class GHPOutputs(BaseModel, models.Model): 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) + annual_thermal_production_mmbtu = models.FloatField(null=True, blank=True) + annual_thermal_production_tonhour = models.FloatField(null=True, blank=True) def get_input_dict_from_run_uuid(run_uuid:str): """ diff --git a/reoptjl/test/posts/all_inputs_test.json b/reoptjl/test/posts/all_inputs_test.json index 6811af43c..7bfb1c979 100644 --- a/reoptjl/test/posts/all_inputs_test.json +++ b/reoptjl/test/posts/all_inputs_test.json @@ -26,7 +26,9 @@ "PM25_onsite_fuelburn_cost_per_tonne": null, "NOx_cost_escalation_rate_fraction": null, "SO2_cost_escalation_rate_fraction": null, - "PM25_cost_escalation_rate_fraction": null + "PM25_cost_escalation_rate_fraction": null, + "min_initial_capital_costs_before_incentives": null, + "max_initial_capital_costs_before_incentives": null }, "ElectricLoad": { "annual_kwh": 190000.0, @@ -167,10 +169,14 @@ "can_grid_charge": true, "installed_cost_per_kw": 840.0, "installed_cost_per_kwh": 420.0, + "installed_cost_constant": 0.0, "replace_cost_per_kw": 410.0, "replace_cost_per_kwh": 200.0, + "replace_cost_constant": 0.0, "inverter_replacement_year": 10, "battery_replacement_year": 10, + "cost_constant_replacement_year": 10, + "om_cost_fraction_of_installed_cost": 0.0, "macrs_option_years": 7, "macrs_bonus_fraction": 1.0, "macrs_itc_reduction": 0.5, diff --git a/reoptjl/test/posts/outage.json b/reoptjl/test/posts/outage.json index 1a212b871..5658437c5 100644 --- a/reoptjl/test/posts/outage.json +++ b/reoptjl/test/posts/outage.json @@ -75,8 +75,11 @@ "macrs_bonus_fraction": 1.0, "installed_cost_per_kw": 840.0, "installed_cost_per_kwh": 420.0, + "installed_cost_constant": 0.0, "replace_cost_per_kw": 410.0, - "replace_cost_per_kwh": 200.0 + "replace_cost_per_kwh": 200.0, + "replace_cost_constant": 0.0, + "om_cost_fraction_of_installed_cost": 0.0 }, "Financial": { "value_of_lost_load_per_kwh": 100.0, diff --git a/reoptjl/test/posts/pv_batt_emissions.json b/reoptjl/test/posts/pv_batt_emissions.json index fc926b1a9..4853c5b43 100644 --- a/reoptjl/test/posts/pv_batt_emissions.json +++ b/reoptjl/test/posts/pv_batt_emissions.json @@ -35,8 +35,11 @@ "macrs_bonus_fraction": 0.4, "replace_cost_per_kw": 460.0, "replace_cost_per_kwh": 230.0, + "replace_cost_constant": 0.0, "installed_cost_per_kw": 1000.0, "installed_cost_per_kwh": 500.0, + "installed_cost_constant": 0.0, + "om_cost_fraction_of_installed_cost": 0.0, "total_itc_fraction": 0.0 }, "ElectricTariff": { diff --git a/reoptjl/testing.txt b/reoptjl/testing.txt new file mode 100644 index 000000000..e69de29bb