spb/cancerindex
Public
TypeScript 97.2%
SQL 1.5%
CSS 0.6%
JavaScript 0.5%
1/**2 * NCIt neoplasm concepts that are *qualified disease states* rather than taxonomy nodes3 * (CLAUDE.md §217-218: "Do not create a unique canonical cancer entity for every arbitrary4 * combination"). Stage / recurrence / resectability / laterality / treatment-history qualified5 * concepts are retained as source records and mapped to their base disease, but are not canonical6 * cancers.7 *8 * Rules are deterministic and tested (test/qualifiers.test.ts). When in doubt a concept is KEPT9 * (a false negative costs an extra node; a false positive loses a real disease entity).10 */1112/** Words that mark a clinical state, never a disease identity. */13const STATE_WORDS =14 /\b(stage\s?[0iv]+[abcs]?\d?|stages?\s[0iv]+|recurrent|refractory|relapsed|resectable|unresectable|borderline resectable|localized|locally advanced|advanced|metastatic|progressive|residual|persistent|newly diagnosed|untreated|previously treated|previously untreated|treatment[- ]naive|bilateral|unilateral|synchronous|metachronous|multifocal|multicentric|unifocal|occult|extensive[- ]stage|limited[- ]stage|in remission|in relapse|in complete remission|minimal residual disease|post[- ]?transplant(ation)?|aggravated|complicated|uncomplicated|with (unknown|known) primary site|of unknown primary origin)\b/i;1516/** Staging-system suffixes: "… AJCC v8", "… FIGO Stage IIIC". */17const STAGING_SUFFIX = /\s(by\s)?(ajcc|figo|uicc|inrg|inss|cog|enneking|lugano|ann arbor|binet|rai|durie[- ]salmon|r?-?iss|tnm)(\sv\d+)?(\sstage\s?[0iv]+[abc]?)?$/i;1819/** Laterality prefixes only count when followed by an organ-level disease. */20const LATERALITY_PREFIX = /^(left|right)\s/i;2122/** Non-roman stage patterns and disease-specific staging guidelines ("Stage 4S Neuroblastoma", "Stage D Prostate Cancer", "… FIGO 2023", "… by Toronto Guidelines v2", INRG/INSS). */23const EXTRA_STAGE = /^stage\s+[a-z0-9][a-z0-9-]*\s|\b(figo\s+\d{4}|by\s+toronto\s+guidelines|toronto\s+guidelines\s+v\d+|inrg\s+stage|inss\s+stage)\b/i;2425/**26 * Recognized disease names that contain a state word as part of their identity.27 * Kept deliberately short: the regexes above are the primary mechanism.28 */29const KEEP_EXACT = new Set(30 [31 'Progressive Multifocal Leukoencephalopathy',32 'Persistent Mullerian Duct Syndrome',33 'Advanced Sclerosing Systemic Mastocytosis',34 'Residual Neuroblastoma',35 'Cancer of Unknown Primary Origin',36 'Carcinoma of Unknown Primary Origin',37 'Neoplasm of Unknown Primary Origin',38 'Adenocarcinoma of Unknown Primary Origin',39 'Squamous Cell Carcinoma of Unknown Primary Origin',40 'Neuroendocrine Carcinoma of Unknown Primary Origin',41 'Melanoma of Unknown Primary Origin',42 'Metastatic Malignant Neoplasm of Unknown Primary Origin',43 'Multifocal Motor Neuropathy',44 'Multicentric Castleman Disease',45 'Idiopathic Multicentric Castleman Disease',46 'HHV8-Associated Multicentric Castleman Disease',47 'Multicentric Reticulohistiocytosis',48 'Multifocal Langerhans Cell Histiocytosis',49 'Unifocal Langerhans Cell Histiocytosis',50 'Multifocal Osteosarcoma',51 'Synchronous Multiple Primary Malignant Neoplasm',52 'Metachronous Multiple Primary Malignant Neoplasm',53 'Occult Breast Carcinoma',54 'Occult Primary Malignant Neoplasm',55 ].map((s) => s.toLowerCase()),56);5758/** True when a preferred name denotes a qualified state rather than a disease taxonomy node. */59export function isQualifiedState(preferredName: string): boolean {60 const name = preferredName.trim().toLowerCase();61 if (KEEP_EXACT.has(name)) return false;62 if (STAGING_SUFFIX.test(name)) return true;63 if (STATE_WORDS.test(name)) return true;64 if (LATERALITY_PREFIX.test(name)) return true;65 if (EXTRA_STAGE.test(name)) return true;66 return false;67}6869/** Strip qualifiers to find the base disease label (used to attach qualified states to their disease). */70export function baseDiseaseLabel(preferredName: string): string {71 let s = preferredName.trim();72 for (let i = 0; i < 5; i++) {73 const before = s;74 s = s.replace(STAGING_SUFFIX, '');75 s = s.replace(/^(stage\s?[0iv]+[abcs]?\d?|recurrent|refractory|relapsed|resectable|unresectable|borderline resectable|localized|locally advanced|advanced|metastatic|progressive|residual|persistent|newly diagnosed|untreated|previously treated|previously untreated|treatment[- ]naive|bilateral|unilateral|left|right|synchronous|metachronous|multifocal|multicentric|unifocal|occult|extensive[- ]stage|limited[- ]stage)\s+/i, '');76 s = s.replace(/\s+(stage\s?[0iv]+[abcs]?\d?|in remission|in relapse|in complete remission)$/i, '');77 if (s === before) break;78 }79 return s.trim();80}81