/** * Machine Learning Outcome Prediction Infrastructure * * Provides ML-based prediction models for: * - Treatment response prediction * - Survival probability estimation * - Toxicity risk assessment * - Resistance prediction * - Optimal therapy selection * * This module provides the infrastructure for training and deploying * ML models. In production, models would be trained on real patient cohorts * with proper validation and FDA clearance. */ import { EventEmitter } from 'events'; import { createHash } from 'crypto'; // ═══════════════════════════════════════════════════════════════════════════════ // FEATURE TYPES // ═══════════════════════════════════════════════════════════════════════════════ export interface PatientFeatures { // Demographics age: number; gender: 'male' | 'female'; ethnicity?: string; bmi?: number; // Disease characteristics cancerType: string; histology?: string; stage: 'I' | 'II' | 'III' | 'IV' | 'IA' | 'IB' | 'IIA' | 'IIB' | 'IIIA' | 'IIIB' | 'IIIC' | 'IVA' | 'IVB'; grade?: 1 | 2 | 3; tumorSize?: number; // cm lymphNodeInvolvement?: number; metastaticSites?: string[]; // Performance status ecogStatus: 0 | 1 | 2 | 3 | 4; // Biomarkers genomicAlterations: { gene: string; alteration: string; type: 'mutation' | 'amplification' | 'deletion' | 'fusion'; vaf?: number; }[]; // Immunotherapy markers pdl1Score?: number; pdl1ScoreType?: 'TPS' | 'CPS' | 'IC'; msiStatus?: 'MSI-H' | 'MSI-L' | 'MSS'; tmbValue?: number; tmbStatus?: 'high' | 'low'; // HRD status hrdScore?: number; hrdStatus?: 'positive' | 'negative'; brcaStatus?: 'BRCA1' | 'BRCA2' | 'wild-type'; // Lab values ldh?: number; albumin?: number; hemoglobin?: number; neutrophils?: number; lymphocytes?: number; platelets?: number; creatinine?: number; bilirubin?: number; alkalinePhosphatase?: number; // Prior treatments priorLines?: number; priorTherapies?: string[]; priorResponse?: 'CR' | 'PR' | 'SD' | 'PD'; treatmentFreeInterval?: number; // months // Comorbidities comorbidityIndex?: number; organFunction?: { cardiac?: 'normal' | 'impaired'; hepatic?: 'normal' | 'impaired'; renal?: 'normal' | 'impaired'; pulmonary?: 'normal' | 'impaired'; }; } export interface TreatmentFeatures { regimen: string; drugs: string[]; treatmentType: 'chemotherapy' | 'immunotherapy' | 'targeted' | 'combination' | 'radiation' | 'surgery' | 'car-t'; setting: 'neoadjuvant' | 'adjuvant' | 'first-line' | 'second-line' | 'third-line-plus' | 'maintenance'; dosing?: 'standard' | 'reduced' | 'dose-dense'; schedule?: string; } // ═══════════════════════════════════════════════════════════════════════════════ // PREDICTION TYPES // ═══════════════════════════════════════════════════════════════════════════════ export interface ResponsePrediction { predictedResponse: 'CR' | 'PR' | 'SD' | 'PD'; probabilities: { completeResponse: number; partialResponse: number; stableDisease: number; progressiveDisease: number; }; confidence: number; objectiveResponseRate: number; // CR + PR probability diseaseControlRate: number; // CR + PR + SD probability timeToResponse?: { median: number; // weeks range: [number, number]; }; } export interface SurvivalPrediction { // Progression-Free Survival pfs: { median: number; // months sixMonth: number; // probability twelveMonth: number; twentyFourMonth: number; confidence: number; hazardRatio?: number; }; // Overall Survival os: { median: number; twelveMonth: number; twentyFourMonth: number; fiveYear: number; confidence: number; hazardRatio?: number; }; // Risk stratification riskGroup: 'low' | 'intermediate-low' | 'intermediate-high' | 'high'; riskScore: number; // 0-100 } export interface ToxicityPrediction { overallRisk: 'low' | 'moderate' | 'high'; grade3PlusRisk: number; // Probability of Grade 3+ toxicity specificRisks: { toxicity: string; grade: 1 | 2 | 3 | 4 | 5; probability: number; timeToOnset?: { median: number; unit: 'days' | 'weeks' | 'cycles' }; reversible: boolean; management?: string; }[]; // Immune-related adverse events (for immunotherapy) iraeRisk?: { any: number; grade3Plus: number; specificOrgans: { organ: string; risk: number }[]; }; // Dose modification recommendation doseModification?: { recommended: boolean; reduction: number; // percentage reason: string; }; } export interface ResistancePrediction { intrinsicResistanceRisk: number; // Probability of primary resistance acquiredResistanceRisk: number; // Probability of developing resistance predictedMechanisms: { mechanism: string; probability: number; monitoringBiomarker?: string; }[]; timeToResistance?: { median: number; // months range: [number, number]; }; nextLineOptions: { therapy: string; rationale: string; expectedBenefit: number; // expected months of benefit }[]; } export interface TherapyRanking { therapies: { regimen: string; drugs: string[]; rank: number; predictions: { responseRate: number; pfsSurvival: number; osSurvival: number; toxicityRisk: number; qualityOfLifeScore: number; }; overallScore: number; confidence: number; matchingBiomarkers: string[]; fdaApproved: boolean; nccnRecommended: boolean; considerations: string[]; contraindications?: string[]; }[]; bestChoice: { regimen: string; rationale: string[]; }; } // ═══════════════════════════════════════════════════════════════════════════════ // MODEL TYPES // ═══════════════════════════════════════════════════════════════════════════════ export interface MLModel { id: string; name: string; version: string; type: 'classification' | 'regression' | 'survival' | 'ranking'; target: 'response' | 'pfs' | 'os' | 'toxicity' | 'resistance'; cancerTypes: string[]; performance: { auc?: number; accuracy?: number; sensitivity?: number; specificity?: number; cIndex?: number; // For survival models calibration?: number; brier?: number; }; validation: { method: 'cross-validation' | 'temporal' | 'external' | 'prospective'; cohortSize: number; testSetSize: number; validationDate: Date; }; features: string[]; importantFeatures: { feature: string; importance: number }[]; status: 'development' | 'validation' | 'clinical-use' | 'deprecated'; regulatoryStatus?: 'not-submitted' | 'pending' | 'cleared' | '510k' | 'de-novo'; } export interface ModelRegistry { models: MLModel[]; getModel(id: string): MLModel | undefined; getBestModel(target: MLModel['target'], cancerType: string): MLModel | undefined; registerModel(model: MLModel): void; } // ═══════════════════════════════════════════════════════════════════════════════ // OUTCOME PREDICTOR SERVICE // ═══════════════════════════════════════════════════════════════════════════════ export class OutcomePredictorService extends EventEmitter { private modelRegistry: Map = new Map(); private featureEncoders: Map = new Map(); private predictionCache: Map = new Map(); private cacheDuration = 3600000; // 1 hour constructor() { super(); this.initializeDefaultModels(); this.initializeFeatureEncoders(); } /** * Predict treatment response */ async predictResponse( patient: PatientFeatures, treatment: TreatmentFeatures ): Promise { const cacheKey = this.getCacheKey('response', patient, treatment); const cached = this.getFromCache(cacheKey); if (cached) return cached; // Get appropriate model const model = this.getBestModel('response', patient.cancerType); // Encode features const features = this.encodeFeatures(patient, treatment); // Make prediction (in production, this would call a trained model) const prediction = this.computeResponsePrediction(features, patient, treatment, model); this.setCache(cacheKey, prediction); this.emit('prediction-made', { type: 'response', patient, treatment, prediction }); return prediction; } /** * Predict survival outcomes */ async predictSurvival( patient: PatientFeatures, treatment: TreatmentFeatures ): Promise { const cacheKey = this.getCacheKey('survival', patient, treatment); const cached = this.getFromCache(cacheKey); if (cached) return cached; const model = this.getBestModel('pfs', patient.cancerType); const features = this.encodeFeatures(patient, treatment); const prediction = this.computeSurvivalPrediction(features, patient, treatment, model); this.setCache(cacheKey, prediction); this.emit('prediction-made', { type: 'survival', patient, treatment, prediction }); return prediction; } /** * Predict toxicity risk */ async predictToxicity( patient: PatientFeatures, treatment: TreatmentFeatures ): Promise { const cacheKey = this.getCacheKey('toxicity', patient, treatment); const cached = this.getFromCache(cacheKey); if (cached) return cached; const model = this.getBestModel('toxicity', patient.cancerType); const features = this.encodeFeatures(patient, treatment); const prediction = this.computeToxicityPrediction(features, patient, treatment, model); this.setCache(cacheKey, prediction); this.emit('prediction-made', { type: 'toxicity', patient, treatment, prediction }); return prediction; } /** * Predict resistance development */ async predictResistance( patient: PatientFeatures, treatment: TreatmentFeatures ): Promise { const cacheKey = this.getCacheKey('resistance', patient, treatment); const cached = this.getFromCache(cacheKey); if (cached) return cached; const model = this.getBestModel('resistance', patient.cancerType); const features = this.encodeFeatures(patient, treatment); const prediction = this.computeResistancePrediction(features, patient, treatment, model); this.setCache(cacheKey, prediction); this.emit('prediction-made', { type: 'resistance', patient, treatment, prediction }); return prediction; } /** * Rank treatment options */ async rankTherapies( patient: PatientFeatures, treatmentOptions: TreatmentFeatures[], preferences?: { prioritizeEfficacy?: boolean; prioritizeToxicity?: boolean; prioritizeQoL?: boolean; } ): Promise { const rankings: TherapyRanking['therapies'] = []; // Get predictions for each treatment option for (const treatment of treatmentOptions) { const [response, survival, toxicity] = await Promise.all([ this.predictResponse(patient, treatment), this.predictSurvival(patient, treatment), this.predictToxicity(patient, treatment) ]); // Calculate overall score based on preferences const weights = { efficacy: preferences?.prioritizeEfficacy ? 0.5 : 0.35, toxicity: preferences?.prioritizeToxicity ? 0.3 : 0.25, qol: preferences?.prioritizeQoL ? 0.3 : 0.2, survival: 0.2 }; const efficacyScore = response.objectiveResponseRate; const toxicityScore = 1 - toxicity.grade3PlusRisk; const survivalScore = survival.pfs.twelveMonth; const qolScore = this.estimateQoLScore(toxicity); const overallScore = efficacyScore * weights.efficacy + toxicityScore * weights.toxicity + survivalScore * weights.survival + qolScore * weights.qol; rankings.push({ regimen: treatment.regimen, drugs: treatment.drugs, rank: 0, // Will be set after sorting predictions: { responseRate: response.objectiveResponseRate, pfsSurvival: survival.pfs.median, osSurvival: survival.os.median, toxicityRisk: toxicity.grade3PlusRisk, qualityOfLifeScore: qolScore }, overallScore, confidence: (response.confidence + survival.pfs.confidence) / 2, matchingBiomarkers: this.getMatchingBiomarkers(patient, treatment), fdaApproved: this.checkFDAApproval(treatment, patient.cancerType), nccnRecommended: this.checkNCCNRecommendation(treatment, patient.cancerType, patient.stage), considerations: this.getConsiderations(patient, treatment) }); } // Sort by overall score rankings.sort((a, b) => b.overallScore - a.overallScore); // Assign ranks rankings.forEach((r, i) => r.rank = i + 1); const bestChoice = rankings[0]; return { therapies: rankings, bestChoice: { regimen: bestChoice.regimen, rationale: [ `Highest overall score (${(bestChoice.overallScore * 100).toFixed(1)}%)`, `Expected response rate: ${(bestChoice.predictions.responseRate * 100).toFixed(1)}%`, `Expected median PFS: ${bestChoice.predictions.pfsSurvival.toFixed(1)} months`, bestChoice.fdaApproved ? 'FDA approved for this indication' : '', bestChoice.nccnRecommended ? 'NCCN recommended' : '', ...bestChoice.matchingBiomarkers.map(b => `Matches biomarker: ${b}`) ].filter(Boolean) } }; } /** * Get comprehensive prediction report */ async getComprehensivePrediction( patient: PatientFeatures, treatment: TreatmentFeatures ): Promise<{ response: ResponsePrediction; survival: SurvivalPrediction; toxicity: ToxicityPrediction; resistance: ResistancePrediction; overallAssessment: { recommendation: 'strongly-recommended' | 'recommended' | 'consider' | 'caution' | 'not-recommended'; rationale: string[]; caveats: string[]; alternativeOptions: string[]; }; }> { const [response, survival, toxicity, resistance] = await Promise.all([ this.predictResponse(patient, treatment), this.predictSurvival(patient, treatment), this.predictToxicity(patient, treatment), this.predictResistance(patient, treatment) ]); // Generate overall assessment const overallAssessment = this.generateOverallAssessment( patient, treatment, response, survival, toxicity, resistance ); return { response, survival, toxicity, resistance, overallAssessment }; } // ═══════════════════════════════════════════════════════════════════════════════ // PREDICTION COMPUTATION (Placeholder implementations) // In production, these would use trained ML models // ═══════════════════════════════════════════════════════════════════════════════ private computeResponsePrediction( features: number[], patient: PatientFeatures, treatment: TreatmentFeatures, model?: MLModel ): ResponsePrediction { // Base probabilities based on treatment type and cancer let baseCR = 0.15; let basePR = 0.30; let baseSD = 0.30; // Adjust based on biomarkers if (this.hasBiomarkerMatch(patient, treatment)) { baseCR += 0.15; basePR += 0.15; } // Adjust based on stage const stageModifiers: Record = { 'I': 1.3, 'IA': 1.3, 'IB': 1.25, 'II': 1.15, 'IIA': 1.15, 'IIB': 1.1, 'III': 0.9, 'IIIA': 0.95, 'IIIB': 0.85, 'IIIC': 0.8, 'IV': 0.7, 'IVA': 0.75, 'IVB': 0.65 }; const stageMod = stageModifiers[patient.stage] || 1.0; // Adjust based on ECOG const ecogModifiers = [1.0, 0.9, 0.75, 0.5, 0.25]; const ecogMod = ecogModifiers[patient.ecogStatus]; // Adjust based on treatment setting const settingModifiers: Record = { 'first-line': 1.0, 'second-line': 0.75, 'third-line-plus': 0.5, 'neoadjuvant': 1.1, 'adjuvant': 1.05, 'maintenance': 0.9 }; const settingMod = settingModifiers[treatment.setting] || 1.0; // Apply modifiers const modifier = stageMod * ecogMod * settingMod; const crProb = Math.min(baseCR * modifier, 0.6); const prProb = Math.min(basePR * modifier, 0.5); const sdProb = Math.min(baseSD * modifier, 0.4); const pdProb = Math.max(1 - crProb - prProb - sdProb, 0.05); // Normalize const total = crProb + prProb + sdProb + pdProb; const probabilities = { completeResponse: crProb / total, partialResponse: prProb / total, stableDisease: sdProb / total, progressiveDisease: pdProb / total }; // Determine best response const maxProb = Math.max(...Object.values(probabilities)); let predictedResponse: ResponsePrediction['predictedResponse'] = 'SD'; if (probabilities.completeResponse === maxProb) predictedResponse = 'CR'; else if (probabilities.partialResponse === maxProb) predictedResponse = 'PR'; else if (probabilities.progressiveDisease === maxProb) predictedResponse = 'PD'; return { predictedResponse, probabilities, confidence: model ? model.performance.auc || 0.75 : 0.70, objectiveResponseRate: probabilities.completeResponse + probabilities.partialResponse, diseaseControlRate: probabilities.completeResponse + probabilities.partialResponse + probabilities.stableDisease, timeToResponse: { median: 8, range: [4, 16] } }; } private computeSurvivalPrediction( features: number[], patient: PatientFeatures, treatment: TreatmentFeatures, model?: MLModel ): SurvivalPrediction { // Base survival estimates (in months) let basePFS = 12; let baseOS = 24; // Cancer type adjustments const cancerPFSModifiers: Record = { 'NSCLC': 1.0, 'SCLC': 0.5, 'Breast': 1.5, 'Colorectal': 1.0, 'Melanoma': 1.2, 'RCC': 1.3, 'Ovarian': 0.8, 'Pancreatic': 0.4, 'Glioblastoma': 0.3, 'AML': 0.6, 'Multiple Myeloma': 1.5 }; const cancerMod = cancerPFSModifiers[patient.cancerType] || 1.0; basePFS *= cancerMod; baseOS *= cancerMod; // Stage adjustments const stageModifiers: Record = { 'I': 3.0, 'IA': 3.5, 'IB': 2.8, 'II': 2.0, 'IIA': 2.2, 'IIB': 1.8, 'III': 1.0, 'IIIA': 1.2, 'IIIB': 0.9, 'IIIC': 0.7, 'IV': 0.5, 'IVA': 0.55, 'IVB': 0.4 }; const stageMod = stageModifiers[patient.stage] || 1.0; basePFS *= stageMod; baseOS *= stageMod; // Biomarker-driven therapy boost if (this.hasBiomarkerMatch(patient, treatment)) { basePFS *= 1.5; baseOS *= 1.3; } // ECOG adjustment const ecogMultipliers = [1.0, 0.85, 0.65, 0.4, 0.2]; basePFS *= ecogMultipliers[patient.ecogStatus]; baseOS *= ecogMultipliers[patient.ecogStatus]; // Calculate probabilities using exponential survival model const lambda_pfs = 1 / basePFS; const lambda_os = 1 / baseOS; const pfs = { median: basePFS, sixMonth: Math.exp(-lambda_pfs * 6), twelveMonth: Math.exp(-lambda_pfs * 12), twentyFourMonth: Math.exp(-lambda_pfs * 24), confidence: model ? model.performance.cIndex || 0.72 : 0.68 }; const os = { median: baseOS, twelveMonth: Math.exp(-lambda_os * 12), twentyFourMonth: Math.exp(-lambda_os * 24), fiveYear: Math.exp(-lambda_os * 60), confidence: model ? model.performance.cIndex || 0.72 : 0.68 }; // Calculate risk score (0-100) const riskFactors = [ patient.stage.startsWith('IV') ? 25 : patient.stage.startsWith('III') ? 15 : 5, patient.ecogStatus >= 2 ? 20 : patient.ecogStatus === 1 ? 10 : 0, (patient.priorLines || 0) >= 2 ? 15 : (patient.priorLines || 0) >= 1 ? 8 : 0, patient.ldh && patient.ldh > 250 ? 10 : 0, patient.metastaticSites && patient.metastaticSites.length > 2 ? 15 : 0, patient.age > 75 ? 10 : patient.age > 65 ? 5 : 0 ]; const riskScore = Math.min(riskFactors.reduce((a, b) => a + b, 0), 100); let riskGroup: SurvivalPrediction['riskGroup']; if (riskScore < 25) riskGroup = 'low'; else if (riskScore < 50) riskGroup = 'intermediate-low'; else if (riskScore < 75) riskGroup = 'intermediate-high'; else riskGroup = 'high'; return { pfs, os, riskGroup, riskScore }; } private computeToxicityPrediction( features: number[], patient: PatientFeatures, treatment: TreatmentFeatures, model?: MLModel ): ToxicityPrediction { const specificRisks: ToxicityPrediction['specificRisks'] = []; let grade3PlusRisk = 0; // Define toxicity profiles by drug class const drugToxicities = this.getDrugToxicityProfiles(treatment.drugs); for (const tox of drugToxicities) { // Adjust risk based on patient factors let adjustedRisk = tox.baseRisk; // Age adjustment if (patient.age > 70) adjustedRisk *= 1.2; if (patient.age > 80) adjustedRisk *= 1.4; // Organ function adjustment if (tox.affectedOrgan === 'hepatic' && patient.organFunction?.hepatic === 'impaired') { adjustedRisk *= 1.5; } if (tox.affectedOrgan === 'renal' && patient.organFunction?.renal === 'impaired') { adjustedRisk *= 1.5; } if (tox.affectedOrgan === 'cardiac' && patient.organFunction?.cardiac === 'impaired') { adjustedRisk *= 1.5; } // ECOG adjustment adjustedRisk *= (1 + patient.ecogStatus * 0.1); adjustedRisk = Math.min(adjustedRisk, 0.95); specificRisks.push({ toxicity: tox.name, grade: tox.typicalGrade, probability: adjustedRisk, timeToOnset: tox.timeToOnset, reversible: tox.reversible, management: tox.management }); if (tox.typicalGrade >= 3) { grade3PlusRisk += adjustedRisk * 0.3; // Weighted contribution } } grade3PlusRisk = Math.min(grade3PlusRisk, 0.9); // Immunotherapy-specific irAE prediction let iraeRisk: ToxicityPrediction['iraeRisk']; if (treatment.treatmentType === 'immunotherapy' || this.hasImmunotherapyDrug(treatment.drugs)) { iraeRisk = { any: 0.60, grade3Plus: 0.15, specificOrgans: [ { organ: 'skin', risk: 0.35 }, { organ: 'GI', risk: 0.20 }, { organ: 'endocrine', risk: 0.15 }, { organ: 'hepatic', risk: 0.10 }, { organ: 'pulmonary', risk: 0.08 } ] }; } // Dose modification recommendation let doseModification: ToxicityPrediction['doseModification']; if (patient.age > 75 || patient.ecogStatus >= 2 || patient.organFunction?.renal === 'impaired') { doseModification = { recommended: true, reduction: 20, reason: patient.age > 75 ? 'Advanced age' : patient.ecogStatus >= 2 ? 'Poor performance status' : 'Impaired organ function' }; } return { overallRisk: grade3PlusRisk > 0.4 ? 'high' : grade3PlusRisk > 0.2 ? 'moderate' : 'low', grade3PlusRisk, specificRisks, iraeRisk, doseModification }; } private computeResistancePrediction( features: number[], patient: PatientFeatures, treatment: TreatmentFeatures, model?: MLModel ): ResistancePrediction { let intrinsicRisk = 0.2; let acquiredRisk = 0.6; // Adjust based on prior treatments if (patient.priorLines && patient.priorLines > 0) { intrinsicRisk += 0.1 * patient.priorLines; acquiredRisk += 0.05 * patient.priorLines; } // Adjust based on prior response if (patient.priorResponse === 'PD') { intrinsicRisk += 0.2; } else if (patient.priorResponse === 'CR') { intrinsicRisk -= 0.1; } // Get resistance mechanisms based on treatment and mutations const mechanisms = this.getResistanceMechanisms(patient, treatment); // Calculate time to resistance const baseTimeToResistance = this.hasBiomarkerMatch(patient, treatment) ? 18 : 9; const timeModifier = patient.priorLines ? 1 - (patient.priorLines * 0.15) : 1; // Get next line options const nextLineOptions = this.getNextLineOptions(patient, treatment, mechanisms); return { intrinsicResistanceRisk: Math.min(intrinsicRisk, 0.9), acquiredResistanceRisk: Math.min(acquiredRisk, 0.95), predictedMechanisms: mechanisms, timeToResistance: { median: baseTimeToResistance * timeModifier, range: [baseTimeToResistance * timeModifier * 0.5, baseTimeToResistance * timeModifier * 2] }, nextLineOptions }; } // ═══════════════════════════════════════════════════════════════════════════════ // HELPER METHODS // ═══════════════════════════════════════════════════════════════════════════════ private initializeDefaultModels(): void { // Register placeholder models const defaultModels: MLModel[] = [ { id: 'response-nsclc-v1', name: 'NSCLC Response Predictor', version: '1.0', type: 'classification', target: 'response', cancerTypes: ['NSCLC'], performance: { auc: 0.78, accuracy: 0.72 }, validation: { method: 'cross-validation', cohortSize: 1500, testSetSize: 300, validationDate: new Date() }, features: ['age', 'stage', 'ecog', 'pdl1', 'tmb', 'egfr', 'alk', 'kras'], importantFeatures: [ { feature: 'pdl1', importance: 0.25 }, { feature: 'tmb', importance: 0.20 }, { feature: 'egfr', importance: 0.18 } ], status: 'validation' }, { id: 'survival-pan-cancer-v1', name: 'Pan-Cancer Survival Model', version: '1.0', type: 'survival', target: 'os', cancerTypes: ['all'], performance: { cIndex: 0.72 }, validation: { method: 'cross-validation', cohortSize: 10000, testSetSize: 2000, validationDate: new Date() }, features: ['age', 'stage', 'ecog', 'cancerType', 'priorLines', 'ldh'], importantFeatures: [ { feature: 'stage', importance: 0.30 }, { feature: 'ecog', importance: 0.25 }, { feature: 'priorLines', importance: 0.15 } ], status: 'validation' } ]; for (const model of defaultModels) { this.modelRegistry.set(model.id, model); } } private initializeFeatureEncoders(): void { // Initialize encoders for categorical variables this.featureEncoders.set('stage', new FeatureEncoder({ 'I': 1, 'IA': 1, 'IB': 1.5, 'II': 2, 'IIA': 2, 'IIB': 2.5, 'III': 3, 'IIIA': 3, 'IIIB': 3.5, 'IIIC': 3.8, 'IV': 4, 'IVA': 4, 'IVB': 4.5 })); this.featureEncoders.set('cancerType', new FeatureEncoder({ 'NSCLC': 1, 'SCLC': 2, 'Breast': 3, 'Colorectal': 4, 'Melanoma': 5, 'RCC': 6, 'Ovarian': 7, 'Pancreatic': 8 })); } private encodeFeatures(patient: PatientFeatures, treatment: TreatmentFeatures): number[] { const features: number[] = []; // Numeric features (normalized) features.push(patient.age / 100); features.push(patient.ecogStatus / 4); features.push(patient.gender === 'male' ? 1 : 0); // Encoded categorical features const stageEncoder = this.featureEncoders.get('stage'); features.push((stageEncoder?.encode(patient.stage) || 2) / 5); // Biomarker features features.push((patient.pdl1Score || 0) / 100); features.push((patient.tmbValue || 0) / 50); features.push(patient.msiStatus === 'MSI-H' ? 1 : 0); features.push(patient.hrdStatus === 'positive' ? 1 : 0); // Prior treatment features features.push((patient.priorLines || 0) / 5); // Treatment features features.push(treatment.treatmentType === 'immunotherapy' ? 1 : 0); features.push(treatment.treatmentType === 'targeted' ? 1 : 0); return features; } private getBestModel(target: MLModel['target'], cancerType: string): MLModel | undefined { const models = Array.from(this.modelRegistry.values()) .filter(m => m.target === target && (m.cancerTypes.includes(cancerType) || m.cancerTypes.includes('all')) && m.status !== 'deprecated' ) .sort((a, b) => (b.performance.auc || b.performance.cIndex || 0) - (a.performance.auc || a.performance.cIndex || 0)); return models[0]; } private hasBiomarkerMatch(patient: PatientFeatures, treatment: TreatmentFeatures): boolean { // Check for biomarker-drug matches const matches = [ { biomarker: 'EGFR', drugs: ['osimertinib', 'erlotinib', 'gefitinib', 'afatinib'] }, { biomarker: 'ALK', drugs: ['alectinib', 'crizotinib', 'brigatinib', 'lorlatinib'] }, { biomarker: 'BRAF V600', drugs: ['dabrafenib', 'vemurafenib', 'encorafenib'] }, { biomarker: 'HER2', drugs: ['trastuzumab', 'pertuzumab', 't-dxd'] }, { biomarker: 'BRCA', drugs: ['olaparib', 'rucaparib', 'niraparib', 'talazoparib'] }, { biomarker: 'KRAS G12C', drugs: ['sotorasib', 'adagrasib'] } ]; for (const match of matches) { const hasBiomarker = patient.genomicAlterations.some(g => g.gene.toUpperCase().includes(match.biomarker) || g.alteration.toUpperCase().includes(match.biomarker) ); const hasDrug = treatment.drugs.some(d => match.drugs.some(md => d.toLowerCase().includes(md)) ); if (hasBiomarker && hasDrug) return true; } // Check immunotherapy eligibility if (treatment.treatmentType === 'immunotherapy' || this.hasImmunotherapyDrug(treatment.drugs)) { if (patient.msiStatus === 'MSI-H') return true; if (patient.tmbStatus === 'high') return true; if (patient.pdl1Score && patient.pdl1Score >= 50) return true; } return false; } private hasImmunotherapyDrug(drugs: string[]): boolean { const immunoDrugs = ['pembrolizumab', 'nivolumab', 'ipilimumab', 'atezolizumab', 'durvalumab', 'avelumab']; return drugs.some(d => immunoDrugs.some(id => d.toLowerCase().includes(id))); } private getDrugToxicityProfiles(drugs: string[]): { name: string; baseRisk: number; typicalGrade: 1 | 2 | 3 | 4 | 5; affectedOrgan?: string; timeToOnset?: { median: number; unit: 'days' | 'weeks' | 'cycles' }; reversible: boolean; management?: string; }[] { const profiles: any[] = []; // Check for common drug classes and their toxicities for (const drug of drugs) { const lower = drug.toLowerCase(); // Checkpoint inhibitors if (['pembrolizumab', 'nivolumab', 'ipilimumab', 'atezolizumab'].some(d => lower.includes(d))) { profiles.push( { name: 'Immune-related dermatitis', baseRisk: 0.35, typicalGrade: 2, affectedOrgan: 'skin', reversible: true }, { name: 'Immune-related colitis', baseRisk: 0.15, typicalGrade: 3, affectedOrgan: 'GI', reversible: true, management: 'Corticosteroids' }, { name: 'Immune-related pneumonitis', baseRisk: 0.05, typicalGrade: 3, affectedOrgan: 'pulmonary', reversible: true, management: 'Hold treatment, corticosteroids' }, { name: 'Immune-related hepatitis', baseRisk: 0.08, typicalGrade: 3, affectedOrgan: 'hepatic', reversible: true }, { name: 'Immune-related thyroiditis', baseRisk: 0.15, typicalGrade: 2, affectedOrgan: 'endocrine', reversible: false } ); } // Platinum agents if (['carboplatin', 'cisplatin', 'oxaliplatin'].some(d => lower.includes(d))) { profiles.push( { name: 'Nausea/Vomiting', baseRisk: 0.60, typicalGrade: 2, affectedOrgan: 'GI', reversible: true }, { name: 'Nephrotoxicity', baseRisk: lower.includes('cisplatin') ? 0.25 : 0.08, typicalGrade: 2, affectedOrgan: 'renal', reversible: true }, { name: 'Myelosuppression', baseRisk: 0.40, typicalGrade: 3, affectedOrgan: 'hematologic', reversible: true }, { name: 'Peripheral neuropathy', baseRisk: 0.30, typicalGrade: 2, affectedOrgan: 'neurologic', reversible: false } ); } // EGFR TKIs if (['osimertinib', 'erlotinib', 'gefitinib', 'afatinib'].some(d => lower.includes(d))) { profiles.push( { name: 'Rash', baseRisk: 0.45, typicalGrade: 2, affectedOrgan: 'skin', reversible: true }, { name: 'Diarrhea', baseRisk: 0.50, typicalGrade: 2, affectedOrgan: 'GI', reversible: true }, { name: 'Interstitial lung disease', baseRisk: 0.03, typicalGrade: 4, affectedOrgan: 'pulmonary', reversible: false } ); } // CDK4/6 inhibitors if (['palbociclib', 'ribociclib', 'abemaciclib'].some(d => lower.includes(d))) { profiles.push( { name: 'Neutropenia', baseRisk: 0.70, typicalGrade: 3, affectedOrgan: 'hematologic', reversible: true }, { name: 'Fatigue', baseRisk: 0.40, typicalGrade: 2, reversible: true }, { name: 'Diarrhea', baseRisk: lower.includes('abemaciclib') ? 0.80 : 0.20, typicalGrade: 2, affectedOrgan: 'GI', reversible: true } ); } } return profiles; } private getResistanceMechanisms(patient: PatientFeatures, treatment: TreatmentFeatures): ResistancePrediction['predictedMechanisms'] { const mechanisms: ResistancePrediction['predictedMechanisms'] = []; // EGFR TKI resistance mechanisms if (treatment.drugs.some(d => ['osimertinib', 'erlotinib', 'gefitinib'].some(e => d.toLowerCase().includes(e)))) { mechanisms.push( { mechanism: 'MET amplification', probability: 0.20, monitoringBiomarker: 'MET FISH/NGS' }, { mechanism: 'EGFR C797S mutation', probability: 0.15, monitoringBiomarker: 'ctDNA EGFR' }, { mechanism: 'Histologic transformation (SCLC)', probability: 0.05, monitoringBiomarker: 'Tissue biopsy' }, { mechanism: 'HER2 amplification', probability: 0.10, monitoringBiomarker: 'HER2 NGS/FISH' } ); } // Immunotherapy resistance if (this.hasImmunotherapyDrug(treatment.drugs)) { mechanisms.push( { mechanism: 'Beta-2 microglobulin loss', probability: 0.10, monitoringBiomarker: 'B2M NGS' }, { mechanism: 'JAK1/2 mutations', probability: 0.08, monitoringBiomarker: 'JAK1/2 NGS' }, { mechanism: 'Immunosuppressive TME', probability: 0.25, monitoringBiomarker: 'Tissue biopsy + IHC' }, { mechanism: 'Antigen loss', probability: 0.15 } ); } // BRAF inhibitor resistance if (treatment.drugs.some(d => ['dabrafenib', 'vemurafenib', 'encorafenib'].some(b => d.toLowerCase().includes(b)))) { mechanisms.push( { mechanism: 'MAPK reactivation', probability: 0.30, monitoringBiomarker: 'NRAS/MEK NGS' }, { mechanism: 'BRAF amplification', probability: 0.15, monitoringBiomarker: 'BRAF CNV' }, { mechanism: 'RTK bypass (EGFR, MET)', probability: 0.20, monitoringBiomarker: 'Comprehensive NGS' } ); } return mechanisms; } private getNextLineOptions( patient: PatientFeatures, treatment: TreatmentFeatures, mechanisms: ResistancePrediction['predictedMechanisms'] ): ResistancePrediction['nextLineOptions'] { const options: ResistancePrediction['nextLineOptions'] = []; // Based on predicted resistance mechanisms for (const mech of mechanisms.slice(0, 3)) { if (mech.mechanism === 'MET amplification') { options.push({ therapy: 'Osimertinib + Savolitinib', rationale: 'Targets MET bypass pathway', expectedBenefit: 8 }); } if (mech.mechanism === 'EGFR C797S mutation') { options.push({ therapy: 'First-generation EGFR TKI + Third-generation EGFR TKI', rationale: 'C797S may restore sensitivity to 1st-gen TKI', expectedBenefit: 6 }); } if (mech.mechanism === 'Histologic transformation (SCLC)') { options.push({ therapy: 'Platinum-etoposide chemotherapy', rationale: 'Standard SCLC treatment', expectedBenefit: 4 }); } } // Add general next-line options if (treatment.treatmentType !== 'immunotherapy' && !this.hasImmunotherapyDrug(treatment.drugs)) { options.push({ therapy: 'Immunotherapy (if PD-L1+/TMB-H)', rationale: 'Different mechanism of action', expectedBenefit: 10 }); } // Clinical trial option options.push({ therapy: 'Clinical trial enrollment', rationale: 'Access to novel agents', expectedBenefit: 8 }); return options; } private estimateQoLScore(toxicity: ToxicityPrediction): number { let score = 0.85; // Base quality of life // Reduce based on toxicity severity score -= toxicity.grade3PlusRisk * 0.3; // Specific high-impact toxicities for (const tox of toxicity.specificRisks) { if (tox.toxicity.toLowerCase().includes('neuropathy') && tox.grade >= 2) { score -= 0.1; } if (tox.toxicity.toLowerCase().includes('fatigue') && tox.grade >= 2) { score -= 0.08; } if (tox.toxicity.toLowerCase().includes('nausea') && tox.grade >= 2) { score -= 0.05; } } return Math.max(score, 0.3); } private getMatchingBiomarkers(patient: PatientFeatures, treatment: TreatmentFeatures): string[] { const matches: string[] = []; for (const alt of patient.genomicAlterations) { if (this.isBiomarkerRelevant(alt.gene, alt.alteration, treatment)) { matches.push(`${alt.gene} ${alt.alteration}`); } } if (patient.msiStatus === 'MSI-H' && this.hasImmunotherapyDrug(treatment.drugs)) { matches.push('MSI-H'); } if (patient.tmbStatus === 'high' && this.hasImmunotherapyDrug(treatment.drugs)) { matches.push('TMB-High'); } if (patient.hrdStatus === 'positive' && treatment.drugs.some(d => ['olaparib', 'rucaparib', 'niraparib', 'talazoparib'].some(p => d.toLowerCase().includes(p)) )) { matches.push('HRD-positive'); } return matches; } private isBiomarkerRelevant(gene: string, alteration: string, treatment: TreatmentFeatures): boolean { const relevantPairs: Record = { 'EGFR': ['osimertinib', 'erlotinib', 'gefitinib', 'afatinib'], 'ALK': ['alectinib', 'crizotinib', 'brigatinib', 'lorlatinib'], 'ROS1': ['crizotinib', 'entrectinib'], 'BRAF': ['dabrafenib', 'vemurafenib', 'encorafenib'], 'HER2': ['trastuzumab', 'pertuzumab', 't-dxd'], 'BRCA': ['olaparib', 'rucaparib', 'niraparib', 'talazoparib'], 'KRAS': ['sotorasib', 'adagrasib'], 'NTRK': ['larotrectinib', 'entrectinib'], 'RET': ['selpercatinib', 'pralsetinib'], 'MET': ['capmatinib', 'tepotinib'] }; const drugs = relevantPairs[gene.toUpperCase()]; if (!drugs) return false; return treatment.drugs.some(d => drugs.some(rd => d.toLowerCase().includes(rd))); } private checkFDAApproval(treatment: TreatmentFeatures, cancerType: string): boolean { // Simplified FDA approval check - would reference actual database in production const approvedCombinations: Record = { 'NSCLC': ['osimertinib', 'pembrolizumab', 'alectinib', 'sotorasib'], 'Breast': ['palbociclib', 'trastuzumab', 'olaparib', 'sacituzumab'], 'Melanoma': ['pembrolizumab', 'nivolumab', 'ipilimumab', 'dabrafenib'], 'Colorectal': ['pembrolizumab', 'cetuximab', 'bevacizumab'], 'RCC': ['pembrolizumab', 'nivolumab', 'cabozantinib'] }; const approvedDrugs = approvedCombinations[cancerType] || []; return treatment.drugs.some(d => approvedDrugs.some(ad => d.toLowerCase().includes(ad))); } private checkNCCNRecommendation(treatment: TreatmentFeatures, cancerType: string, stage: string): boolean { // Simplified NCCN check - would reference actual guidelines in production return this.checkFDAApproval(treatment, cancerType); } private getConsiderations(patient: PatientFeatures, treatment: TreatmentFeatures): string[] { const considerations: string[] = []; if (patient.age > 75) { considerations.push('Consider dose reduction for elderly patient'); } if (patient.ecogStatus >= 2) { considerations.push('Poor performance status may limit tolerability'); } if (patient.organFunction?.renal === 'impaired') { considerations.push('Dose adjustment may be needed for renal impairment'); } if (patient.priorLines && patient.priorLines >= 2) { considerations.push('Heavily pretreated - consider clinical trial'); } return considerations; } private generateOverallAssessment( patient: PatientFeatures, treatment: TreatmentFeatures, response: ResponsePrediction, survival: SurvivalPrediction, toxicity: ToxicityPrediction, resistance: ResistancePrediction ): { recommendation: 'strongly-recommended' | 'recommended' | 'consider' | 'caution' | 'not-recommended'; rationale: string[]; caveats: string[]; alternativeOptions: string[]; } { const rationale: string[] = []; const caveats: string[] = []; const alternativeOptions: string[] = []; // Calculate recommendation score let score = 50; // Response contribution if (response.objectiveResponseRate >= 0.6) { score += 20; rationale.push('High expected response rate'); } else if (response.objectiveResponseRate >= 0.4) { score += 10; } else if (response.objectiveResponseRate < 0.2) { score -= 20; caveats.push('Low expected response rate'); } // Survival contribution if (survival.pfs.twelveMonth >= 0.6) { score += 15; rationale.push('Favorable survival outlook'); } else if (survival.pfs.twelveMonth < 0.3) { score -= 15; caveats.push('Limited expected duration of benefit'); } // Toxicity contribution if (toxicity.grade3PlusRisk < 0.2) { score += 10; rationale.push('Favorable toxicity profile'); } else if (toxicity.grade3PlusRisk > 0.5) { score -= 20; caveats.push('High risk of severe toxicity'); } // Biomarker match if (this.hasBiomarkerMatch(patient, treatment)) { score += 15; rationale.push('Biomarker-matched therapy'); } // FDA approval and guidelines if (this.checkFDAApproval(treatment, patient.cancerType)) { score += 10; rationale.push('FDA approved for indication'); } // Add alternatives if (resistance.nextLineOptions.length > 0) { alternativeOptions.push(...resistance.nextLineOptions.slice(0, 2).map(o => o.therapy)); } // Determine recommendation let recommendation: 'strongly-recommended' | 'recommended' | 'consider' | 'caution' | 'not-recommended'; if (score >= 80) recommendation = 'strongly-recommended'; else if (score >= 60) recommendation = 'recommended'; else if (score >= 40) recommendation = 'consider'; else if (score >= 20) recommendation = 'caution'; else recommendation = 'not-recommended'; return { recommendation, rationale, caveats, alternativeOptions }; } private getCacheKey(type: string, patient: PatientFeatures, treatment: TreatmentFeatures): string { const data = JSON.stringify({ type, patient, treatment }); return createHash('sha256').update(data).digest('hex').substring(0, 16); } private getFromCache(key: string): T | null { const cached = this.predictionCache.get(key); if (cached && Date.now() - cached.timestamp.getTime() < this.cacheDuration) { return cached.prediction as T; } return null; } private setCache(key: string, prediction: any): void { this.predictionCache.set(key, { prediction, timestamp: new Date() }); } /** * Register a new model */ registerModel(model: MLModel): void { this.modelRegistry.set(model.id, model); this.emit('model-registered', model); } /** * Get model performance metrics */ getModelMetrics(modelId: string): MLModel['performance'] | undefined { return this.modelRegistry.get(modelId)?.performance; } /** * List all available models */ listModels(): MLModel[] { return Array.from(this.modelRegistry.values()); } } // ═══════════════════════════════════════════════════════════════════════════════ // FEATURE ENCODER // ═══════════════════════════════════════════════════════════════════════════════ class FeatureEncoder { private mapping: Record; constructor(mapping: Record) { this.mapping = mapping; } encode(value: string): number { return this.mapping[value] ?? 0; } } export default OutcomePredictorService;