@erosolaraijs/cure 1.0.0
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/LICENSE +21 -0
- package/README.md +120 -0
- package/dist/bin/cure.d.ts +10 -0
- package/dist/bin/cure.d.ts.map +1 -0
- package/dist/bin/cure.js +169 -0
- package/dist/bin/cure.js.map +1 -0
- package/dist/capabilities/cancerTreatmentCapability.d.ts +167 -0
- package/dist/capabilities/cancerTreatmentCapability.d.ts.map +1 -0
- package/dist/capabilities/cancerTreatmentCapability.js +912 -0
- package/dist/capabilities/cancerTreatmentCapability.js.map +1 -0
- package/dist/capabilities/index.d.ts +2 -0
- package/dist/capabilities/index.d.ts.map +1 -0
- package/dist/capabilities/index.js +3 -0
- package/dist/capabilities/index.js.map +1 -0
- package/dist/compliance/hipaa.d.ts +337 -0
- package/dist/compliance/hipaa.d.ts.map +1 -0
- package/dist/compliance/hipaa.js +929 -0
- package/dist/compliance/hipaa.js.map +1 -0
- package/dist/examples/cancerTreatmentDemo.d.ts +21 -0
- package/dist/examples/cancerTreatmentDemo.d.ts.map +1 -0
- package/dist/examples/cancerTreatmentDemo.js +216 -0
- package/dist/examples/cancerTreatmentDemo.js.map +1 -0
- package/dist/integrations/clinicalTrials/clinicalTrialsGov.d.ts +265 -0
- package/dist/integrations/clinicalTrials/clinicalTrialsGov.d.ts.map +1 -0
- package/dist/integrations/clinicalTrials/clinicalTrialsGov.js +808 -0
- package/dist/integrations/clinicalTrials/clinicalTrialsGov.js.map +1 -0
- package/dist/integrations/ehr/fhir.d.ts +455 -0
- package/dist/integrations/ehr/fhir.d.ts.map +1 -0
- package/dist/integrations/ehr/fhir.js +859 -0
- package/dist/integrations/ehr/fhir.js.map +1 -0
- package/dist/integrations/genomics/genomicPlatforms.d.ts +362 -0
- package/dist/integrations/genomics/genomicPlatforms.d.ts.map +1 -0
- package/dist/integrations/genomics/genomicPlatforms.js +1079 -0
- package/dist/integrations/genomics/genomicPlatforms.js.map +1 -0
- package/package.json +52 -0
- package/src/bin/cure.ts +182 -0
- package/src/capabilities/cancerTreatmentCapability.ts +1161 -0
- package/src/capabilities/index.ts +2 -0
- package/src/compliance/hipaa.ts +1365 -0
- package/src/examples/cancerTreatmentDemo.ts +241 -0
- package/src/integrations/clinicalTrials/clinicalTrialsGov.ts +1143 -0
- package/src/integrations/ehr/fhir.ts +1304 -0
- package/src/integrations/genomics/genomicPlatforms.ts +1480 -0
- package/src/ml/outcomePredictor.ts +1301 -0
- package/src/safety/drugInteractions.ts +942 -0
- package/src/validation/retrospectiveValidator.ts +887 -0
|
@@ -0,0 +1,1301 @@
|
|
|
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
|
+
|
|
16
|
+
import { EventEmitter } from 'events';
|
|
17
|
+
import { createHash } from 'crypto';
|
|
18
|
+
|
|
19
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
20
|
+
// FEATURE TYPES
|
|
21
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
22
|
+
|
|
23
|
+
export interface PatientFeatures {
|
|
24
|
+
// Demographics
|
|
25
|
+
age: number;
|
|
26
|
+
gender: 'male' | 'female';
|
|
27
|
+
ethnicity?: string;
|
|
28
|
+
bmi?: number;
|
|
29
|
+
|
|
30
|
+
// Disease characteristics
|
|
31
|
+
cancerType: string;
|
|
32
|
+
histology?: string;
|
|
33
|
+
stage: 'I' | 'II' | 'III' | 'IV' | 'IA' | 'IB' | 'IIA' | 'IIB' | 'IIIA' | 'IIIB' | 'IIIC' | 'IVA' | 'IVB';
|
|
34
|
+
grade?: 1 | 2 | 3;
|
|
35
|
+
tumorSize?: number; // cm
|
|
36
|
+
lymphNodeInvolvement?: number;
|
|
37
|
+
metastaticSites?: string[];
|
|
38
|
+
|
|
39
|
+
// Performance status
|
|
40
|
+
ecogStatus: 0 | 1 | 2 | 3 | 4;
|
|
41
|
+
|
|
42
|
+
// Biomarkers
|
|
43
|
+
genomicAlterations: {
|
|
44
|
+
gene: string;
|
|
45
|
+
alteration: string;
|
|
46
|
+
type: 'mutation' | 'amplification' | 'deletion' | 'fusion';
|
|
47
|
+
vaf?: number;
|
|
48
|
+
}[];
|
|
49
|
+
|
|
50
|
+
// Immunotherapy markers
|
|
51
|
+
pdl1Score?: number;
|
|
52
|
+
pdl1ScoreType?: 'TPS' | 'CPS' | 'IC';
|
|
53
|
+
msiStatus?: 'MSI-H' | 'MSI-L' | 'MSS';
|
|
54
|
+
tmbValue?: number;
|
|
55
|
+
tmbStatus?: 'high' | 'low';
|
|
56
|
+
|
|
57
|
+
// HRD status
|
|
58
|
+
hrdScore?: number;
|
|
59
|
+
hrdStatus?: 'positive' | 'negative';
|
|
60
|
+
brcaStatus?: 'BRCA1' | 'BRCA2' | 'wild-type';
|
|
61
|
+
|
|
62
|
+
// Lab values
|
|
63
|
+
ldh?: number;
|
|
64
|
+
albumin?: number;
|
|
65
|
+
hemoglobin?: number;
|
|
66
|
+
neutrophils?: number;
|
|
67
|
+
lymphocytes?: number;
|
|
68
|
+
platelets?: number;
|
|
69
|
+
creatinine?: number;
|
|
70
|
+
bilirubin?: number;
|
|
71
|
+
alkalinePhosphatase?: number;
|
|
72
|
+
|
|
73
|
+
// Prior treatments
|
|
74
|
+
priorLines?: number;
|
|
75
|
+
priorTherapies?: string[];
|
|
76
|
+
priorResponse?: 'CR' | 'PR' | 'SD' | 'PD';
|
|
77
|
+
treatmentFreeInterval?: number; // months
|
|
78
|
+
|
|
79
|
+
// Comorbidities
|
|
80
|
+
comorbidityIndex?: number;
|
|
81
|
+
organFunction?: {
|
|
82
|
+
cardiac?: 'normal' | 'impaired';
|
|
83
|
+
hepatic?: 'normal' | 'impaired';
|
|
84
|
+
renal?: 'normal' | 'impaired';
|
|
85
|
+
pulmonary?: 'normal' | 'impaired';
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface TreatmentFeatures {
|
|
90
|
+
regimen: string;
|
|
91
|
+
drugs: string[];
|
|
92
|
+
treatmentType: 'chemotherapy' | 'immunotherapy' | 'targeted' | 'combination' | 'radiation' | 'surgery' | 'car-t';
|
|
93
|
+
setting: 'neoadjuvant' | 'adjuvant' | 'first-line' | 'second-line' | 'third-line-plus' | 'maintenance';
|
|
94
|
+
dosing?: 'standard' | 'reduced' | 'dose-dense';
|
|
95
|
+
schedule?: string;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
99
|
+
// PREDICTION TYPES
|
|
100
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
101
|
+
|
|
102
|
+
export interface ResponsePrediction {
|
|
103
|
+
predictedResponse: 'CR' | 'PR' | 'SD' | 'PD';
|
|
104
|
+
probabilities: {
|
|
105
|
+
completeResponse: number;
|
|
106
|
+
partialResponse: number;
|
|
107
|
+
stableDisease: number;
|
|
108
|
+
progressiveDisease: number;
|
|
109
|
+
};
|
|
110
|
+
confidence: number;
|
|
111
|
+
objectiveResponseRate: number; // CR + PR probability
|
|
112
|
+
diseaseControlRate: number; // CR + PR + SD probability
|
|
113
|
+
timeToResponse?: {
|
|
114
|
+
median: number; // weeks
|
|
115
|
+
range: [number, number];
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface SurvivalPrediction {
|
|
120
|
+
// Progression-Free Survival
|
|
121
|
+
pfs: {
|
|
122
|
+
median: number; // months
|
|
123
|
+
sixMonth: number; // probability
|
|
124
|
+
twelveMonth: number;
|
|
125
|
+
twentyFourMonth: number;
|
|
126
|
+
confidence: number;
|
|
127
|
+
hazardRatio?: number;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// Overall Survival
|
|
131
|
+
os: {
|
|
132
|
+
median: number;
|
|
133
|
+
twelveMonth: number;
|
|
134
|
+
twentyFourMonth: number;
|
|
135
|
+
fiveYear: number;
|
|
136
|
+
confidence: number;
|
|
137
|
+
hazardRatio?: number;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// Risk stratification
|
|
141
|
+
riskGroup: 'low' | 'intermediate-low' | 'intermediate-high' | 'high';
|
|
142
|
+
riskScore: number; // 0-100
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export interface ToxicityPrediction {
|
|
146
|
+
overallRisk: 'low' | 'moderate' | 'high';
|
|
147
|
+
grade3PlusRisk: number; // Probability of Grade 3+ toxicity
|
|
148
|
+
|
|
149
|
+
specificRisks: {
|
|
150
|
+
toxicity: string;
|
|
151
|
+
grade: 1 | 2 | 3 | 4 | 5;
|
|
152
|
+
probability: number;
|
|
153
|
+
timeToOnset?: { median: number; unit: 'days' | 'weeks' | 'cycles' };
|
|
154
|
+
reversible: boolean;
|
|
155
|
+
management?: string;
|
|
156
|
+
}[];
|
|
157
|
+
|
|
158
|
+
// Immune-related adverse events (for immunotherapy)
|
|
159
|
+
iraeRisk?: {
|
|
160
|
+
any: number;
|
|
161
|
+
grade3Plus: number;
|
|
162
|
+
specificOrgans: { organ: string; risk: number }[];
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// Dose modification recommendation
|
|
166
|
+
doseModification?: {
|
|
167
|
+
recommended: boolean;
|
|
168
|
+
reduction: number; // percentage
|
|
169
|
+
reason: string;
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export interface ResistancePrediction {
|
|
174
|
+
intrinsicResistanceRisk: number; // Probability of primary resistance
|
|
175
|
+
acquiredResistanceRisk: number; // Probability of developing resistance
|
|
176
|
+
|
|
177
|
+
predictedMechanisms: {
|
|
178
|
+
mechanism: string;
|
|
179
|
+
probability: number;
|
|
180
|
+
monitoringBiomarker?: string;
|
|
181
|
+
}[];
|
|
182
|
+
|
|
183
|
+
timeToResistance?: {
|
|
184
|
+
median: number; // months
|
|
185
|
+
range: [number, number];
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
nextLineOptions: {
|
|
189
|
+
therapy: string;
|
|
190
|
+
rationale: string;
|
|
191
|
+
expectedBenefit: number; // expected months of benefit
|
|
192
|
+
}[];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export interface TherapyRanking {
|
|
196
|
+
therapies: {
|
|
197
|
+
regimen: string;
|
|
198
|
+
drugs: string[];
|
|
199
|
+
rank: number;
|
|
200
|
+
|
|
201
|
+
predictions: {
|
|
202
|
+
responseRate: number;
|
|
203
|
+
pfsSurvival: number;
|
|
204
|
+
osSurvival: number;
|
|
205
|
+
toxicityRisk: number;
|
|
206
|
+
qualityOfLifeScore: number;
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
overallScore: number;
|
|
210
|
+
confidence: number;
|
|
211
|
+
|
|
212
|
+
matchingBiomarkers: string[];
|
|
213
|
+
fdaApproved: boolean;
|
|
214
|
+
nccnRecommended: boolean;
|
|
215
|
+
|
|
216
|
+
considerations: string[];
|
|
217
|
+
contraindications?: string[];
|
|
218
|
+
}[];
|
|
219
|
+
|
|
220
|
+
bestChoice: {
|
|
221
|
+
regimen: string;
|
|
222
|
+
rationale: string[];
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
227
|
+
// MODEL TYPES
|
|
228
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
229
|
+
|
|
230
|
+
export interface MLModel {
|
|
231
|
+
id: string;
|
|
232
|
+
name: string;
|
|
233
|
+
version: string;
|
|
234
|
+
type: 'classification' | 'regression' | 'survival' | 'ranking';
|
|
235
|
+
target: 'response' | 'pfs' | 'os' | 'toxicity' | 'resistance';
|
|
236
|
+
cancerTypes: string[];
|
|
237
|
+
|
|
238
|
+
performance: {
|
|
239
|
+
auc?: number;
|
|
240
|
+
accuracy?: number;
|
|
241
|
+
sensitivity?: number;
|
|
242
|
+
specificity?: number;
|
|
243
|
+
cIndex?: number; // For survival models
|
|
244
|
+
calibration?: number;
|
|
245
|
+
brier?: number;
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
validation: {
|
|
249
|
+
method: 'cross-validation' | 'temporal' | 'external' | 'prospective';
|
|
250
|
+
cohortSize: number;
|
|
251
|
+
testSetSize: number;
|
|
252
|
+
validationDate: Date;
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
features: string[];
|
|
256
|
+
importantFeatures: { feature: string; importance: number }[];
|
|
257
|
+
|
|
258
|
+
status: 'development' | 'validation' | 'clinical-use' | 'deprecated';
|
|
259
|
+
regulatoryStatus?: 'not-submitted' | 'pending' | 'cleared' | '510k' | 'de-novo';
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export interface ModelRegistry {
|
|
263
|
+
models: MLModel[];
|
|
264
|
+
getModel(id: string): MLModel | undefined;
|
|
265
|
+
getBestModel(target: MLModel['target'], cancerType: string): MLModel | undefined;
|
|
266
|
+
registerModel(model: MLModel): void;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
270
|
+
// OUTCOME PREDICTOR SERVICE
|
|
271
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
272
|
+
|
|
273
|
+
export class OutcomePredictorService extends EventEmitter {
|
|
274
|
+
private modelRegistry: Map<string, MLModel> = new Map();
|
|
275
|
+
private featureEncoders: Map<string, FeatureEncoder> = new Map();
|
|
276
|
+
private predictionCache: Map<string, { prediction: any; timestamp: Date }> = new Map();
|
|
277
|
+
private cacheDuration = 3600000; // 1 hour
|
|
278
|
+
|
|
279
|
+
constructor() {
|
|
280
|
+
super();
|
|
281
|
+
this.initializeDefaultModels();
|
|
282
|
+
this.initializeFeatureEncoders();
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Predict treatment response
|
|
287
|
+
*/
|
|
288
|
+
async predictResponse(
|
|
289
|
+
patient: PatientFeatures,
|
|
290
|
+
treatment: TreatmentFeatures
|
|
291
|
+
): Promise<ResponsePrediction> {
|
|
292
|
+
const cacheKey = this.getCacheKey('response', patient, treatment);
|
|
293
|
+
const cached = this.getFromCache<ResponsePrediction>(cacheKey);
|
|
294
|
+
if (cached) return cached;
|
|
295
|
+
|
|
296
|
+
// Get appropriate model
|
|
297
|
+
const model = this.getBestModel('response', patient.cancerType);
|
|
298
|
+
|
|
299
|
+
// Encode features
|
|
300
|
+
const features = this.encodeFeatures(patient, treatment);
|
|
301
|
+
|
|
302
|
+
// Make prediction (in production, this would call a trained model)
|
|
303
|
+
const prediction = this.computeResponsePrediction(features, patient, treatment, model);
|
|
304
|
+
|
|
305
|
+
this.setCache(cacheKey, prediction);
|
|
306
|
+
this.emit('prediction-made', { type: 'response', patient, treatment, prediction });
|
|
307
|
+
|
|
308
|
+
return prediction;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Predict survival outcomes
|
|
313
|
+
*/
|
|
314
|
+
async predictSurvival(
|
|
315
|
+
patient: PatientFeatures,
|
|
316
|
+
treatment: TreatmentFeatures
|
|
317
|
+
): Promise<SurvivalPrediction> {
|
|
318
|
+
const cacheKey = this.getCacheKey('survival', patient, treatment);
|
|
319
|
+
const cached = this.getFromCache<SurvivalPrediction>(cacheKey);
|
|
320
|
+
if (cached) return cached;
|
|
321
|
+
|
|
322
|
+
const model = this.getBestModel('pfs', patient.cancerType);
|
|
323
|
+
const features = this.encodeFeatures(patient, treatment);
|
|
324
|
+
const prediction = this.computeSurvivalPrediction(features, patient, treatment, model);
|
|
325
|
+
|
|
326
|
+
this.setCache(cacheKey, prediction);
|
|
327
|
+
this.emit('prediction-made', { type: 'survival', patient, treatment, prediction });
|
|
328
|
+
|
|
329
|
+
return prediction;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Predict toxicity risk
|
|
334
|
+
*/
|
|
335
|
+
async predictToxicity(
|
|
336
|
+
patient: PatientFeatures,
|
|
337
|
+
treatment: TreatmentFeatures
|
|
338
|
+
): Promise<ToxicityPrediction> {
|
|
339
|
+
const cacheKey = this.getCacheKey('toxicity', patient, treatment);
|
|
340
|
+
const cached = this.getFromCache<ToxicityPrediction>(cacheKey);
|
|
341
|
+
if (cached) return cached;
|
|
342
|
+
|
|
343
|
+
const model = this.getBestModel('toxicity', patient.cancerType);
|
|
344
|
+
const features = this.encodeFeatures(patient, treatment);
|
|
345
|
+
const prediction = this.computeToxicityPrediction(features, patient, treatment, model);
|
|
346
|
+
|
|
347
|
+
this.setCache(cacheKey, prediction);
|
|
348
|
+
this.emit('prediction-made', { type: 'toxicity', patient, treatment, prediction });
|
|
349
|
+
|
|
350
|
+
return prediction;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Predict resistance development
|
|
355
|
+
*/
|
|
356
|
+
async predictResistance(
|
|
357
|
+
patient: PatientFeatures,
|
|
358
|
+
treatment: TreatmentFeatures
|
|
359
|
+
): Promise<ResistancePrediction> {
|
|
360
|
+
const cacheKey = this.getCacheKey('resistance', patient, treatment);
|
|
361
|
+
const cached = this.getFromCache<ResistancePrediction>(cacheKey);
|
|
362
|
+
if (cached) return cached;
|
|
363
|
+
|
|
364
|
+
const model = this.getBestModel('resistance', patient.cancerType);
|
|
365
|
+
const features = this.encodeFeatures(patient, treatment);
|
|
366
|
+
const prediction = this.computeResistancePrediction(features, patient, treatment, model);
|
|
367
|
+
|
|
368
|
+
this.setCache(cacheKey, prediction);
|
|
369
|
+
this.emit('prediction-made', { type: 'resistance', patient, treatment, prediction });
|
|
370
|
+
|
|
371
|
+
return prediction;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Rank treatment options
|
|
376
|
+
*/
|
|
377
|
+
async rankTherapies(
|
|
378
|
+
patient: PatientFeatures,
|
|
379
|
+
treatmentOptions: TreatmentFeatures[],
|
|
380
|
+
preferences?: {
|
|
381
|
+
prioritizeEfficacy?: boolean;
|
|
382
|
+
prioritizeToxicity?: boolean;
|
|
383
|
+
prioritizeQoL?: boolean;
|
|
384
|
+
}
|
|
385
|
+
): Promise<TherapyRanking> {
|
|
386
|
+
const rankings: TherapyRanking['therapies'] = [];
|
|
387
|
+
|
|
388
|
+
// Get predictions for each treatment option
|
|
389
|
+
for (const treatment of treatmentOptions) {
|
|
390
|
+
const [response, survival, toxicity] = await Promise.all([
|
|
391
|
+
this.predictResponse(patient, treatment),
|
|
392
|
+
this.predictSurvival(patient, treatment),
|
|
393
|
+
this.predictToxicity(patient, treatment)
|
|
394
|
+
]);
|
|
395
|
+
|
|
396
|
+
// Calculate overall score based on preferences
|
|
397
|
+
const weights = {
|
|
398
|
+
efficacy: preferences?.prioritizeEfficacy ? 0.5 : 0.35,
|
|
399
|
+
toxicity: preferences?.prioritizeToxicity ? 0.3 : 0.25,
|
|
400
|
+
qol: preferences?.prioritizeQoL ? 0.3 : 0.2,
|
|
401
|
+
survival: 0.2
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
const efficacyScore = response.objectiveResponseRate;
|
|
405
|
+
const toxicityScore = 1 - toxicity.grade3PlusRisk;
|
|
406
|
+
const survivalScore = survival.pfs.twelveMonth;
|
|
407
|
+
const qolScore = this.estimateQoLScore(toxicity);
|
|
408
|
+
|
|
409
|
+
const overallScore =
|
|
410
|
+
efficacyScore * weights.efficacy +
|
|
411
|
+
toxicityScore * weights.toxicity +
|
|
412
|
+
survivalScore * weights.survival +
|
|
413
|
+
qolScore * weights.qol;
|
|
414
|
+
|
|
415
|
+
rankings.push({
|
|
416
|
+
regimen: treatment.regimen,
|
|
417
|
+
drugs: treatment.drugs,
|
|
418
|
+
rank: 0, // Will be set after sorting
|
|
419
|
+
predictions: {
|
|
420
|
+
responseRate: response.objectiveResponseRate,
|
|
421
|
+
pfsSurvival: survival.pfs.median,
|
|
422
|
+
osSurvival: survival.os.median,
|
|
423
|
+
toxicityRisk: toxicity.grade3PlusRisk,
|
|
424
|
+
qualityOfLifeScore: qolScore
|
|
425
|
+
},
|
|
426
|
+
overallScore,
|
|
427
|
+
confidence: (response.confidence + survival.pfs.confidence) / 2,
|
|
428
|
+
matchingBiomarkers: this.getMatchingBiomarkers(patient, treatment),
|
|
429
|
+
fdaApproved: this.checkFDAApproval(treatment, patient.cancerType),
|
|
430
|
+
nccnRecommended: this.checkNCCNRecommendation(treatment, patient.cancerType, patient.stage),
|
|
431
|
+
considerations: this.getConsiderations(patient, treatment)
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Sort by overall score
|
|
436
|
+
rankings.sort((a, b) => b.overallScore - a.overallScore);
|
|
437
|
+
|
|
438
|
+
// Assign ranks
|
|
439
|
+
rankings.forEach((r, i) => r.rank = i + 1);
|
|
440
|
+
|
|
441
|
+
const bestChoice = rankings[0];
|
|
442
|
+
|
|
443
|
+
return {
|
|
444
|
+
therapies: rankings,
|
|
445
|
+
bestChoice: {
|
|
446
|
+
regimen: bestChoice.regimen,
|
|
447
|
+
rationale: [
|
|
448
|
+
`Highest overall score (${(bestChoice.overallScore * 100).toFixed(1)}%)`,
|
|
449
|
+
`Expected response rate: ${(bestChoice.predictions.responseRate * 100).toFixed(1)}%`,
|
|
450
|
+
`Expected median PFS: ${bestChoice.predictions.pfsSurvival.toFixed(1)} months`,
|
|
451
|
+
bestChoice.fdaApproved ? 'FDA approved for this indication' : '',
|
|
452
|
+
bestChoice.nccnRecommended ? 'NCCN recommended' : '',
|
|
453
|
+
...bestChoice.matchingBiomarkers.map(b => `Matches biomarker: ${b}`)
|
|
454
|
+
].filter(Boolean)
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Get comprehensive prediction report
|
|
461
|
+
*/
|
|
462
|
+
async getComprehensivePrediction(
|
|
463
|
+
patient: PatientFeatures,
|
|
464
|
+
treatment: TreatmentFeatures
|
|
465
|
+
): Promise<{
|
|
466
|
+
response: ResponsePrediction;
|
|
467
|
+
survival: SurvivalPrediction;
|
|
468
|
+
toxicity: ToxicityPrediction;
|
|
469
|
+
resistance: ResistancePrediction;
|
|
470
|
+
overallAssessment: {
|
|
471
|
+
recommendation: 'strongly-recommended' | 'recommended' | 'consider' | 'caution' | 'not-recommended';
|
|
472
|
+
rationale: string[];
|
|
473
|
+
caveats: string[];
|
|
474
|
+
alternativeOptions: string[];
|
|
475
|
+
};
|
|
476
|
+
}> {
|
|
477
|
+
const [response, survival, toxicity, resistance] = await Promise.all([
|
|
478
|
+
this.predictResponse(patient, treatment),
|
|
479
|
+
this.predictSurvival(patient, treatment),
|
|
480
|
+
this.predictToxicity(patient, treatment),
|
|
481
|
+
this.predictResistance(patient, treatment)
|
|
482
|
+
]);
|
|
483
|
+
|
|
484
|
+
// Generate overall assessment
|
|
485
|
+
const overallAssessment = this.generateOverallAssessment(
|
|
486
|
+
patient, treatment, response, survival, toxicity, resistance
|
|
487
|
+
);
|
|
488
|
+
|
|
489
|
+
return {
|
|
490
|
+
response,
|
|
491
|
+
survival,
|
|
492
|
+
toxicity,
|
|
493
|
+
resistance,
|
|
494
|
+
overallAssessment
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
499
|
+
// PREDICTION COMPUTATION (Placeholder implementations)
|
|
500
|
+
// In production, these would use trained ML models
|
|
501
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
502
|
+
|
|
503
|
+
private computeResponsePrediction(
|
|
504
|
+
features: number[],
|
|
505
|
+
patient: PatientFeatures,
|
|
506
|
+
treatment: TreatmentFeatures,
|
|
507
|
+
model?: MLModel
|
|
508
|
+
): ResponsePrediction {
|
|
509
|
+
// Base probabilities based on treatment type and cancer
|
|
510
|
+
let baseCR = 0.15;
|
|
511
|
+
let basePR = 0.30;
|
|
512
|
+
let baseSD = 0.30;
|
|
513
|
+
|
|
514
|
+
// Adjust based on biomarkers
|
|
515
|
+
if (this.hasBiomarkerMatch(patient, treatment)) {
|
|
516
|
+
baseCR += 0.15;
|
|
517
|
+
basePR += 0.15;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Adjust based on stage
|
|
521
|
+
const stageModifiers: Record<string, number> = {
|
|
522
|
+
'I': 1.3, 'IA': 1.3, 'IB': 1.25,
|
|
523
|
+
'II': 1.15, 'IIA': 1.15, 'IIB': 1.1,
|
|
524
|
+
'III': 0.9, 'IIIA': 0.95, 'IIIB': 0.85, 'IIIC': 0.8,
|
|
525
|
+
'IV': 0.7, 'IVA': 0.75, 'IVB': 0.65
|
|
526
|
+
};
|
|
527
|
+
const stageMod = stageModifiers[patient.stage] || 1.0;
|
|
528
|
+
|
|
529
|
+
// Adjust based on ECOG
|
|
530
|
+
const ecogModifiers = [1.0, 0.9, 0.75, 0.5, 0.25];
|
|
531
|
+
const ecogMod = ecogModifiers[patient.ecogStatus];
|
|
532
|
+
|
|
533
|
+
// Adjust based on treatment setting
|
|
534
|
+
const settingModifiers: Record<string, number> = {
|
|
535
|
+
'first-line': 1.0,
|
|
536
|
+
'second-line': 0.75,
|
|
537
|
+
'third-line-plus': 0.5,
|
|
538
|
+
'neoadjuvant': 1.1,
|
|
539
|
+
'adjuvant': 1.05,
|
|
540
|
+
'maintenance': 0.9
|
|
541
|
+
};
|
|
542
|
+
const settingMod = settingModifiers[treatment.setting] || 1.0;
|
|
543
|
+
|
|
544
|
+
// Apply modifiers
|
|
545
|
+
const modifier = stageMod * ecogMod * settingMod;
|
|
546
|
+
const crProb = Math.min(baseCR * modifier, 0.6);
|
|
547
|
+
const prProb = Math.min(basePR * modifier, 0.5);
|
|
548
|
+
const sdProb = Math.min(baseSD * modifier, 0.4);
|
|
549
|
+
const pdProb = Math.max(1 - crProb - prProb - sdProb, 0.05);
|
|
550
|
+
|
|
551
|
+
// Normalize
|
|
552
|
+
const total = crProb + prProb + sdProb + pdProb;
|
|
553
|
+
const probabilities = {
|
|
554
|
+
completeResponse: crProb / total,
|
|
555
|
+
partialResponse: prProb / total,
|
|
556
|
+
stableDisease: sdProb / total,
|
|
557
|
+
progressiveDisease: pdProb / total
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
// Determine best response
|
|
561
|
+
const maxProb = Math.max(...Object.values(probabilities));
|
|
562
|
+
let predictedResponse: ResponsePrediction['predictedResponse'] = 'SD';
|
|
563
|
+
if (probabilities.completeResponse === maxProb) predictedResponse = 'CR';
|
|
564
|
+
else if (probabilities.partialResponse === maxProb) predictedResponse = 'PR';
|
|
565
|
+
else if (probabilities.progressiveDisease === maxProb) predictedResponse = 'PD';
|
|
566
|
+
|
|
567
|
+
return {
|
|
568
|
+
predictedResponse,
|
|
569
|
+
probabilities,
|
|
570
|
+
confidence: model ? model.performance.auc || 0.75 : 0.70,
|
|
571
|
+
objectiveResponseRate: probabilities.completeResponse + probabilities.partialResponse,
|
|
572
|
+
diseaseControlRate: probabilities.completeResponse + probabilities.partialResponse + probabilities.stableDisease,
|
|
573
|
+
timeToResponse: {
|
|
574
|
+
median: 8,
|
|
575
|
+
range: [4, 16]
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
private computeSurvivalPrediction(
|
|
581
|
+
features: number[],
|
|
582
|
+
patient: PatientFeatures,
|
|
583
|
+
treatment: TreatmentFeatures,
|
|
584
|
+
model?: MLModel
|
|
585
|
+
): SurvivalPrediction {
|
|
586
|
+
// Base survival estimates (in months)
|
|
587
|
+
let basePFS = 12;
|
|
588
|
+
let baseOS = 24;
|
|
589
|
+
|
|
590
|
+
// Cancer type adjustments
|
|
591
|
+
const cancerPFSModifiers: Record<string, number> = {
|
|
592
|
+
'NSCLC': 1.0, 'SCLC': 0.5, 'Breast': 1.5, 'Colorectal': 1.0,
|
|
593
|
+
'Melanoma': 1.2, 'RCC': 1.3, 'Ovarian': 0.8, 'Pancreatic': 0.4,
|
|
594
|
+
'Glioblastoma': 0.3, 'AML': 0.6, 'Multiple Myeloma': 1.5
|
|
595
|
+
};
|
|
596
|
+
|
|
597
|
+
const cancerMod = cancerPFSModifiers[patient.cancerType] || 1.0;
|
|
598
|
+
basePFS *= cancerMod;
|
|
599
|
+
baseOS *= cancerMod;
|
|
600
|
+
|
|
601
|
+
// Stage adjustments
|
|
602
|
+
const stageModifiers: Record<string, number> = {
|
|
603
|
+
'I': 3.0, 'IA': 3.5, 'IB': 2.8,
|
|
604
|
+
'II': 2.0, 'IIA': 2.2, 'IIB': 1.8,
|
|
605
|
+
'III': 1.0, 'IIIA': 1.2, 'IIIB': 0.9, 'IIIC': 0.7,
|
|
606
|
+
'IV': 0.5, 'IVA': 0.55, 'IVB': 0.4
|
|
607
|
+
};
|
|
608
|
+
const stageMod = stageModifiers[patient.stage] || 1.0;
|
|
609
|
+
basePFS *= stageMod;
|
|
610
|
+
baseOS *= stageMod;
|
|
611
|
+
|
|
612
|
+
// Biomarker-driven therapy boost
|
|
613
|
+
if (this.hasBiomarkerMatch(patient, treatment)) {
|
|
614
|
+
basePFS *= 1.5;
|
|
615
|
+
baseOS *= 1.3;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// ECOG adjustment
|
|
619
|
+
const ecogMultipliers = [1.0, 0.85, 0.65, 0.4, 0.2];
|
|
620
|
+
basePFS *= ecogMultipliers[patient.ecogStatus];
|
|
621
|
+
baseOS *= ecogMultipliers[patient.ecogStatus];
|
|
622
|
+
|
|
623
|
+
// Calculate probabilities using exponential survival model
|
|
624
|
+
const lambda_pfs = 1 / basePFS;
|
|
625
|
+
const lambda_os = 1 / baseOS;
|
|
626
|
+
|
|
627
|
+
const pfs = {
|
|
628
|
+
median: basePFS,
|
|
629
|
+
sixMonth: Math.exp(-lambda_pfs * 6),
|
|
630
|
+
twelveMonth: Math.exp(-lambda_pfs * 12),
|
|
631
|
+
twentyFourMonth: Math.exp(-lambda_pfs * 24),
|
|
632
|
+
confidence: model ? model.performance.cIndex || 0.72 : 0.68
|
|
633
|
+
};
|
|
634
|
+
|
|
635
|
+
const os = {
|
|
636
|
+
median: baseOS,
|
|
637
|
+
twelveMonth: Math.exp(-lambda_os * 12),
|
|
638
|
+
twentyFourMonth: Math.exp(-lambda_os * 24),
|
|
639
|
+
fiveYear: Math.exp(-lambda_os * 60),
|
|
640
|
+
confidence: model ? model.performance.cIndex || 0.72 : 0.68
|
|
641
|
+
};
|
|
642
|
+
|
|
643
|
+
// Calculate risk score (0-100)
|
|
644
|
+
const riskFactors = [
|
|
645
|
+
patient.stage.startsWith('IV') ? 25 : patient.stage.startsWith('III') ? 15 : 5,
|
|
646
|
+
patient.ecogStatus >= 2 ? 20 : patient.ecogStatus === 1 ? 10 : 0,
|
|
647
|
+
(patient.priorLines || 0) >= 2 ? 15 : (patient.priorLines || 0) >= 1 ? 8 : 0,
|
|
648
|
+
patient.ldh && patient.ldh > 250 ? 10 : 0,
|
|
649
|
+
patient.metastaticSites && patient.metastaticSites.length > 2 ? 15 : 0,
|
|
650
|
+
patient.age > 75 ? 10 : patient.age > 65 ? 5 : 0
|
|
651
|
+
];
|
|
652
|
+
const riskScore = Math.min(riskFactors.reduce((a, b) => a + b, 0), 100);
|
|
653
|
+
|
|
654
|
+
let riskGroup: SurvivalPrediction['riskGroup'];
|
|
655
|
+
if (riskScore < 25) riskGroup = 'low';
|
|
656
|
+
else if (riskScore < 50) riskGroup = 'intermediate-low';
|
|
657
|
+
else if (riskScore < 75) riskGroup = 'intermediate-high';
|
|
658
|
+
else riskGroup = 'high';
|
|
659
|
+
|
|
660
|
+
return { pfs, os, riskGroup, riskScore };
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
private computeToxicityPrediction(
|
|
664
|
+
features: number[],
|
|
665
|
+
patient: PatientFeatures,
|
|
666
|
+
treatment: TreatmentFeatures,
|
|
667
|
+
model?: MLModel
|
|
668
|
+
): ToxicityPrediction {
|
|
669
|
+
const specificRisks: ToxicityPrediction['specificRisks'] = [];
|
|
670
|
+
let grade3PlusRisk = 0;
|
|
671
|
+
|
|
672
|
+
// Define toxicity profiles by drug class
|
|
673
|
+
const drugToxicities = this.getDrugToxicityProfiles(treatment.drugs);
|
|
674
|
+
|
|
675
|
+
for (const tox of drugToxicities) {
|
|
676
|
+
// Adjust risk based on patient factors
|
|
677
|
+
let adjustedRisk = tox.baseRisk;
|
|
678
|
+
|
|
679
|
+
// Age adjustment
|
|
680
|
+
if (patient.age > 70) adjustedRisk *= 1.2;
|
|
681
|
+
if (patient.age > 80) adjustedRisk *= 1.4;
|
|
682
|
+
|
|
683
|
+
// Organ function adjustment
|
|
684
|
+
if (tox.affectedOrgan === 'hepatic' && patient.organFunction?.hepatic === 'impaired') {
|
|
685
|
+
adjustedRisk *= 1.5;
|
|
686
|
+
}
|
|
687
|
+
if (tox.affectedOrgan === 'renal' && patient.organFunction?.renal === 'impaired') {
|
|
688
|
+
adjustedRisk *= 1.5;
|
|
689
|
+
}
|
|
690
|
+
if (tox.affectedOrgan === 'cardiac' && patient.organFunction?.cardiac === 'impaired') {
|
|
691
|
+
adjustedRisk *= 1.5;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// ECOG adjustment
|
|
695
|
+
adjustedRisk *= (1 + patient.ecogStatus * 0.1);
|
|
696
|
+
|
|
697
|
+
adjustedRisk = Math.min(adjustedRisk, 0.95);
|
|
698
|
+
|
|
699
|
+
specificRisks.push({
|
|
700
|
+
toxicity: tox.name,
|
|
701
|
+
grade: tox.typicalGrade,
|
|
702
|
+
probability: adjustedRisk,
|
|
703
|
+
timeToOnset: tox.timeToOnset,
|
|
704
|
+
reversible: tox.reversible,
|
|
705
|
+
management: tox.management
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
if (tox.typicalGrade >= 3) {
|
|
709
|
+
grade3PlusRisk += adjustedRisk * 0.3; // Weighted contribution
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
grade3PlusRisk = Math.min(grade3PlusRisk, 0.9);
|
|
714
|
+
|
|
715
|
+
// Immunotherapy-specific irAE prediction
|
|
716
|
+
let iraeRisk: ToxicityPrediction['iraeRisk'];
|
|
717
|
+
if (treatment.treatmentType === 'immunotherapy' || this.hasImmunotherapyDrug(treatment.drugs)) {
|
|
718
|
+
iraeRisk = {
|
|
719
|
+
any: 0.60,
|
|
720
|
+
grade3Plus: 0.15,
|
|
721
|
+
specificOrgans: [
|
|
722
|
+
{ organ: 'skin', risk: 0.35 },
|
|
723
|
+
{ organ: 'GI', risk: 0.20 },
|
|
724
|
+
{ organ: 'endocrine', risk: 0.15 },
|
|
725
|
+
{ organ: 'hepatic', risk: 0.10 },
|
|
726
|
+
{ organ: 'pulmonary', risk: 0.08 }
|
|
727
|
+
]
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
// Dose modification recommendation
|
|
732
|
+
let doseModification: ToxicityPrediction['doseModification'];
|
|
733
|
+
if (patient.age > 75 || patient.ecogStatus >= 2 || patient.organFunction?.renal === 'impaired') {
|
|
734
|
+
doseModification = {
|
|
735
|
+
recommended: true,
|
|
736
|
+
reduction: 20,
|
|
737
|
+
reason: patient.age > 75 ? 'Advanced age' :
|
|
738
|
+
patient.ecogStatus >= 2 ? 'Poor performance status' :
|
|
739
|
+
'Impaired organ function'
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
return {
|
|
744
|
+
overallRisk: grade3PlusRisk > 0.4 ? 'high' : grade3PlusRisk > 0.2 ? 'moderate' : 'low',
|
|
745
|
+
grade3PlusRisk,
|
|
746
|
+
specificRisks,
|
|
747
|
+
iraeRisk,
|
|
748
|
+
doseModification
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
private computeResistancePrediction(
|
|
753
|
+
features: number[],
|
|
754
|
+
patient: PatientFeatures,
|
|
755
|
+
treatment: TreatmentFeatures,
|
|
756
|
+
model?: MLModel
|
|
757
|
+
): ResistancePrediction {
|
|
758
|
+
let intrinsicRisk = 0.2;
|
|
759
|
+
let acquiredRisk = 0.6;
|
|
760
|
+
|
|
761
|
+
// Adjust based on prior treatments
|
|
762
|
+
if (patient.priorLines && patient.priorLines > 0) {
|
|
763
|
+
intrinsicRisk += 0.1 * patient.priorLines;
|
|
764
|
+
acquiredRisk += 0.05 * patient.priorLines;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// Adjust based on prior response
|
|
768
|
+
if (patient.priorResponse === 'PD') {
|
|
769
|
+
intrinsicRisk += 0.2;
|
|
770
|
+
} else if (patient.priorResponse === 'CR') {
|
|
771
|
+
intrinsicRisk -= 0.1;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// Get resistance mechanisms based on treatment and mutations
|
|
775
|
+
const mechanisms = this.getResistanceMechanisms(patient, treatment);
|
|
776
|
+
|
|
777
|
+
// Calculate time to resistance
|
|
778
|
+
const baseTimeToResistance = this.hasBiomarkerMatch(patient, treatment) ? 18 : 9;
|
|
779
|
+
const timeModifier = patient.priorLines ? 1 - (patient.priorLines * 0.15) : 1;
|
|
780
|
+
|
|
781
|
+
// Get next line options
|
|
782
|
+
const nextLineOptions = this.getNextLineOptions(patient, treatment, mechanisms);
|
|
783
|
+
|
|
784
|
+
return {
|
|
785
|
+
intrinsicResistanceRisk: Math.min(intrinsicRisk, 0.9),
|
|
786
|
+
acquiredResistanceRisk: Math.min(acquiredRisk, 0.95),
|
|
787
|
+
predictedMechanisms: mechanisms,
|
|
788
|
+
timeToResistance: {
|
|
789
|
+
median: baseTimeToResistance * timeModifier,
|
|
790
|
+
range: [baseTimeToResistance * timeModifier * 0.5, baseTimeToResistance * timeModifier * 2]
|
|
791
|
+
},
|
|
792
|
+
nextLineOptions
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
797
|
+
// HELPER METHODS
|
|
798
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
799
|
+
|
|
800
|
+
private initializeDefaultModels(): void {
|
|
801
|
+
// Register placeholder models
|
|
802
|
+
const defaultModels: MLModel[] = [
|
|
803
|
+
{
|
|
804
|
+
id: 'response-nsclc-v1',
|
|
805
|
+
name: 'NSCLC Response Predictor',
|
|
806
|
+
version: '1.0',
|
|
807
|
+
type: 'classification',
|
|
808
|
+
target: 'response',
|
|
809
|
+
cancerTypes: ['NSCLC'],
|
|
810
|
+
performance: { auc: 0.78, accuracy: 0.72 },
|
|
811
|
+
validation: { method: 'cross-validation', cohortSize: 1500, testSetSize: 300, validationDate: new Date() },
|
|
812
|
+
features: ['age', 'stage', 'ecog', 'pdl1', 'tmb', 'egfr', 'alk', 'kras'],
|
|
813
|
+
importantFeatures: [
|
|
814
|
+
{ feature: 'pdl1', importance: 0.25 },
|
|
815
|
+
{ feature: 'tmb', importance: 0.20 },
|
|
816
|
+
{ feature: 'egfr', importance: 0.18 }
|
|
817
|
+
],
|
|
818
|
+
status: 'validation'
|
|
819
|
+
},
|
|
820
|
+
{
|
|
821
|
+
id: 'survival-pan-cancer-v1',
|
|
822
|
+
name: 'Pan-Cancer Survival Model',
|
|
823
|
+
version: '1.0',
|
|
824
|
+
type: 'survival',
|
|
825
|
+
target: 'os',
|
|
826
|
+
cancerTypes: ['all'],
|
|
827
|
+
performance: { cIndex: 0.72 },
|
|
828
|
+
validation: { method: 'cross-validation', cohortSize: 10000, testSetSize: 2000, validationDate: new Date() },
|
|
829
|
+
features: ['age', 'stage', 'ecog', 'cancerType', 'priorLines', 'ldh'],
|
|
830
|
+
importantFeatures: [
|
|
831
|
+
{ feature: 'stage', importance: 0.30 },
|
|
832
|
+
{ feature: 'ecog', importance: 0.25 },
|
|
833
|
+
{ feature: 'priorLines', importance: 0.15 }
|
|
834
|
+
],
|
|
835
|
+
status: 'validation'
|
|
836
|
+
}
|
|
837
|
+
];
|
|
838
|
+
|
|
839
|
+
for (const model of defaultModels) {
|
|
840
|
+
this.modelRegistry.set(model.id, model);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
private initializeFeatureEncoders(): void {
|
|
845
|
+
// Initialize encoders for categorical variables
|
|
846
|
+
this.featureEncoders.set('stage', new FeatureEncoder({
|
|
847
|
+
'I': 1, 'IA': 1, 'IB': 1.5,
|
|
848
|
+
'II': 2, 'IIA': 2, 'IIB': 2.5,
|
|
849
|
+
'III': 3, 'IIIA': 3, 'IIIB': 3.5, 'IIIC': 3.8,
|
|
850
|
+
'IV': 4, 'IVA': 4, 'IVB': 4.5
|
|
851
|
+
}));
|
|
852
|
+
|
|
853
|
+
this.featureEncoders.set('cancerType', new FeatureEncoder({
|
|
854
|
+
'NSCLC': 1, 'SCLC': 2, 'Breast': 3, 'Colorectal': 4,
|
|
855
|
+
'Melanoma': 5, 'RCC': 6, 'Ovarian': 7, 'Pancreatic': 8
|
|
856
|
+
}));
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
private encodeFeatures(patient: PatientFeatures, treatment: TreatmentFeatures): number[] {
|
|
860
|
+
const features: number[] = [];
|
|
861
|
+
|
|
862
|
+
// Numeric features (normalized)
|
|
863
|
+
features.push(patient.age / 100);
|
|
864
|
+
features.push(patient.ecogStatus / 4);
|
|
865
|
+
features.push(patient.gender === 'male' ? 1 : 0);
|
|
866
|
+
|
|
867
|
+
// Encoded categorical features
|
|
868
|
+
const stageEncoder = this.featureEncoders.get('stage');
|
|
869
|
+
features.push((stageEncoder?.encode(patient.stage) || 2) / 5);
|
|
870
|
+
|
|
871
|
+
// Biomarker features
|
|
872
|
+
features.push((patient.pdl1Score || 0) / 100);
|
|
873
|
+
features.push((patient.tmbValue || 0) / 50);
|
|
874
|
+
features.push(patient.msiStatus === 'MSI-H' ? 1 : 0);
|
|
875
|
+
features.push(patient.hrdStatus === 'positive' ? 1 : 0);
|
|
876
|
+
|
|
877
|
+
// Prior treatment features
|
|
878
|
+
features.push((patient.priorLines || 0) / 5);
|
|
879
|
+
|
|
880
|
+
// Treatment features
|
|
881
|
+
features.push(treatment.treatmentType === 'immunotherapy' ? 1 : 0);
|
|
882
|
+
features.push(treatment.treatmentType === 'targeted' ? 1 : 0);
|
|
883
|
+
|
|
884
|
+
return features;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
private getBestModel(target: MLModel['target'], cancerType: string): MLModel | undefined {
|
|
888
|
+
const models = Array.from(this.modelRegistry.values())
|
|
889
|
+
.filter(m =>
|
|
890
|
+
m.target === target &&
|
|
891
|
+
(m.cancerTypes.includes(cancerType) || m.cancerTypes.includes('all')) &&
|
|
892
|
+
m.status !== 'deprecated'
|
|
893
|
+
)
|
|
894
|
+
.sort((a, b) => (b.performance.auc || b.performance.cIndex || 0) - (a.performance.auc || a.performance.cIndex || 0));
|
|
895
|
+
|
|
896
|
+
return models[0];
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
private hasBiomarkerMatch(patient: PatientFeatures, treatment: TreatmentFeatures): boolean {
|
|
900
|
+
// Check for biomarker-drug matches
|
|
901
|
+
const matches = [
|
|
902
|
+
{ biomarker: 'EGFR', drugs: ['osimertinib', 'erlotinib', 'gefitinib', 'afatinib'] },
|
|
903
|
+
{ biomarker: 'ALK', drugs: ['alectinib', 'crizotinib', 'brigatinib', 'lorlatinib'] },
|
|
904
|
+
{ biomarker: 'BRAF V600', drugs: ['dabrafenib', 'vemurafenib', 'encorafenib'] },
|
|
905
|
+
{ biomarker: 'HER2', drugs: ['trastuzumab', 'pertuzumab', 't-dxd'] },
|
|
906
|
+
{ biomarker: 'BRCA', drugs: ['olaparib', 'rucaparib', 'niraparib', 'talazoparib'] },
|
|
907
|
+
{ biomarker: 'KRAS G12C', drugs: ['sotorasib', 'adagrasib'] }
|
|
908
|
+
];
|
|
909
|
+
|
|
910
|
+
for (const match of matches) {
|
|
911
|
+
const hasBiomarker = patient.genomicAlterations.some(g =>
|
|
912
|
+
g.gene.toUpperCase().includes(match.biomarker) ||
|
|
913
|
+
g.alteration.toUpperCase().includes(match.biomarker)
|
|
914
|
+
);
|
|
915
|
+
const hasDrug = treatment.drugs.some(d =>
|
|
916
|
+
match.drugs.some(md => d.toLowerCase().includes(md))
|
|
917
|
+
);
|
|
918
|
+
if (hasBiomarker && hasDrug) return true;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// Check immunotherapy eligibility
|
|
922
|
+
if (treatment.treatmentType === 'immunotherapy' || this.hasImmunotherapyDrug(treatment.drugs)) {
|
|
923
|
+
if (patient.msiStatus === 'MSI-H') return true;
|
|
924
|
+
if (patient.tmbStatus === 'high') return true;
|
|
925
|
+
if (patient.pdl1Score && patient.pdl1Score >= 50) return true;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
return false;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
private hasImmunotherapyDrug(drugs: string[]): boolean {
|
|
932
|
+
const immunoDrugs = ['pembrolizumab', 'nivolumab', 'ipilimumab', 'atezolizumab', 'durvalumab', 'avelumab'];
|
|
933
|
+
return drugs.some(d => immunoDrugs.some(id => d.toLowerCase().includes(id)));
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
private getDrugToxicityProfiles(drugs: string[]): {
|
|
937
|
+
name: string;
|
|
938
|
+
baseRisk: number;
|
|
939
|
+
typicalGrade: 1 | 2 | 3 | 4 | 5;
|
|
940
|
+
affectedOrgan?: string;
|
|
941
|
+
timeToOnset?: { median: number; unit: 'days' | 'weeks' | 'cycles' };
|
|
942
|
+
reversible: boolean;
|
|
943
|
+
management?: string;
|
|
944
|
+
}[] {
|
|
945
|
+
const profiles: any[] = [];
|
|
946
|
+
|
|
947
|
+
// Check for common drug classes and their toxicities
|
|
948
|
+
for (const drug of drugs) {
|
|
949
|
+
const lower = drug.toLowerCase();
|
|
950
|
+
|
|
951
|
+
// Checkpoint inhibitors
|
|
952
|
+
if (['pembrolizumab', 'nivolumab', 'ipilimumab', 'atezolizumab'].some(d => lower.includes(d))) {
|
|
953
|
+
profiles.push(
|
|
954
|
+
{ name: 'Immune-related dermatitis', baseRisk: 0.35, typicalGrade: 2, affectedOrgan: 'skin', reversible: true },
|
|
955
|
+
{ name: 'Immune-related colitis', baseRisk: 0.15, typicalGrade: 3, affectedOrgan: 'GI', reversible: true, management: 'Corticosteroids' },
|
|
956
|
+
{ name: 'Immune-related pneumonitis', baseRisk: 0.05, typicalGrade: 3, affectedOrgan: 'pulmonary', reversible: true, management: 'Hold treatment, corticosteroids' },
|
|
957
|
+
{ name: 'Immune-related hepatitis', baseRisk: 0.08, typicalGrade: 3, affectedOrgan: 'hepatic', reversible: true },
|
|
958
|
+
{ name: 'Immune-related thyroiditis', baseRisk: 0.15, typicalGrade: 2, affectedOrgan: 'endocrine', reversible: false }
|
|
959
|
+
);
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
// Platinum agents
|
|
963
|
+
if (['carboplatin', 'cisplatin', 'oxaliplatin'].some(d => lower.includes(d))) {
|
|
964
|
+
profiles.push(
|
|
965
|
+
{ name: 'Nausea/Vomiting', baseRisk: 0.60, typicalGrade: 2, affectedOrgan: 'GI', reversible: true },
|
|
966
|
+
{ name: 'Nephrotoxicity', baseRisk: lower.includes('cisplatin') ? 0.25 : 0.08, typicalGrade: 2, affectedOrgan: 'renal', reversible: true },
|
|
967
|
+
{ name: 'Myelosuppression', baseRisk: 0.40, typicalGrade: 3, affectedOrgan: 'hematologic', reversible: true },
|
|
968
|
+
{ name: 'Peripheral neuropathy', baseRisk: 0.30, typicalGrade: 2, affectedOrgan: 'neurologic', reversible: false }
|
|
969
|
+
);
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// EGFR TKIs
|
|
973
|
+
if (['osimertinib', 'erlotinib', 'gefitinib', 'afatinib'].some(d => lower.includes(d))) {
|
|
974
|
+
profiles.push(
|
|
975
|
+
{ name: 'Rash', baseRisk: 0.45, typicalGrade: 2, affectedOrgan: 'skin', reversible: true },
|
|
976
|
+
{ name: 'Diarrhea', baseRisk: 0.50, typicalGrade: 2, affectedOrgan: 'GI', reversible: true },
|
|
977
|
+
{ name: 'Interstitial lung disease', baseRisk: 0.03, typicalGrade: 4, affectedOrgan: 'pulmonary', reversible: false }
|
|
978
|
+
);
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
// CDK4/6 inhibitors
|
|
982
|
+
if (['palbociclib', 'ribociclib', 'abemaciclib'].some(d => lower.includes(d))) {
|
|
983
|
+
profiles.push(
|
|
984
|
+
{ name: 'Neutropenia', baseRisk: 0.70, typicalGrade: 3, affectedOrgan: 'hematologic', reversible: true },
|
|
985
|
+
{ name: 'Fatigue', baseRisk: 0.40, typicalGrade: 2, reversible: true },
|
|
986
|
+
{ name: 'Diarrhea', baseRisk: lower.includes('abemaciclib') ? 0.80 : 0.20, typicalGrade: 2, affectedOrgan: 'GI', reversible: true }
|
|
987
|
+
);
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
return profiles;
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
private getResistanceMechanisms(patient: PatientFeatures, treatment: TreatmentFeatures): ResistancePrediction['predictedMechanisms'] {
|
|
995
|
+
const mechanisms: ResistancePrediction['predictedMechanisms'] = [];
|
|
996
|
+
|
|
997
|
+
// EGFR TKI resistance mechanisms
|
|
998
|
+
if (treatment.drugs.some(d => ['osimertinib', 'erlotinib', 'gefitinib'].some(e => d.toLowerCase().includes(e)))) {
|
|
999
|
+
mechanisms.push(
|
|
1000
|
+
{ mechanism: 'MET amplification', probability: 0.20, monitoringBiomarker: 'MET FISH/NGS' },
|
|
1001
|
+
{ mechanism: 'EGFR C797S mutation', probability: 0.15, monitoringBiomarker: 'ctDNA EGFR' },
|
|
1002
|
+
{ mechanism: 'Histologic transformation (SCLC)', probability: 0.05, monitoringBiomarker: 'Tissue biopsy' },
|
|
1003
|
+
{ mechanism: 'HER2 amplification', probability: 0.10, monitoringBiomarker: 'HER2 NGS/FISH' }
|
|
1004
|
+
);
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
// Immunotherapy resistance
|
|
1008
|
+
if (this.hasImmunotherapyDrug(treatment.drugs)) {
|
|
1009
|
+
mechanisms.push(
|
|
1010
|
+
{ mechanism: 'Beta-2 microglobulin loss', probability: 0.10, monitoringBiomarker: 'B2M NGS' },
|
|
1011
|
+
{ mechanism: 'JAK1/2 mutations', probability: 0.08, monitoringBiomarker: 'JAK1/2 NGS' },
|
|
1012
|
+
{ mechanism: 'Immunosuppressive TME', probability: 0.25, monitoringBiomarker: 'Tissue biopsy + IHC' },
|
|
1013
|
+
{ mechanism: 'Antigen loss', probability: 0.15 }
|
|
1014
|
+
);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
// BRAF inhibitor resistance
|
|
1018
|
+
if (treatment.drugs.some(d => ['dabrafenib', 'vemurafenib', 'encorafenib'].some(b => d.toLowerCase().includes(b)))) {
|
|
1019
|
+
mechanisms.push(
|
|
1020
|
+
{ mechanism: 'MAPK reactivation', probability: 0.30, monitoringBiomarker: 'NRAS/MEK NGS' },
|
|
1021
|
+
{ mechanism: 'BRAF amplification', probability: 0.15, monitoringBiomarker: 'BRAF CNV' },
|
|
1022
|
+
{ mechanism: 'RTK bypass (EGFR, MET)', probability: 0.20, monitoringBiomarker: 'Comprehensive NGS' }
|
|
1023
|
+
);
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
return mechanisms;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
private getNextLineOptions(
|
|
1030
|
+
patient: PatientFeatures,
|
|
1031
|
+
treatment: TreatmentFeatures,
|
|
1032
|
+
mechanisms: ResistancePrediction['predictedMechanisms']
|
|
1033
|
+
): ResistancePrediction['nextLineOptions'] {
|
|
1034
|
+
const options: ResistancePrediction['nextLineOptions'] = [];
|
|
1035
|
+
|
|
1036
|
+
// Based on predicted resistance mechanisms
|
|
1037
|
+
for (const mech of mechanisms.slice(0, 3)) {
|
|
1038
|
+
if (mech.mechanism === 'MET amplification') {
|
|
1039
|
+
options.push({
|
|
1040
|
+
therapy: 'Osimertinib + Savolitinib',
|
|
1041
|
+
rationale: 'Targets MET bypass pathway',
|
|
1042
|
+
expectedBenefit: 8
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
if (mech.mechanism === 'EGFR C797S mutation') {
|
|
1046
|
+
options.push({
|
|
1047
|
+
therapy: 'First-generation EGFR TKI + Third-generation EGFR TKI',
|
|
1048
|
+
rationale: 'C797S may restore sensitivity to 1st-gen TKI',
|
|
1049
|
+
expectedBenefit: 6
|
|
1050
|
+
});
|
|
1051
|
+
}
|
|
1052
|
+
if (mech.mechanism === 'Histologic transformation (SCLC)') {
|
|
1053
|
+
options.push({
|
|
1054
|
+
therapy: 'Platinum-etoposide chemotherapy',
|
|
1055
|
+
rationale: 'Standard SCLC treatment',
|
|
1056
|
+
expectedBenefit: 4
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
// Add general next-line options
|
|
1062
|
+
if (treatment.treatmentType !== 'immunotherapy' && !this.hasImmunotherapyDrug(treatment.drugs)) {
|
|
1063
|
+
options.push({
|
|
1064
|
+
therapy: 'Immunotherapy (if PD-L1+/TMB-H)',
|
|
1065
|
+
rationale: 'Different mechanism of action',
|
|
1066
|
+
expectedBenefit: 10
|
|
1067
|
+
});
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
// Clinical trial option
|
|
1071
|
+
options.push({
|
|
1072
|
+
therapy: 'Clinical trial enrollment',
|
|
1073
|
+
rationale: 'Access to novel agents',
|
|
1074
|
+
expectedBenefit: 8
|
|
1075
|
+
});
|
|
1076
|
+
|
|
1077
|
+
return options;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
private estimateQoLScore(toxicity: ToxicityPrediction): number {
|
|
1081
|
+
let score = 0.85; // Base quality of life
|
|
1082
|
+
|
|
1083
|
+
// Reduce based on toxicity severity
|
|
1084
|
+
score -= toxicity.grade3PlusRisk * 0.3;
|
|
1085
|
+
|
|
1086
|
+
// Specific high-impact toxicities
|
|
1087
|
+
for (const tox of toxicity.specificRisks) {
|
|
1088
|
+
if (tox.toxicity.toLowerCase().includes('neuropathy') && tox.grade >= 2) {
|
|
1089
|
+
score -= 0.1;
|
|
1090
|
+
}
|
|
1091
|
+
if (tox.toxicity.toLowerCase().includes('fatigue') && tox.grade >= 2) {
|
|
1092
|
+
score -= 0.08;
|
|
1093
|
+
}
|
|
1094
|
+
if (tox.toxicity.toLowerCase().includes('nausea') && tox.grade >= 2) {
|
|
1095
|
+
score -= 0.05;
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
return Math.max(score, 0.3);
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
private getMatchingBiomarkers(patient: PatientFeatures, treatment: TreatmentFeatures): string[] {
|
|
1103
|
+
const matches: string[] = [];
|
|
1104
|
+
|
|
1105
|
+
for (const alt of patient.genomicAlterations) {
|
|
1106
|
+
if (this.isBiomarkerRelevant(alt.gene, alt.alteration, treatment)) {
|
|
1107
|
+
matches.push(`${alt.gene} ${alt.alteration}`);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
if (patient.msiStatus === 'MSI-H' && this.hasImmunotherapyDrug(treatment.drugs)) {
|
|
1112
|
+
matches.push('MSI-H');
|
|
1113
|
+
}
|
|
1114
|
+
if (patient.tmbStatus === 'high' && this.hasImmunotherapyDrug(treatment.drugs)) {
|
|
1115
|
+
matches.push('TMB-High');
|
|
1116
|
+
}
|
|
1117
|
+
if (patient.hrdStatus === 'positive' && treatment.drugs.some(d =>
|
|
1118
|
+
['olaparib', 'rucaparib', 'niraparib', 'talazoparib'].some(p => d.toLowerCase().includes(p))
|
|
1119
|
+
)) {
|
|
1120
|
+
matches.push('HRD-positive');
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
return matches;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
private isBiomarkerRelevant(gene: string, alteration: string, treatment: TreatmentFeatures): boolean {
|
|
1127
|
+
const relevantPairs: Record<string, string[]> = {
|
|
1128
|
+
'EGFR': ['osimertinib', 'erlotinib', 'gefitinib', 'afatinib'],
|
|
1129
|
+
'ALK': ['alectinib', 'crizotinib', 'brigatinib', 'lorlatinib'],
|
|
1130
|
+
'ROS1': ['crizotinib', 'entrectinib'],
|
|
1131
|
+
'BRAF': ['dabrafenib', 'vemurafenib', 'encorafenib'],
|
|
1132
|
+
'HER2': ['trastuzumab', 'pertuzumab', 't-dxd'],
|
|
1133
|
+
'BRCA': ['olaparib', 'rucaparib', 'niraparib', 'talazoparib'],
|
|
1134
|
+
'KRAS': ['sotorasib', 'adagrasib'],
|
|
1135
|
+
'NTRK': ['larotrectinib', 'entrectinib'],
|
|
1136
|
+
'RET': ['selpercatinib', 'pralsetinib'],
|
|
1137
|
+
'MET': ['capmatinib', 'tepotinib']
|
|
1138
|
+
};
|
|
1139
|
+
|
|
1140
|
+
const drugs = relevantPairs[gene.toUpperCase()];
|
|
1141
|
+
if (!drugs) return false;
|
|
1142
|
+
|
|
1143
|
+
return treatment.drugs.some(d => drugs.some(rd => d.toLowerCase().includes(rd)));
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
private checkFDAApproval(treatment: TreatmentFeatures, cancerType: string): boolean {
|
|
1147
|
+
// Simplified FDA approval check - would reference actual database in production
|
|
1148
|
+
const approvedCombinations: Record<string, string[]> = {
|
|
1149
|
+
'NSCLC': ['osimertinib', 'pembrolizumab', 'alectinib', 'sotorasib'],
|
|
1150
|
+
'Breast': ['palbociclib', 'trastuzumab', 'olaparib', 'sacituzumab'],
|
|
1151
|
+
'Melanoma': ['pembrolizumab', 'nivolumab', 'ipilimumab', 'dabrafenib'],
|
|
1152
|
+
'Colorectal': ['pembrolizumab', 'cetuximab', 'bevacizumab'],
|
|
1153
|
+
'RCC': ['pembrolizumab', 'nivolumab', 'cabozantinib']
|
|
1154
|
+
};
|
|
1155
|
+
|
|
1156
|
+
const approvedDrugs = approvedCombinations[cancerType] || [];
|
|
1157
|
+
return treatment.drugs.some(d => approvedDrugs.some(ad => d.toLowerCase().includes(ad)));
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
private checkNCCNRecommendation(treatment: TreatmentFeatures, cancerType: string, stage: string): boolean {
|
|
1161
|
+
// Simplified NCCN check - would reference actual guidelines in production
|
|
1162
|
+
return this.checkFDAApproval(treatment, cancerType);
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
private getConsiderations(patient: PatientFeatures, treatment: TreatmentFeatures): string[] {
|
|
1166
|
+
const considerations: string[] = [];
|
|
1167
|
+
|
|
1168
|
+
if (patient.age > 75) {
|
|
1169
|
+
considerations.push('Consider dose reduction for elderly patient');
|
|
1170
|
+
}
|
|
1171
|
+
if (patient.ecogStatus >= 2) {
|
|
1172
|
+
considerations.push('Poor performance status may limit tolerability');
|
|
1173
|
+
}
|
|
1174
|
+
if (patient.organFunction?.renal === 'impaired') {
|
|
1175
|
+
considerations.push('Dose adjustment may be needed for renal impairment');
|
|
1176
|
+
}
|
|
1177
|
+
if (patient.priorLines && patient.priorLines >= 2) {
|
|
1178
|
+
considerations.push('Heavily pretreated - consider clinical trial');
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
return considerations;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
private generateOverallAssessment(
|
|
1185
|
+
patient: PatientFeatures,
|
|
1186
|
+
treatment: TreatmentFeatures,
|
|
1187
|
+
response: ResponsePrediction,
|
|
1188
|
+
survival: SurvivalPrediction,
|
|
1189
|
+
toxicity: ToxicityPrediction,
|
|
1190
|
+
resistance: ResistancePrediction
|
|
1191
|
+
): {
|
|
1192
|
+
recommendation: 'strongly-recommended' | 'recommended' | 'consider' | 'caution' | 'not-recommended';
|
|
1193
|
+
rationale: string[];
|
|
1194
|
+
caveats: string[];
|
|
1195
|
+
alternativeOptions: string[];
|
|
1196
|
+
} {
|
|
1197
|
+
const rationale: string[] = [];
|
|
1198
|
+
const caveats: string[] = [];
|
|
1199
|
+
const alternativeOptions: string[] = [];
|
|
1200
|
+
|
|
1201
|
+
// Calculate recommendation score
|
|
1202
|
+
let score = 50;
|
|
1203
|
+
|
|
1204
|
+
// Response contribution
|
|
1205
|
+
if (response.objectiveResponseRate >= 0.6) { score += 20; rationale.push('High expected response rate'); }
|
|
1206
|
+
else if (response.objectiveResponseRate >= 0.4) { score += 10; }
|
|
1207
|
+
else if (response.objectiveResponseRate < 0.2) { score -= 20; caveats.push('Low expected response rate'); }
|
|
1208
|
+
|
|
1209
|
+
// Survival contribution
|
|
1210
|
+
if (survival.pfs.twelveMonth >= 0.6) { score += 15; rationale.push('Favorable survival outlook'); }
|
|
1211
|
+
else if (survival.pfs.twelveMonth < 0.3) { score -= 15; caveats.push('Limited expected duration of benefit'); }
|
|
1212
|
+
|
|
1213
|
+
// Toxicity contribution
|
|
1214
|
+
if (toxicity.grade3PlusRisk < 0.2) { score += 10; rationale.push('Favorable toxicity profile'); }
|
|
1215
|
+
else if (toxicity.grade3PlusRisk > 0.5) { score -= 20; caveats.push('High risk of severe toxicity'); }
|
|
1216
|
+
|
|
1217
|
+
// Biomarker match
|
|
1218
|
+
if (this.hasBiomarkerMatch(patient, treatment)) {
|
|
1219
|
+
score += 15;
|
|
1220
|
+
rationale.push('Biomarker-matched therapy');
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
// FDA approval and guidelines
|
|
1224
|
+
if (this.checkFDAApproval(treatment, patient.cancerType)) {
|
|
1225
|
+
score += 10;
|
|
1226
|
+
rationale.push('FDA approved for indication');
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
// Add alternatives
|
|
1230
|
+
if (resistance.nextLineOptions.length > 0) {
|
|
1231
|
+
alternativeOptions.push(...resistance.nextLineOptions.slice(0, 2).map(o => o.therapy));
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
// Determine recommendation
|
|
1235
|
+
let recommendation: 'strongly-recommended' | 'recommended' | 'consider' | 'caution' | 'not-recommended';
|
|
1236
|
+
if (score >= 80) recommendation = 'strongly-recommended';
|
|
1237
|
+
else if (score >= 60) recommendation = 'recommended';
|
|
1238
|
+
else if (score >= 40) recommendation = 'consider';
|
|
1239
|
+
else if (score >= 20) recommendation = 'caution';
|
|
1240
|
+
else recommendation = 'not-recommended';
|
|
1241
|
+
|
|
1242
|
+
return { recommendation, rationale, caveats, alternativeOptions };
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
private getCacheKey(type: string, patient: PatientFeatures, treatment: TreatmentFeatures): string {
|
|
1246
|
+
const data = JSON.stringify({ type, patient, treatment });
|
|
1247
|
+
return createHash('sha256').update(data).digest('hex').substring(0, 16);
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
private getFromCache<T>(key: string): T | null {
|
|
1251
|
+
const cached = this.predictionCache.get(key);
|
|
1252
|
+
if (cached && Date.now() - cached.timestamp.getTime() < this.cacheDuration) {
|
|
1253
|
+
return cached.prediction as T;
|
|
1254
|
+
}
|
|
1255
|
+
return null;
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
private setCache(key: string, prediction: any): void {
|
|
1259
|
+
this.predictionCache.set(key, { prediction, timestamp: new Date() });
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
/**
|
|
1263
|
+
* Register a new model
|
|
1264
|
+
*/
|
|
1265
|
+
registerModel(model: MLModel): void {
|
|
1266
|
+
this.modelRegistry.set(model.id, model);
|
|
1267
|
+
this.emit('model-registered', model);
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
/**
|
|
1271
|
+
* Get model performance metrics
|
|
1272
|
+
*/
|
|
1273
|
+
getModelMetrics(modelId: string): MLModel['performance'] | undefined {
|
|
1274
|
+
return this.modelRegistry.get(modelId)?.performance;
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
/**
|
|
1278
|
+
* List all available models
|
|
1279
|
+
*/
|
|
1280
|
+
listModels(): MLModel[] {
|
|
1281
|
+
return Array.from(this.modelRegistry.values());
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1286
|
+
// FEATURE ENCODER
|
|
1287
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1288
|
+
|
|
1289
|
+
class FeatureEncoder {
|
|
1290
|
+
private mapping: Record<string, number>;
|
|
1291
|
+
|
|
1292
|
+
constructor(mapping: Record<string, number>) {
|
|
1293
|
+
this.mapping = mapping;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
encode(value: string): number {
|
|
1297
|
+
return this.mapping[value] ?? 0;
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
export default OutcomePredictorService;
|