修复机场高频推送:东京ICAO不匹配、釜山缺趋势数据、港/流浮频率及obs_time去重
- 东京 HIGH_FREQ_AIRPORT_ICAO 从 RJTT 改为 JMA 站号 44166 - 釜山 METAR 集群数据写入 airport_obs_log 供趋势检测 - 香港/流浮山推送间隔从 60s 改为 600s(匹配实际 10min 更新) - 新增 obs_time 去重:同一观测数据不重复推送 Constraint: JMA AMeDAS 使用站号而非 ICAO 作为站点标识 Tested: ruff check 通过
This commit is contained in:
@@ -453,6 +453,7 @@ class MetarSourceMixin:
|
|||||||
"lat": lat,
|
"lat": lat,
|
||||||
"lon": lon,
|
"lon": lon,
|
||||||
"temp": round(display_temp, 1),
|
"temp": round(display_temp, 1),
|
||||||
|
"temp_c": temp_c,
|
||||||
"istNo": icao,
|
"istNo": icao,
|
||||||
"icao": icao,
|
"icao": icao,
|
||||||
"wind_dir": obs.get("wdir"),
|
"wind_dir": obs.get("wdir"),
|
||||||
|
|||||||
@@ -866,6 +866,23 @@ 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"
|
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(
|
def _attach_china_official_nearby(
|
||||||
self, results: Dict, city_lower: str, use_fahrenheit: bool
|
self, results: Dict, city_lower: str, use_fahrenheit: bool
|
||||||
|
|||||||
@@ -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 push loop ──
|
||||||
|
|
||||||
HIGH_FREQ_AIRPORT_CITIES = {"seoul", "busan", "tokyo", "ankara", "helsinki", "amsterdam", "istanbul", "paris", "hong kong", "lau fau shan", "taipei"}
|
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(
|
_AIRPORT_PUSH_STATE_PATH = os.path.join(
|
||||||
os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))),
|
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
|
"amsterdam": 600, # KNMI 10-min
|
||||||
"istanbul": 600, # MGM ~10-min
|
"istanbul": 600, # MGM ~10-min
|
||||||
"paris": 900, # AROME HD 15-min model
|
"paris": 900, # AROME HD 15-min model
|
||||||
"hong kong": 60, # HKO 1-min
|
"hong kong": 600, # HKO 10-min
|
||||||
"lau fau shan": 60, # HKO 1-min
|
"lau fau shan": 600, # HKO 10-min
|
||||||
"taipei": 600, # CWA ~10-min
|
"taipei": 600, # CWA ~10-min
|
||||||
}
|
}
|
||||||
# Per-city temperature window threshold (°C below DEB predicted high)
|
# Per-city temperature window threshold (°C below DEB predicted high)
|
||||||
@@ -910,6 +910,7 @@ def _run_high_freq_airport_cycle(
|
|||||||
try:
|
try:
|
||||||
last_city = last_by_city.get(city) or {}
|
last_city = last_by_city.get(city) or {}
|
||||||
last_city_ts = int(last_city.get("ts") or 0)
|
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)
|
city_interval = _AIRPORT_PUSH_INTERVAL.get(city, 600)
|
||||||
if now_ts - last_city_ts < city_interval:
|
if now_ts - last_city_ts < city_interval:
|
||||||
continue
|
continue
|
||||||
@@ -937,12 +938,14 @@ def _run_high_freq_airport_cycle(
|
|||||||
if not airport_row:
|
if not airport_row:
|
||||||
airport_row = mgm_nearby[0] if mgm_nearby else {}
|
airport_row = mgm_nearby[0] if mgm_nearby else {}
|
||||||
station_temp = airport_row.get("temp") if airport_row else None
|
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 []
|
runway_temps = (amos.get("runway_obs") or {}).get("temperatures") or []
|
||||||
if runway_temps:
|
if runway_temps:
|
||||||
valid_temps = [t for (t, _d) in runway_temps if t is not None]
|
valid_temps = [t for (t, _d) in runway_temps if t is not None]
|
||||||
if valid_temps:
|
if valid_temps:
|
||||||
station_temp = max(valid_temps)
|
station_temp = max(valid_temps)
|
||||||
|
# 跑道数据没有独立的 obs_time,保持 current_obs_time
|
||||||
|
|
||||||
current_temp = station_temp
|
current_temp = station_temp
|
||||||
if current_temp is None:
|
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:
|
if current_temp is None or deb_pred is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# 基于原始观测数据时间的去重:同一条观测不重复推送
|
||||||
|
if current_obs_time and last_obs_time and current_obs_time == last_obs_time:
|
||||||
|
continue
|
||||||
|
|
||||||
# ── Three-condition heat window ──
|
# ── Three-condition heat window ──
|
||||||
threshold = _AIRPORT_HEAT_THRESHOLD.get(city, 3.0)
|
threshold = _AIRPORT_HEAT_THRESHOLD.get(city, 3.0)
|
||||||
time_ok = _in_peak_time_window(city_weather)
|
time_ok = _in_peak_time_window(city_weather)
|
||||||
@@ -965,7 +972,7 @@ def _run_high_freq_airport_cycle(
|
|||||||
|
|
||||||
if not in_window:
|
if not in_window:
|
||||||
if last_city.get("active"):
|
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
|
state_dirty = True
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -981,9 +988,9 @@ def _run_high_freq_airport_cycle(
|
|||||||
logger.warning("airport push failed city={} chat_id={}: {}", city, chat_id, exc)
|
logger.warning("airport push failed city={} chat_id={}: {}", city, chat_id, exc)
|
||||||
|
|
||||||
if sent:
|
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
|
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:
|
except Exception:
|
||||||
logger.exception("airport cycle failed for city={}", city)
|
logger.exception("airport cycle failed for city={}", city)
|
||||||
|
|||||||
Reference in New Issue
Block a user