spb/cancerindex
Public
TypeScript 97.2%
SQL 1.5%
CSS 0.6%
JavaScript 0.5%
1/**2 * String normalization used by reconciliation (CLAUDE.md §69). Deterministic, no LLM.3 */45const GREEK: Record<string, string> = { α: 'alpha', β: 'beta', γ: 'gamma', δ: 'delta', κ: 'kappa', λ: 'lambda', μ: 'mu' };67export function stripDiacritics(s: string): string {8 return s.normalize('NFKD').replace(/[̀-ͯ]/g, '');9}1011/** Lowercase, ASCII, punctuation → space, collapse whitespace. Keeps digits (G12C, HER2). */12export function normalizeLabel(input: string): string {13 let s = stripDiacritics(input).toLowerCase();14 s = s.replace(/[αβγδκλμ]/g, (c) => GREEK[c] ?? c);15 s = s.replace(/['’`"]/g, '');16 s = s.replace(/[^a-z0-9]+/g, ' ');17 s = s.replace(/\b(nos|unspecified|not otherwise specified)\b/g, ' ');18 s = s.replace(/\bcarcinomas\b/g, 'carcinoma').replace(/\bcancers\b/g, 'cancer').replace(/\btumou?rs\b/g, 'tumor').replace(/\btumour\b/g, 'tumor').replace(/\bneoplasms\b/g, 'neoplasm').replace(/\bsarcomas\b/g, 'sarcoma').replace(/\blymphomas\b/g, 'lymphoma').replace(/\bleukaemia\b/g, 'leukemia').replace(/\bleukemias\b/g, 'leukemia');19 return s.replace(/\s+/g, ' ').trim();20}2122/** "Carcinoma, Non-Small-Cell Lung" (MeSH inverted form) → "Non-Small-Cell Lung Carcinoma". */23export function uninvertMeshTerm(term: string): string {24 const parts = term.split(',').map((p) => p.trim());25 if (parts.length < 2) return term;26 return [...parts.slice(1), parts[0]].join(' ');27}2829/** URL slug for entity routes. */30export function slugify(input: string): string {31 return stripDiacritics(input)32 .toLowerCase()33 .replace(/[^a-z0-9]+/g, '-')34 .replace(/^-+|-+$/g, '')35 .slice(0, 120);36}3738export function sha256Hex(data: string | Uint8Array): Promise<string> {39 return import('node:crypto').then(({ createHash }) => createHash('sha256').update(data).digest('hex'));40}41