"use client"; import Link from "next/link"; import { useEffect, useState } from "react"; import { api, type Anomaly, type Dataset, type QualityOut } from "@/lib/api"; import { useAsync, useDebounced, useQueryParam } from "@/lib/hooks"; import { fmtInt, fmtTs } from "@/lib/format"; import { Card, Empty, ErrorState, Field, PageHeader, SeverityBadge, Skeleton, KV } from "@/components/ui"; import { DataTable } from "@/components/DataTable"; import { JsonView } from "@/components/JsonView"; import { CategoryBars } from "@/components/charts"; const DEFAULT_DS = "market_bars.stock.1m.raw"; export default function QualityPage() { const qDs = useQueryParam("dataset_id"); const [datasetId, setDatasetId] = useState(DEFAULT_DS); const [code, setCode] = useState(""); const [severity, setSeverity] = useState(""); const [symbol, setSymbol] = useState(""); const dsym = useDebounced(symbol.trim().toUpperCase(), 300); useEffect(() => { if (qDs) setDatasetId(qDs); }, [qDs]); const datasets = useAsync((s) => api.get<{ data: Dataset[] }>("/catalog/datasets", undefined, s), []); const q = useAsync((s) => api.get("/quality", { dataset_id: datasetId }, s), [datasetId]); const an = useAsync((s) => api.get<{ data: Anomaly[] }>("/quality/anomalies", { dataset_id: datasetId, code, severity, symbol: dsym, limit: 500 }, s), [datasetId, code, severity, dsym]); const latest = q.data?.latest; const byCode = (q.data?.anomalies_by_code_30d ?? []).map((r) => ({ name: r.code, value: Number(r.n) })); return ( <> } /> {q.error && }
{q.loading && !q.data ? ( ) : latest ? ( <> {String(latest.id ?? "—")}], ["Created", fmtTs(latest.created_at as string)], ["Rows checked", fmtInt(latest.rows_checked as number)], ["Anomalies", fmtInt(latest.anomalies as number)], ]} />
summary
) : ( No quality report yet for this dataset — enqueue a quality job from Ingestion. )}
{byCode.length ? : No anomalies in the last 30 days.}
    {Object.entries(q.data?.codes ?? {}).map(([c, d]) => (
  • {d}
  • ))}
r.id} mobile="scroll" maxHeight="18rem" columns={[ { key: "created_at", header: "Created", render: (r) => {fmtTs(r.created_at)} }, { key: "rows_checked", header: "Rows checked", num: true, render: (r) => fmtInt(r.rows_checked) }, { key: "anomalies", header: "Anomalies", num: true, render: (r) => fmtInt(r.anomalies) }, { key: "summary", header: "Summary", render: (r) => }, ]} empty="No reports." /> setSymbol(e.target.value)} aria-label="Symbol" /> } > {an.error && } String(r.id ?? i)} mobile="scroll" maxHeight="60dvh" columns={[ { key: "created_at", header: "Recorded", render: (r) => {fmtTs(r.created_at)} }, { key: "severity", header: "Severity", render: (r) => }, { key: "code", header: "Code", mono: true }, { key: "symbol", header: "Symbol", render: (r) => (r.symbol ? {r.symbol} : "—") }, { key: "session_date", header: "Session", hideSm: true, render: (r) => {r.session_date ?? "—"} }, { key: "detail", header: "Detail", render: (r) => }, ]} empty="No anomalies match." footer={{fmtInt(an.data?.data.length ?? 0)} rows (max 500)} /> ); }