Fix METAR refresh timing and local observation display
This commit is contained in:
@@ -2993,6 +2993,14 @@ function formatObservationUpdate(value: unknown, locale: Locale) {
|
|||||||
return normalizeHm(raw) || raw;
|
return normalizeHm(raw) || raw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function localObservationTimeCandidate(value: unknown) {
|
||||||
|
const raw = String(value ?? "").trim();
|
||||||
|
if (!raw || raw.includes("T") || /^\d{4}-\d{2}-\d{2}/.test(raw)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return normalizeHm(raw) || raw;
|
||||||
|
}
|
||||||
|
|
||||||
function getOfficialObservationCandidates(detail: CityDetail) {
|
function getOfficialObservationCandidates(detail: CityDetail) {
|
||||||
const officialNearby = Array.isArray(detail.official_nearby)
|
const officialNearby = Array.isArray(detail.official_nearby)
|
||||||
? detail.official_nearby
|
? detail.official_nearby
|
||||||
@@ -3078,15 +3086,15 @@ function getObservationUpdateProfile(detail: CityDetail, locale: Locale) {
|
|||||||
.toLowerCase();
|
.toLowerCase();
|
||||||
const isNmcCurrent = currentSource === "nmc" || currentSource.includes("nmc");
|
const isNmcCurrent = currentSource === "nmc" || currentSource.includes("nmc");
|
||||||
const rawValue = firstNonEmptyString([
|
const rawValue = firstNonEmptyString([
|
||||||
detail.airport_primary?.obs_time,
|
|
||||||
detail.airport_primary?.report_time,
|
|
||||||
detail.airport_current?.obs_time,
|
|
||||||
detail.airport_current?.report_time,
|
|
||||||
isNmcCurrent ? "" : detail.current?.obs_time,
|
isNmcCurrent ? "" : detail.current?.obs_time,
|
||||||
|
localObservationTimeCandidate(detail.airport_primary?.obs_time),
|
||||||
|
localObservationTimeCandidate(detail.airport_current?.obs_time),
|
||||||
|
localObservationTimeCandidate(mgmFirstRecord?.obs_time),
|
||||||
|
localObservationTimeCandidate(mgmFirstRecord?.time),
|
||||||
|
detail.airport_primary?.report_time,
|
||||||
|
detail.airport_current?.report_time,
|
||||||
isNmcCurrent ? "" : detail.current?.report_time,
|
isNmcCurrent ? "" : detail.current?.report_time,
|
||||||
mgmFirstRecord?.obs_time,
|
|
||||||
mgmFirstRecord?.report_time,
|
mgmFirstRecord?.report_time,
|
||||||
mgmFirstRecord?.time,
|
|
||||||
detail.updated_at,
|
detail.updated_at,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ CITY_REGISTRY = {
|
|||||||
"tz_offset": 10800,
|
"tz_offset": 10800,
|
||||||
"use_fahrenheit": False,
|
"use_fahrenheit": False,
|
||||||
"is_major": False,
|
"is_major": False,
|
||||||
|
"fast_metar_refresh": True,
|
||||||
"risk_level": "medium",
|
"risk_level": "medium",
|
||||||
"risk_emoji": "🟡",
|
"risk_emoji": "🟡",
|
||||||
"airport_name": "Esenboğa 机场",
|
"airport_name": "Esenboğa 机场",
|
||||||
@@ -27,6 +28,7 @@ CITY_REGISTRY = {
|
|||||||
"tz_offset": 10800,
|
"tz_offset": 10800,
|
||||||
"use_fahrenheit": False,
|
"use_fahrenheit": False,
|
||||||
"is_major": True,
|
"is_major": True,
|
||||||
|
"fast_metar_refresh": True,
|
||||||
"risk_level": "medium",
|
"risk_level": "medium",
|
||||||
"risk_emoji": "🟡",
|
"risk_emoji": "🟡",
|
||||||
"airport_name": "Istanbul Airport",
|
"airport_name": "Istanbul Airport",
|
||||||
|
|||||||
@@ -51,6 +51,28 @@ class MetarSourceMixin:
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def _metar_cache_ttl_for_city(self, city: str, icao: Optional[str] = None) -> int:
|
||||||
|
normalized = str(city or "").strip().lower()
|
||||||
|
city_meta = (getattr(self, "CITY_REGISTRY", {}) or {}).get(normalized) or {}
|
||||||
|
if not city_meta and icao:
|
||||||
|
icao_upper = str(icao or "").strip().upper()
|
||||||
|
for candidate in (getattr(self, "CITY_REGISTRY", {}) or {}).values():
|
||||||
|
if str(candidate.get("icao") or "").strip().upper() == icao_upper:
|
||||||
|
city_meta = candidate
|
||||||
|
break
|
||||||
|
|
||||||
|
raw_ttl = city_meta.get("metar_cache_ttl_sec")
|
||||||
|
try:
|
||||||
|
ttl = int(raw_ttl)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
ttl = 0
|
||||||
|
if ttl > 0:
|
||||||
|
return ttl
|
||||||
|
|
||||||
|
if city_meta.get("fast_metar_refresh"):
|
||||||
|
return max(1, int(getattr(self, "metar_fast_cache_ttl_sec", 60)))
|
||||||
|
return max(1, int(getattr(self, "metar_cache_ttl_sec", 600)))
|
||||||
|
|
||||||
def get_icao_code(self, city: str) -> Optional[str]:
|
def get_icao_code(self, city: str) -> Optional[str]:
|
||||||
"""根据城市名获取对应的 ICAO 机场代码"""
|
"""根据城市名获取对应的 ICAO 机场代码"""
|
||||||
normalized = city.lower().strip()
|
normalized = city.lower().strip()
|
||||||
@@ -74,9 +96,10 @@ class MetarSourceMixin:
|
|||||||
|
|
||||||
cache_key = f"{icao}:{utc_offset}:{use_fahrenheit}"
|
cache_key = f"{icao}:{utc_offset}:{use_fahrenheit}"
|
||||||
now_ts = time.time()
|
now_ts = time.time()
|
||||||
|
cache_ttl_sec = self._metar_cache_ttl_for_city(city, icao)
|
||||||
with self._metar_cache_lock:
|
with self._metar_cache_lock:
|
||||||
cached = self._metar_cache.get(cache_key)
|
cached = self._metar_cache.get(cache_key)
|
||||||
if cached and now_ts - cached["t"] < self.metar_cache_ttl_sec:
|
if cached and now_ts - cached["t"] < cache_ttl_sec:
|
||||||
logger.debug(f"METAR cache hit {icao} age={int(now_ts - cached['t'])}s")
|
logger.debug(f"METAR cache hit {icao} age={int(now_ts - cached['t'])}s")
|
||||||
record_source_call("metar", "current", "cache_hit", (time.perf_counter() - started) * 1000.0)
|
record_source_call("metar", "current", "cache_hit", (time.perf_counter() - started) * 1000.0)
|
||||||
return cached["d"]
|
return cached["d"]
|
||||||
|
|||||||
@@ -184,6 +184,9 @@ class WeatherDataCollector(OpenMeteoCacheMixin, SettlementSourceMixin, MetarSour
|
|||||||
self.metar_cache_ttl_sec = int(
|
self.metar_cache_ttl_sec = int(
|
||||||
os.getenv("METAR_CACHE_TTL_SEC", "600") # 默认 10 分钟
|
os.getenv("METAR_CACHE_TTL_SEC", "600") # 默认 10 分钟
|
||||||
)
|
)
|
||||||
|
self.metar_fast_cache_ttl_sec = int(
|
||||||
|
os.getenv("METAR_FAST_CACHE_TTL_SEC", "60")
|
||||||
|
)
|
||||||
self._metar_cache: Dict[str, Dict] = {}
|
self._metar_cache: Dict[str, Dict] = {}
|
||||||
self._metar_cache_lock = threading.Lock()
|
self._metar_cache_lock = threading.Lock()
|
||||||
self.taf_cache_ttl_sec = int(
|
self.taf_cache_ttl_sec = int(
|
||||||
|
|||||||
@@ -1,9 +1,17 @@
|
|||||||
from src.data_collection.country_networks import build_country_network_snapshot
|
from src.data_collection.country_networks import build_country_network_snapshot
|
||||||
from src.data_collection.city_registry import ALIASES, CITY_REGISTRY
|
from src.data_collection.city_registry import ALIASES, CITY_REGISTRY
|
||||||
|
from src.data_collection.metar_sources import MetarSourceMixin
|
||||||
from web.analysis_service import _build_city_detail_payload, _build_intraday_meteorology
|
from web.analysis_service import _build_city_detail_payload, _build_intraday_meteorology
|
||||||
from web.core import CITIES
|
from web.core import CITIES
|
||||||
|
|
||||||
|
|
||||||
|
class _DummyMetarSource(MetarSourceMixin):
|
||||||
|
CITY_REGISTRY = CITY_REGISTRY
|
||||||
|
CITY_TO_ICAO = {key: value["icao"] for key, value in CITY_REGISTRY.items() if value.get("icao")}
|
||||||
|
metar_cache_ttl_sec = 600
|
||||||
|
metar_fast_cache_ttl_sec = 60
|
||||||
|
|
||||||
|
|
||||||
def test_new_south_asia_city_registry_entries_are_wired():
|
def test_new_south_asia_city_registry_entries_are_wired():
|
||||||
assert CITY_REGISTRY["manila"]["settlement_source"] == "wunderground"
|
assert CITY_REGISTRY["manila"]["settlement_source"] == "wunderground"
|
||||||
assert CITY_REGISTRY["manila"]["settlement_station_code"] == "RPLL"
|
assert CITY_REGISTRY["manila"]["settlement_station_code"] == "RPLL"
|
||||||
@@ -18,6 +26,14 @@ def test_new_south_asia_city_registry_entries_are_wired():
|
|||||||
assert CITIES["masroor air base"]["settlement_source"] == "metar"
|
assert CITIES["masroor air base"]["settlement_source"] == "metar"
|
||||||
|
|
||||||
|
|
||||||
|
def test_turkey_metar_uses_fast_cache_ttl():
|
||||||
|
source = _DummyMetarSource()
|
||||||
|
|
||||||
|
assert source._metar_cache_ttl_for_city("ankara", "LTAC") == 60
|
||||||
|
assert source._metar_cache_ttl_for_city("istanbul", "LTFM") == 60
|
||||||
|
assert source._metar_cache_ttl_for_city("karachi", "OPKC") == 600
|
||||||
|
|
||||||
|
|
||||||
def test_turkey_mgm_provider_returns_official_nearby_rows():
|
def test_turkey_mgm_provider_returns_official_nearby_rows():
|
||||||
raw = {
|
raw = {
|
||||||
"metar": {
|
"metar": {
|
||||||
|
|||||||
+11
-2
@@ -1650,6 +1650,15 @@ def _analyze(
|
|||||||
int(utc_offset or 0),
|
int(utc_offset or 0),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
airport_primary_current = dict(network_snapshot.get("airport_primary_current") or {})
|
||||||
|
if (
|
||||||
|
airport_primary_current.get("source_code") == "metar"
|
||||||
|
and obs_time_str
|
||||||
|
and not use_settlement_current
|
||||||
|
):
|
||||||
|
airport_primary_current["obs_time"] = obs_time_str
|
||||||
|
airport_primary_current["obs_age_min"] = metar_age_min
|
||||||
|
|
||||||
settlement_today_obs = []
|
settlement_today_obs = []
|
||||||
if use_settlement_current:
|
if use_settlement_current:
|
||||||
explicit_settlement_obs = settlement_current.get("today_obs") or []
|
explicit_settlement_obs = settlement_current.get("today_obs") or []
|
||||||
@@ -2266,7 +2275,7 @@ def _analyze(
|
|||||||
},
|
},
|
||||||
"airport_current": {
|
"airport_current": {
|
||||||
"temp": _sf(mc.get("temp")),
|
"temp": _sf(mc.get("temp")),
|
||||||
"obs_time": metar.get("obs_time"),
|
"obs_time": obs_time_str,
|
||||||
"max_so_far": airport_max_so_far,
|
"max_so_far": airport_max_so_far,
|
||||||
"max_temp_time": airport_max_temp_time,
|
"max_temp_time": airport_max_temp_time,
|
||||||
"obs_age_min": metar_age_min,
|
"obs_age_min": metar_age_min,
|
||||||
@@ -2283,7 +2292,7 @@ def _analyze(
|
|||||||
"source_label": "METAR",
|
"source_label": "METAR",
|
||||||
},
|
},
|
||||||
"settlement_station": network_snapshot.get("settlement_station") or {},
|
"settlement_station": network_snapshot.get("settlement_station") or {},
|
||||||
"airport_primary": network_snapshot.get("airport_primary_current") or {},
|
"airport_primary": airport_primary_current,
|
||||||
"airport_primary_today_obs": network_snapshot.get("airport_primary_today_obs") or [],
|
"airport_primary_today_obs": network_snapshot.get("airport_primary_today_obs") or [],
|
||||||
"official_nearby": network_snapshot.get("official_nearby") or [],
|
"official_nearby": network_snapshot.get("official_nearby") or [],
|
||||||
"official_network_source": network_snapshot.get("official_network_source"),
|
"official_network_source": network_snapshot.get("official_network_source"),
|
||||||
|
|||||||
Reference in New Issue
Block a user