feat: implement analysis service and TTL configuration for regional city-specific cache management

This commit is contained in:
2569718930@qq.com
2026-05-28 10:59:54 +08:00
parent 79b82a34cb
commit f56c604677
2 changed files with 101 additions and 4 deletions
+31 -1
View File
@@ -1,4 +1,8 @@
from web.analysis_service import _analysis_ttl_for_city, _mgm_hourly_high
from web.analysis_service import (
_analysis_ttl_for_city,
_mgm_hourly_high,
_runway_history_temp_for_city,
)
from web.core import CACHE_TTL
@@ -22,3 +26,29 @@ def test_mgm_hourly_high_fills_turkish_model_support():
]
}
) == 21.5
def test_runway_history_temp_uses_settlement_endpoint_for_second_runway():
assert _runway_history_temp_for_city(
"chongqing",
{
"runway": "20R/02L",
"tdz_temp": 33.8,
"mid_temp": 34.5,
"end_temp": 31.2,
"target_runway_max": 34.5,
},
) == 31.2
def test_runway_history_temp_uses_settlement_endpoint_for_first_runway():
assert _runway_history_temp_for_city(
"wuhan",
{
"runway": "04/22",
"tdz_temp": 31.2,
"mid_temp": 33.4,
"end_temp": 32.6,
"target_runway_max": 33.4,
},
) == 31.2
+70 -3
View File
@@ -84,6 +84,26 @@ HIGH_FREQ_AIRPORT_ANALYSIS_CITIES = {
"wuhan",
}
AMSC_SETTLEMENT_RUNWAY_PAIRS: Dict[str, tuple[str, str]] = {
"shanghai": ("17L", "35R"),
"chengdu": ("02L", "20R"),
"chongqing": ("20R", "02L"),
"guangzhou": ("02L", "20R"),
"wuhan": ("04", "22"),
"beijing": ("19", "01"),
"qingdao": ("16", "34"),
}
AMSC_SETTLEMENT_RUNWAY_TARGETS: Dict[str, str] = {
"shanghai": "35R",
"chengdu": "02L",
"chongqing": "02L",
"guangzhou": "02L",
"wuhan": "04",
"beijing": "01",
"qingdao": "34",
}
def _mgm_hourly_high(mgm: Dict[str, Any]) -> Optional[float]:
hourly = mgm.get("hourly") if isinstance(mgm, dict) else []
@@ -97,6 +117,55 @@ def _mgm_hourly_high(mgm: Dict[str, Any]) -> Optional[float]:
if value is not None:
values.append(value)
return max(values) if values else None
def _normalize_runway_label(value: Any) -> str:
return re.sub(r"[^0-9A-Z]+", "", str(value or "").strip().upper())
def _split_runway_pair_label(value: Any) -> tuple[str, str]:
parts = [_normalize_runway_label(part) for part in str(value or "").split("/") if str(part).strip()]
if len(parts) >= 2:
return parts[0], parts[1]
runway = _normalize_runway_label(value)
return runway, runway
def _runway_pair_matches(left: tuple[str, str], right: tuple[str, str]) -> bool:
return tuple(sorted(left)) == tuple(sorted(right))
def _settlement_runway_endpoint_temp(city: str, row: Dict[str, Any]) -> Optional[float]:
city_key = (city or "").strip().lower()
configured_pair = AMSC_SETTLEMENT_RUNWAY_PAIRS.get(city_key)
target = _normalize_runway_label(AMSC_SETTLEMENT_RUNWAY_TARGETS.get(city_key))
if not configured_pair or not target:
return None
pair = _split_runway_pair_label(row.get("runway"))
configured = tuple(_normalize_runway_label(part) for part in configured_pair)
if not _runway_pair_matches(pair, configured):
return None
tdz_temp = _sf(row.get("tdz_temp"))
end_temp = _sf(row.get("end_temp"))
if target == pair[0]:
return tdz_temp if tdz_temp is not None else end_temp
if target == pair[1]:
return end_temp if end_temp is not None else tdz_temp
return None
def _runway_history_temp_for_city(city: str, row: Dict[str, Any]) -> Optional[float]:
endpoint_temp = _settlement_runway_endpoint_temp(city, row)
if endpoint_temp is not None:
return endpoint_temp
target_runway_max = _sf(row.get("target_runway_max"))
if target_runway_max is not None:
return target_runway_max
return _sf(row.get("tdz_temp"))
_ANALYSIS_CACHE_STATS_LOCK = threading.Lock()
_ANALYSIS_CACHE_STATS: Dict[str, Any] = {
"total_requests": 0,
@@ -1624,9 +1693,7 @@ def _analyze(
rw = r.get("runway")
if not rw:
continue
temp_val = r.get("target_runway_max")
if temp_val is None:
temp_val = r.get("tdz_temp")
temp_val = _runway_history_temp_for_city(city, r)
if temp_val is not None:
temp_val = float(temp_val)
if is_f: