Skip to content

Commit 7411c43

Browse files
committed
v0.75053 - only include eligible territories in U.S. NWS queries
1 parent 8c9f07c commit 7411c43

File tree

5 files changed

+187
-92
lines changed

5 files changed

+187
-92
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,8 @@ If you run into any issues, consult the logs or reach out on the repository's [I
236236
---
237237

238238
# Changelog
239+
- v0.75053 - only include eligible territories in U.S. NWS queries
240+
- list of queried / eligible territories can be set in `config.ini` under the `NWS` section
239241
- v0.75052 - include the details from U.S. National Weather Service on alerts
240242
- v0.75051 - updated `config.ini` for configuring NWS weather forecasts & alerts
241243
- suggested method is to supplement via NWS the additional weather data you need

config/config.ini

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,17 @@ ChunkSize = 500
174174
# (weather.gov)
175175
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
176176
[NWS]
177-
# Fetch NWS foreacsts and/or alerts? (true/false)
177+
# Set NwsOnlyEligibleCountries to False if you want to include U.S. NWS calls outside of the country list.
178+
NwsOnlyEligibleCountries = True
179+
# 'NwsEligibleCountries' is a configurable list of countries (in ISO-3166 country code format)
180+
# that are eligible for NWS data. You can add or remove countries from this list as needed.
181+
# (Legend:)
182+
# AQ: Antarctica (for U.S. research stations)
183+
# UM: United States Minor Outlying Islands (like Wake Island, Midway Atoll, etc.)
184+
# XW: International Waters (this isn't an official ISO code but could be used as a placeholder for maritime areas under U.S. influence or international jurisdictions)
185+
# ZZ: Unknown or undefined region (could be used as a placeholder for situations where precise location data isn't available or relevant)
186+
NwsEligibleCountries = US, PR, GU, AS, VI, MP, CA, MX, AQ, UM, XW, ZZ
187+
# Fetch NWS foreacsts and/or alerts (true/false)
178188
# Note that the service can be slow and unreliable at times.
179189
# I recommand getting the alerts to supplement i.e. OpenWeatherMap.
180190
# The alerts usually work, but sadly their open API forecasts are often broken.

src/api_get_openweathermap.py

Lines changed: 171 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
# github.com/FlyingFathead/TelegramBot-OpenAI-API/
44
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
55
#
6-
# >>> weather fetcher module version: v0.728
7-
# >>> (Updated July 13 2024)
6+
# >>> weather fetcher module version: v0.75053
7+
# >>> (Updated Oct 8 2024)
88
#
99
# This API functionality requires both OpenWeatherMap and MapTiler API keys.
1010
# You can get both from the corresponding service providers.
@@ -14,7 +14,10 @@
1414

1515
# Import the NWS data fetching function
1616
from api_get_nws_weather import get_nws_forecast, get_nws_alerts
17-
from config_paths import NWS_USER_AGENT, NWS_RETRIES, NWS_RETRY_DELAY, FETCH_NWS_FORECAST, FETCH_NWS_ALERTS
17+
from config_paths import NWS_USER_AGENT, NWS_RETRIES, NWS_RETRY_DELAY, FETCH_NWS_FORECAST, FETCH_NWS_ALERTS, NWS_ELIGIBLE_COUNTRIES, NWS_ONLY_ELIGIBLE_COUNTRIES
18+
19+
# linter annotation (use only if you use the v2 method of `get_coordinates`)
20+
# from typing import Optional, Tuple
1821

1922
# date & time utils
2023
import datetime as dt
@@ -82,26 +85,40 @@ async def get_weather(city_name, country, exclude='', units='metric', lang='fi')
8285
# combined_data = await combine_weather_data(city_name, resolved_country, lat, lon, current_weather_data, forecast_data, moon_phase_data, daily_forecast_data, current_weather_data_from_weatherapi, astronomy_data, additional_data)
8386
# return combined_data
8487

