Add Warsaw official nearby station data
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "PolyWeather Side Panel",
|
"name": "PolyWeather Side Panel",
|
||||||
"description": "PolyWeather 右侧城市卡片(浏览器侧边栏)",
|
"description": "PolyWeather 右侧城市卡片(浏览器侧边栏)",
|
||||||
"version": "0.1.1",
|
"version": "0.1.2",
|
||||||
"icons": {
|
"icons": {
|
||||||
"16": "icon-16.png",
|
"16": "icon-16.png",
|
||||||
"32": "icon-32.png",
|
"32": "icon-32.png",
|
||||||
|
|||||||
@@ -267,6 +267,7 @@ export interface CityDetail {
|
|||||||
current: CurrentConditions;
|
current: CurrentConditions;
|
||||||
mgm?: MgmData;
|
mgm?: MgmData;
|
||||||
mgm_nearby?: NearbyStation[];
|
mgm_nearby?: NearbyStation[];
|
||||||
|
nearby_source?: string;
|
||||||
forecast?: ForecastData;
|
forecast?: ForecastData;
|
||||||
multi_model?: Record<string, number | null>;
|
multi_model?: Record<string, number | null>;
|
||||||
deb?: DebForecast;
|
deb?: DebForecast;
|
||||||
|
|||||||
@@ -1132,11 +1132,16 @@ export function getShortTermNowcastLines(
|
|||||||
? detail.metar_recent_obs.slice(-4)
|
? detail.metar_recent_obs.slice(-4)
|
||||||
: [];
|
: [];
|
||||||
const nearby = Array.isArray(detail.mgm_nearby) ? detail.mgm_nearby : [];
|
const nearby = Array.isArray(detail.mgm_nearby) ? detail.mgm_nearby : [];
|
||||||
|
const nearbySource = String(detail.nearby_source || "").toLowerCase();
|
||||||
const sourceLabel =
|
const sourceLabel =
|
||||||
detail.name === "ankara"
|
nearbySource === "mgm" || detail.name === "ankara"
|
||||||
? isEnglish(locale)
|
? isEnglish(locale)
|
||||||
? "MGM nearby stations"
|
? "MGM nearby stations"
|
||||||
: "MGM 周边站"
|
: "MGM 周边站"
|
||||||
|
: nearbySource === "official_cluster"
|
||||||
|
? isEnglish(locale)
|
||||||
|
? "Official nearby stations"
|
||||||
|
: "官方周边站"
|
||||||
: isEnglish(locale)
|
: isEnglish(locale)
|
||||||
? "METAR nearby stations"
|
? "METAR nearby stations"
|
||||||
: "METAR 周边站";
|
: "METAR 周边站";
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ from loguru import logger
|
|||||||
|
|
||||||
|
|
||||||
class SettlementSourceMixin:
|
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]]:
|
def _get_settlement_cache(self, key: str) -> Optional[Dict[str, Any]]:
|
||||||
now_ts = time.time()
|
now_ts = time.time()
|
||||||
with self._settlement_cache_lock:
|
with self._settlement_cache_lock:
|
||||||
@@ -260,6 +263,84 @@ class SettlementSourceMixin:
|
|||||||
logger.warning(f"CWA Forecast request failed: {exc}")
|
logger.warning(f"CWA Forecast request failed: {exc}")
|
||||||
return None
|
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]]:
|
def fetch_settlement_current(self, city: str) -> Optional[Dict[str, Any]]:
|
||||||
normalized = str(city or "").strip().lower()
|
normalized = str(city or "").strip().lower()
|
||||||
if normalized == "hong kong":
|
if normalized == "hong kong":
|
||||||
|
|||||||
@@ -585,6 +585,32 @@ class WeatherDataCollector(OpenMeteoCacheMixin, SettlementSourceMixin, MetarSour
|
|||||||
)
|
)
|
||||||
if cluster_data:
|
if cluster_data:
|
||||||
results["mgm_nearby"] = 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(
|
def _attach_nws_and_models(
|
||||||
self,
|
self,
|
||||||
@@ -649,6 +675,8 @@ class WeatherDataCollector(OpenMeteoCacheMixin, SettlementSourceMixin, MetarSour
|
|||||||
results["metar"] = metar_data
|
results["metar"] = metar_data
|
||||||
|
|
||||||
self._attach_turkish_mgm_data(results, city_lower)
|
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(
|
self._attach_global_nearby_cluster(
|
||||||
results, city_lower, use_fahrenheit
|
results, city_lower, use_fahrenheit
|
||||||
)
|
)
|
||||||
@@ -668,6 +696,8 @@ class WeatherDataCollector(OpenMeteoCacheMixin, SettlementSourceMixin, MetarSour
|
|||||||
results["metar"] = metar_data
|
results["metar"] = metar_data
|
||||||
|
|
||||||
self._attach_turkish_mgm_data(results, city_lower)
|
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(
|
self._attach_global_nearby_cluster(
|
||||||
results, city_lower, use_fahrenheit
|
results, city_lower, use_fahrenheit
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -635,6 +635,7 @@ def _analyze(city: str, force_refresh: bool = False) -> Dict[str, Any]:
|
|||||||
},
|
},
|
||||||
"mgm": mgm_data,
|
"mgm": mgm_data,
|
||||||
"mgm_nearby": raw.get("mgm_nearby", []),
|
"mgm_nearby": raw.get("mgm_nearby", []),
|
||||||
|
"nearby_source": raw.get("nearby_source") or ("mgm" if city.lower() == "ankara" else "metar_cluster"),
|
||||||
"forecast": {
|
"forecast": {
|
||||||
"today_high": om_today,
|
"today_high": om_today,
|
||||||
"daily": forecast_daily,
|
"daily": forecast_daily,
|
||||||
@@ -837,7 +838,7 @@ def _build_city_detail_payload(
|
|||||||
"weather_gov": {},
|
"weather_gov": {},
|
||||||
"mgm": data.get("mgm") or {},
|
"mgm": data.get("mgm") or {},
|
||||||
"mgm_nearby": data.get("mgm_nearby") 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": {
|
"timeseries": {
|
||||||
"metar_recent_obs": data.get("metar_recent_obs") or [],
|
"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": []},
|
"dynamic_commentary": data.get("dynamic_commentary") or {"summary": "", "notes": []},
|
||||||
"market_scan": market_scan,
|
"market_scan": market_scan,
|
||||||
"risk": data.get("risk"),
|
"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 "",
|
"ai_analysis": data.get("ai_analysis") or "",
|
||||||
"errors": {},
|
"errors": {},
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user