Spaces:
Sleeping
Sleeping
Update app/main.py
Browse files- app/main.py +22 -33
app/main.py
CHANGED
@@ -3,7 +3,8 @@ HVAC Calculator Code Documentation.
|
|
3 |
Updated 2025-05-02: Integrated skylights, surface color, glazing type, frame type, and drapery adjustments from main_new.py.
|
4 |
Updated 2025-05-02: Enhanced per Plan.txt to include winter design temperature, humidity, building height, ventilation rate, internal load enhancements, and calculation parameters.
|
5 |
Updated 2025-05-09: Fixed latitude parsing to return string (e.g., "24N") to match ASHRAE table keys and added group validation.
|
6 |
-
Updated 2025-05-09: Corrected group validation to use alphabetical groups (A
|
|
|
7 |
"""
|
8 |
|
9 |
import streamlit as st
|
@@ -54,8 +55,8 @@ VENTILATION_RATES = {
|
|
54 |
"Custom": {"people_rate": 0.0, "area_rate": 0.0}
|
55 |
}
|
56 |
|
57 |
-
# Valid wall and roof groups for ASHRAE CLTD tables (
|
58 |
-
VALID_WALL_GROUPS = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'
|
59 |
VALID_ROOF_GROUPS = ['A', 'B', 'C', 'D', 'E', 'F', 'G']
|
60 |
|
61 |
class HVACCalculator:
|
@@ -231,7 +232,7 @@ class HVACCalculator:
|
|
231 |
return False, f"Ground temperature for {comp.name} must be between -10°C and 40°C"
|
232 |
if getattr(comp, 'perimeter', 0) < 0:
|
233 |
return False, f"Perimeter for {comp.name} cannot be negative"
|
234 |
-
#
|
235 |
if component_type in ['walls', 'roofs']:
|
236 |
if not 0.1 <= getattr(comp, 'solar_absorptivity', 0.6) <= 1.0:
|
237 |
return False, f"Invalid solar absorptivity for {component_type}: {comp.name} (must be 0.1-1.0)"
|
@@ -243,7 +244,7 @@ class HVACCalculator:
|
|
243 |
return False, f"Glazing type missing for {component_type}: {comp.name}"
|
244 |
if getattr(comp, 'frame_type', None) is None:
|
245 |
return False, f"Frame type missing for {component_type}: {comp.name}"
|
246 |
-
#
|
247 |
if component_type == 'walls':
|
248 |
if getattr(comp, 'wall_group', '') not in VALID_WALL_GROUPS:
|
249 |
return False, f"Invalid wall group '{comp.wall_group}' for {comp.name}. Valid groups: {', '.join(VALID_WALL_GROUPS)}"
|
@@ -257,7 +258,7 @@ class HVACCalculator:
|
|
257 |
if building_info.get('zone_type', '') == 'Custom' and building_info.get('ventilation_rate', 0) == 0:
|
258 |
return False, "Custom ventilation rate must be specified"
|
259 |
|
260 |
-
#
|
261 |
if not -50 <= building_info.get('winter_temp', -10) <= 20:
|
262 |
return False, "Winter design temperature must be -50 to 20°C"
|
263 |
if not 0 <= building_info.get('outdoor_rh', 50) <= 100:
|
@@ -307,26 +308,11 @@ class HVACCalculator:
|
|
307 |
def parse_latitude(self, latitude: Any) -> str:
|
308 |
"""Parse latitude from string or number to ASHRAE table format (e.g., '24N')."""
|
309 |
try:
|
310 |
-
|
311 |
-
|
312 |
-
|
313 |
-
lat_str = latitude.strip().upper().replace('N', '').replace('S', '')
|
314 |
-
lat_value = float(lat_str)
|
315 |
-
else:
|
316 |
-
raise ValueError("Invalid latitude format")
|
317 |
-
|
318 |
-
# Convert to ASHRAE table format (e.g., '24N', '32N')
|
319 |
-
supported_latitudes = [24, 32, 40, 48]
|
320 |
-
if lat_value in supported_latitudes:
|
321 |
-
return f"{int(lat_value)}N"
|
322 |
-
else:
|
323 |
-
closest_lat = min(supported_latitudes, key=lambda x: abs(x - lat_value))
|
324 |
-
st.warning(f"Latitude {lat_value} not in ASHRAE tables. Using closest: {closest_lat}N")
|
325 |
-
return f"{closest_lat}N"
|
326 |
-
|
327 |
-
except (ValueError, AttributeError) as e:
|
328 |
st.error(f"Invalid latitude: {latitude}. Using default 32N.")
|
329 |
-
return "32N"
|
330 |
|
331 |
def display_internal_loads(self):
|
332 |
st.title("Internal Loads")
|
@@ -664,13 +650,16 @@ class HVACCalculator:
|
|
664 |
"Jul": 196, "Aug": 227, "Sep": 258, "Oct": 288, "Nov": 319, "Dec": 350
|
665 |
}
|
666 |
|
|
|
|
|
|
|
667 |
# Format conditions
|
668 |
outdoor_conditions = {
|
669 |
'temperature': location['summer_design_temp_db'],
|
670 |
'relative_humidity': building_info.get('outdoor_rh', location['monthly_humidity'].get('Jul', 50.0)),
|
671 |
'ground_temperature': location['monthly_temps'].get('Jul', 20.0),
|
672 |
'month': 'Jul',
|
673 |
-
'latitude':
|
674 |
'wind_speed': building_info.get('wind_speed', 4.0),
|
675 |
'day_of_year': month_to_day.get('Jul', 182)
|
676 |
}
|
@@ -821,7 +810,7 @@ class HVACCalculator:
|
|
821 |
'group': wall.wall_group,
|
822 |
'orientation': wall.orientation.value,
|
823 |
'hour': design_loads['design_hour'],
|
824 |
-
'latitude':
|
825 |
'solar_absorptivity': wall.solar_absorptivity
|
826 |
})
|
827 |
results['detailed_loads']['walls'].append({
|
@@ -835,7 +824,7 @@ class HVACCalculator:
|
|
835 |
group=wall.wall_group,
|
836 |
orientation=wall.orientation.value,
|
837 |
hour=design_loads['design_hour'],
|
838 |
-
latitude=
|
839 |
solar_absorptivity=wall.solar_absorptivity
|
840 |
),
|
841 |
'load': load / 1000
|
@@ -862,7 +851,7 @@ class HVACCalculator:
|
|
862 |
'group': roof.roof_group,
|
863 |
'orientation': roof.orientation.value,
|
864 |
'hour': design_loads['design_hour'],
|
865 |
-
'latitude':
|
866 |
'solar_absorptivity': roof.solar_absorptivity
|
867 |
})
|
868 |
results['detailed_loads']['roofs'].append({
|
@@ -876,7 +865,7 @@ class HVACCalculator:
|
|
876 |
group=roof.roof_group,
|
877 |
orientation=roof.orientation.value,
|
878 |
hour=design_loads['design_hour'],
|
879 |
-
latitude=
|
880 |
solar_absorptivity=roof.solar_absorptivity
|
881 |
),
|
882 |
'load': load / 1000
|
@@ -922,7 +911,7 @@ class HVACCalculator:
|
|
922 |
'shading_device': window.shading_device,
|
923 |
'shading_coefficient': window.shading_coefficient,
|
924 |
'scl': self.cooling_calculator.ashrae_tables.get_scl(
|
925 |
-
latitude=
|
926 |
month=outdoor_conditions['month'].lower(),
|
927 |
orientation=window.orientation.value,
|
928 |
hour=design_loads['design_hour']
|
@@ -980,7 +969,7 @@ class HVACCalculator:
|
|
980 |
'drapery_type': skylight.drapery_type if hasattr(skylight, 'drapery_type') else 'None',
|
981 |
'shading_coefficient': skylight.shading_coefficient,
|
982 |
'scl': self.cooling_calculator.ashrae_tables.get_scl(
|
983 |
-
latitude=
|
984 |
month=outdoor_conditions['month'].lower(),
|
985 |
orientation='Horizontal',
|
986 |
hour=design_loads['design_hour']
|
@@ -1292,7 +1281,7 @@ class HVACCalculator:
|
|
1292 |
results['detailed_loads']['floors'].append({
|
1293 |
'name': floor.name,
|
1294 |
'area': floor.area,
|
1295 |
-
'u_value': floor.u_value,
|
1296 |
'delta_t': indoor_conditions['temperature'] - outdoor_conditions['ground_temperature'],
|
1297 |
'load': load / 1000
|
1298 |
})
|
|
|
3 |
Updated 2025-05-02: Integrated skylights, surface color, glazing type, frame type, and drapery adjustments from main_new.py.
|
4 |
Updated 2025-05-02: Enhanced per Plan.txt to include winter design temperature, humidity, building height, ventilation rate, internal load enhancements, and calculation parameters.
|
5 |
Updated 2025-05-09: Fixed latitude parsing to return string (e.g., "24N") to match ASHRAE table keys and added group validation.
|
6 |
+
Updated 2025-05-09: Corrected group validation to use alphabetical groups (A-H for walls, A-G for roofs) and enhanced stale component handling.
|
7 |
+
Updated 2025-05-10: Aligned latitude parsing with cooling_load.py's validate_latitude and updated wall groups to A-H to match cooling_load.py.
|
8 |
"""
|
9 |
|
10 |
import streamlit as st
|
|
|
55 |
"Custom": {"people_rate": 0.0, "area_rate": 0.0}
|
56 |
}
|
57 |
|
58 |
+
# Valid wall and roof groups for ASHRAE CLTD tables (aligned with cooling_load.py)
|
59 |
+
VALID_WALL_GROUPS = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']
|
60 |
VALID_ROOF_GROUPS = ['A', 'B', 'C', 'D', 'E', 'F', 'G']
|
61 |
|
62 |
class HVACCalculator:
|
|
|
232 |
return False, f"Ground temperature for {comp.name} must be between -10°C and 40°C"
|
233 |
if getattr(comp, 'perimeter', 0) < 0:
|
234 |
return False, f"Perimeter for {comp.name} cannot be negative"
|
235 |
+
# Validate solar absorptivity for walls and roofs
|
236 |
if component_type in ['walls', 'roofs']:
|
237 |
if not 0.1 <= getattr(comp, 'solar_absorptivity', 0.6) <= 1.0:
|
238 |
return False, f"Invalid solar absorptivity for {component_type}: {comp.name} (must be 0.1-1.0)"
|
|
|
244 |
return False, f"Glazing type missing for {component_type}: {comp.name}"
|
245 |
if getattr(comp, 'frame_type', None) is None:
|
246 |
return False, f"Frame type missing for {component_type}: {comp.name}"
|
247 |
+
# Validate wall and roof groups
|
248 |
if component_type == 'walls':
|
249 |
if getattr(comp, 'wall_group', '') not in VALID_WALL_GROUPS:
|
250 |
return False, f"Invalid wall group '{comp.wall_group}' for {comp.name}. Valid groups: {', '.join(VALID_WALL_GROUPS)}"
|
|
|
258 |
if building_info.get('zone_type', '') == 'Custom' and building_info.get('ventilation_rate', 0) == 0:
|
259 |
return False, "Custom ventilation rate must be specified"
|
260 |
|
261 |
+
# Validate new inputs from Plan.txt
|
262 |
if not -50 <= building_info.get('winter_temp', -10) <= 20:
|
263 |
return False, "Winter design temperature must be -50 to 20°C"
|
264 |
if not 0 <= building_info.get('outdoor_rh', 50) <= 100:
|
|
|
308 |
def parse_latitude(self, latitude: Any) -> str:
|
309 |
"""Parse latitude from string or number to ASHRAE table format (e.g., '24N')."""
|
310 |
try:
|
311 |
+
# Use cooling_calculator's validate_latitude for consistency
|
312 |
+
return self.cooling_calculator.validate_latitude(latitude)
|
313 |
+
except Exception as e:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
314 |
st.error(f"Invalid latitude: {latitude}. Using default 32N.")
|
315 |
+
return "32N"
|
316 |
|
317 |
def display_internal_loads(self):
|
318 |
st.title("Internal Loads")
|
|
|
650 |
"Jul": 196, "Aug": 227, "Sep": 258, "Oct": 288, "Nov": 319, "Dec": 350
|
651 |
}
|
652 |
|
653 |
+
# Validate latitude using cooling_calculator
|
654 |
+
latitude = self.cooling_calculator.validate_latitude(location['latitude'])
|
655 |
+
|
656 |
# Format conditions
|
657 |
outdoor_conditions = {
|
658 |
'temperature': location['summer_design_temp_db'],
|
659 |
'relative_humidity': building_info.get('outdoor_rh', location['monthly_humidity'].get('Jul', 50.0)),
|
660 |
'ground_temperature': location['monthly_temps'].get('Jul', 20.0),
|
661 |
'month': 'Jul',
|
662 |
+
'latitude': latitude,
|
663 |
'wind_speed': building_info.get('wind_speed', 4.0),
|
664 |
'day_of_year': month_to_day.get('Jul', 182)
|
665 |
}
|
|
|
810 |
'group': wall.wall_group,
|
811 |
'orientation': wall.orientation.value,
|
812 |
'hour': design_loads['design_hour'],
|
813 |
+
'latitude': outdoor_conditions['latitude'],
|
814 |
'solar_absorptivity': wall.solar_absorptivity
|
815 |
})
|
816 |
results['detailed_loads']['walls'].append({
|
|
|
824 |
group=wall.wall_group,
|
825 |
orientation=wall.orientation.value,
|
826 |
hour=design_loads['design_hour'],
|
827 |
+
latitude=float(outdoor_conditions['latitude'].replace('N', '')),
|
828 |
solar_absorptivity=wall.solar_absorptivity
|
829 |
),
|
830 |
'load': load / 1000
|
|
|
851 |
'group': roof.roof_group,
|
852 |
'orientation': roof.orientation.value,
|
853 |
'hour': design_loads['design_hour'],
|
854 |
+
'latitude': outdoor_conditions['latitude'],
|
855 |
'solar_absorptivity': roof.solar_absorptivity
|
856 |
})
|
857 |
results['detailed_loads']['roofs'].append({
|
|
|
865 |
group=roof.roof_group,
|
866 |
orientation=roof.orientation.value,
|
867 |
hour=design_loads['design_hour'],
|
868 |
+
latitude=float(outdoor_conditions['latitude'].replace('N', '')),
|
869 |
solar_absorptivity=roof.solar_absorptivity
|
870 |
),
|
871 |
'load': load / 1000
|
|
|
911 |
'shading_device': window.shading_device,
|
912 |
'shading_coefficient': window.shading_coefficient,
|
913 |
'scl': self.cooling_calculator.ashrae_tables.get_scl(
|
914 |
+
latitude=outdoor_conditions['latitude'],
|
915 |
month=outdoor_conditions['month'].lower(),
|
916 |
orientation=window.orientation.value,
|
917 |
hour=design_loads['design_hour']
|
|
|
969 |
'drapery_type': skylight.drapery_type if hasattr(skylight, 'drapery_type') else 'None',
|
970 |
'shading_coefficient': skylight.shading_coefficient,
|
971 |
'scl': self.cooling_calculator.ashrae_tables.get_scl(
|
972 |
+
latitude=outdoor_conditions['latitude'],
|
973 |
month=outdoor_conditions['month'].lower(),
|
974 |
orientation='Horizontal',
|
975 |
hour=design_loads['design_hour']
|
|
|
1281 |
results['detailed_loads']['floors'].append({
|
1282 |
'name': floor.name,
|
1283 |
'area': floor.area,
|
1284 |
+
'u_value': floor.u_value,
|
1285 |
'delta_t': indoor_conditions['temperature'] - outdoor_conditions['ground_temperature'],
|
1286 |
'load': load / 1000
|
1287 |
})
|