Skip to content

Commit

Permalink
Merge pull request #397 from NREL/develop
Browse files Browse the repository at this point in the history
Error passing from Julia and Add Thermal Storages to job app (/dev)
  • Loading branch information
Bill-Becker authored Jan 4, 2023
2 parents c8dc820 + 65610e9 commit fb6c764
Show file tree
Hide file tree
Showing 20 changed files with 1,271 additions and 247 deletions.
31 changes: 31 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,34 @@ Classify the change according to the following categories:
##### Removed
### Patches

## v2.6.0
### Minor Updates
#### Added
1. **REoptjlMessageOutputs** model to capture errors and warnings returned by REoptjl during input processing and post optimization
2. Missing output fields for **ExistingBoilerOutputs** model
3. API test `job\test\posts\all_inputs_test.json` to include all input models in a single API test
- added **HotThermalStorageInputs** model
- added **HotThermalStorageOutputs** model
- added **ColdThermalStorageInputs** model
- added **ColdThermalStorageOutputs** model
- add **HotThermalStorageOutputs**
- add **ColdThermalStorageOutputs**
- `0012_coldthermalstorageinputs....` file used to add new models to the db

#### Changed
1. Default values for the following fields were changed to align them with REopt API v2 (i.e. stable, and REopt.jl) defaults. As-is, these values are aligned with REopt v1 defaults. Units were unchanged.
- **FinancialInputs.elec_cost_escalation_rate_fraction** from 0.023 to 0.019
- **FinancialInputs.offtaker_discount_rate_fraction** from 0.083 to 0.0564
- **FinancialInputs.owner_discount_rate_fraction** from 0.083 to 0.0564
- **PVInputs.installed_cost_per_kw** from 1600 to 1592
- **PVInputs.om_cost_per_kw** from 16 to 17
- **WindInputs.om_cost_per_kw** from 16 to 35
- **ElectricStorageInputs.installed_cost_per_kw** from 840 to 775
- **ElectricStorageInputs.installed_cost_per_kwh** from 420 to 388
- **ElectricStorageInputs.replace_cost_per_kw** from 410 to 440
- **ElectricStorageInputs.replace_cost_per_kwh** from 200 to 220
2. Modified `julia_src\http.jl` and `julia_src\cbc\http.jl` to return status 400 when REopt responds with an error
3. Updated `r["Messages"]` in `views.py` to include **REoptjlMessageOutputs** errors and warnings

## v2.5.0
### Minor Updates
Expand Down Expand Up @@ -63,6 +91,7 @@ In `job/views.py`:
### Minor Updates
##### Fixed
Lookback charge parameters expected from the URDB API call were changed to the non-caplitalized format, so they are now used properly.

## v2.3.0
##### Changed
The following name changes were made in the `job/` endpoint and `julia_src/http.jl`:
Expand All @@ -72,6 +101,7 @@ The following name changes were made in the `job/` endpoint and `julia_src/http.
- Updated the version of REopt.jl in /julia_src to v0.20.0 which includes the addition of:
- Boiler tech from the REopt_API (known as NewBoiler in API)
- SteamTurbine tech from the REopt_API

## v2.2.0
### Minor Updates
##### Fixed
Expand Down Expand Up @@ -114,6 +144,7 @@ The following name changes were made in the `job/` endpoint and `julia_src/http.
- **FinancialOutputs**
- add `breakeven_cost_of_emissions_reduction_per_tonnes_CO2`
In `job/process_results.py`:
- add **HotThermalStorageOutputs**
- add **ExistingBoilerOutputs**
In `job/test/test_job_endpoint.py`:
- test that AVERT and EASIUR defaults for emissions inputs not provided by user are passed back from REopt.jl and saved in database
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Generated by Django 4.0.7 on 2022-12-21 17:55

import django.contrib.postgres.fields
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import job.models


class Migration(migrations.Migration):

dependencies = [
('job', '0015_coolingloadinputs_coolingloadoutputs_and_more'),
]

