mabuseif commited on
Commit
4845c4e
·
verified ·
1 Parent(s): 5350061

Update data/climate_data.py

Browse files
Files changed (1) hide show
  1. data/climate_data.py +111 -17
data/climate_data.py CHANGED
@@ -171,6 +171,8 @@ class ClimateLocation:
171
 
172
  # Store hourly data with enhanced fields, including solar angles and ground-reflected radiation
173
  self.hourly_data = []
 
 
174
  for i in range(len(months)):
175
  if np.isnan(months[i]) or np.isnan(days[i]) or np.isnan(hours[i]) or np.isnan(dry_bulb[i]):
176
  continue # Skip records with missing critical fields
@@ -180,18 +182,25 @@ class ClimateLocation:
180
  solar_time = self._calculate_solar_time(timestamp)
181
  solar_angles = self._calculate_solar_angles(timestamp)
182
 
183
- # Validate or fallback DNI/DHI
184
- dni = float(direct_normal_irradiance[i]) if not np.isnan(direct_normal_irradiance[i]) else self._calculate_clear_sky_irradiance(timestamp, 'dni')
185
- dhi = float(diffuse_horizontal_irradiance[i]) if not np.isnan(diffuse_horizontal_irradiance[i]) else self._calculate_clear_sky_irradiance(timestamp, 'dhi')
186
- if dni < 0:
187
- logger.warning(f"Negative DNI at {timestamp}, using clear-sky model: {dni}")
188
- dni = self._calculate_clear_sky_irradiance(timestamp, 'dni')
189
- if dhi < 0:
190
- logger.warning(f"Negative DHI at {timestamp}, using clear-sky model: {dhi}")
191
- dhi = self._calculate_clear_sky_irradiance(timestamp, 'dhi')
192
 
193
- # Calculate ground-reflected radiation
194
- ground_radiation = self._calculate_ground_reflected_radiation(dni, dhi, solar_angles['zenith'])
 
 
 
 
 
 
 
 
 
 
 
 
195
 
196
  record = {
197
  "month": int(months[i]),
@@ -211,6 +220,12 @@ class ClimateLocation:
211
  }
212
  self.hourly_data.append(record)
213
 
 
 
 
 
 
 
214
  if len(self.hourly_data) != 8760:
215
  st.warning(f"Hourly data has {len(self.hourly_data)} records instead of 8760. Some records may have been excluded due to missing data.")
216
 
@@ -487,6 +502,13 @@ class ClimateData:
487
  st.error(f"Validation failed: Invalid {season} angles: {angles}")
488
  return False
489
 
 
 
 
 
 
 
 
490
  return True
491
 
492
  @staticmethod
@@ -518,7 +540,7 @@ class ClimateData:
518
  return False
519
 
520
  def display_climate_input(self, session_state: Dict[str, Any]):
521
- """Display Streamlit interface for EPW upload and visualizations."""
522
  st.title("Climate Data Analysis")
523
 
524
  # Apply consistent styling
@@ -529,16 +551,64 @@ class ClimateData:
529
  st.warning("Clearing invalid climate data from session state.")
530
  del session_state["climate_data"]
531
 
532
- uploaded_file = st.file_uploader("Upload EPW File", type=["epw"])
 
 
533
 
534
  # Initialize location and epw_data for display
535
  location = None
536
  epw_data = None
537
 
538
- if uploaded_file:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
539
  try:
540
  # Process new EPW file
541
- epw_content = uploaded_file.read().decode("utf-8")
542
  epw_lines = epw_content.splitlines()
543
 
544
  # Parse header
@@ -608,7 +678,7 @@ class ClimateData:
608
 
609
  # Parse GROUND TEMPERATURES
610
  ground_temperatures = {}
611
- for sista in epw_lines:
612
  if line.startswith("GROUND TEMPERATURES"):
613
  parts = line.strip().split(',')
614
  try:
@@ -663,8 +733,17 @@ class ClimateData:
663
  climate_data_dict = location.to_dict()
664
  if not self.validate_climate_data(climate_data_dict):
665
  raise ValueError("Invalid climate data extracted from EPW file.")
 
 
 
666
  session_state["climate_data"] = climate_data_dict
667
  st.success("Climate data extracted from EPW file!")
 
 
 
 
 
 
668
 
669
  except Exception as e:
670
  st.error(f"Error processing EPW file: {str(e)}. Ensure it has 8760 hourly records and correct format.")
@@ -735,7 +814,7 @@ class ClimateData:
735
  self.plot_wind_rose(epw_data)
736
 
737
  else:
738
- st.info("No climate data available. Please upload an EPW file to proceed.")
739
 
740
  def display_design_conditions(self, location: ClimateLocation):
