@erosolaraijs/cure 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/cure.d.ts +2 -5
- package/dist/bin/cure.d.ts.map +1 -1
- package/dist/bin/cure.js +285 -124
- package/dist/bin/cure.js.map +1 -1
- package/dist/clinician/decisionSupport.d.ts +325 -0
- package/dist/clinician/decisionSupport.d.ts.map +1 -0
- package/dist/clinician/decisionSupport.js +604 -0
- package/dist/clinician/decisionSupport.js.map +1 -0
- package/dist/clinician/index.d.ts +5 -0
- package/dist/clinician/index.d.ts.map +1 -0
- package/dist/clinician/index.js +5 -0
- package/dist/clinician/index.js.map +1 -0
- package/dist/compliance/index.d.ts +5 -0
- package/dist/compliance/index.d.ts.map +1 -0
- package/dist/compliance/index.js +5 -0
- package/dist/compliance/index.js.map +1 -0
- package/dist/integrations/clinicalTrials/index.d.ts +5 -0
- package/dist/integrations/clinicalTrials/index.d.ts.map +1 -0
- package/dist/integrations/clinicalTrials/index.js +5 -0
- package/dist/integrations/clinicalTrials/index.js.map +1 -0
- package/dist/integrations/ehr/index.d.ts +5 -0
- package/dist/integrations/ehr/index.d.ts.map +1 -0
- package/dist/integrations/ehr/index.js +5 -0
- package/dist/integrations/ehr/index.js.map +1 -0
- package/dist/integrations/genomics/index.d.ts +5 -0
- package/dist/integrations/genomics/index.d.ts.map +1 -0
- package/dist/integrations/genomics/index.js +5 -0
- package/dist/integrations/genomics/index.js.map +1 -0
- package/dist/integrations/index.d.ts +7 -0
- package/dist/integrations/index.d.ts.map +1 -0
- package/dist/integrations/index.js +10 -0
- package/dist/integrations/index.js.map +1 -0
- package/dist/ml/index.d.ts +5 -0
- package/dist/ml/index.d.ts.map +1 -0
- package/dist/ml/index.js +5 -0
- package/dist/ml/index.js.map +1 -0
- package/dist/ml/outcomePredictor.d.ts +297 -0
- package/dist/ml/outcomePredictor.d.ts.map +1 -0
- package/dist/ml/outcomePredictor.js +823 -0
- package/dist/ml/outcomePredictor.js.map +1 -0
- package/dist/patient/index.d.ts +5 -0
- package/dist/patient/index.d.ts.map +1 -0
- package/dist/patient/index.js +5 -0
- package/dist/patient/index.js.map +1 -0
- package/dist/patient/patientPortal.d.ts +337 -0
- package/dist/patient/patientPortal.d.ts.map +1 -0
- package/dist/patient/patientPortal.js +667 -0
- package/dist/patient/patientPortal.js.map +1 -0
- package/dist/safety/drugInteractions.d.ts +230 -0
- package/dist/safety/drugInteractions.d.ts.map +1 -0
- package/dist/safety/drugInteractions.js +697 -0
- package/dist/safety/drugInteractions.js.map +1 -0
- package/dist/safety/index.d.ts +5 -0
- package/dist/safety/index.d.ts.map +1 -0
- package/dist/safety/index.js +5 -0
- package/dist/safety/index.js.map +1 -0
- package/dist/validation/index.d.ts +5 -0
- package/dist/validation/index.d.ts.map +1 -0
- package/dist/validation/index.js +5 -0
- package/dist/validation/index.js.map +1 -0
- package/dist/validation/retrospectiveValidator.d.ts +246 -0
- package/dist/validation/retrospectiveValidator.d.ts.map +1 -0
- package/dist/validation/retrospectiveValidator.js +602 -0
- package/dist/validation/retrospectiveValidator.js.map +1 -0
- package/package.json +1 -1
- package/src/bin/cure.ts +331 -140
- package/src/clinician/decisionSupport.ts +949 -0
- package/src/patient/patientPortal.ts +1039 -0
|
@@ -0,0 +1,823 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Machine Learning Outcome Prediction Infrastructure
|
|
3
|
+
*
|
|
4
|
+
* Provides ML-based prediction models for:
|
|
5
|
+
* - Treatment response prediction
|
|
6
|
+
* - Survival probability estimation
|
|
7
|
+
* - Toxicity risk assessment
|
|
8
|
+
* - Resistance prediction
|
|
9
|
+
* - Optimal therapy selection
|
|
10
|
+
*
|
|
11
|
+
* This module provides the infrastructure for training and deploying
|
|
12
|
+
* ML models. In production, models would be trained on real patient cohorts
|
|
13
|
+
* with proper validation and FDA clearance.
|
|
14
|
+
*/
|
|
15
|
+
import { EventEmitter } from 'events';
|
|
16
|
+
import { createHash } from 'crypto';
|
|
17
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
18
|
+
// OUTCOME PREDICTOR SERVICE
|
|
19
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
20
|
+
export class OutcomePredictorService extends EventEmitter {
|
|
21
|
+
modelRegistry = new Map();
|
|
22
|
+
featureEncoders = new Map();
|
|
23
|
+
predictionCache = new Map();
|
|
24
|
+
cacheDuration = 3600000; // 1 hour
|
|
25
|
+
constructor() {
|
|
26
|
+
super();
|
|
27
|
+
this.initializeDefaultModels();
|
|
28
|
+
this.initializeFeatureEncoders();
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Predict treatment response
|
|
32
|
+
*/
|
|
33
|
+
async predictResponse(patient, treatment) {
|
|
34
|
+
const cacheKey = this.getCacheKey('response', patient, treatment);
|
|
35
|
+
const cached = this.getFromCache(cacheKey);
|
|
36
|
+
if (cached)
|
|
37
|
+
return cached;
|
|
38
|
+
// Get appropriate model
|
|
39
|
+
const model = this.getBestModel('response', patient.cancerType);
|
|
40
|
+
// Encode features
|
|
41
|
+
const features = this.encodeFeatures(patient, treatment);
|
|
42
|
+
// Make prediction (in production, this would call a trained model)
|
|
43
|
+
const prediction = this.computeResponsePrediction(features, patient, treatment, model);
|
|
44
|
+
this.setCache(cacheKey, prediction);
|
|
45
|
+
this.emit('prediction-made', { type: 'response', patient, treatment, prediction });
|
|
46
|
+
return prediction;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Predict survival outcomes
|
|
50
|
+
*/
|
|
51
|
+
async predictSurvival(patient, treatment) {
|
|
52
|
+
const cacheKey = this.getCacheKey('survival', patient, treatment);
|
|
53
|
+
const cached = this.getFromCache(cacheKey);
|
|
54
|
+
if (cached)
|
|
55
|
+
return cached;
|
|
56
|
+
const model = this.getBestModel('pfs', patient.cancerType);
|
|
57
|
+
const features = this.encodeFeatures(patient, treatment);
|
|
58
|
+
const prediction = this.computeSurvivalPrediction(features, patient, treatment, model);
|
|
59
|
+
this.setCache(cacheKey, prediction);
|
|
60
|
+
this.emit('prediction-made', { type: 'survival', patient, treatment, prediction });
|
|
61
|
+
return prediction;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Predict toxicity risk
|
|
65
|
+
*/
|
|
66
|
+
async predictToxicity(patient, treatment) {
|
|
67
|
+
const cacheKey = this.getCacheKey('toxicity', patient, treatment);
|
|
68
|
+
const cached = this.getFromCache(cacheKey);
|
|
69
|
+
if (cached)
|
|
70
|
+
return cached;
|
|
71
|
+
const model = this.getBestModel('toxicity', patient.cancerType);
|
|
72
|
+
const features = this.encodeFeatures(patient, treatment);
|
|
73
|
+
const prediction = this.computeToxicityPrediction(features, patient, treatment, model);
|
|
74
|
+
this.setCache(cacheKey, prediction);
|
|
75
|
+
this.emit('prediction-made', { type: 'toxicity', patient, treatment, prediction });
|
|
76
|
+
return prediction;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Predict resistance development
|
|
80
|
+
*/
|
|
81
|
+
async predictResistance(patient, treatment) {
|
|
82
|
+
const cacheKey = this.getCacheKey('resistance', patient, treatment);
|
|
83
|
+
const cached = this.getFromCache(cacheKey);
|
|
84
|
+
if (cached)
|
|
85
|
+
return cached;
|
|
86
|
+
const model = this.getBestModel('resistance', patient.cancerType);
|
|
87
|
+
const features = this.encodeFeatures(patient, treatment);
|
|
88
|
+
const prediction = this.computeResistancePrediction(features, patient, treatment, model);
|
|
89
|
+
this.setCache(cacheKey, prediction);
|
|
90
|
+
this.emit('prediction-made', { type: 'resistance', patient, treatment, prediction });
|
|
91
|
+
return prediction;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Rank treatment options
|
|
95
|
+
*/
|
|
96
|
+
async rankTherapies(patient, treatmentOptions, preferences) {
|
|
97
|
+
const rankings = [];
|
|
98
|
+
// Get predictions for each treatment option
|
|
99
|
+
for (const treatment of treatmentOptions) {
|
|
100
|
+
const [response, survival, toxicity] = await Promise.all([
|
|
101
|
+
this.predictResponse(patient, treatment),
|
|
102
|
+
this.predictSurvival(patient, treatment),
|
|
103
|
+
this.predictToxicity(patient, treatment)
|
|
104
|
+
]);
|
|
105
|
+
// Calculate overall score based on preferences
|
|
106
|
+
const weights = {
|
|
107
|
+
efficacy: preferences?.prioritizeEfficacy ? 0.5 : 0.35,
|
|
108
|
+
toxicity: preferences?.prioritizeToxicity ? 0.3 : 0.25,
|
|
109
|
+
qol: preferences?.prioritizeQoL ? 0.3 : 0.2,
|
|
110
|
+
survival: 0.2
|
|
111
|
+
};
|
|
112
|
+
const efficacyScore = response.objectiveResponseRate;
|
|
113
|
+
const toxicityScore = 1 - toxicity.grade3PlusRisk;
|
|
114
|
+
const survivalScore = survival.pfs.twelveMonth;
|
|
115
|
+
const qolScore = this.estimateQoLScore(toxicity);
|
|
116
|
+
const overallScore = efficacyScore * weights.efficacy +
|
|
117
|
+
toxicityScore * weights.toxicity +
|
|
118
|
+
survivalScore * weights.survival +
|
|
119
|
+
qolScore * weights.qol;
|
|
120
|
+
rankings.push({
|
|
121
|
+
regimen: treatment.regimen,
|
|
122
|
+
drugs: treatment.drugs,
|
|
123
|
+
rank: 0, // Will be set after sorting
|
|
124
|
+
predictions: {
|
|
125
|
+
responseRate: response.objectiveResponseRate,
|
|
126
|
+
pfsSurvival: survival.pfs.median,
|
|
127
|
+
osSurvival: survival.os.median,
|
|
128
|
+
toxicityRisk: toxicity.grade3PlusRisk,
|
|
129
|
+
qualityOfLifeScore: qolScore
|
|
130
|
+
},
|
|
131
|
+
overallScore,
|
|
132
|
+
confidence: (response.confidence + survival.pfs.confidence) / 2,
|
|
133
|
+
matchingBiomarkers: this.getMatchingBiomarkers(patient, treatment),
|
|
134
|
+
fdaApproved: this.checkFDAApproval(treatment, patient.cancerType),
|
|
135
|
+
nccnRecommended: this.checkNCCNRecommendation(treatment, patient.cancerType, patient.stage),
|
|
136
|
+
considerations: this.getConsiderations(patient, treatment)
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
// Sort by overall score
|
|
140
|
+
rankings.sort((a, b) => b.overallScore - a.overallScore);
|
|
141
|
+
// Assign ranks
|
|
142
|
+
rankings.forEach((r, i) => r.rank = i + 1);
|
|
143
|
+
const bestChoice = rankings[0];
|
|
144
|
+
return {
|
|
145
|
+
therapies: rankings,
|
|
146
|
+
bestChoice: {
|
|
147
|
+
regimen: bestChoice.regimen,
|
|
148
|
+
rationale: [
|
|
149
|
+
`Highest overall score (${(bestChoice.overallScore * 100).toFixed(1)}%)`,
|
|
150
|
+
`Expected response rate: ${(bestChoice.predictions.responseRate * 100).toFixed(1)}%`,
|
|
151
|
+
`Expected median PFS: ${bestChoice.predictions.pfsSurvival.toFixed(1)} months`,
|
|
152
|
+
bestChoice.fdaApproved ? 'FDA approved for this indication' : '',
|
|
153
|
+
bestChoice.nccnRecommended ? 'NCCN recommended' : '',
|
|
154
|
+
...bestChoice.matchingBiomarkers.map(b => `Matches biomarker: ${b}`)
|
|
155
|
+
].filter(Boolean)
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Get comprehensive prediction report
|
|
161
|
+
*/
|
|
162
|
+
async getComprehensivePrediction(patient, treatment) {
|
|
163
|
+
const [response, survival, toxicity, resistance] = await Promise.all([
|
|
164
|
+
this.predictResponse(patient, treatment),
|
|
165
|
+
this.predictSurvival(patient, treatment),
|
|
166
|
+
this.predictToxicity(patient, treatment),
|
|
167
|
+
this.predictResistance(patient, treatment)
|
|
168
|
+
]);
|
|
169
|
+
// Generate overall assessment
|
|
170
|
+
const overallAssessment = this.generateOverallAssessment(patient, treatment, response, survival, toxicity, resistance);
|
|
171
|
+
return {
|
|
172
|
+
response,
|
|
173
|
+
survival,
|
|
174
|
+
toxicity,
|
|
175
|
+
resistance,
|
|
176
|
+
overallAssessment
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
180
|
+
// PREDICTION COMPUTATION (Placeholder implementations)
|
|
181
|
+
// In production, these would use trained ML models
|
|
182
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
183
|
+
computeResponsePrediction(features, patient, treatment, model) {
|
|
184
|
+
// Base probabilities based on treatment type and cancer
|
|
185
|
+
let baseCR = 0.15;
|
|
186
|
+
let basePR = 0.30;
|
|
187
|
+
let baseSD = 0.30;
|
|
188
|
+
// Adjust based on biomarkers
|
|
189
|
+
if (this.hasBiomarkerMatch(patient, treatment)) {
|
|
190
|
+
baseCR += 0.15;
|
|
191
|
+
basePR += 0.15;
|
|
192
|
+
}
|
|
193
|
+
// Adjust based on stage
|
|
194
|
+
const stageModifiers = {
|
|
195
|
+
'I': 1.3, 'IA': 1.3, 'IB': 1.25,
|
|
196
|
+
'II': 1.15, 'IIA': 1.15, 'IIB': 1.1,
|
|
197
|
+
'III': 0.9, 'IIIA': 0.95, 'IIIB': 0.85, 'IIIC': 0.8,
|
|
198
|
+
'IV': 0.7, 'IVA': 0.75, 'IVB': 0.65
|
|
199
|
+
};
|
|
200
|
+
const stageMod = stageModifiers[patient.stage] || 1.0;
|
|
201
|
+
// Adjust based on ECOG
|
|
202
|
+
const ecogModifiers = [1.0, 0.9, 0.75, 0.5, 0.25];
|
|
203
|
+
const ecogMod = ecogModifiers[patient.ecogStatus];
|
|
204
|
+
// Adjust based on treatment setting
|
|
205
|
+
const settingModifiers = {
|
|
206
|
+
'first-line': 1.0,
|
|
207
|
+
'second-line': 0.75,
|
|
208
|
+
'third-line-plus': 0.5,
|
|
209
|
+
'neoadjuvant': 1.1,
|
|
210
|
+
'adjuvant': 1.05,
|
|
211
|
+
'maintenance': 0.9
|
|
212
|
+
};
|
|
213
|
+
const settingMod = settingModifiers[treatment.setting] || 1.0;
|
|
214
|
+
// Apply modifiers
|
|
215
|
+
const modifier = stageMod * ecogMod * settingMod;
|
|
216
|
+
const crProb = Math.min(baseCR * modifier, 0.6);
|
|
217
|
+
const prProb = Math.min(basePR * modifier, 0.5);
|
|
218
|
+
const sdProb = Math.min(baseSD * modifier, 0.4);
|
|
219
|
+
const pdProb = Math.max(1 - crProb - prProb - sdProb, 0.05);
|
|
220
|
+
// Normalize
|
|
221
|
+
const total = crProb + prProb + sdProb + pdProb;
|
|
222
|
+
const probabilities = {
|
|
223
|
+
completeResponse: crProb / total,
|
|
224
|
+
partialResponse: prProb / total,
|
|
225
|
+
stableDisease: sdProb / total,
|
|
226
|
+
progressiveDisease: pdProb / total
|
|
227
|
+
};
|
|
228
|
+
// Determine best response
|
|
229
|
+
const maxProb = Math.max(...Object.values(probabilities));
|
|
230
|
+
let predictedResponse = 'SD';
|
|
231
|
+
if (probabilities.completeResponse === maxProb)
|
|
232
|
+
predictedResponse = 'CR';
|
|
233
|
+
else if (probabilities.partialResponse === maxProb)
|
|
234
|
+
predictedResponse = 'PR';
|
|
235
|
+
else if (probabilities.progressiveDisease === maxProb)
|
|
236
|
+
predictedResponse = 'PD';
|
|
237
|
+
return {
|
|
238
|
+
predictedResponse,
|
|
239
|
+
probabilities,
|
|
240
|
+
confidence: model ? model.performance.auc || 0.75 : 0.70,
|
|
241
|
+
objectiveResponseRate: probabilities.completeResponse + probabilities.partialResponse,
|
|
242
|
+
diseaseControlRate: probabilities.completeResponse + probabilities.partialResponse + probabilities.stableDisease,
|
|
243
|
+
timeToResponse: {
|
|
244
|
+
median: 8,
|
|
245
|
+
range: [4, 16]
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
computeSurvivalPrediction(features, patient, treatment, model) {
|
|
250
|
+
// Base survival estimates (in months)
|
|
251
|
+
let basePFS = 12;
|
|
252
|
+
let baseOS = 24;
|
|
253
|
+
// Cancer type adjustments
|
|
254
|
+
const cancerPFSModifiers = {
|
|
255
|
+
'NSCLC': 1.0, 'SCLC': 0.5, 'Breast': 1.5, 'Colorectal': 1.0,
|
|
256
|
+
'Melanoma': 1.2, 'RCC': 1.3, 'Ovarian': 0.8, 'Pancreatic': 0.4,
|
|
257
|
+
'Glioblastoma': 0.3, 'AML': 0.6, 'Multiple Myeloma': 1.5
|
|
258
|
+
};
|
|
259
|
+
const cancerMod = cancerPFSModifiers[patient.cancerType] || 1.0;
|
|
260
|
+
basePFS *= cancerMod;
|
|
261
|
+
baseOS *= cancerMod;
|
|
262
|
+
// Stage adjustments
|
|
263
|
+
const stageModifiers = {
|
|
264
|
+
'I': 3.0, 'IA': 3.5, 'IB': 2.8,
|
|
265
|
+
'II': 2.0, 'IIA': 2.2, 'IIB': 1.8,
|
|
266
|
+
'III': 1.0, 'IIIA': 1.2, 'IIIB': 0.9, 'IIIC': 0.7,
|
|
267
|
+
'IV': 0.5, 'IVA': 0.55, 'IVB': 0.4
|
|
268
|
+
};
|
|
269
|
+
const stageMod = stageModifiers[patient.stage] || 1.0;
|
|
270
|
+
basePFS *= stageMod;
|
|
271
|
+
baseOS *= stageMod;
|
|
272
|
+
// Biomarker-driven therapy boost
|
|
273
|
+
if (this.hasBiomarkerMatch(patient, treatment)) {
|
|
274
|
+
basePFS *= 1.5;
|
|
275
|
+
baseOS *= 1.3;
|
|
276
|
+
}
|
|
277
|
+
// ECOG adjustment
|
|
278
|
+
const ecogMultipliers = [1.0, 0.85, 0.65, 0.4, 0.2];
|
|
279
|
+
basePFS *= ecogMultipliers[patient.ecogStatus];
|
|
280
|
+
baseOS *= ecogMultipliers[patient.ecogStatus];
|
|
281
|
+
// Calculate probabilities using exponential survival model
|
|
282
|
+
const lambda_pfs = 1 / basePFS;
|
|
283
|
+
const lambda_os = 1 / baseOS;
|
|
284
|
+
const pfs = {
|
|
285
|
+
median: basePFS,
|
|
286
|
+
sixMonth: Math.exp(-lambda_pfs * 6),
|
|
287
|
+
twelveMonth: Math.exp(-lambda_pfs * 12),
|
|
288
|
+
twentyFourMonth: Math.exp(-lambda_pfs * 24),
|
|
289
|
+
confidence: model ? model.performance.cIndex || 0.72 : 0.68
|
|
290
|
+
};
|
|
291
|
+
const os = {
|
|
292
|
+
median: baseOS,
|
|
293
|
+
twelveMonth: Math.exp(-lambda_os * 12),
|
|
294
|
+
twentyFourMonth: Math.exp(-lambda_os * 24),
|
|
295
|
+
fiveYear: Math.exp(-lambda_os * 60),
|
|
296
|
+
confidence: model ? model.performance.cIndex || 0.72 : 0.68
|
|
297
|
+
};
|
|
298
|
+
// Calculate risk score (0-100)
|
|
299
|
+
const riskFactors = [
|
|
300
|
+
patient.stage.startsWith('IV') ? 25 : patient.stage.startsWith('III') ? 15 : 5,
|
|
301
|
+
patient.ecogStatus >= 2 ? 20 : patient.ecogStatus === 1 ? 10 : 0,
|
|
302
|
+
(patient.priorLines || 0) >= 2 ? 15 : (patient.priorLines || 0) >= 1 ? 8 : 0,
|
|
303
|
+
patient.ldh && patient.ldh > 250 ? 10 : 0,
|
|
304
|
+
patient.metastaticSites && patient.metastaticSites.length > 2 ? 15 : 0,
|
|
305
|
+
patient.age > 75 ? 10 : patient.age > 65 ? 5 : 0
|
|
306
|
+
];
|
|
307
|
+
const riskScore = Math.min(riskFactors.reduce((a, b) => a + b, 0), 100);
|
|
308
|
+
let riskGroup;
|
|
309
|
+
if (riskScore < 25)
|
|
310
|
+
riskGroup = 'low';
|
|
311
|
+
else if (riskScore < 50)
|
|
312
|
+
riskGroup = 'intermediate-low';
|
|
313
|
+
else if (riskScore < 75)
|
|
314
|
+
riskGroup = 'intermediate-high';
|
|
315
|
+
else
|
|
316
|
+
riskGroup = 'high';
|
|
317
|
+
return { pfs, os, riskGroup, riskScore };
|
|
318
|
+
}
|
|
319
|
+
computeToxicityPrediction(features, patient, treatment, model) {
|
|
320
|
+
const specificRisks = [];
|
|
321
|
+
let grade3PlusRisk = 0;
|
|
322
|
+
// Define toxicity profiles by drug class
|
|
323
|
+
const drugToxicities = this.getDrugToxicityProfiles(treatment.drugs);
|
|
324
|
+
for (const tox of drugToxicities) {
|
|
325
|
+
// Adjust risk based on patient factors
|
|
326
|
+
let adjustedRisk = tox.baseRisk;
|
|
327
|
+
// Age adjustment
|
|
328
|
+
if (patient.age > 70)
|
|
329
|
+
adjustedRisk *= 1.2;
|
|
330
|
+
if (patient.age > 80)
|
|
331
|
+
adjustedRisk *= 1.4;
|
|
332
|
+
// Organ function adjustment
|
|
333
|
+
if (tox.affectedOrgan === 'hepatic' && patient.organFunction?.hepatic === 'impaired') {
|
|
334
|
+
adjustedRisk *= 1.5;
|
|
335
|
+
}
|
|
336
|
+
if (tox.affectedOrgan === 'renal' && patient.organFunction?.renal === 'impaired') {
|
|
337
|
+
adjustedRisk *= 1.5;
|
|
338
|
+
}
|
|
339
|
+
if (tox.affectedOrgan === 'cardiac' && patient.organFunction?.cardiac === 'impaired') {
|
|
340
|
+
adjustedRisk *= 1.5;
|
|
341
|
+
}
|
|
342
|
+
// ECOG adjustment
|
|
343
|
+
adjustedRisk *= (1 + patient.ecogStatus * 0.1);
|
|
344
|
+
adjustedRisk = Math.min(adjustedRisk, 0.95);
|
|
345
|
+
specificRisks.push({
|
|
346
|
+
toxicity: tox.name,
|
|
347
|
+
grade: tox.typicalGrade,
|
|
348
|
+
probability: adjustedRisk,
|
|
349
|
+
timeToOnset: tox.timeToOnset,
|
|
350
|
+
reversible: tox.reversible,
|
|
351
|
+
management: tox.management
|
|
352
|
+
});
|
|
353
|
+
if (tox.typicalGrade >= 3) {
|
|
354
|
+
grade3PlusRisk += adjustedRisk * 0.3; // Weighted contribution
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
grade3PlusRisk = Math.min(grade3PlusRisk, 0.9);
|
|
358
|
+
// Immunotherapy-specific irAE prediction
|
|
359
|
+
let iraeRisk;
|
|
360
|
+
if (treatment.treatmentType === 'immunotherapy' || this.hasImmunotherapyDrug(treatment.drugs)) {
|
|
361
|
+
iraeRisk = {
|
|
362
|
+
any: 0.60,
|
|
363
|
+
grade3Plus: 0.15,
|
|
364
|
+
specificOrgans: [
|
|
365
|
+
{ organ: 'skin', risk: 0.35 },
|
|
366
|
+
{ organ: 'GI', risk: 0.20 },
|
|
367
|
+
{ organ: 'endocrine', risk: 0.15 },
|
|
368
|
+
{ organ: 'hepatic', risk: 0.10 },
|
|
369
|
+
{ organ: 'pulmonary', risk: 0.08 }
|
|
370
|
+
]
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
// Dose modification recommendation
|
|
374
|
+
let doseModification;
|
|
375
|
+
if (patient.age > 75 || patient.ecogStatus >= 2 || patient.organFunction?.renal === 'impaired') {
|
|
376
|
+
doseModification = {
|
|
377
|
+
recommended: true,
|
|
378
|
+
reduction: 20,
|
|
379
|
+
reason: patient.age > 75 ? 'Advanced age' :
|
|
380
|
+
patient.ecogStatus >= 2 ? 'Poor performance status' :
|
|
381
|
+
'Impaired organ function'
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
return {
|
|
385
|
+
overallRisk: grade3PlusRisk > 0.4 ? 'high' : grade3PlusRisk > 0.2 ? 'moderate' : 'low',
|
|
386
|
+
grade3PlusRisk,
|
|
387
|
+
specificRisks,
|
|
388
|
+
iraeRisk,
|
|
389
|
+
doseModification
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
computeResistancePrediction(features, patient, treatment, model) {
|
|
393
|
+
let intrinsicRisk = 0.2;
|
|
394
|
+
let acquiredRisk = 0.6;
|
|
395
|
+
// Adjust based on prior treatments
|
|
396
|
+
if (patient.priorLines && patient.priorLines > 0) {
|
|
397
|
+
intrinsicRisk += 0.1 * patient.priorLines;
|
|
398
|
+
acquiredRisk += 0.05 * patient.priorLines;
|
|
399
|
+
}
|
|
400
|
+
// Adjust based on prior response
|
|
401
|
+
if (patient.priorResponse === 'PD') {
|
|
402
|
+
intrinsicRisk += 0.2;
|
|
403
|
+
}
|
|
404
|
+
else if (patient.priorResponse === 'CR') {
|
|
405
|
+
intrinsicRisk -= 0.1;
|
|
406
|
+
}
|
|
407
|
+
// Get resistance mechanisms based on treatment and mutations
|
|
408
|
+
const mechanisms = this.getResistanceMechanisms(patient, treatment);
|
|
409
|
+
// Calculate time to resistance
|
|
410
|
+
const baseTimeToResistance = this.hasBiomarkerMatch(patient, treatment) ? 18 : 9;
|
|
411
|
+
const timeModifier = patient.priorLines ? 1 - (patient.priorLines * 0.15) : 1;
|
|
412
|
+
// Get next line options
|
|
413
|
+
const nextLineOptions = this.getNextLineOptions(patient, treatment, mechanisms);
|
|
414
|
+
return {
|
|
415
|
+
intrinsicResistanceRisk: Math.min(intrinsicRisk, 0.9),
|
|
416
|
+
acquiredResistanceRisk: Math.min(acquiredRisk, 0.95),
|
|
417
|
+
predictedMechanisms: mechanisms,
|
|
418
|
+
timeToResistance: {
|
|
419
|
+
median: baseTimeToResistance * timeModifier,
|
|
420
|
+
range: [baseTimeToResistance * timeModifier * 0.5, baseTimeToResistance * timeModifier * 2]
|
|
421
|
+
},
|
|
422
|
+
nextLineOptions
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
426
|
+
// HELPER METHODS
|
|
427
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
428
|
+
initializeDefaultModels() {
|
|
429
|
+
// Register placeholder models
|
|
430
|
+
const defaultModels = [
|
|
431
|
+
{
|
|
432
|
+
id: 'response-nsclc-v1',
|
|
433
|
+
name: 'NSCLC Response Predictor',
|
|
434
|
+
version: '1.0',
|
|
435
|
+
type: 'classification',
|
|
436
|
+
target: 'response',
|
|
437
|
+
cancerTypes: ['NSCLC'],
|
|
438
|
+
performance: { auc: 0.78, accuracy: 0.72 },
|
|
439
|
+
validation: { method: 'cross-validation', cohortSize: 1500, testSetSize: 300, validationDate: new Date() },
|
|
440
|
+
features: ['age', 'stage', 'ecog', 'pdl1', 'tmb', 'egfr', 'alk', 'kras'],
|
|
441
|
+
importantFeatures: [
|
|
442
|
+
{ feature: 'pdl1', importance: 0.25 },
|
|
443
|
+
{ feature: 'tmb', importance: 0.20 },
|
|
444
|
+
{ feature: 'egfr', importance: 0.18 }
|
|
445
|
+
],
|
|
446
|
+
status: 'validation'
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
id: 'survival-pan-cancer-v1',
|
|
450
|
+
name: 'Pan-Cancer Survival Model',
|
|
451
|
+
version: '1.0',
|
|
452
|
+
type: 'survival',
|
|
453
|
+
target: 'os',
|
|
454
|
+
cancerTypes: ['all'],
|
|
455
|
+
performance: { cIndex: 0.72 },
|
|
456
|
+
validation: { method: 'cross-validation', cohortSize: 10000, testSetSize: 2000, validationDate: new Date() },
|
|
457
|
+
features: ['age', 'stage', 'ecog', 'cancerType', 'priorLines', 'ldh'],
|
|
458
|
+
importantFeatures: [
|
|
459
|
+
{ feature: 'stage', importance: 0.30 },
|
|
460
|
+
{ feature: 'ecog', importance: 0.25 },
|
|
461
|
+
{ feature: 'priorLines', importance: 0.15 }
|
|
462
|
+
],
|
|
463
|
+
status: 'validation'
|
|
464
|
+
}
|
|
465
|
+
];
|
|
466
|
+
for (const model of defaultModels) {
|
|
467
|
+
this.modelRegistry.set(model.id, model);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
initializeFeatureEncoders() {
|
|
471
|
+
// Initialize encoders for categorical variables
|
|
472
|
+
this.featureEncoders.set('stage', new FeatureEncoder({
|
|
473
|
+
'I': 1, 'IA': 1, 'IB': 1.5,
|
|
474
|
+
'II': 2, 'IIA': 2, 'IIB': 2.5,
|
|
475
|
+
'III': 3, 'IIIA': 3, 'IIIB': 3.5, 'IIIC': 3.8,
|
|
476
|
+
'IV': 4, 'IVA': 4, 'IVB': 4.5
|
|
477
|
+
}));
|
|
478
|
+
this.featureEncoders.set('cancerType', new FeatureEncoder({
|
|
479
|
+
'NSCLC': 1, 'SCLC': 2, 'Breast': 3, 'Colorectal': 4,
|
|
480
|
+
'Melanoma': 5, 'RCC': 6, 'Ovarian': 7, 'Pancreatic': 8
|
|
481
|
+
}));
|
|
482
|
+
}
|
|
483
|
+
encodeFeatures(patient, treatment) {
|
|
484
|
+
const features = [];
|
|
485
|
+
// Numeric features (normalized)
|
|
486
|
+
features.push(patient.age / 100);
|
|
487
|
+
features.push(patient.ecogStatus / 4);
|
|
488
|
+
features.push(patient.gender === 'male' ? 1 : 0);
|
|
489
|
+
// Encoded categorical features
|
|
490
|
+
const stageEncoder = this.featureEncoders.get('stage');
|
|
491
|
+
features.push((stageEncoder?.encode(patient.stage) || 2) / 5);
|
|
492
|
+
// Biomarker features
|
|
493
|
+
features.push((patient.pdl1Score || 0) / 100);
|
|
494
|
+
features.push((patient.tmbValue || 0) / 50);
|
|
495
|
+
features.push(patient.msiStatus === 'MSI-H' ? 1 : 0);
|
|
496
|
+
features.push(patient.hrdStatus === 'positive' ? 1 : 0);
|
|
497
|
+
// Prior treatment features
|
|
498
|
+
features.push((patient.priorLines || 0) / 5);
|
|
499
|
+
// Treatment features
|
|
500
|
+
features.push(treatment.treatmentType === 'immunotherapy' ? 1 : 0);
|
|
501
|
+
features.push(treatment.treatmentType === 'targeted' ? 1 : 0);
|
|
502
|
+
return features;
|
|
503
|
+
}
|
|
504
|
+
getBestModel(target, cancerType) {
|
|
505
|
+
const models = Array.from(this.modelRegistry.values())
|
|
506
|
+
.filter(m => m.target === target &&
|
|
507
|
+
(m.cancerTypes.includes(cancerType) || m.cancerTypes.includes('all')) &&
|
|
508
|
+
m.status !== 'deprecated')
|
|
509
|
+
.sort((a, b) => (b.performance.auc || b.performance.cIndex || 0) - (a.performance.auc || a.performance.cIndex || 0));
|
|
510
|
+
return models[0];
|
|
511
|
+
}
|
|
512
|
+
hasBiomarkerMatch(patient, treatment) {
|
|
513
|
+
// Check for biomarker-drug matches
|
|
514
|
+
const matches = [
|
|
515
|
+
{ biomarker: 'EGFR', drugs: ['osimertinib', 'erlotinib', 'gefitinib', 'afatinib'] },
|
|
516
|
+
{ biomarker: 'ALK', drugs: ['alectinib', 'crizotinib', 'brigatinib', 'lorlatinib'] },
|
|
517
|
+
{ biomarker: 'BRAF V600', drugs: ['dabrafenib', 'vemurafenib', 'encorafenib'] },
|
|
518
|
+
{ biomarker: 'HER2', drugs: ['trastuzumab', 'pertuzumab', 't-dxd'] },
|
|
519
|
+
{ biomarker: 'BRCA', drugs: ['olaparib', 'rucaparib', 'niraparib', 'talazoparib'] },
|
|
520
|
+
{ biomarker: 'KRAS G12C', drugs: ['sotorasib', 'adagrasib'] }
|
|
521
|
+
];
|
|
522
|
+
for (const match of matches) {
|
|
523
|
+
const hasBiomarker = patient.genomicAlterations.some(g => g.gene.toUpperCase().includes(match.biomarker) ||
|
|
524
|
+
g.alteration.toUpperCase().includes(match.biomarker));
|
|
525
|
+
const hasDrug = treatment.drugs.some(d => match.drugs.some(md => d.toLowerCase().includes(md)));
|
|
526
|
+
if (hasBiomarker && hasDrug)
|
|
527
|
+
return true;
|
|
528
|
+
}
|
|
529
|
+
// Check immunotherapy eligibility
|
|
530
|
+
if (treatment.treatmentType === 'immunotherapy' || this.hasImmunotherapyDrug(treatment.drugs)) {
|
|
531
|
+
if (patient.msiStatus === 'MSI-H')
|
|
532
|
+
return true;
|
|
533
|
+
if (patient.tmbStatus === 'high')
|
|
534
|
+
return true;
|
|
535
|
+
if (patient.pdl1Score && patient.pdl1Score >= 50)
|
|
536
|
+
return true;
|
|
537
|
+
}
|
|
538
|
+
return false;
|
|
539
|
+
}
|
|
540
|
+
hasImmunotherapyDrug(drugs) {
|
|
541
|
+
const immunoDrugs = ['pembrolizumab', 'nivolumab', 'ipilimumab', 'atezolizumab', 'durvalumab', 'avelumab'];
|
|
542
|
+
return drugs.some(d => immunoDrugs.some(id => d.toLowerCase().includes(id)));
|
|
543
|
+
}
|
|
544
|
+
getDrugToxicityProfiles(drugs) {
|
|
545
|
+
const profiles = [];
|
|
546
|
+
// Check for common drug classes and their toxicities
|
|
547
|
+
for (const drug of drugs) {
|
|
548
|
+
const lower = drug.toLowerCase();
|
|
549
|
+
// Checkpoint inhibitors
|
|
550
|
+
if (['pembrolizumab', 'nivolumab', 'ipilimumab', 'atezolizumab'].some(d => lower.includes(d))) {
|
|
551
|
+
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 });
|
|
552
|
+
}
|
|
553
|
+
// Platinum agents
|
|
554
|
+
if (['carboplatin', 'cisplatin', 'oxaliplatin'].some(d => lower.includes(d))) {
|
|
555
|
+
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 });
|
|
556
|
+
}
|
|
557
|
+
// EGFR TKIs
|
|
558
|
+
if (['osimertinib', 'erlotinib', 'gefitinib', 'afatinib'].some(d => lower.includes(d))) {
|
|
559
|
+
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 });
|
|
560
|
+
}
|
|
561
|
+
// CDK4/6 inhibitors
|
|
562
|
+
if (['palbociclib', 'ribociclib', 'abemaciclib'].some(d => lower.includes(d))) {
|
|
563
|
+
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 });
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
return profiles;
|
|
567
|
+
}
|
|
568
|
+
getResistanceMechanisms(patient, treatment) {
|
|
569
|
+
const mechanisms = [];
|
|
570
|
+
// EGFR TKI resistance mechanisms
|
|
571
|
+
if (treatment.drugs.some(d => ['osimertinib', 'erlotinib', 'gefitinib'].some(e => d.toLowerCase().includes(e)))) {
|
|
572
|
+
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' });
|
|
573
|
+
}
|
|
574
|
+
// Immunotherapy resistance
|
|
575
|
+
if (this.hasImmunotherapyDrug(treatment.drugs)) {
|
|
576
|
+
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 });
|
|
577
|
+
}
|
|
578
|
+
// BRAF inhibitor resistance
|
|
579
|
+
if (treatment.drugs.some(d => ['dabrafenib', 'vemurafenib', 'encorafenib'].some(b => d.toLowerCase().includes(b)))) {
|
|
580
|
+
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' });
|
|
581
|
+
}
|
|
582
|
+
return mechanisms;
|
|
583
|
+
}
|
|
584
|
+
getNextLineOptions(patient, treatment, mechanisms) {
|
|
585
|
+
const options = [];
|
|
586
|
+
// Based on predicted resistance mechanisms
|
|
587
|
+
for (const mech of mechanisms.slice(0, 3)) {
|
|
588
|
+
if (mech.mechanism === 'MET amplification') {
|
|
589
|
+
options.push({
|
|
590
|
+
therapy: 'Osimertinib + Savolitinib',
|
|
591
|
+
rationale: 'Targets MET bypass pathway',
|
|
592
|
+
expectedBenefit: 8
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
if (mech.mechanism === 'EGFR C797S mutation') {
|
|
596
|
+
options.push({
|
|
597
|
+
therapy: 'First-generation EGFR TKI + Third-generation EGFR TKI',
|
|
598
|
+
rationale: 'C797S may restore sensitivity to 1st-gen TKI',
|
|
599
|
+
expectedBenefit: 6
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
if (mech.mechanism === 'Histologic transformation (SCLC)') {
|
|
603
|
+
options.push({
|
|
604
|
+
therapy: 'Platinum-etoposide chemotherapy',
|
|
605
|
+
rationale: 'Standard SCLC treatment',
|
|
606
|
+
expectedBenefit: 4
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
// Add general next-line options
|
|
611
|
+
if (treatment.treatmentType !== 'immunotherapy' && !this.hasImmunotherapyDrug(treatment.drugs)) {
|
|
612
|
+
options.push({
|
|
613
|
+
therapy: 'Immunotherapy (if PD-L1+/TMB-H)',
|
|
614
|
+
rationale: 'Different mechanism of action',
|
|
615
|
+
expectedBenefit: 10
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
// Clinical trial option
|
|
619
|
+
options.push({
|
|
620
|
+
therapy: 'Clinical trial enrollment',
|
|
621
|
+
rationale: 'Access to novel agents',
|
|
622
|
+
expectedBenefit: 8
|
|
623
|
+
});
|
|
624
|
+
return options;
|
|
625
|
+
}
|
|
626
|
+
estimateQoLScore(toxicity) {
|
|
627
|
+
let score = 0.85; // Base quality of life
|
|
628
|
+
// Reduce based on toxicity severity
|
|
629
|
+
score -= toxicity.grade3PlusRisk * 0.3;
|
|
630
|
+
// Specific high-impact toxicities
|
|
631
|
+
for (const tox of toxicity.specificRisks) {
|
|
632
|
+
if (tox.toxicity.toLowerCase().includes('neuropathy') && tox.grade >= 2) {
|
|
633
|
+
score -= 0.1;
|
|
634
|
+
}
|
|
635
|
+
if (tox.toxicity.toLowerCase().includes('fatigue') && tox.grade >= 2) {
|
|
636
|
+
score -= 0.08;
|
|
637
|
+
}
|
|
638
|
+
if (tox.toxicity.toLowerCase().includes('nausea') && tox.grade >= 2) {
|
|
639
|
+
score -= 0.05;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
return Math.max(score, 0.3);
|
|
643
|
+
}
|
|
644
|
+
getMatchingBiomarkers(patient, treatment) {
|
|
645
|
+
const matches = [];
|
|
646
|
+
for (const alt of patient.genomicAlterations) {
|
|
647
|
+
if (this.isBiomarkerRelevant(alt.gene, alt.alteration, treatment)) {
|
|
648
|
+
matches.push(`${alt.gene} ${alt.alteration}`);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
if (patient.msiStatus === 'MSI-H' && this.hasImmunotherapyDrug(treatment.drugs)) {
|
|
652
|
+
matches.push('MSI-H');
|
|
653
|
+
}
|
|
654
|
+
if (patient.tmbStatus === 'high' && this.hasImmunotherapyDrug(treatment.drugs)) {
|
|
655
|
+
matches.push('TMB-High');
|
|
656
|
+
}
|
|
657
|
+
if (patient.hrdStatus === 'positive' && treatment.drugs.some(d => ['olaparib', 'rucaparib', 'niraparib', 'talazoparib'].some(p => d.toLowerCase().includes(p)))) {
|
|
658
|
+
matches.push('HRD-positive');
|
|
659
|
+
}
|
|
660
|
+
return matches;
|
|
661
|
+
}
|
|
662
|
+
isBiomarkerRelevant(gene, alteration, treatment) {
|
|
663
|
+
const relevantPairs = {
|
|
664
|
+
'EGFR': ['osimertinib', 'erlotinib', 'gefitinib', 'afatinib'],
|
|
665
|
+
'ALK': ['alectinib', 'crizotinib', 'brigatinib', 'lorlatinib'],
|
|
666
|
+
'ROS1': ['crizotinib', 'entrectinib'],
|
|
667
|
+
'BRAF': ['dabrafenib', 'vemurafenib', 'encorafenib'],
|
|
668
|
+
'HER2': ['trastuzumab', 'pertuzumab', 't-dxd'],
|
|
669
|
+
'BRCA': ['olaparib', 'rucaparib', 'niraparib', 'talazoparib'],
|
|
670
|
+
'KRAS': ['sotorasib', 'adagrasib'],
|
|
671
|
+
'NTRK': ['larotrectinib', 'entrectinib'],
|
|
672
|
+
'RET': ['selpercatinib', 'pralsetinib'],
|
|
673
|
+
'MET': ['capmatinib', 'tepotinib']
|
|
674
|
+
};
|
|
675
|
+
const drugs = relevantPairs[gene.toUpperCase()];
|
|
676
|
+
if (!drugs)
|
|
677
|
+
return false;
|
|
678
|
+
return treatment.drugs.some(d => drugs.some(rd => d.toLowerCase().includes(rd)));
|
|
679
|
+
}
|
|
680
|
+
checkFDAApproval(treatment, cancerType) {
|
|
681
|
+
// Simplified FDA approval check - would reference actual database in production
|
|
682
|
+
const approvedCombinations = {
|
|
683
|
+
'NSCLC': ['osimertinib', 'pembrolizumab', 'alectinib', 'sotorasib'],
|
|
684
|
+
'Breast': ['palbociclib', 'trastuzumab', 'olaparib', 'sacituzumab'],
|
|
685
|
+
'Melanoma': ['pembrolizumab', 'nivolumab', 'ipilimumab', 'dabrafenib'],
|
|
686
|
+
'Colorectal': ['pembrolizumab', 'cetuximab', 'bevacizumab'],
|
|
687
|
+
'RCC': ['pembrolizumab', 'nivolumab', 'cabozantinib']
|
|
688
|
+
};
|
|
689
|
+
const approvedDrugs = approvedCombinations[cancerType] || [];
|
|
690
|
+
return treatment.drugs.some(d => approvedDrugs.some(ad => d.toLowerCase().includes(ad)));
|
|
691
|
+
}
|
|
692
|
+
checkNCCNRecommendation(treatment, cancerType, stage) {
|
|
693
|
+
// Simplified NCCN check - would reference actual guidelines in production
|
|
694
|
+
return this.checkFDAApproval(treatment, cancerType);
|
|
695
|
+
}
|
|
696
|
+
getConsiderations(patient, treatment) {
|
|
697
|
+
const considerations = [];
|
|
698
|
+
if (patient.age > 75) {
|
|
699
|
+
considerations.push('Consider dose reduction for elderly patient');
|
|
700
|
+
}
|
|
701
|
+
if (patient.ecogStatus >= 2) {
|
|
702
|
+
considerations.push('Poor performance status may limit tolerability');
|
|
703
|
+
}
|
|
704
|
+
if (patient.organFunction?.renal === 'impaired') {
|
|
705
|
+
considerations.push('Dose adjustment may be needed for renal impairment');
|
|
706
|
+
}
|
|
707
|
+
if (patient.priorLines && patient.priorLines >= 2) {
|
|
708
|
+
considerations.push('Heavily pretreated - consider clinical trial');
|
|
709
|
+
}
|
|
710
|
+
return considerations;
|
|
711
|
+
}
|
|
712
|
+
generateOverallAssessment(patient, treatment, response, survival, toxicity, resistance) {
|
|
713
|
+
const rationale = [];
|
|
714
|
+
const caveats = [];
|
|
715
|
+
const alternativeOptions = [];
|
|
716
|
+
// Calculate recommendation score
|
|
717
|
+
let score = 50;
|
|
718
|
+
// Response contribution
|
|
719
|
+
if (response.objectiveResponseRate >= 0.6) {
|
|
720
|
+
score += 20;
|
|
721
|
+
rationale.push('High expected response rate');
|
|
722
|
+
}
|
|
723
|
+
else if (response.objectiveResponseRate >= 0.4) {
|
|
724
|
+
score += 10;
|
|
725
|
+
}
|
|
726
|
+
else if (response.objectiveResponseRate < 0.2) {
|
|
727
|
+
score -= 20;
|
|
728
|
+
caveats.push('Low expected response rate');
|
|
729
|
+
}
|
|
730
|
+
// Survival contribution
|
|
731
|
+
if (survival.pfs.twelveMonth >= 0.6) {
|
|
732
|
+
score += 15;
|
|
733
|
+
rationale.push('Favorable survival outlook');
|
|
734
|
+
}
|
|
735
|
+
else if (survival.pfs.twelveMonth < 0.3) {
|
|
736
|
+
score -= 15;
|
|
737
|
+
caveats.push('Limited expected duration of benefit');
|
|
738
|
+
}
|
|
739
|
+
// Toxicity contribution
|
|
740
|
+
if (toxicity.grade3PlusRisk < 0.2) {
|
|
741
|
+
score += 10;
|
|
742
|
+
rationale.push('Favorable toxicity profile');
|
|
743
|
+
}
|
|
744
|
+
else if (toxicity.grade3PlusRisk > 0.5) {
|
|
745
|
+
score -= 20;
|
|
746
|
+
caveats.push('High risk of severe toxicity');
|
|
747
|
+
}
|
|
748
|
+
// Biomarker match
|
|
749
|
+
if (this.hasBiomarkerMatch(patient, treatment)) {
|
|
750
|
+
score += 15;
|
|
751
|
+
rationale.push('Biomarker-matched therapy');
|
|
752
|
+
}
|
|
753
|
+
// FDA approval and guidelines
|
|
754
|
+
if (this.checkFDAApproval(treatment, patient.cancerType)) {
|
|
755
|
+
score += 10;
|
|
756
|
+
rationale.push('FDA approved for indication');
|
|
757
|
+
}
|
|
758
|
+
// Add alternatives
|
|
759
|
+
if (resistance.nextLineOptions.length > 0) {
|
|
760
|
+
alternativeOptions.push(...resistance.nextLineOptions.slice(0, 2).map(o => o.therapy));
|
|
761
|
+
}
|
|
762
|
+
// Determine recommendation
|
|
763
|
+
let recommendation;
|
|
764
|
+
if (score >= 80)
|
|
765
|
+
recommendation = 'strongly-recommended';
|
|
766
|
+
else if (score >= 60)
|
|
767
|
+
recommendation = 'recommended';
|
|
768
|
+
else if (score >= 40)
|
|
769
|
+
recommendation = 'consider';
|
|
770
|
+
else if (score >= 20)
|
|
771
|
+
recommendation = 'caution';
|
|
772
|
+
else
|
|
773
|
+
recommendation = 'not-recommended';
|
|
774
|
+
return { recommendation, rationale, caveats, alternativeOptions };
|
|
775
|
+
}
|
|
776
|
+
getCacheKey(type, patient, treatment) {
|
|
777
|
+
const data = JSON.stringify({ type, patient, treatment });
|
|
778
|
+
return createHash('sha256').update(data).digest('hex').substring(0, 16);
|
|
779
|
+
}
|
|
780
|
+
getFromCache(key) {
|
|
781
|
+
const cached = this.predictionCache.get(key);
|
|
782
|
+
if (cached && Date.now() - cached.timestamp.getTime() < this.cacheDuration) {
|
|
783
|
+
return cached.prediction;
|
|
784
|
+
}
|
|
785
|
+
return null;
|
|
786
|
+
}
|
|
787
|
+
setCache(key, prediction) {
|
|
788
|
+
this.predictionCache.set(key, { prediction, timestamp: new Date() });
|
|
789
|
+
}
|
|
790
|
+
/**
|
|
791
|
+
* Register a new model
|
|
792
|
+
*/
|
|
793
|
+
registerModel(model) {
|
|
794
|
+
this.modelRegistry.set(model.id, model);
|
|
795
|
+
this.emit('model-registered', model);
|
|
796
|
+
}
|
|
797
|
+
/**
|
|
798
|
+
* Get model performance metrics
|
|
799
|
+
*/
|
|
800
|
+
getModelMetrics(modelId) {
|
|
801
|
+
return this.modelRegistry.get(modelId)?.performance;
|
|
802
|
+
}
|
|
803
|
+
/**
|
|
804
|
+
* List all available models
|
|
805
|
+
*/
|
|
806
|
+
listModels() {
|
|
807
|
+
return Array.from(this.modelRegistry.values());
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
811
|
+
// FEATURE ENCODER
|
|
812
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
813
|
+
class FeatureEncoder {
|
|
814
|
+
mapping;
|
|
815
|
+
constructor(mapping) {
|
|
816
|
+
this.mapping = mapping;
|
|
817
|
+
}
|
|
818
|
+
encode(value) {
|
|
819
|
+
return this.mapping[value] ?? 0;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
export default OutcomePredictorService;
|
|
823
|
+
//# sourceMappingURL=outcomePredictor.js.map
|