Track observation source health states
This commit is contained in:
@@ -162,6 +162,62 @@ def test_raw_observation_store_records_latest_observation(tmp_path):
|
||||
assert latest["payload"]["temp_c"] == 24.0
|
||||
|
||||
|
||||
def test_raw_observation_failure_preserves_last_success_and_increments_errors(tmp_path):
|
||||
from src.database.db_manager import DBManager
|
||||
|
||||
db = DBManager(str(tmp_path / "polyweather.db"))
|
||||
|
||||
db.append_raw_observation(
|
||||
source="amsc_awos",
|
||||
city="Qingdao",
|
||||
value=24.0,
|
||||
observed_at="2026-06-14T01:00:00+00:00",
|
||||
fetched_at="2026-06-14T01:01:00+00:00",
|
||||
station_code="ZSQD",
|
||||
station_name="Qingdao Jiaodong",
|
||||
status="ok",
|
||||
payload={"temp_c": 24.0},
|
||||
)
|
||||
db.append_raw_observation(
|
||||
source="amsc_awos",
|
||||
city="qingdao",
|
||||
fetched_at="2026-06-14T01:02:00+00:00",
|
||||
status="timeout",
|
||||
payload={"error": "upstream timeout"},
|
||||
)
|
||||
|
||||
latest = db.get_latest_raw_observation("amsc_awos", "qingdao")
|
||||
|
||||
assert latest is not None
|
||||
assert latest["status"] == "timeout"
|
||||
assert latest["value"] is None
|
||||
assert latest["error_count"] == 1
|
||||
assert latest["last_success_at"] == "2026-06-14T01:01:00+00:00"
|
||||
assert latest["payload"]["error"] == "upstream timeout"
|
||||
|
||||
|
||||
def test_raw_observation_store_computes_source_latency_when_times_are_known(tmp_path):
|
||||
from src.database.db_manager import DBManager
|
||||
|
||||
db = DBManager(str(tmp_path / "polyweather.db"))
|
||||
|
||||
db.append_raw_observation(
|
||||
source="amsc_awos",
|
||||
city="Qingdao",
|
||||
value=24.0,
|
||||
observed_at="2026-06-14T01:00:00+00:00",
|
||||
fetched_at="2026-06-14T01:01:30+00:00",
|
||||
station_code="ZSQD",
|
||||
status="ok",
|
||||
payload={"temp_c": 24.0},
|
||||
)
|
||||
|
||||
latest = db.get_latest_raw_observation("amsc_awos", "qingdao")
|
||||
|
||||
assert latest is not None
|
||||
assert latest["source_latency_sec"] == 90.0
|
||||
|
||||
|
||||
def test_observation_refresh_request_queue_claims_pending_requests(tmp_path):
|
||||
from src.database.db_manager import DBManager
|
||||
|
||||
@@ -254,6 +310,40 @@ def test_observation_collector_writes_raw_observation_store(tmp_path):
|
||||
assert latest["station_code"] == "ZSQD"
|
||||
|
||||
|
||||
def test_observation_collector_records_no_results_source_health(tmp_path):
|
||||
from src.database.db_manager import DBManager
|
||||
from web.observation_collector_service import (
|
||||
ObservationCollector,
|
||||
ObservationSourceProfile,
|
||||
)
|
||||
|
||||
db = DBManager(str(tmp_path / "polyweather.db"))
|
||||
|
||||
class FakeWeather:
|
||||
def _uses_fahrenheit(self, city):
|
||||
return False
|
||||
|
||||
def _attach_china_amsc_awos_data(self, results, city, use_fahrenheit):
|
||||
return None
|
||||
|
||||
collector = ObservationCollector(
|
||||
weather=FakeWeather(),
|
||||
profiles=[ObservationSourceProfile("amsc_awos", ("qingdao",), 180)],
|
||||
observation_store=db,
|
||||
async_cache_refresh=False,
|
||||
)
|
||||
|
||||
assert collector.run_due_once(now_ts=1000.0) == 0
|
||||
|
||||
latest = db.get_latest_raw_observation("amsc_awos", "qingdao")
|
||||
assert latest is not None
|
||||
assert latest["status"] == "no_results"
|
||||
assert latest["value"] is None
|
||||
assert latest["error_count"] == 1
|
||||
assert latest["last_success_at"] == ""
|
||||
assert latest["payload"]["status"] == "no_results"
|
||||
|
||||
|
||||
def test_observation_collector_writes_canonical_latest_from_source(tmp_path):
|
||||
from src.database.db_manager import DBManager
|
||||
from web.observation_collector_service import (
|
||||
@@ -294,6 +384,41 @@ def test_observation_collector_writes_canonical_latest_from_source(tmp_path):
|
||||
assert canonical["payload"]["observed_at"] == "2026-06-14T01:00:00+00:00"
|
||||
|
||||
|
||||
def test_observation_collector_canonical_uses_source_freshness_profile(tmp_path):
|
||||
from src.database.db_manager import DBManager
|
||||
from web.observation_collector_service import ObservationCollector
|
||||
|
||||
db = DBManager(str(tmp_path / "polyweather.db"))
|
||||
collector = ObservationCollector(
|
||||
weather=object(),
|
||||
profiles=[],
|
||||
observation_store=db,
|
||||
async_cache_refresh=False,
|
||||
)
|
||||
|
||||
collector._store_canonical_temperature_from_observation(
|
||||
city="qingdao",
|
||||
source="amsc_awos",
|
||||
row={
|
||||
"source": "amsc_awos",
|
||||
"source_label": "AMSC AWOS",
|
||||
"temp_c": 24.0,
|
||||
"observation_time": "2026-06-14T01:00:00+00:00",
|
||||
"icao": "ZSQD",
|
||||
},
|
||||
value=24.0,
|
||||
observed_at="2026-06-14T01:00:00+00:00",
|
||||
fetched_at="2026-06-14T01:05:00+00:00",
|
||||
)
|
||||
|
||||
canonical = db.get_canonical_temperature("qingdao")
|
||||
|
||||
assert canonical is not None
|
||||
assert canonical["payload"]["freshness_status"] == "expected_wait"
|
||||
assert canonical["payload"]["freshness_sec"] == 300
|
||||
assert canonical["payload"]["confidence"] < 0.92
|
||||
|
||||
|
||||
def test_observation_collector_consumes_refresh_request_queue(tmp_path):
|
||||
from src.database.db_manager import DBManager
|
||||
from web.observation_collector_service import (
|
||||
|
||||
Reference in New Issue
Block a user