reefscan-api / backend /observability.py
HrishiKabra's picture
Upload folder using huggingface_hub
f8dd35f verified
Raw
History Blame
2.84 kB
"""Observability aggregations over inference_logs. Phase 7.
Pure functions (testable) that turn raw inference_logs rows into the three dashboard views
computed entirely from Supabase data — no external observability tool:
- rolling mean prediction_set_size per day -> drift proxy (rising = distribution shift)
- latency p50 / p95 per day
- class distribution: current 7-day window vs the prior 7-day baseline
"""
from __future__ import annotations
from collections import defaultdict
from datetime import date, timedelta
def _day(ts) -> str:
return str(ts)[:10]
def _percentile(values: list[float], p: float) -> float:
if not values:
return 0.0
v = sorted(values)
k = (len(v) - 1) * p / 100.0
f = int(k)
if f + 1 < len(v):
return round(v[f] + (v[f + 1] - v[f]) * (k - f), 1)
return round(v[f], 1)
def rolling_set_size(logs: list[dict]) -> list[dict]:
by: dict[str, list[float]] = defaultdict(list)
for r in logs:
by[_day(r["ts"])].append(float(r.get("prediction_set_size", 1)))
return [{"date": d, "avg_set_size": round(sum(v) / len(v), 3), "n": len(v)}
for d, v in sorted(by.items())]
def latency_percentiles(logs: list[dict]) -> list[dict]:
by: dict[str, list[float]] = defaultdict(list)
for r in logs:
by[_day(r["ts"])].append(float(r.get("latency_ms", 0)))
return [{"date": d, "p50": _percentile(v, 50), "p95": _percentile(v, 95), "n": len(v)}
for d, v in sorted(by.items())]
def class_distribution(logs: list[dict], classes: tuple[str, ...]) -> dict:
days = sorted({_day(r["ts"]) for r in logs})
if not days:
return {"current": {}, "baseline": {}, "current_window": None, "baseline_window": None}
anchor = date.fromisoformat(days[-1])
cur_lo = anchor - timedelta(days=6)
base_hi = cur_lo - timedelta(days=1)
base_lo = base_hi - timedelta(days=6)
def frac(lo: date, hi: date) -> dict:
counts = {c: 0 for c in classes}
for r in logs:
d = date.fromisoformat(_day(r["ts"]))
if lo <= d <= hi:
lab = r.get("predicted_label")
if lab in counts:
counts[lab] += 1
total = sum(counts.values()) or 1
return {c: round(counts[c] / total * 100, 1) for c in classes}
return {
"current": frac(cur_lo, anchor),
"baseline": frac(base_lo, base_hi),
"current_window": [cur_lo.isoformat(), anchor.isoformat()],
"baseline_window": [base_lo.isoformat(), base_hi.isoformat()],
}
def build(logs: list[dict], classes: tuple[str, ...]) -> dict:
return {
"drift": rolling_set_size(logs),
"latency": latency_percentiles(logs),
"class_distribution": class_distribution(logs, classes),
"total_logs": len(logs),
}