import type { Metadata } from 'next'; import Link from 'next/link'; import { ResolutionPair } from '@/components/admin/resolution-pair'; import { AdminFilters, AdminTitle, Mono, Notice } from '@/components/admin/ui'; import { DataTable, EmptyRow, Td, Th } from '@/components/ui/data-table'; import { Pagination, withParams } from '@/components/ui/pagination'; import { Note } from '@/components/ui/section'; import { EmptyState, Unavailable } from '@/components/ui/unavailable'; import { adminApi, load, requireAdmin } from '@/lib/admin/admin-api'; import { fmtDateTime, fmtInt, fmtPct, num } from '@/lib/format'; import { routes } from '@/lib/site'; export const metadata: Metadata = { title: 'Entity resolution', robots: { index: false, follow: false } }; export const dynamic = 'force-dynamic'; const LIMIT = 20; const TYPES = ['model', 'company', 'organization', 'lab', 'paper', 'benchmark', 'provider', 'hardware', 'framework', 'dataset']; export default async function EntityResolutionPage({ searchParams }: { searchParams: Promise> }) { await requireAdmin(); const sp = await searchParams; const current: Record = { type: sp.type ?? 'model', status: sp.status ?? 'pending' }; if (sp.threshold) current.threshold = sp.threshold; if (sp.offset) current.offset = sp.offset; const offset = Math.max(0, Number(current.offset) || 0); const href = (patch: Record) => withParams('/admin/entity-resolution', current, patch); const ret = href({}); const [res, decided] = await Promise.all([load(adminApi.entityResolution({ type: current.type, status: current.status, threshold: current.threshold, limit: LIMIT, offset })), current.status === 'decided' ? Promise.resolve(null) : load(adminApi.entityResolution({ type: current.type, status: 'decided', limit: 10 }))]); return ( <> Data quality → ({ value: t, label: t })) }, { kind: 'select', name: 'status', label: 'Status', value: current.status, any: 'pending', options: ['pending', 'decided', 'all'].map((s) => ({ value: s, label: s })) }, { kind: 'select', name: 'threshold', label: 'Similarity ≥', value: current.threshold, any: '0.8 (default)', options: ['0.6', '0.7', '0.8', '0.9', '0.95'].map((v) => ({ value: v, label: v })) }, ]} /> {!res.ok ? ( ) : res.data.items.length === 0 ? ( {current.status === 'pending' ? 'Nothing above the similarity threshold awaits a decision for this type.' : 'No decided pairs for this type yet.'} ) : ( <>

Threshold {fmtPct((num(res.data.threshold) ?? 0) * 100, 0)} · signals: review queue, trigram similarity, shared variant key. {res.data.note}

    {res.data.items.map((it) => (
  • ))}
href({ offset: o || undefined })} className="mt-4" /> )} {decided && decided.ok && (

Decision history {fmtInt(decided.data.total)}

Latest decided pairs for this type (persisted in resolution_decisions; every POST is also in the audit log). A B Decision Note Decided {decided.data.items.length === 0 && No decision recorded yet.} {decided.data.items.map((it) => { const d = (typeof it.decision === 'string' ? { decision: it.decision } : it.decision) as { decision: string; note?: string | null; decided_at?: string | null; applied?: boolean } | null; return ( {it.a.name} {' '} {it.a.slug} {it.b.name} {' '} {it.b.slug} {d?.decision ?? '—'} {d && 'applied' in d && d.applied !== undefined && {d.applied ? 'applied' : 'recorded'}} {d && typeof d.note === 'string' ? d.note : '—'} {d && typeof d.decided_at === 'string' ? fmtDateTime(d.decided_at) : '—'} ); })}

All decided pairs → {' '} · Audit log →

)} ); }