export const DASH = '—'; export function fmtInt(n: number | null | undefined): string { return n == null ? DASH : n.toLocaleString('en-US'); } /** 113581394 → "113.6M", 24673 → "24.7K" */ export function fmtCompact(n: number | null | undefined, digits = 1): string { if (n == null) return DASH; const abs = Math.abs(n); const trim = (v: number) => v.toFixed(digits).replace(/\.0+$/, ''); if (abs >= 1e9) return `${trim(n / 1e9)}B`; if (abs >= 1e6) return `${trim(n / 1e6)}M`; if (abs >= 1e4) return `${trim(n / 1e3)}K`; return n.toLocaleString('en-US'); } export function fmtNum(n: number | null | undefined, digits = 2): string { return n == null || !Number.isFinite(n) ? DASH : n.toFixed(digits); } export function fmtSigned(n: number | null | undefined, digits = 1): string { if (n == null || !Number.isFinite(n)) return DASH; const s = Math.abs(n) >= 1000 ? n.toFixed(0) : n.toFixed(digits); return n > 0 ? `+${s}` : s; } export function fmtPct(n: number | null | undefined, digits = 0): string { return n == null ? DASH : `${(n * 100).toFixed(digits)}%`; } export function fmtShare(part: number, total: number, digits = 1): string { if (!total) return DASH; return `${((part / total) * 100).toFixed(digits)}%`; } export function chainLabel(pdb: string, chain: string): string { return `${pdb.toUpperCase()}.${chain}`; } export function chainPath(pdb: string, chain: string): string { return `/chain/${pdb.toLowerCase()}/${encodeURIComponent(chain)}`; } export function sequencePath(id: string): string { return `/sequence/${id}`; } const METHOD_SHORT: Record = { 'X-RAY DIFFRACTION': 'X-ray', 'ELECTRON MICROSCOPY': 'Cryo-EM', 'SOLUTION NMR': 'NMR', 'SOLID-STATE NMR': 'ssNMR', 'ELECTRON CRYSTALLOGRAPHY': 'MicroED', 'NEUTRON DIFFRACTION': 'Neutron', 'SOLUTION SCATTERING': 'SAXS', 'POWDER DIFFRACTION': 'Powder', 'FIBER DIFFRACTION': 'Fiber', 'INFRARED SPECTROSCOPY': 'IR', }; export function methodShort(m: string | null | undefined): string { if (!m) return DASH; return METHOD_SHORT[m] ?? titleCase(m); } export function titleCase(s: string): string { return s.toLowerCase().replace(/\b\w/g, (c) => c.toUpperCase()); } export function capitalize(s: string | null | undefined): string { if (!s) return DASH; return s.charAt(0).toUpperCase() + s.slice(1); } export function year(date: string | null | undefined): string { return date ? date.slice(0, 4) : DASH; } /** Remove trailing evidence codes like "{ECO:0000256|ARBA...}" from function text. */ export function cleanFunction(text: string): string { return text.replace(/\s*\{ECO:[^}]*\}\.?/g, '').replace(/\s+/g, ' ').trim(); }