diff --git a/src/data_collection/amsc_awos_sources.py b/src/data_collection/amsc_awos_sources.py index 33a7f7c3..5d203a05 100644 --- a/src/data_collection/amsc_awos_sources.py +++ b/src/data_collection/amsc_awos_sources.py @@ -164,6 +164,7 @@ def _amsc_parse_wind_plate_payload( valid_values = [] observation_time = None observation_time_local = None + observation_time_epoch = None raw_metar = None for key, raw_row in data.items(): @@ -179,8 +180,22 @@ def _amsc_parse_wind_plate_payload( if not points: continue - if observation_time is None: - observation_time, observation_time_local = _amsc_parse_utc_time(raw_row.get("OTIME")) + row_observation_time, row_observation_time_local = _amsc_parse_utc_time(raw_row.get("OTIME")) + row_observation_epoch = None + if row_observation_time: + try: + row_observation_epoch = datetime.fromisoformat(row_observation_time).timestamp() + except ValueError: + row_observation_epoch = None + if row_observation_epoch is not None and ( + observation_time_epoch is None or row_observation_epoch > observation_time_epoch + ): + observation_time = row_observation_time + observation_time_local = row_observation_time_local + observation_time_epoch = row_observation_epoch + elif observation_time is None and row_observation_time: + observation_time = row_observation_time + observation_time_local = row_observation_time_local if raw_metar is None and raw_row.get("METAR"): raw_metar = str(raw_row.get("METAR")) diff --git a/tests/test_amsc_awos_sources.py b/tests/test_amsc_awos_sources.py index fa8acaab..96c51074 100644 --- a/tests/test_amsc_awos_sources.py +++ b/tests/test_amsc_awos_sources.py @@ -135,6 +135,36 @@ def test_parse_wind_plate_payload_uses_end_temperature_when_target_is_second_run assert parsed["runway_obs"]["point_temperatures"][0]["target_runway_max"] == 33.7 +def test_parse_wind_plate_payload_uses_latest_runway_observation_time(): + payload = { + "code": 200, + "data": { + "16L/34R": { + "RNO": "16L/34R", + "OTIME": "2026-06-14 10:44:00", + "TDZ_TEMP": "22.0", + "MID_TEMP": "22.1", + "END_TEMP": "22.2", + }, + "17L/35R": { + "RNO": "17L/35R", + "OTIME": "2026-06-14 15:43:00", + "TDZ_TEMP": "24.9", + "MID_TEMP": "25.1", + "END_TEMP": "25.4", + }, + }, + } + + parsed = _amsc_parse_wind_plate_payload(payload, city_key="shanghai", icao="ZSPD") + + assert parsed is not None + assert parsed["settlement_runway"] == "35R" + assert parsed["temp_c"] == 25.4 + assert parsed["observation_time"] == "2026-06-14T15:43:00+00:00" + assert parsed["observation_time_local"] == "2026-06-14 23:43:00" + + def test_parse_wind_plate_payload_rejects_unauthorized_or_empty_payloads(): assert _amsc_parse_wind_plate_payload( {"errCode": -12010, "errMsg": "无权访问此接口"}, diff --git a/tests/test_api_performance_timing.py b/tests/test_api_performance_timing.py index 3b34cab3..728b3323 100644 --- a/tests/test_api_performance_timing.py +++ b/tests/test_api_performance_timing.py @@ -129,7 +129,7 @@ def test_scan_terminal_response_includes_backend_server_timing(monkeypatch): monkeypatch.setattr( scan_api.legacy_routes, "build_scan_terminal_payload", - lambda filters, force_refresh=False, timing_recorder=None: { + lambda filters, force_refresh=False, timing_recorder=None, **_kwargs: { "rows": [], "filters": filters, "status": "ready", @@ -155,7 +155,7 @@ def test_scan_terminal_stale_response_is_not_cached(monkeypatch): monkeypatch.setattr( scan_api.legacy_routes, "build_scan_terminal_payload", - lambda filters, force_refresh=False, timing_recorder=None: { + lambda filters, force_refresh=False, timing_recorder=None, **_kwargs: { "rows": [], "filters": filters, "status": "ready", diff --git a/web/services/latest_observation_overlay.py b/web/services/latest_observation_overlay.py index 5b017217..0027b4e8 100644 --- a/web/services/latest_observation_overlay.py +++ b/web/services/latest_observation_overlay.py @@ -58,13 +58,18 @@ def _block_epoch(block: Any) -> Optional[int]: def _raw_amsc_epoch(row: dict[str, Any], raw_payload: dict[str, Any]) -> Optional[int]: - values = ( + payload_values = ( raw_payload.get("observation_time"), raw_payload.get("observed_at"), - row.get("observed_at"), ) - parsed = [epoch for epoch in (parse_observation_epoch(value) for value in values) if epoch is not None] - return max(parsed) if parsed else None + parsed = [ + epoch + for epoch in (parse_observation_epoch(value) for value in payload_values) + if epoch is not None + ] + if parsed: + return max(parsed) + return parse_observation_epoch(row.get("observed_at")) def _to_float(value: Any) -> Optional[float]: