import Link from 'next/link'; import { Badge, ClaimBadge, MatchBadge, isExactMatch } from '@/components/ui/badge'; import { SourceBadge, TableProvenance } from '@/components/ui/source-badge'; import { Freshness } from '@/components/ui/freshness'; import type { FreqRow } from '@/lib/queries/genomics'; import type { ProvRow } from '@/lib/queries/provenance'; import { toInfo } from '@/lib/queries/provenance'; import { fmtInt, fmtPct, humanize } from '@/lib/format'; import { Pager } from '@/components/ui/pager'; import { pageInfo } from '@/lib/pagination'; /** * Gene alteration frequencies with mandatory denominators (§260-261), one table per cohort. * Every row of a cohort table shares one provenance (one cohort, one release), so the popover is * rendered once per cohort in the caption; rows carry the compact badge. */ export function FrequencyTables({ rows, prov, cohortFilter, showCancer = false }: { rows: FreqRow[]; prov: Map; cohortFilter?: string | null; showCancer?: boolean }) { const cohorts = new Map(); for (const r of rows) { if (cohortFilter && r.cohort_id !== cohortFilter) continue; if (!cohorts.has(r.cohort_id)) cohorts.set(r.cohort_id, []); cohorts.get(r.cohort_id)!.push(r); } return (
{[...cohorts.entries()].map(([cohortId, rs]) => { const c = rs[0]!; const info = toInfo(prov.get(c.provenance_id)) ?? { sourceSlug: c.source_slug, sourceName: c.source_name, datasetVersion: c.data_release }; return (

{c.cohort_name} {c.study_id} {c.program ? {c.program} : null}

{c.cases_with_ssm != null ? ( <> {fmtInt(c.cases_with_ssm)} cases with simple somatic mutation data ) : null} {c.case_count != null ? ( <> {' '} · {fmtInt(c.case_count)} cases total ) : null} {c.data_release ? · {c.data_release} : null} {showCancer && c.cancer_slug ? ( <> {' '} · mapped to{' '} {c.cancer_name} {' '} ) : null}

}> {rs.length} gene{rs.length === 1 ? '' : 's'} · frequency = affected / profiled, as published by the cohort.
{rs.map((r, i) => ( ))}
# Gene Alteration Affected (n) Profiled (n) Frequency (%) Source
{r.rank ?? i + 1} {r.gene_symbol} {humanize(r.alteration_type)} {fmtInt(r.cases_affected)} {fmtInt(r.cases_profiled)} {fmtPct(r.frequency, 1)}
); })}
); } /** * Gene-centric view: one compact row per cohort (used on /gene/[symbol]); denominators stay mandatory. * Rows come from different cohorts (different releases), so each compact badge carries a `title` * with dataset · version · retrieved; the caption shows one popover for the source. */ export function GeneFrequencyTable({ rows, prov, page = 1, pageSize = 50, hrefFor }: { rows: FreqRow[]; prov: Map; page?: number; pageSize?: number; hrefFor?: (page: number) => string }) { const all = [...rows].sort((a, b) => b.frequency - a.frequency); const info = pageInfo(page, pageSize, all.length); const sorted = all.slice(info.offset, info.offset + pageSize); const first = sorted[0]; const caption = first ? (toInfo(prov.get(first.provenance_id)) ?? { sourceSlug: first.source_slug, sourceName: first.source_name }) : null; return (
{caption ? ( }> {all.length} cohort row{all.length === 1 ? '' : 's'}{all.length > sorted.length ? ` (showing ${sorted.length}, sorted by frequency)` : ''} · frequency = affected / profiled, as published by each cohort; cohorts are not pooled. Hover a row badge for its release. ) : null}
{sorted.map((r) => ( ))}
Cohort Mapped cancer Alteration Affected (n) Profiled (n) Frequency (%) Rank in cohort Source
{r.cohort_name} {r.study_id} {r.cancer_slug ? ( <> {r.cancer_name} {!isExactMatch(r.cancer_match_type) ? : null} ) : ( unmapped )} {humanize(r.alteration_type)} {fmtInt(r.cases_affected)} {fmtInt(r.cases_profiled)} {fmtPct(r.frequency, 1)} {r.rank ?? '—'}
{hrefFor ? : null}
); }