operations = [
migrations.CreateModel(
name='ColdThermalStorageInputs',
fields=[
('meta', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='ColdThermalStorageInputs', serialize=False, to='job.apimeta')),
('min_gal', models.FloatField(blank=True, default=0.0, help_text='Minimum TES volume (energy) size constraint for optimization', null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000000000.0)])),
('max_gal', models.FloatField(blank=True, default=0.0, help_text='Maximum TES volume (energy) size constraint for optimization. Set to zero to disable storage', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000000000.0)])),
('cool_water_temp_degF', models.FloatField(blank=True, default=44.0, help_text='Cold-side supply water temperature from ColdTES (top of tank) to the heating load', validators=[django.core.validators.MinValueValidator(33.0), django.core.validators.MaxValueValidator(200.0)])),
('hot_water_temp_degF', models.FloatField(blank=True, default=56.0, help_text='Cold-side return water temperature from the heating load to the ColdTES (bottom of tank)', validators=[django.core.validators.MinValueValidator(40.0), django.core.validators.MaxValueValidator(210.0)])),
('internal_efficiency_fraction', models.FloatField(blank=True, default=0.999999, help_text='Thermal losses due to mixing from thermal power entering or leaving tank', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1.0)])),
('soc_min_fraction', models.FloatField(blank=True, default=0.1, help_text='Minimum allowable battery state of charge as fraction of energy capacity.', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1.0)])),
('soc_init_fraction', models.FloatField(blank=True, default=0.5, help_text='Battery state of charge at first hour of optimization as fraction of energy capacity.', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1.0)])),
('installed_cost_per_gal', models.FloatField(blank=True, default=1.5, help_text='Installed cold TES cost in $/gal', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10000.0)])),
('om_cost_per_gal', models.FloatField(blank=True, default=0.0, help_text='Annual cold TES operations and maintenance costs in $/gal', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000.0)])),
('thermal_decay_rate_fraction', models.FloatField(blank=True, default=0.0004, help_text='Thermal energy-based cost of TES (e.g. volume of the tank)', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1.0)])),
('macrs_option_years', models.IntegerField(blank=True, choices=[(0, 'Zero'), (5, 'Five'), (7, 'Seven')], default=0, help_text='Duration over which accelerated depreciation will occur. Set to zero to disable')),
('macrs_bonus_fraction', models.FloatField(blank=True, default=0.0, help_text='Percent of upfront project costs to depreciate in year one in addition to scheduled depreciation', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1)])),
('macrs_itc_reduction', models.FloatField(blank=True, default=0.0, help_text='Percent of the ITC value by which depreciable basis is reduced', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1)])),
('total_itc_fraction', models.FloatField(blank=True, default=0.0, help_text='Total investment tax credit in percent applied toward capital costs', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1)])),
('total_rebate_per_kwh', models.FloatField(blank=True, default=0.0, help_text='Rebate per unit installed energy capacity', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000000000.0)])),
],
bases=(job.models.BaseModel, models.Model),
),
migrations.CreateModel(
name='ColdThermalStorageOutputs',
fields=[
('meta', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='ColdThermalStorageOutputs', serialize=False, to='job.apimeta')),
('size_gal', models.FloatField(blank=True, null=True)),
('year_one_soc_series_fraction', django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, size=None)),
('year_one_to_load_series_ton', django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, size=None)),
],
bases=(job.models.BaseModel, models.Model),
),
migrations.CreateModel(
name='HotThermalStorageInputs',
fields=[
('meta', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='HotThermalStorageInputs', serialize=False, to='job.apimeta')),
('min_gal', models.FloatField(blank=True, default=0.0, help_text='Minimum TES volume (energy) size constraint for optimization', null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000000000.0)])),
('max_gal', models.FloatField(blank=True, default=0.0, help_text='Maximum TES volume (energy) size constraint for optimization. Set to zero to disable storage', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000000000.0)])),
('hot_water_temp_degF', models.FloatField(blank=True, default=180.0, help_text='Hot-side supply water temperature from HotTES (top of tank) to the heating load', validators=[django.core.validators.MinValueValidator(40.0), django.core.validators.MaxValueValidator(210.0)])),
('cool_water_temp_degF', models.FloatField(blank=True, default=160.0, help_text='Cold-side return water temperature from the heating load to the HotTES (bottom of tank)', validators=[django.core.validators.MinValueValidator(33.0), django.core.validators.MaxValueValidator(200.0)])),
('internal_efficiency_fraction', models.FloatField(blank=True, default=0.999999, help_text='Thermal losses due to mixing from thermal power entering or leaving tank', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1.0)])),
('soc_min_fraction', models.FloatField(blank=True, default=0.1, help_text='Minimum allowable battery state of charge as fraction of energy capacity.', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1.0)])),
('soc_init_fraction', models.FloatField(blank=True, default=0.5, help_text='Battery state of charge at first hour of optimization as fraction of energy capacity.', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1.0)])),
('installed_cost_per_gal', models.FloatField(blank=True, default=1.5, help_text='Installed hot TES cost in $/gal', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10000.0)])),
('om_cost_per_gal', models.FloatField(blank=True, default=0.0, help_text='Annual hot TES operations and maintenance costs in $/gal', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000.0)])),
('thermal_decay_rate_fraction', models.FloatField(blank=True, default=0.0004, help_text='Thermal energy-based cost of TES (e.g. volume of the tank)', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1.0)])),
('macrs_option_years', models.IntegerField(blank=True, choices=[(0, 'Zero'), (5, 'Five'), (7, 'Seven')], default=0, help_text='Duration over which accelerated depreciation will occur. Set to zero to disable')),
('macrs_bonus_fraction', models.FloatField(blank=True, default=0.0, help_text='Percent of upfront project costs to depreciate in year one in addition to scheduled depreciation', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1)])),
('macrs_itc_reduction', models.FloatField(blank=True, default=0.0, help_text='Percent of the ITC value by which depreciable basis is reduced', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1)])),
('total_itc_fraction', models.FloatField(blank=True, default=0.0, help_text='Total investment tax credit in percent applied toward capital costs', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1)])),
('total_rebate_per_kwh', models.FloatField(blank=True, default=0.0, help_text='Rebate per unit installed energy capacity', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000000000.0)])),
],
bases=(job.models.BaseModel, models.Model),
),
migrations.CreateModel(
name='HotThermalStorageOutputs',
fields=[
('meta', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='HotThermalStorageOutputs', serialize=False, to='job.apimeta')),
('size_gal', models.FloatField(blank=True, null=True)),
('year_one_soc_series_fraction', django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, size=None)),
('year_one_to_load_series_mmbtu_per_hour', django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, size=None)),
],
bases=(job.models.BaseModel, models.Model),
),
]
81 changes: 81 additions & 0 deletions job/migrations/0016_reoptjlmessageoutputs_and_more.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Generated by Django 4.0.7 on 2022-12-19 15:24

