Skip to content

Commit d200b46

Browse files
authored
Merge pull request #421 from NREL/dependabot/pip/oauthlib-3.2.2
Bump oauthlib from 3.2.1 to 3.2.2
2 parents 55f71d7 + b8035db commit d200b46

21 files changed

+70762
-55
lines changed

CHANGELOG.md

+8
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@ Classify the change according to the following categories:
2626
##### Removed
2727
### Patches
2828

29+
## Develop 2023-08-07
30+
### Minor Updates
31+
##### Added
32+
- Add `GHP` to `job` app for v3
33+
- Add `/ghp_efficiency_thermal_factors` endpoint to `job` app for v
34+
##### Changed
35+
- Update a couple of GHP functions to use the GhpGhx.jl package instead of previous Julia scripts and data from v2
36+
2937
## v2.14.0
3038
### Minor Updates
3139
##### Fixed

ghpghx/models.py

+1-19
Original file line numberDiff line numberDiff line change
@@ -82,25 +82,7 @@ class GHPGHXInputs(models.Model):
8282
ghx_shank_space_inch = models.FloatField(blank=True,
8383
default=2.5, validators=[MinValueValidator(0.5), MaxValueValidator(100.0)],
8484
help_text="Distance between the centerline of the upwards and downwards u-tube legs [in]")
85-
86-
ground_k_by_climate_zone = {
87-
"1A": 1.029,
88-
"2A": 1.348,
89-
"2B": 0.917,
90-
"3A": 1.243,
91-
"3B": 1.364,
92-
"3C": 1.117,
93-
"4A": 1.023,
94-
"4B": 0.972,
95-
"4C": 1.418,
96-
"5A": 1.726,
97-
"5B": 1.177,
98-
"6A": 0.977,
99-
"6B": 0.981,
100-
"7": 1.271,
101-
"8": 1.189
102-
}
103-
85+
# Default for ground_thermal_conductivity_btu_per_hr_ft_f varies by ASHRAE climate zone
10486
ground_thermal_conductivity_btu_per_hr_ft_f = models.FloatField(blank=True,
10587
default=1.18, validators=[MinValueValidator(0.01), MaxValueValidator(15.0)],
10688
help_text="Thermal conductivity of the ground surrounding the borehole field [Btu/(hr-ft-degF)]")

ghpghx/views.py

+7-9
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,14 @@
3535
import csv
3636
import json
3737
import pandas as pd
38+
import requests
3839
import logging
3940
from django.http import JsonResponse
4041
from django.http import HttpResponse
4142
from django.template import loader
4243
from django.views.decorators.csrf import csrf_exempt
4344
from ghpghx.resources import UUIDFilter
4445
from ghpghx.models import ModelManager, GHPGHXInputs
45-
from reo.utilities import get_climate_zone_and_nearest_city
46-
from reo.src.load_profile import BuiltInProfile
4746

4847
log = logging.getLogger(__name__)
4948

@@ -115,15 +114,14 @@ def ground_conductivity(request):
115114
latitude = float(request.GET['latitude']) # need float to convert unicode
116115
longitude = float(request.GET['longitude'])
117116

118-
climate_zone, nearest_city, geometric_flag = get_climate_zone_and_nearest_city(latitude, longitude, BuiltInProfile.default_cities)
119-
k_by_zone = copy.deepcopy(GHPGHXInputs.ground_k_by_climate_zone)
120-
k = k_by_zone[climate_zone]
117+
inputs_dict = {"latitude": latitude,
118+
"longitude": longitude}
121119

120+
julia_host = os.environ.get('JULIA_HOST', "julia")
121+
http_jl_response = requests.get("http://" + julia_host + ":8081/ground_conductivity/", json=inputs_dict)
122+
122123
response = JsonResponse(
123-
{
124-
"climate_zone": climate_zone,
125-
"thermal_conductivity": k
126-
}
124+
http_jl_response.json()
127125
)
128126
return response
129127

julia_src/http.jl

