"use client"; import Link from "next/link"; import { useEffect, useMemo, useState } from "react"; import { api, type Dataset, type Heatmap, type SessionRow } from "@/lib/api"; import { useAsync, useDebounced, useQueryParam } from "@/lib/hooks"; import { daysAgo, fmtCompact, fmtInt, fmtPct, fmtTime, today } from "@/lib/format"; import { Card, ErrorState, Field, PageHeader, Skeleton } from "@/components/ui"; import { DataTable } from "@/components/DataTable"; import { DateRange } from "@/components/DateRange"; import { CompletenessHeatmap, VolumeBarChart } from "@/components/charts"; const DEFAULT_DS = "market_bars.stock.1m.raw"; export default function CoveragePage() { const qDs = useQueryParam("dataset_id"); const qSym = useQueryParam("symbol"); const [datasetId, setDatasetId] = useState(DEFAULT_DS); const [start, setStart] = useState(daysAgo(120)); const [end, setEnd] = useState(today()); const [symbol, setSymbol] = useState(""); const dsym = useDebounced(symbol.trim().toUpperCase(), 400); useEffect(() => { if (qDs) setDatasetId(qDs); }, [qDs]); useEffect(() => { if (qSym) setSymbol(qSym.toUpperCase()); }, [qSym]); const datasets = useAsync((s) => api.get<{ data: Dataset[] }>("/catalog/datasets", undefined, s), []); const heat = useAsync((s) => api.get("/coverage/heatmap", { dataset_id: datasetId, start, end }, s), [datasetId, start, end]); const interval = datasetId.includes(".5m.") ? "5m" : "1m"; const sessions = useAsync((s) => api.get<{ data: SessionRow[]; meta: { dataset_id: string; symbol: string } }>("/market/sessions", { symbol: dsym, interval, start, end }, s), [dsym, interval, start, end], { enabled: dsym.length > 0 }); const barData = useMemo(() => (heat.data?.sessions ?? []).map((s) => ({ label: s.session_date, t: s.session_date, symbols: s.symbols, bars: s.bars })), [heat.data]); const totals = useMemo(() => { const ss = heat.data?.sessions ?? []; const bars = ss.reduce((a, s) => a + Number(s.bars ?? 0), 0); const comp = ss.filter((s) => s.completeness !== null); const avg = comp.length ? comp.reduce((a, s) => a + Number(s.completeness), 0) / comp.length : null; return { sessions: ss.length, bars, avg, maxSymbols: Math.max(0, ...ss.map((s) => s.symbols)) }; }, [heat.data]); const candidates = (datasets.data?.data ?? []).filter((d) => d.kind === "market_bars" || d.kind === "derived"); return ( <>
{ setStart(s); setEnd(e); }} />
{heat.error && }
Sessions in range
{fmtInt(totals.sessions)}
Bars in range
{fmtCompact(totals.bars)}
Mean completeness
{fmtPct(totals.avg)}
Max symbols / session
{fmtInt(totals.maxSymbols)}
{heat.loading && !heat.data ? : heat.data ? : null} {heat.data && barData.length > 0 ? :
No sessions in range.
}
{heat.data && barData.length > 0 ? :
No sessions in range.
}
r.symbol} mobile="scroll" maxHeight="32rem" onRowClick={(r) => setSymbol(r.symbol)} columns={[ { key: "symbol", header: "Symbol", render: (r) => e.stopPropagation()}>{r.symbol} }, { key: "sessions", header: "Sessions", num: true, render: (r) => fmtInt(r.sessions) }, { key: "bars", header: "Bars", num: true, render: (r) => fmtCompact(r.bars) }, { key: "completeness", header: "Compl.", num: true, render: (r) => fmtPct(r.completeness) }, ]} empty="No coverage rows in this range." />
click a top symbol or type one}> setSymbol(e.target.value)} /> {sessions.error && } {dsym ? ( r.session_date} mobile="scroll" maxHeight="28rem" columns={[ { key: "session_date", header: "Session", render: (r) => {r.session_date} }, { key: "bar_count", header: "Bars", num: true, render: (r) => fmtInt(r.bar_count) }, { key: "expected_bars", header: "Expected", num: true, render: (r) => fmtInt(r.expected_bars) }, { key: "completeness", header: "Compl.", num: true, render: (r) => (r.expected_bars ? fmtPct(Math.min(1, r.bar_count / r.expected_bars)) : "—") }, { key: "first_ts", header: "First (UTC)", render: (r) => {fmtTime(r.first_ts)} }, { key: "last_ts", header: "Last (UTC)", render: (r) => {fmtTime(r.last_ts)} }, { key: "flags", header: "Flags", render: (r) => (r.flags?.length ? r.flags.map((f) => {f}) : —) }, ]} empty={`No sessions for ${dsym} in this range.`} footer={{fmtInt(sessions.data?.data.length ?? 0)} sessions · dataset {sessions.data?.meta.dataset_id}} /> ) : (
Enter a symbol to list its sessions.
)}
); }