Spaces:
Sleeping
Sleeping
Update app/main.py
Browse files- app/main.py +89 -93
app/main.py
CHANGED
|
@@ -17,7 +17,7 @@ import uuid
|
|
| 17 |
|
| 18 |
# Import application modules
|
| 19 |
from app.building_info_form import BuildingInfoForm
|
| 20 |
-
from app.component_selection import ComponentSelectionInterface, Orientation, ComponentType, Wall, Roof, Floor, Window, Door, Skylight, GlazingType, FrameType
|
| 21 |
from app.results_display import ResultsDisplay
|
| 22 |
from app.data_validation import DataValidation
|
| 23 |
from app.data_persistence import DataPersistence
|
|
@@ -41,7 +41,7 @@ from utils.component_visualization import ComponentVisualization
|
|
| 41 |
from utils.scenario_comparison import ScenarioComparisonVisualization
|
| 42 |
from utils.psychrometric_visualization import PsychrometricVisualization
|
| 43 |
from utils.time_based_visualization import TimeBasedVisualization
|
| 44 |
-
from data.drapery import Drapery
|
| 45 |
|
| 46 |
# NEW: ASHRAE 62.1 Ventilation Rates (Table 6.1)
|
| 47 |
VENTILATION_RATES = {
|
|
@@ -75,7 +75,7 @@ class HVACCalculator:
|
|
| 75 |
'floors': [],
|
| 76 |
'windows': [],
|
| 77 |
'doors': [],
|
| 78 |
-
'skylights': []
|
| 79 |
}
|
| 80 |
|
| 81 |
if 'internal_loads' not in st.session_state:
|
|
@@ -109,7 +109,7 @@ class HVACCalculator:
|
|
| 109 |
self.data_export = DataExport()
|
| 110 |
self.cooling_calculator = CoolingLoadCalculator()
|
| 111 |
self.heating_calculator = HeatingLoadCalculator()
|
| 112 |
-
self.drapery = Drapery()
|
| 113 |
|
| 114 |
# Persist ClimateData in session_state
|
| 115 |
if 'climate_data_obj' not in st.session_state:
|
|
@@ -259,7 +259,7 @@ class HVACCalculator:
|
|
| 259 |
existing_load['activity_level'] == new_load['activity_level'] and
|
| 260 |
existing_load['zone_type'] == new_load['zone_type'] and
|
| 261 |
existing_load['hours_in_operation'] == new_load['hours_in_operation'] and
|
| 262 |
-
existing_load['latent_gain'] == new_load['latent_gain']):
|
| 263 |
return False, f"Duplicate people load '{new_load['name']}' already exists."
|
| 264 |
elif load_type == 'lighting':
|
| 265 |
if (existing_load['name'] == new_load['name'] and
|
|
@@ -306,15 +306,15 @@ class HVACCalculator:
|
|
| 306 |
)
|
| 307 |
zone_type = st.selectbox(
|
| 308 |
"Zone Type",
|
| 309 |
-
["A", "B", "C", "D"],
|
| 310 |
help="Select zone type for CLF accuracy per ASHRAE"
|
| 311 |
)
|
| 312 |
hours_in_operation = st.selectbox(
|
| 313 |
-
"Hours Occupied",
|
| 314 |
-
["2h", "4h", "6h"],
|
| 315 |
help="Select hours of occupancy for CLF calculations"
|
| 316 |
)
|
| 317 |
-
latent_gain = st.number_input(
|
| 318 |
"Latent Gain per Person (Btu/h)",
|
| 319 |
min_value=0.0,
|
| 320 |
max_value=500.0,
|
|
@@ -332,7 +332,7 @@ class HVACCalculator:
|
|
| 332 |
"activity_level": activity_level,
|
| 333 |
"zone_type": zone_type,
|
| 334 |
"hours_in_operation": hours_in_operation,
|
| 335 |
-
"latent_gain": latent_gain
|
| 336 |
}
|
| 337 |
is_valid, message = self.validate_internal_load('people', people_load)
|
| 338 |
if is_valid:
|
|
@@ -378,12 +378,12 @@ class HVACCalculator:
|
|
| 378 |
)
|
| 379 |
zone_type = st.selectbox(
|
| 380 |
"Zone Type",
|
| 381 |
-
["A", "B", "C", "D"],
|
| 382 |
help="Select zone type for CLF accuracy per ASHRAE"
|
| 383 |
)
|
| 384 |
hours_in_operation = st.selectbox(
|
| 385 |
-
"Hours On",
|
| 386 |
-
["8h", "10h", "12h"],
|
| 387 |
help="Select hours of lighting operation for CLF calculations"
|
| 388 |
)
|
| 389 |
lighting_name = st.text_input("Name", value="General Lighting")
|
|
@@ -449,12 +449,12 @@ class HVACCalculator:
|
|
| 449 |
)
|
| 450 |
zone_type = st.selectbox(
|
| 451 |
"Zone Type",
|
| 452 |
-
["A", "B", "C", "D"],
|
| 453 |
help="Select zone type for CLF accuracy per ASHRAE"
|
| 454 |
)
|
| 455 |
hours_in_operation = st.selectbox(
|
| 456 |
-
"Hours Operated",
|
| 457 |
-
["2h", "4h", "6h"],
|
| 458 |
help="Select hours of equipment operation for CLF calculations"
|
| 459 |
)
|
| 460 |
equipment_name = st.text_input("Name", value="Office Equipment")
|
|
@@ -524,7 +524,7 @@ class HVACCalculator:
|
|
| 524 |
step=0.1,
|
| 525 |
help="Custom ventilation rate per floor area (ASHRAE 62.1)"
|
| 526 |
)
|
| 527 |
-
ventilation_rate = st.number_input(
|
| 528 |
"Ventilation Rate (m³/s)",
|
| 529 |
min_value=0.0,
|
| 530 |
max_value=10.0,
|
|
@@ -537,7 +537,7 @@ class HVACCalculator:
|
|
| 537 |
area_rate = VENTILATION_RATES[zone_type]["area_rate"]
|
| 538 |
st.write(f"People Rate: {people_rate} L/s/person (ASHRAE 62.1)")
|
| 539 |
st.write(f"Area Rate: {area_rate} L/s/m² (ASHRAE 62.1)")
|
| 540 |
-
ventilation_rate = st.number_input(
|
| 541 |
"Ventilation Rate (m³/s)",
|
| 542 |
min_value=0.0,
|
| 543 |
max_value=10.0,
|
|
@@ -618,16 +618,16 @@ class HVACCalculator:
|
|
| 618 |
# Format conditions
|
| 619 |
outdoor_conditions = {
|
| 620 |
'temperature': location['summer_design_temp_db'],
|
| 621 |
-
'relative_humidity': building_info.get('outdoor_rh', location['monthly_humidity'].get('Jul', 50.0)),
|
| 622 |
'ground_temperature': location['monthly_temps'].get('Jul', 20.0),
|
| 623 |
'month': 'Jul',
|
| 624 |
'latitude': location['latitude'],
|
| 625 |
-
'wind_speed': building_info.get('wind_speed', 4.0),
|
| 626 |
-
'day_of_year': month_to_day.get('Jul', 182)
|
| 627 |
}
|
| 628 |
indoor_conditions = {
|
| 629 |
'temperature': building_info.get('indoor_temp', 24.0),
|
| 630 |
-
'relative_humidity': building_info.get('indoor_rh', 50.0)
|
| 631 |
}
|
| 632 |
|
| 633 |
if st.session_state.get('debug_mode', False):
|
|
@@ -649,31 +649,31 @@ class HVACCalculator:
|
|
| 649 |
'people': {
|
| 650 |
'number': sum(load['num_people'] for load in internal_loads.get('people', [])),
|
| 651 |
'activity_level': internal_loads.get('people', [{}])[0].get('activity_level', 'Seated/Resting'),
|
| 652 |
-
'operating_hours': internal_loads.get('people', [{}])[0].get('hours_in_operation', '8h'),
|
| 653 |
-
'zone_type': internal_loads.get('people', [{}])[0].get('zone_type', 'A'),
|
| 654 |
-
'latent_gain': internal_loads.get('people', [{}])[0].get('latent_gain', 200.0)
|
| 655 |
},
|
| 656 |
'lights': {
|
| 657 |
'power': sum(load['power'] for load in internal_loads.get('lighting', [])),
|
| 658 |
'use_factor': internal_loads.get('lighting', [{}])[0].get('usage_factor', 0.8),
|
| 659 |
'special_allowance': 0.1,
|
| 660 |
-
'hours_operation': internal_loads.get('lighting', [{}])[0].get('hours_in_operation', '8h'),
|
| 661 |
-
'zone_type': internal_loads.get('lighting', [{}])[0].get('zone_type', 'A')
|
| 662 |
},
|
| 663 |
'equipment': {
|
| 664 |
'power': sum(load['power'] for load in internal_loads.get('equipment', [])),
|
| 665 |
'use_factor': internal_loads.get('equipment', [{}])[0].get('usage_factor', 0.7),
|
| 666 |
'radiation_factor': internal_loads.get('equipment', [{}])[0].get('radiation_fraction', 0.3),
|
| 667 |
-
'hours_operation': internal_loads.get('equipment', [{}])[0].get('hours_in_operation', '8h'),
|
| 668 |
-
'zone_type': internal_loads.get('equipment', [{}])[0].get('zone_type', 'A')
|
| 669 |
},
|
| 670 |
'infiltration': {
|
| 671 |
-
'flow_rate': building_info.get('infiltration_rate', 0.
|
| 672 |
-
'height': building_info.get('building_height', 3.0),
|
| 673 |
'crack_length': building_info.get('crack_length', 10.0)
|
| 674 |
},
|
| 675 |
'ventilation': {
|
| 676 |
-
'flow_rate': building_info.get('ventilation_rate', 0.1)
|
| 677 |
},
|
| 678 |
'operating_hours': building_info.get('operating_hours', '8:00-18:00')
|
| 679 |
}
|
|
@@ -723,7 +723,7 @@ class HVACCalculator:
|
|
| 723 |
'roof': design_loads['roofs'] / 1000,
|
| 724 |
'windows': (design_loads['windows_conduction'] + design_loads['windows_solar']) / 1000,
|
| 725 |
'doors': design_loads['doors'] / 1000,
|
| 726 |
-
'skylights': design_loads.get('skylights', 0) / 1000,
|
| 727 |
'people': (design_loads['people_sensible'] + design_loads['people_latent']) / 1000,
|
| 728 |
'lighting': design_loads['lights'] / 1000,
|
| 729 |
'equipment': (design_loads['equipment_sensible'] + design_loads['equipment_latent']) / 1000,
|
|
@@ -735,7 +735,7 @@ class HVACCalculator:
|
|
| 735 |
'roofs': [],
|
| 736 |
'windows': [],
|
| 737 |
'doors': [],
|
| 738 |
-
'skylights': [],
|
| 739 |
'internal': [],
|
| 740 |
'infiltration': {
|
| 741 |
'air_flow': formatted_internal_loads['infiltration']['flow_rate'],
|
|
@@ -762,23 +762,22 @@ class HVACCalculator:
|
|
| 762 |
month=outdoor_conditions['month'],
|
| 763 |
hour=design_loads['design_hour'],
|
| 764 |
latitude=outdoor_conditions['latitude'],
|
| 765 |
-
solar_absorptivity=wall.solar_absorptivity
|
| 766 |
)
|
| 767 |
results['detailed_loads']['walls'].append({
|
| 768 |
'name': wall.name,
|
| 769 |
'orientation': wall.orientation.value,
|
| 770 |
'area': wall.area,
|
| 771 |
'u_value': wall.u_value,
|
| 772 |
-
'solar_absorptivity': wall.solar_absorptivity,
|
| 773 |
-
'cltd': self.cooling_calculator.ashrae_tables.
|
| 774 |
-
|
|
|
|
| 775 |
orientation=wall.orientation.value,
|
| 776 |
hour=design_loads['design_hour'],
|
| 777 |
-
|
| 778 |
-
|
| 779 |
-
|
| 780 |
-
indoor_temp=indoor_conditions['temperature'],
|
| 781 |
-
outdoor_temp=outdoor_conditions['temperature']
|
| 782 |
),
|
| 783 |
'load': load / 1000
|
| 784 |
})
|
|
@@ -791,29 +790,28 @@ class HVACCalculator:
|
|
| 791 |
month=outdoor_conditions['month'],
|
| 792 |
hour=design_loads['design_hour'],
|
| 793 |
latitude=outdoor_conditions['latitude'],
|
| 794 |
-
solar_absorptivity=roof.solar_absorptivity
|
| 795 |
)
|
| 796 |
results['detailed_loads']['roofs'].append({
|
| 797 |
'name': roof.name,
|
| 798 |
'orientation': roof.orientation.value,
|
| 799 |
'area': roof.area,
|
| 800 |
'u_value': roof.u_value,
|
| 801 |
-
'solar_absorptivity': roof.solar_absorptivity,
|
| 802 |
-
'cltd': self.cooling_calculator.ashrae_tables.
|
| 803 |
-
|
|
|
|
|
|
|
| 804 |
hour=design_loads['design_hour'],
|
| 805 |
-
|
| 806 |
-
|
| 807 |
-
|
| 808 |
-
indoor_temp=indoor_conditions['temperature'],
|
| 809 |
-
outdoor_temp=outdoor_conditions['temperature']
|
| 810 |
),
|
| 811 |
'load': load / 1000
|
| 812 |
})
|
| 813 |
|
| 814 |
for window in building_components.get('windows', []):
|
| 815 |
-
|
| 816 |
-
adjusted_shgc = self.drapery.adjust_shgc( # CHANGED: Fixed typo (drapery_system to drapery)
|
| 817 |
base_shgc=window.shgc,
|
| 818 |
glazing_type=window.glazing_type,
|
| 819 |
drapery_type=window.drapery_type if hasattr(window, 'drapery_type') else None
|
|
@@ -826,9 +824,9 @@ class HVACCalculator:
|
|
| 826 |
hour=design_loads['design_hour'],
|
| 827 |
latitude=outdoor_conditions['latitude'],
|
| 828 |
shading_coefficient=window.shading_coefficient,
|
| 829 |
-
adjusted_shgc=adjusted_shgc,
|
| 830 |
-
glazing_type=window.glazing_type,
|
| 831 |
-
frame_type=window.frame_type
|
| 832 |
)
|
| 833 |
if 'total' not in load_dict:
|
| 834 |
if 'conduction' in load_dict and 'solar' in load_dict:
|
|
@@ -842,15 +840,15 @@ class HVACCalculator:
|
|
| 842 |
'area': window.area,
|
| 843 |
'u_value': window.u_value,
|
| 844 |
'shgc': window.shgc,
|
| 845 |
-
'adjusted_shgc': adjusted_shgc,
|
| 846 |
-
'glazing_type': window.glazing_type,
|
| 847 |
-
'frame_type': window.frame_type,
|
| 848 |
-
'drapery_type': window.drapery_type if hasattr(window, 'drapery_type') else 'None',
|
| 849 |
'shading_device': window.shading_device,
|
| 850 |
'shading_coefficient': window.shading_coefficient,
|
| 851 |
'scl': self.cooling_calculator.ashrae_tables.get_scl(
|
| 852 |
-
latitude=outdoor_conditions['latitude'],
|
| 853 |
-
month=outdoor_conditions['month'].
|
| 854 |
orientation=window.orientation.value,
|
| 855 |
hour=design_loads['design_hour']
|
| 856 |
),
|
|
@@ -872,9 +870,8 @@ class HVACCalculator:
|
|
| 872 |
'load': load / 1000
|
| 873 |
})
|
| 874 |
|
| 875 |
-
# NEW: Skylights
|
| 876 |
for skylight in building_components.get('skylights', []):
|
| 877 |
-
adjusted_shgc = self.drapery.adjust_shgc(
|
| 878 |
base_shgc=skylight.shgc,
|
| 879 |
glazing_type=skylight.glazing_type,
|
| 880 |
drapery_type=skylight.drapery_type if hasattr(skylight, 'drapery_type') else None
|
|
@@ -908,9 +905,9 @@ class HVACCalculator:
|
|
| 908 |
'drapery_type': skylight.drapery_type if hasattr(skylight, 'drapery_type') else 'None',
|
| 909 |
'shading_coefficient': skylight.shading_coefficient,
|
| 910 |
'scl': self.cooling_calculator.ashrae_tables.get_scl(
|
| 911 |
-
latitude=outdoor_conditions['latitude'],
|
| 912 |
-
month=outdoor_conditions['month'].
|
| 913 |
-
orientation='Horizontal',
|
| 914 |
hour=design_loads['design_hour']
|
| 915 |
),
|
| 916 |
'load': load_dict['total'] / 1000
|
|
@@ -923,7 +920,7 @@ class HVACCalculator:
|
|
| 923 |
num_people=load['num_people'],
|
| 924 |
activity_level=load['activity_level'],
|
| 925 |
hour=design_loads['design_hour'],
|
| 926 |
-
latent_gain=load.get('latent_gain', 200.0)
|
| 927 |
)
|
| 928 |
if 'total' not in load_dict and ('sensible' in load_dict or 'latent' in load_dict):
|
| 929 |
load_dict['total'] = load_dict.get('sensible', 0) + load_dict.get('latent', 0)
|
|
@@ -1023,7 +1020,7 @@ class HVACCalculator:
|
|
| 1023 |
|
| 1024 |
# Skip heating calculation if outdoor temp exceeds indoor temp
|
| 1025 |
indoor_temp = building_info.get('indoor_temp', 21.0)
|
| 1026 |
-
outdoor_temp = building_info.get('winter_temp', location['winter_design_temp'])
|
| 1027 |
if outdoor_temp >= indoor_temp:
|
| 1028 |
results = {
|
| 1029 |
'total_load': 0.0,
|
|
@@ -1036,7 +1033,7 @@ class HVACCalculator:
|
|
| 1036 |
'floor': 0.0,
|
| 1037 |
'windows': 0.0,
|
| 1038 |
'doors': 0.0,
|
| 1039 |
-
'skylights': 0.0,
|
| 1040 |
'infiltration': 0.0,
|
| 1041 |
'ventilation': 0.0
|
| 1042 |
},
|
|
@@ -1046,7 +1043,7 @@ class HVACCalculator:
|
|
| 1046 |
'floors': [],
|
| 1047 |
'windows': [],
|
| 1048 |
'doors': [],
|
| 1049 |
-
'skylights': [],
|
| 1050 |
'infiltration': {'air_flow': 0.0, 'delta_t': 0.0, 'load': 0.0},
|
| 1051 |
'ventilation': {'air_flow': 0.0, 'delta_t': 0.0, 'load': 0.0}
|
| 1052 |
},
|
|
@@ -1056,14 +1053,14 @@ class HVACCalculator:
|
|
| 1056 |
|
| 1057 |
# Format conditions
|
| 1058 |
outdoor_conditions = {
|
| 1059 |
-
'design_temperature': outdoor_temp,
|
| 1060 |
-
'design_relative_humidity': building_info.get('outdoor_rh', location['monthly_humidity'].get('Jan', 80.0)),
|
| 1061 |
'ground_temperature': ground_temperature,
|
| 1062 |
-
'wind_speed': building_info.get('wind_speed', 4.0)
|
| 1063 |
}
|
| 1064 |
indoor_conditions = {
|
| 1065 |
'temperature': indoor_temp,
|
| 1066 |
-
'relative_humidity': building_info.get('indoor_rh', 40.0)
|
| 1067 |
}
|
| 1068 |
|
| 1069 |
if st.session_state.get('debug_mode', False):
|
|
@@ -1096,29 +1093,29 @@ class HVACCalculator:
|
|
| 1096 |
internal_loads.get('people', [{}])[0].get('activity_level', 'Seated/Resting'),
|
| 1097 |
70.0
|
| 1098 |
),
|
| 1099 |
-
'latent_gain': internal_loads.get('people', [{}])[0].get('latent_gain', 200.0),
|
| 1100 |
-
'operating_hours': internal_loads.get('people', [{}])[0].get('hours_in_operation', '8h'),
|
| 1101 |
-
'zone_type': internal_loads.get('people', [{}])[0].get('zone_type', 'A')
|
| 1102 |
},
|
| 1103 |
'lights': {
|
| 1104 |
'power': sum(load['power'] for load in internal_loads.get('lighting', [])),
|
| 1105 |
'use_factor': internal_loads.get('lighting', [{}])[0].get('usage_factor', 0.8),
|
| 1106 |
-
'hours_operation': internal_loads.get('lighting', [{}])[0].get('hours_in_operation', '8h'),
|
| 1107 |
-
'zone_type': internal_loads.get('lighting', [{}])[0].get('zone_type', 'A')
|
| 1108 |
},
|
| 1109 |
'equipment': {
|
| 1110 |
'power': sum(load['power'] for load in internal_loads.get('equipment', [])),
|
| 1111 |
'use_factor': internal_loads.get('equipment', [{}])[0].get('usage_factor', 0.7),
|
| 1112 |
-
'hours_operation': internal_loads.get('equipment', [{}])[0].get('hours_in_operation', '8h'),
|
| 1113 |
-
'zone_type': internal_loads.get('equipment', [{}])[0].get('zone_type', 'A')
|
| 1114 |
},
|
| 1115 |
'infiltration': {
|
| 1116 |
'flow_rate': building_info.get('infiltration_rate', 0.05),
|
| 1117 |
-
'height': building_info.get('building_height', 3.0),
|
| 1118 |
'crack_length': building_info.get('crack_length', 10.0)
|
| 1119 |
},
|
| 1120 |
'ventilation': {
|
| 1121 |
-
'flow_rate': building_info.get('ventilation_rate', 0.1)
|
| 1122 |
},
|
| 1123 |
'usage_factor': 0.7,
|
| 1124 |
'operating_hours': building_info.get('operating_hours', '8:00-18:00')
|
|
@@ -1152,7 +1149,7 @@ class HVACCalculator:
|
|
| 1152 |
'floor': design_loads['floors'] / 1000,
|
| 1153 |
'windows': design_loads['windows'] / 1000,
|
| 1154 |
'doors': design_loads['doors'] / 1000,
|
| 1155 |
-
'skylights': design_loads.get('skylights', 0) / 1000,
|
| 1156 |
'infiltration': (design_loads['infiltration_sensible'] + design_loads['infiltration_latent']) / 1000,
|
| 1157 |
'ventilation': (design_loads['ventilation_sensible'] + design_loads['ventilation_latent']) / 1000
|
| 1158 |
},
|
|
@@ -1162,7 +1159,7 @@ class HVACCalculator:
|
|
| 1162 |
'floors': [],
|
| 1163 |
'windows': [],
|
| 1164 |
'doors': [],
|
| 1165 |
-
'skylights': [],
|
| 1166 |
'infiltration': {
|
| 1167 |
'air_flow': formatted_internal_loads['infiltration']['flow_rate'],
|
| 1168 |
'delta_t': indoor_conditions['temperature'] - outdoor_conditions['design_temperature'],
|
|
@@ -1190,7 +1187,7 @@ class HVACCalculator:
|
|
| 1190 |
'orientation': wall.orientation.value,
|
| 1191 |
'area': wall.area,
|
| 1192 |
'u_value': wall.u_value,
|
| 1193 |
-
'solar_absorptivity': wall.solar_absorptivity,
|
| 1194 |
'delta_t': delta_t,
|
| 1195 |
'load': load / 1000
|
| 1196 |
})
|
|
@@ -1206,7 +1203,7 @@ class HVACCalculator:
|
|
| 1206 |
'orientation': roof.orientation.value,
|
| 1207 |
'area': roof.area,
|
| 1208 |
'u_value': roof.u_value,
|
| 1209 |
-
'solar_absorptivity': roof.solar_absorptivity,
|
| 1210 |
'delta_t': delta_t,
|
| 1211 |
'load': load / 1000
|
| 1212 |
})
|
|
@@ -1230,15 +1227,15 @@ class HVACCalculator:
|
|
| 1230 |
window=window,
|
| 1231 |
outdoor_temp=outdoor_conditions['design_temperature'],
|
| 1232 |
indoor_temp=indoor_conditions['temperature'],
|
| 1233 |
-
frame_type=window.frame_type
|
| 1234 |
)
|
| 1235 |
results['detailed_loads']['windows'].append({
|
| 1236 |
'name': window.name,
|
| 1237 |
'orientation': window.orientation.value,
|
| 1238 |
'area': window.area,
|
| 1239 |
'u_value': window.u_value,
|
| 1240 |
-
'glazing_type': window.glazing_type,
|
| 1241 |
-
'frame_type': window.frame_type,
|
| 1242 |
'delta_t': delta_t,
|
| 1243 |
'load': load / 1000
|
| 1244 |
})
|
|
@@ -1258,7 +1255,6 @@ class HVACCalculator:
|
|
| 1258 |
'load': load / 1000
|
| 1259 |
})
|
| 1260 |
|
| 1261 |
-
# NEW: Skylights
|
| 1262 |
for skylight in building_components.get('skylights', []):
|
| 1263 |
load = self.heating_calculator.calculate_skylight_heating_load(
|
| 1264 |
skylight=skylight,
|
|
|
|
| 17 |
|
| 18 |
# Import application modules
|
| 19 |
from app.building_info_form import BuildingInfoForm
|
| 20 |
+
from app.component_selection import ComponentSelectionInterface, Orientation, ComponentType, Wall, Roof, Floor, Window, Door, Skylight, GlazingType, FrameType
|
| 21 |
from app.results_display import ResultsDisplay
|
| 22 |
from app.data_validation import DataValidation
|
| 23 |
from app.data_persistence import DataPersistence
|
|
|
|
| 41 |
from utils.scenario_comparison import ScenarioComparisonVisualization
|
| 42 |
from utils.psychrometric_visualization import PsychrometricVisualization
|
| 43 |
from utils.time_based_visualization import TimeBasedVisualization
|
| 44 |
+
from data.drapery import Drapery
|
| 45 |
|
| 46 |
# NEW: ASHRAE 62.1 Ventilation Rates (Table 6.1)
|
| 47 |
VENTILATION_RATES = {
|
|
|
|
| 75 |
'floors': [],
|
| 76 |
'windows': [],
|
| 77 |
'doors': [],
|
| 78 |
+
'skylights': []
|
| 79 |
}
|
| 80 |
|
| 81 |
if 'internal_loads' not in st.session_state:
|
|
|
|
| 109 |
self.data_export = DataExport()
|
| 110 |
self.cooling_calculator = CoolingLoadCalculator()
|
| 111 |
self.heating_calculator = HeatingLoadCalculator()
|
| 112 |
+
self.drapery = Drapery()
|
| 113 |
|
| 114 |
# Persist ClimateData in session_state
|
| 115 |
if 'climate_data_obj' not in st.session_state:
|
|
|
|
| 259 |
existing_load['activity_level'] == new_load['activity_level'] and
|
| 260 |
existing_load['zone_type'] == new_load['zone_type'] and
|
| 261 |
existing_load['hours_in_operation'] == new_load['hours_in_operation'] and
|
| 262 |
+
existing_load['latent_gain'] == new_load['latent_gain']):
|
| 263 |
return False, f"Duplicate people load '{new_load['name']}' already exists."
|
| 264 |
elif load_type == 'lighting':
|
| 265 |
if (existing_load['name'] == new_load['name'] and
|
|
|
|
| 306 |
)
|
| 307 |
zone_type = st.selectbox(
|
| 308 |
"Zone Type",
|
| 309 |
+
["A", "B", "C", "D"],
|
| 310 |
help="Select zone type for CLF accuracy per ASHRAE"
|
| 311 |
)
|
| 312 |
hours_in_operation = st.selectbox(
|
| 313 |
+
"Hours Occupied",
|
| 314 |
+
["2h", "4h", "6h"],
|
| 315 |
help="Select hours of occupancy for CLF calculations"
|
| 316 |
)
|
| 317 |
+
latent_gain = st.number_input(
|
| 318 |
"Latent Gain per Person (Btu/h)",
|
| 319 |
min_value=0.0,
|
| 320 |
max_value=500.0,
|
|
|
|
| 332 |
"activity_level": activity_level,
|
| 333 |
"zone_type": zone_type,
|
| 334 |
"hours_in_operation": hours_in_operation,
|
| 335 |
+
"latent_gain": latent_gain
|
| 336 |
}
|
| 337 |
is_valid, message = self.validate_internal_load('people', people_load)
|
| 338 |
if is_valid:
|
|
|
|
| 378 |
)
|
| 379 |
zone_type = st.selectbox(
|
| 380 |
"Zone Type",
|
| 381 |
+
["A", "B", "C", "D"],
|
| 382 |
help="Select zone type for CLF accuracy per ASHRAE"
|
| 383 |
)
|
| 384 |
hours_in_operation = st.selectbox(
|
| 385 |
+
"Hours On",
|
| 386 |
+
["8h", "10h", "12h"],
|
| 387 |
help="Select hours of lighting operation for CLF calculations"
|
| 388 |
)
|
| 389 |
lighting_name = st.text_input("Name", value="General Lighting")
|
|
|
|
| 449 |
)
|
| 450 |
zone_type = st.selectbox(
|
| 451 |
"Zone Type",
|
| 452 |
+
["A", "B", "C", "D"],
|
| 453 |
help="Select zone type for CLF accuracy per ASHRAE"
|
| 454 |
)
|
| 455 |
hours_in_operation = st.selectbox(
|
| 456 |
+
"Hours Operated",
|
| 457 |
+
["2h", "4h", "6h"],
|
| 458 |
help="Select hours of equipment operation for CLF calculations"
|
| 459 |
)
|
| 460 |
equipment_name = st.text_input("Name", value="Office Equipment")
|
|
|
|
| 524 |
step=0.1,
|
| 525 |
help="Custom ventilation rate per floor area (ASHRAE 62.1)"
|
| 526 |
)
|
| 527 |
+
ventilation_rate = st.number_input(
|
| 528 |
"Ventilation Rate (m³/s)",
|
| 529 |
min_value=0.0,
|
| 530 |
max_value=10.0,
|
|
|
|
| 537 |
area_rate = VENTILATION_RATES[zone_type]["area_rate"]
|
| 538 |
st.write(f"People Rate: {people_rate} L/s/person (ASHRAE 62.1)")
|
| 539 |
st.write(f"Area Rate: {area_rate} L/s/m² (ASHRAE 62.1)")
|
| 540 |
+
ventilation_rate = st.number_input(
|
| 541 |
"Ventilation Rate (m³/s)",
|
| 542 |
min_value=0.0,
|
| 543 |
max_value=10.0,
|
|
|
|
| 618 |
# Format conditions
|
| 619 |
outdoor_conditions = {
|
| 620 |
'temperature': location['summer_design_temp_db'],
|
| 621 |
+
'relative_humidity': building_info.get('outdoor_rh', location['monthly_humidity'].get('Jul', 50.0)),
|
| 622 |
'ground_temperature': location['monthly_temps'].get('Jul', 20.0),
|
| 623 |
'month': 'Jul',
|
| 624 |
'latitude': location['latitude'],
|
| 625 |
+
'wind_speed': building_info.get('wind_speed', 4.0),
|
| 626 |
+
'day_of_year': month_to_day.get('Jul', 182)
|
| 627 |
}
|
| 628 |
indoor_conditions = {
|
| 629 |
'temperature': building_info.get('indoor_temp', 24.0),
|
| 630 |
+
'relative_humidity': building_info.get('indoor_rh', 50.0)
|
| 631 |
}
|
| 632 |
|
| 633 |
if st.session_state.get('debug_mode', False):
|
|
|
|
| 649 |
'people': {
|
| 650 |
'number': sum(load['num_people'] for load in internal_loads.get('people', [])),
|
| 651 |
'activity_level': internal_loads.get('people', [{}])[0].get('activity_level', 'Seated/Resting'),
|
| 652 |
+
'operating_hours': internal_loads.get('people', [{}])[0].get('hours_in_operation', '8h'),
|
| 653 |
+
'zone_type': internal_loads.get('people', [{}])[0].get('zone_type', 'A'),
|
| 654 |
+
'latent_gain': internal_loads.get('people', [{}])[0].get('latent_gain', 200.0)
|
| 655 |
},
|
| 656 |
'lights': {
|
| 657 |
'power': sum(load['power'] for load in internal_loads.get('lighting', [])),
|
| 658 |
'use_factor': internal_loads.get('lighting', [{}])[0].get('usage_factor', 0.8),
|
| 659 |
'special_allowance': 0.1,
|
| 660 |
+
'hours_operation': internal_loads.get('lighting', [{}])[0].get('hours_in_operation', '8h'),
|
| 661 |
+
'zone_type': internal_loads.get('lighting', [{}])[0].get('zone_type', 'A')
|
| 662 |
},
|
| 663 |
'equipment': {
|
| 664 |
'power': sum(load['power'] for load in internal_loads.get('equipment', [])),
|
| 665 |
'use_factor': internal_loads.get('equipment', [{}])[0].get('usage_factor', 0.7),
|
| 666 |
'radiation_factor': internal_loads.get('equipment', [{}])[0].get('radiation_fraction', 0.3),
|
| 667 |
+
'hours_operation': internal_loads.get('equipment', [{}])[0].get('hours_in_operation', '8h'),
|
| 668 |
+
'zone_type': internal_loads.get('equipment', [{}])[0].get('zone_type', 'A')
|
| 669 |
},
|
| 670 |
'infiltration': {
|
| 671 |
+
'flow_rate': building_info.get('infiltration_rate', 0.5),
|
| 672 |
+
'height': building_info.get('building_height', 3.0),
|
| 673 |
'crack_length': building_info.get('crack_length', 10.0)
|
| 674 |
},
|
| 675 |
'ventilation': {
|
| 676 |
+
'flow_rate': building_info.get('ventilation_rate', 0.1)
|
| 677 |
},
|
| 678 |
'operating_hours': building_info.get('operating_hours', '8:00-18:00')
|
| 679 |
}
|
|
|
|
| 723 |
'roof': design_loads['roofs'] / 1000,
|
| 724 |
'windows': (design_loads['windows_conduction'] + design_loads['windows_solar']) / 1000,
|
| 725 |
'doors': design_loads['doors'] / 1000,
|
| 726 |
+
'skylights': design_loads.get('skylights', 0) / 1000,
|
| 727 |
'people': (design_loads['people_sensible'] + design_loads['people_latent']) / 1000,
|
| 728 |
'lighting': design_loads['lights'] / 1000,
|
| 729 |
'equipment': (design_loads['equipment_sensible'] + design_loads['equipment_latent']) / 1000,
|
|
|
|
| 735 |
'roofs': [],
|
| 736 |
'windows': [],
|
| 737 |
'doors': [],
|
| 738 |
+
'skylights': [],
|
| 739 |
'internal': [],
|
| 740 |
'infiltration': {
|
| 741 |
'air_flow': formatted_internal_loads['infiltration']['flow_rate'],
|
|
|
|
| 762 |
month=outdoor_conditions['month'],
|
| 763 |
hour=design_loads['design_hour'],
|
| 764 |
latitude=outdoor_conditions['latitude'],
|
| 765 |
+
solar_absorptivity=wall.solar_absorptivity
|
| 766 |
)
|
| 767 |
results['detailed_loads']['walls'].append({
|
| 768 |
'name': wall.name,
|
| 769 |
'orientation': wall.orientation.value,
|
| 770 |
'area': wall.area,
|
| 771 |
'u_value': wall.u_value,
|
| 772 |
+
'solar_absorptivity': wall.solar_absorptivity,
|
| 773 |
+
'cltd': self.cooling_calculator.ashrae_tables.get_cltd(
|
| 774 |
+
element_type='wall',
|
| 775 |
+
group=wall.wall_group,
|
| 776 |
orientation=wall.orientation.value,
|
| 777 |
hour=design_loads['design_hour'],
|
| 778 |
+
latitude=float(outdoor_conditions['latitude'].replace('N', '')),
|
| 779 |
+
solar_absorptivity=wall.solar_absorptivity,
|
| 780 |
+
month=outdoor_conditions['month'].lower()
|
|
|
|
|
|
|
| 781 |
),
|
| 782 |
'load': load / 1000
|
| 783 |
})
|
|
|
|
| 790 |
month=outdoor_conditions['month'],
|
| 791 |
hour=design_loads['design_hour'],
|
| 792 |
latitude=outdoor_conditions['latitude'],
|
| 793 |
+
solar_absorptivity=roof.solar_absorptivity
|
| 794 |
)
|
| 795 |
results['detailed_loads']['roofs'].append({
|
| 796 |
'name': roof.name,
|
| 797 |
'orientation': roof.orientation.value,
|
| 798 |
'area': roof.area,
|
| 799 |
'u_value': roof.u_value,
|
| 800 |
+
'solar_absorptivity': roof.solar_absorptivity,
|
| 801 |
+
'cltd': self.cooling_calculator.ashrae_tables.get_cltd(
|
| 802 |
+
element_type='roof',
|
| 803 |
+
group=roof.roof_group,
|
| 804 |
+
orientation=roof.orientation.value,
|
| 805 |
hour=design_loads['design_hour'],
|
| 806 |
+
latitude=float(outdoor_conditions['latitude'].replace('N', '')),
|
| 807 |
+
solar_absorptivity=roof.solar_absorptivity,
|
| 808 |
+
month=outdoor_conditions['month'].lower()
|
|
|
|
|
|
|
| 809 |
),
|
| 810 |
'load': load / 1000
|
| 811 |
})
|
| 812 |
|
| 813 |
for window in building_components.get('windows', []):
|
| 814 |
+
adjusted_shgc = self.drapery.adjust_shgc(
|
|
|
|
| 815 |
base_shgc=window.shgc,
|
| 816 |
glazing_type=window.glazing_type,
|
| 817 |
drapery_type=window.drapery_type if hasattr(window, 'drapery_type') else None
|
|
|
|
| 824 |
hour=design_loads['design_hour'],
|
| 825 |
latitude=outdoor_conditions['latitude'],
|
| 826 |
shading_coefficient=window.shading_coefficient,
|
| 827 |
+
adjusted_shgc=adjusted_shgc,
|
| 828 |
+
glazing_type=window.glazing_type,
|
| 829 |
+
frame_type=window.frame_type
|
| 830 |
)
|
| 831 |
if 'total' not in load_dict:
|
| 832 |
if 'conduction' in load_dict and 'solar' in load_dict:
|
|
|
|
| 840 |
'area': window.area,
|
| 841 |
'u_value': window.u_value,
|
| 842 |
'shgc': window.shgc,
|
| 843 |
+
'adjusted_shgc': adjusted_shgc,
|
| 844 |
+
'glazing_type': window.glazing_type,
|
| 845 |
+
'frame_type': window.frame_type,
|
| 846 |
+
'drapery_type': window.drapery_type if hasattr(window, 'drapery_type') else 'None',
|
| 847 |
'shading_device': window.shading_device,
|
| 848 |
'shading_coefficient': window.shading_coefficient,
|
| 849 |
'scl': self.cooling_calculator.ashrae_tables.get_scl(
|
| 850 |
+
latitude=float(outdoor_conditions['latitude'].replace('N', '')),
|
| 851 |
+
month=outdoor_conditions['month'].lower(),
|
| 852 |
orientation=window.orientation.value,
|
| 853 |
hour=design_loads['design_hour']
|
| 854 |
),
|
|
|
|
| 870 |
'load': load / 1000
|
| 871 |
})
|
| 872 |
|
|
|
|
| 873 |
for skylight in building_components.get('skylights', []):
|
| 874 |
+
adjusted_shgc = self.drapery.adjust_shgc(
|
| 875 |
base_shgc=skylight.shgc,
|
| 876 |
glazing_type=skylight.glazing_type,
|
| 877 |
drapery_type=skylight.drapery_type if hasattr(skylight, 'drapery_type') else None
|
|
|
|
| 905 |
'drapery_type': skylight.drapery_type if hasattr(skylight, 'drapery_type') else 'None',
|
| 906 |
'shading_coefficient': skylight.shading_coefficient,
|
| 907 |
'scl': self.cooling_calculator.ashrae_tables.get_scl(
|
| 908 |
+
latitude=float(outdoor_conditions['latitude'].replace('N', '')),
|
| 909 |
+
month=outdoor_conditions['month'].lower(),
|
| 910 |
+
orientation='Horizontal',
|
| 911 |
hour=design_loads['design_hour']
|
| 912 |
),
|
| 913 |
'load': load_dict['total'] / 1000
|
|
|
|
| 920 |
num_people=load['num_people'],
|
| 921 |
activity_level=load['activity_level'],
|
| 922 |
hour=design_loads['design_hour'],
|
| 923 |
+
latent_gain=load.get('latent_gain', 200.0)
|
| 924 |
)
|
| 925 |
if 'total' not in load_dict and ('sensible' in load_dict or 'latent' in load_dict):
|
| 926 |
load_dict['total'] = load_dict.get('sensible', 0) + load_dict.get('latent', 0)
|
|
|
|
| 1020 |
|
| 1021 |
# Skip heating calculation if outdoor temp exceeds indoor temp
|
| 1022 |
indoor_temp = building_info.get('indoor_temp', 21.0)
|
| 1023 |
+
outdoor_temp = building_info.get('winter_temp', location['winter_design_temp'])
|
| 1024 |
if outdoor_temp >= indoor_temp:
|
| 1025 |
results = {
|
| 1026 |
'total_load': 0.0,
|
|
|
|
| 1033 |
'floor': 0.0,
|
| 1034 |
'windows': 0.0,
|
| 1035 |
'doors': 0.0,
|
| 1036 |
+
'skylights': 0.0,
|
| 1037 |
'infiltration': 0.0,
|
| 1038 |
'ventilation': 0.0
|
| 1039 |
},
|
|
|
|
| 1043 |
'floors': [],
|
| 1044 |
'windows': [],
|
| 1045 |
'doors': [],
|
| 1046 |
+
'skylights': [],
|
| 1047 |
'infiltration': {'air_flow': 0.0, 'delta_t': 0.0, 'load': 0.0},
|
| 1048 |
'ventilation': {'air_flow': 0.0, 'delta_t': 0.0, 'load': 0.0}
|
| 1049 |
},
|
|
|
|
| 1053 |
|
| 1054 |
# Format conditions
|
| 1055 |
outdoor_conditions = {
|
| 1056 |
+
'design_temperature': outdoor_temp,
|
| 1057 |
+
'design_relative_humidity': building_info.get('outdoor_rh', location['monthly_humidity'].get('Jan', 80.0)),
|
| 1058 |
'ground_temperature': ground_temperature,
|
| 1059 |
+
'wind_speed': building_info.get('wind_speed', 4.0)
|
| 1060 |
}
|
| 1061 |
indoor_conditions = {
|
| 1062 |
'temperature': indoor_temp,
|
| 1063 |
+
'relative_humidity': building_info.get('indoor_rh', 40.0)
|
| 1064 |
}
|
| 1065 |
|
| 1066 |
if st.session_state.get('debug_mode', False):
|
|
|
|
| 1093 |
internal_loads.get('people', [{}])[0].get('activity_level', 'Seated/Resting'),
|
| 1094 |
70.0
|
| 1095 |
),
|
| 1096 |
+
'latent_gain': internal_loads.get('people', [{}])[0].get('latent_gain', 200.0),
|
| 1097 |
+
'operating_hours': internal_loads.get('people', [{}])[0].get('hours_in_operation', '8h'),
|
| 1098 |
+
'zone_type': internal_loads.get('people', [{}])[0].get('zone_type', 'A')
|
| 1099 |
},
|
| 1100 |
'lights': {
|
| 1101 |
'power': sum(load['power'] for load in internal_loads.get('lighting', [])),
|
| 1102 |
'use_factor': internal_loads.get('lighting', [{}])[0].get('usage_factor', 0.8),
|
| 1103 |
+
'hours_operation': internal_loads.get('lighting', [{}])[0].get('hours_in_operation', '8h'),
|
| 1104 |
+
'zone_type': internal_loads.get('lighting', [{}])[0].get('zone_type', 'A')
|
| 1105 |
},
|
| 1106 |
'equipment': {
|
| 1107 |
'power': sum(load['power'] for load in internal_loads.get('equipment', [])),
|
| 1108 |
'use_factor': internal_loads.get('equipment', [{}])[0].get('usage_factor', 0.7),
|
| 1109 |
+
'hours_operation': internal_loads.get('equipment', [{}])[0].get('hours_in_operation', '8h'),
|
| 1110 |
+
'zone_type': internal_loads.get('equipment', [{}])[0].get('zone_type', 'A')
|
| 1111 |
},
|
| 1112 |
'infiltration': {
|
| 1113 |
'flow_rate': building_info.get('infiltration_rate', 0.05),
|
| 1114 |
+
'height': building_info.get('building_height', 3.0),
|
| 1115 |
'crack_length': building_info.get('crack_length', 10.0)
|
| 1116 |
},
|
| 1117 |
'ventilation': {
|
| 1118 |
+
'flow_rate': building_info.get('ventilation_rate', 0.1)
|
| 1119 |
},
|
| 1120 |
'usage_factor': 0.7,
|
| 1121 |
'operating_hours': building_info.get('operating_hours', '8:00-18:00')
|
|
|
|
| 1149 |
'floor': design_loads['floors'] / 1000,
|
| 1150 |
'windows': design_loads['windows'] / 1000,
|
| 1151 |
'doors': design_loads['doors'] / 1000,
|
| 1152 |
+
'skylights': design_loads.get('skylights', 0) / 1000,
|
| 1153 |
'infiltration': (design_loads['infiltration_sensible'] + design_loads['infiltration_latent']) / 1000,
|
| 1154 |
'ventilation': (design_loads['ventilation_sensible'] + design_loads['ventilation_latent']) / 1000
|
| 1155 |
},
|
|
|
|
| 1159 |
'floors': [],
|
| 1160 |
'windows': [],
|
| 1161 |
'doors': [],
|
| 1162 |
+
'skylights': [],
|
| 1163 |
'infiltration': {
|
| 1164 |
'air_flow': formatted_internal_loads['infiltration']['flow_rate'],
|
| 1165 |
'delta_t': indoor_conditions['temperature'] - outdoor_conditions['design_temperature'],
|
|
|
|
| 1187 |
'orientation': wall.orientation.value,
|
| 1188 |
'area': wall.area,
|
| 1189 |
'u_value': wall.u_value,
|
| 1190 |
+
'solar_absorptivity': wall.solar_absorptivity,
|
| 1191 |
'delta_t': delta_t,
|
| 1192 |
'load': load / 1000
|
| 1193 |
})
|
|
|
|
| 1203 |
'orientation': roof.orientation.value,
|
| 1204 |
'area': roof.area,
|
| 1205 |
'u_value': roof.u_value,
|
| 1206 |
+
'solar_absorptivity': roof.solar_absorptivity,
|
| 1207 |
'delta_t': delta_t,
|
| 1208 |
'load': load / 1000
|
| 1209 |
})
|
|
|
|
| 1227 |
window=window,
|
| 1228 |
outdoor_temp=outdoor_conditions['design_temperature'],
|
| 1229 |
indoor_temp=indoor_conditions['temperature'],
|
| 1230 |
+
frame_type=window.frame_type
|
| 1231 |
)
|
| 1232 |
results['detailed_loads']['windows'].append({
|
| 1233 |
'name': window.name,
|
| 1234 |
'orientation': window.orientation.value,
|
| 1235 |
'area': window.area,
|
| 1236 |
'u_value': window.u_value,
|
| 1237 |
+
'glazing_type': window.glazing_type,
|
| 1238 |
+
'frame_type': window.frame_type,
|
| 1239 |
'delta_t': delta_t,
|
| 1240 |
'load': load / 1000
|
| 1241 |
})
|
|
|
|
| 1255 |
'load': load / 1000
|
| 1256 |
})
|
| 1257 |
|
|
|
|
| 1258 |
for skylight in building_components.get('skylights', []):
|
| 1259 |
load = self.heating_calculator.calculate_skylight_heating_load(
|
| 1260 |
skylight=skylight,
|