feat: implement live temperature threshold charting component with SSE patch support and data collection logic
This commit is contained in:
@@ -103,6 +103,40 @@ def _amos_is_runway_token(value: str) -> bool:
|
||||
)
|
||||
|
||||
|
||||
def _amos_normalize_runway_label(value: Any) -> str:
|
||||
return re.sub(r"\s+", "", str(value or "").strip().upper())
|
||||
|
||||
|
||||
def _amos_runway_pair_label(pair: Any, index: int) -> str:
|
||||
if isinstance(pair, (list, tuple)) and len(pair) >= 2:
|
||||
left = _amos_normalize_runway_label(pair[0])
|
||||
right = _amos_normalize_runway_label(pair[1])
|
||||
if left and right:
|
||||
return f"{left}/{right}"
|
||||
return f"RWY {index + 1}"
|
||||
|
||||
|
||||
def _amos_build_point_temperatures(
|
||||
runway_pairs: list[tuple[str, str]],
|
||||
temperatures: list[tuple[Any, Any]],
|
||||
) -> list[dict[str, Any]]:
|
||||
points: list[dict[str, Any]] = []
|
||||
for index, pair in enumerate(runway_pairs):
|
||||
if index >= len(temperatures):
|
||||
continue
|
||||
temp = _amos_safe_float(temperatures[index][0])
|
||||
if temp is None:
|
||||
continue
|
||||
points.append(
|
||||
{
|
||||
"runway": _amos_runway_pair_label(pair, index),
|
||||
"temp": temp,
|
||||
"target_runway_max": temp,
|
||||
}
|
||||
)
|
||||
return points
|
||||
|
||||
|
||||
def _amos_parse_cell_table(lines: list[str]) -> Optional[dict[str, Any]]:
|
||||
"""Parse the actual AMOS HTML table after it has been flattened to cells."""
|
||||
runway_rows: list[dict[str, Any]] = []
|
||||
@@ -200,6 +234,7 @@ def _amos_parse_cell_table(lines: list[str]) -> Optional[dict[str, Any]]:
|
||||
return {
|
||||
"runway_pairs": runway_pairs,
|
||||
"temperatures": temperatures,
|
||||
"point_temperatures": _amos_build_point_temperatures(runway_pairs, temperatures),
|
||||
"pressures_hpa": pressures_hpa,
|
||||
"wind_directions": wind_directions,
|
||||
"wind_speeds": wind_speeds,
|
||||
@@ -323,6 +358,7 @@ def _amos_parse_runway_table(text: str) -> dict[str, Any]:
|
||||
return {
|
||||
"runway_pairs": runway_pairs,
|
||||
"temperatures": temperatures,
|
||||
"point_temperatures": _amos_build_point_temperatures(runway_pairs, temperatures),
|
||||
"pressures_hpa": pressures_hpa,
|
||||
"wind_directions": wind_directions,
|
||||
"wind_speeds": wind_speeds,
|
||||
|
||||
@@ -1371,14 +1371,32 @@ class WeatherDataCollector(OpenMeteoCacheMixin, SettlementSourceMixin, MetarSour
|
||||
runway_obs = amos_data.get("runway_obs") or {}
|
||||
rw_pairs = runway_obs.get("runway_pairs") or []
|
||||
rw_temps = runway_obs.get("temperatures") or []
|
||||
point_temps = runway_obs.get("point_temperatures") or []
|
||||
for i, (pair, (t, _d)) in enumerate(zip(rw_pairs, rw_temps)):
|
||||
if t is not None and i < 4:
|
||||
point = point_temps[i] if i < len(point_temps) and isinstance(point_temps[i], dict) else {}
|
||||
runway_label = str(point.get("runway") or "").strip().upper()
|
||||
if not runway_label and isinstance(pair, (list, tuple)) and len(pair) >= 2:
|
||||
runway_label = f"{str(pair[0]).replace(' ', '').upper()}/{str(pair[1]).replace(' ', '').upper()}"
|
||||
point_temp = point.get("temp") if point else None
|
||||
if point_temp is None and point:
|
||||
point_temp = point.get("target_runway_max")
|
||||
if point_temp is None:
|
||||
point_temp = t
|
||||
if point_temp is not None and i < 4:
|
||||
DBManager().append_airport_obs(
|
||||
icao=f"{amos_data.get('icao', '')}_RWY_{i}",
|
||||
city=city_lower,
|
||||
temp_c=t,
|
||||
temp_c=point_temp,
|
||||
obs_time=amos_data.get("observation_time") or datetime.now().isoformat(),
|
||||
)
|
||||
if point_temp is not None and runway_label:
|
||||
DBManager().append_runway_obs(
|
||||
icao=amos_data.get("icao") or "",
|
||||
city=city_lower,
|
||||
runway=runway_label,
|
||||
target_runway_max=point_temp,
|
||||
otime_utc=amos_data.get("observation_time") or datetime.now().isoformat(),
|
||||
)
|
||||
except Exception:
|
||||
logger.exception("airport_obs_log append failed for amos city={}", city_lower)
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user