修复 analysis_service.py 语法错误 + scan.py Python 3.8 兼容
This commit is contained in:
+109
-11
@@ -1,7 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import time as _time
|
||||
import threading as _threading
|
||||
import threading
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from datetime import datetime, timezone, timedelta
|
||||
from typing import Dict, Any, Optional
|
||||
@@ -42,15 +43,8 @@ from web.services.analysis_utils import (
|
||||
add_signal as _add_signal,
|
||||
bucket_label as _bucket_label,
|
||||
bucket_label_from_value as _bucket_label_from_value,
|
||||
dedupe_forecast_daily as _dedupe_forecast_daily,
|
||||
format_clock_minutes as _format_clock_minutes,
|
||||
format_observation_time_local as _format_observation_time_local,
|
||||
is_plausible_city_temp as _is_plausible_city_temp,
|
||||
metar_is_current_local_day as _metar_is_current_local_day,
|
||||
mgm_hourly_high as _mgm_hourly_high,
|
||||
next_observation_clock as _next_observation_clock,
|
||||
parse_local_hour as _parse_local_hour,
|
||||
parse_utc_datetime as _parse_utc_datetime,
|
||||
top_probability_bucket as _top_probability_bucket,
|
||||
)
|
||||
from web.services.analysis_signals import (
|
||||
@@ -85,7 +79,20 @@ HIGH_FREQ_AIRPORT_ANALYSIS_CITIES = {
|
||||
"wuhan",
|
||||
}
|
||||
|
||||
_ANALYSIS_CACHE_STATS_LOCK = _threading.Lock()
|
||||
|
||||
def _mgm_hourly_high(mgm: Dict[str, Any]) -> Optional[float]:
|
||||
hourly = mgm.get("hourly") if isinstance(mgm, dict) else []
|
||||
if not isinstance(hourly, list):
|
||||
return None
|
||||
values = []
|
||||
for row in hourly:
|
||||
if not isinstance(row, dict):
|
||||
continue
|
||||
value = _sf(row.get("temp"))
|
||||
if value is not None:
|
||||
values.append(value)
|
||||
return max(values) if values else None
|
||||
_ANALYSIS_CACHE_STATS_LOCK = threading.Lock()
|
||||
_ANALYSIS_CACHE_STATS: Dict[str, Any] = {
|
||||
"total_requests": 0,
|
||||
"cache_hits": 0,
|
||||
@@ -95,14 +102,105 @@ _ANALYSIS_CACHE_STATS: Dict[str, Any] = {
|
||||
"last_cache_miss_at": None,
|
||||
"last_city": None,
|
||||
}
|
||||
_SUMMARY_CACHE_LOCK = _threading.Lock()
|
||||
_SUMMARY_CACHE = LRUDict(maxsize=128)
|
||||
_SUMMARY_CACHE_LOCK = threading.Lock()
|
||||
_SUMMARY_CACHE_MAXSIZE = 128
|
||||
_SUMMARY_CACHE = LRUDict(maxsize=_SUMMARY_CACHE_MAXSIZE)
|
||||
def _dedupe_forecast_daily(rows: Any) -> list[Dict[str, Any]]:
|
||||
if not isinstance(rows, list):
|
||||
return []
|
||||
seen = set()
|
||||
out = []
|
||||
for row in rows:
|
||||
if not isinstance(row, dict):
|
||||
continue
|
||||
date = str(row.get("date") or "").strip()
|
||||
if not date or date in seen:
|
||||
continue
|
||||
seen.add(date)
|
||||
out.append(row)
|
||||
return out
|
||||
|
||||
|
||||
def _format_observation_time_local(value: Any, utc_offset: int) -> str:
|
||||
raw = str(value or "").strip()
|
||||
if not raw:
|
||||
return ""
|
||||
if "T" in raw:
|
||||
try:
|
||||
dt = datetime.fromisoformat(raw.replace("Z", "+00:00"))
|
||||
if dt.tzinfo is None:
|
||||
dt = dt.replace(tzinfo=timezone.utc)
|
||||
return dt.astimezone(timezone(timedelta(seconds=utc_offset))).strftime("%H:%M")
|
||||
except Exception:
|
||||
pass
|
||||
match = re.search(r"(\d{1,2}):(\d{2})", raw)
|
||||
if match:
|
||||
return f"{int(match.group(1)):02d}:{match.group(2)}"
|
||||
return raw[:16]
|
||||
|
||||
|
||||
def _fetch_nmc_current_fallback(city: str, *, use_fahrenheit: bool) -> Dict[str, Any]:
|
||||
return {}
|
||||
|
||||
|
||||
def _is_plausible_city_temp(city: str, value: Any, unit: str = "°C") -> bool:
|
||||
temp = _sf(value)
|
||||
if temp is None:
|
||||
return False
|
||||
meta = CITY_REGISTRY.get(str(city or "").strip().lower(), {}) or {}
|
||||
min_c = _sf(meta.get("min_plausible_metar_temp_c"))
|
||||
if min_c is None:
|
||||
return True
|
||||
min_value = min_c * 9 / 5 + 32 if str(unit or "").upper().endswith("F") else min_c
|
||||
return temp >= min_value
|
||||
|
||||
|
||||
def _parse_local_hour(local_time_str: Optional[str]) -> Optional[int]:
|
||||
if not local_time_str:
|
||||
return None
|
||||
try:
|
||||
parts = str(local_time_str).strip().split(":")
|
||||
hour = int(parts[0])
|
||||
if 0 <= hour <= 23:
|
||||
return hour
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def _parse_utc_datetime(value: Any) -> Optional[datetime]:
|
||||
raw = str(value or "").strip()
|
||||
if not raw or "T" not in raw:
|
||||
return None
|
||||
try:
|
||||
dt = datetime.fromisoformat(raw.replace("Z", "+00:00"))
|
||||
except Exception:
|
||||
return None
|
||||
if dt.tzinfo is None:
|
||||
dt = dt.replace(tzinfo=timezone.utc)
|
||||
return dt.astimezone(timezone.utc)
|
||||
|
||||
|
||||
def _metar_is_current_local_day(
|
||||
metar: Dict[str, Any],
|
||||
*,
|
||||
local_date: str,
|
||||
utc_offset: int,
|
||||
) -> bool:
|
||||
if not isinstance(metar, dict) or not metar:
|
||||
return False
|
||||
if metar.get("stale_for_today") is True:
|
||||
return False
|
||||
observation_local_date = str(metar.get("observation_local_date") or "").strip()
|
||||
if observation_local_date:
|
||||
return observation_local_date == local_date
|
||||
obs_dt = _parse_utc_datetime(metar.get("observation_time"))
|
||||
if obs_dt is None:
|
||||
return True
|
||||
local_dt = obs_dt.astimezone(timezone(timedelta(seconds=utc_offset)))
|
||||
return local_dt.strftime("%Y-%m-%d") == local_date
|
||||
|
||||
|
||||
def _record_analysis_cache_event(*, city: str, hit: bool, force_refresh: bool) -> None:
|
||||
now = datetime.now(timezone.utc).isoformat()
|
||||
with _ANALYSIS_CACHE_STATS_LOCK:
|
||||
|
||||
Reference in New Issue
Block a user