feat: implement telegram notification push utilities with state management and alert filtering
This commit is contained in:
+36
-23
@@ -4,6 +4,7 @@ import os
|
|||||||
import re
|
import re
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from typing import Any, Dict, List, Optional, Tuple
|
from typing import Any, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
@@ -581,10 +582,11 @@ def _format_prob(value: Any) -> str:
|
|||||||
|
|
||||||
def _build_market_monitor_message(city: str, city_weather: Dict[str, Any]) -> str:
|
def _build_market_monitor_message(city: str, city_weather: Dict[str, Any]) -> str:
|
||||||
current = city_weather.get("current") or {}
|
current = city_weather.get("current") or {}
|
||||||
|
airport_cur = city_weather.get("airport_current") or {}
|
||||||
deb = city_weather.get("deb") or {}
|
deb = city_weather.get("deb") or {}
|
||||||
local_time = str(city_weather.get("local_time") or "").strip() or "--"
|
local_time = str(city_weather.get("local_time") or "").strip() or "--"
|
||||||
city_label = str(city or "").strip().title()
|
city_label = str(city or "").strip().title()
|
||||||
current_temp = current.get("temp")
|
current_temp = airport_cur.get("temp") if airport_cur.get("temp") is not None else current.get("temp")
|
||||||
deb_pred = deb.get("prediction")
|
deb_pred = deb.get("prediction")
|
||||||
temp_symbol = str(city_weather.get("temp_symbol") or "°C").strip()
|
temp_symbol = str(city_weather.get("temp_symbol") or "°C").strip()
|
||||||
|
|
||||||
@@ -956,19 +958,17 @@ def _run_high_freq_airport_cycle(
|
|||||||
elif current_obs_time and last_obs_time and current_obs_time == last_obs_time:
|
elif current_obs_time and last_obs_time and current_obs_time == last_obs_time:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# ── Three-condition heat window ──
|
# 跑道城市:任意一条跑道温度满足 DEB 温差规则就推送
|
||||||
threshold = _AIRPORT_HEAT_THRESHOLD.get(city, 3.0)
|
if runway_temps:
|
||||||
time_ok = _in_peak_time_window(city, city_weather)
|
any_in_window = False
|
||||||
temp_ok = (deb_pred - current_temp) <= threshold
|
for (t, _d) in runway_temps:
|
||||||
trend_ok = _check_rising_trend(airport_icao) if city != "paris" else True
|
if t is not None and deb_pred is not None:
|
||||||
|
d = float(t) - float(deb_pred)
|
||||||
in_window = time_ok and temp_ok and trend_ok
|
if -5.0 <= d <= 3.0:
|
||||||
|
any_in_window = True
|
||||||
if not in_window:
|
break
|
||||||
if last_city.get("active"):
|
if not any_in_window:
|
||||||
last_by_city[city] = {"ts": now_ts, "active": False, "obs_time": current_obs_time}
|
continue
|
||||||
state_dirty = True
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 用观测数据时间而非当前本地时间
|
# 用观测数据时间而非当前本地时间
|
||||||
airport_cur = city_weather.get("airport_current") or {}
|
airport_cur = city_weather.get("airport_current") or {}
|
||||||
@@ -986,7 +986,7 @@ def _run_high_freq_airport_cycle(
|
|||||||
if sent:
|
if sent:
|
||||||
last_by_city[city] = {"ts": now_ts, "active": True, "obs_time": current_obs_time}
|
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={} obs_time={}", city, current_temp, deb_pred, threshold, current_obs_time)
|
logger.info("airport status pushed city={} temp={} deb={} obs_time={}", city, current_temp, deb_pred, current_obs_time)
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception("airport cycle failed for city={}", city)
|
logger.exception("airport cycle failed for city={}", city)
|
||||||
@@ -1046,30 +1046,43 @@ def _run_market_monitor_cycle(bot: Any, chat_ids: List[str]) -> bool:
|
|||||||
logger.warning("market monitor push skipped: analyze import failed: {}", exc)
|
logger.warning("market monitor push skipped: analyze import failed: {}", exc)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
for city in MARKET_MONITOR_CITIES:
|
def _process_one(city: str) -> Optional[str]:
|
||||||
try:
|
try:
|
||||||
city_weather = _analyze(city)
|
city_weather = _analyze(city)
|
||||||
scan_payload = build_city_market_scan_payload(city_weather)
|
scan_payload = build_city_market_scan_payload(city_weather)
|
||||||
market = scan_payload.get("market_scan") or {}
|
market = scan_payload.get("market_scan") or {}
|
||||||
if not market.get("available"):
|
if not market.get("available"):
|
||||||
continue
|
return None
|
||||||
city_weather["market_scan"] = market
|
city_weather["market_scan"] = market
|
||||||
current_temp = (city_weather.get("current") or {}).get("temp")
|
ac = city_weather.get("airport_current") or {}
|
||||||
|
current_temp = ac.get("temp") if ac.get("temp") is not None else (city_weather.get("current") or {}).get("temp")
|
||||||
deb_pred = (city_weather.get("deb") or {}).get("prediction")
|
deb_pred = (city_weather.get("deb") or {}).get("prediction")
|
||||||
if current_temp is not None and deb_pred is not None:
|
if current_temp is not None and deb_pred is not None:
|
||||||
delta = float(current_temp) - float(deb_pred)
|
delta = float(current_temp) - float(deb_pred)
|
||||||
is_f = "F" in str(city_weather.get("temp_symbol") or "").upper()
|
is_f = "F" in str(city_weather.get("temp_symbol") or "").upper()
|
||||||
if delta > (5.0 if is_f else 3.0) or delta < -(9.0 if is_f else 5.0):
|
if delta > (5.0 if is_f else 3.0) or delta < -(9.0 if is_f else 5.0):
|
||||||
continue
|
return None
|
||||||
message = _build_market_monitor_message(city, city_weather)
|
return _build_market_monitor_message(city, city_weather)
|
||||||
|
except Exception:
|
||||||
|
logger.exception("market monitor cycle failed for city={}", city)
|
||||||
|
return None
|
||||||
|
|
||||||
|
cities = list(MARKET_MONITOR_CITIES)
|
||||||
|
with ThreadPoolExecutor(max_workers=6) as pool:
|
||||||
|
futures = {pool.submit(_process_one, city): city for city in cities}
|
||||||
|
for future in as_completed(futures):
|
||||||
|
try:
|
||||||
|
message = future.result()
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
if message is None:
|
||||||
|
continue
|
||||||
for chat_id in chat_ids:
|
for chat_id in chat_ids:
|
||||||
try:
|
try:
|
||||||
bot.send_message(chat_id, message)
|
bot.send_message(chat_id, message)
|
||||||
sent_any = True
|
sent_any = True
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.warning("market monitor push failed city={} chat_id={}: {}", city, chat_id, exc)
|
logger.warning("market monitor push failed city={} chat_id={}: {}", futures[future], chat_id, exc)
|
||||||
except Exception:
|
|
||||||
logger.exception("market monitor cycle failed for city={}", city)
|
|
||||||
return sent_any
|
return sent_any
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user