nakas Claude commited on
Commit
397e2de
·
1 Parent(s): 7d4fcc3

Add precipitation chances, 6-hour timing, and sky conditions

Browse files

Enhanced NOAA-style forecasting with:

🌧️ **Precipitation Chances**: Calculate and display percentage chances (20%, 50%, 70%, 90%)
⏰ **6-Hour Timing Analysis**: Identify when rain is most likely (morning, afternoon, evening, overnight)
☀️ **Sky Conditions**: Detailed cloud cover descriptions (sunny, partly cloudy, overcast, etc.)
🌪️ **Wind Integration**: Natural wind descriptions integrated into narrative

Example output:
"Today: Mostly cloudy, then chance of rain, mainly afternoon.
Chance of precipitation 50%. High 21°C."

"Tonight: Rain likely, mainly evening. Chance of precipitation 70%.
Low 9°C."

Features authentic NOAA timing phrases like "rain mainly afternoon"
and proper precipitation probability formatting.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

Files changed (1) hide show
  1. app.py +190 -21
app.py CHANGED
@@ -747,7 +747,7 @@ def generate_overview_paragraph(rain_hours, heavy_rain_hours, windy_hours, max_t
747
  return f"{weather_pattern}. Temperatures will be {temp_desc}."
748
 
749
  def generate_daily_detailed_forecasts(forecast_data, events):
750
- """Generate detailed daily forecasts in natural NOAA style"""
751
  current_time = datetime.now()
752
  forecasts = []
753
 
@@ -757,6 +757,7 @@ def generate_daily_detailed_forecasts(forecast_data, events):
757
  precipitation = forecast_data.get('precipitation', [0] * len(temperatures))
758
  wind_speeds = forecast_data['wind_speed']
759
  humidity = forecast_data['humidity']
 
760
 
761
  # Process 4 days of forecasts
762
  for day_offset in range(4):
@@ -776,16 +777,12 @@ def generate_daily_detailed_forecasts(forecast_data, events):
776
  if not day_indices and not night_indices:
777
  continue
778
 
779
- # Day period
780
  if day_indices:
781
- day_temps = [temperatures[i] for i in day_indices]
782
- day_precip = [precipitation[i] for i in day_indices if i < len(precipitation)]
783
- day_winds = [wind_speeds[i] for i in day_indices]
784
-
785
- day_high = max(day_temps) if day_temps else 20
786
- day_rain = any(p > 0.1 for p in day_precip)
787
- day_heavy_rain = any(p > 2.0 for p in day_precip)
788
- day_windy = any(w > 12 for w in day_winds)
789
 
790
  # Generate day forecast
791
  if day_offset == 0:
@@ -795,19 +792,15 @@ def generate_daily_detailed_forecasts(forecast_data, events):
795
  else:
796
  period_name = target_date.strftime("%A")
797
 
798
- day_forecast = generate_period_narrative(period_name, day_high, None, day_rain, day_heavy_rain, day_windy, True)
799
  forecasts.append(day_forecast)
800
 
801
- # Night period
802
  if night_indices:
803
- night_temps = [temperatures[i] for i in night_indices]
804
- night_precip = [precipitation[i] for i in night_indices if i < len(precipitation)]
805
- night_winds = [wind_speeds[i] for i in night_indices]
806
-
807
- night_low = min(night_temps) if night_temps else 10
808
- night_rain = any(p > 0.1 for p in night_precip)
809
- night_heavy_rain = any(p > 2.0 for p in night_precip)
810
- night_windy = any(w > 12 for w in night_winds)
811
 
812
  # Generate night forecast
813
  if day_offset == 0:
@@ -817,11 +810,187 @@ def generate_daily_detailed_forecasts(forecast_data, events):
817
  else:
818
  night_name = f"{target_date.strftime('%A')} Night"
819
 
820
- night_forecast = generate_period_narrative(night_name, None, night_low, night_rain, night_heavy_rain, night_windy, False)
821
  forecasts.append(night_forecast)
822
 
823
  return '\n\n'.join(forecasts)
824
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
825
  def generate_period_narrative(period_name, high_temp, low_temp, has_rain, heavy_rain, windy, is_day):
826
  """Generate natural narrative for a specific period"""
827
  conditions = []
 
747
  return f"{weather_pattern}. Temperatures will be {temp_desc}."
748
 
749
  def generate_daily_detailed_forecasts(forecast_data, events):
750
+ """Generate detailed daily forecasts with precipitation chances, timing, and cloud cover"""
751
  current_time = datetime.now()
752
  forecasts = []
753
 
 
757
  precipitation = forecast_data.get('precipitation', [0] * len(temperatures))
758
  wind_speeds = forecast_data['wind_speed']
759
  humidity = forecast_data['humidity']
760
+ cloud_cover = forecast_data.get('cloud_cover', [50] * len(temperatures))
761
 
762
  # Process 4 days of forecasts
763
  for day_offset in range(4):
 
777
  if not day_indices and not night_indices:
778
  continue
779
 
780
+ # Day period analysis
781
  if day_indices:
782
+ day_data = analyze_period_conditions(
783
+ day_indices, timestamps, temperatures, precipitation,
784
+ wind_speeds, cloud_cover, day_start, day_end
785
+ )
 
 
 
 
786
 
787
  # Generate day forecast
788
  if day_offset == 0:
 
792
  else:
793
  period_name = target_date.strftime("%A")
794
 
795
+ day_forecast = generate_enhanced_period_narrative(period_name, day_data, True)
796
  forecasts.append(day_forecast)
797
 
798
+ # Night period analysis
799
  if night_indices:
800
+ night_data = analyze_period_conditions(
801
+ night_indices, timestamps, temperatures, precipitation,
802
+ wind_speeds, cloud_cover, day_end, night_end
803
+ )
 
 
 
 
804
 
805
  # Generate night forecast
806
  if day_offset == 0:
 
810
  else:
811
  night_name = f"{target_date.strftime('%A')} Night"
812
 
813
+ night_forecast = generate_enhanced_period_narrative(night_name, night_data, False)
814
  forecasts.append(night_forecast)
815
 
816
  return '\n\n'.join(forecasts)
817
 
818
+ def analyze_period_conditions(indices, timestamps, temperatures, precipitation, wind_speeds, cloud_cover, period_start, period_end):
819
+ """Analyze weather conditions for a specific time period with 6-hour sub-periods"""
820
+ data = {
821
+ 'temps': [temperatures[i] for i in indices],
822
+ 'precip': [precipitation[i] for i in indices if i < len(precipitation)],
823
+ 'winds': [wind_speeds[i] for i in indices],
824
+ 'clouds': [cloud_cover[i] for i in indices if i < len(cloud_cover)],
825
+ 'timestamps': [timestamps[i] for i in indices]
826
+ }
827
+
828
+ # Calculate statistics
829
+ data['high_temp'] = max(data['temps']) if data['temps'] else 20
830
+ data['low_temp'] = min(data['temps']) if data['temps'] else 10
831
+ data['avg_clouds'] = sum(data['clouds']) / len(data['clouds']) if data['clouds'] else 50
832
+ data['max_wind'] = max(data['winds']) if data['winds'] else 5
833
+
834
+ # Precipitation analysis
835
+ data['precip_chance'] = calculate_precipitation_chance(data['precip'])
836
+ data['rain_timing'] = analyze_6hour_precipitation_timing(data['timestamps'], data['precip'], period_start, period_end)
837
+
838
+ # Cloud cover description
839
+ data['sky_condition'] = get_sky_condition(data['avg_clouds'])
840
+
841
+ # Wind conditions
842
+ data['wind_desc'] = get_wind_description(data['max_wind'])
843
+
844
+ return data
845
+
846
+ def calculate_precipitation_chance(precip_data):
847
+ """Calculate precipitation chance percentage"""
848
+ if not precip_data:
849
+ return 0
850
+
851
+ rainy_hours = sum(1 for p in precip_data if p > 0.1)
852
+ total_hours = len(precip_data)
853
+
854
+ if rainy_hours == 0:
855
+ return 0
856
+ elif rainy_hours >= total_hours * 0.8:
857
+ return 90
858
+ elif rainy_hours >= total_hours * 0.6:
859
+ return 70
860
+ elif rainy_hours >= total_hours * 0.4:
861
+ return 50
862
+ elif rainy_hours >= total_hours * 0.2:
863
+ return 30
864
+ else:
865
+ return 20
866
+
867
+ def analyze_6hour_precipitation_timing(timestamps, precip_data, period_start, period_end):
868
+ """Analyze when rain is most likely within the period using 6-hour blocks"""
869
+ if not timestamps or not precip_data:
870
+ return None
871
+
872
+ # Define 6-hour periods
873
+ period_duration = (period_end - period_start).total_seconds() / 3600 # hours
874
+
875
+ if period_duration <= 6:
876
+ # Single period
877
+ avg_precip = sum(precip_data) / len(precip_data) if precip_data else 0
878
+ if avg_precip > 0.1:
879
+ start_hour = period_start.hour
880
+ if 6 <= start_hour < 12:
881
+ return "morning"
882
+ elif 12 <= start_hour < 18:
883
+ return "afternoon"
884
+ elif 18 <= start_hour < 24:
885
+ return "evening"
886
+ else:
887
+ return "overnight"
888
+ return None
889
+
890
+ # Analyze multiple 6-hour blocks
891
+ blocks = []
892
+ block_size = max(1, len(timestamps) // int(period_duration / 6))
893
+
894
+ for i in range(0, len(timestamps), block_size):
895
+ block_precip = precip_data[i:i+block_size] if i+block_size <= len(precip_data) else precip_data[i:]
896
+ block_avg = sum(block_precip) / len(block_precip) if block_precip else 0
897
+ block_time = timestamps[i]
898
+ blocks.append((block_time, block_avg))
899
+
900
+ # Find the block with highest precipitation
901
+ if blocks:
902
+ max_precip_block = max(blocks, key=lambda x: x[1])
903
+ if max_precip_block[1] > 0.1:
904
+ hour = max_precip_block[0].hour
905
+ if 6 <= hour < 12:
906
+ return "morning"
907
+ elif 12 <= hour < 18:
908
+ return "afternoon"
909
+ elif 18 <= hour < 24:
910
+ return "evening"
911
+ else:
912
+ return "overnight"
913
+
914
+ return None
915
+
916
+ def get_sky_condition(cloud_percentage):
917
+ """Convert cloud percentage to descriptive terms"""
918
+ if cloud_percentage < 10:
919
+ return "sunny"
920
+ elif cloud_percentage < 25:
921
+ return "mostly sunny"
922
+ elif cloud_percentage < 50:
923
+ return "partly cloudy"
924
+ elif cloud_percentage < 75:
925
+ return "mostly cloudy"
926
+ elif cloud_percentage < 90:
927
+ return "cloudy"
928
+ else:
929
+ return "overcast"
930
+
931
+ def get_wind_description(wind_speed):
932
+ """Convert wind speed to descriptive terms"""
933
+ if wind_speed < 3:
934
+ return "light winds"
935
+ elif wind_speed < 8:
936
+ return "light winds"
937
+ elif wind_speed < 12:
938
+ return "breezy"
939
+ elif wind_speed < 18:
940
+ return "windy"
941
+ else:
942
+ return "very windy"
943
+
944
+ def generate_enhanced_period_narrative(period_name, data, is_day):
945
+ """Generate enhanced narrative with precipitation chances, timing, and sky conditions"""
946
+ conditions = []
947
+
948
+ # Sky condition and precipitation
949
+ if data['precip_chance'] > 60:
950
+ if data['rain_timing']:
951
+ conditions.append(f"rain likely, mainly {data['rain_timing']}")
952
+ else:
953
+ conditions.append("rain likely")
954
+ conditions.append(f"Chance of precipitation {data['precip_chance']}%")
955
+ elif data['precip_chance'] > 30:
956
+ if data['rain_timing']:
957
+ conditions.append(f"chance of rain, mainly {data['rain_timing']}")
958
+ else:
959
+ conditions.append("chance of rain")
960
+ conditions.append(f"Chance of precipitation {data['precip_chance']}%")
961
+ elif data['precip_chance'] > 0:
962
+ conditions.append(f"slight chance of rain. Chance of precipitation {data['precip_chance']}%")
963
+
964
+ # If no significant precipitation, describe sky condition
965
+ if data['precip_chance'] <= 30:
966
+ if not conditions: # No rain mentioned yet
967
+ conditions.insert(0, data['sky_condition'])
968
+ else:
969
+ conditions.insert(0, f"{data['sky_condition']}, then")
970
+
971
+ # Wind conditions
972
+ if data['max_wind'] > 12:
973
+ conditions.append(data['wind_desc'])
974
+
975
+ # Build the narrative
976
+ if is_day:
977
+ if conditions:
978
+ weather_text = f"**{period_name}:** {' '.join(conditions).capitalize()}"
979
+ else:
980
+ weather_text = f"**{period_name}:** {data['sky_condition'].capitalize()}"
981
+
982
+ weather_text += f". High {data['high_temp']:.0f}°C"
983
+ else:
984
+ if conditions:
985
+ weather_text = f"**{period_name}:** {' '.join(conditions).capitalize()}"
986
+ else:
987
+ weather_text = f"**{period_name}:** {data['sky_condition'].capitalize()}"
988
+
989
+ weather_text += f". Low {data['low_temp']:.0f}°C"
990
+
991
+ weather_text += "."
992
+ return weather_text
993
+
994
  def generate_period_narrative(period_name, high_temp, low_temp, has_rain, heavy_rain, windy, is_day):
995
  """Generate natural narrative for a specific period"""
996
  conditions = []