+73-1
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,21 @@ function reopt(req::HTTP.Request)
102102
chp_dict = Dict(key=>getfield(model_inputs.s.chp, key) for key in inputs_with_defaults_from_chp)
103103
else
104104
chp_dict = Dict()
105+
end
106+
if haskey(d, "GHP")
107+
inputs_with_defaults_from_ghp = [
108+
:space_heating_efficiency_thermal_factor,
109+
:cooling_efficiency_thermal_factor
110+
]
111+
ghp_dict = Dict(key=>getfield(model_inputs.s.ghp_option_list[1], key) for key in inputs_with_defaults_from_ghp)
112+
else
113+
ghp_dict = Dict()
105114
end
106115
inputs_with_defaults_set_in_julia = Dict(
107116
"Financial" => Dict(key=>getfield(model_inputs.s.financial, key) for key in inputs_with_defaults_from_easiur),
108117
"ElectricUtility" => Dict(key=>getfield(model_inputs.s.electric_utility, key) for key in inputs_with_defaults_from_avert),
109-
"CHP" => chp_dict
118+
"CHP" => chp_dict,
119+
"GHP" => ghp_dict
110120
)
111121
catch e
112122
@error "Something went wrong in REopt optimization!" exception=(e, catch_backtrace())
@@ -343,6 +353,66 @@ function simulated_load(req::HTTP.Request)
343353
end
344354
end
345355

356+
function ghp_efficiency_thermal_factors(req::HTTP.Request)
357+
d = JSON.parse(String(req.body))
358+
359+
@info "Getting ghp_efficiency_thermal_factors..."
360+
# The REopt.jl function assumes the REopt input dictionary is being mutated, so put in that form
361+
data = Dict([("Site", Dict([("latitude", d["latitude"]), ("longitude", d["longitude"])])),
362+
("SpaceHeatingLoad", Dict([("doe_reference_name", d["doe_reference_name"])])),
363+
("CoolingLoad", Dict([("doe_reference_name", d["doe_reference_name"])])),
364+
("GHP", Dict())])
365+
error_response = Dict()
366+
nearest_city = ""
367+
climate_zone = ""
368+
try
369+
for factor in ["space_heating", "cooling"]
370+
nearest_city, climate_zone = reoptjl.assign_thermal_factor!(data, factor)
371+
end
372+
catch e
373+
@error "Something went wrong in the ghp_efficiency_thermal_factors" exception=(e, catch_backtrace())
374+
error_response["error"] = sprint(showerror, e)
375+
end
376+
if isempty(error_response)
377+
@info "ghp_efficiency_thermal_factors determined."
378+
response = Dict([("doe_reference_name", d["doe_reference_name"]),
379+
("nearest_city", nearest_city),
380+
("climate_zone", climate_zone),
381+
data["GHP"]...])
382+
return HTTP.Response(200, JSON.json(response))
383+
else
384+
@info "An error occured in the ghp_efficiency_thermal_factors endpoint"
385+
return HTTP.Response(500, JSON.json(error_response))
386+
end
387+
end
388+
389+
function ground_conductivity(req::HTTP.Request)
390+
d = JSON.parse(String(req.body))
391+
392+
@info "Getting ground_conductivity..."
393+
error_response = Dict()
394+
nearest_city = ""
395+
climate_zone = ""
396+
ground_thermal_conductivity = 0.01
397+
try
398+
nearest_city, climate_zone = reoptjl.find_ashrae_zone_city(d["latitude"], d["longitude"], get_zone=true)
399+
ground_thermal_conductivity = GhpGhx.ground_k_by_climate_zone[climate_zone]
400+
catch e
401+
@error "Something went wrong in the ground_conductivity" exception=(e, catch_backtrace())
402+
error_response["error"] = sprint(showerror, e)
403+
end
404+
if isempty(error_response)
405+
@info "ground_conductivity determined."
406+
response = Dict([("climate_zone", climate_zone),
407+
("nearest_city", nearest_city),
408+
("thermal_conductivity", ground_thermal_conductivity)])
409+
return HTTP.Response(200, JSON.json(response))
410+
else
411+
@info "An error occured in the ground_conductivity endpoint"
412+
return HTTP.Response(500, JSON.json(error_response))
413+
end
414+
end
415+
346416
function health(req::HTTP.Request)
347417
return HTTP.Response(200, JSON.json(Dict("Julia-api"=>"healthy!")))
348418
end
@@ -359,5 +429,7 @@ HTTP.@register(ROUTER, "GET", "/emissions_profile", emissions_profile)
359429
HTTP.@register(ROUTER, "GET", "/easiur_costs", easiur_costs)
360430
HTTP.@register(ROUTER, "GET", "/simulated_load", simulated_load)
361431
HTTP.@register(ROUTER, "GET", "/absorption_chiller_defaults", absorption_chiller_defaults)
432+
HTTP.@register(ROUTER, "GET", "/ghp_efficiency_thermal_factors", ghp_efficiency_thermal_factors)
433+
HTTP.@register(ROUTER, "GET", "/ground_conductivity", ground_conductivity)
362434
HTTP.@register(ROUTER, "GET", "/health", health)
363435
HTTP.serve(ROUTER, "0.0.0.0", 8081, reuseaddr=true)

