mabuseif commited on
Commit
4dde00e
·
verified ·
1 Parent(s): 73416f0

Update app/hvac_loads.py

Browse files
Files changed (1) hide show
  1. app/hvac_loads.py +93 -145
app/hvac_loads.py CHANGED
@@ -801,15 +801,7 @@ 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
-
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,135 +811,41 @@ def make_pie(data: Dict[str, float], title: str) -> px.pie:
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,50 +854,87 @@ def display_hvac_results_ui(loads: List[Dict[str, Any]], run_id: str = "default"
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,6 +946,7 @@ def display_hvac_loads_page():
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,6 +957,7 @@ def display_hvac_loads_page():
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,7 +973,7 @@ def display_hvac_loads_page():
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,10 +1200,21 @@ def display_hvac_loads_page():
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)
 
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
  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
  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
  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
  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
  "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
  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)