spb/cancerindex
Public
TypeScript 97.2%
SQL 1.5%
CSS 0.6%
JavaScript 0.5%
1import { traceValue } from '@cancerindex/ranking';2import { Section } from '@/components/ui/section';3import { JsonView } from '@/components/ui/json-view';4import { EmptyState } from '@/components/ui/empty-state';5import { db } from '@/lib/db';6import { str, type SP } from '@/lib/search-params';78const TABLES = ['rankings', 'epidemiology_observations', 'survival_observations', 'cancer_gene_frequencies', 'literature_counts'];910/** Lineage trace tool (§252-253): value → inputs → observation → provenance → raw record. */11export default async function TracePage({ searchParams }: { searchParams: Promise<SP> }) {12 const sp = await searchParams;13 const table = str(sp, 'table');14 const id = str(sp, 'id');15 let result: Record<string, unknown> | null = null;16 let error: string | null = null;17 if (table && id) {18 if (!TABLES.includes(table)) error = 'unsupported table';19 else if (!/^\d{1,12}$/.test(id)) error = 'id must be a numeric row id';20 else {21 try {22 result = await traceValue(db(), table, id);23 } catch (e) {24 error = (e as Error).message;25 }26 }27 }28 return (29 <div className="space-y-6">30 <header className="pt-4">31 <h1 className="text-3xl">Trace</h1>32 <p className="mt-1 text-[13.5px] text-ink-2">Follow any derived or observed value back to its provenance row and raw source record.</p>33 </header>34 <form method="get" action="/admin/trace" className="flex flex-wrap items-end gap-2 text-[13.5px]">35 <label className="flex flex-col gap-1">36 <span className="ci-kicker">Table</span>37 <select name="table" defaultValue={table || 'rankings'} className="border border-rule-strong bg-white px-2 py-1.5">38 {TABLES.map((t) => (39 <option key={t} value={t}>40 {t}41 </option>42 ))}43 </select>44 </label>45 <label className="flex flex-col gap-1">46 <span className="ci-kicker">Row id</span>47 <input name="id" defaultValue={id} className="ci-mono w-40 border border-rule-strong bg-white px-2 py-1.5" placeholder="123" />48 </label>49 <button type="submit" className="border border-ink bg-ink px-3 py-1.5 text-paper hover:bg-ink-2">50 Trace51 </button>52 </form>53 {error ? <p className="text-[13px] text-danger">{error}</p> : null}54 {result ? (55 <Section id="result" kicker={`${table} / ${id}`} title="Lineage">56 {'error' in result ? <EmptyState compact title={String(result.error)} /> : <JsonView data={result} />}57 </Section>58 ) : !table ? (59 <EmptyState compact title="Enter a table and row id">Ranking rows link here from every "Why this rank?" panel.</EmptyState>60 ) : null}61 </div>62 );63}64