fix: align telegram runway slope with settlement endpoint
This commit is contained in:
@@ -897,14 +897,24 @@ def _wind_regime_label(city: str, wind_dir: Optional[int], language: Optional[st
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _compute_slope_15m(icao: str, current_temp: float) -> Optional[float]:
|
def _runway_row_temp_for_city(city: str, row: Dict[str, Any]) -> Optional[float]:
|
||||||
|
endpoint = _settlement_endpoint_for_point(city, row.get("runway"), row)
|
||||||
|
if endpoint is not None:
|
||||||
|
return _safe_float(endpoint.get("temp"))
|
||||||
|
target_runway_max = _safe_float(row.get("target_runway_max"))
|
||||||
|
if target_runway_max is not None:
|
||||||
|
return target_runway_max
|
||||||
|
return _safe_float(row.get("tdz_temp"))
|
||||||
|
|
||||||
|
|
||||||
|
def _compute_slope_15m(icao: str, current_temp: float, city: str = "") -> Optional[float]:
|
||||||
"""Estimate 15-minute temperature trend from runway_obs_log."""
|
"""Estimate 15-minute temperature trend from runway_obs_log."""
|
||||||
try:
|
try:
|
||||||
db = DBManager()
|
db = DBManager()
|
||||||
rows = db.get_runway_obs_recent(icao, minutes=20)
|
rows = db.get_runway_obs_recent(icao, minutes=20)
|
||||||
temps = []
|
temps = []
|
||||||
for r in rows:
|
for r in rows:
|
||||||
t = r.get("target_runway_max") or r.get("tdz_temp")
|
t = _runway_row_temp_for_city(city, r)
|
||||||
if t is not None:
|
if t is not None:
|
||||||
temps.append(float(t))
|
temps.append(float(t))
|
||||||
if len(temps) >= 2:
|
if len(temps) >= 2:
|
||||||
@@ -1147,7 +1157,7 @@ def _build_airport_status_message(
|
|||||||
|
|
||||||
# ── Heat model ──
|
# ── Heat model ──
|
||||||
wind_dir = amos.get("wind_dir") if is_amsc else None
|
wind_dir = amos.get("wind_dir") if is_amsc else None
|
||||||
slope_15m = _compute_slope_15m(amos_icao, display_temp) if is_amsc and display_temp is not None else None
|
slope_15m = _compute_slope_15m(amos_icao, display_temp, city) if is_amsc and display_temp is not None else None
|
||||||
heat_signal = _runway_heat_signal(display_temp or 0, slope_15m, wind_dir, city, language) if is_amsc else ""
|
heat_signal = _runway_heat_signal(display_temp or 0, slope_15m, wind_dir, city, language) if is_amsc else ""
|
||||||
wind_label = _wind_regime_label(city, wind_dir, language) if is_amsc and wind_dir is not None else None
|
wind_label = _wind_regime_label(city, wind_dir, language) if is_amsc and wind_dir is not None else None
|
||||||
|
|
||||||
@@ -1192,14 +1202,11 @@ def _build_airport_status_message(
|
|||||||
end = pts.get("end_temp")
|
end = pts.get("end_temp")
|
||||||
is_settlement = _is_settlement_runway(city, r1, r2)
|
is_settlement = _is_settlement_runway(city, r1, r2)
|
||||||
marker = f" {_copy(language, '★Settlement', '★结算')}" if is_settlement else ""
|
marker = f" {_copy(language, '★Settlement', '★结算')}" if is_settlement else ""
|
||||||
tmax = pts.get("target_runway_max")
|
|
||||||
if tdz is not None or mid is not None or end is not None:
|
if tdz is not None or mid is not None or end is not None:
|
||||||
line = f"{r1}/{r2}{marker} TDZ:{_fmt(tdz)} MID:{_fmt(mid)} END:{_fmt(end)}"
|
line = f"{r1}/{r2}{marker} TDZ:{_fmt(tdz)} MID:{_fmt(mid)} END:{_fmt(end)}"
|
||||||
settlement_line_endpoint = _settlement_endpoint_for_point(city, (r1, r2), pts) if is_settlement else None
|
settlement_line_endpoint = _settlement_endpoint_for_point(city, (r1, r2), pts) if is_settlement else None
|
||||||
if settlement_line_endpoint is not None:
|
if settlement_line_endpoint is not None:
|
||||||
line += f" settle:{float(settlement_line_endpoint['temp']):.1f}"
|
line += f" settle:{float(settlement_line_endpoint['temp']):.1f}"
|
||||||
elif tmax is not None:
|
|
||||||
line += f" max:{float(tmax):.1f}"
|
|
||||||
lines.append(line)
|
lines.append(line)
|
||||||
else:
|
else:
|
||||||
temp_symbol = str(city_weather.get("temp_symbol") or "°C").strip()
|
temp_symbol = str(city_weather.get("temp_symbol") or "°C").strip()
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from src.utils.telegram_push import (
|
|||||||
HIGH_FREQ_AIRPORT_CITIES,
|
HIGH_FREQ_AIRPORT_CITIES,
|
||||||
HIGH_FREQ_AIRPORT_ICAO,
|
HIGH_FREQ_AIRPORT_ICAO,
|
||||||
_build_airport_status_message,
|
_build_airport_status_message,
|
||||||
|
_compute_slope_15m,
|
||||||
_run_high_freq_airport_cycle,
|
_run_high_freq_airport_cycle,
|
||||||
_telegram_push_language,
|
_telegram_push_language,
|
||||||
)
|
)
|
||||||
@@ -43,6 +44,7 @@ def test_airport_status_message_defaults_to_bilingual_runway_copy(monkeypatch):
|
|||||||
assert "TDZ:23.0" in text
|
assert "TDZ:23.0" in text
|
||||||
assert "Settlement runway now / 结算跑道当前:" in text
|
assert "Settlement runway now / 结算跑道当前:" in text
|
||||||
assert "Today's runway high / 今日跑道高点:" in text
|
assert "Today's runway high / 今日跑道高点:" in text
|
||||||
|
assert "max:" not in text
|
||||||
assert "DEB: 24.0°C" in text
|
assert "DEB: 24.0°C" in text
|
||||||
|
|
||||||
|
|
||||||
@@ -98,6 +100,61 @@ def test_airport_status_uses_tdz_when_settlement_target_is_first_runway():
|
|||||||
assert "02L/20R ★Settlement / ★结算 TDZ:24.4 MID:26.1 END:27.9 settle:24.4" in text
|
assert "02L/20R ★Settlement / ★结算 TDZ:24.4 MID:26.1 END:27.9 settle:24.4" in text
|
||||||
assert "Settlement runway now / 结算跑道当前: 24.4°C" in text
|
assert "Settlement runway now / 结算跑道当前: 24.4°C" in text
|
||||||
assert "Settlement runway now / 结算跑道当前: 27.9°C" not in text
|
assert "Settlement runway now / 结算跑道当前: 27.9°C" not in text
|
||||||
|
assert "max:" not in text
|
||||||
|
|
||||||
|
|
||||||
|
def test_airport_status_removes_max_when_runway_endpoints_are_shown():
|
||||||
|
text = _build_airport_status_message(
|
||||||
|
"shanghai",
|
||||||
|
{
|
||||||
|
"current": {"temp": 24.0},
|
||||||
|
"airport_current": {"max_so_far": 25.0, "max_temp_time": "07:00"},
|
||||||
|
"amos": {
|
||||||
|
"source": "amsc_awos",
|
||||||
|
"runway_obs": {
|
||||||
|
"runway_pairs": [("35R", "17L"), ("34L", "16R")],
|
||||||
|
"temperatures": [(25.2, None), (25.4, None)],
|
||||||
|
"point_temperatures": [
|
||||||
|
{"runway": "35R/17L", "tdz_temp": 25.2, "mid_temp": None, "end_temp": 24.6, "target_runway_max": 25.2},
|
||||||
|
{"runway": "34L/16R", "tdz_temp": 25.4, "mid_temp": None, "end_temp": 24.8, "target_runway_max": 25.4},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
27.2,
|
||||||
|
"10:58",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "35R/17L ★Settlement / ★结算 TDZ:25.2 MID:-- END:24.6 settle:25.2" in text
|
||||||
|
assert "34L/16R TDZ:25.4 MID:-- END:24.8" in text
|
||||||
|
assert "max:" not in text
|
||||||
|
|
||||||
|
|
||||||
|
def test_telegram_slope_uses_settlement_endpoint_not_runway_max(monkeypatch):
|
||||||
|
import src.utils.telegram_push as telegram_push
|
||||||
|
|
||||||
|
class FakeDB:
|
||||||
|
def get_runway_obs_recent(self, icao, minutes=20):
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"runway": "20R/02L",
|
||||||
|
"tdz_temp": 33.7,
|
||||||
|
"mid_temp": 34.1,
|
||||||
|
"end_temp": 30.8,
|
||||||
|
"target_runway_max": 34.1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"runway": "20R/02L",
|
||||||
|
"tdz_temp": 33.8,
|
||||||
|
"mid_temp": 34.5,
|
||||||
|
"end_temp": 31.2,
|
||||||
|
"target_runway_max": 34.5,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
monkeypatch.setattr(telegram_push, "DBManager", lambda: FakeDB())
|
||||||
|
|
||||||
|
assert _compute_slope_15m("ZUCK", 31.2, "chongqing") == 0.4
|
||||||
|
|
||||||
|
|
||||||
def test_singapore_is_in_telegram_push_city_lists():
|
def test_singapore_is_in_telegram_push_city_lists():
|
||||||
|
|||||||
Reference in New Issue
Block a user