Skip to content

Commit

Permalink
Merge pull request #501 from NREL/develop
Browse files Browse the repository at this point in the history
Production K8S server resources for v3 and add /v3 endpoints
  • Loading branch information
Bill-Becker authored Sep 15, 2023
2 parents 88dbb42 + 6a53751 commit 121f87c
Show file tree
Hide file tree
Showing 10 changed files with 179 additions and 21 deletions.
10 changes: 5 additions & 5 deletions .helm/values.production.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ djangoSettingsModule: reopt_api.production_settings
djangoReplicas: 10
djangoMemoryRequest: "2800Mi"
djangoMemoryLimit: "2800Mi"
celeryReplicas: 20
celeryReplicas: 10
celeryMemoryRequest: "900Mi"
celeryMemoryLimit: "900Mi"
juliaReplicas: 20
juliaCpuRequest: "300m"
juliaReplicas: 15
juliaCpuRequest: "1000m"
juliaCpuLimit: "4000m"
juliaMemoryRequest: "8000Mi"
juliaMemoryLimit: "8000Mi"
juliaMemoryRequest: "12000Mi"
juliaMemoryLimit: "12000Mi"
2 changes: 1 addition & 1 deletion .helm/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ djangoMemoryRequest: "1600Mi"
djangoMemoryLimit: "1600Mi"
celeryReplicas: 2
celeryCpuRequest: "100m"
celeryCpuLimit: "800m"
celeryCpuLimit: "2000m"
celeryMemoryRequest: "700Mi"
celeryMemoryLimit: "700Mi"
juliaReplicas: 2
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ Classify the change according to the following categories:
##### Removed
### Patches

## Develop 2023/09/06
### Minor Updates
##### Added
- /v3 endpoints which use the reoptjl app and the REopt.jl Julia package, but /stable still points to /v2 so this is not a breaking change
##### Fixed
- Fixed a bug in the `get_existing_chiller_default_cop` endpoint not accepting blank/null inputs that are optional

## v2.15.0
### Minor Updates
##### Added
Expand Down
17 changes: 15 additions & 2 deletions reopt_api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
from resilience_stats.api import ERPJob
from tastypie.api import Api
from reo import views
from reoptjl.api import Job as DevJob
from reoptjl.api import Job as REoptJLJob
from futurecosts.api import FutureCostsAPI
from ghpghx.resources import GHPGHXJob
from reo.api import Job2
Expand All @@ -50,13 +50,18 @@
v2_api.register(OutageSimJob())
v2_api.register(GHPGHXJob())

v3_api = Api(api_name='v3')
v3_api.register(REoptJLJob())
v3_api.register(GHPGHXJob())
v3_api.register(ERPJob())

stable_api = Api(api_name='stable')
stable_api.register(Job2())
stable_api.register(OutageSimJob())
stable_api.register(GHPGHXJob())

dev_api = Api(api_name='dev')
dev_api.register(DevJob())
dev_api.register(REoptJLJob())
dev_api.register(FutureCostsAPI())
dev_api.register(GHPGHXJob())
dev_api.register(ERPJob())
Expand Down Expand Up @@ -92,6 +97,14 @@ def page_not_found(request, url):
path('v2/', include('ghpghx.urls')),
re_path(r'', include(v2_api.urls)),

path('v3/', include('reoptjl.urls')),
path('v3/', include('resilience_stats.urls_v3plus')),
path('v3/', include('ghpghx.urls')),
path('v3/', include('load_builder.urls')),
# TODO proforma for v3
# (summary is within reoptjl.urls)
re_path(r'', include(v3_api.urls)),

path('stable/', include('reo.urls_v2')),
path('stable/', include('resilience_stats.urls_v1_v2')),
path('stable/', include('proforma.urls')),
Expand Down
2 changes: 1 addition & 1 deletion reoptjl/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ def obj_create(self, bundle, **kwargs):
meta = {
"run_uuid": run_uuid,
"api_version": 3,
"reopt_version": "0.30.0",
"reopt_version": "0.32.7",
"status": "Validating..."
}
bundle.data.update({"APIMeta": meta})
Expand Down
94 changes: 94 additions & 0 deletions reoptjl/migrations/0042_alter_boilerinputs_efficiency_and_more.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Generated by Django 4.0.7 on 2023-09-12 22:25

import django.core.validators
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('reoptjl', '0041_merge_20230901_1601'),
]

