Spaces:
Sleeping
Sleeping
Update app/hvac_loads.py
Browse files- app/hvac_loads.py +84 -45
app/hvac_loads.py
CHANGED
@@ -810,42 +810,53 @@ def make_pie(data: Dict[str, float], title: str) -> px.pie:
|
|
810 |
return fig
|
811 |
|
812 |
def display_hvac_results_ui(loads: List[Dict[str, Any]]):
|
813 |
-
"""Display HVAC load results with enhanced UI elements."""
|
814 |
st.subheader("HVAC Load Results")
|
815 |
|
816 |
-
#
|
817 |
-
st.subheader("Monthly Heating and Cooling Load")
|
818 |
-
monthly_df = pd.DataFrame(loads).groupby("month").agg({
|
819 |
-
"total_cooling": "sum",
|
820 |
-
"total_heating": "sum"
|
821 |
-
}).reset_index()
|
822 |
-
monthly_df = monthly_df.rename(columns={"total_cooling": "Cooling", "total_heating": "Heating"})
|
823 |
-
fig = px.line(monthly_df, x="month", y=["Cooling", "Heating"], markers=True, title="Monthly Load Summary")
|
824 |
-
fig.update_xaxes(title="Month", tickvals=list(range(1, 13)))
|
825 |
-
fig.update_yaxes(title="Load (kW)")
|
826 |
-
st.plotly_chart(fig)
|
827 |
-
st.session_state.project_data["hvac_loads"]["monthly_summary"] = monthly_df.to_dict()
|
828 |
-
|
829 |
-
# Two-Column Layout for Equipment Sizing and Pie Charts
|
830 |
col1, col2 = st.columns(2)
|
831 |
with col1:
|
832 |
-
st.subheader("Equipment Sizing")
|
833 |
cooling_loads = [load for load in loads if load["total_cooling"] > 0]
|
834 |
-
heating_loads = [load for load in loads if load["total_heating"] > 0]
|
835 |
peak_cooling = max(cooling_loads, key=lambda x: x["total_cooling"]) if cooling_loads else None
|
836 |
-
peak_heating = max(heating_loads, key=lambda x: x["total_heating"]) if heating_loads else None
|
837 |
if peak_cooling:
|
838 |
st.write(f"**Peak Cooling Load**: {peak_cooling['total_cooling']:.2f} kW")
|
839 |
st.write(f"Occurred on: {peak_cooling['month']}/{peak_cooling['day']} at {peak_cooling['hour']}:00")
|
840 |
else:
|
841 |
st.write("**Peak Cooling Load**: 0.00 kW")
|
|
|
|
|
|
|
|
|
|
|
842 |
if peak_heating:
|
843 |
st.write(f"**Peak Heating Load**: {peak_heating['total_heating']:.2f} kW")
|
844 |
st.write(f"Occurred on: {peak_heating['month']}/{peak_heating['day']} at {peak_heating['hour']}:00")
|
845 |
else:
|
846 |
st.write("**Peak Heating Load**: 0.00 kW")
|
847 |
|
848 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
849 |
st.subheader("Cooling Load Breakdown")
|
850 |
cooling_breakdown = {
|
851 |
"Conduction": sum(load["conduction_cooling"] for load in cooling_loads),
|
@@ -854,18 +865,11 @@ def display_hvac_results_ui(loads: List[Dict[str, Any]]):
|
|
854 |
"Ventilation": sum(load["ventilation_cooling"] for load in cooling_loads),
|
855 |
"Infiltration": sum(load["infiltration_cooling"] for load in cooling_loads)
|
856 |
}
|
857 |
-
st.markdown("**Conduction**", help="Heat transfer through building envelope materials.")
|
858 |
-
st.markdown("**Solar Gains**", help="Radiative heat gains from sun through surfaces and windows.")
|
859 |
-
st.markdown("**Ventilation**", help="Loads due to outdoor air entering through ventilation systems.")
|
860 |
-
st.markdown("**Infiltration**", help="Loads due to unintended air leakage through the building envelope.")
|
861 |
-
st.markdown("**Internal**", help="Heat gains from occupants, lighting, and equipment inside the building.")
|
862 |
cooling_pie = make_pie({k: v for k, v in cooling_breakdown.items() if v > 0}, "Cooling Load Components")
|
863 |
st.plotly_chart(cooling_pie)
|
864 |
st.session_state.project_data["hvac_loads"]["cooling"]["charts"]["pie_by_component"] = cooling_breakdown
|
865 |
|
866 |
-
|
867 |
-
col3, col4 = st.columns(2)
|
868 |
-
with col3:
|
869 |
st.subheader("Heating Load Breakdown")
|
870 |
heating_breakdown = {
|
871 |
"Conduction": sum(load["conduction_heating"] for load in heating_loads),
|
@@ -876,8 +880,10 @@ def display_hvac_results_ui(loads: List[Dict[str, Any]]):
|
|
876 |
st.plotly_chart(heating_pie)
|
877 |
st.session_state.project_data["hvac_loads"]["heating"]["charts"]["pie_by_component"] = heating_breakdown
|
878 |
|
879 |
-
|
880 |
-
|
|
|
|
|
881 |
orientation_solar = defaultdict(float)
|
882 |
orientation_conduction = defaultdict(float)
|
883 |
for load in cooling_loads:
|
@@ -886,38 +892,71 @@ def display_hvac_results_ui(loads: List[Dict[str, Any]]):
|
|
886 |
for key, value in load["conduction_by_orientation"].items():
|
887 |
orientation_conduction[key] += value
|
888 |
orientation_breakdown = {k: orientation_solar[k] + orientation_conduction[k] for k in set(orientation_solar) | set(orientation_conduction)}
|
889 |
-
orientation_pie = make_pie({k: v for k, v in orientation_breakdown.items() if v > 0}, "Cooling
|
890 |
st.plotly_chart(orientation_pie)
|
891 |
st.session_state.project_data["hvac_loads"]["cooling"]["charts"]["pie_by_orientation"] = orientation_breakdown
|
892 |
|
893 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
894 |
st.subheader("Explore Hourly Loads")
|
895 |
df = pd.DataFrame(loads)
|
896 |
-
|
897 |
-
|
898 |
-
|
899 |
-
|
900 |
-
|
901 |
-
|
902 |
-
|
903 |
-
"
|
904 |
-
"
|
905 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
906 |
|
907 |
# CSV Export
|
908 |
-
csv =
|
909 |
-
st.download_button("Download Hourly Summary as CSV", data=csv, file_name=
|
910 |
|
911 |
def display_hvac_loads_page():
|
912 |
"""
|
913 |
Display the HVAC Loads page in the Streamlit application.
|
914 |
-
|
915 |
-
calculates HVAC loads using TFMCalculations, and displays results.
|
916 |
"""
|
917 |
try:
|
918 |
st.header("HVAC Loads")
|
919 |
st.markdown("Configure and calculate HVAC loads for the building.")
|
920 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
921 |
# Location Information
|
922 |
st.subheader("Location Information")
|
923 |
climate_data = st.session_state.project_data["climate_data"]
|
|
|
810 |
return fig
|
811 |
|
812 |
def display_hvac_results_ui(loads: List[Dict[str, Any]]):
|
813 |
+
"""Display HVAC load results with enhanced UI elements in a two-column format."""
|
814 |
st.subheader("HVAC Load Results")
|
815 |
|
816 |
+
# First Row: Equipment Sizing (Two Columns)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
817 |
col1, col2 = st.columns(2)
|
818 |
with col1:
|
819 |
+
st.subheader("Cooling Equipment Sizing")
|
820 |
cooling_loads = [load for load in loads if load["total_cooling"] > 0]
|
|
|
821 |
peak_cooling = max(cooling_loads, key=lambda x: x["total_cooling"]) if cooling_loads else None
|
|
|
822 |
if peak_cooling:
|
823 |
st.write(f"**Peak Cooling Load**: {peak_cooling['total_cooling']:.2f} kW")
|
824 |
st.write(f"Occurred on: {peak_cooling['month']}/{peak_cooling['day']} at {peak_cooling['hour']}:00")
|
825 |
else:
|
826 |
st.write("**Peak Cooling Load**: 0.00 kW")
|
827 |
+
|
828 |
+
with col2:
|
829 |
+
st.subheader("Heating Equipment Sizing")
|
830 |
+
heating_loads = [load for load in loads if load["total_heating"] > 0]
|
831 |
+
peak_heating = max(heating_loads, key=lambda x: x["total_heating"]) if heating_loads else None
|
832 |
if peak_heating:
|
833 |
st.write(f"**Peak Heating Load**: {peak_heating['total_heating']:.2f} kW")
|
834 |
st.write(f"Occurred on: {peak_heating['month']}/{peak_heating['day']} at {peak_heating['hour']}:00")
|
835 |
else:
|
836 |
st.write("**Peak Heating Load**: 0.00 kW")
|
837 |
|
838 |
+
# Second Row: Monthly Loads Graph (Single Column)
|
839 |
+
st.subheader("Monthly Heating and Cooling Load")
|
840 |
+
monthly_df = pd.DataFrame(loads).groupby("month").agg({
|
841 |
+
"total_cooling": "sum",
|
842 |
+
"total_heating": "sum"
|
843 |
+
}).reset_index()
|
844 |
+
monthly_df = monthly_df.rename(columns={"total_cooling": "Cooling", "total_heating": "Heating"})
|
845 |
+
fig = px.bar(
|
846 |
+
monthly_df,
|
847 |
+
x="month",
|
848 |
+
y=["Cooling", "Heating"],
|
849 |
+
barmode="group",
|
850 |
+
title="Monthly Load Summary"
|
851 |
+
)
|
852 |
+
fig.update_xaxes(title="Month", tickvals=list(range(1, 13)))
|
853 |
+
fig.update_yaxes(title="Load (kW)")
|
854 |
+
st.plotly_chart(fig, use_container_width=True)
|
855 |
+
st.session_state.project_data["hvac_loads"]["monthly_summary"] = monthly_df.to_dict()
|
856 |
+
|
857 |
+
# Third Row: Load Breakdown (Two Columns)
|
858 |
+
col3, col4 = st.columns(2)
|
859 |
+
with col3:
|
860 |
st.subheader("Cooling Load Breakdown")
|
861 |
cooling_breakdown = {
|
862 |
"Conduction": sum(load["conduction_cooling"] for load in cooling_loads),
|
|
|
865 |
"Ventilation": sum(load["ventilation_cooling"] for load in cooling_loads),
|
866 |
"Infiltration": sum(load["infiltration_cooling"] for load in cooling_loads)
|
867 |
}
|
|
|
|
|
|
|
|
|
|
|
868 |
cooling_pie = make_pie({k: v for k, v in cooling_breakdown.items() if v > 0}, "Cooling Load Components")
|
869 |
st.plotly_chart(cooling_pie)
|
870 |
st.session_state.project_data["hvac_loads"]["cooling"]["charts"]["pie_by_component"] = cooling_breakdown
|
871 |
|
872 |
+
with col4:
|
|
|
|
|
873 |
st.subheader("Heating Load Breakdown")
|
874 |
heating_breakdown = {
|
875 |
"Conduction": sum(load["conduction_heating"] for load in heating_loads),
|
|
|
880 |
st.plotly_chart(heating_pie)
|
881 |
st.session_state.project_data["hvac_loads"]["heating"]["charts"]["pie_by_component"] = heating_breakdown
|
882 |
|
883 |
+
# Fourth Row: Heat Gain by Orientation (Two Columns)
|
884 |
+
col5, col6 = st.columns(2)
|
885 |
+
with col5:
|
886 |
+
st.subheader("Cooling Heat Gain by Orientation")
|
887 |
orientation_solar = defaultdict(float)
|
888 |
orientation_conduction = defaultdict(float)
|
889 |
for load in cooling_loads:
|
|
|
892 |
for key, value in load["conduction_by_orientation"].items():
|
893 |
orientation_conduction[key] += value
|
894 |
orientation_breakdown = {k: orientation_solar[k] + orientation_conduction[k] for k in set(orientation_solar) | set(orientation_conduction)}
|
895 |
+
orientation_pie = make_pie({k: v for k, v in orientation_breakdown.items() if v > 0}, "Cooling Heat Gain by Orientation")
|
896 |
st.plotly_chart(orientation_pie)
|
897 |
st.session_state.project_data["hvac_loads"]["cooling"]["charts"]["pie_by_orientation"] = orientation_breakdown
|
898 |
|
899 |
+
with col6:
|
900 |
+
st.subheader("Heating Heat Gain by Orientation")
|
901 |
+
orientation_conduction = defaultdict(float)
|
902 |
+
for load in heating_loads:
|
903 |
+
for key, value in load["conduction_by_orientation"].items():
|
904 |
+
orientation_conduction[key] += value
|
905 |
+
orientation_breakdown = {k: v for k, v in orientation_conduction.items() if v > 0}
|
906 |
+
orientation_pie = make_pie(orientation_breakdown, "Heating Heat Gain by Orientation")
|
907 |
+
st.plotly_chart(orientation_pie)
|
908 |
+
st.session_state.project_data["hvac_loads"]["heating"]["charts"]["pie_by_orientation"] = orientation_breakdown
|
909 |
+
|
910 |
+
# Fifth Row: Explore Hourly Loads (Single Column)
|
911 |
st.subheader("Explore Hourly Loads")
|
912 |
df = pd.DataFrame(loads)
|
913 |
+
# Flatten orientation-based loads
|
914 |
+
unique_orientations = set()
|
915 |
+
for load in loads:
|
916 |
+
unique_orientations.update(load["solar_by_orientation"].keys())
|
917 |
+
unique_orientations.update(load["conduction_by_orientation"].keys())
|
918 |
+
|
919 |
+
columns = [
|
920 |
+
"month", "day", "hour",
|
921 |
+
"total_cooling", "total_heating",
|
922 |
+
"conduction_cooling", "solar", "internal",
|
923 |
+
"ventilation_cooling", "infiltration_cooling",
|
924 |
+
"ventilation_heating", "infiltration_heating"
|
925 |
+
]
|
926 |
+
for orient in sorted(unique_orientations):
|
927 |
+
df[f"solar_{orient}"] = df["solar_by_orientation"].apply(lambda x: x.get(orient, 0.0))
|
928 |
+
df[f"conduction_{orient}"] = df["conduction_by_orientation"].apply(lambda x: x.get(orient, 0.0))
|
929 |
+
columns.extend([f"solar_{orient}", f"conduction_{orient}"])
|
930 |
+
|
931 |
+
st.dataframe(df[columns])
|
932 |
|
933 |
# CSV Export
|
934 |
+
csv = df[columns].to_csv(index=False)
|
935 |
+
st.download_button("Download Hourly Summary as CSV", data=csv, file_name="hourly_loads.csv")
|
936 |
|
937 |
def display_hvac_loads_page():
|
938 |
"""
|
939 |
Display the HVAC Loads page in the Streamlit application.
|
940 |
+
Checks for existing results in session state before recalculating.
|
|
|
941 |
"""
|
942 |
try:
|
943 |
st.header("HVAC Loads")
|
944 |
st.markdown("Configure and calculate HVAC loads for the building.")
|
945 |
|
946 |
+
# Check for existing results
|
947 |
+
if (st.session_state.project_data.get("hvac_loads", {}).get("cooling", {}).get("hourly") or
|
948 |
+
st.session_state.project_data.get("hvac_loads", {}).get("heating", {}).get("hourly")):
|
949 |
+
loads = []
|
950 |
+
cooling_loads = st.session_state.project_data["hvac_loads"]["cooling"].get("hourly", [])
|
951 |
+
heating_loads = st.session_state.project_data["hvac_loads"]["heating"].get("hourly", [])
|
952 |
+
loads.extend(cooling_loads)
|
953 |
+
loads.extend(heating_loads)
|
954 |
+
# Sort loads by month, day, hour to ensure consistent display
|
955 |
+
loads = sorted(loads, key=lambda x: (x["month"], x["day"], x["hour"]))
|
956 |
+
if loads:
|
957 |
+
st.info("Displaying previously calculated HVAC load results.")
|
958 |
+
display_hvac_results_ui(loads)
|
959 |
+
|
960 |
# Location Information
|
961 |
st.subheader("Location Information")
|
962 |
climate_data = st.session_state.project_data["climate_data"]
|