Update data/calculation.py
Browse files- data/calculation.py +131 -94
data/calculation.py
CHANGED
|
@@ -14,8 +14,8 @@ from data.internal_loads import PEOPLE_ACTIVITY_LEVELS, DIVERSITY_FACTORS, LIGHT
|
|
| 14 |
from datetime import datetime
|
| 15 |
from collections import defaultdict
|
| 16 |
import logging
|
| 17 |
-
|
| 18 |
-
|
| 19 |
|
| 20 |
# Configure logging
|
| 21 |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
|
@@ -34,16 +34,7 @@ class TFMCalculations:
|
|
| 34 |
if mode == "heating" and delta_t >= 0:
|
| 35 |
return 0, 0
|
| 36 |
|
| 37 |
-
#
|
| 38 |
-
if component.component_type in [ComponentType.WINDOW, ComponentType.SKYLIGHT]:
|
| 39 |
-
load = component.u_value * component.area * delta_t
|
| 40 |
-
cooling_load = load / 1000 if mode == "cooling" else 0
|
| 41 |
-
heating_load = -load / 1000 if mode == "heating" else 0
|
| 42 |
-
logger.info(f"Conduction load for {component.component_type} {getattr(component, 'id', 'unknown_component')} "
|
| 43 |
-
f"at hour {hour}: cooling={cooling_load:.2f} kW, heating={heating_load:.2f} kW (U={component.u_value}, A={component.area}, ΔT={delta_t:.2f})")
|
| 44 |
-
return cooling_load, heating_load
|
| 45 |
-
|
| 46 |
-
# For other components, use CTFCalculator
|
| 47 |
ctf = CTFCalculator.calculate_ctf_coefficients(component)
|
| 48 |
|
| 49 |
# Initialize history terms (simplified: assume steady-state history for demonstration)
|
|
@@ -55,8 +46,6 @@ class TFMCalculations:
|
|
| 55 |
# Note: F terms require flux history, omitted here for simplicity
|
| 56 |
cooling_load = load / 1000 if mode == "cooling" else 0
|
| 57 |
heating_load = -load / 1000 if mode == "heating" else 0
|
| 58 |
-
logger.info(f"Conduction load for {component.component_type} {getattr(component, 'id', 'unknown_component')} "
|
| 59 |
-
f"at hour {hour}: cooling={cooling_load:.2f} kW, heating={heating_load:.2f} kW (CTF Y[0]={ctf.Y[0] if ctf.Y else 0})")
|
| 60 |
return cooling_load, heating_load
|
| 61 |
|
| 62 |
@staticmethod
|
|
@@ -210,70 +199,30 @@ class TFMCalculations:
|
|
| 210 |
operating_periods = hvac_settings.get("operating_hours", [{"start": 8, "end": 18}])
|
| 211 |
area = building_info.get("floor_area", 100.0)
|
| 212 |
|
| 213 |
-
#
|
| 214 |
-
|
| 215 |
-
logger.warning("No filtered hourly data available. Returning empty loads.")
|
| 216 |
-
return []
|
| 217 |
-
|
| 218 |
-
required_fields = ["global_horizontal_radiation", "dry_bulb", "month", "day", "hour"]
|
| 219 |
-
for data in filtered_data:
|
| 220 |
-
missing = [field for field in required_fields if field not in data]
|
| 221 |
-
if missing:
|
| 222 |
-
logger.warning(f"Missing fields {missing} in hourly data for {data.get('month')}/{data.get('day')}/{data.get('hour')}")
|
| 223 |
-
|
| 224 |
-
# Validate fenestration components
|
| 225 |
-
fenestration_count = 0
|
| 226 |
-
for comp_type in ['windows', 'skylights']:
|
| 227 |
-
comp_list = components.get(comp_type, [])
|
| 228 |
-
fenestration_count += len(comp_list)
|
| 229 |
-
for comp in comp_list:
|
| 230 |
-
comp_id = getattr(comp, 'id', 'unknown_component')
|
| 231 |
-
if not hasattr(comp, 'area') or comp.area <= 0:
|
| 232 |
-
logger.warning(f"Component {comp_id} ({comp_type}) has invalid area: {getattr(comp, 'area', 'None')}")
|
| 233 |
-
if not hasattr(comp, 'shgc') or comp.shgc <= 0:
|
| 234 |
-
logger.warning(f"Component {comp_id} ({comp_type}) has invalid SHGC: {getattr(comp, 'shgc', 'None')}")
|
| 235 |
-
if not hasattr(comp, 'u_value') or comp.u_value <= 0:
|
| 236 |
-
logger.warning(f"Component {comp_id} ({comp_type}) has invalid U-value: {getattr(comp, 'u_value', 'None')}")
|
| 237 |
-
if not hasattr(comp, 'facade') or comp.facade not in ['A', 'B', 'C', 'D']:
|
| 238 |
-
logger.warning(f"Component {comp_id} ({comp_type}) has invalid facade: {getattr(comp, 'facade', 'None')}")
|
| 239 |
-
if not hasattr(comp, 'id') or not comp.id:
|
| 240 |
-
logger.warning(f"Component {comp_type} missing id. Assigning temporary id.")
|
| 241 |
-
comp.id = f"{comp_type}_{id(comp)}"
|
| 242 |
-
logger.info(f"Number of fenestration components (windows/skylights): {fenestration_count}")
|
| 243 |
-
if fenestration_count == 0:
|
| 244 |
-
logger.warning("No windows or skylights defined. Solar load will be 0.")
|
| 245 |
-
|
| 246 |
-
# Validate building_info
|
| 247 |
-
climate_data = building_info.get("climate_data", {})
|
| 248 |
-
for field in ["latitude", "longitude", "timezone"]:
|
| 249 |
-
if field not in climate_data:
|
| 250 |
-
logger.warning(f"Missing {field} in building_info.climate_data. Using default 0.0.")
|
| 251 |
-
climate_data[field] = 0.0
|
| 252 |
-
if "ground_reflectivity" not in building_info:
|
| 253 |
-
logger.warning("Missing ground_reflectivity in building_info. Using default 0.2.")
|
| 254 |
-
building_info["ground_reflectivity"] = 0.2
|
| 255 |
-
|
| 256 |
-
# Pre-calculate CTF coefficients for non-fenestration components
|
| 257 |
-
for comp_type, comp_list in components.items():
|
| 258 |
-
if comp_type in ['windows', 'skylights']:
|
| 259 |
-
continue
|
| 260 |
for comp in comp_list:
|
| 261 |
comp.ctf = CTFCalculator.calculate_ctf_coefficients(comp)
|
| 262 |
|
| 263 |
# Calculate solar parameters
|
|
|
|
|
|
|
| 264 |
solar_results = SolarCalculations.calculate_solar_parameters(
|
| 265 |
hourly_data=filtered_data,
|
| 266 |
latitude=climate_data.get("latitude", 0.0),
|
| 267 |
longitude=climate_data.get("longitude", 0.0),
|
| 268 |
timezone=climate_data.get("timezone", 0.0),
|
| 269 |
-
ground_reflectivity=
|
| 270 |
components=components
|
| 271 |
)
|
| 272 |
|
| 273 |
# Create a dictionary for quick lookup of solar results by hour
|
| 274 |
solar_results_by_hour = {(res["month"], res["day"], res["hour"]): res for res in solar_results}
|
| 275 |
-
|
| 276 |
-
|
|
|
|
|
|
|
|
|
|
| 277 |
|
| 278 |
for hour_data in filtered_data:
|
| 279 |
hour = hour_data["hour"]
|
|
@@ -292,43 +241,40 @@ class TFMCalculations:
|
|
| 292 |
break
|
| 293 |
# Determine mode based on temperature threshold (18°C)
|
| 294 |
mode = "none" if abs(outdoor_temp - 18) < 0.01 else "cooling" if outdoor_temp > 18 else "heating"
|
| 295 |
-
|
| 296 |
-
solar_result_hour = solar_results_by_hour.get(solar_key, {})
|
| 297 |
-
if is_operating:
|
| 298 |
for comp_type, comp_list in components.items():
|
| 299 |
for comp in comp_list:
|
| 300 |
# Get solar result for this component and hour
|
| 301 |
-
|
| 302 |
-
solar_result = next((res for res in
|
| 303 |
-
if res["component_id"] ==
|
| 304 |
sol_air_temp = solar_result.get("sol_air_temp") if solar_result else None
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
cool_load, _ = TFMCalculations.calculate_conduction_load(comp, outdoor_temp, indoor_temp, hour, sol_air_temp, mode="cooling")
|
| 308 |
-
conduction_cooling += cool_load
|
| 309 |
-
elif mode == "heating":
|
| 310 |
-
_, heat_load = TFMCalculations.calculate_conduction_load(comp, outdoor_temp, indoor_temp, hour, sol_air_temp, mode="heating")
|
| 311 |
-
conduction_heating += heat_load
|
| 312 |
-
# Add solar heat gain for windows and skylights if available
|
| 313 |
if solar_result and comp.component_type in [ComponentType.WINDOW, ComponentType.SKYLIGHT]:
|
| 314 |
solar_heat_gain = solar_result.get("solar_heat_gain", 0)
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
f"at {hour_data['month']}/{hour_data['day']}/{hour}: {solar_heat_gain:.2f} kW")
|
| 319 |
-
else:
|
| 320 |
-
logger.info(f"No solar heat gain for component {comp_id} "
|
| 321 |
-
f"at {hour_data['month']}/{hour_data['day']}/{hour}")
|
| 322 |
logger.info(f"Total solar load for {hour_data['month']}/{hour_data['day']}/{hour}: {solar:.2f} kW")
|
| 323 |
internal = TFMCalculations.calculate_internal_load(internal_loads, hour, max([p["end"] - p["start"] for p in operating_periods]), area)
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 332 |
# Calculate total loads, subtracting internal load for heating
|
| 333 |
total_cooling = conduction_cooling + solar + internal + ventilation_cooling + infiltration_cooling
|
| 334 |
total_heating = max(conduction_heating + ventilation_heating + infiltration_heating - internal, 0)
|
|
@@ -372,4 +318,95 @@ class TFMCalculations:
|
|
| 372 |
load["total_cooling"] = 0
|
| 373 |
load["total_heating"] = 0 # Zero both totals, keep components
|
| 374 |
final_loads.append(load)
|
| 375 |
-
return final_loads
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
from datetime import datetime
|
| 15 |
from collections import defaultdict
|
| 16 |
import logging
|
| 17 |
+
import streamlit as st
|
| 18 |
+
import plotly.graph_objects as go
|
| 19 |
|
| 20 |
# Configure logging
|
| 21 |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
|
|
|
| 34 |
if mode == "heating" and delta_t >= 0:
|
| 35 |
return 0, 0
|
| 36 |
|
| 37 |
+
# Get CTF coefficients using CTFCalculator
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
ctf = CTFCalculator.calculate_ctf_coefficients(component)
|
| 39 |
|
| 40 |
# Initialize history terms (simplified: assume steady-state history for demonstration)
|
|
|
|
| 46 |
# Note: F terms require flux history, omitted here for simplicity
|
| 47 |
cooling_load = load / 1000 if mode == "cooling" else 0
|
| 48 |
heating_load = -load / 1000 if mode == "heating" else 0
|
|
|
|
|
|
|
| 49 |
return cooling_load, heating_load
|
| 50 |
|
| 51 |
@staticmethod
|
|
|
|
| 199 |
operating_periods = hvac_settings.get("operating_hours", [{"start": 8, "end": 18}])
|
| 200 |
area = building_info.get("floor_area", 100.0)
|
| 201 |
|
| 202 |
+
# Pre-calculate CTF coefficients for all components using CTFCalculator
|
| 203 |
+
for comp_list in components.values():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 204 |
for comp in comp_list:
|
| 205 |
comp.ctf = CTFCalculator.calculate_ctf_coefficients(comp)
|
| 206 |
|
| 207 |
# Calculate solar parameters
|
| 208 |
+
climate_data = building_info.get("climate_data", {})
|
| 209 |
+
ground_reflectivity = building_info.get("ground_reflectivity", 0.2)
|
| 210 |
solar_results = SolarCalculations.calculate_solar_parameters(
|
| 211 |
hourly_data=filtered_data,
|
| 212 |
latitude=climate_data.get("latitude", 0.0),
|
| 213 |
longitude=climate_data.get("longitude", 0.0),
|
| 214 |
timezone=climate_data.get("timezone", 0.0),
|
| 215 |
+
ground_reflectivity=ground_reflectivity,
|
| 216 |
components=components
|
| 217 |
)
|
| 218 |
|
| 219 |
# Create a dictionary for quick lookup of solar results by hour
|
| 220 |
solar_results_by_hour = {(res["month"], res["day"], res["hour"]): res for res in solar_results}
|
| 221 |
+
|
| 222 |
+
# Log presence of fenestration components
|
| 223 |
+
fenestration_count = sum(len(comp_list) for comp_type, comp_list in components.items()
|
| 224 |
+
if comp_type in ['windows', 'skylights'])
|
| 225 |
+
logger.info(f"Number of fenestration components (windows/skylights): {fenestration_count}")
|
| 226 |
|
| 227 |
for hour_data in filtered_data:
|
| 228 |
hour = hour_data["hour"]
|
|
|
|
| 241 |
break
|
| 242 |
# Determine mode based on temperature threshold (18°C)
|
| 243 |
mode = "none" if abs(outdoor_temp - 18) < 0.01 else "cooling" if outdoor_temp > 18 else "heating"
|
| 244 |
+
if is_operating and mode == "cooling":
|
|
|
|
|
|
|
| 245 |
for comp_type, comp_list in components.items():
|
| 246 |
for comp in comp_list:
|
| 247 |
# Get solar result for this component and hour
|
| 248 |
+
solar_key = (hour_data["month"], hour_data["day"], hour)
|
| 249 |
+
solar_result = next((res for res in solar_results_by_hour.get(solar_key, {}).get("component_results", [])
|
| 250 |
+
if res["component_id"] == getattr(comp, 'id', 'unknown_component')), None)
|
| 251 |
sol_air_temp = solar_result.get("sol_air_temp") if solar_result else None
|
| 252 |
+
cool_load, _ = TFMCalculations.calculate_conduction_load(comp, outdoor_temp, indoor_temp, hour, sol_air_temp, mode="cooling")
|
| 253 |
+
conduction_cooling += cool_load
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 254 |
if solar_result and comp.component_type in [ComponentType.WINDOW, ComponentType.SKYLIGHT]:
|
| 255 |
solar_heat_gain = solar_result.get("solar_heat_gain", 0)
|
| 256 |
+
solar += solar_heat_gain
|
| 257 |
+
logger.info(f"Adding solar heat gain for component {getattr(comp, 'id', 'unknown_component')} "
|
| 258 |
+
f"at {hour_data['month']}/{hour_data['day']}/{hour}: {solar_heat_gain:.2f} kW")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 259 |
logger.info(f"Total solar load for {hour_data['month']}/{hour_data['day']}/{hour}: {solar:.2f} kW")
|
| 260 |
internal = TFMCalculations.calculate_internal_load(internal_loads, hour, max([p["end"] - p["start"] for p in operating_periods]), area)
|
| 261 |
+
ventilation_cooling, _ = TFMCalculations.calculate_ventilation_load(internal_loads, outdoor_temp, indoor_temp, area, building_info, mode="cooling")
|
| 262 |
+
infiltration_cooling, _ = TFMCalculations.calculate_infiltration_load(internal_loads, outdoor_temp, indoor_temp, area, building_info, mode="cooling")
|
| 263 |
+
elif is_operating and mode == "heating":
|
| 264 |
+
for comp_type, comp_list in components.items():
|
| 265 |
+
for comp in comp_list:
|
| 266 |
+
# Get solar result for this component and hour
|
| 267 |
+
solar_key = (hour_data["month"], hour_data["day"], hour)
|
| 268 |
+
solar_result = next((res for res in solar_results_by_hour.get(solar_key, {}).get("component_results", [])
|
| 269 |
+
if res["component_id"] == getattr(comp, 'id', 'unknown_component')), None)
|
| 270 |
+
sol_air_temp = solar_result.get("sol_air_temp") if solar_result else None
|
| 271 |
+
_, heat_load = TFMCalculations.calculate_conduction_load(comp, outdoor_temp, indoor_temp, hour, sol_air_temp, mode="heating")
|
| 272 |
+
conduction_heating += heat_load
|
| 273 |
+
internal = TFMCalculations.calculate_internal_load(internal_loads, hour, max([p["end"] - p["start"] for p in operating_periods]), area)
|
| 274 |
+
_, ventilation_heating = TFMCalculations.calculate_ventilation_load(internal_loads, outdoor_temp, indoor_temp, area, building_info, mode="heating")
|
| 275 |
+
_, infiltration_heating = TFMCalculations.calculate_infiltration_load(internal_loads, outdoor_temp, indoor_temp, area, building_info, mode="heating")
|
| 276 |
+
else: # mode == "none" or not is_operating
|
| 277 |
+
internal = 0 # No internal loads when no heating or cooling is needed or outside operating hours
|
| 278 |
# Calculate total loads, subtracting internal load for heating
|
| 279 |
total_cooling = conduction_cooling + solar + internal + ventilation_cooling + infiltration_cooling
|
| 280 |
total_heating = max(conduction_heating + ventilation_heating + infiltration_heating - internal, 0)
|
|
|
|
| 318 |
load["total_cooling"] = 0
|
| 319 |
load["total_heating"] = 0 # Zero both totals, keep components
|
| 320 |
final_loads.append(load)
|
| 321 |
+
return final_loads
|
| 322 |
+
|
| 323 |
+
@staticmethod
|
| 324 |
+
def display_results(loads: List[Dict]):
|
| 325 |
+
"""Display simulation results including equipment sizing, load breakdown, monthly loads, and summary table."""
|
| 326 |
+
df = pd.DataFrame(loads)
|
| 327 |
+
if df.empty:
|
| 328 |
+
st.error("No load calculations available.")
|
| 329 |
+
return
|
| 330 |
+
|
| 331 |
+
# Equipment Sizing
|
| 332 |
+
st.subheader("Equipment Sizing")
|
| 333 |
+
peak_cooling_load = df["total_cooling"].max() if "total_cooling" in df else 0.0
|
| 334 |
+
peak_heating_load = df["total_heating"].max() if "total_heating" in df else 0.0
|
| 335 |
+
col1, col2 = st.columns(2)
|
| 336 |
+
with col1:
|
| 337 |
+
st.metric("Cooling Equipment Size", f"{peak_cooling_load:.2f} kW", help="Peak hourly cooling load")
|
| 338 |
+
with col2:
|
| 339 |
+
st.metric("Heating Equipment Size", f"{peak_heating_load:.2f} kW", help="Peak hourly heating load")
|
| 340 |
+
|
| 341 |
+
# Pie Charts for Load Breakdown
|
| 342 |
+
st.subheader("Load Breakdown")
|
| 343 |
+
cooling_totals = {
|
| 344 |
+
"Conduction": df["conduction_cooling"].sum(),
|
| 345 |
+
"Solar": df["solar"].sum(),
|
| 346 |
+
"Internal": df["internal"].sum(),
|
| 347 |
+
"Ventilation": df["ventilation_cooling"].sum(),
|
| 348 |
+
"Infiltration": df["infiltration_cooling"].sum()
|
| 349 |
+
}
|
| 350 |
+
heating_totals = {
|
| 351 |
+
"Conduction": df["conduction_heating"].sum(),
|
| 352 |
+
"Ventilation": df["ventilation_heating"].sum(),
|
| 353 |
+
"Infiltration": df["infiltration_heating"].sum()
|
| 354 |
+
}
|
| 355 |
+
col1, col2 = st.columns(2)
|
| 356 |
+
with col1:
|
| 357 |
+
fig_cooling = go.Figure(data=[
|
| 358 |
+
go.Pie(labels=list(cooling_totals.keys()), values=list(cooling_totals.values()))
|
| 359 |
+
])
|
| 360 |
+
fig_cooling.update_layout(title="Cooling Load Breakdown (kWh)", width=400, height=400)
|
| 361 |
+
st.plotly_chart(fig_cooling, use_container_width=True)
|
| 362 |
+
with col2:
|
| 363 |
+
fig_heating = go.Figure(data=[
|
| 364 |
+
go.Pie(labels=list(heating_totals.keys()), values=list(heating_totals.values()))
|
| 365 |
+
])
|
| 366 |
+
fig_heating.update_layout(title="Heating Load Breakdown (kWh)", width=400, height=400)
|
| 367 |
+
st.plotly_chart(fig_heating, use_container_width=True)
|
| 368 |
+
|
| 369 |
+
# Monthly Loads Bar Chart
|
| 370 |
+
st.subheader("Monthly Heating and Cooling Loads")
|
| 371 |
+
df["month_name"] = df["month"].map({
|
| 372 |
+
1: "Jan", 2: "Feb", 3: "Mar", 4: "Apr", 5: "May", 6: "Jun",
|
| 373 |
+
7: "Jul", 8: "Aug", 9: "Sep", 10: "Oct", 11: "Nov", 12: "Dec"
|
| 374 |
+
})
|
| 375 |
+
monthly_loads = df.groupby("month_name")[["total_cooling", "total_heating"]].sum().reindex(
|
| 376 |
+
["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
|
| 377 |
+
)
|
| 378 |
+
fig_monthly = go.Figure(data=[
|
| 379 |
+
go.Bar(name="Cooling Load (kWh)", x=monthly_loads.index, y=monthly_loads["total_cooling"]),
|
| 380 |
+
go.Bar(name="Heating Load (kWh)", x=monthly_loads.index, y=monthly_loads["total_heating"])
|
| 381 |
+
])
|
| 382 |
+
fig_monthly.update_layout(
|
| 383 |
+
title="Monthly Heating and Cooling Loads",
|
| 384 |
+
xaxis_title="Month",
|
| 385 |
+
yaxis_title="Load (kWh)",
|
| 386 |
+
barmode="group",
|
| 387 |
+
width=800,
|
| 388 |
+
height=400
|
| 389 |
+
)
|
| 390 |
+
st.plotly_chart(fig_monthly, use_container_width=True)
|
| 391 |
+
|
| 392 |
+
# Detailed Load Summary Table
|
| 393 |
+
st.subheader("Load Summary")
|
| 394 |
+
summary_row = {
|
| 395 |
+
"hour": "Total",
|
| 396 |
+
"month_name": "",
|
| 397 |
+
"conduction_cooling": df["conduction_cooling"].sum(),
|
| 398 |
+
"conduction_heating": df["conduction_heating"].sum(),
|
| 399 |
+
"solar": df["solar"].sum(),
|
| 400 |
+
"internal": df["internal"].sum(),
|
| 401 |
+
"ventilation_cooling": df["ventilation_cooling"].sum(),
|
| 402 |
+
"ventilation_heating": df["ventilation_heating"].sum(),
|
| 403 |
+
"infiltration_cooling": df["infiltration_cooling"].sum(),
|
| 404 |
+
"infiltration_heating": df["infiltration_heating"].sum(),
|
| 405 |
+
"total_cooling": df["total_cooling"].sum(),
|
| 406 |
+
"total_heating": df["total_heating"].sum()
|
| 407 |
+
}
|
| 408 |
+
display_df = df[["hour", "month_name", "conduction_cooling", "conduction_heating", "solar",
|
| 409 |
+
"internal", "ventilation_cooling", "ventilation_heating",
|
| 410 |
+
"infiltration_cooling", "infiltration_heating", "total_cooling", "total_heating"]]
|
| 411 |
+
display_df = pd.concat([display_df, pd.DataFrame([summary_row])], ignore_index=True)
|
| 412 |
+
st.dataframe(display_df.rename(columns={"month_name": "Month"}), use_container_width=True)
|