接入巴黎 Le Bourget AROME HD 15分钟模型预报数据

Météo-France 站点实测无法获取,改为通过 Open-Meteo 取 AROME France HD
15分钟模型温度。非实测数据,标注为模型预报类型。
巴黎跳过 obs_log 峰值检测(无站点观测日志)。

Constraint: AROME 为模型格点值非实测,与 AMOS/JMA/FMI 精度不同
Tested: pytest 176 passed, AROME API 实测返回 16.2°C
This commit is contained in:
2569718930@qq.com
2026-05-12 23:59:06 +08:00
parent a3bef5185e
commit 6f83b9d7ea
4 changed files with 35 additions and 8 deletions
+1 -1
View File
@@ -11,6 +11,7 @@
| 伊斯坦布尔 | 伊斯坦布尔机场 | 17058 | MGM (`servis.mgm.gov.tr`) | 5-15 分钟 | 机场站点实时温度 | 免费 |
| 赫尔辛基 | Vantaa | EFHK | FMI (`opendata.fmi.fi`) | 10 分钟 | 机场站点实时温度 | 免费 |
| 阿姆斯特丹 | Schiphol | EHAM | KNMI (`dataplatform.knmi.nl`) | 10 分钟 | 机场站点实时温度 | 免费(需注册) |
| 巴黎 | Le Bourget | LFPB | AROME HD (`api.open-meteo.com`) | 15 分钟 | 模型预报(非实测) | 免费 |
## 推送机制
@@ -43,7 +44,6 @@ Seoul / Incheon 16:03
| 城市 | 原因 |
|------|------|
| 巴黎/Le Bourget | Météo-France 注册认证失败 |
| 马德里/Barajas | AEMET 注册页面失效 |
| 伦敦/Heathrow | Met Office 仅 1 小时更新 |
| 慕尼黑 | DWD 延迟 ~1 小时 |
+1 -1
View File
@@ -813,7 +813,7 @@ def _build_advice_cn(
return "".join(parts) + ""
_AIRPORT_ICAO_MAP = {"seoul": "RKSI", "busan": "RKPK", "tokyo": "RJTT", "ankara": "17128", "helsinki": "EFHK", "amsterdam": "EHAM", "istanbul": "17058"}
_AIRPORT_ICAO_MAP = {"seoul": "RKSI", "busan": "RKPK", "tokyo": "RJTT", "ankara": "17128", "helsinki": "EFHK", "amsterdam": "EHAM", "istanbul": "17058", "paris": "LFPB"}
def _calc_airport_rapid_temp_change(
+1 -1
View File
@@ -190,7 +190,7 @@ class StartupCoordinator:
details = {
"mode": "airport-periodic",
"interval_sec": interval,
"cities": ["seoul", "busan", "tokyo", "ankara", "helsinki", "amsterdam", "istanbul"],
"cities": ["seoul", "busan", "tokyo", "ankara", "helsinki", "amsterdam", "istanbul", "paris"],
"chat_targets": len(chat_ids),
"window": "DEB proximity ≤3°C",
}
+32 -5
View File
@@ -690,8 +690,8 @@ def start_trade_alert_push_loop(bot: Any, config: Dict[str, Any]) -> Optional[th
# ── high-freq airport push loop ──
HIGH_FREQ_AIRPORT_CITIES = {"seoul", "busan", "tokyo", "ankara", "helsinki", "amsterdam", "istanbul"}
HIGH_FREQ_AIRPORT_ICAO = {"seoul": "RKSI", "busan": "RKPK", "tokyo": "RJTT", "ankara": "17128", "helsinki": "EFHK", "amsterdam": "EHAM", "istanbul": "17058"}
HIGH_FREQ_AIRPORT_CITIES = {"seoul", "busan", "tokyo", "ankara", "helsinki", "amsterdam", "istanbul", "paris"}
HIGH_FREQ_AIRPORT_ICAO = {"seoul": "RKSI", "busan": "RKPK", "tokyo": "RJTT", "ankara": "17128", "helsinki": "EFHK", "amsterdam": "EHAM", "istanbul": "17058", "paris": "LFPB"}
_AIRPORT_PUSH_STATE_PATH = os.path.join(
os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))),
@@ -723,6 +723,26 @@ def _save_airport_state(state: Dict[str, Any]) -> None:
os.replace(tmp, path)
def _fetch_arome_temp() -> Optional[float]:
"""Fetch latest AROME France HD 15-min temperature for LFPB from Open-Meteo."""
try:
import requests
url = (
"https://api.open-meteo.com/v1/forecast?"
"latitude=48.9673&longitude=2.4277"
"&models=meteofrance_arome_france_hd"
"&minutely_15=temperature_2m"
"&timezone=Europe/Paris"
"&forecast_minutely_15=2"
)
resp = requests.get(url, timeout=8)
data = resp.json()
temps = (data.get("minutely_15") or {}).get("temperature_2m") or []
return float(temps[-1]) if temps else None
except Exception:
return None
def _build_airport_status_message(
city: str,
city_weather: Dict[str, Any],
@@ -731,7 +751,7 @@ def _build_airport_status_message(
) -> str:
_AIRPORT_EN = {"seoul": "Incheon", "busan": "Gimhae", "tokyo": "Haneda",
"ankara": "Esenboğa", "helsinki": "Vantaa", "amsterdam": "Schiphol",
"istanbul": "Airport"}
"istanbul": "Airport", "paris": "Le Bourget"}
en_name = city.title()
ap_name = _AIRPORT_EN.get(city, "")
time_suffix = f" {local_time}" if local_time else ""
@@ -792,6 +812,7 @@ _AIRPORT_PUSH_INTERVAL = {
"helsinki": 600, # FMI 10-min
"amsterdam": 600, # KNMI 10-min
"istanbul": 600, # MGM ~10-min
"paris": 900, # AROME HD 15-min model
}
_DEB_PROXIMITY_THRESHOLD_C = 3.0
@@ -827,6 +848,12 @@ def _run_high_freq_airport_cycle(
current = city_weather.get("current") or {}
current_temp = current.get("temp")
# Paris: use AROME 15-min model temp instead of METAR
if city == "paris":
arome_temp = _fetch_arome_temp()
if arome_temp is not None:
current_temp = arome_temp
city_weather.setdefault("current", {})["temp"] = arome_temp
if current_temp is None or deb_pred is None:
continue
@@ -834,8 +861,8 @@ def _run_high_freq_airport_cycle(
proximity = deb_pred - current_temp
in_window = proximity <= _DEB_PROXIMITY_THRESHOLD_C
# Stop if past peak: current below daily max in recent hour and declining
if in_window:
# Stop if past peak (skip for Paris: no obs_log for AROME model data)
if in_window and city != "paris":
try:
from src.database.db_manager import DBManager
icao = HIGH_FREQ_AIRPORT_ICAO.get(city, "")