feat: precompute route health in background sweep

Persist route health derivations so the API layer no longer recomputes
them on every request. Two new tables (route_result_history,
route_recent_matches) plus a quality_avg column on route_results back
the dashboard strip and per-route history endpoints.

Collector:
- run_evaluation (60s) writes snapshot + quality_avg + recent_matches
- run_history_backfill (hourly) recomputes completed-day buckets
- subscriber wires a dedicated backfill scheduler thread

API:
- dashboard routes-overview bulk-loads via single history SELECT
- routes detail/detail history read from precomputed tables with
  live-compute fallback
- POST/PUT on routes upserts recent_matches and quality_avg inline
- dashboard cache TTL lowered from 1h to 5m (invalidation-aware)

Config: route_history_backfill_interval_seconds=3600,
        redis_cache_ttl_dashboard default 3600 -> 300
This commit is contained in:
Louis King
2026-07-19 19:56:24 +01:00
parent 82a6d032d0
commit 938028a7a7
17 changed files with 1605 additions and 111 deletions
+24 -8
View File
@@ -965,22 +965,38 @@ class TestCacheControlMiddleware:
assert response.status_code == 200
assert response.headers["cache-control"] == "no-store"
def test_kill_switch_suppresses_cache_control(self, client_no_auth):
def test_kill_switch_suppresses_cache_control(self, client_no_auth, monkeypatch):
"""When api_cache_control_enabled is False, no Cache-Control is added."""
client_no_auth.app.state.api_cache_control_enabled = False
if hasattr(client_no_auth.app.state, "redis_cache"):
del client_no_auth.app.state.redis_cache
monkeypatch.setattr(
client_no_auth.app.state, "api_cache_control_enabled", False
)
# Remove any pre-existing cache backend for the duration of the test;
# ``monkeypatch.delattr`` with raising=False is a no-op when the
# attribute is absent.
monkeypatch.delattr(client_no_auth.app.state, "redis_cache", raising=False)
response = client_no_auth.get("/api/v1/nodes")
assert "cache-control" not in response.headers
def test_kill_switch_preserves_x_cache_header(self, client_no_auth):
def test_kill_switch_preserves_x_cache_header(self, client_no_auth, monkeypatch):
"""X-Cache is observability, not a client-caching directive, so the
kill switch should not suppress it."""
mock_cache = MagicMock()
mock_cache.get.return_value = None
client_no_auth.app.state.redis_cache = mock_cache
client_no_auth.app.state.redis_cache_ttl = 30
client_no_auth.app.state.api_cache_control_enabled = False
# Force-set the cache + ttl + kill switch on app.state. Using
# ``setattr(..., raising=False)`` lets us create the attribute
# even when a prior test removed it, and ``monkeypatch`` restores
# the original (or removes what it added) on teardown — fixing
# the cross-test leak that previously left ``api_cache_control_enabled``
# flipped off for every subsequent test in this module.
monkeypatch.setattr(
client_no_auth.app.state, "redis_cache", mock_cache, raising=False
)
monkeypatch.setattr(
client_no_auth.app.state, "redis_cache_ttl", 30, raising=False
)
monkeypatch.setattr(
client_no_auth.app.state, "api_cache_control_enabled", False
)
response = client_no_auth.get("/api/v1/nodes")
assert response.headers.get("x-cache") == "MISS"
assert "cache-control" not in response.headers