utils.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. # -*- coding: utf-8 -*-
  2. """
  3. Hilfsfunktionen fuer das Weather-Plugin.
  4. WMO-Code-Tabelle, Bright-Sky-Condition-Tabelle, TTS-Formatierung
  5. und Windrichtungs-Konvertierung.
  6. """
  7. from plugins.weather.models import ForecastDay, WeatherData
  8. # =========================================================================
  9. # WMO-Wettercode-Tabelle (Open-Meteo)
  10. # https://www.noaa.gov/weather/wmo-weather-codes
  11. # =========================================================================
  12. WMO_DESCRIPTIONS: dict[int, str] = {
  13. 0: "Klarer Himmel",
  14. 1: "Ueberwiegend klar",
  15. 2: "Teilweise bewoelkt",
  16. 3: "Bedeckt",
  17. 45: "Nebel",
  18. 48: "Gefrierender Nebel",
  19. 51: "Leichter Nieselregen",
  20. 53: "Maessiger Nieselregen",
  21. 55: "Starker Nieselregen",
  22. 56: "Leichter gefrierender Nieselregen",
  23. 57: "Starker gefrierender Nieselregen",
  24. 61: "Leichter Regen",
  25. 63: "Maessiger Regen",
  26. 65: "Starker Regen",
  27. 66: "Leichter gefrierender Regen",
  28. 67: "Starker gefrierender Regen",
  29. 71: "Leichter Schneefall",
  30. 73: "Maessiger Schneefall",
  31. 75: "Starker Schneefall",
  32. 77: "Schneegriesel",
  33. 80: "Leichte Regenschauer",
  34. 81: "Maessige Regenschauer",
  35. 82: "Starke Regenschauer",
  36. 85: "Leichte Schneeschauer",
  37. 86: "Starke Schneeschauer",
  38. 95: "Gewitter",
  39. 96: "Gewitter mit leichtem Hagel",
  40. 99: "Gewitter mit starkem Hagel",
  41. }
  42. # =========================================================================
  43. # Bright Sky (DWD) Condition-Tabelle
  44. # =========================================================================
  45. BRIGHTSKY_CONDITIONS: dict[str, str] = {
  46. "dry": "Trocken",
  47. "fog": "Nebel",
  48. "rain": "Regen",
  49. "sleet": "Schneeregen",
  50. "snow": "Schnee",
  51. "hail": "Hagel",
  52. "thunderstorm": "Gewitter",
  53. "wind": "Windig",
  54. }
  55. # =========================================================================
  56. # Windrichtung
  57. # =========================================================================
  58. _WIND_DIRECTIONS = [
  59. (0, 22.5, "Nord"),
  60. (22.5, 67.5, "Nordost"),
  61. (67.5, 112.5, "Ost"),
  62. (112.5, 157.5, "Suedost"),
  63. (157.5, 202.5, "Sued"),
  64. (202.5, 247.5, "Suedwest"),
  65. (247.5, 292.5, "West"),
  66. (292.5, 337.5, "Nordwest"),
  67. (337.5, 360.1, "Nord"),
  68. ]
  69. def format_wind_direction(degrees: int) -> str:
  70. """
  71. Konvertiert Windrichtung in Grad zu Himmelsrichtung.
  72. Args:
  73. degrees: Windrichtung in Grad (0-360)
  74. Returns:
  75. Himmelsrichtung als String (z.B. "Suedwest")
  76. """
  77. degrees = degrees % 360
  78. for low, high, name in _WIND_DIRECTIONS:
  79. if low <= degrees < high:
  80. return name
  81. return "Nord"
  82. # =========================================================================
  83. # Lookup-Funktionen
  84. # =========================================================================
  85. def get_wmo_description(code: int) -> str:
  86. """
  87. Gibt die deutsche Beschreibung fuer einen WMO-Wettercode zurueck.
  88. Args:
  89. code: WMO-Wettercode
  90. Returns:
  91. Deutsche Wetterbeschreibung
  92. """
  93. return WMO_DESCRIPTIONS.get(code, f"Unbekannt ({code})")
  94. def get_brightsky_description(condition: str) -> str:
  95. """
  96. Gibt die deutsche Beschreibung fuer eine Bright-Sky-Condition zurueck.
  97. Args:
  98. condition: Bright-Sky-Condition-String
  99. Returns:
  100. Deutsche Wetterbeschreibung
  101. """
  102. return BRIGHTSKY_CONDITIONS.get(condition, condition.capitalize())
  103. # =========================================================================
  104. # TTS-Formatierung
  105. # =========================================================================
  106. def format_weather_for_tts(data: WeatherData) -> str:
  107. """
  108. Formatiert aktuelle Wetterdaten fuer die Sprachausgabe.
  109. Beispiel: "In Sinsheim sind es 8 Grad, leichter Regen bei 12 km/h Wind
  110. aus Suedwest."
  111. Args:
  112. data: Aktuelle Wetterdaten
  113. Returns:
  114. TTS-formatierter String
  115. """
  116. temp = round(data.temperature)
  117. parts = [f"In {data.location} sind es {temp} Grad"]
  118. if data.feels_like is not None:
  119. diff = abs(round(data.feels_like) - temp)
  120. if diff >= 3:
  121. parts.append(f"gefuehlt {round(data.feels_like)} Grad")
  122. parts.append(data.description)
  123. if data.wind_speed >= 5:
  124. wind_dir = format_wind_direction(data.wind_direction)
  125. parts.append(f"bei {round(data.wind_speed)} km/h Wind aus {wind_dir}")
  126. if data.precipitation > 0:
  127. parts.append(
  128. f"mit {data.precipitation:.1f} Millimeter Niederschlag"
  129. )
  130. return ", ".join(parts) + "."
  131. def format_forecast_for_tts(day: ForecastDay, date_label: str) -> str:
  132. """
  133. Formatiert eine Tagesvorhersage fuer die Sprachausgabe.
  134. Beispiel: "Morgen in Sinsheim: 5 bis 12 Grad, bewoelkt."
  135. Args:
  136. day: Vorhersagedaten fuer einen Tag
  137. date_label: Datum-Label (z.B. "Morgen", "Am Freitag")
  138. Returns:
  139. TTS-formatierter String
  140. """
  141. temp_min = round(day.temp_min)
  142. temp_max = round(day.temp_max)
  143. parts = [f"{date_label}: {temp_min} bis {temp_max} Grad, {day.description}"]
  144. if day.precipitation > 0:
  145. parts.append(
  146. f"{day.precipitation:.1f} Millimeter Niederschlag"
  147. )
  148. if day.wind_speed_max >= 30:
  149. parts.append(f"Windboeen bis {round(day.wind_speed_max)} km/h")
  150. return ", ".join(parts) + "."