85-
# Fetch NWS data
86-
logging.info("Fetching NWS data.")
87-
nws_data = await get_nws_forecast(lat, lon)
88-
if nws_data:
89-
logging.info("NWS data fetched successfully.")
90-
nws_forecast = nws_data.get('nws_forecast')
91-
nws_forecast_hourly = nws_data.get('nws_forecast_hourly')
92-
else:
93-
logging.warning("Failed to fetch NWS data.")
88+
# Check if NWS should only be used for eligible countries
89+
if NWS_ONLY_ELIGIBLE_COUNTRIES and resolved_country.upper() not in NWS_ELIGIBLE_COUNTRIES:
90+
logging.info(f"NOTE: NWS data will not be fetched as {resolved_country.upper()} is not in the eligible country list.")
9491
nws_forecast = None
9592
nws_forecast_hourly = None
96-
97-
# Fetch NWS alerts data
98-
logging.info("Fetching NWS alerts data.")
99-
nws_alerts = await get_nws_alerts(lat, lon)
100-
if nws_alerts:
101-
logging.info(f"Fetched {len(nws_alerts)} active NWS alerts.")
93+
nws_alerts = None
10294
else:
103-
logging.info("No active NWS alerts found.")
104-
95+
# Ensure that NWS API requests are made only if eligible
96+
try:
97+
logging.info("Fetching NWS data.")
98+
nws_data = await get_nws_forecast(lat, lon)
99+
if nws_data:
100+
logging.info("NWS data fetched successfully.")
101+
nws_forecast = nws_data.get('nws_forecast')
102+
nws_forecast_hourly = nws_data.get('nws_forecast_hourly')
103+
else:
104+
logging.warning("Failed to fetch NWS data.")
105+
nws_forecast = None
106+
nws_forecast_hourly = None
107+
108+
# Fetch NWS alerts data only if the forecast is successful
109+
logging.info("Fetching NWS alerts data.")
110+
nws_alerts = await get_nws_alerts(lat, lon)
111+
if nws_alerts:
112+
logging.info(f"Fetched {len(nws_alerts)} active NWS alerts.")
113+
else:
114+
logging.info("No active NWS alerts found.")
115+
except Exception as e:
116+
logging.error(f"Error fetching NWS data: {e}")
117+
nws_forecast = None
118+
nws_forecast_hourly = None
119+
nws_alerts = None
120+
121+
# combine the weather data
105122
combined_data = await combine_weather_data(
106123
city_name, resolved_country, lat, lon,
107124
current_weather_data, forecast_data, moon_phase_data,
@@ -114,7 +131,49 @@ async def get_weather(city_name, country, exclude='', units='metric', lang='fi')
114131
logging.error(f"Failed to fetch weather data: {current_weather_response.text} / {forecast_response.text}")
115132
return "[Inform the user that data fetching the weather data failed, current information could not be fetched. Reply in the user's language.]"
116133

117-
# get coordinates
134+
# # get coordinates (method 2; might introduce complexity; needs `Typing`)
135+
# async def get_coordinates(city_name: str, country: Optional[str] = None) -> Tuple[Optional[float], Optional[float], Optional[str]]:
136+
# lat: Optional[float] = None
137+
# lon: Optional[float] = None
138+
# resolved_country: Optional[str] = None
139+
140+
# logging.info(f"Coordinates for {city_name}, {country}: Latitude: {lat}, Longitude: {lon}")
141+
# api_key = os.getenv('MAPTILER_API_KEY')
142+
143+
# if not api_key:
144+
# logging.info("[WARNING] MapTiler API key not set. You need to set the 'MAPTILER_API_KEY' environment variable to use coordinate lookups!")
145+
# return None, None, None
146+
147+
# query = f"{city_name}"
148+
# if country:
149+
# query += f", {country}"
150+
151+
# geocode_url = f"https://api.maptiler.com/geocoding/{query}.json?key={api_key}"
152+
# logging.info(f"Making API request to URL: {geocode_url}")
153+
154+
# async with httpx.AsyncClient() as client:
155+
# response = await client.get(geocode_url)
156+
# logging.info(f"Received response with status code: {response.status_code}")
157+
158+
# if response.status_code == 200:
159+
# data = response.json()
160+
# logging.info(f"Response data: {data}")
161+
162+
# if data.get('features'):
163+
# feature = data['features'][0]
164+
# lat = feature['geometry']['coordinates'][1]
165+
# lon = feature['geometry']['coordinates'][0]
166+
# resolved_country = feature['properties'].get('country_code', 'Unknown')
167+
# logging.info(f"Coordinates for {city_name}, {resolved_country}: Latitude: {lat}, Longitude: {lon}")
168+
# return lat, lon, resolved_country
169+
# else:
170+
# logging.error("No features found in the geocoding response.")
171+
# return None, None, None
172+
# else:
173+
# logging.error(f"Failed to fetch coordinates: {response.text}")
174+
# return None, None, None
175+
176+
# # // (old method for country lookup)
118177
async def get_coordinates(city_name, country=None):
119178
lat = lon = None
120179
resolved_country = None
@@ -208,7 +267,7 @@ def convert_to_24_hour(time_str, timezone_str):
208267
# combined weather data
209268
# async def combine_weather_data(city_name, country, lat, lon, current_weather_data, forecast_data, moon_phase_data, daily_forecast_data, current_weather_data_from_weatherapi, astronomy_data, additional_data):
210269
# Define the combine_weather_data function with NWS integration
211-
async def combine_weather_data(city_name, country, lat, lon, current_weather_data, forecast_data, moon_phase_data, daily_forecast_data, current_weather_data_from_weatherapi, astronomy_data, additional_data, nws_forecast, nws_forecast_hourly):
270+
async def combine_weather_data(city_name, resolved_country, lat, lon, current_weather_data, forecast_data, moon_phase_data, daily_forecast_data, current_weather_data_from_weatherapi, astronomy_data, additional_data, nws_forecast, nws_forecast_hourly):
212271
tf = TimezoneFinder()
213272
timezone_str = tf.timezone_at(lat=lat, lng=lon)
214273
local_timezone = pytz.timezone(timezone_str)
@@ -360,7 +419,6 @@ async def combine_weather_data(city_name, country, lat, lon, current_weather_dat
360419

361420
combined_info = f"{detailed_weather_info}\n\n{final_forecast}"
362421

363-
364422
# Append NWS data (Forecasts)
365423
if nws_forecast:
366424
nws_forecast_info = ""
@@ -398,73 +456,96 @@ async def combine_weather_data(city_name, country, lat, lon, current_weather_dat
398456

399457
combined_info += f"\n{nws_forecast_info}\n{nws_hourly_forecast_info}"
400458

401-
# Fetch and append NWS Alerts
402-
try:
403-
# Round coordinates to 4 decimal places to comply with NWS API
404-
lat_rounded = round(lat, 4)
405-
lon_rounded = round(lon, 4)
406-
alerts_url = f"https://api.weather.gov/alerts/active?point={lat_rounded},{lon_rounded}"
407-
async with httpx.AsyncClient(follow_redirects=True) as client:
408-
alerts_response = await client.get(alerts_url, headers={'User-Agent': NWS_USER_AGENT})
409-
alerts_response.raise_for_status()
410-
alerts_data = alerts_response.json()
411-
except httpx.HTTPStatusError as e:
412-
logging.error(f"NWS Alerts HTTP error: {e.response.status_code} - {e.response.text}")
413-
alerts_data = None
414-
except Exception as e:
415-
logging.error(f"Error fetching NWS alerts: {e}")
416-
alerts_data = None
417-
418-
alerts_info = ""
419-
if alerts_data and 'features' in alerts_data and alerts_data['features']:
420-
alerts_info += "[HUOM! HUOMIOI NÄMÄ! TAKE THESE INTO ACCOUNT!!! MENTION THESE TO THE USER IF THERE ARE WEATHER ALERTS -- INCLUDE ALL THE DETAILS. WHAT, WHEN, WHERE, WHAT SEVERITY, ETC.]\n🚨 <b>ONGOING ALERTS FROM THE U.S. NWS (weather.gov):</b>\n"
421-
for idx, alert in enumerate(alerts_data['features'], start=1):
422-
properties = alert.get('properties', {})
423-
424-
event = properties.get('event', 'EVENT').upper()
425-
headline = properties.get('headline', 'HEADLINE')
426-
description = properties.get('description', 'No further details available') # Fetching the detailed description
427-
instruction = properties.get('instruction', 'INSTRUCTION')
428-
severity = properties.get('severity', 'Unknown').capitalize()
429-
certainty = properties.get('certainty', 'Unknown').capitalize()
430-
urgency = properties.get('urgency', 'Unknown').capitalize()
431-
area_desc = properties.get('areaDesc', 'N/A')
432-
effective = properties.get('effective', 'N/A')
433-
expires = properties.get('expires', 'N/A')
434-
435-
alerts_info += (
436-
f"{idx}. ⚠️ <b>{event}</b>\n"
437-
f"<b>Vaara:</b> {headline}\n"
438-
f"<b>Kuvaus:</b> {description}\n" # Adding the detailed description
439-
f"<b>Ohjeet:</b> {instruction}\n"
440-
f"<b>Alue:</b> {area_desc}\n"
441-
f"<b>Vakavuus:</b> {severity}\n"
442-
f"<b>Varmuus:</b> {certainty}\n"
443-
f"<b>Kiireellisyys:</b> {urgency}\n"
444-
f"<b>Voimassa alkaen:</b> {effective}\n"
445-
f"<b>Päättyy:</b> {expires}\n\n"
446-
)
447-
else:
448-
alerts_info += "\n🚨 Ei aktiivisia varoituksia U.S. NWS:n (weather.gov) mukaan.\n"
449-
450-
# if alerts_data and 'features' in alerts_data and alerts_data['features']:
451-
# alerts_info += "\n🚨 <b>NWS ALERTS:</b>\n"
452-
# for alert in alerts_data['features']:
453-
# event = alert.get('properties', {}).get('event', 'EVENT').upper()
454-
# severity = alert.get('properties', {}).get('severity', 'SEVERITY').upper()
455-
# headline = alert.get('properties', {}).get('headline', 'HEADLINE')
456-
# instruction = alert.get('properties', {}).get('instruction', 'INSTRUCTION')
457-
458-
# # Highlight severe alerts
459-
# if 'HURRICANE' in event or severity in ['WATCH', 'WARNING', 'EMERGENCY']:
460-
# alerts_info += f"🔥 <b>{event}</b>\n<b>Vaara:</b> {headline}\n<b>Ohjeet:</b> {instruction}\n\n"
461-
# else:
462-
# # Include less severe alerts if needed
463-
# alerts_info += f"<b>{event}</b>\n{headline}\n{instruction}\n\n"
464-
# else:
465-
# alerts_info += "\n🚨 <b>NWS ALERTS:</b> Ei aktiivisia varoituksia.\n"
466-
467-
combined_info += alerts_info
459+
# Append NWS data (Forecasts) only for eligible countries
460+
if not NWS_ONLY_ELIGIBLE_COUNTRIES or resolved_country.upper() in NWS_ELIGIBLE_COUNTRIES:
461+
462+
# Append NWS Forecasts
463+
if nws_forecast:
464+
nws_forecast_info = ""
465+
nws_periods = nws_forecast.get('properties', {}).get('periods', [])
466+
if nws_periods:
467+
nws_forecast_info += "🌦️ <b>NWS Forecast (weather.gov):</b>\n"
468+
for period in nws_periods[:3]: # Limit to next 3 periods
469+
name = period.get('name', 'N/A')
470+
temperature = period.get('temperature', 'N/A')
471+
temperature_unit = period.get('temperatureUnit', 'N/A')
472+
wind_speed = period.get('windSpeed', 'N/A')
473+
wind_direction = period.get('windDirection', 'N/A')
474+
short_forecast = period.get('shortForecast', 'N/A')
475+
nws_forecast_info += f"{name}: {short_forecast}, {temperature}°{temperature_unit}, Wind: {wind_speed} {wind_direction}\n"
476+
else:
477+
nws_forecast_info += "🌦️ <b>NWS Forecast (weather.gov):</b> Ei saatavilla.\n"
478+
479+
if nws_forecast_hourly:
480+
nws_hourly_forecast_info = ""
481+
nws_hourly_periods = nws_forecast_hourly.get('properties', {}).get('periods', [])
482+
if nws_hourly_periods:
483+
nws_hourly_forecast_info += "⏰ <b>NWS Hourly Forecast:</b>\n"
484+
for period in nws_hourly_periods[:3]: # Limit to next 3 hourly forecasts
485+
start_time = period.get('startTime', 'N/A')
486+
temperature = period.get('temperature', 'N/A')
487+
temperature_unit = period.get('temperatureUnit', 'N/A')
488+
wind_speed = period.get('windSpeed', 'N/A')
489+
wind_direction = period.get('windDirection', 'N/A')
490+
short_forecast = period.get('shortForecast', 'N/A')
491+
nws_hourly_forecast_info += f"{start_time}: {short_forecast}, {temperature}°{temperature_unit}, Wind: {wind_speed} {wind_direction}\n"
492+
else:
493+
nws_hourly_forecast_info += "⏰ <b>NWS Hourly Forecast:</b> Ei saatavilla.\n"
494+
else:
495+
nws_hourly_forecast_info = "⏰ <b>NWS Hourly Forecast:</b> Ei saatavilla.\n"
496+
497+
combined_info += f"\n{nws_forecast_info}\n{nws_hourly_forecast_info}"
498+
499+
# Fetch and append NWS Alerts
500+
try:
501+
# Round coordinates to 4 decimal places to comply with NWS API
502+
lat_rounded = round(lat, 4)
503+
lon_rounded = round(lon, 4)
504+
alerts_url = f"https://api.weather.gov/alerts/active?point={lat_rounded},{lon_rounded}"
505+
async with httpx.AsyncClient(follow_redirects=True) as client:
506+
alerts_response = await client.get(alerts_url, headers={'User-Agent': NWS_USER_AGENT})
507+
alerts_response.raise_for_status()
508+
alerts_data = alerts_response.json()
509+
except httpx.HTTPStatusError as e:
510+
logging.error(f"NWS Alerts HTTP error: {e.response.status_code} - {e.response.text}")
511+
alerts_data = None
512+
except Exception as e:
513+
logging.error(f"Error fetching NWS alerts: {e}")
514+
alerts_data = None
515+
516+
alerts_info = ""
517+
if alerts_data and 'features' in alerts_data and alerts_data['features']:
518+
alerts_info += "[HUOM! HUOMIOI NÄMÄ! TAKE THESE INTO ACCOUNT!!! MENTION THESE TO THE USER IF THERE ARE WEATHER ALERTS -- INCLUDE ALL THE DETAILS. WHAT, WHEN, WHERE, WHAT SEVERITY, ETC.]\n🚨 <b>ONGOING ALERTS FROM THE U.S. NWS (weather.gov):</b>\n"
519+
for idx, alert in enumerate(alerts_data['features'], start=1):
520+
properties = alert.get('properties', {})
521+
522+
event = properties.get('event', 'EVENT').upper()
523+
headline = properties.get('headline', 'HEADLINE')
524+
description = properties.get('description', 'No further details available') # Fetching the detailed description
525+
instruction = properties.get('instruction', 'INSTRUCTION')
526+
severity = properties.get('severity', 'Unknown').capitalize()
527+
certainty = properties.get('certainty', 'Unknown').capitalize()
528+
urgency = properties.get('urgency', 'Unknown').capitalize()
529+
area_desc = properties.get('areaDesc', 'N/A')
530+
effective = properties.get('effective', 'N/A')
531+
expires = properties.get('expires', 'N/A')
532+
533+
alerts_info += (
534+
f"{idx}. ⚠️ <b>{event}</b>\n"
535+
f"<b>Vaara:</b> {headline}\n"
536+
f"<b>Kuvaus:</b> {description}\n" # Adding the detailed description
537+
f"<b>Ohjeet:</b> {instruction}\n"
538+
f"<b>Alue:</b> {area_desc}\n"
539+
f"<b>Vakavuus:</b> {severity}\n"
540+
f"<b>Varmuus:</b> {certainty}\n"
541+
f"<b>Kiireellisyys:</b> {urgency}\n"
542+
f"<b>Voimassa alkaen:</b> {effective}\n"
543+
f"<b>Päättyy:</b> {expires}\n\n"
544+
)
545+
else:
546+
alerts_info += "\n🚨 Ei aktiivisia varoituksia U.S. NWS:n (weather.gov) mukaan.\n"
547+
548+
combined_info += alerts_info
468549

469550
# Combine all information
470551
combined_info += f"\n{detailed_weather_info}\n\n{final_forecast}"

src/config_paths.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@
8484
NWS_RETRY_DELAY = config['NWS'].getint('NWSRetryDelay', fallback=2)
8585
FETCH_NWS_FORECAST = config['NWS'].getboolean('FetchNWSForecast', fallback=True)
8686
FETCH_NWS_ALERTS = config['NWS'].getboolean('FetchNWSAlerts', fallback=True)
87+
NWS_ONLY_ELIGIBLE_COUNTRIES = config['NWS'].getboolean('NwsOnlyEligibleCountries', fallback=True)
88+
NWS_ELIGIBLE_COUNTRIES = config['NWS'].get('NwsEligibleCountries', fallback='US, PR, GU, AS, VI, MP').split(', ')
8789
logger.info(f"NWS Config: User-Agent={NWS_USER_AGENT}, Retries={NWS_RETRIES}, Retry Delay={NWS_RETRY_DELAY}, Fetch Forecast={FETCH_NWS_FORECAST}, Fetch Alerts={FETCH_NWS_ALERTS}")
8890
else:
8991
logger.warning("NWS section not found in config.ini. Using default NWS settings.")

0 commit comments

Comments
 (0)