Add Warsaw official nearby station data
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
"manifest_version": 3,
|
||||
"name": "PolyWeather Side Panel",
|
||||
"description": "PolyWeather 右侧城市卡片(浏览器侧边栏)",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.2",
|
||||
"icons": {
|
||||
"16": "icon-16.png",
|
||||
"32": "icon-32.png",
|
||||
|
||||
@@ -267,6 +267,7 @@ export interface CityDetail {
|
||||
current: CurrentConditions;
|
||||
mgm?: MgmData;
|
||||
mgm_nearby?: NearbyStation[];
|
||||
nearby_source?: string;
|
||||
forecast?: ForecastData;
|
||||
multi_model?: Record<string, number | null>;
|
||||
deb?: DebForecast;
|
||||
|
||||
@@ -1132,11 +1132,16 @@ export function getShortTermNowcastLines(
|
||||
? detail.metar_recent_obs.slice(-4)
|
||||
: [];
|
||||
const nearby = Array.isArray(detail.mgm_nearby) ? detail.mgm_nearby : [];
|
||||
const nearbySource = String(detail.nearby_source || "").toLowerCase();
|
||||
const sourceLabel =
|
||||
detail.name === "ankara"
|
||||
nearbySource === "mgm" || detail.name === "ankara"
|
||||
? isEnglish(locale)
|
||||
? "MGM nearby stations"
|
||||
: "MGM 周边站"
|
||||
: nearbySource === "official_cluster"
|
||||
? isEnglish(locale)
|
||||
? "Official nearby stations"
|
||||
: "官方周边站"
|
||||
: isEnglish(locale)
|
||||
? "METAR nearby stations"
|
||||
: "METAR 周边站";
|
||||
|
||||
@@ -9,6 +9,9 @@ from loguru import logger
|
||||
|
||||
|
||||
class SettlementSourceMixin:
|
||||
IMGW_METEO_API_BASE = "https://meteo.imgw.pl/api/v1"
|
||||
IMGW_METEO_API_TOKEN = "p4DXKjsYadfBV21TYrDk"
|
||||
|
||||
def _get_settlement_cache(self, key: str) -> Optional[Dict[str, Any]]:
|
||||
now_ts = time.time()
|
||||
with self._settlement_cache_lock:
|
||||
@@ -260,6 +263,84 @@ class SettlementSourceMixin:
|
||||
logger.warning(f"CWA Forecast request failed: {exc}")
|
||||
return None
|
||||
|
||||
def _imgw_api_get(self, path: str, params: Optional[Dict[str, Any]] = None) -> Optional[Dict[str, Any]]:
|
||||
try:
|
||||
query = {"token": self.IMGW_METEO_API_TOKEN}
|
||||
if isinstance(params, dict):
|
||||
query.update(params)
|
||||
response = self.session.get(
|
||||
f"{self.IMGW_METEO_API_BASE}/{path}",
|
||||
params=query,
|
||||
timeout=self.timeout,
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json() if response.content else None
|
||||
except Exception as exc:
|
||||
logger.warning(f"IMGW API request failed path={path}: {exc}")
|
||||
return None
|
||||
|
||||
def fetch_imgw_synoptic_station_current(
|
||||
self,
|
||||
localization: str,
|
||||
*,
|
||||
display_name: Optional[str] = None,
|
||||
use_fahrenheit: bool = False,
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
cache_key = f"imgw:synoptic:{str(localization or '').strip().lower()}"
|
||||
cached = self._get_settlement_cache(cache_key)
|
||||
if cached:
|
||||
return cached
|
||||
|
||||
location_payload = self._imgw_api_get("geo/search", {"name": localization})
|
||||
location_rows = (location_payload or {}).get("data") or []
|
||||
if not isinstance(location_rows, list) or not location_rows:
|
||||
return None
|
||||
location_row = location_rows[0] if isinstance(location_rows[0], dict) else None
|
||||
if not isinstance(location_row, dict):
|
||||
return None
|
||||
|
||||
date_str = datetime.now(timezone.utc).strftime("%Y-%m-%d")
|
||||
synoptic_payload = self._imgw_api_get(
|
||||
"forecast/synoptic",
|
||||
{"date": date_str, "localization": localization},
|
||||
)
|
||||
data = (synoptic_payload or {}).get("data") or {}
|
||||
if not isinstance(data, dict) or not data:
|
||||
return None
|
||||
|
||||
latest_row = None
|
||||
for key in sorted(data.keys()):
|
||||
row = data.get(key)
|
||||
if isinstance(row, dict) and row.get("temp") is not None:
|
||||
latest_row = row
|
||||
if not isinstance(latest_row, dict):
|
||||
return None
|
||||
|
||||
temp_kelvin = self._safe_float(latest_row.get("temp"))
|
||||
if temp_kelvin is None:
|
||||
return None
|
||||
temp_c = temp_kelvin - 273.15
|
||||
temp_value = (temp_c * 9 / 5) + 32 if use_fahrenheit else temp_c
|
||||
wind_speed_ms = self._safe_float(latest_row.get("ws"))
|
||||
|
||||
payload = {
|
||||
"name": display_name or str(location_row.get("name") or localization),
|
||||
"lat": self._safe_float(location_row.get("lat")),
|
||||
"lon": self._safe_float(location_row.get("lon")),
|
||||
"temp": round(temp_value, 1),
|
||||
"icao": "IMGW",
|
||||
"istNo": "IMGW",
|
||||
"wind_dir": self._safe_float(latest_row.get("wd")),
|
||||
"wind_speed": wind_speed_ms,
|
||||
"wind_speed_kt": round(float(wind_speed_ms) * 1.943844, 1) if wind_speed_ms is not None else None,
|
||||
"source": "imgw_synoptic",
|
||||
"raw_time": latest_row.get("fd"),
|
||||
}
|
||||
if payload["lat"] is None or payload["lon"] is None:
|
||||
return None
|
||||
self._set_settlement_cache(cache_key, payload)
|
||||
return payload
|
||||
|
||||
def fetch_settlement_current(self, city: str) -> Optional[Dict[str, Any]]:
|
||||
normalized = str(city or "").strip().lower()
|
||||
if normalized == "hong kong":
|
||||
|
||||
@@ -585,6 +585,32 @@ class WeatherDataCollector(OpenMeteoCacheMixin, SettlementSourceMixin, MetarSour
|
||||
)
|
||||
if cluster_data:
|
||||
results["mgm_nearby"] = cluster_data
|
||||
results["nearby_source"] = "metar_cluster"
|
||||
|
||||
def _attach_warsaw_official_nearby(
|
||||
self, results: Dict, use_fahrenheit: bool
|
||||
) -> None:
|
||||
if "mgm_nearby" in results:
|
||||
return
|
||||
|
||||
official_rows = []
|
||||
epwa_rows = self.fetch_metar_nearby_cluster(["EPWA"], use_fahrenheit=use_fahrenheit)
|
||||
if epwa_rows:
|
||||
epwa = dict(epwa_rows[0])
|
||||
epwa["name"] = "Warszawa-Okęcie (EPWA)"
|
||||
official_rows.append(epwa)
|
||||
|
||||
imgw_row = self.fetch_imgw_synoptic_station_current(
|
||||
"Warszawa",
|
||||
display_name="Warszawa (IMGW synoptic)",
|
||||
use_fahrenheit=use_fahrenheit,
|
||||
)
|
||||
if imgw_row:
|
||||
official_rows.append(imgw_row)
|
||||
|
||||
if official_rows:
|
||||
results["mgm_nearby"] = official_rows
|
||||
results["nearby_source"] = "official_cluster"
|
||||
|
||||
def _attach_nws_and_models(
|
||||
self,
|
||||
@@ -649,6 +675,8 @@ class WeatherDataCollector(OpenMeteoCacheMixin, SettlementSourceMixin, MetarSour
|
||||
results["metar"] = metar_data
|
||||
|
||||
self._attach_turkish_mgm_data(results, city_lower)
|
||||
if city_lower == "warsaw":
|
||||
self._attach_warsaw_official_nearby(results, use_fahrenheit)
|
||||
self._attach_global_nearby_cluster(
|
||||
results, city_lower, use_fahrenheit
|
||||
)
|
||||
@@ -668,6 +696,8 @@ class WeatherDataCollector(OpenMeteoCacheMixin, SettlementSourceMixin, MetarSour
|
||||
results["metar"] = metar_data
|
||||
|
||||
self._attach_turkish_mgm_data(results, city_lower)
|
||||
if city_lower == "warsaw":
|
||||
self._attach_warsaw_official_nearby(results, use_fahrenheit)
|
||||
self._attach_global_nearby_cluster(
|
||||
results, city_lower, use_fahrenheit
|
||||
)
|
||||
|
||||
@@ -635,6 +635,7 @@ def _analyze(city: str, force_refresh: bool = False) -> Dict[str, Any]:
|
||||
},
|
||||
"mgm": mgm_data,
|
||||
"mgm_nearby": raw.get("mgm_nearby", []),
|
||||
"nearby_source": raw.get("nearby_source") or ("mgm" if city.lower() == "ankara" else "metar_cluster"),
|
||||
"forecast": {
|
||||
"today_high": om_today,
|
||||
"daily": forecast_daily,
|
||||
@@ -837,7 +838,7 @@ def _build_city_detail_payload(
|
||||
"weather_gov": {},
|
||||
"mgm": data.get("mgm") or {},
|
||||
"mgm_nearby": data.get("mgm_nearby") or [],
|
||||
"nearby_source": "mgm" if data.get("name") == "ankara" else "metar_cluster",
|
||||
"nearby_source": data.get("nearby_source") or ("mgm" if data.get("name") == "ankara" else "metar_cluster"),
|
||||
},
|
||||
"timeseries": {
|
||||
"metar_recent_obs": data.get("metar_recent_obs") or [],
|
||||
@@ -856,6 +857,7 @@ def _build_city_detail_payload(
|
||||
"dynamic_commentary": data.get("dynamic_commentary") or {"summary": "", "notes": []},
|
||||
"market_scan": market_scan,
|
||||
"risk": data.get("risk"),
|
||||
"nearby_source": data.get("nearby_source") or ("mgm" if data.get("name") == "ankara" else "metar_cluster"),
|
||||
"ai_analysis": data.get("ai_analysis") or "",
|
||||
"errors": {},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user