Fix WU historical current observation merge
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
from web.services.city_payloads import build_city_summary_payload
|
||||
from web.services.city_payloads import build_city_detail_payload, build_city_summary_payload
|
||||
from web.services import city_runtime
|
||||
|
||||
|
||||
def test_city_summary_payload_preserves_deb_metadata():
|
||||
@@ -26,3 +27,77 @@ def test_city_summary_payload_preserves_deb_metadata():
|
||||
"bias_adjustment": 1.3,
|
||||
"bias_samples": 18,
|
||||
}
|
||||
|
||||
|
||||
def test_city_payloads_expose_wunderground_current():
|
||||
data = {
|
||||
"name": "shanghai",
|
||||
"display_name": "Shanghai",
|
||||
"temp_symbol": "°C",
|
||||
"current": {},
|
||||
"risk": {},
|
||||
"wunderground_current": {
|
||||
"source": "wunderground_historical",
|
||||
"station_code": "ZSPD",
|
||||
"max_so_far": 26,
|
||||
"today_obs": [{"time": "13:30", "temp": 26}],
|
||||
},
|
||||
}
|
||||
|
||||
summary = build_city_summary_payload(data)
|
||||
detail = build_city_detail_payload(data)
|
||||
|
||||
assert summary["wunderground_current"]["max_so_far"] == 26
|
||||
assert detail["wunderground_current"]["max_so_far"] == 26
|
||||
assert detail["official"]["wunderground_current"]["station_code"] == "ZSPD"
|
||||
assert detail["timeseries"]["wunderground_today_obs"] == [{"time": "13:30", "temp": 26}]
|
||||
|
||||
|
||||
def test_api_payload_overlays_latest_wunderground_state(monkeypatch):
|
||||
stale_payload = {
|
||||
"name": "guangzhou",
|
||||
"temp_symbol": "°C",
|
||||
"utc_offset_seconds": 28800,
|
||||
"wunderground_current": {
|
||||
"source": "wunderground_historical",
|
||||
"station_code": "ZGGG",
|
||||
"temp": 36,
|
||||
"max_so_far": 36,
|
||||
"today_obs": [{"time": "14:00", "temp": 36}],
|
||||
},
|
||||
"official": {
|
||||
"wunderground_current": {
|
||||
"source": "wunderground_historical",
|
||||
"station_code": "ZGGG",
|
||||
"temp": 36,
|
||||
"max_so_far": 36,
|
||||
},
|
||||
},
|
||||
"timeseries": {
|
||||
"wunderground_today_obs": [{"time": "14:00", "temp": 36}],
|
||||
},
|
||||
}
|
||||
|
||||
def fake_fetch(city: str, *, use_fahrenheit: bool, utc_offset: int):
|
||||
assert city == "guangzhou"
|
||||
assert use_fahrenheit is False
|
||||
assert utc_offset == 28800
|
||||
return {
|
||||
"source": "wunderground_historical",
|
||||
"station_code": "ZGGG",
|
||||
"temp": 38,
|
||||
"max_so_far": 38,
|
||||
"today_obs": [{"time": "15:17", "temp": 38}],
|
||||
}
|
||||
|
||||
monkeypatch.setattr(city_runtime._weather, "fetch_wunderground_historical", fake_fetch)
|
||||
|
||||
overlay = getattr(city_runtime, "_overlay_latest_wunderground_current", None)
|
||||
assert callable(overlay), "city API must overlay cached payloads with latest WU state"
|
||||
payload = overlay("guangzhou", stale_payload)
|
||||
|
||||
assert payload["wunderground_current"]["temp"] == 38
|
||||
assert payload["wunderground_current"]["max_so_far"] == 38
|
||||
assert payload["official"]["wunderground_current"]["max_so_far"] == 38
|
||||
assert payload["timeseries"]["wunderground_today_obs"] == [{"time": "15:17", "temp": 38}]
|
||||
assert stale_payload["wunderground_current"]["max_so_far"] == 36
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from urllib.parse import parse_qs, urlparse
|
||||
|
||||
import httpx
|
||||
|
||||
from src.data_collection.weather_sources import WeatherDataCollector
|
||||
|
||||
|
||||
def _collector(monkeypatch, tmp_path) -> WeatherDataCollector:
|
||||
monkeypatch.setenv("OPEN_METEO_DISK_CACHE_PATH", str(tmp_path / "om-cache.json"))
|
||||
return WeatherDataCollector({"weather": {}})
|
||||
|
||||
|
||||
def test_fetch_wunderground_historical_uses_weather_com_historical_json(monkeypatch, tmp_path):
|
||||
collector = _collector(monkeypatch, tmp_path)
|
||||
called = {}
|
||||
|
||||
def fake_get(url: str, **_kwargs):
|
||||
called["url"] = url
|
||||
return httpx.Response(
|
||||
200,
|
||||
json={
|
||||
"metadata": {
|
||||
"status_code": 200,
|
||||
"location_id": "ZSPD:9:CN",
|
||||
"units": "m",
|
||||
},
|
||||
"observations": [
|
||||
{
|
||||
"obs_id": "ZSPD",
|
||||
"obs_name": "Shanghai/Pudong",
|
||||
"valid_time_gmt": 1780027200,
|
||||
"temp": 25,
|
||||
"wx_phrase": "Fair",
|
||||
},
|
||||
{
|
||||
"obs_id": "ZSPD",
|
||||
"obs_name": "Shanghai/Pudong",
|
||||
"valid_time_gmt": 1780029000,
|
||||
"temp": 26,
|
||||
"wx_phrase": "Fair",
|
||||
},
|
||||
{
|
||||
"obs_id": "ZSPD",
|
||||
"obs_name": "Shanghai/Pudong",
|
||||
"valid_time_gmt": 1780032600,
|
||||
"temp": 26,
|
||||
"wx_phrase": "Fair",
|
||||
},
|
||||
],
|
||||
},
|
||||
request=httpx.Request("GET", url),
|
||||
)
|
||||
|
||||
monkeypatch.setattr(collector, "_http_get", fake_get)
|
||||
|
||||
payload = collector.fetch_wunderground_historical(
|
||||
"shanghai",
|
||||
use_fahrenheit=False,
|
||||
utc_offset=28800,
|
||||
local_date="2026-05-29",
|
||||
)
|
||||
|
||||
parsed = urlparse(called["url"])
|
||||
query = parse_qs(parsed.query)
|
||||
assert parsed.path == "/v1/location/ZSPD:9:CN/observations/historical.json"
|
||||
assert query["units"] == ["m"]
|
||||
assert query["startDate"] == ["20260529"]
|
||||
assert query["endDate"] == ["20260529"]
|
||||
assert payload["source"] == "wunderground_historical"
|
||||
assert payload["station_code"] == "ZSPD"
|
||||
assert payload["location_id"] == "ZSPD:9:CN"
|
||||
assert payload["temp"] == 26
|
||||
assert payload["max_so_far"] == 26
|
||||
assert payload["max_temp_time"] == "12:30"
|
||||
assert payload["obs_time"] == "13:30"
|
||||
|
||||
|
||||
def test_fetch_wunderground_historical_uses_current_json_to_lift_latest_and_high(monkeypatch, tmp_path):
|
||||
collector = _collector(monkeypatch, tmp_path)
|
||||
calls = []
|
||||
|
||||
def fake_get(url: str, **kwargs):
|
||||
calls.append({"url": url, "headers": kwargs.get("headers") or {}})
|
||||
if urlparse(url).path.endswith("/current.json"):
|
||||
return httpx.Response(
|
||||
200,
|
||||
json={
|
||||
"metadata": {
|
||||
"status_code": 200,
|
||||
"location_id": "ZGGG:9:CN",
|
||||
"units": "m",
|
||||
},
|
||||
"observation": {
|
||||
"obs_time": 1780039020,
|
||||
"obs_time_local": "2026-05-29T15:17:00+0800",
|
||||
"metric": {
|
||||
"temp": 38,
|
||||
"temp_max_24hour": 38,
|
||||
},
|
||||
},
|
||||
},
|
||||
request=httpx.Request("GET", url),
|
||||
)
|
||||
return httpx.Response(
|
||||
200,
|
||||
json={
|
||||
"metadata": {
|
||||
"status_code": 200,
|
||||
"location_id": "ZGGG:9:CN",
|
||||
"units": "m",
|
||||
},
|
||||
"observations": [
|
||||
{
|
||||
"obs_id": "ZGGG",
|
||||
"valid_time_gmt": 1780034400,
|
||||
"temp": 36,
|
||||
},
|
||||
],
|
||||
},
|
||||
request=httpx.Request("GET", url),
|
||||
)
|
||||
|
||||
monkeypatch.setattr(collector, "_http_get", fake_get)
|
||||
|
||||
payload = collector.fetch_wunderground_historical(
|
||||
"guangzhou",
|
||||
use_fahrenheit=False,
|
||||
utc_offset=28800,
|
||||
local_date="2026-05-29",
|
||||
)
|
||||
|
||||
paths = [urlparse(call["url"]).path for call in calls]
|
||||
assert "/v1/location/ZGGG:9:CN/observations/historical.json" in paths
|
||||
assert "/v1/location/ZGGG:9:CN/observations/current.json" in paths
|
||||
assert all(call["headers"].get("Cache-Control") == "no-cache" for call in calls)
|
||||
assert all(call["headers"].get("Pragma") == "no-cache" for call in calls)
|
||||
assert payload["temp"] == 38
|
||||
assert payload["obs_time"] == "15:17"
|
||||
assert payload["max_so_far"] == 38
|
||||
assert payload["max_temp_time"] == "15:17"
|
||||
|
||||
|
||||
def test_fetch_wunderground_historical_keeps_fahrenheit_for_us_cities(monkeypatch, tmp_path):
|
||||
collector = _collector(monkeypatch, tmp_path)
|
||||
called = {}
|
||||
|
||||
def fake_get(url: str, **_kwargs):
|
||||
called["url"] = url
|
||||
return httpx.Response(
|
||||
200,
|
||||
json={
|
||||
"metadata": {
|
||||
"status_code": 200,
|
||||
"location_id": "KLGA:9:US",
|
||||
"units": "e",
|
||||
},
|
||||
"observations": [
|
||||
{"obs_id": "KLGA", "valid_time_gmt": 1780027200, "temp": 73},
|
||||
{"obs_id": "KLGA", "valid_time_gmt": 1780030800, "temp": 75},
|
||||
],
|
||||
},
|
||||
request=httpx.Request("GET", url),
|
||||
)
|
||||
|
||||
monkeypatch.setattr(collector, "_http_get", fake_get)
|
||||
|
||||
payload = collector.fetch_wunderground_historical(
|
||||
"new york",
|
||||
use_fahrenheit=True,
|
||||
utc_offset=-14400,
|
||||
local_date="2026-05-29",
|
||||
)
|
||||
|
||||
parsed = urlparse(called["url"])
|
||||
assert parsed.path == "/v1/location/KLGA:9:US/observations/historical.json"
|
||||
assert parse_qs(parsed.query)["units"] == ["e"]
|
||||
assert payload["temp_symbol"] == "°F"
|
||||
assert payload["max_so_far"] == 75
|
||||
|
||||
|
||||
def test_fetch_all_sources_attaches_wunderground_historical(monkeypatch, tmp_path):
|
||||
collector = _collector(monkeypatch, tmp_path)
|
||||
called = {}
|
||||
|
||||
monkeypatch.setattr(collector, "fetch_settlement_current", lambda _city: None)
|
||||
monkeypatch.setattr(collector, "_supports_aviationweather", lambda _city: False)
|
||||
|
||||
def fake_fetch(city: str, *, use_fahrenheit: bool, utc_offset: int, local_date=None):
|
||||
called["city"] = city
|
||||
called["use_fahrenheit"] = use_fahrenheit
|
||||
called["utc_offset"] = utc_offset
|
||||
called["local_date"] = local_date
|
||||
return {
|
||||
"source": "wunderground_historical",
|
||||
"location_id": "ZSPD:9:CN",
|
||||
"max_so_far": 26,
|
||||
}
|
||||
|
||||
monkeypatch.setattr(collector, "fetch_wunderground_historical", fake_fetch)
|
||||
|
||||
payload = collector.fetch_all_sources("shanghai")
|
||||
|
||||
assert payload["wunderground_current"]["max_so_far"] == 26
|
||||
assert called == {
|
||||
"city": "shanghai",
|
||||
"use_fahrenheit": False,
|
||||
"utc_offset": 28800,
|
||||
"local_date": None,
|
||||
}
|
||||
Reference in New Issue
Block a user