import Link from 'next/link'; import { Section, KV, Note } from '@/components/ui/section'; import { Badge, ClaimBadge } from '@/components/ui/badge'; import { EmptyState } from '@/components/ui/empty-state'; import { Freshness } from '@/components/ui/freshness'; import { SourceBadge } from '@/components/ui/source-badge'; import { gapComponentForCancer, gapScopeKey } from '@/lib/queries/research-gap'; import { fmtInt, fmtPct, fmtValue, humanize } from '@/lib/format'; function ratioText(v: number | null): string { if (v == null) return 'undefined (no activity)'; const abs = Math.abs(v); const factor = 2 ** abs; const how = factor >= 10 ? `${fmtInt(factor)}×` : `${fmtValue(factor, 'ratio')}×`; if (abs < 0.15) return 'about proportional to its share of deaths'; return v > 0 ? `death share ${how} its activity share` : `activity share ${how} its death share`; } /** * Small server component for a cancer page: that cancer's research-gap components in the latest * burden scope of a geography (default USA, both sexes). Shows the inputs behind each ratio and links * to the full index and the two ratio rankings. Renders an EmptyState when the cancer is not a * top-level entity of the scope (components exist only for top-level cancers with a mortality * observation — CLAUDE.md §246-247). */ export async function CancerGapCard({ cancerId, geography = 'USA' }: { cancerId: string; geography?: string }) { const res = await gapComponentForCancer(cancerId, geography); return (
Full index → } > {!res ? ( Components are computed for top-level cancers with a mortality observation in a burden scope (United States only for now). Entities below the top level inherit no value: shares would double-count their parent. ) : ( (() => { const { scope, row, sums } = res; const key = gapScopeKey(scope); const src = ; return ( <>

{scope.geography} · {scope.year} · {scope.sex === 'all' ? 'both sexes' : humanize(scope.sex)} · all ages · {fmtInt(sums.eligible)} eligible top-level cancers {row.eligible ? ( eligible ) : ( ineligible )}

{fmtInt(row.deaths)} {src} {row.death_share != null ? · {fmtPct(row.death_share, 1)} of {fmtInt(sums.deaths)} in the eligible set : null} ), }, { k: 'Active trials', v: ( <> {fmtInt(row.active_trials)} clinicaltrials {row.trial_share != null ? · {fmtPct(row.trial_share, 1)} of {fmtInt(sums.activeTrials)} : null} · {fmtValue(row.trials_per_1000_deaths, 'per_1000_deaths')} per 1,000 deaths ), }, { k: 'Publications 5 y', v: ( <> {fmtInt(row.publications_5y)} pubmed {row.publication_share != null ? · {fmtPct(row.publication_share, 1)} of {fmtInt(sums.publications5y)} : null} · {fmtValue(row.publications_per_1000_deaths, 'per_1000_deaths')} per 1,000 deaths ), }, { k: 'Trial gap ratio', v: row.eligible ? ( <> = 1 ? 'text-danger' : ''}`}>{fmtValue(row.trial_gap_ratio, 'log2_ratio')} log₂ · {ratioText(row.trial_gap_ratio)} {row.trial_gap_rank != null ? ( <> {' '} ·{' '} rank #{row.trial_gap_rank} of {fmtInt(sums.eligible)} ) : null} ) : ( not computed — {row.ineligible_reason?.replace(/_/g, ' ')} ), }, { k: 'Research gap ratio', v: row.eligible ? ( <> = 1 ? 'text-danger' : ''}`}>{fmtValue(row.research_gap_ratio, 'log2_ratio')} log₂ · {ratioText(row.research_gap_ratio)} {row.research_gap_rank != null ? ( <> {' '} ·{' '} rank #{row.research_gap_rank} of {fmtInt(sums.eligible)} ) : null} ) : ( not computed — {row.ineligible_reason?.replace(/_/g, ' ')} ), }, { k: 'Formula', v: log2(death share ÷ activity share) · {row.formula_version} }, ]} /> A gap ratio compares registered activity with deaths inside one scope. It is a signal, not a judgement of research quality; trial counts include NCIt descendants, literature counts are query-based per entity. ); })() )}
); }