reo/scenario.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -409,9 +409,12 @@ def setup_pv(pv_dict, latitude, longitude, time_steps_per_hour):
409409
client = TestApiClient()
410410
# Update ground thermal conductivity based on climate zone if not user-input
411411
if not ghpghx_post.get("ground_thermal_conductivity_btu_per_hr_ft_f"):
412-
k_by_zone = copy.deepcopy(GHPGHXInputs.ground_k_by_climate_zone)
413-
climate_zone, nearest_city, geometric_flag = get_climate_zone_and_nearest_city(ghpghx_post["latitude"], ghpghx_post["longitude"], BuiltInProfile.default_cities)
414-
ghpghx_post["ground_thermal_conductivity_btu_per_hr_ft_f"] = k_by_zone[climate_zone]
412+
ground_k_inputs = {"latitude": ghpghx_post["latitude"],
413+
"longitude": ghpghx_post["longitude"]}
414+
# Call to the django view endpoint /ghp_efficiency_thermal_factors which calls the http.jl endpoint
415+
ground_k_resp = client.get(f'/dev/ghpghx/ground_conductivity', data=ground_k_inputs)
416+
ground_k_response = json.loads(ground_k_resp.content)
417+
ghpghx_post["ground_thermal_conductivity_btu_per_hr_ft_f"] = ground_k_response["thermal_conductivity"]
415418

416419
# Hybrid
417420
# Determine if location is heating or cooling dominated

reopt_api/urls.py

+2
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
dev_api = Api(api_name='dev')
5959
dev_api.register(DevJob())
6060
dev_api.register(FutureCostsAPI())
61+
dev_api.register(GHPGHXJob())
6162
dev_api.register(ERPJob())
6263

6364

@@ -102,6 +103,7 @@ def page_not_found(request, url):
102103
path('dev/', include('reoptjl.urls')),
103104
path('dev/', include('resilience_stats.urls_v3plus')),
104105
path('dev/', include('futurecosts.urls')),
106+
path('dev/', include('ghpghx.urls')),
105107
re_path(r'', include(dev_api.urls)),
106108

107109
re_path(r'(.*)', page_not_found, name='404'),

reoptjl/api.py

+16-1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@
4242
# from reo.src.profiler import Profiler # TODO use Profiler?
4343
from reoptjl.src.run_jump_model import run_jump_model
4444
from reo.exceptions import UnexpectedError, REoptError
45+
from ghpghx.models import GHPGHXInputs
46+
from django.core.exceptions import ValidationError
4547
from reoptjl.models import APIMeta
4648
log = logging.getLogger(__name__)
4749

@@ -147,9 +149,22 @@ def obj_create(self, bundle, **kwargs):
147149
if test_case.startswith('check_http/'):
148150
bundle.data['APIMeta']['job_type'] = 'Monitoring'
149151

