706 lines
24 KiB
Python
706 lines
24 KiB
Python
import web.services.latest_observation_overlay as observation_overlay
|
|
from web.services.latest_observation_overlay import overlay_latest_amsc_observation
|
|
|
|
|
|
def test_overlay_replaces_amos_when_old_local_time_string_looks_later_than_new_utc():
|
|
class FakeDB:
|
|
def get_latest_raw_observation(self, source, city):
|
|
assert (source, city) == ("amsc_awos", "qingdao")
|
|
return {
|
|
"observed_at": "2026-06-14T17:23:00+00:00",
|
|
"fetched_at": "2026-06-14T17:23:30+00:00",
|
|
"station_code": "ZSQD",
|
|
"station_name": "Qingdao Jiaodong",
|
|
"payload": {
|
|
"source": "amsc_awos",
|
|
"source_label": "AMSC AWOS Qingdao Jiaodong (ZSQD)",
|
|
"icao": "ZSQD",
|
|
"temp_c": 18.2,
|
|
"observation_time": "2026-06-14T17:23:00+00:00",
|
|
"observation_time_local": "2026-06-15 01:23:00",
|
|
"runway_obs": {
|
|
"point_temperatures": [
|
|
{
|
|
"runway": "17L/35R",
|
|
"tdz_temp": 18.0,
|
|
"mid_temp": 18.2,
|
|
"end_temp": 18.1,
|
|
}
|
|
],
|
|
},
|
|
},
|
|
}
|
|
|
|
stale_payload = {
|
|
"name": "qingdao",
|
|
"temp_symbol": "C",
|
|
"amos": {
|
|
"source": "amsc_awos",
|
|
"temp_c": 21.0,
|
|
"observation_time": "2026-06-14T10:46:00+00:00",
|
|
"observation_time_local": "2026-06-14 18:46:00",
|
|
},
|
|
}
|
|
|
|
result = overlay_latest_amsc_observation(FakeDB(), "qingdao", stale_payload)
|
|
|
|
assert result["amos"]["observation_time"] == "2026-06-14T17:23:00+00:00"
|
|
assert result["amos"]["observation_time_local"] == "2026-06-15 01:23:00"
|
|
assert result["amos"]["runway_obs"]["point_temperatures"][0]["runway"] == "17L/35R"
|
|
|
|
|
|
def test_overlay_appends_latest_amsc_points_to_runway_history():
|
|
runway_writes = []
|
|
|
|
class FakeDB:
|
|
def get_latest_raw_observation(self, source, city):
|
|
assert (source, city) == ("amsc_awos", "chengdu")
|
|
return {
|
|
"observed_at": "2026-06-15T10:51:00+00:00",
|
|
"fetched_at": "2026-06-15T10:51:30+00:00",
|
|
"station_code": "ZUUU",
|
|
"station_name": "Chengdu Shuangliu",
|
|
"payload": {
|
|
"source": "amsc_awos",
|
|
"source_label": "AMSC AWOS Chengdu Shuangliu (ZUUU)",
|
|
"icao": "ZUUU",
|
|
"temp_c": 28.4,
|
|
"observation_time": "2026-06-15T10:51:00+00:00",
|
|
"observation_time_local": "2026-06-15 18:51:00",
|
|
"runway_obs": {
|
|
"point_temperatures": [
|
|
{
|
|
"runway": "02L/20R",
|
|
"tdz_temp": 28.4,
|
|
"end_temp": 28.2,
|
|
"target_runway_max": 28.4,
|
|
},
|
|
{
|
|
"runway": "02R/20L",
|
|
"tdz_temp": 28.1,
|
|
"end_temp": 28.0,
|
|
"target_runway_max": 28.1,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
}
|
|
|
|
def append_runway_obs(self, **kwargs):
|
|
runway_writes.append(kwargs)
|
|
|
|
stale_payload = {
|
|
"name": "chengdu",
|
|
"temp_symbol": "°C",
|
|
"runway_plate_history": {
|
|
"02L/20R": [{"time": "2026-06-14T10:44:00+00:00", "temp": 31.6}],
|
|
},
|
|
"amos": {
|
|
"source": "amsc_awos",
|
|
"observation_time": "2026-06-14T10:44:00+00:00",
|
|
},
|
|
}
|
|
|
|
result = overlay_latest_amsc_observation(FakeDB(), "chengdu", stale_payload)
|
|
|
|
assert result["runway_plate_history"]["02L/20R"][-1] == {
|
|
"time": "2026-06-15T10:51:00+00:00",
|
|
"temp": 28.4,
|
|
}
|
|
assert result["runway_plate_history"]["02R/20L"][-1] == {
|
|
"time": "2026-06-15T10:51:00+00:00",
|
|
"temp": 28.1,
|
|
}
|
|
assert [(row["icao"], row["runway"], row["otime_utc"]) for row in runway_writes] == [
|
|
("ZUUU", "02L/20R", "2026-06-15T10:51:00+00:00"),
|
|
("ZUUU", "02R/20L", "2026-06-15T10:51:00+00:00"),
|
|
]
|
|
assert runway_writes[0]["target_runway_max"] == 28.4
|
|
|
|
|
|
def test_overlay_builds_amsc_runway_history_from_raw_store():
|
|
def raw_row(observed_at, temp):
|
|
return {
|
|
"observed_at": observed_at,
|
|
"payload": {
|
|
"source": "amsc_awos",
|
|
"icao": "ZUUU",
|
|
"temp_c": temp,
|
|
"observation_time": observed_at,
|
|
"runway_obs": {
|
|
"point_temperatures": [
|
|
{
|
|
"runway": "02L/20R",
|
|
"target_runway_max": temp,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
}
|
|
|
|
class FakeDB:
|
|
def get_latest_raw_observation(self, source, city):
|
|
assert (source, city) == ("amsc_awos", "chengdu")
|
|
return raw_row("2026-06-15T11:08:00+00:00", 25.4)
|
|
|
|
def list_raw_observation_history(self, source, city, *, minutes=60, limit=1000):
|
|
assert (source, city) == ("amsc_awos", "chengdu")
|
|
return [
|
|
raw_row("2026-06-15T11:04:00+00:00", 25.6),
|
|
raw_row("2026-06-15T11:08:00+00:00", 25.4),
|
|
]
|
|
|
|
result = overlay_latest_amsc_observation(
|
|
FakeDB(),
|
|
"chengdu",
|
|
{
|
|
"name": "chengdu",
|
|
"temp_symbol": "°C",
|
|
"runway_plate_history": {
|
|
"02L/20R": [{"time": "2026-06-12T10:44:00+00:00", "temp": 31.6}],
|
|
},
|
|
},
|
|
)
|
|
|
|
assert result["runway_plate_history"]["02L/20R"] == [
|
|
{"time": "2026-06-15T11:04:00+00:00", "temp": 25.6},
|
|
{"time": "2026-06-15T11:08:00+00:00", "temp": 25.4},
|
|
]
|
|
|
|
|
|
def test_overlay_skips_raw_store_when_runway_history_already_has_points():
|
|
existing_points = [
|
|
{"time": f"2026-06-15T{hour:02d}:00:00+00:00", "temp": 25.0 + hour / 10}
|
|
for hour in range(0, 15)
|
|
for _ in range(2)
|
|
]
|
|
|
|
class FakeDB:
|
|
def get_latest_raw_observation(self, source, city):
|
|
assert (source, city) == ("amsc_awos", "chengdu")
|
|
return {
|
|
"observed_at": "2026-06-15T14:30:00+00:00",
|
|
"station_code": "ZUUU",
|
|
"payload": {
|
|
"source": "amsc_awos",
|
|
"icao": "ZUUU",
|
|
"temp_c": 25.4,
|
|
"observation_time": "2026-06-15T14:30:00+00:00",
|
|
"runway_obs": {
|
|
"point_temperatures": [
|
|
{
|
|
"runway": "02L/20R",
|
|
"target_runway_max": 25.4,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
}
|
|
|
|
def list_raw_observation_history(self, source, city, *, minutes=60, limit=1000):
|
|
raise AssertionError("raw store history should not be read for populated runway history")
|
|
|
|
result = overlay_latest_amsc_observation(
|
|
FakeDB(),
|
|
"chengdu",
|
|
{
|
|
"name": "chengdu",
|
|
"temp_symbol": "°C",
|
|
"runway_plate_history": {
|
|
"02L/20R": existing_points,
|
|
},
|
|
},
|
|
)
|
|
|
|
assert result["runway_plate_history"]["02L/20R"][-1] == {
|
|
"time": "2026-06-15T14:30:00+00:00",
|
|
"temp": 25.4,
|
|
}
|
|
|
|
|
|
def test_overlay_uses_latest_success_when_newer_status_row_has_no_observation():
|
|
class FakeDB:
|
|
def get_latest_raw_observation(self, source, city):
|
|
assert (source, city) == ("amsc_awos", "chengdu")
|
|
return {
|
|
"status": "no_results",
|
|
"observed_at": "",
|
|
"fetched_at": "2026-06-14T17:02:09+00:00",
|
|
"updated_at_ts": 1781456529.0,
|
|
"payload": {
|
|
"source": "amsc_awos",
|
|
"city": "chengdu",
|
|
"status": "no_results",
|
|
"error": "source returned no observation rows",
|
|
},
|
|
}
|
|
|
|
def list_latest_raw_observations_for_city(self, city, *, limit=100):
|
|
assert city == "chengdu"
|
|
return [
|
|
self.get_latest_raw_observation("amsc_awos", "chengdu"),
|
|
{
|
|
"source": "amsc_awos",
|
|
"city": "chengdu",
|
|
"station_code": "ZUUU",
|
|
"station_name": "Chengdu Shuangliu",
|
|
"status": "ok",
|
|
"observed_at": "2026-06-14T17:00:00+00:00",
|
|
"fetched_at": "2026-06-14T17:00:30+00:00",
|
|
"updated_at_ts": 1781456430.0,
|
|
"payload": {
|
|
"source": "amsc_awos",
|
|
"source_label": "AMSC AWOS Chengdu Shuangliu (ZUUU)",
|
|
"icao": "ZUUU",
|
|
"temp_c": 25.8,
|
|
"observation_time": "2026-06-14T17:00:00+00:00",
|
|
"observation_time_local": "2026-06-15 01:00:00",
|
|
},
|
|
},
|
|
]
|
|
|
|
result = overlay_latest_amsc_observation(
|
|
FakeDB(),
|
|
"chengdu",
|
|
{"name": "chengdu", "temp_symbol": "°C", "amos": {}},
|
|
)
|
|
|
|
assert result["amos"]["temp_c"] == 25.8
|
|
assert result["current"]["temp"] == 25.8
|
|
assert result["airport_current"]["source_code"] == "amsc_awos"
|
|
|
|
|
|
def test_overlay_latest_jma_resets_stale_tokyo_detail_to_latest_local_day():
|
|
class FakeWeather:
|
|
def fetch_jma_amedas_official_nearby(self, city, use_fahrenheit=False):
|
|
assert (city, use_fahrenheit) == ("tokyo", False)
|
|
return [
|
|
{
|
|
"station_label": "羽田 10分实况 (JMA)",
|
|
"temp": 24.0,
|
|
"icao": "44166",
|
|
"source": "jma",
|
|
"source_label": "JMA",
|
|
"obs_time": "2026-06-16T06:00:00+09:00",
|
|
}
|
|
]
|
|
|
|
stale_payload = {
|
|
"name": "tokyo",
|
|
"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",
|
|
},
|
|
"airport_primary": {
|
|
"temp": 23.0,
|
|
"source_code": "metar",
|
|
"obs_time": "2026-06-14T10:00:00+00:00",
|
|
},
|
|
"overview": {
|
|
"local_date": "2026-06-14",
|
|
"local_time": "19:00",
|
|
"current_temp": 23.0,
|
|
},
|
|
"metar_today_obs": [
|
|
{"time": "00:00", "temp": 23.0},
|
|
{"time": "17:00", "temp": 25.0},
|
|
],
|
|
"timeseries": {
|
|
"metar_today_obs": [
|
|
{"time": "00:00", "temp": 23.0},
|
|
{"time": "17:00", "temp": 25.0},
|
|
],
|
|
},
|
|
}
|
|
|
|
assert hasattr(observation_overlay, "overlay_latest_jma_amedas_observation")
|
|
|
|
result = observation_overlay.overlay_latest_jma_amedas_observation(
|
|
FakeWeather(),
|
|
"tokyo",
|
|
stale_payload,
|
|
)
|
|
|
|
assert result["local_date"] == "2026-06-16"
|
|
assert result["local_time"] == "06:00"
|
|
assert result["current"]["temp"] == 24.0
|
|
assert result["current"]["source_code"] == "jma_amedas"
|
|
assert result["airport_current"]["obs_time"] == "2026-06-16T06:00:00+09:00"
|
|
assert result["airport_primary"]["station_code"] == "44166"
|
|
assert result["overview"]["local_date"] == "2026-06-16"
|
|
assert result["overview"]["current_temp"] == 24.0
|
|
assert result["metar_today_obs"] == [
|
|
{
|
|
"time": "06:00",
|
|
"temp": 24.0,
|
|
"obs_time": "2026-06-16T06:00:00+09:00",
|
|
"source_code": "jma_amedas",
|
|
"source_label": "JMA",
|
|
}
|
|
]
|
|
assert result["timeseries"]["metar_today_obs"] == result["metar_today_obs"]
|
|
|
|
|
|
def test_overlay_latest_jma_does_not_downgrade_newer_payload_current():
|
|
class FakeWeather:
|
|
def fetch_jma_amedas_official_nearby(self, city, use_fahrenheit=False):
|
|
return [
|
|
{
|
|
"station_label": "羽田 10分实况 (JMA)",
|
|
"temp": 24.0,
|
|
"icao": "44166",
|
|
"source": "jma",
|
|
"source_label": "JMA",
|
|
"obs_time": "2026-06-16T06:00:00+09:00",
|
|
}
|
|
]
|
|
|
|
payload = {
|
|
"name": "tokyo",
|
|
"local_date": "2026-06-16",
|
|
"local_time": "07:00",
|
|
"current": {
|
|
"temp": 25.0,
|
|
"source_code": "jma_amedas",
|
|
"obs_time": "2026-06-16T07:00:00+09:00",
|
|
},
|
|
}
|
|
|
|
result = observation_overlay.overlay_latest_jma_amedas_observation(
|
|
FakeWeather(),
|
|
"tokyo",
|
|
payload,
|
|
)
|
|
|
|
assert result is payload
|
|
assert result["current"]["temp"] == 25.0
|
|
assert result["local_time"] == "07:00"
|
|
|
|
|
|
def test_non_amsc_raw_overlays_keep_their_source_codes():
|
|
cases = [
|
|
(
|
|
"amos",
|
|
observation_overlay.overlay_latest_amos_observation,
|
|
"seoul",
|
|
{
|
|
"observed_at": "2026-06-16T06:03:00+09:00",
|
|
"fetched_at": "2026-06-15T21:03:30+00:00",
|
|
"station_code": "RKSS",
|
|
"station_name": "Gimpo",
|
|
"payload": {
|
|
"source": "amos",
|
|
"source_label": "AMOS",
|
|
"icao": "RKSS",
|
|
"temp": 22.4,
|
|
"observation_time": "2026-06-16T06:03:00+09:00",
|
|
},
|
|
},
|
|
),
|
|
(
|
|
"hko_obs",
|
|
observation_overlay.overlay_latest_hko_observation,
|
|
"hong kong",
|
|
{
|
|
"observed_at": "2026-06-16T05:55:00+08:00",
|
|
"fetched_at": "2026-06-15T21:55:30+00:00",
|
|
"station_code": "HKO",
|
|
"station_name": "Hong Kong Observatory",
|
|
"payload": {
|
|
"source": "hko_obs",
|
|
"source_label": "HKO",
|
|
"station_code": "HKO",
|
|
"station_label": "Hong Kong Observatory",
|
|
"temp": 28.6,
|
|
"obs_time": "2026-06-16T05:55:00+08:00",
|
|
},
|
|
},
|
|
),
|
|
(
|
|
"mgm",
|
|
observation_overlay.overlay_latest_mgm_observation,
|
|
"ankara",
|
|
{
|
|
"observed_at": "2026-06-16T00:10:00+03:00",
|
|
"fetched_at": "2026-06-15T21:10:30+00:00",
|
|
"station_code": "17130",
|
|
"station_name": "Ankara",
|
|
"payload": {
|
|
"source": "mgm",
|
|
"source_label": "MGM",
|
|
"icao": "17130",
|
|
"station_label": "Ankara (Bölge/Center)",
|
|
"temp": 25.3,
|
|
"obs_time": "2026-06-16T00:10:00+03:00",
|
|
},
|
|
},
|
|
),
|
|
]
|
|
|
|
for source_code, overlay_fn, city, row in cases:
|
|
class FakeDB:
|
|
def get_latest_raw_observation(self, source, requested_city):
|
|
assert (source, requested_city) == (source_code, city)
|
|
return row
|
|
|
|
result = overlay_fn(
|
|
FakeDB(),
|
|
city,
|
|
{
|
|
"name": city,
|
|
"temp_symbol": "°C",
|
|
"current": {
|
|
"temp": 19.0,
|
|
"source_code": "metar",
|
|
"obs_time": "2026-06-15T00:00:00+00:00",
|
|
},
|
|
"airport_current": {
|
|
"temp": 19.0,
|
|
"source_code": "metar",
|
|
"obs_time": "2026-06-15T00:00:00+00:00",
|
|
},
|
|
},
|
|
)
|
|
|
|
assert result["current"]["source_code"] == source_code
|
|
assert result["airport_current"]["source_code"] == source_code
|
|
assert result["current"]["source_label"] == row["payload"]["source_label"]
|
|
assert result["current"].get("settlement_source") != "amsc_awos"
|
|
assert result["canonical_temperature"]["source"] == source_code
|
|
|
|
|
|
def test_overlay_latest_cwa_resets_stale_taipei_detail_to_latest_local_day():
|
|
class FakeWeather:
|
|
def fetch_cwa_taipei_settlement_current(self):
|
|
return {
|
|
"source": "cwa",
|
|
"source_label": "CWA",
|
|
"station_code": "466920",
|
|
"station_name": "臺北",
|
|
"observation_time": "2026-06-16T15:30:00+08:00",
|
|
"current": {
|
|
"temp": 29.4,
|
|
"max_temp_so_far": 31.0,
|
|
},
|
|
}
|
|
|
|
stale_payload = {
|
|
"name": "taipei",
|
|
"display_name": "Taipei",
|
|
"temp_symbol": "°C",
|
|
"local_date": "2026-06-14",
|
|
"local_time": "18:00",
|
|
"current": {
|
|
"temp": 24.0,
|
|
"source_code": "metar",
|
|
"obs_time": "2026-06-14T10:00:00+00:00",
|
|
},
|
|
"airport_current": {
|
|
"temp": 24.0,
|
|
"source_code": "metar",
|
|
"obs_time": "2026-06-14T10:00:00+00:00",
|
|
},
|
|
"airport_primary": {
|
|
"temp": 24.0,
|
|
"source_code": "metar",
|
|
"obs_time": "2026-06-14T10:00:00+00:00",
|
|
},
|
|
"overview": {
|
|
"local_date": "2026-06-14",
|
|
"local_time": "18:00",
|
|
"current_temp": 24.0,
|
|
},
|
|
"metar_today_obs": [
|
|
{"time": "18:00", "temp": 24.0},
|
|
],
|
|
"timeseries": {
|
|
"metar_today_obs": [
|
|
{"time": "18:00", "temp": 24.0},
|
|
],
|
|
},
|
|
}
|
|
|
|
result = observation_overlay.overlay_latest_cwa_observation(
|
|
FakeWeather(),
|
|
"taipei",
|
|
stale_payload,
|
|
)
|
|
|
|
assert result["local_date"] == "2026-06-16"
|
|
assert result["local_time"] == "15:30"
|
|
assert result["current"]["temp"] == 29.4
|
|
assert result["current"]["source_code"] == "cwa"
|
|
assert result["airport_current"]["obs_time"] == "2026-06-16T15:30:00+08:00"
|
|
assert result["overview"]["local_date"] == "2026-06-16"
|
|
assert result["overview"]["current_temp"] == 29.4
|
|
assert result["metar_today_obs"] == [
|
|
{
|
|
"time": "15:30",
|
|
"temp": 29.4,
|
|
"obs_time": "2026-06-16T15:30:00+08:00",
|
|
"source_code": "cwa",
|
|
"source_label": "CWA",
|
|
}
|
|
]
|
|
assert result["timeseries"]["metar_today_obs"] == result["metar_today_obs"]
|
|
|
|
|
|
def test_overlay_latest_cwa_uses_official_intraday_history_when_fetcher_is_empty(monkeypatch):
|
|
from datetime import datetime, timedelta, timezone
|
|
|
|
expected_date = (datetime.now(timezone.utc) + timedelta(hours=8)).strftime("%Y-%m-%d")
|
|
|
|
class FakeRepo:
|
|
def load_points(self, *, source_code, station_code, target_date):
|
|
assert source_code == "cwa"
|
|
assert station_code == "466920"
|
|
return [
|
|
{"time": "14:30", "temp": 28.8},
|
|
{"time": "15:30", "temp": 29.4},
|
|
]
|
|
|
|
class FakeWeather:
|
|
def fetch_cwa_taipei_settlement_current(self):
|
|
return None
|
|
|
|
monkeypatch.setattr(
|
|
observation_overlay,
|
|
"OfficialIntradayObservationRepository",
|
|
lambda: FakeRepo(),
|
|
)
|
|
|
|
result = observation_overlay.overlay_latest_cwa_observation(
|
|
FakeWeather(),
|
|
"taipei",
|
|
{
|
|
"name": "taipei",
|
|
"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",
|
|
},
|
|
"metar_today_obs": [{"time": "18:00", "temp": 26.0}],
|
|
"timeseries": {"metar_today_obs": [{"time": "18:00", "temp": 26.0}]},
|
|
},
|
|
)
|
|
|
|
assert result["local_date"] == expected_date
|
|
assert result["local_time"] == "15:30"
|
|
assert result["airport_current"]["temp"] == 29.4
|
|
assert result["airport_current"]["source_code"] == "cwa"
|
|
assert result["airport_current"]["obs_time"] == f"{expected_date}T15:30:00+08:00"
|
|
|
|
|
|
def test_overlay_latest_cwa_uses_current_metar_when_cwa_source_is_stale(monkeypatch):
|
|
from datetime import datetime, timedelta, timezone
|
|
|
|
local_now = datetime.now(timezone.utc).astimezone(timezone(timedelta(hours=8)))
|
|
today = local_now.strftime("%Y-%m-%d")
|
|
stale_date = (local_now - timedelta(days=2)).strftime("%Y-%m-%d")
|
|
|
|
class EmptyRepo:
|
|
def load_points(self, *, source_code, station_code, target_date):
|
|
return []
|
|
|
|
class FakeWeather:
|
|
def fetch_cwa_taipei_settlement_current(self):
|
|
return {
|
|
"source": "cwa",
|
|
"source_label": "CWA",
|
|
"station_code": "466920",
|
|
"station_name": "臺北",
|
|
"observation_time": f"{stale_date}T10:00:00Z",
|
|
"current": {"temp": 26.0, "max_temp_so_far": 26.0},
|
|
}
|
|
|
|
def fetch_metar(self, city, *, use_fahrenheit=False, utc_offset=0):
|
|
assert city == "taipei"
|
|
assert utc_offset == 28800
|
|
assert use_fahrenheit is False
|
|
return {
|
|
"icao": "RCSS",
|
|
"station_name": "Taipei Songshan",
|
|
"observation_time": f"{today}T09:30:00Z",
|
|
"current_local_date": today,
|
|
"stale_for_today": False,
|
|
"current": {
|
|
"temp": 30.0,
|
|
"max_temp_so_far": 31.0,
|
|
"max_temp_time": "15:00",
|
|
"wind_speed_kt": 8,
|
|
"wind_dir": 70,
|
|
"humidity": 66,
|
|
"raw_metar": "RCSS 160930Z 07008KT 9999 FEW020 30/23 Q1008",
|
|
},
|
|
"today_obs": [("15:00", 31.0), ("17:30", 30.0)],
|
|
}
|
|
|
|
monkeypatch.setattr(
|
|
observation_overlay,
|
|
"OfficialIntradayObservationRepository",
|
|
lambda: EmptyRepo(),
|
|
)
|
|
|
|
result = observation_overlay.overlay_latest_cwa_observation(
|
|
FakeWeather(),
|
|
"taipei",
|
|
{
|
|
"name": "taipei",
|
|
"display_name": "Taipei",
|
|
"temp_symbol": "°C",
|
|
"local_date": stale_date,
|
|
"local_time": "18:00",
|
|
"current": {
|
|
"temp": 26.0,
|
|
"source_code": "cwa",
|
|
"obs_time": f"{stale_date}T10:00:00Z",
|
|
},
|
|
"airport_current": {
|
|
"temp": 26.0,
|
|
"source_code": "cwa",
|
|
"obs_time": f"{stale_date}T10:00:00Z",
|
|
},
|
|
"metar_today_obs": [{"time": "18:00", "temp": 26.0}],
|
|
"timeseries": {"metar_today_obs": [{"time": "18:00", "temp": 26.0}]},
|
|
"overview": {"local_date": stale_date, "local_time": "18:00", "current_temp": 26.0},
|
|
},
|
|
)
|
|
|
|
assert result["local_date"] == today
|
|
assert result["local_time"] == "17:30"
|
|
assert result["airport_current"]["temp"] == 30.0
|
|
assert result["airport_current"]["source_code"] == "metar"
|
|
assert result["airport_current"]["obs_time"] == f"{today}T09:30:00Z"
|
|
assert result["overview"]["current_temp"] == 30.0
|
|
assert result["overview"]["settlement_source"] == "metar"
|
|
assert result["metar_today_obs"] == [
|
|
{"time": "15:00", "temp": 31.0, "source_code": "metar", "source_label": "METAR"},
|
|
{
|
|
"time": "17:30",
|
|
"temp": 30.0,
|
|
"obs_time": f"{today}T09:30:00Z",
|
|
"source_code": "metar",
|
|
"source_label": "METAR",
|
|
},
|
|
]
|
|
assert result["timeseries"]["metar_today_obs"] == result["metar_today_obs"]
|