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 |
})
|