152+
# Validate ghpghx_inputs, if applicable
153+
ghpghx_inputs_validation_errors = []
154+
if bundle.data.get("GHP") is not None and \
155+
bundle.data["GHP"].get("ghpghx_inputs") not in [None, []] and \
156+
bundle.data["GHP"].get("ghpghx_response_uuids") in [None, []]:
157+
for ghpghx_inputs in bundle.data["GHP"]["ghpghx_inputs"]:
158+
ghpghxM = GHPGHXInputs(**ghpghx_inputs)
159+
try:
160+
# Validate individual model fields
161+
ghpghxM.clean_fields()
162+
except ValidationError as ve:
163+
ghpghx_inputs_validation_errors += [key + ": " + val[i] + " " for key, val in ve.message_dict.items() for i in range(len(val))]
164+
150165
# Validate inputs
151166
try:
152-
input_validator = InputValidator(bundle.data)
167+
input_validator = InputValidator(bundle.data, ghpghx_inputs_validation_errors=ghpghx_inputs_validation_errors)
153168
input_validator.clean_fields() # step 1 check field values
154169
if not input_validator.is_valid:
155170
return400(meta, input_validator)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Generated by Django 4.0.7 on 2023-02-09 02:19
2+
3+
import django.contrib.postgres.fields
4+
import django.core.validators
5+
from django.db import migrations, models
6+
import django.db.models.deletion
7+
import reoptjl.models
8+
9+
10+
class Migration(migrations.Migration):
11+
12+
dependencies = [
13+
('reoptjl', '0021_merge_20230112_1748'),
14+
]
15+
16+
operations = [
17+
migrations.CreateModel(
18+
name='GHPInputs',
19+
fields=[
20+
('meta', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='GHPInputs', serialize=False, to='reoptjl.apimeta')),
21+
('require_ghp_purchase', models.BooleanField(blank=True, default=False, help_text='Force one of the considered GHP design options.', null=True)),
22+
('installed_cost_heatpump_per_ton', models.FloatField(blank=True, default=1075.0, help_text='Installed heating heat pump cost in $/ton (based on peak coincident cooling+heating thermal load)', null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100000.0)])),
23+
('heatpump_capacity_sizing_factor_on_peak_load', models.FloatField(blank=True, default=1.1, help_text='Factor on peak heating and cooling load served by GHP used for determining GHP installed capacity', null=True, validators=[django.core.validators.MinValueValidator(1.0), django.core.validators.MaxValueValidator(5.0)])),
24+
('installed_cost_ghx_per_ft', models.FloatField(blank=True, default=14.0, help_text='Installed cost of the ground heat exchanger (GHX) in $/ft of vertical piping', null=True, validators=[django.core.validators.MinValueValidator(0.0), django.core.validators.MaxValueValidator(100.0)])),
25+
('installed_cost_building_hydronic_loop_per_sqft', models.FloatField(blank=True, default=1.7, help_text='Installed cost of the building hydronic loop per floor space of the site', null=True, validators=[django.core.validators.MinValueValidator(0.0), django.core.validators.MaxValueValidator(100.0)])),
26+
('om_cost_per_sqft_year', models.FloatField(blank=True, default=-0.51, help_text='Annual GHP incremental operations and maintenance costs in $/ft^2-building/year', null=True, validators=[django.core.validators.MinValueValidator(-100.0), django.core.validators.MaxValueValidator(100.0)])),
27+
('building_sqft', models.FloatField(help_text='Building square footage for GHP/HVAC cost calculations', validators=[django.core.validators.MinValueValidator(0.0), django.core.validators.MaxValueValidator(100000000.0)])),
28+
('space_heating_efficiency_thermal_factor', models.FloatField(blank=True, help_text='Heating efficiency factor (annual average) to account for reduced space heating thermal load from GHP retrofit (e.g. reduced reheat)', null=True, validators=[django.core.validators.MinValueValidator(0.001), django.core.validators.MaxValueValidator(1.0)])),
29+
('cooling_efficiency_thermal_factor', models.FloatField(blank=True, help_text='Cooling efficiency factor (annual average) to account for reduced cooling thermal load from GHP retrofit (e.g. reduced reheat)', null=True, validators=[django.core.validators.MinValueValidator(0.001), django.core.validators.MaxValueValidator(1.0)])),
30+
('ghpghx_inputs', django.contrib.postgres.fields.ArrayField(base_field=models.JSONField(null=True), blank=True, default=list, help_text='GhpGhx.jl inputs/POST to ghpghx app', null=True, size=None)),
31+
('ghpghx_response_uuids', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(blank=True), blank=True, default=list, help_text="List of ghp_uuid's (like run_uuid for REopt) from ghpghx app, used to get GhpGhx.jl run data", null=True, size=None)),
32+
('ghpghx_responses', django.contrib.postgres.fields.ArrayField(base_field=models.JSONField(null=True), blank=True, default=list, help_text='ghpghx app response(s) to re-use a previous GhpGhx.jl run without relying on a database entry', null=True, size=None)),
33+
('can_serve_dhw', models.BooleanField(blank=True, default=False, help_text='If GHP can serve the domestic hot water (DHW) portion of the heating load', null=True)),
34+
('macrs_option_years', models.IntegerField(blank=True, choices=[(0, 'Zero'), (5, 'Five'), (7, 'Seven')], default=5, help_text='Duration over which accelerated depreciation will occur. Set to zero to disable')),
35+
('macrs_bonus_fraction', models.FloatField(blank=True, default=0.8, 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)])),
36+
('macrs_itc_reduction', models.FloatField(blank=True, default=0.5, help_text='Percent of the ITC value by which depreciable basis is reduced', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1)])),
37+
('federal_itc_fraction', models.FloatField(blank=True, default=0.3, help_text='Percentage of capital costs that are credited towards federal taxes', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1)])),
38+
('state_ibi_fraction', models.FloatField(blank=True, default=0, help_text='Percentage of capital costs offset by state incentives', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1)])),
39+
('state_ibi_max', models.FloatField(blank=True, default=10000000000.0, help_text='Maximum dollar value of state percentage-based capital cost incentive', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10000000000.0)])),
40+
('utility_ibi_fraction', models.FloatField(blank=True, default=0, help_text='Percentage of capital costs offset by utility incentives', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1)])),
41+
('utility_ibi_max', models.FloatField(blank=True, default=10000000000.0, help_text='Maximum dollar value of utility percentage-based capital cost incentive', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10000000000.0)])),
42+
('federal_rebate_per_ton', models.FloatField(blank=True, default=0, help_text='Federal rebates based on installed capacity of heat pumps', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000000000.0)])),
43+
('state_rebate_per_ton', models.FloatField(blank=True, default=0, help_text='State rebate based on installed capacity of heat pumps', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000000000.0)])),
44+
('state_rebate_max', models.FloatField(blank=True, default=10000000000.0, help_text='Maximum state rebate', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10000000000.0)])),
45+
('utility_rebate_per_ton', models.FloatField(blank=True, default=0, help_text='Utility rebate based on installed capacity of heat pumps', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000000000.0)])),
46+
('utility_rebate_max', models.FloatField(blank=True, default=10000000000.0, help_text='Maximum utility rebate', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10000000000.0)])),
47+
],
48+
bases=(reoptjl.models.BaseModel, models.Model),
49+
),
50+
migrations.CreateModel(
51+
name='GHPOutputs',
52+
fields=[
53+
('meta', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='GHPOutputs', serialize=False, to='reoptjl.apimeta')),
54+
('ghp_option_chosen', models.IntegerField(blank=True, null=True)),
55+
('ghpghx_chosen_outputs', models.JSONField(null=True)),
56+
('size_heat_pump_ton', models.FloatField(blank=True, null=True)),
57+
('space_heating_thermal_load_reduction_with_ghp_mmbtu_per_hour', django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), blank=True, default=list, null=True, size=None)),
58+
('cooling_thermal_load_reduction_with_ghp_ton', django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), blank=True, default=list, null=True, size=None)),
59+
],
60+
bases=(reoptjl.models.BaseModel, models.Model),
61+
),
62+
]

0 commit comments

Comments
 (0)