推送窗口改为 DEB 曲线动态判断,替代固定 08:00-20:00

当前温度距 DEB 预测最高 ≤3°C 时开始推送,一旦确认已过峰值
(近 1h 内最高已过且回落 >0.5°C)自动停止。不再依赖固定时段。

Constraint: DEB 预测本身就是每日最高温,比时钟判断更贴合实际峰值
Scope-risk: 中,核心推送逻辑变更
Tested: ruff check 通过
This commit is contained in:
2569718930@qq.com
2026-05-12 19:08:44 +08:00
parent 9026ccf4c0
commit a5522b4b16
2 changed files with 37 additions and 13 deletions
+1 -1
View File
@@ -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(
+36 -12
View File
@@ -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)