@tool(annotations=_READ_ONLY_OPEN)
def search_weather_stations(
query: Annotated[str | None, Field(description="City or airport name.")] = None,
latitude: Annotated[float | None, Field(description="Latitude.")] = None,
longitude: Annotated[float | None, Field(description="Longitude.")] = None,
country: Annotated[str | None, Field(description='Country code, e.g. "USA".')] = None,
state: Annotated[str | None, Field(description='State code, e.g. "MA".')] = None,
limit: Annotated[int, Field(description="Max results.")] = 5,
) -> SearchWeatherStationsResult:
"""Find weather stations by name or coordinates."""
index = get_state().get_or_load_station_index()
if latitude is not None and longitude is not None:
spatial_results = index.nearest(latitude, longitude, limit=limit)
spatial_stations: list[dict[str, Any]] = []
for r in spatial_results:
if not _matches_filters(r.station, country, state):
continue
spatial_stations.append({
**serialize_station(r.station),
"distance_km": round(r.distance_km, 1),
})
logger.debug(
"search_weather_stations: spatial lat=%s lon=%s found=%d", latitude, longitude, len(spatial_stations)
)
return SearchWeatherStationsResult(
search_type="spatial",
count=len(spatial_stations),
stations=spatial_stations[:limit],
)
if query is not None:
search_results = index.search(query, limit=limit * 3)
text_stations: list[dict[str, Any]] = []
for r in search_results:
if not _matches_filters(r.station, country, state):
continue
text_stations.append({
**serialize_station(r.station),
"score": round(r.score, 3),
"match_field": r.match_field,
})
if len(text_stations) >= limit:
break
logger.debug("search_weather_stations: query=%r found=%d", query, len(text_stations))
return SearchWeatherStationsResult(
search_type="text",
query=query,
count=len(text_stations),
stations=text_stations,
)
raise ToolError("Provide either 'query' for text search or 'latitude'/'longitude' for spatial search.")