From a5522b4b16141c45d13296795b8a23cda684950e Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Tue, 12 May 2026 19:08:44 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8E=A8=E9=80=81=E7=AA=97=E5=8F=A3=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=20DEB=20=E6=9B=B2=E7=BA=BF=E5=8A=A8=E6=80=81=E5=88=A4?= =?UTF-8?q?=E6=96=AD=EF=BC=8C=E6=9B=BF=E4=BB=A3=E5=9B=BA=E5=AE=9A=2008:00-?= =?UTF-8?q?20:00?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 当前温度距 DEB 预测最高 ≤3°C 时开始推送,一旦确认已过峰值 (近 1h 内最高已过且回落 >0.5°C)自动停止。不再依赖固定时段。 Constraint: DEB 预测本身就是每日最高温,比时钟判断更贴合实际峰值 Scope-risk: 中,核心推送逻辑变更 Tested: ruff check 通过 --- src/bot/runtime_coordinator.py | 2 +- src/utils/telegram_push.py | 48 +++++++++++++++++++++++++--------- 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/src/bot/runtime_coordinator.py b/src/bot/runtime_coordinator.py index 5781acd2..16e175c8 100644 --- a/src/bot/runtime_coordinator.py +++ b/src/bot/runtime_coordinator.py @@ -192,7 +192,7 @@ class StartupCoordinator: "interval_sec": interval, "cities": ["seoul", "busan", "tokyo", "ankara"], "chat_targets": len(chat_ids), - "daytime_only": "08:00-20:00 local", + "window": "DEB proximity ≤3°C", } validation_error = None if chat_ids else "missing_TELEGRAM_CHAT_IDS" return self._start_with_validation( diff --git a/src/utils/telegram_push.py b/src/utils/telegram_push.py index e2ed177b..58efb5f0 100644 --- a/src/utils/telegram_push.py +++ b/src/utils/telegram_push.py @@ -753,8 +753,7 @@ def _build_airport_status_message( _AIRPORT_PUSH_INTERVAL_SEC = 120 -_AIRPORT_PUSH_HOUR_START = 8 -_AIRPORT_PUSH_HOUR_END = 20 +_DEB_PROXIMITY_THRESHOLD_C = 3.0 def _run_high_freq_airport_cycle( @@ -763,8 +762,6 @@ def _run_high_freq_airport_cycle( chat_ids: List[str], state: Dict[str, Any], ) -> bool: - from datetime import timedelta - state_dirty = False now_ts = int(time.time()) last_by_city = state.setdefault("last_by_city", {}) @@ -787,11 +784,39 @@ def _run_high_freq_airport_cycle( except Exception: pass - # Time gate: skip outside local daytime - tz_offsets = {"seoul": 9, "busan": 9, "tokyo": 9, "ankara": 3} - offset = tz_offsets.get(city, 0) - local_hour = (datetime.utcnow() + timedelta(hours=offset)).hour - if not (_AIRPORT_PUSH_HOUR_START <= local_hour < _AIRPORT_PUSH_HOUR_END): + current = city_weather.get("current") or {} + current_temp = current.get("temp") + if current_temp is None or deb_pred is None: + continue + + # Push only when approaching DEB predicted high + 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: + try: + from src.database.db_manager import DBManager + icao = HIGH_FREQ_AIRPORT_ICAO.get(city, "") + db = DBManager() + obs = db.get_airport_obs_recent(icao, minutes=60) + if obs: + temps = [r.get("temp_c") for r in obs if r.get("temp_c") is not None] + if len(temps) >= 6: + peak = max(temps) + peak_idx = temps.index(peak) + if peak_idx < len(temps) - 2: + post = temps[peak_idx:] + if all(post[i] <= post[i - 1] + 0.1 for i in range(1, len(post))): + if current_temp < peak - 0.5: + in_window = False + except Exception: + pass + + if not in_window: + if last_city.get("active"): + last_by_city[city] = {"ts": now_ts, "active": False} + state_dirty = True continue local_time = city_weather.get("local_time") or "" @@ -806,10 +831,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} + last_by_city[city] = {"ts": now_ts, "active": True} state_dirty = True - logger.info("airport status pushed city={} temp={}", city, - (city_weather.get("current") or {}).get("temp")) + logger.info("airport status pushed city={} temp={} proximity={:.1f}", city, current_temp, proximity) except Exception: logger.exception("airport cycle failed for city={}", city)