operations = [
migrations.AlterField(
model_name='boilerinputs',
name='efficiency',
field=models.FloatField(blank=True, default=0.8, help_text='New boiler system efficiency - conversion of fuel to usable heating thermal energy.', null=True, validators=[django.core.validators.MinValueValidator(0.0), django.core.validators.MaxValueValidator(1.0)]),
),
migrations.AlterField(
model_name='boilerinputs',
name='max_mmbtu_per_hour',
field=models.FloatField(blank=True, default=10000000.0, help_text='Maximum thermal power size', null=True, validators=[django.core.validators.MinValueValidator(0.0), django.core.validators.MaxValueValidator(100000000.0)]),
),
migrations.AlterField(
model_name='boilerinputs',
name='min_mmbtu_per_hour',
field=models.FloatField(blank=True, default=0.0, help_text='Minimum thermal power size', null=True, validators=[django.core.validators.MinValueValidator(0.0), django.core.validators.MaxValueValidator(100000000.0)]),
),
migrations.AlterField(
model_name='ghpinputs',
name='building_sqft',
field=models.FloatField(help_text='Building square footage for GHP/HVAC cost calculations', null=True, validators=[django.core.validators.MinValueValidator(0.0), django.core.validators.MaxValueValidator(100000000.0)]),
),
migrations.AlterField(
model_name='steamturbineinputs',
name='can_curtail',
field=models.BooleanField(blank=True, default=False, help_text='True/False for if technology has the ability to curtail energy production.', null=True),
),
migrations.AlterField(
model_name='steamturbineinputs',
name='can_export_beyond_nem_limit',
field=models.BooleanField(blank=True, default=False, help_text='True/False for if technology can export energy beyond the annual site load (and be compensated for that energy at the export_rate_beyond_net_metering_limit).Note that if off-grid is true, can_export_beyond_nem_limit is always set to False.', null=True),
),
migrations.AlterField(
model_name='steamturbineinputs',
name='can_net_meter',
field=models.BooleanField(blank=True, default=False, help_text='True/False for if technology has option to participate in net metering agreement with utility. Note that a technology can only participate in either net metering or wholesale rates (not both).Note that if off-grid is true, net metering is always set to False.', null=True),
),
migrations.AlterField(
model_name='steamturbineinputs',
name='can_wholesale',
field=models.BooleanField(blank=True, default=False, help_text='True/False for if technology has option to export energy that is compensated at the wholesale_rate. Note that a technology can only participate in either net metering or wholesale rates (not both).Note that if off-grid is true, can_wholesale is always set to False.', null=True),
),
migrations.AlterField(
model_name='steamturbineinputs',
name='inlet_steam_superheat_degF',
field=models.FloatField(blank=True, default=0.0, help_text='Alternative input to inlet steam temperature, this is the superheat amount (delta from T_saturation) to the steam turbine', null=True, validators=[django.core.validators.MinValueValidator(0.0), django.core.validators.MaxValueValidator(700.0)]),
),
migrations.AlterField(
model_name='steamturbineinputs',
name='is_condensing',
field=models.BooleanField(blank=True, default=False, help_text='Steam turbine type, if it is a condensing turbine which produces no useful thermal (max electric output)', null=True),
),
migrations.AlterField(
model_name='steamturbineinputs',
name='macrs_bonus_fraction',
field=models.FloatField(blank=True, default=1.0, help_text='Percent of upfront project costs to depreciate in year one in addition to scheduled depreciation', null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1)]),
),
migrations.AlterField(
model_name='steamturbineinputs',
name='macrs_option_years',
field=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', null=True),
),
migrations.AlterField(
model_name='steamturbineinputs',
name='max_kw',
field=models.FloatField(blank=True, default=100000000.0, help_text='Maximum steam turbine size constraint for optimization', null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000000000.0)]),
),
migrations.AlterField(
model_name='steamturbineinputs',
name='min_kw',
field=models.FloatField(blank=True, default=0.0, help_text='Minimum steam turbine size constraint for optimization', null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000000000.0)]),
),
migrations.AlterField(
model_name='steamturbineinputs',
name='om_cost_per_kw',
field=models.FloatField(blank=True, default=0.0, help_text='Annual steam turbine fixed operations and maintenance costs in $/kW', null=True, validators=[django.core.validators.MinValueValidator(0.0), django.core.validators.MaxValueValidator(5000.0)]),
),
migrations.AlterField(
model_name='steamturbineinputs',
name='outlet_steam_min_vapor_fraction',
field=models.FloatField(blank=True, default=0.8, help_text='Minimum practical vapor fraction of steam at the exit of the steam turbine', null=True, validators=[django.core.validators.MinValueValidator(0.0), django.core.validators.MaxValueValidator(1.0)]),
),
]
22 changes: 20 additions & 2 deletions reoptjl/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4819,6 +4819,8 @@ class BoilerInputs(BaseModel, models.Model):
))

