1from __future__ import annotations23from fastapi import APIRouter, Depends, Query4from findata_core.db import connection56from findata_api import auth as A78router = APIRouter(prefix="/quality", tags=["quality"])91011@router.get("")12def quality(dataset_id: str = "market_bars.stock.1m.raw", p: A.Principal = Depends(A.require("read:market"))):13 with connection() as conn:14 report = conn.execute("SELECT * FROM quality_reports WHERE dataset_id=%s ORDER BY created_at DESC LIMIT 1", (dataset_id,)).fetchone()15 by_code = conn.execute("SELECT code, severity, count(*) n, sum((detail->>'rows')::int) rows FROM quality_anomalies WHERE dataset_id=%s AND created_at > now() - interval '30 days' GROUP BY 1,2 ORDER BY n DESC", (dataset_id,)).fetchall()16 reports = conn.execute("SELECT id, created_at, rows_checked, anomalies, summary FROM quality_reports WHERE dataset_id=%s ORDER BY created_at DESC LIMIT 30", (dataset_id,)).fetchall()17 return {"latest": report, "anomalies_by_code_30d": by_code, "reports": reports,18 "codes": {"ohlc_inconsistent": "high < max(open,close) or low > min(open,close)", "nonpositive_price": "price <= 0",19 "negative_volume": "volume < 0", "zero_volume": "volume == 0 (vendor normally omits these)", "fractional_volume": "non-integral volume",20 "off_interval": "timestamp not on the interval grid", "duplicate": "duplicate (symbol, timestamp)",21 "outside_session": "outside 04:00–20:00 ET", "non_trading_day": "bars on a non-session date", "price_jump": ">25 % move between consecutive bars"}}222324@router.get("/anomalies")25def anomalies(dataset_id: str = "market_bars.stock.1m.raw", code: str | None = None, severity: str | None = None, symbol: str | None = None,26 limit: int = Query(200, le=5000), p: A.Principal = Depends(A.require("read:market"))):27 with connection() as conn:28 rows = conn.execute("SELECT id, symbol, session_date, code, severity, detail, created_at FROM quality_anomalies WHERE dataset_id=%s "29 "AND (%s::text IS NULL OR code=%s) AND (%s::text IS NULL OR severity=%s) AND (%s::text IS NULL OR symbol=%s) ORDER BY created_at DESC, id DESC LIMIT %s",30 (dataset_id, code, code, severity, severity, symbol, symbol, limit)).fetchall()31 return {"data": rows}32