mabuseif commited on
Commit
73416f0
·
verified ·
1 Parent(s): 70e6fe9

Update app/hvac_loads.py

Browse files
Files changed (1) hide show
  1. app/hvac_loads.py +145 -93
app/hvac_loads.py CHANGED
@@ -801,7 +801,15 @@ class TFMCalculations:
801
  return final_loads
802
 
803
  def make_pie(data: Dict[str, float], title: str) -> px.pie:
804
- """Create a Plotly pie chart from a dictionary of values."""
 
 
 
 
 
 
 
 
805
  fig = px.pie(
806
  names=list(data.keys()),
807
  values=list(data.values()),
@@ -811,41 +819,135 @@ def make_pie(data: Dict[str, float], title: str) -> px.pie:
811
  fig.update_traces(textinfo='percent+label', hoverinfo='label+percent+value')
812
  return fig
813
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
814
  def display_hvac_results_ui(loads: List[Dict[str, Any]], run_id: str = "default"):
815
- """Display HVAC load results with enhanced UI elements in a two-column format."""
 
 
 
 
 
816
  st.subheader("HVAC Load Results")
817
 
 
 
 
818
  # First Row: Equipment Sizing (Two Columns)
819
  col1, col2 = st.columns(2)
820
  with col1:
821
  st.subheader("Cooling Equipment Sizing")
822
- cooling_loads = [load for load in loads if load["total_cooling"] > 0]
823
- peak_cooling = max(cooling_loads, key=lambda x: x["total_cooling"]) if cooling_loads else None
824
- if peak_cooling:
825
- st.write(f"**Peak Cooling Load**: {peak_cooling['total_cooling']:.2f} kW")
826
- st.write(f"Occurred on: {peak_cooling['month']}/{peak_cooling['day']} at {peak_cooling['hour']}:00")
827
  else:
828
  st.write("**Peak Cooling Load**: 0.00 kW")
829
 
830
  with col2:
831
  st.subheader("Heating Equipment Sizing")
832
- heating_loads = [load for load in loads if load["total_heating"] > 0]
833
- peak_heating = max(heating_loads, key=lambda x: x["total_heating"]) if heating_loads else None
834
- if peak_heating:
835
- st.write(f"**Peak Heating Load**: {peak_heating['total_heating']:.2f} kW")
836
- st.write(f"Occurred on: {peak_heating['month']}/{peak_heating['day']} at {peak_heating['hour']}:00")
837
  else:
838
  st.write("**Peak Heating Load**: 0.00 kW")
839
 
840
  # Second Row: Monthly Loads Graph (Single Column)
841
  st.subheader("Monthly Heating and Cooling Load")
842
- monthly_df = pd.DataFrame(loads).groupby("month").agg({
843
- "total_cooling": "sum",
844
- "total_heating": "sum"
845
- }).reset_index()
846
- monthly_df = monthly_df.rename(columns={"total_cooling": "Cooling", "total_heating": "Heating"})
847
  fig = px.bar(
848
- monthly_df,
849
  x="month",
850
  y=["Cooling", "Heating"],
851
  barmode="group",
@@ -854,87 +956,50 @@ def display_hvac_results_ui(loads: List[Dict[str, Any]], run_id: str = "default"
854
  fig.update_xaxes(title="Month", tickvals=list(range(1, 13)))
855
  fig.update_yaxes(title="Load (kW)")
856
  st.plotly_chart(fig, use_container_width=True)
857
- st.session_state.project_data["hvac_loads"]["monthly_summary"] = monthly_df.to_dict()
858
 
859
  # Third Row: Load Breakdown (Two Columns)
860
  col3, col4 = st.columns(2)
861
  with col3:
862
  st.subheader("Cooling Load Breakdown")
863
- cooling_breakdown = {
864
- "Conduction": sum(load["conduction_cooling"] for load in cooling_loads),
865
- "Solar Gains": sum(load["solar"] for load in cooling_loads),
866
- "Internal": sum(load["internal"] for load in cooling_loads),
867
- "Ventilation": sum(load["ventilation_cooling"] for load in cooling_loads),
868
- "Infiltration": sum(load["infiltration_cooling"] for load in cooling_loads)
869
- }
870
- cooling_pie = make_pie({k: v for k, v in cooling_breakdown.items() if v > 0}, "Cooling Load Components")
871
  st.plotly_chart(cooling_pie)
872
- st.session_state.project_data["hvac_loads"]["cooling"]["charts"]["pie_by_component"] = cooling_breakdown
873
 
874
  with col4:
875
  st.subheader("Heating Load Breakdown")
876
- heating_breakdown = {
877
- "Conduction": sum(load["conduction_heating"] for load in heating_loads),
878
- "Ventilation": sum(load["ventilation_heating"] for load in heating_loads),
879
- "Infiltration": sum(load["infiltration_heating"] for load in heating_loads)
880
- }
881
- heating_pie = make_pie({k: v for k, v in heating_breakdown.items() if v > 0}, "Heating Load Components")
882
  st.plotly_chart(heating_pie)
883
- st.session_state.project_data["hvac_loads"]["heating"]["charts"]["pie_by_component"] = heating_breakdown
884
 
885
  # Fourth Row: Heat Gain by Orientation (Two Columns)
886
  col5, col6 = st.columns(2)
887
  with col5:
888
  st.subheader("Cooling Heat Gain by Orientation")
889
- orientation_solar = defaultdict(float)
890
- orientation_conduction = defaultdict(float)
891
- for load in cooling_loads:
892
- for key, value in load["solar_by_orientation"].items():
893
- orientation_solar[key] += value
894
- for key, value in load["conduction_by_orientation"].items():
895
- orientation_conduction[key] += value
896
- orientation_breakdown = {k: orientation_solar[k] + orientation_conduction[k] for k in set(orientation_solar) | set(orientation_conduction)}
897
- orientation_pie = make_pie({k: v for k, v in orientation_breakdown.items() if v > 0}, "Cooling Heat Gain by Orientation")
898
  st.plotly_chart(orientation_pie)
899
- st.session_state.project_data["hvac_loads"]["cooling"]["charts"]["pie_by_orientation"] = orientation_breakdown
900
 
901
  with col6:
902
  st.subheader("Heating Heat Gain by Orientation")
903
- orientation_conduction = defaultdict(float)
904
- for load in heating_loads:
905
- for key, value in load["conduction_by_orientation"].items():
906
- orientation_conduction[key] += value
907
- orientation_breakdown = {k: v for k, v in orientation_conduction.items() if v > 0}
908
- orientation_pie = make_pie(orientation_breakdown, "Heating Heat Gain by Orientation")
909
  st.plotly_chart(orientation_pie)
910
- st.session_state.project_data["hvac_loads"]["heating"]["charts"]["pie_by_orientation"] = orientation_breakdown
911
 
912
  # Fifth Row: Explore Hourly Loads (Single Column)
913
  st.subheader("Explore Hourly Loads")
914
- df = pd.DataFrame(loads)
915
- # Flatten orientation-based loads
916
- unique_orientations = set()
917
- for load in loads:
918
- unique_orientations.update(load["solar_by_orientation"].keys())
919
- unique_orientations.update(load["conduction_by_orientation"].keys())
920
 
921
- columns = [
922
- "month", "day", "hour",
923
- "total_cooling", "total_heating",
924
- "conduction_cooling", "solar", "internal",
925
- "ventilation_cooling", "infiltration_cooling",
926
- "ventilation_heating", "infiltration_heating"
927
- ]
928
- for orient in sorted(unique_orientations):
929
- df[f"solar_{orient}"] = df["solar_by_orientation"].apply(lambda x: x.get(orient, 0.0))
930
- df[f"conduction_{orient}"] = df["conduction_by_orientation"].apply(lambda x: x.get(orient, 0.0))
931
- columns.extend([f"solar_{orient}", f"conduction_{orient}"])
932
-
933
- st.dataframe(df[columns])
934
-
935
- # CSV Export
936
- csv = df[columns].to_csv(index=False)
937
- st.download_button("Download Hourly Summary as CSV", data=csv, file_name="hourly_loads.csv")
938
 
939
  def display_hvac_loads_page():
940
  """
@@ -946,7 +1011,6 @@ def display_hvac_loads_page():
946
  st.markdown("Configure and calculate HVAC loads for the building.")
947
 
948
  # Generate a unique run ID for this session
949
- import uuid
950
  run_id = str(uuid.uuid4())
951
 
952
  # Check for existing results
@@ -957,7 +1021,6 @@ def display_hvac_loads_page():
957
  heating_loads = st.session_state.project_data["hvac_loads"]["heating"].get("hourly", [])
958
  loads.extend(cooling_loads)
959
  loads.extend(heating_loads)
960
- # Sort loads by month, day, hour to ensure consistent display
961
  loads = sorted(loads, key=lambda x: (x["month"], x["day"], x["hour"]))
962
  if loads:
963
  st.info("Displaying previously calculated HVAC load results.")
@@ -973,7 +1036,7 @@ def display_hvac_loads_page():
973
  "Latitude": climate_data.get("location", {}).get("latitude", 0.0),
974
  "Longitude": climate_data.get("location", {}).get("longitude", 0.0),
975
  "Elevation": climate_data.get("location", {}).get("elevation", 0.0),
976
- "Time Zone": climate_data.get("location", {}).get("timezone", "UTC"),
977
  "Ground Reflectivity": climate_data.get("ground_reflectivity", 0.2)
978
  }
979
 
@@ -1200,21 +1263,10 @@ def display_hvac_loads_page():
1200
  st.session_state.project_data["hvac_loads"]["cooling"]["charts"] = {}
1201
  st.session_state.project_data["hvac_loads"]["heating"]["charts"] = {}
1202
 
1203
- # Store breakdown
1204
- cooling_breakdown = {
1205
- "conduction": sum(load["conduction_cooling"] for load in cooling_loads),
1206
- "solar": sum(load["solar"] for load in cooling_loads if load["total_cooling"] > 0),
1207
- "internal": sum(load["internal"] for load in cooling_loads if load["total_cooling"] > 0),
1208
- "ventilation": sum(load["ventilation_cooling"] for load in cooling_loads),
1209
- "infiltration": sum(load["infiltration_cooling"] for load in cooling_loads)
1210
- }
1211
- heating_breakdown = {
1212
- "conduction": sum(load["conduction_heating"] for load in heating_loads),
1213
- "ventilation": sum(load["ventilation_heating"] for load in heating_loads),
1214
- "infiltration": sum(load["infiltration_heating"] for load in heating_loads)
1215
- }
1216
- st.session_state.project_data["hvac_loads"]["cooling"]["breakdown"] = cooling_breakdown
1217
- st.session_state.project_data["hvac_loads"]["heating"]["breakdown"] = heating_breakdown
1218
 
1219
  # Display Results UI with unique run_id
1220
  display_hvac_results_ui(loads, run_id=run_id)
 
801
  return final_loads
802
 
803
  def make_pie(data: Dict[str, float], title: str) -> px.pie:
804
+ """Create a Plotly pie chart from a dictionary of values.
805
+
806
+ Args:
807
+ data: Dictionary with keys as labels and values as magnitudes.
808
+ title: Title for the pie chart.
809
+
810
+ Returns:
811
+ Plotly pie chart figure.
812
+ """
813
  fig = px.pie(
814
  names=list(data.keys()),
815
  values=list(data.values()),
 
819
  fig.update_traces(textinfo='percent+label', hoverinfo='label+percent+value')
820
  return fig
821
 
822
+ @st.cache_data
823
+ def compute_chart_data(_loads: List[Dict[str, Any]]) -> Dict[str, Any]:
824
+ """Compute and cache chart and breakdown data to avoid recalculation.
825
+
826
+ Args:
827
+ _loads: List of dictionaries containing HVAC load data.
828
+
829
+ Returns:
830
+ Dictionary containing precomputed data for charts and breakdowns.
831
+ """
832
+ cooling_loads = [load for load in _loads if load["total_cooling"] > 0]
833
+ heating_loads = [load for load in _loads if load["total_heating"] > 0]
834
+
835
+ # Monthly summary
836
+ monthly_df = pd.DataFrame(_loads).groupby("month").agg({
837
+ "total_cooling": "sum",
838
+ "total_heating": "sum"
839
+ }).reset_index()
840
+ monthly_df = monthly_df.rename(columns={"total_cooling": "Cooling", "total_heating": "Heating"})
841
+
842
+ # Cooling breakdown
843
+ cooling_breakdown = {
844
+ "Conduction": sum(load["conduction_cooling"] for load in cooling_loads),
845
+ "Solar Gains": sum(load["solar"] for load in cooling_loads),
846
+ "Internal": sum(load["internal"] for load in cooling_loads),
847
+ "Ventilation": sum(load["ventilation_cooling"] for load in cooling_loads),
848
+ "Infiltration": sum(load["infiltration_cooling"] for load in cooling_loads)
849
+ }
850
+
851
+ # Heating breakdown
852
+ heating_breakdown = {
853
+ "Conduction": sum(load["conduction_heating"] for load in heating_loads),
854
+ "Ventilation": sum(load["ventilation_heating"] for load in heating_loads),
855
+ "Infiltration": sum(load["infiltration_heating"] for load in heating_loads)
856
+ }
857
+
858
+ # Orientation breakdowns
859
+ orientation_solar = defaultdict(float)
860
+ orientation_conduction_cooling = defaultdict(float)
861
+ orientation_conduction_heating = defaultdict(float)
862
+ for load in cooling_loads:
863
+ for key, value in load["solar_by_orientation"].items():
864
+ orientation_solar[key] += value
865
+ for key, value in load["conduction_by_orientation"].items():
866
+ orientation_conduction_cooling[key] += value
867
+ for load in heating_loads:
868
+ for key, value in load["conduction_by_orientation"].items():
869
+ orientation_conduction_heating[key] += value
870
+ cooling_orientation_breakdown = {k: orientation_solar[k] + orientation_conduction_cooling[k] for k in set(orientation_solar) | set(orientation_conduction_cooling)}
871
+ heating_orientation_breakdown = {k: v for k, v in orientation_conduction_heating.items() if v > 0}
872
+
873
+ # Peak loads
874
+ peak_cooling = max(cooling_loads, key=lambda x: x["total_cooling"]) if cooling_loads else None
875
+ peak_heating = max(heating_loads, key=lambda x: x["total_heating"]) if heating_loads else None
876
+
877
+ return {
878
+ "monthly_df": monthly_df,
879
+ "cooling_breakdown": cooling_breakdown,
880
+ "heating_breakdown": heating_breakdown,
881
+ "cooling_orientation_breakdown": cooling_orientation_breakdown,
882
+ "heating_orientation_breakdown": heating_orientation_breakdown,
883
+ "peak_cooling": peak_cooling,
884
+ "peak_heating": peak_heating
885
+ }
886
+
887
+ @st.cache_data
888
+ def compute_table_data(_loads: List[Dict[str, Any]]) -> pd.DataFrame:
889
+ """Compute and cache DataFrame for hourly loads table.
890
+
891
+ Args:
892
+ _loads: List of dictionaries containing HVAC load data.
893
+
894
+ Returns:
895
+ Pandas DataFrame with hourly load data and orientation columns.
896
+ """
897
+ df = pd.DataFrame(_loads)
898
+ unique_orientations = set()
899
+ for load in _loads:
900
+ unique_orientations.update(load["solar_by_orientation"].keys())
901
+ unique_orientations.update(load["conduction_by_orientation"].keys())
902
+
903
+ columns = [
904
+ "month", "day", "hour",
905
+ "total_cooling", "total_heating",
906
+ "conduction_cooling", "solar", "internal",
907
+ "ventilation_cooling", "infiltration_cooling",
908
+ "ventilation_heating", "infiltration_heating"
909
+ ]
910
+ for orient in sorted(unique_orientations):
911
+ df[f"solar_{orient}"] = df["solar_by_orientation"].apply(lambda x: x.get(orient, 0.0))
912
+ df[f"conduction_{orient}"] = df["conduction_by_orientation"].apply(lambda x: x.get(orient, 0.0))
913
+ columns.extend([f"solar_{orient}", f"conduction_{orient}"])
914
+
915
+ return df[columns]
916
+
917
  def display_hvac_results_ui(loads: List[Dict[str, Any]], run_id: str = "default"):
918
+ """Display HVAC load results with enhanced UI elements in a two-column format.
919
+
920
+ Args:
921
+ loads: List of dictionaries containing HVAC load data.
922
+ run_id: Unique identifier for the simulation run to ensure unique widget keys.
923
+ """
924
  st.subheader("HVAC Load Results")
925
 
926
+ # Compute cached data
927
+ chart_data = compute_chart_data(loads)
928
+
929
  # First Row: Equipment Sizing (Two Columns)
930
  col1, col2 = st.columns(2)
931
  with col1:
932
  st.subheader("Cooling Equipment Sizing")
933
+ if chart_data["peak_cooling"]:
934
+ st.write(f"**Peak Cooling Load**: {chart_data['peak_cooling']['total_cooling']:.2f} kW")
935
+ st.write(f"Occurred on: {chart_data['peak_cooling']['month']}/{chart_data['peak_cooling']['day']} at {chart_data['peak_cooling']['hour']}:00")
 
 
936
  else:
937
  st.write("**Peak Cooling Load**: 0.00 kW")
938
 
939
  with col2:
940
  st.subheader("Heating Equipment Sizing")
941
+ if chart_data["peak_heating"]:
942
+ st.write(f"**Peak Heating Load**: {chart_data['peak_heating']['total_heating']:.2f} kW")
943
+ st.write(f"Occurred on: {chart_data['peak_heating']['month']}/{chart_data['peak_heating']['day']} at {chart_data['peak_cooling']['hour']}:00")
 
 
944
  else:
945
  st.write("**Peak Heating Load**: 0.00 kW")
946
 
947
  # Second Row: Monthly Loads Graph (Single Column)
948
  st.subheader("Monthly Heating and Cooling Load")
 
 
 
 
 
949
  fig = px.bar(
950
+ chart_data["monthly_df"],
951
  x="month",
952
  y=["Cooling", "Heating"],
953
  barmode="group",
 
956
  fig.update_xaxes(title="Month", tickvals=list(range(1, 13)))
957
  fig.update_yaxes(title="Load (kW)")
958
  st.plotly_chart(fig, use_container_width=True)
959
+ st.session_state.project_data["hvac_loads"]["monthly_summary"] = chart_data["monthly_df"].to_dict()
960
 
961
  # Third Row: Load Breakdown (Two Columns)
962
  col3, col4 = st.columns(2)
963
  with col3:
964
  st.subheader("Cooling Load Breakdown")
965
+ cooling_pie = make_pie({k: v for k, v in chart_data["cooling_breakdown"].items() if v > 0}, "Cooling Load Components")
 
 
 
 
 
 
 
966
  st.plotly_chart(cooling_pie)
967
+ st.session_state.project_data["hvac_loads"]["cooling"]["charts"]["pie_by_component"] = chart_data["cooling_breakdown"]
968
 
969
  with col4:
970
  st.subheader("Heating Load Breakdown")
971
+ heating_pie = make_pie({k: v for k, v in chart_data["heating_breakdown"].items() if v > 0}, "Heating Load Components")
 
 
 
 
 
972
  st.plotly_chart(heating_pie)
973
+ st.session_state.project_data["hvac_loads"]["heating"]["charts"]["pie_by_component"] = chart_data["heating_breakdown"]
974
 
975
  # Fourth Row: Heat Gain by Orientation (Two Columns)
976
  col5, col6 = st.columns(2)
977
  with col5:
978
  st.subheader("Cooling Heat Gain by Orientation")
979
+ orientation_pie = make_pie({k: v for k, v in chart_data["cooling_orientation_breakdown"].items() if v > 0}, "Cooling Heat Gain by Orientation")
 
 
 
 
 
 
 
 
980
  st.plotly_chart(orientation_pie)
981
+ st.session_state.project_data["hvac_loads"]["cooling"]["charts"]["pie_by_orientation"] = chart_data["cooling_orientation_breakdown"]
982
 
983
  with col6:
984
  st.subheader("Heating Heat Gain by Orientation")
985
+ orientation_pie = make_pie(chart_data["heating_orientation_breakdown"], "Heating Heat Gain by Orientation")
 
 
 
 
 
986
  st.plotly_chart(orientation_pie)
987
+ st.session_state.project_data["hvac_loads"]["heating"]["charts"]["pie_by_orientation"] = chart_data["heating_orientation_breakdown"]
988
 
989
  # Fifth Row: Explore Hourly Loads (Single Column)
990
  st.subheader("Explore Hourly Loads")
991
+ table_df = compute_table_data(loads)
992
+ # Limit to first 100 rows to improve performance
993
+ st.dataframe(table_df.head(100))
 
 
 
994
 
995
+ # CSV Export with unique key
996
+ csv = table_df.to_csv(index=False)
997
+ st.download_button(
998
+ label="Download Hourly Summary as CSV",
999
+ data=csv,
1000
+ file_name="hourly_loads.csv",
1001
+ key=f"download_hvac_loads_{run_id}"
1002
+ )
 
 
 
 
 
 
 
 
 
1003
 
1004
  def display_hvac_loads_page():
1005
  """
 
1011
  st.markdown("Configure and calculate HVAC loads for the building.")
1012
 
1013
  # Generate a unique run ID for this session
 
1014
  run_id = str(uuid.uuid4())
1015
 
1016
  # Check for existing results
 
1021
  heating_loads = st.session_state.project_data["hvac_loads"]["heating"].get("hourly", [])
1022
  loads.extend(cooling_loads)
1023
  loads.extend(heating_loads)
 
1024
  loads = sorted(loads, key=lambda x: (x["month"], x["day"], x["hour"]))
1025
  if loads:
1026
  st.info("Displaying previously calculated HVAC load results.")
 
1036
  "Latitude": climate_data.get("location", {}).get("latitude", 0.0),
1037
  "Longitude": climate_data.get("location", {}).get("longitude", 0.0),
1038
  "Elevation": climate_data.get("location", {}).get("elevation", 0.0),
1039
+ "Time Zone": climate_data.get("location", {}).get("timezone", 0.0),
1040
  "Ground Reflectivity": climate_data.get("ground_reflectivity", 0.2)
1041
  }
1042
 
 
1263
  st.session_state.project_data["hvac_loads"]["cooling"]["charts"] = {}
1264
  st.session_state.project_data["hvac_loads"]["heating"]["charts"] = {}
1265
 
1266
+ # Store breakdown (cached function handles this, but store for consistency)
1267
+ chart_data = compute_chart_data(loads)
1268
+ st.session_state.project_data["hvac_loads"]["cooling"]["breakdown"] = chart_data["cooling_breakdown"]
1269
+ st.session_state.project_data["hvac_loads"]["heating"]["breakdown"] = chart_data["heating_breakdown"]
 
 
 
 
 
 
 
 
 
 
 
1270
 
1271
  # Display Results UI with unique run_id
1272
  display_hvac_results_ui(loads, run_id=run_id)