From 30b289ec8fe02ec5a64692e568882e3dbbec87b7 Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Wed, 13 May 2026 13:45:00 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=9C=BA=E5=9C=BA=E9=AB=98?= =?UTF-8?q?=E9=A2=91=E6=8E=A8=E9=80=81=EF=BC=9A=E4=B8=9C=E4=BA=ACICAO?= =?UTF-8?q?=E4=B8=8D=E5=8C=B9=E9=85=8D=E3=80=81=E9=87=9C=E5=B1=B1=E7=BC=BA?= =?UTF-8?q?=E8=B6=8B=E5=8A=BF=E6=95=B0=E6=8D=AE=E3=80=81=E6=B8=AF/?= =?UTF-8?q?=E6=B5=81=E6=B5=AE=E9=A2=91=E7=8E=87=E5=8F=8Aobs=5Ftime?= =?UTF-8?q?=E5=8E=BB=E9=87=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 东京 HIGH_FREQ_AIRPORT_ICAO 从 RJTT 改为 JMA 站号 44166 - 釜山 METAR 集群数据写入 airport_obs_log 供趋势检测 - 香港/流浮山推送间隔从 60s 改为 600s(匹配实际 10min 更新) - 新增 obs_time 去重:同一观测数据不重复推送 Constraint: JMA AMeDAS 使用站号而非 ICAO 作为站点标识 Tested: ruff check 通过 --- src/data_collection/metar_sources.py | 1 + src/data_collection/weather_sources.py | 17 +++++++++++++++++ src/utils/telegram_push.py | 19 +++++++++++++------ 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/data_collection/metar_sources.py b/src/data_collection/metar_sources.py index 7fbb6d1f..f611f266 100644 --- a/src/data_collection/metar_sources.py +++ b/src/data_collection/metar_sources.py @@ -453,6 +453,7 @@ class MetarSourceMixin: "lat": lat, "lon": lon, "temp": round(display_temp, 1), + "temp_c": temp_c, "istNo": icao, "icao": icao, "wind_dir": obs.get("wdir"), diff --git a/src/data_collection/weather_sources.py b/src/data_collection/weather_sources.py index 8cd5c979..1b0783a7 100644 --- a/src/data_collection/weather_sources.py +++ b/src/data_collection/weather_sources.py @@ -866,6 +866,23 @@ class WeatherDataCollector(OpenMeteoCacheMixin, SettlementSourceMixin, MetarSour if cluster_data: results["mgm_nearby"] = cluster_data results["nearby_source"] = "metar_cluster" + # 写入 airport_obs_log,供高频推送的趋势检测使用 + try: + from src.database.db_manager import DBManager + db = DBManager() + for row in cluster_data: + icao = row.get("icao") + temp_c = row.get("temp_c") if "temp_c" in row else row.get("temp") + if icao and temp_c is not None: + db.append_airport_obs( + icao=str(icao), + city=city_lower, + temp_c=temp_c, + wind_kt=row.get("wind_speed_kt"), + obs_time=str(row.get("obs_time") or datetime.now().isoformat()), + ) + except Exception: + logger.exception("airport_obs_log append failed for metar cluster city={}", city_lower) def _attach_china_official_nearby( self, results: Dict, city_lower: str, use_fahrenheit: bool diff --git a/src/utils/telegram_push.py b/src/utils/telegram_push.py index dca46e66..196e3a0d 100644 --- a/src/utils/telegram_push.py +++ b/src/utils/telegram_push.py @@ -691,7 +691,7 @@ 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", "paris", "hong kong", "lau fau shan", "taipei"} -HIGH_FREQ_AIRPORT_ICAO = {"seoul": "RKSI", "busan": "RKPK", "tokyo": "RJTT", "ankara": "17128", "helsinki": "EFHK", "amsterdam": "EHAM", "istanbul": "17058", "paris": "LFPB", "hong kong": "HKO", "lau fau shan": "LFS", "taipei": "466920"} +HIGH_FREQ_AIRPORT_ICAO = {"seoul": "RKSI", "busan": "RKPK", "tokyo": "44166", "ankara": "17128", "helsinki": "EFHK", "amsterdam": "EHAM", "istanbul": "17058", "paris": "LFPB", "hong kong": "HKO", "lau fau shan": "LFS", "taipei": "466920"} _AIRPORT_PUSH_STATE_PATH = os.path.join( os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), @@ -837,8 +837,8 @@ _AIRPORT_PUSH_INTERVAL = { "amsterdam": 600, # KNMI 10-min "istanbul": 600, # MGM ~10-min "paris": 900, # AROME HD 15-min model - "hong kong": 60, # HKO 1-min - "lau fau shan": 60, # HKO 1-min + "hong kong": 600, # HKO 10-min + "lau fau shan": 600, # HKO 10-min "taipei": 600, # CWA ~10-min } # Per-city temperature window threshold (°C below DEB predicted high) @@ -910,6 +910,7 @@ def _run_high_freq_airport_cycle( try: last_city = last_by_city.get(city) or {} last_city_ts = int(last_city.get("ts") or 0) + last_obs_time = str(last_city.get("obs_time") or "") city_interval = _AIRPORT_PUSH_INTERVAL.get(city, 600) if now_ts - last_city_ts < city_interval: continue @@ -937,12 +938,14 @@ def _run_high_freq_airport_cycle( if not airport_row: airport_row = mgm_nearby[0] if mgm_nearby else {} station_temp = airport_row.get("temp") if airport_row else None + current_obs_time = str(airport_row.get("obs_time") or "") runway_temps = (amos.get("runway_obs") or {}).get("temperatures") or [] if runway_temps: valid_temps = [t for (t, _d) in runway_temps if t is not None] if valid_temps: station_temp = max(valid_temps) + # 跑道数据没有独立的 obs_time,保持 current_obs_time current_temp = station_temp if current_temp is None: @@ -955,6 +958,10 @@ def _run_high_freq_airport_cycle( if current_temp is None or deb_pred is None: continue + # 基于原始观测数据时间的去重:同一条观测不重复推送 + if current_obs_time and last_obs_time and current_obs_time == last_obs_time: + continue + # ── Three-condition heat window ── threshold = _AIRPORT_HEAT_THRESHOLD.get(city, 3.0) time_ok = _in_peak_time_window(city_weather) @@ -965,7 +972,7 @@ def _run_high_freq_airport_cycle( if not in_window: if last_city.get("active"): - last_by_city[city] = {"ts": now_ts, "active": False} + last_by_city[city] = {"ts": now_ts, "active": False, "obs_time": current_obs_time} state_dirty = True continue @@ -981,9 +988,9 @@ def _run_high_freq_airport_cycle( logger.warning("airport push failed city={} chat_id={}: {}", city, chat_id, exc) if sent: - last_by_city[city] = {"ts": now_ts, "active": True} + last_by_city[city] = {"ts": now_ts, "active": True, "obs_time": current_obs_time} state_dirty = True - logger.info("airport status pushed city={} temp={} deb={} thresh={}", city, current_temp, deb_pred, threshold) + logger.info("airport status pushed city={} temp={} deb={} thresh={} obs_time={}", city, current_temp, deb_pred, threshold, current_obs_time) except Exception: logger.exception("airport cycle failed for city={}", city)