diff --git a/CHANGELOG.md b/CHANGELOG.md index 7458b5124..af7c02ee0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,15 @@ Classify the change according to the following categories: ##### Removed ### Patches +## v3.17.4 +### Minor Updates +##### Fixed +- `/schedule_stats` endpoint by updating syntax for Pandas date_range based on Python 3.12 upgrade +- Enabled campus/blended building types for a POST request to `/simulated_load` endpoint by adding `percent_share` +##### Changed +- Reduced the minimum energy input for heating and cooling loads to zero, from 1.0 +- Increased the max value for `annual_tonhour` cooling input by an order of magnitude + ## v3.17.3 ### Minor Updates ##### Added diff --git a/reo/utilities.py b/reo/utilities.py index da9b8b510..4e239072f 100644 --- a/reo/utilities.py +++ b/reo/utilities.py @@ -273,7 +273,7 @@ def generate_year_profile_hourly(year, consecutive_periods): end_date = "12/31/"+str(year) else: end_date = "1/1/"+str(year+1) - dt_profile = pd.date_range(start='1/1/'+str(year), end=end_date, freq="1H", closed="left") + dt_profile = pd.date_range(start='1/1/'+str(year), end=end_date, freq="1h", inclusive="left") year_profile_hourly_series = pd.Series(np.zeros(8760), index=dt_profile) # Check if the consecutive_periods is a list_of_dict or other (must be Pandas DataFrame), and if other, convert to list_of_dict @@ -333,7 +333,7 @@ def get_weekday_weekend_total_hours_by_month(year, year_profile_hourly_list): end_date = "12/31/"+str(year) else: end_date = "1/1/"+str(year+1) - dt_profile = pd.date_range(start='1/1/'+str(year), end=end_date, freq="1H", closed="left") + dt_profile = pd.date_range(start='1/1/'+str(year), end=end_date, freq="1h", inclusive="left") year_profile_hourly_series = pd.Series(year_profile_hourly_list, index=dt_profile) unavail_hours = year_profile_hourly_series[year_profile_hourly_series == 1] weekday_weekend_total_hours_by_month = {m:{} for m in range(1,13)} diff --git a/reoptjl/migrations/0105_alter_coolingloadinputs_annual_tonhour_and_more.py b/reoptjl/migrations/0105_alter_coolingloadinputs_annual_tonhour_and_more.py new file mode 100644 index 000000000..3c69ba70c --- /dev/null +++ b/reoptjl/migrations/0105_alter_coolingloadinputs_annual_tonhour_and_more.py @@ -0,0 +1,34 @@ +# Generated by Django 4.0.7 on 2025-09-26 14:42 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('reoptjl', '0104_cstinputs_can_waste_heat'), + ] + + operations = [ + migrations.AlterField( + model_name='coolingloadinputs', + name='annual_tonhour', + field=models.FloatField(blank=True, help_text="Annual electric chiller thermal energy production, in [Ton-Hour],used to scale simulated default electric chiller load profile for the site's climate zone", null=True, validators=[django.core.validators.MinValueValidator(0.0), django.core.validators.MaxValueValidator(1000000000.0)]), + ), + migrations.AlterField( + model_name='domestichotwaterloadinputs', + name='annual_mmbtu', + field=models.FloatField(blank=True, help_text="Annual site DHW consumption, used to scale simulated default building load profile for the site's climate zone [MMBtu]", null=True, validators=[django.core.validators.MinValueValidator(0.0), django.core.validators.MaxValueValidator(100000000.0)]), + ), + migrations.AlterField( + model_name='processheatloadinputs', + name='annual_mmbtu', + field=models.FloatField(blank=True, help_text='Annual site process heat fuel consumption, used to scale simulated default industry load profile [MMBtu]', null=True, validators=[django.core.validators.MinValueValidator(0.0), django.core.validators.MaxValueValidator(100000000.0)]), + ), + migrations.AlterField( + model_name='spaceheatingloadinputs', + name='annual_mmbtu', + field=models.FloatField(blank=True, help_text="Annual site space heating consumption, used to scale simulated default building load profile for the site's climate zone [MMBtu]", null=True, validators=[django.core.validators.MinValueValidator(0.0), django.core.validators.MaxValueValidator(100000000.0)]), + ), + ] diff --git a/reoptjl/migrations/0113_merge_20251209_2338.py b/reoptjl/migrations/0113_merge_20251209_2338.py new file mode 100644 index 000000000..05d790afc --- /dev/null +++ b/reoptjl/migrations/0113_merge_20251209_2338.py @@ -0,0 +1,14 @@ +# Generated by Django 4.2.26 on 2025-12-09 23:38 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('reoptjl', '0105_alter_coolingloadinputs_annual_tonhour_and_more'), + ('reoptjl', '0112_windinputs_acres_per_kw'), + ] + + operations = [ + ] diff --git a/reoptjl/models.py b/reoptjl/models.py index 9335f5929..17a9560b3 100644 --- a/reoptjl/models.py +++ b/reoptjl/models.py @@ -5116,8 +5116,8 @@ class CoolingLoadInputs(BaseModel, models.Model): annual_tonhour = models.FloatField( validators=[ - MinValueValidator(1), - MaxValueValidator(MAX_BIG_NUMBER) + MinValueValidator(0.0), + MaxValueValidator(1.0e9) ], null=True, blank=True, @@ -7654,7 +7654,7 @@ class SpaceHeatingLoadInputs(BaseModel, models.Model): annual_mmbtu = models.FloatField( validators=[ - MinValueValidator(1), + MinValueValidator(0.0), MaxValueValidator(MAX_BIG_NUMBER) ], null=True, @@ -7829,7 +7829,7 @@ class DomesticHotWaterLoadInputs(BaseModel, models.Model): annual_mmbtu = models.FloatField( validators=[ - MinValueValidator(1), + MinValueValidator(0.0), MaxValueValidator(MAX_BIG_NUMBER) ], null=True, @@ -7985,7 +7985,7 @@ class ProcessHeatLoadInputs(BaseModel, models.Model): annual_mmbtu = models.FloatField( validators=[ - MinValueValidator(1), + MinValueValidator(0.0), MaxValueValidator(MAX_BIG_NUMBER) ], null=True, diff --git a/reoptjl/views.py b/reoptjl/views.py index efe4a7733..e4984a43e 100644 --- a/reoptjl/views.py +++ b/reoptjl/views.py @@ -656,6 +656,7 @@ def simulated_load(request): data = json.loads(request.body) required_post_fields = ["load_type", "year"] either_required = ["normalize_and_scale_load_profile_input", "doe_reference_name"] + optional = ["percent_share"] either_check = 0 for either in either_required: if data.get(either) is not None: @@ -668,6 +669,9 @@ def simulated_load(request): for field in required_post_fields: # TODO make year optional for doe_reference_name input inputs[field] = data[field] + for opt in optional: + if data.get(opt) is not None: + inputs[opt] = data[opt] if data.get("normalize_and_scale_load_profile_input") is not None: if "load_profile" not in data: return JsonResponse({"Error": "load_profile is required when normalize_and_scale_load_profile_input is provided."}, status=400)