统一城市详情实时观测叠加路径
This commit is contained in:
@@ -1518,6 +1518,127 @@ def test_chart_data_cache_hit_overlays_latest_cwa_from_airport_obs_log(monkeypat
|
|||||||
assert payload["airport_current"]["source_code"] == "cwa"
|
assert payload["airport_current"]["source_code"] == "cwa"
|
||||||
|
|
||||||
|
|
||||||
|
def test_full_detail_batch_overlays_latest_official_observations_from_airport_obs_log(monkeypatch):
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
class FakeCache:
|
||||||
|
def get_city_cache(self, kind, city):
|
||||||
|
assert kind == "full"
|
||||||
|
if city == "tokyo":
|
||||||
|
return {
|
||||||
|
"payload": {
|
||||||
|
"name": city,
|
||||||
|
"display_name": "Tokyo",
|
||||||
|
"temp_symbol": "°C",
|
||||||
|
"local_date": "2026-06-14",
|
||||||
|
"local_time": "19:00",
|
||||||
|
"current": {
|
||||||
|
"temp": 23.0,
|
||||||
|
"source_code": "metar",
|
||||||
|
"obs_time": "2026-06-14T10:00:00+00:00",
|
||||||
|
},
|
||||||
|
"airport_current": {
|
||||||
|
"temp": 23.0,
|
||||||
|
"source_code": "metar",
|
||||||
|
"obs_time": "2026-06-14T10:00:00+00:00",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
"payload": {
|
||||||
|
"name": city,
|
||||||
|
"display_name": "Taipei",
|
||||||
|
"temp_symbol": "°C",
|
||||||
|
"local_date": "2026-06-14",
|
||||||
|
"local_time": "18:00",
|
||||||
|
"current": {
|
||||||
|
"temp": 26.0,
|
||||||
|
"source_code": "cwa",
|
||||||
|
"obs_time": "2026-06-14T10:00:00+00:00",
|
||||||
|
},
|
||||||
|
"airport_current": {
|
||||||
|
"temp": 26.0,
|
||||||
|
"source_code": "cwa",
|
||||||
|
"obs_time": "2026-06-14T10:00:00+00:00",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_latest_raw_observation(self, source, city):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_airport_obs_recent(self, icao, minutes=30):
|
||||||
|
if icao == "44166":
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"icao": "44166",
|
||||||
|
"city": "tokyo",
|
||||||
|
"temp_c": 24.0,
|
||||||
|
"obs_time": "2026-06-16T06:00:00+09:00",
|
||||||
|
"created_at": "2026-06-15T21:00:15+00:00",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
if icao == "466920":
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"icao": "466920",
|
||||||
|
"city": "taipei",
|
||||||
|
"temp_c": 29.4,
|
||||||
|
"obs_time": "2026-06-16T15:30:00+08:00",
|
||||||
|
"created_at": "2026-06-16T07:30:15+00:00",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
return []
|
||||||
|
|
||||||
|
class FakeWeather:
|
||||||
|
def fetch_jma_amedas_official_nearby(self, city, use_fahrenheit=False):
|
||||||
|
return []
|
||||||
|
|
||||||
|
def fetch_jma_amedas_current(self, city, use_fahrenheit=False):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def fetch_cwa_taipei_settlement_current(self):
|
||||||
|
return None
|
||||||
|
|
||||||
|
city_api._CITY_DETAIL_PAYLOAD_CACHE.clear()
|
||||||
|
city_api._CITY_DETAIL_PAYLOAD_CACHE_TS.clear()
|
||||||
|
monkeypatch.setattr(city_api.legacy_routes, "_CACHE_DB", FakeCache())
|
||||||
|
monkeypatch.setattr(city_api.legacy_routes, "_weather", FakeWeather())
|
||||||
|
monkeypatch.setattr(
|
||||||
|
city_api.legacy_routes,
|
||||||
|
"_overlay_latest_wunderground_current",
|
||||||
|
lambda city, payload: payload,
|
||||||
|
)
|
||||||
|
|
||||||
|
_, tokyo = asyncio.run(
|
||||||
|
city_api._build_city_detail_batch_item_async(
|
||||||
|
"tokyo",
|
||||||
|
force_refresh=False,
|
||||||
|
market_slug=None,
|
||||||
|
target_date=None,
|
||||||
|
resolution="10m",
|
||||||
|
detail_scope="full",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
_, taipei = asyncio.run(
|
||||||
|
city_api._build_city_detail_batch_item_async(
|
||||||
|
"taipei",
|
||||||
|
force_refresh=False,
|
||||||
|
market_slug=None,
|
||||||
|
target_date=None,
|
||||||
|
resolution="10m",
|
||||||
|
detail_scope="full",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assert tokyo["overview"]["local_date"] == "2026-06-16"
|
||||||
|
assert tokyo["airport_current"]["source_code"] == "jma_amedas"
|
||||||
|
assert tokyo["airport_current"]["temp"] == 24.0
|
||||||
|
assert taipei["overview"]["local_date"] == "2026-06-16"
|
||||||
|
assert taipei["airport_current"]["source_code"] == "cwa"
|
||||||
|
assert taipei["airport_current"]["temp"] == 29.4
|
||||||
|
|
||||||
|
|
||||||
def test_chart_data_returns_cached_payload_when_optional_overlay_times_out(monkeypatch):
|
def test_chart_data_returns_cached_payload_when_optional_overlay_times_out(monkeypatch):
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
|
|||||||
+49
-90
@@ -138,6 +138,52 @@ async def _run_latest_observation_city_chart_overlay(
|
|||||||
return payload
|
return payload
|
||||||
|
|
||||||
|
|
||||||
|
async def _overlay_latest_observation_sources(city: str, payload: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
latest_payload = payload
|
||||||
|
latest_payload = await _run_latest_observation_city_chart_overlay(
|
||||||
|
city=city,
|
||||||
|
overlay_name="amsc_latest_raw",
|
||||||
|
payload=latest_payload,
|
||||||
|
fn=overlay_latest_amsc_observation,
|
||||||
|
args=(legacy_routes._CACHE_DB, city, latest_payload),
|
||||||
|
)
|
||||||
|
latest_payload = await _run_latest_observation_city_chart_overlay(
|
||||||
|
city=city,
|
||||||
|
overlay_name="jma_amedas_latest",
|
||||||
|
payload=latest_payload,
|
||||||
|
fn=overlay_latest_jma_amedas_observation,
|
||||||
|
args=(legacy_routes._weather, city, latest_payload, legacy_routes._CACHE_DB),
|
||||||
|
)
|
||||||
|
latest_payload = await _run_latest_observation_city_chart_overlay(
|
||||||
|
city=city,
|
||||||
|
overlay_name="amos_latest_raw",
|
||||||
|
payload=latest_payload,
|
||||||
|
fn=overlay_latest_amos_observation,
|
||||||
|
args=(legacy_routes._CACHE_DB, city, latest_payload),
|
||||||
|
)
|
||||||
|
latest_payload = await _run_latest_observation_city_chart_overlay(
|
||||||
|
city=city,
|
||||||
|
overlay_name="mgm_latest_raw",
|
||||||
|
payload=latest_payload,
|
||||||
|
fn=overlay_latest_mgm_observation,
|
||||||
|
args=(legacy_routes._CACHE_DB, city, latest_payload),
|
||||||
|
)
|
||||||
|
latest_payload = await _run_latest_observation_city_chart_overlay(
|
||||||
|
city=city,
|
||||||
|
overlay_name="hko_latest_raw",
|
||||||
|
payload=latest_payload,
|
||||||
|
fn=overlay_latest_hko_observation,
|
||||||
|
args=(legacy_routes._CACHE_DB, city, latest_payload),
|
||||||
|
)
|
||||||
|
return await _run_latest_observation_city_chart_overlay(
|
||||||
|
city=city,
|
||||||
|
overlay_name="cwa_taipei",
|
||||||
|
payload=latest_payload,
|
||||||
|
fn=overlay_latest_cwa_observation,
|
||||||
|
args=(legacy_routes._weather, city, latest_payload, legacy_routes._CACHE_DB),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def _get_cached_city_payload(city: str, kind: str) -> Dict[str, Any]:
|
async def _get_cached_city_payload(city: str, kind: str) -> Dict[str, Any]:
|
||||||
cached_entry = await run_in_threadpool(legacy_routes._CACHE_DB.get_city_cache, kind, city)
|
cached_entry = await run_in_threadpool(legacy_routes._CACHE_DB.get_city_cache, kind, city)
|
||||||
if not isinstance(cached_entry, dict):
|
if not isinstance(cached_entry, dict):
|
||||||
@@ -327,12 +373,7 @@ def _start_city_cache_stale_refresh(
|
|||||||
|
|
||||||
async def _overlay_cached_wunderground(city: str, payload: Dict[str, Any]) -> Dict[str, Any]:
|
async def _overlay_cached_wunderground(city: str, payload: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
latest_payload = await _overlay_cached_canonical_observation(city, payload)
|
latest_payload = await _overlay_cached_canonical_observation(city, payload)
|
||||||
latest_payload = await run_in_threadpool(
|
latest_payload = await _overlay_latest_observation_sources(city, latest_payload)
|
||||||
overlay_latest_amsc_observation,
|
|
||||||
legacy_routes._CACHE_DB,
|
|
||||||
city,
|
|
||||||
latest_payload,
|
|
||||||
)
|
|
||||||
return await run_in_threadpool(
|
return await run_in_threadpool(
|
||||||
legacy_routes._overlay_latest_wunderground_current,
|
legacy_routes._overlay_latest_wunderground_current,
|
||||||
city,
|
city,
|
||||||
@@ -681,48 +722,7 @@ async def _get_city_chart_data(city: str, *, force_refresh: bool) -> Dict[str, A
|
|||||||
fn=_overlay_cached_runway_history_from_db,
|
fn=_overlay_cached_runway_history_from_db,
|
||||||
args=(city, payload),
|
args=(city, payload),
|
||||||
)
|
)
|
||||||
payload = await _run_latest_observation_city_chart_overlay(
|
payload = await _overlay_latest_observation_sources(city, payload)
|
||||||
city=city,
|
|
||||||
overlay_name="amsc_latest_raw",
|
|
||||||
payload=payload,
|
|
||||||
fn=overlay_latest_amsc_observation,
|
|
||||||
args=(legacy_routes._CACHE_DB, city, payload),
|
|
||||||
)
|
|
||||||
payload = await _run_latest_observation_city_chart_overlay(
|
|
||||||
city=city,
|
|
||||||
overlay_name="jma_amedas_latest",
|
|
||||||
payload=payload,
|
|
||||||
fn=overlay_latest_jma_amedas_observation,
|
|
||||||
args=(legacy_routes._weather, city, payload, legacy_routes._CACHE_DB),
|
|
||||||
)
|
|
||||||
payload = await _run_latest_observation_city_chart_overlay(
|
|
||||||
city=city,
|
|
||||||
overlay_name="amos_latest_raw",
|
|
||||||
payload=payload,
|
|
||||||
fn=overlay_latest_amos_observation,
|
|
||||||
args=(legacy_routes._CACHE_DB, city, payload),
|
|
||||||
)
|
|
||||||
payload = await _run_latest_observation_city_chart_overlay(
|
|
||||||
city=city,
|
|
||||||
overlay_name="mgm_latest_raw",
|
|
||||||
payload=payload,
|
|
||||||
fn=overlay_latest_mgm_observation,
|
|
||||||
args=(legacy_routes._CACHE_DB, city, payload),
|
|
||||||
)
|
|
||||||
payload = await _run_latest_observation_city_chart_overlay(
|
|
||||||
city=city,
|
|
||||||
overlay_name="hko_latest_raw",
|
|
||||||
payload=payload,
|
|
||||||
fn=overlay_latest_hko_observation,
|
|
||||||
args=(legacy_routes._CACHE_DB, city, payload),
|
|
||||||
)
|
|
||||||
payload = await _run_latest_observation_city_chart_overlay(
|
|
||||||
city=city,
|
|
||||||
overlay_name="cwa_taipei",
|
|
||||||
payload=payload,
|
|
||||||
fn=overlay_latest_cwa_observation,
|
|
||||||
args=(legacy_routes._weather, city, payload, legacy_routes._CACHE_DB),
|
|
||||||
)
|
|
||||||
return await _run_optional_city_chart_overlay(
|
return await _run_optional_city_chart_overlay(
|
||||||
city=city,
|
city=city,
|
||||||
overlay_name="wunderground_current",
|
overlay_name="wunderground_current",
|
||||||
@@ -745,48 +745,7 @@ async def _get_city_chart_data(city: str, *, force_refresh: bool) -> Dict[str, A
|
|||||||
fn=_overlay_cached_runway_history_from_db,
|
fn=_overlay_cached_runway_history_from_db,
|
||||||
args=(city, payload),
|
args=(city, payload),
|
||||||
)
|
)
|
||||||
payload = await _run_latest_observation_city_chart_overlay(
|
payload = await _overlay_latest_observation_sources(city, payload)
|
||||||
city=city,
|
|
||||||
overlay_name="amsc_latest_raw",
|
|
||||||
payload=payload,
|
|
||||||
fn=overlay_latest_amsc_observation,
|
|
||||||
args=(legacy_routes._CACHE_DB, city, payload),
|
|
||||||
)
|
|
||||||
payload = await _run_latest_observation_city_chart_overlay(
|
|
||||||
city=city,
|
|
||||||
overlay_name="jma_amedas_latest",
|
|
||||||
payload=payload,
|
|
||||||
fn=overlay_latest_jma_amedas_observation,
|
|
||||||
args=(legacy_routes._weather, city, payload, legacy_routes._CACHE_DB),
|
|
||||||
)
|
|
||||||
payload = await _run_latest_observation_city_chart_overlay(
|
|
||||||
city=city,
|
|
||||||
overlay_name="amos_latest_raw",
|
|
||||||
payload=payload,
|
|
||||||
fn=overlay_latest_amos_observation,
|
|
||||||
args=(legacy_routes._CACHE_DB, city, payload),
|
|
||||||
)
|
|
||||||
payload = await _run_latest_observation_city_chart_overlay(
|
|
||||||
city=city,
|
|
||||||
overlay_name="mgm_latest_raw",
|
|
||||||
payload=payload,
|
|
||||||
fn=overlay_latest_mgm_observation,
|
|
||||||
args=(legacy_routes._CACHE_DB, city, payload),
|
|
||||||
)
|
|
||||||
payload = await _run_latest_observation_city_chart_overlay(
|
|
||||||
city=city,
|
|
||||||
overlay_name="hko_latest_raw",
|
|
||||||
payload=payload,
|
|
||||||
fn=overlay_latest_hko_observation,
|
|
||||||
args=(legacy_routes._CACHE_DB, city, payload),
|
|
||||||
)
|
|
||||||
payload = await _run_latest_observation_city_chart_overlay(
|
|
||||||
city=city,
|
|
||||||
overlay_name="cwa_taipei",
|
|
||||||
payload=payload,
|
|
||||||
fn=overlay_latest_cwa_observation,
|
|
||||||
args=(legacy_routes._weather, city, payload, legacy_routes._CACHE_DB),
|
|
||||||
)
|
|
||||||
return await _run_optional_city_chart_overlay(
|
return await _run_optional_city_chart_overlay(
|
||||||
city=city,
|
city=city,
|
||||||
overlay_name="wunderground_current",
|
overlay_name="wunderground_current",
|
||||||
|
|||||||
Reference in New Issue
Block a user