import django.contrib.postgres.fields
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import job.models


class Migration(migrations.Migration):

dependencies = [
('job', '0015_coolingloadinputs_coolingloadoutputs_and_more'),
]

operations = [
migrations.CreateModel(
name='REoptjlMessageOutputs',
fields=[
('meta', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='REoptjlMessageOutputs', serialize=False, to='job.apimeta')),
('errors', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(blank=True, null=True), default=list, size=None)),
('warnings', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(blank=True, null=True), default=list, size=None)),
],
bases=(job.models.BaseModel, models.Model),
),
migrations.AddField(
model_name='existingboileroutputs',
name='thermal_to_tes_series_mmbtu_per_hour',
field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, size=None),
),
migrations.AlterField(
model_name='electricstorageinputs',
name='installed_cost_per_kw',
field=models.FloatField(blank=True, default=775.0, help_text='Total upfront battery power capacity costs (e.g. inverter and balance of power systems)', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10000.0)]),
),
migrations.AlterField(
model_name='electricstorageinputs',
name='installed_cost_per_kwh',
field=models.FloatField(blank=True, default=388.0, help_text='Total upfront battery costs', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10000.0)]),
),
migrations.AlterField(
model_name='electricstorageinputs',
name='replace_cost_per_kw',
field=models.FloatField(blank=True, default=440.0, help_text='Battery power capacity replacement cost at time of replacement year', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10000.0)]),
),
migrations.AlterField(
model_name='electricstorageinputs',
name='replace_cost_per_kwh',
field=models.FloatField(blank=True, default=220.0, help_text='Battery energy capacity replacement cost at time of replacement year', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10000.0)]),
),
migrations.AlterField(
model_name='financialinputs',
name='elec_cost_escalation_rate_fraction',
field=models.FloatField(blank=True, default=0.019, help_text='Annual nominal utility electricity cost escalation rate.', validators=[django.core.validators.MinValueValidator(-1), django.core.validators.MaxValueValidator(1)]),
),
migrations.AlterField(
model_name='financialinputs',
name='offtaker_discount_rate_fraction',
field=models.FloatField(blank=True, default=0.0564, help_text='Nominal energy offtaker discount rate. In single ownership model the offtaker is also the generation owner.', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1)]),
),
migrations.AlterField(
model_name='financialinputs',
name='owner_discount_rate_fraction',
field=models.FloatField(blank=True, default=0.0564, help_text='Nominal generation owner discount rate. Used for two party financing model. In two party ownership model the offtaker does not own the generator(s).', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1)]),
),
migrations.AlterField(
model_name='pvinputs',
name='installed_cost_per_kw',
field=models.FloatField(blank=True, default=1592, help_text='Installed PV cost in $/kW', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100000.0)]),
),
migrations.AlterField(
model_name='pvinputs',
name='om_cost_per_kw',
field=models.FloatField(blank=True, default=17, help_text='Annual PV operations and maintenance costs in $/kW', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000.0)]),
),
migrations.AlterField(
model_name='windinputs',
name='om_cost_per_kw',
field=models.FloatField(blank=True, default=35, help_text='Annual operations and maintenance costs in $/kW', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000.0)]),
),
]
14 changes: 14 additions & 0 deletions job/migrations/0017_merge_20230102_1621.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Generated by Django 4.0.4 on 2023-01-02 16:21

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('job', '0016_coldthermalstorageinputs_coldthermalstorageoutputs_and_more'),
('job', '0016_reoptjlmessageoutputs_and_more'),
]

operations = [
]
Loading

0 comments on commit fb6c764

Please sign in to comment.