min_mmbtu_per_hour = models.FloatField(
null=True,
blank=True,
validators=[
MinValueValidator(0.0),
MaxValueValidator(MAX_BIG_NUMBER)
Expand All @@ -4828,11 +4830,13 @@ class BoilerInputs(BaseModel, models.Model):
)

max_mmbtu_per_hour = models.FloatField(
null=True,
blank=True,
validators=[
MinValueValidator(0.0),
MaxValueValidator(MAX_BIG_NUMBER)
],
default=0.0,
default=1.0E7,
help_text="Maximum thermal power size"
)

Expand All @@ -4842,6 +4846,7 @@ class BoilerInputs(BaseModel, models.Model):
MaxValueValidator(1.0)
],
null=True,
blank=True,
default=0.8,
help_text="New boiler system efficiency - conversion of fuel to usable heating thermal energy."
)
Expand Down Expand Up @@ -5009,6 +5014,7 @@ class SIZE_CLASS_LIST(models.IntegerChoices):
FOUR = 4

min_kw = models.FloatField(
null=True,
default=0.0,
validators=[
MinValueValidator(0),
Expand All @@ -5018,7 +5024,8 @@ class SIZE_CLASS_LIST(models.IntegerChoices):
help_text="Minimum steam turbine size constraint for optimization"
)
max_kw = models.FloatField(
default=0.0,
null=True,
default=MAX_BIG_NUMBER,
validators=[
MinValueValidator(0),
MaxValueValidator(1.0e9)
Expand Down Expand Up @@ -5137,13 +5144,15 @@ class SIZE_CLASS_LIST(models.IntegerChoices):
)

is_condensing = models.BooleanField(
null=True,
blank=True,
default = False,
help_text="Steam turbine type, if it is a condensing turbine which produces no useful thermal (max electric output)"
)

inlet_steam_superheat_degF = models.FloatField(
null=True,
blank=True,
validators=[
MinValueValidator(0.0),
MaxValueValidator(700.0)
Expand All @@ -5153,6 +5162,7 @@ class SIZE_CLASS_LIST(models.IntegerChoices):
)

outlet_steam_min_vapor_fraction = models.FloatField(
null=True,
default=0.8,
validators=[
MinValueValidator(0.0),
Expand All @@ -5179,10 +5189,12 @@ class SIZE_CLASS_LIST(models.IntegerChoices):
],
default=0.0,
null=True,
blank=True,
help_text="Annual steam turbine fixed operations and maintenance costs in $/kW"
)

can_net_meter = models.BooleanField(
null=True,
blank=True,
default = False,
help_text=("True/False for if technology has option to participate in net metering agreement with utility. "
Expand All @@ -5191,13 +5203,15 @@ class SIZE_CLASS_LIST(models.IntegerChoices):
)

can_wholesale = models.BooleanField(
null=True,
blank=True,
default = False,
help_text=("True/False for if technology has option to export energy that is compensated at the wholesale_rate. "
"Note that a technology can only participate in either net metering or wholesale rates (not both)."
"Note that if off-grid is true, can_wholesale is always set to False.")
)
can_export_beyond_nem_limit = models.BooleanField(
null=True,
blank=True,
default = False,
help_text=("True/False for if technology can export energy beyond the annual site load (and be compensated for "
Expand All @@ -5207,13 +5221,15 @@ class SIZE_CLASS_LIST(models.IntegerChoices):

can_curtail = models.BooleanField(
default=False,
null=True,
blank=True,
help_text="True/False for if technology has the ability to curtail energy production."
)

macrs_option_years = models.IntegerField(
default=MACRS_YEARS_CHOICES.ZERO,
choices=MACRS_YEARS_CHOICES.choices,
null=True,
blank=True,
help_text="Duration over which accelerated depreciation will occur. Set to zero to disable"
)
Expand All @@ -5224,6 +5240,7 @@ class SIZE_CLASS_LIST(models.IntegerChoices):
MinValueValidator(0),
MaxValueValidator(1)
],
null=True,
blank=True,
help_text="Percent of upfront project costs to depreciate in year one in addition to scheduled depreciation"
)
Expand Down Expand Up @@ -6431,6 +6448,7 @@ class GHPInputs(BaseModel, models.Model):

# REQUIRED FOR GHP
building_sqft = models.FloatField(
null=True,
validators=[
MinValueValidator(0.0),
MaxValueValidator(MAX_BIG_NUMBER)
Expand Down
16 changes: 14 additions & 2 deletions reoptjl/test/test_http_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,9 +218,21 @@ def test_default_existing_chiller_cop(self):
"max_load_kw_thermal":100
}

# Call to the django view endpoint /ghp_efficiency_thermal_factors which calls the http.jl endpoint
# Call to the django view endpoint /get_existing_chiller_default_cop which calls the http.jl endpoint
resp = self.api_client.get(f'/dev/get_existing_chiller_default_cop', data=inputs_dict)
view_response = json.loads(resp.content)
print(view_response)

self.assertEqual(view_response["existing_chiller_cop"], 4.4)

# Test empty dictionary, which should return unknown value
inputs_dict = {}

# Call to the django view endpoint /get_existing_chiller_default_cop which calls the http.jl endpoint
resp = self.api_client.get(f'/dev/get_existing_chiller_default_cop', data=inputs_dict)
view_response = json.loads(resp.content)
print(view_response)

self.assertEqual(view_response["existing_chiller_cop"], 4.4)
self.assertEqual(view_response["existing_chiller_cop"], 4.545)


2 changes: 1 addition & 1 deletion reoptjl/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -542,7 +542,7 @@ def assign_ref_buildings_from_electric_load(self, load_to_assign):

def validate_offgrid_keys(self):
# From https://github.com/NREL/REopt.jl/blob/4b0fb7f6556b2b6e9a9a7e8fa65398096fb6610f/src/core/scenario.jl#L88
valid_input_keys_offgrid = ["PV", "Wind", "ElectricStorage", "Generator", "Settings", "Site", "Financial", "ElectricLoad", "ElectricTariff", "ElectricUtility"]
valid_input_keys_offgrid = ["PV", "Wind", "ElectricStorage", "Generator", "Settings", "Site", "Financial", "ElectricLoad", "ElectricTariff", "ElectricUtility", "Meta"]

invalid_input_keys_offgrid = list(set(list(self.models.keys()))-set(valid_input_keys_offgrid))
if 'APIMeta' in invalid_input_keys_offgrid:
Expand Down
28 changes: 21 additions & 7 deletions reoptjl/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -593,13 +593,27 @@ def get_existing_chiller_default_cop(request):
return: existing_chiller_cop: default COP of existing chiller [fraction]
"""
try:
existing_chiller_max_thermal_factor_on_peak_load = float(request.GET['existing_chiller_max_thermal_factor_on_peak_load']) # need float to convert unicode
max_load_kw = float(request.GET['max_load_kw'])
max_load_kw_thermal = float(request.GET['max_load_kw_thermal'])

inputs_dict = {"existing_chiller_max_thermal_factor_on_peak_load": existing_chiller_max_thermal_factor_on_peak_load,
"max_load_kw": max_load_kw,
"max_load_kw_thermal": max_load_kw_thermal}
existing_chiller_max_thermal_factor_on_peak_load = request.GET.get('existing_chiller_max_thermal_factor_on_peak_load')
if existing_chiller_max_thermal_factor_on_peak_load is not None:
existing_chiller_max_thermal_factor_on_peak_load = float(existing_chiller_max_thermal_factor_on_peak_load)
else:
existing_chiller_max_thermal_factor_on_peak_load = 1.25 # default from REopt.jl

max_load_kw = request.GET.get('max_load_kw')
if max_load_kw is not None:
max_load_kw = float(max_load_kw)

max_load_ton = request.GET.get('max_load_ton')
if max_load_ton is not None:
max_load_kw_thermal = float(max_load_ton) * 3.51685 # kWh thermal per ton-hour
else:
max_load_kw_thermal = None

inputs_dict = {
"existing_chiller_max_thermal_factor_on_peak_load": existing_chiller_max_thermal_factor_on_peak_load,
"max_load_kw": max_load_kw,
"max_load_kw_thermal": max_load_kw_thermal
}

julia_host = os.environ.get('JULIA_HOST', "julia")
http_jl_response = requests.get("http://" + julia_host + ":8081/get_existing_chiller_default_cop/", json=inputs_dict)
Expand Down

0 comments on commit 121f87c

Please sign in to comment.