741
  """Display design conditions for HVAC calculations using styled HTML."""
@@ -798,6 +877,21 @@ class ClimateData:
798
  </div>
799
  """, unsafe_allow_html=True)
800
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
801
  # Ground Temperatures (Table)
802
  if location.ground_temperatures:
803
  st.markdown('<div class="markdown-text"><h3>Ground Temperatures</h3></div>', unsafe_allow_html=True)
 
171
 
172
  # Store hourly data with enhanced fields, including solar angles and ground-reflected radiation
173
  self.hourly_data = []
174
+ negative_dni_count = 0
175
+ negative_dhi_count = 0
176
  for i in range(len(months)):
177
  if np.isnan(months[i]) or np.isnan(days[i]) or np.isnan(hours[i]) or np.isnan(dry_bulb[i]):
178
  continue # Skip records with missing critical fields
 
182
  solar_time = self._calculate_solar_time(timestamp)
183
  solar_angles = self._calculate_solar_angles(timestamp)
184
 
185
+ # Initialize defaults for solar-related fields
186
+ dni = 0.0
187
+ dhi = 0.0
188
+ ground_radiation = 0.0
 
 
 
 
 
189
 
190
+ # Perform solar calculations only if sun is above horizon (zenith < 90°)
191
+ if solar_angles['zenith'] < 90:
192
+ # Validate or fallback DNI/DHI
193
+ dni = float(direct_normal_irradiance[i]) if not np.isnan(direct_normal_irradiance[i]) else self._calculate_clear_sky_irradiance(timestamp, 'dni')
194
+ dhi = float(diffuse_horizontal_irradiance[i]) if not np.isnan(diffuse_horizontal_irradiance[i]) else self._calculate_clear_sky_irradiance(timestamp, 'dhi')
195
+ if dni < 0:
196
+ negative_dni_count += 1
197
+ dni = self._calculate_clear_sky_irradiance(timestamp, 'dni')
198
+ if dhi < 0:
199
+ negative_dhi_count += 1
200
+ dhi = self._calculate_clear_sky_irradiance(timestamp, 'dhi')
201
+
202
+ # Calculate ground-reflected radiation
203
+ ground_radiation = self._calculate_ground_reflected_radiation(dni, dhi, solar_angles['zenith'])
204
 
205
  record = {
206
  "month": int(months[i]),
 
220
  }
221
  self.hourly_data.append(record)
222
 
223
+ # Log summary of negative DNI/DHI corrections
224
+ if negative_dni_count > 0:
225
+ logger.warning(f"Corrected {negative_dni_count} negative DNI values using clear-sky model")
226
+ if negative_dhi_count > 0:
227
+ logger.warning(f"Corrected {negative_dhi_count} negative DHI values using clear-sky model")
228
+
229
  if len(self.hourly_data) != 8760:
230
  st.warning(f"Hourly data has {len(self.hourly_data)} records instead of 8760. Some records may have been excluded due to missing data.")
231
 
 
502
  st.error(f"Validation failed: Invalid {season} angles: {angles}")
503
  return False
504
 
505
+ # Log presence of ground_reflected_radiation during import
506
+ if all("ground_reflected_radiation" in record for record in data["hourly_data"]):
507
+ logger.info("Validated ground_reflected_radiation present in all hourly data records")
508
+ else:
509
+ st.error("Validation failed: Missing ground_reflected_radiation in some hourly data records")
510
+ return False
511
+
512
  return True
513
 
514
  @staticmethod
 
540
  return False
541
 
542
  def display_climate_input(self, session_state: Dict[str, Any]):
543
+ """Display Streamlit interface for EPW and JSON upload, and visualizations."""
544
  st.title("Climate Data Analysis")
545
 
546
  # Apply consistent styling
 
551
  st.warning("Clearing invalid climate data from session state.")
552
  del session_state["climate_data"]
553
 
554
+ # File uploaders
555
+ uploaded_epw_file = st.file_uploader("Upload EPW File", type=["epw"])
556
+ uploaded_json_file = st.file_uploader("Upload JSON File", type=["json"])
557
 
558
  # Initialize location and epw_data for display
559
  location = None
560
  epw_data = None
561
 
562
+ # Process JSON file if uploaded
563
+ if uploaded_json_file:
564
+ try:
565
+ json_content = uploaded_json_file.read().decode("utf-8")
566
+ json_data = json.loads(json_content)
567
+ for loc_id, loc_dict in json_data.items():
568
+ if not self.validate_climate_data(loc_dict):
569
+ raise ValueError("Invalid climate data in JSON file.")
570
+ # Rebuild epw_data from hourly_data
571
+ hourly_data = loc_dict["hourly_data"]
572
+ epw_data = pd.DataFrame({
573
+ 1: [d["month"] for d in hourly_data],
574
+ 2: [d["day"] for d in hourly_data],
575
+ 3: [d["hour"] for d in hourly_data],
576
+ 6: [d["dry_bulb"] for d in hourly_data],
577
+ 8: [d["relative_humidity"] for d in hourly_data],
578
+ 9: [d["atmospheric_pressure"] for d in hourly_data],
579
+ 13: [d["global_horizontal_radiation"] for d in hourly_data],
580
+ 14: [d["direct_normal_irradiance"] for d in hourly_data],
581
+ 15: [d["diffuse_horizontal_irradiance"] for d in hourly_data],
582
+ 20: [d["wind_direction"] for d in hourly_data],
583
+ 21: [d["wind_speed"] for d in hourly_data],
584
+ })
585
+ location = ClimateLocation(
586
+ epw_file=epw_data,
587
+ typical_extreme_periods=loc_dict["typical_extreme_periods"],
588
+ ground_temperatures=loc_dict["ground_temperatures"],
589
+ id=loc_dict["id"],
590
+ country=loc_dict["country"],
591
+ state_province=loc_dict["state_province"],
592
+ city=loc_dict["city"],
593
+ latitude=loc_dict["latitude"],
594
+ longitude=loc_dict["longitude"],
595
+ elevation=loc_dict["elevation"],
596
+ time_zone=loc_dict["time_zone"]
597
+ )
598
+ location.hourly_data = loc_dict["hourly_data"]
599
+ location.solstice_solar_angles = loc_dict["solstice_solar_angles"]
600
+ self.add_location(location)
601
+ session_state["climate_data"] = loc_dict
602
+ st.success(f"Climate data loaded from JSON for {loc_dict['city']}")
603
+ logger.info(f"Successfully imported JSON with ground_reflected_radiation for {loc_dict['city']}")
604
+ except Exception as e:
605
+ st.error(f"Error processing JSON file: {str(e)}. Ensure it has valid climate data.")
606
+
607
+ # Process EPW file if uploaded
608
+ elif uploaded_epw_file:
609
  try:
610
  # Process new EPW file
611
+ epw_content = uploaded_epw_file.read().decode("utf-8")
612
  epw_lines = epw_content.splitlines()
613
 
614
  # Parse header
 
678
 
679
  # Parse GROUND TEMPERATURES
680
  ground_temperatures = {}
681
+ for line in epw_lines:
682
  if line.startswith("GROUND TEMPERATURES"):
683
  parts = line.strip().split(',')
684
  try:
 
733
  climate_data_dict = location.to_dict()
734
  if not self.validate_climate_data(climate_data_dict):
735
  raise ValueError("Invalid climate data extracted from EPW file.")
736
+
737
+ # Add location and save to session state
738
+ self.add_location(location)
739
  session_state["climate_data"] = climate_data_dict
740
  st.success("Climate data extracted from EPW file!")
741
+
742
+ # Export to JSON automatically
743
+ json_file_path = os.path.join(DATA_DIR, f"{city}_climate_data.json")
744
+ self.export_to_json(json_file_path)
745
+ st.info(f"Climate data exported to {json_file_path}")
746
+ logger.info(f"Exported climate data with ground_reflected_radiation to {json_file_path}")
747
 
748
  except Exception as e:
749
  st.error(f"Error processing EPW file: {str(e)}. Ensure it has 8760 hourly records and correct format.")
 
814
  self.plot_wind_rose(epw_data)
815
 
816
  else:
817
+ st.info("No climate data available. Please upload an EPW or JSON file to proceed.")
818
 
819
  def display_design_conditions(self, location: ClimateLocation):
820
  """Display design conditions for HVAC calculations using styled HTML."""
 
877
  </div>
878
  """, unsafe_allow_html=True)
879
 
880
+ # Ground Temperatures (Table)
881
+ if location.typical_extreme_periods:
882
+ period_items = [
883
+ f"<li><strong>{key.replace('_', ' ').title()}:</strong> {period['start']['month']}/{period['start']['day']} to {period['end']['month']}/{period['end']['day']}</li>"
884
+ for key, period in location.typical_extreme_periods.items()
885
+ ]
886
+ st.markdown(f"""
887
+ <div class="markdown-text">
888
+ <h3>Typical/Extreme Periods</h3>
889
+ <ul>
890
+ {''.join(period_items)}
891
+ </ul>
892
+ </div>
893
+ """, unsafe_allow_html=True)
894
+
895
  # Ground Temperatures (Table)
896
  if location.ground_temperatures:
897
  st.markdown('<div class="markdown-text"><h3>Ground Temperatures</h3></div>', unsafe_allow_html=True)