import Link from 'next/link'; import type { ReactNode } from 'react'; import { ClaimBadge, type ClaimKind } from '@/components/ui/badge'; import { SourceBadge } from '@/components/ui/source-badge'; import { Freshness } from '@/components/ui/freshness'; import { EmptyState } from '@/components/ui/empty-state'; import { latestFiguresFor, nearestRegistryAncestor, type LatestFigure } from '@/lib/queries/epidemiology'; import { rankingsForCancers, pickLatestScopes, bestRankFor } from '@/lib/queries/rankings'; import { loadProvenance, toInfo } from '@/lib/queries/provenance'; import { fmtInt, fmtValue, unitLabel, scopeLabel, toDate } from '@/lib/format'; import type { CancerBundle } from './load'; /** * "Key figures" strip for the cancer overview (SPEC §43, §309). Every tile shows value + unit, the period, * a source badge and — when a current ranking snapshot covers the entity — "rank #n of m (scope)". * Registry figures come from the entity's own observations or, for entities below the top level, from the * nearest top-level ancestor with an explicit note. Nothing is estimated or extrapolated here. */ interface Tile { key: string; label: string; value: ReactNode; unit?: string | null; period: string; source: ReactNode; claim: ClaimKind; rank?: { rank: number; eligible: number; scope: string; href: string } | null; note?: string | null; href?: string; } const RANK_METRIC_FOR: Record = { mortality_count: 'mortality_count', incidence_count: 'incidence_count', as_mortality_rate: 'as_mortality_rate' }; export async function KeyFigures({ b }: { b: CancerBundle }) { const { cancer: c, counters } = b; const registry = c.top_level ? { id: c.id, slug: c.slug, canonical_name: c.canonical_name, depth: 0 } : await nearestRegistryAncestor(c.id); const rankIds = registry && registry.id !== c.id ? [c.id, registry.id] : [c.id]; const [figures, ranksAll] = await Promise.all([registry ? latestFiguresFor([registry.id], 'USA', 'all') : Promise.resolve([] as LatestFigure[]), rankingsForCancers(rankIds)]); const ranks = pickLatestScopes(ranksAll); const prov = await loadProvenance(figures.map((f) => f.provenance_id)); const byMetric = new Map(figures.map((f) => [f.metric, f])); const level: 'top' | 'all' = c.top_level ? 'top' : 'all'; const rankOf = (metric: string, forId: string, geo?: string) => { const r = bestRankFor( ranks.filter((x) => x.cancer_id === forId), metric, { geo, level: forId === c.id ? level : 'top' }, ); return r ? { rank: r.rank, eligible: r.eligible_entities, scope: scopeLabel(r.scope_key), href: `/rankings/${r.metric_slug}?scope=${encodeURIComponent(r.scope_key)}` } : null; }; const tiles: Tile[] = []; const epiNote = registry && registry.depth > 0 ? `figures shown for ${registry.canonical_name} (registry level)` : null; const epi: Array<[string, string]> = [ ['mortality_count', 'US deaths'], ['incidence_count', 'US new cases'], ['as_mortality_rate', 'US age-standardized mortality'], ]; for (const [metric, label] of epi) { const f = byMetric.get(metric); if (!f) continue; const p = toInfo(prov.get(f.provenance_id), 'normalized') ?? { sourceSlug: f.source_slug, sourceName: f.source_name }; tiles.push({ key: metric, label, value: fmtValue(f.value, f.unit), unit: f.unit, period: `${f.geography_name} · ${f.year_end && f.year_end !== f.year ? `${f.year}–${f.year_end}` : f.year} · ${f.sex === 'all' ? 'both sexes' : f.sex} · all ages${f.standard_population ? ` · ${f.standard_population.replace(/\s*\(.*\)$/, '')}` : ''}${f.estimate_type !== 'observed' ? ` · ${f.estimate_type}` : ''}`, source: , claim: 'observed', rank: registry ? rankOf(RANK_METRIC_FOR[metric] ?? metric, registry.id, f.iso3 ?? undefined) : null, note: epiNote, href: registry && registry.depth > 0 ? `/cancer/${registry.slug}/statistics` : `/cancer/${c.slug}/statistics`, }); } const registryTilesMissing = tiles.length === 0; if (counters) { const src = (slug: string, name: string) => ; tiles.push( { key: 'active_trials', label: 'Active trials', value: fmtInt(counters.active_trial_count), unit: 'count', period: 'current registry status · entity + descendants', source: src('clinicaltrials', 'ClinicalTrials.gov'), claim: 'computed', rank: rankOf('active_trials', c.id), href: `/cancer/${c.slug}/trials` }, { key: 'recruiting_trials', label: 'Recruiting trials', value: fmtInt(counters.recruiting_trial_count), unit: 'count', period: 'status RECRUITING · entity + descendants', source: src('clinicaltrials', 'ClinicalTrials.gov'), claim: 'computed', rank: rankOf('recruiting_trials', c.id), href: `/cancer/${c.slug}/trials?status=RECRUITING` }, { key: 'evidence', label: 'Curated evidence items', value: fmtInt(counters.evidence_count), unit: 'count', period: 'accepted CIViC items · entity + descendants', source: src('civic', 'CIViC'), claim: 'computed', rank: rankOf('curated_evidence_items', c.id), href: `/cancer/${c.slug}/evidence` }, { key: 'pubs12m', label: 'Publications, last 12 months', value: fmtInt(counters.publication_count_12m), unit: 'count', period: 'PubMed records · stored query · 12-month window', source: src('pubmed', 'PubMed'), claim: 'computed', rank: rankOf('publications_12m', c.id), href: `/cancer/${c.slug}/research` }, { key: 'cohorts', label: 'Genomic cohorts', value: fmtInt(counters.cohort_count), unit: 'count', period: 'open GDC projects · entity + descendants', source: src('gdc', 'NCI GDC'), claim: 'computed', rank: rankOf('genomic_cohorts', c.id), href: `/cancer/${c.slug}/genomics` }, ); } const freshest = [...figures.map((f) => toDate(f.updated_at)), counters ? toDate(counters.updated_at) : null].filter((d): d is Date => !!d).sort((a, b) => b.getTime() - a.getTime())[0] ?? null; return (

Key figures

At a glance

Each figure carries its unit, period and source; ranks link to "Why this rank?".

{tiles.length === 0 ? ( No registry observation, trial, evidence, literature or cohort counter is attached to this entity. Counters are refreshed after each connector run. ) : ( <> {registryTilesMissing ? (

{registry ? `No US registry observation for ${registry.depth > 0 ? `${registry.canonical_name} (nearest registry-level ancestor)` : 'this top-level site'} yet.` : 'No registry-level ancestor: burden figures are published for the mutually exclusive top-level site groups only, and this entity does not descend from one.'}{' '} Global figures (IARC / GLOBOCAN) stay under license review and SEER awaits credentials.

) : null}
    {tiles.map((t) => (
  • {t.label} {t.value} {t.unit ? {unitLabel(t.unit)} : null} {t.period} {t.source} {t.rank ? ( rank #{t.rank.rank} of {fmtInt(t.rank.eligible)} ({t.rank.scope}) ) : ( not in a current ranking snapshot )} {t.note ? ( {t.note} {registry && registry.depth > 0 ? ( <> {' '} —{' '} open {registry.canonical_name} ) : null} ) : null}
  • ))}
)}
); } export const COMPLETENESS_DIMENSIONS: Array<{ key: string; label: string; hint: string }> = [ { key: 'epidemiology', label: 'Epidemiology', hint: 'At least one incidence/mortality observation attached to this entity' }, { key: 'survival', label: 'Survival', hint: 'At least one survival observation' }, { key: 'trials', label: 'Trials', hint: 'At least one ClinicalTrials.gov study mapped to the entity or a descendant' }, { key: 'literature', label: 'Literature', hint: 'At least one PubMed record linked' }, { key: 'genomics', label: 'Genomics', hint: 'At least one open genomic cohort (GDC project)' }, { key: 'evidence', label: 'Evidence', hint: 'At least one accepted CIViC evidence item' }, { key: 'therapies', label: 'Therapies', hint: 'At least one drug with a regulatory approval for the entity or a descendant' }, ]; /** Data completeness row: the 7 dimensions stored in entity_counters.completeness as filled/empty squares with labels. */ export function CompletenessRow({ completeness, computedAt }: { completeness: Record | null | undefined; computedAt?: Date | string | null }) { const has = completeness && Object.keys(completeness).length > 0; const filled = COMPLETENESS_DIMENSIONS.filter((d) => has && Number(completeness![d.key] ?? 0) > 0).length; return (

Data completeness {filled} / {COMPLETENESS_DIMENSIONS.length} dimensions{has ? '' : ' · counters not computed yet'}

    {COMPLETENESS_DIMENSIONS.map((d) => { const on = has && Number(completeness![d.key] ?? 0) > 0; return (
  • {d.label} {on ? ' (data present)' : ' (no data yet)'}
  • ); })}
{computedAt ?

Computed from entity_counters.completeness · refreshed after each connector run

: null}
); }