@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.
Files changed (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +120 -0
  3. package/dist/bin/cure.d.ts +10 -0
  4. package/dist/bin/cure.d.ts.map +1 -0
  5. package/dist/bin/cure.js +169 -0
  6. package/dist/bin/cure.js.map +1 -0
  7. package/dist/capabilities/cancerTreatmentCapability.d.ts +167 -0
  8. package/dist/capabilities/cancerTreatmentCapability.d.ts.map +1 -0
  9. package/dist/capabilities/cancerTreatmentCapability.js +912 -0
  10. package/dist/capabilities/cancerTreatmentCapability.js.map +1 -0
  11. package/dist/capabilities/index.d.ts +2 -0
  12. package/dist/capabilities/index.d.ts.map +1 -0
  13. package/dist/capabilities/index.js +3 -0
  14. package/dist/capabilities/index.js.map +1 -0
  15. package/dist/compliance/hipaa.d.ts +337 -0
  16. package/dist/compliance/hipaa.d.ts.map +1 -0
  17. package/dist/compliance/hipaa.js +929 -0
  18. package/dist/compliance/hipaa.js.map +1 -0
  19. package/dist/examples/cancerTreatmentDemo.d.ts +21 -0
  20. package/dist/examples/cancerTreatmentDemo.d.ts.map +1 -0
  21. package/dist/examples/cancerTreatmentDemo.js +216 -0
  22. package/dist/examples/cancerTreatmentDemo.js.map +1 -0
  23. package/dist/integrations/clinicalTrials/clinicalTrialsGov.d.ts +265 -0
  24. package/dist/integrations/clinicalTrials/clinicalTrialsGov.d.ts.map +1 -0
  25. package/dist/integrations/clinicalTrials/clinicalTrialsGov.js +808 -0
  26. package/dist/integrations/clinicalTrials/clinicalTrialsGov.js.map +1 -0
  27. package/dist/integrations/ehr/fhir.d.ts +455 -0
  28. package/dist/integrations/ehr/fhir.d.ts.map +1 -0
  29. package/dist/integrations/ehr/fhir.js +859 -0
  30. package/dist/integrations/ehr/fhir.js.map +1 -0
  31. package/dist/integrations/genomics/genomicPlatforms.d.ts +362 -0
  32. package/dist/integrations/genomics/genomicPlatforms.d.ts.map +1 -0
  33. package/dist/integrations/genomics/genomicPlatforms.js +1079 -0
  34. package/dist/integrations/genomics/genomicPlatforms.js.map +1 -0
  35. package/package.json +52 -0
  36. package/src/bin/cure.ts +182 -0
  37. package/src/capabilities/cancerTreatmentCapability.ts +1161 -0
  38. package/src/capabilities/index.ts +2 -0
  39. package/src/compliance/hipaa.ts +1365 -0
  40. package/src/examples/cancerTreatmentDemo.ts +241 -0
  41. package/src/integrations/clinicalTrials/clinicalTrialsGov.ts +1143 -0
  42. package/src/integrations/ehr/fhir.ts +1304 -0
  43. package/src/integrations/genomics/genomicPlatforms.ts +1480 -0
  44. package/src/ml/outcomePredictor.ts +1301 -0
  45. package/src/safety/drugInteractions.ts +942 -0
  46. package/src/validation/retrospectiveValidator.ts +887 -0
@@ -0,0 +1,1480 @@
1
+ /**
2
+ * Genomic Testing Platform Integrations
3
+ *
4
+ * Connects to major genomic testing providers:
5
+ * - Foundation Medicine (FoundationOne CDx, FoundationOne Liquid CDx)
6
+ * - Guardant Health (Guardant360, GuardantOMNI)
7
+ * - Tempus (xT, xF, xR panels)
8
+ * - Caris Life Sciences (Caris Molecular Intelligence)
9
+ * - NeoGenomics
10
+ *
11
+ * All data handling complies with HIPAA and CAP/CLIA requirements.
12
+ */
13
+
14
+ import { EventEmitter } from 'events';
15
+
16
+ // ═══════════════════════════════════════════════════════════════════════════════
17
+ // COMMON GENOMIC DATA TYPES
18
+ // ═══════════════════════════════════════════════════════════════════════════════
19
+
20
+ export interface GenomicTestOrder {
21
+ orderId: string;
22
+ patientId: string;
23
+ testType: string;
24
+ panelName: string;
25
+ specimenType: 'tissue' | 'blood' | 'bone-marrow' | 'other';
26
+ specimenId?: string;
27
+ orderDate: Date;
28
+ clinicalIndication: string;
29
+ icdCodes: string[];
30
+ orderingPhysician: {
31
+ name: string;
32
+ npi: string;
33
+ facility: string;
34
+ };
35
+ status: 'ordered' | 'specimen-received' | 'in-process' | 'completed' | 'failed' | 'cancelled';
36
+ }
37
+
38
+ export interface GenomicTestResult {
39
+ reportId: string;
40
+ orderId: string;
41
+ patientId: string;
42
+ testType: string;
43
+ panelName: string;
44
+ specimenInfo: {
45
+ type: 'tissue' | 'blood' | 'bone-marrow' | 'other';
46
+ site?: string;
47
+ collectionDate: Date;
48
+ tumorPurity?: number;
49
+ cellularity?: number;
50
+ };
51
+ reportDate: Date;
52
+ variants: GenomicVariant[];
53
+ copyNumberAlterations: CopyNumberAlteration[];
54
+ fusions: GeneFusion[];
55
+ biomarkers: GenomicBiomarker[];
56
+ therapyMatches: TherapyMatch[];
57
+ clinicalTrialMatches: ClinicalTrialMatch[];
58
+ signatures?: {
59
+ microsatelliteInstability?: MSIResult;
60
+ tumorMutationalBurden?: TMBResult;
61
+ homologousRecombinationDeficiency?: HRDResult;
62
+ lossOfHeterozygosity?: LOHResult;
63
+ };
64
+ qualityMetrics: {
65
+ meanCoverage?: number;
66
+ percentBases100x?: number;
67
+ tumorFraction?: number;
68
+ contamination?: number;
69
+ mappingRate?: number;
70
+ };
71
+ reportPdf?: string; // Base64 encoded PDF
72
+ rawData?: {
73
+ vcfUrl?: string;
74
+ bamUrl?: string;
75
+ fastqUrl?: string;
76
+ };
77
+ }
78
+
79
+ export interface GenomicVariant {
80
+ gene: string;
81
+ hgvsC?: string; // cDNA change
82
+ hgvsP?: string; // Protein change
83
+ transcript?: string;
84
+ chromosome?: string;
85
+ position?: number;
86
+ refAllele?: string;
87
+ altAllele?: string;
88
+ variantType: 'SNV' | 'insertion' | 'deletion' | 'indel' | 'MNV' | 'complex';
89
+ variantAlleleFrequency: number;
90
+ coverage?: number;
91
+ zygosity?: 'heterozygous' | 'homozygous' | 'hemizygous';
92
+ clinicalSignificance: 'pathogenic' | 'likely-pathogenic' | 'vus' | 'likely-benign' | 'benign';
93
+ tier?: 'I' | 'II' | 'III' | 'IV'; // AMP/ASCO/CAP tiering
94
+ oncogenicity?: 'oncogenic' | 'likely-oncogenic' | 'vus' | 'likely-neutral' | 'neutral';
95
+ functionalEffect?: 'loss-of-function' | 'gain-of-function' | 'switch-of-function' | 'unknown';
96
+ somaticStatus?: 'somatic' | 'germline' | 'unknown';
97
+ actionability?: {
98
+ level: 'FDA-approved' | 'clinical-guideline' | 'clinical-evidence' | 'preclinical';
99
+ therapies: string[];
100
+ evidence: string[];
101
+ };
102
+ annotations?: {
103
+ cosmic?: string;
104
+ dbSNP?: string;
105
+ clinVar?: string;
106
+ gnomAD?: { frequency: number; popMax?: number };
107
+ oncokb?: { level: string; description: string };
108
+ };
109
+ }
110
+
111
+ export interface CopyNumberAlteration {
112
+ gene: string;
113
+ chromosome?: string;
114
+ startPosition?: number;
115
+ endPosition?: number;
116
+ type: 'amplification' | 'gain' | 'loss' | 'deep-deletion';
117
+ copyNumber?: number;
118
+ logRatio?: number;
119
+ clinicalSignificance: 'pathogenic' | 'likely-pathogenic' | 'vus' | 'likely-benign' | 'benign';
120
+ actionability?: {
121
+ level: 'FDA-approved' | 'clinical-guideline' | 'clinical-evidence' | 'preclinical';
122
+ therapies: string[];
123
+ };
124
+ }
125
+
126
+ export interface GeneFusion {
127
+ gene5Prime: string;
128
+ gene3Prime: string;
129
+ fusionName: string;
130
+ breakpoint5Prime?: string;
131
+ breakpoint3Prime?: string;
132
+ readsSupporting?: number;
133
+ inFrame?: boolean;
134
+ clinicalSignificance: 'pathogenic' | 'likely-pathogenic' | 'vus';
135
+ actionability?: {
136
+ level: 'FDA-approved' | 'clinical-guideline' | 'clinical-evidence' | 'preclinical';
137
+ therapies: string[];
138
+ };
139
+ }
140
+
141
+ export interface GenomicBiomarker {
142
+ name: string;
143
+ value: number | string;
144
+ unit?: string;
145
+ status: 'positive' | 'negative' | 'equivocal' | 'indeterminate';
146
+ threshold?: number;
147
+ method?: string;
148
+ clinicalImplication?: string;
149
+ }
150
+
151
+ export interface MSIResult {
152
+ status: 'MSI-H' | 'MSI-L' | 'MSS' | 'indeterminate';
153
+ score?: number;
154
+ markersAnalyzed?: number;
155
+ markersUnstable?: number;
156
+ method: 'NGS' | 'PCR' | 'IHC';
157
+ }
158
+
159
+ export interface TMBResult {
160
+ value: number;
161
+ unit: 'mutations/Mb';
162
+ status: 'high' | 'intermediate' | 'low';
163
+ threshold: number;
164
+ percentile?: number;
165
+ }
166
+
167
+ export interface HRDResult {
168
+ status: 'positive' | 'negative' | 'indeterminate';
169
+ score?: number;
170
+ components?: {
171
+ loh?: number;
172
+ tai?: number;
173
+ lst?: number;
174
+ };
175
+ brcaStatus?: 'BRCA1-mut' | 'BRCA2-mut' | 'BRCA-wt';
176
+ }
177
+
178
+ export interface LOHResult {
179
+ percentage: number;
180
+ status: 'high' | 'intermediate' | 'low';
181
+ genomeFraction?: number;
182
+ }
183
+
184
+ export interface TherapyMatch {
185
+ therapy: string;
186
+ drugs: string[];
187
+ biomarkers: string[];
188
+ evidenceLevel: 'FDA-approved' | 'NCCN-guideline' | 'clinical-evidence' | 'case-report' | 'preclinical';
189
+ cancerType: string;
190
+ approvalStatus?: string;
191
+ clinicalTrials?: string[];
192
+ responseRate?: number;
193
+ references: string[];
194
+ }
195
+
196
+ export interface ClinicalTrialMatch {
197
+ trialId: string; // NCT number
198
+ title: string;
199
+ phase: 'I' | 'I/II' | 'II' | 'II/III' | 'III' | 'IV';
200
+ matchingBiomarkers: string[];
201
+ status: 'recruiting' | 'active-not-recruiting' | 'enrolling-by-invitation';
202
+ locations?: { name: string; city: string; state: string; country: string }[];
203
+ sponsor?: string;
204
+ drugs?: string[];
205
+ }
206
+
207
+ // ═══════════════════════════════════════════════════════════════════════════════
208
+ // PLATFORM CONFIGURATION
209
+ // ═══════════════════════════════════════════════════════════════════════════════
210
+
211
+ export interface GenomicPlatformConfig {
212
+ platform: 'foundation-medicine' | 'guardant' | 'tempus' | 'caris' | 'neogenomics' | 'generic';
213
+ apiBaseUrl: string;
214
+ apiKey?: string;
215
+ clientId?: string;
216
+ clientSecret?: string;
217
+ organizationId?: string;
218
+ webhookUrl?: string;
219
+ timeout?: number;
220
+ }
221
+
222
+ // ═══════════════════════════════════════════════════════════════════════════════
223
+ // ABSTRACT GENOMIC PLATFORM CLIENT
224
+ // ═══════════════════════════════════════════════════════════════════════════════
225
+
226
+ export abstract class GenomicPlatformClient extends EventEmitter {
227
+ protected config: GenomicPlatformConfig;
228
+ protected accessToken?: string;
229
+ protected tokenExpiry?: Date;
230
+
231
+ constructor(config: GenomicPlatformConfig) {
232
+ super();
233
+ this.config = {
234
+ timeout: 60000,
235
+ ...config
236
+ };
237
+ }
238
+
239
+ abstract authenticate(): Promise<void>;
240
+ abstract submitOrder(order: GenomicTestOrder): Promise<{ orderId: string; status: string }>;
241
+ abstract getOrderStatus(orderId: string): Promise<GenomicTestOrder>;
242
+ abstract getResults(orderId: string): Promise<GenomicTestResult>;
243
+ abstract listPatientResults(patientId: string): Promise<GenomicTestResult[]>;
244
+
245
+ protected async httpRequest(url: string, options: {
246
+ method: string;
247
+ headers?: Record<string, string>;
248
+ body?: string;
249
+ }): Promise<string> {
250
+ const response = await fetch(url, {
251
+ method: options.method,
252
+ headers: {
253
+ 'Content-Type': 'application/json',
254
+ ...options.headers
255
+ },
256
+ body: options.body,
257
+ signal: AbortSignal.timeout(this.config.timeout || 60000)
258
+ });
259
+
260
+ if (!response.ok) {
261
+ const errorBody = await response.text();
262
+ throw new Error(`API request failed: ${response.status} ${response.statusText} - ${errorBody}`);
263
+ }
264
+
265
+ return await response.text();
266
+ }
267
+
268
+ protected async getAuthHeaders(): Promise<Record<string, string>> {
269
+ if (this.tokenExpiry && new Date() >= this.tokenExpiry) {
270
+ await this.authenticate();
271
+ }
272
+
273
+ if (this.accessToken) {
274
+ return { 'Authorization': `Bearer ${this.accessToken}` };
275
+ }
276
+
277
+ if (this.config.apiKey) {
278
+ return { 'X-API-Key': this.config.apiKey };
279
+ }
280
+
281
+ return {};
282
+ }
283
+ }
284
+
285
+ // ═══════════════════════════════════════════════════════════════════════════════
286
+ // FOUNDATION MEDICINE CLIENT
287
+ // ═══════════════════════════════════════════════════════════════════════════════
288
+
289
+ export class FoundationMedicineClient extends GenomicPlatformClient {
290
+ constructor(config: Omit<GenomicPlatformConfig, 'platform'>) {
291
+ super({ ...config, platform: 'foundation-medicine' });
292
+ }
293
+
294
+ async authenticate(): Promise<void> {
295
+ const tokenUrl = `${this.config.apiBaseUrl}/oauth/token`;
296
+
297
+ const response = await this.httpRequest(tokenUrl, {
298
+ method: 'POST',
299
+ headers: {
300
+ 'Content-Type': 'application/x-www-form-urlencoded'
301
+ },
302
+ body: new URLSearchParams({
303
+ grant_type: 'client_credentials',
304
+ client_id: this.config.clientId || '',
305
+ client_secret: this.config.clientSecret || ''
306
+ }).toString()
307
+ });
308
+
309
+ const data = JSON.parse(response);
310
+ this.accessToken = data.access_token;
311
+ this.tokenExpiry = new Date(Date.now() + (data.expires_in * 1000));
312
+ }
313
+
314
+ async submitOrder(order: GenomicTestOrder): Promise<{ orderId: string; status: string }> {
315
+ const url = `${this.config.apiBaseUrl}/v1/orders`;
316
+
317
+ const fmiOrder = this.mapToFMIOrder(order);
318
+
319
+ const response = await this.httpRequest(url, {
320
+ method: 'POST',
321
+ headers: await this.getAuthHeaders(),
322
+ body: JSON.stringify(fmiOrder)
323
+ });
324
+
325
+ const result = JSON.parse(response);
326
+ return {
327
+ orderId: result.orderId || result.id,
328
+ status: result.status || 'submitted'
329
+ };
330
+ }
331
+
332
+ async getOrderStatus(orderId: string): Promise<GenomicTestOrder> {
333
+ const url = `${this.config.apiBaseUrl}/v1/orders/${orderId}`;
334
+
335
+ const response = await this.httpRequest(url, {
336
+ method: 'GET',
337
+ headers: await this.getAuthHeaders()
338
+ });
339
+
340
+ const fmiOrder = JSON.parse(response);
341
+ return this.mapFromFMIOrder(fmiOrder);
342
+ }
343
+
344
+ async getResults(orderId: string): Promise<GenomicTestResult> {
345
+ const url = `${this.config.apiBaseUrl}/v1/orders/${orderId}/report`;
346
+
347
+ const response = await this.httpRequest(url, {
348
+ method: 'GET',
349
+ headers: await this.getAuthHeaders()
350
+ });
351
+
352
+ const fmiReport = JSON.parse(response);
353
+ return this.mapFromFMIReport(fmiReport, orderId);
354
+ }
355
+
356
+ async listPatientResults(patientId: string): Promise<GenomicTestResult[]> {
357
+ const url = `${this.config.apiBaseUrl}/v1/patients/${patientId}/reports`;
358
+
359
+ const response = await this.httpRequest(url, {
360
+ method: 'GET',
361
+ headers: await this.getAuthHeaders()
362
+ });
363
+
364
+ const reports = JSON.parse(response);
365
+ return reports.map((r: any) => this.mapFromFMIReport(r, r.orderId));
366
+ }
367
+
368
+ /**
369
+ * Get FoundationOne CDx specific data
370
+ */
371
+ async getFoundationOneCDxReport(orderId: string): Promise<{
372
+ variants: GenomicVariant[];
373
+ cnvs: CopyNumberAlteration[];
374
+ fusions: GeneFusion[];
375
+ msi: MSIResult;
376
+ tmb: TMBResult;
377
+ loh: LOHResult;
378
+ therapies: TherapyMatch[];
379
+ }> {
380
+ const result = await this.getResults(orderId);
381
+
382
+ return {
383
+ variants: result.variants,
384
+ cnvs: result.copyNumberAlterations,
385
+ fusions: result.fusions,
386
+ msi: result.signatures?.microsatelliteInstability || { status: 'MSS', method: 'NGS' },
387
+ tmb: result.signatures?.tumorMutationalBurden || { value: 0, unit: 'mutations/Mb', status: 'low', threshold: 10 },
388
+ loh: result.signatures?.lossOfHeterozygosity || { percentage: 0, status: 'low' },
389
+ therapies: result.therapyMatches
390
+ };
391
+ }
392
+
393
+ private mapToFMIOrder(order: GenomicTestOrder): Record<string, any> {
394
+ return {
395
+ externalOrderId: order.orderId,
396
+ patient: {
397
+ externalId: order.patientId
398
+ },
399
+ specimen: {
400
+ type: order.specimenType,
401
+ externalId: order.specimenId
402
+ },
403
+ test: {
404
+ code: this.mapPanelToFMICode(order.panelName)
405
+ },
406
+ diagnosis: {
407
+ icdCodes: order.icdCodes,
408
+ clinicalHistory: order.clinicalIndication
409
+ },
410
+ orderingPhysician: {
411
+ name: order.orderingPhysician.name,
412
+ npi: order.orderingPhysician.npi,
413
+ facility: order.orderingPhysician.facility
414
+ }
415
+ };
416
+ }
417
+
418
+ private mapPanelToFMICode(panelName: string): string {
419
+ const mapping: Record<string, string> = {
420
+ 'FoundationOne CDx': 'F1CDX',
421
+ 'FoundationOne Liquid CDx': 'F1LCDX',
422
+ 'FoundationOne Heme': 'F1HEME',
423
+ 'FoundationACT': 'FACT'
424
+ };
425
+ return mapping[panelName] || 'F1CDX';
426
+ }
427
+
428
+ private mapFromFMIOrder(fmiOrder: any): GenomicTestOrder {
429
+ return {
430
+ orderId: fmiOrder.externalOrderId || fmiOrder.id,
431
+ patientId: fmiOrder.patient?.externalId,
432
+ testType: 'comprehensive-genomic-profiling',
433
+ panelName: this.mapFMICodeToPanel(fmiOrder.test?.code),
434
+ specimenType: fmiOrder.specimen?.type || 'tissue',
435
+ specimenId: fmiOrder.specimen?.externalId,
436
+ orderDate: new Date(fmiOrder.createdAt),
437
+ clinicalIndication: fmiOrder.diagnosis?.clinicalHistory || '',
438
+ icdCodes: fmiOrder.diagnosis?.icdCodes || [],
439
+ orderingPhysician: {
440
+ name: fmiOrder.orderingPhysician?.name || '',
441
+ npi: fmiOrder.orderingPhysician?.npi || '',
442
+ facility: fmiOrder.orderingPhysician?.facility || ''
443
+ },
444
+ status: this.mapFMIStatus(fmiOrder.status)
445
+ };
446
+ }
447
+
448
+ private mapFMICodeToPanel(code: string): string {
449
+ const mapping: Record<string, string> = {
450
+ 'F1CDX': 'FoundationOne CDx',
451
+ 'F1LCDX': 'FoundationOne Liquid CDx',
452
+ 'F1HEME': 'FoundationOne Heme',
453
+ 'FACT': 'FoundationACT'
454
+ };
455
+ return mapping[code] || 'FoundationOne CDx';
456
+ }
457
+
458
+ private mapFMIStatus(status: string): GenomicTestOrder['status'] {
459
+ const mapping: Record<string, GenomicTestOrder['status']> = {
460
+ 'ORDERED': 'ordered',
461
+ 'SPECIMEN_RECEIVED': 'specimen-received',
462
+ 'IN_PROCESS': 'in-process',
463
+ 'COMPLETE': 'completed',
464
+ 'FAILED': 'failed',
465
+ 'CANCELLED': 'cancelled'
466
+ };
467
+ return mapping[status] || 'ordered';
468
+ }
469
+
470
+ private mapFromFMIReport(fmiReport: any, orderId: string): GenomicTestResult {
471
+ const variants: GenomicVariant[] = (fmiReport.shortVariants || []).map((v: any) => ({
472
+ gene: v.gene,
473
+ hgvsC: v.cdsEffect,
474
+ hgvsP: v.proteinEffect,
475
+ transcript: v.transcript,
476
+ variantType: this.mapVariantType(v.variantType),
477
+ variantAlleleFrequency: v.alleleFrequency || 0,
478
+ coverage: v.depth,
479
+ clinicalSignificance: this.mapPathogenicity(v.pathogenicity),
480
+ tier: this.mapTier(v.tier),
481
+ actionability: v.therapies ? {
482
+ level: v.fdaApproved ? 'FDA-approved' : 'clinical-evidence',
483
+ therapies: v.therapies,
484
+ evidence: v.evidenceSummary ? [v.evidenceSummary] : []
485
+ } : undefined
486
+ }));
487
+
488
+ const cnvs: CopyNumberAlteration[] = (fmiReport.copyNumberAlterations || []).map((c: any) => ({
489
+ gene: c.gene,
490
+ type: c.type?.toLowerCase() === 'amplification' ? 'amplification' : 'deep-deletion',
491
+ copyNumber: c.copyNumber,
492
+ clinicalSignificance: this.mapPathogenicity(c.pathogenicity),
493
+ actionability: c.therapies ? {
494
+ level: c.fdaApproved ? 'FDA-approved' : 'clinical-evidence',
495
+ therapies: c.therapies
496
+ } : undefined
497
+ }));
498
+
499
+ const fusions: GeneFusion[] = (fmiReport.rearrangements || []).map((r: any) => ({
500
+ gene5Prime: r.gene1,
501
+ gene3Prime: r.gene2,
502
+ fusionName: `${r.gene1}-${r.gene2}`,
503
+ inFrame: r.inFrame,
504
+ clinicalSignificance: this.mapPathogenicity(r.pathogenicity),
505
+ actionability: r.therapies ? {
506
+ level: r.fdaApproved ? 'FDA-approved' : 'clinical-evidence',
507
+ therapies: r.therapies
508
+ } : undefined
509
+ }));
510
+
511
+ return {
512
+ reportId: fmiReport.reportId || fmiReport.id,
513
+ orderId,
514
+ patientId: fmiReport.patient?.externalId,
515
+ testType: 'comprehensive-genomic-profiling',
516
+ panelName: this.mapFMICodeToPanel(fmiReport.testCode),
517
+ specimenInfo: {
518
+ type: fmiReport.specimen?.type || 'tissue',
519
+ site: fmiReport.specimen?.site,
520
+ collectionDate: new Date(fmiReport.specimen?.collectionDate || Date.now()),
521
+ tumorPurity: fmiReport.specimen?.tumorPurity,
522
+ cellularity: fmiReport.specimen?.cellularity
523
+ },
524
+ reportDate: new Date(fmiReport.reportDate || Date.now()),
525
+ variants,
526
+ copyNumberAlterations: cnvs,
527
+ fusions,
528
+ biomarkers: [],
529
+ therapyMatches: (fmiReport.therapyMatches || []).map((t: any) => ({
530
+ therapy: t.therapy,
531
+ drugs: t.drugs || [t.therapy],
532
+ biomarkers: t.biomarkers || [],
533
+ evidenceLevel: t.fdaApproved ? 'FDA-approved' : 'clinical-evidence',
534
+ cancerType: t.indication || '',
535
+ references: t.references || []
536
+ })),
537
+ clinicalTrialMatches: (fmiReport.clinicalTrials || []).map((ct: any) => ({
538
+ trialId: ct.nctId,
539
+ title: ct.title,
540
+ phase: ct.phase,
541
+ matchingBiomarkers: ct.matchingBiomarkers || [],
542
+ status: ct.status || 'recruiting'
543
+ })),
544
+ signatures: {
545
+ microsatelliteInstability: fmiReport.msi ? {
546
+ status: fmiReport.msi.status,
547
+ score: fmiReport.msi.score,
548
+ method: 'NGS'
549
+ } : undefined,
550
+ tumorMutationalBurden: fmiReport.tmb ? {
551
+ value: fmiReport.tmb.score,
552
+ unit: 'mutations/Mb',
553
+ status: fmiReport.tmb.score >= 10 ? 'high' : fmiReport.tmb.score >= 6 ? 'intermediate' : 'low',
554
+ threshold: 10,
555
+ percentile: fmiReport.tmb.percentile
556
+ } : undefined,
557
+ lossOfHeterozygosity: fmiReport.loh ? {
558
+ percentage: fmiReport.loh.percentage,
559
+ status: fmiReport.loh.percentage >= 16 ? 'high' : 'low',
560
+ genomeFraction: fmiReport.loh.genomeFraction
561
+ } : undefined
562
+ },
563
+ qualityMetrics: {
564
+ meanCoverage: fmiReport.qc?.meanCoverage,
565
+ percentBases100x: fmiReport.qc?.percentBases100x,
566
+ tumorFraction: fmiReport.qc?.tumorFraction
567
+ }
568
+ };
569
+ }
570
+
571
+ private mapVariantType(type: string): GenomicVariant['variantType'] {
572
+ const mapping: Record<string, GenomicVariant['variantType']> = {
573
+ 'SNV': 'SNV',
574
+ 'INSERTION': 'insertion',
575
+ 'DELETION': 'deletion',
576
+ 'INDEL': 'indel',
577
+ 'MNV': 'MNV'
578
+ };
579
+ return mapping[type?.toUpperCase()] || 'SNV';
580
+ }
581
+
582
+ private mapPathogenicity(path: string): GenomicVariant['clinicalSignificance'] {
583
+ const mapping: Record<string, GenomicVariant['clinicalSignificance']> = {
584
+ 'PATHOGENIC': 'pathogenic',
585
+ 'LIKELY_PATHOGENIC': 'likely-pathogenic',
586
+ 'VUS': 'vus',
587
+ 'LIKELY_BENIGN': 'likely-benign',
588
+ 'BENIGN': 'benign'
589
+ };
590
+ return mapping[path?.toUpperCase()] || 'vus';
591
+ }
592
+
593
+ private mapTier(tier: string): GenomicVariant['tier'] {
594
+ if (tier === '1' || tier === 'I') return 'I';
595
+ if (tier === '2' || tier === 'II') return 'II';
596
+ if (tier === '3' || tier === 'III') return 'III';
597
+ return 'IV';
598
+ }
599
+ }
600
+
601
+ // ═══════════════════════════════════════════════════════════════════════════════
602
+ // GUARDANT HEALTH CLIENT
603
+ // ═══════════════════════════════════════════════════════════════════════════════
604
+
605
+ export class GuardantHealthClient extends GenomicPlatformClient {
606
+ constructor(config: Omit<GenomicPlatformConfig, 'platform'>) {
607
+ super({ ...config, platform: 'guardant' });
608
+ }
609
+
610
+ async authenticate(): Promise<void> {
611
+ // Guardant uses API key authentication
612
+ if (!this.config.apiKey) {
613
+ throw new Error('Guardant Health API requires an API key');
614
+ }
615
+ // No token fetch needed for API key auth
616
+ }
617
+
618
+ async submitOrder(order: GenomicTestOrder): Promise<{ orderId: string; status: string }> {
619
+ const url = `${this.config.apiBaseUrl}/v2/orders`;
620
+
621
+ const guardantOrder = {
622
+ externalOrderId: order.orderId,
623
+ patient: {
624
+ externalId: order.patientId
625
+ },
626
+ test: this.mapPanelToGuardantTest(order.panelName),
627
+ specimen: {
628
+ type: 'blood', // Guardant is primarily liquid biopsy
629
+ collectionDate: new Date().toISOString()
630
+ },
631
+ diagnosis: {
632
+ icdCodes: order.icdCodes,
633
+ cancerType: order.clinicalIndication
634
+ },
635
+ orderingProvider: {
636
+ name: order.orderingPhysician.name,
637
+ npi: order.orderingPhysician.npi,
638
+ organization: order.orderingPhysician.facility
639
+ }
640
+ };
641
+
642
+ const response = await this.httpRequest(url, {
643
+ method: 'POST',
644
+ headers: {
645
+ ...await this.getAuthHeaders()
646
+ },
647
+ body: JSON.stringify(guardantOrder)
648
+ });
649
+
650
+ const result = JSON.parse(response);
651
+ return {
652
+ orderId: result.orderId,
653
+ status: 'ordered'
654
+ };
655
+ }
656
+
657
+ async getOrderStatus(orderId: string): Promise<GenomicTestOrder> {
658
+ const url = `${this.config.apiBaseUrl}/v2/orders/${orderId}`;
659
+
660
+ const response = await this.httpRequest(url, {
661
+ method: 'GET',
662
+ headers: await this.getAuthHeaders()
663
+ });
664
+
665
+ const data = JSON.parse(response);
666
+ return this.mapFromGuardantOrder(data);
667
+ }
668
+
669
+ async getResults(orderId: string): Promise<GenomicTestResult> {
670
+ const url = `${this.config.apiBaseUrl}/v2/orders/${orderId}/results`;
671
+
672
+ const response = await this.httpRequest(url, {
673
+ method: 'GET',
674
+ headers: await this.getAuthHeaders()
675
+ });
676
+
677
+ const data = JSON.parse(response);
678
+ return this.mapFromGuardantReport(data, orderId);
679
+ }
680
+
681
+ async listPatientResults(patientId: string): Promise<GenomicTestResult[]> {
682
+ const url = `${this.config.apiBaseUrl}/v2/patients/${patientId}/results`;
683
+
684
+ const response = await this.httpRequest(url, {
685
+ method: 'GET',
686
+ headers: await this.getAuthHeaders()
687
+ });
688
+
689
+ const reports = JSON.parse(response);
690
+ return reports.map((r: any) => this.mapFromGuardantReport(r, r.orderId));
691
+ }
692
+
693
+ /**
694
+ * Get Guardant360 specific metrics including ctDNA fraction
695
+ */
696
+ async getGuardant360Metrics(orderId: string): Promise<{
697
+ ctDNAFraction: number;
698
+ maxMAF: number;
699
+ somatic: GenomicVariant[];
700
+ clonalHematopoiesis: GenomicVariant[];
701
+ msi: MSIResult;
702
+ }> {
703
+ const result = await this.getResults(orderId);
704
+
705
+ // Filter for clonal hematopoiesis variants (common in blood-based testing)
706
+ const chVariants = result.variants.filter(v =>
707
+ ['DNMT3A', 'TET2', 'ASXL1', 'PPM1D', 'TP53', 'SF3B1', 'SRSF2'].includes(v.gene) &&
708
+ v.variantAlleleFrequency < 0.1
709
+ );
710
+
711
+ const somaticVariants = result.variants.filter(v =>
712
+ !chVariants.includes(v)
713
+ );
714
+
715
+ return {
716
+ ctDNAFraction: result.qualityMetrics.tumorFraction || 0,
717
+ maxMAF: Math.max(...result.variants.map(v => v.variantAlleleFrequency), 0),
718
+ somatic: somaticVariants,
719
+ clonalHematopoiesis: chVariants,
720
+ msi: result.signatures?.microsatelliteInstability || { status: 'MSS', method: 'NGS' }
721
+ };
722
+ }
723
+
724
+ private mapPanelToGuardantTest(panelName: string): string {
725
+ const mapping: Record<string, string> = {
726
+ 'Guardant360': 'G360',
727
+ 'Guardant360 CDx': 'G360CDX',
728
+ 'GuardantOMNI': 'GOMNI',
729
+ 'Guardant360 TissueNext': 'G360TN',
730
+ 'GuardantReveal': 'GREVEAL'
731
+ };
732
+ return mapping[panelName] || 'G360';
733
+ }
734
+
735
+ private mapFromGuardantOrder(data: any): GenomicTestOrder {
736
+ return {
737
+ orderId: data.externalOrderId || data.orderId,
738
+ patientId: data.patient?.externalId,
739
+ testType: 'liquid-biopsy',
740
+ panelName: 'Guardant360',
741
+ specimenType: 'blood',
742
+ specimenId: data.specimen?.id,
743
+ orderDate: new Date(data.orderDate),
744
+ clinicalIndication: data.diagnosis?.cancerType || '',
745
+ icdCodes: data.diagnosis?.icdCodes || [],
746
+ orderingPhysician: {
747
+ name: data.orderingProvider?.name || '',
748
+ npi: data.orderingProvider?.npi || '',
749
+ facility: data.orderingProvider?.organization || ''
750
+ },
751
+ status: this.mapGuardantStatus(data.status)
752
+ };
753
+ }
754
+
755
+ private mapGuardantStatus(status: string): GenomicTestOrder['status'] {
756
+ const mapping: Record<string, GenomicTestOrder['status']> = {
757
+ 'ORDERED': 'ordered',
758
+ 'RECEIVED': 'specimen-received',
759
+ 'PROCESSING': 'in-process',
760
+ 'COMPLETE': 'completed',
761
+ 'FAILED': 'failed',
762
+ 'CANCELLED': 'cancelled'
763
+ };
764
+ return mapping[status?.toUpperCase()] || 'ordered';
765
+ }
766
+
767
+ private mapFromGuardantReport(data: any, orderId: string): GenomicTestResult {
768
+ const variants: GenomicVariant[] = (data.alterations || [])
769
+ .filter((a: any) => a.type === 'SNV' || a.type === 'INDEL')
770
+ .map((v: any) => ({
771
+ gene: v.gene,
772
+ hgvsP: v.proteinChange,
773
+ hgvsC: v.cdsChange,
774
+ variantType: v.type === 'INDEL' ? 'indel' : 'SNV',
775
+ variantAlleleFrequency: v.plasmaAF || v.af || 0,
776
+ clinicalSignificance: 'likely-pathogenic',
777
+ actionability: v.therapies ? {
778
+ level: v.fdaApproved ? 'FDA-approved' : 'clinical-evidence',
779
+ therapies: v.therapies,
780
+ evidence: []
781
+ } : undefined
782
+ }));
783
+
784
+ const cnvs: CopyNumberAlteration[] = (data.alterations || [])
785
+ .filter((a: any) => a.type === 'AMPLIFICATION' || a.type === 'LOSS')
786
+ .map((c: any) => ({
787
+ gene: c.gene,
788
+ type: c.type === 'AMPLIFICATION' ? 'amplification' : 'deep-deletion',
789
+ copyNumber: c.copyNumber,
790
+ clinicalSignificance: 'likely-pathogenic'
791
+ }));
792
+
793
+ const fusions: GeneFusion[] = (data.alterations || [])
794
+ .filter((a: any) => a.type === 'FUSION')
795
+ .map((f: any) => ({
796
+ gene5Prime: f.gene.split('-')[0] || f.gene,
797
+ gene3Prime: f.gene.split('-')[1] || f.partner,
798
+ fusionName: f.gene,
799
+ clinicalSignificance: 'pathogenic'
800
+ }));
801
+
802
+ return {
803
+ reportId: data.reportId,
804
+ orderId,
805
+ patientId: data.patient?.externalId,
806
+ testType: 'liquid-biopsy',
807
+ panelName: 'Guardant360',
808
+ specimenInfo: {
809
+ type: 'blood',
810
+ collectionDate: new Date(data.collectionDate || Date.now()),
811
+ tumorPurity: data.ctDNAFraction
812
+ },
813
+ reportDate: new Date(data.reportDate || Date.now()),
814
+ variants,
815
+ copyNumberAlterations: cnvs,
816
+ fusions,
817
+ biomarkers: [],
818
+ therapyMatches: (data.therapyAssociations || []).map((t: any) => ({
819
+ therapy: t.therapy,
820
+ drugs: t.drugs || [t.therapy],
821
+ biomarkers: [t.biomarker],
822
+ evidenceLevel: t.level || 'clinical-evidence',
823
+ cancerType: t.indication || '',
824
+ references: t.references || []
825
+ })),
826
+ clinicalTrialMatches: [],
827
+ signatures: {
828
+ microsatelliteInstability: data.msi ? {
829
+ status: data.msi.status,
830
+ method: 'NGS'
831
+ } : undefined,
832
+ tumorMutationalBurden: data.bTMB ? {
833
+ value: data.bTMB.score,
834
+ unit: 'mutations/Mb',
835
+ status: data.bTMB.score >= 16 ? 'high' : 'low',
836
+ threshold: 16
837
+ } : undefined
838
+ },
839
+ qualityMetrics: {
840
+ tumorFraction: data.ctDNAFraction,
841
+ meanCoverage: data.qc?.meanCoverage
842
+ }
843
+ };
844
+ }
845
+ }
846
+
847
+ // ═══════════════════════════════════════════════════════════════════════════════
848
+ // TEMPUS CLIENT
849
+ // ═══════════════════════════════════════════════════════════════════════════════
850
+
851
+ export class TempusClient extends GenomicPlatformClient {
852
+ constructor(config: Omit<GenomicPlatformConfig, 'platform'>) {
853
+ super({ ...config, platform: 'tempus' });
854
+ }
855
+
856
+ async authenticate(): Promise<void> {
857
+ const tokenUrl = `${this.config.apiBaseUrl}/auth/token`;
858
+
859
+ const response = await this.httpRequest(tokenUrl, {
860
+ method: 'POST',
861
+ headers: {
862
+ 'Content-Type': 'application/json'
863
+ },
864
+ body: JSON.stringify({
865
+ client_id: this.config.clientId,
866
+ client_secret: this.config.clientSecret,
867
+ grant_type: 'client_credentials'
868
+ })
869
+ });
870
+
871
+ const data = JSON.parse(response);
872
+ this.accessToken = data.access_token;
873
+ this.tokenExpiry = new Date(Date.now() + (data.expires_in * 1000));
874
+ }
875
+
876
+ async submitOrder(order: GenomicTestOrder): Promise<{ orderId: string; status: string }> {
877
+ const url = `${this.config.apiBaseUrl}/v1/orders`;
878
+
879
+ const tempusOrder = {
880
+ externalId: order.orderId,
881
+ patient: {
882
+ externalId: order.patientId
883
+ },
884
+ testCode: this.mapPanelToTempusCode(order.panelName),
885
+ specimen: {
886
+ type: order.specimenType,
887
+ externalId: order.specimenId
888
+ },
889
+ indication: {
890
+ codes: order.icdCodes,
891
+ description: order.clinicalIndication
892
+ },
893
+ provider: {
894
+ name: order.orderingPhysician.name,
895
+ npi: order.orderingPhysician.npi,
896
+ facility: order.orderingPhysician.facility
897
+ }
898
+ };
899
+
900
+ const response = await this.httpRequest(url, {
901
+ method: 'POST',
902
+ headers: await this.getAuthHeaders(),
903
+ body: JSON.stringify(tempusOrder)
904
+ });
905
+
906
+ const result = JSON.parse(response);
907
+ return {
908
+ orderId: result.id,
909
+ status: 'ordered'
910
+ };
911
+ }
912
+
913
+ async getOrderStatus(orderId: string): Promise<GenomicTestOrder> {
914
+ const url = `${this.config.apiBaseUrl}/v1/orders/${orderId}`;
915
+
916
+ const response = await this.httpRequest(url, {
917
+ method: 'GET',
918
+ headers: await this.getAuthHeaders()
919
+ });
920
+
921
+ const data = JSON.parse(response);
922
+ return this.mapFromTempusOrder(data);
923
+ }
924
+
925
+ async getResults(orderId: string): Promise<GenomicTestResult> {
926
+ const url = `${this.config.apiBaseUrl}/v1/orders/${orderId}/report`;
927
+
928
+ const response = await this.httpRequest(url, {
929
+ method: 'GET',
930
+ headers: await this.getAuthHeaders()
931
+ });
932
+
933
+ const data = JSON.parse(response);
934
+ return this.mapFromTempusReport(data, orderId);
935
+ }
936
+
937
+ async listPatientResults(patientId: string): Promise<GenomicTestResult[]> {
938
+ const url = `${this.config.apiBaseUrl}/v1/patients/${patientId}/reports`;
939
+
940
+ const response = await this.httpRequest(url, {
941
+ method: 'GET',
942
+ headers: await this.getAuthHeaders()
943
+ });
944
+
945
+ const reports = JSON.parse(response);
946
+ return reports.map((r: any) => this.mapFromTempusReport(r, r.orderId));
947
+ }
948
+
949
+ /**
950
+ * Get Tempus xT/xF specific analysis including RNA expression
951
+ */
952
+ async getTempusAnalysis(orderId: string): Promise<{
953
+ dnaFindings: GenomicVariant[];
954
+ rnaExpression?: { gene: string; zscore: number; percentile: number }[];
955
+ immuneProfile?: {
956
+ pdl1: { score: number; method: string };
957
+ tils: number;
958
+ immuneScore: number;
959
+ };
960
+ hrd: HRDResult;
961
+ }> {
962
+ const result = await this.getResults(orderId);
963
+
964
+ // Tempus provides RNA expression data for xT panel
965
+ let rnaExpression;
966
+ if (result.biomarkers.some(b => b.name.toLowerCase().includes('expression'))) {
967
+ rnaExpression = result.biomarkers
968
+ .filter(b => b.name.toLowerCase().includes('expression'))
969
+ .map(b => ({
970
+ gene: b.name.replace(' expression', ''),
971
+ zscore: typeof b.value === 'number' ? b.value : 0,
972
+ percentile: 50 // Would come from actual data
973
+ }));
974
+ }
975
+
976
+ return {
977
+ dnaFindings: result.variants,
978
+ rnaExpression,
979
+ immuneProfile: result.biomarkers.some(b => b.name === 'PD-L1') ? {
980
+ pdl1: {
981
+ score: Number(result.biomarkers.find(b => b.name === 'PD-L1')?.value || 0),
982
+ method: 'IHC'
983
+ },
984
+ tils: Number(result.biomarkers.find(b => b.name === 'TILs')?.value || 0),
985
+ immuneScore: 0
986
+ } : undefined,
987
+ hrd: result.signatures?.homologousRecombinationDeficiency || {
988
+ status: 'indeterminate'
989
+ }
990
+ };
991
+ }
992
+
993
+ private mapPanelToTempusCode(panelName: string): string {
994
+ const mapping: Record<string, string> = {
995
+ 'Tempus xT': 'XT',
996
+ 'Tempus xF': 'XF',
997
+ 'Tempus xR': 'XR',
998
+ 'Tempus xG': 'XG',
999
+ 'Tempus xE': 'XE'
1000
+ };
1001
+ return mapping[panelName] || 'XT';
1002
+ }
1003
+
1004
+ private mapFromTempusOrder(data: any): GenomicTestOrder {
1005
+ return {
1006
+ orderId: data.externalId || data.id,
1007
+ patientId: data.patient?.externalId,
1008
+ testType: 'comprehensive-genomic-profiling',
1009
+ panelName: this.mapTempusCodeToPanel(data.testCode),
1010
+ specimenType: data.specimen?.type || 'tissue',
1011
+ specimenId: data.specimen?.externalId,
1012
+ orderDate: new Date(data.createdAt),
1013
+ clinicalIndication: data.indication?.description || '',
1014
+ icdCodes: data.indication?.codes || [],
1015
+ orderingPhysician: {
1016
+ name: data.provider?.name || '',
1017
+ npi: data.provider?.npi || '',
1018
+ facility: data.provider?.facility || ''
1019
+ },
1020
+ status: this.mapTempusStatus(data.status)
1021
+ };
1022
+ }
1023
+
1024
+ private mapTempusCodeToPanel(code: string): string {
1025
+ const mapping: Record<string, string> = {
1026
+ 'XT': 'Tempus xT',
1027
+ 'XF': 'Tempus xF',
1028
+ 'XR': 'Tempus xR',
1029
+ 'XG': 'Tempus xG',
1030
+ 'XE': 'Tempus xE'
1031
+ };
1032
+ return mapping[code] || 'Tempus xT';
1033
+ }
1034
+
1035
+ private mapTempusStatus(status: string): GenomicTestOrder['status'] {
1036
+ const mapping: Record<string, GenomicTestOrder['status']> = {
1037
+ 'ordered': 'ordered',
1038
+ 'received': 'specimen-received',
1039
+ 'processing': 'in-process',
1040
+ 'completed': 'completed',
1041
+ 'failed': 'failed',
1042
+ 'cancelled': 'cancelled'
1043
+ };
1044
+ return mapping[status?.toLowerCase()] || 'ordered';
1045
+ }
1046
+
1047
+ private mapFromTempusReport(data: any, orderId: string): GenomicTestResult {
1048
+ // Map Tempus report format to our common format
1049
+ const variants: GenomicVariant[] = (data.somaticVariants || []).map((v: any) => ({
1050
+ gene: v.gene,
1051
+ hgvsP: v.proteinChange,
1052
+ hgvsC: v.codingChange,
1053
+ transcript: v.transcript,
1054
+ variantType: v.type || 'SNV',
1055
+ variantAlleleFrequency: v.vaf || 0,
1056
+ coverage: v.depth,
1057
+ clinicalSignificance: v.pathogenicity || 'vus',
1058
+ tier: v.tier,
1059
+ oncogenicity: v.oncogenicity,
1060
+ functionalEffect: v.functionalEffect,
1061
+ somaticStatus: 'somatic'
1062
+ }));
1063
+
1064
+ return {
1065
+ reportId: data.reportId || data.id,
1066
+ orderId,
1067
+ patientId: data.patient?.externalId,
1068
+ testType: 'comprehensive-genomic-profiling',
1069
+ panelName: this.mapTempusCodeToPanel(data.testCode),
1070
+ specimenInfo: {
1071
+ type: data.specimen?.type || 'tissue',
1072
+ site: data.specimen?.site,
1073
+ collectionDate: new Date(data.specimen?.collectionDate || Date.now()),
1074
+ tumorPurity: data.specimen?.tumorContent
1075
+ },
1076
+ reportDate: new Date(data.reportDate || Date.now()),
1077
+ variants,
1078
+ copyNumberAlterations: (data.copyNumberVariants || []).map((c: any) => ({
1079
+ gene: c.gene,
1080
+ type: c.type,
1081
+ copyNumber: c.copyNumber,
1082
+ clinicalSignificance: c.pathogenicity || 'vus'
1083
+ })),
1084
+ fusions: (data.fusions || []).map((f: any) => ({
1085
+ gene5Prime: f.gene1,
1086
+ gene3Prime: f.gene2,
1087
+ fusionName: `${f.gene1}-${f.gene2}`,
1088
+ inFrame: f.inFrame,
1089
+ clinicalSignificance: f.pathogenicity || 'pathogenic'
1090
+ })),
1091
+ biomarkers: (data.biomarkers || []).map((b: any) => ({
1092
+ name: b.name,
1093
+ value: b.value,
1094
+ unit: b.unit,
1095
+ status: b.status,
1096
+ method: b.method
1097
+ })),
1098
+ therapyMatches: (data.therapyOptions || []).map((t: any) => ({
1099
+ therapy: t.name,
1100
+ drugs: t.drugs || [t.name],
1101
+ biomarkers: t.biomarkers || [],
1102
+ evidenceLevel: t.evidenceLevel || 'clinical-evidence',
1103
+ cancerType: t.indication || '',
1104
+ references: t.references || []
1105
+ })),
1106
+ clinicalTrialMatches: (data.clinicalTrials || []).map((ct: any) => ({
1107
+ trialId: ct.nctNumber,
1108
+ title: ct.title,
1109
+ phase: ct.phase,
1110
+ matchingBiomarkers: ct.matchingBiomarkers || [],
1111
+ status: ct.status || 'recruiting',
1112
+ locations: ct.sites
1113
+ })),
1114
+ signatures: {
1115
+ microsatelliteInstability: data.msi,
1116
+ tumorMutationalBurden: data.tmb ? {
1117
+ value: data.tmb.score,
1118
+ unit: 'mutations/Mb',
1119
+ status: data.tmb.score >= 10 ? 'high' : 'low',
1120
+ threshold: 10
1121
+ } : undefined,
1122
+ homologousRecombinationDeficiency: data.hrd
1123
+ },
1124
+ qualityMetrics: data.qcMetrics
1125
+ };
1126
+ }
1127
+ }
1128
+
1129
+ // ═══════════════════════════════════════════════════════════════════════════════
1130
+ // UNIFIED GENOMICS SERVICE
1131
+ // ═══════════════════════════════════════════════════════════════════════════════
1132
+
1133
+ export class UnifiedGenomicsService {
1134
+ private clients: Map<string, GenomicPlatformClient> = new Map();
1135
+
1136
+ /**
1137
+ * Register a genomic platform client
1138
+ */
1139
+ registerClient(name: string, client: GenomicPlatformClient): void {
1140
+ this.clients.set(name, client);
1141
+ }
1142
+
1143
+ /**
1144
+ * Get results from all platforms for a patient
1145
+ */
1146
+ async getAllPatientResults(patientId: string): Promise<{
1147
+ platform: string;
1148
+ results: GenomicTestResult[];
1149
+ }[]> {
1150
+ const allResults: { platform: string; results: GenomicTestResult[] }[] = [];
1151
+
1152
+ for (const [platform, client] of this.clients) {
1153
+ try {
1154
+ const results = await client.listPatientResults(patientId);
1155
+ allResults.push({ platform, results });
1156
+ } catch (error) {
1157
+ console.error(`Failed to get results from ${platform}:`, error);
1158
+ }
1159
+ }
1160
+
1161
+ return allResults;
1162
+ }
1163
+
1164
+ /**
1165
+ * Aggregate and deduplicate variants across all platforms
1166
+ */
1167
+ async getAggregatedVariants(patientId: string): Promise<{
1168
+ variants: GenomicVariant[];
1169
+ cnvs: CopyNumberAlteration[];
1170
+ fusions: GeneFusion[];
1171
+ biomarkers: {
1172
+ msi?: MSIResult;
1173
+ tmb?: TMBResult;
1174
+ hrd?: HRDResult;
1175
+ pdl1?: { score: number; scoreType: string };
1176
+ };
1177
+ }> {
1178
+ const allResults = await this.getAllPatientResults(patientId);
1179
+
1180
+ const variantMap = new Map<string, GenomicVariant>();
1181
+ const cnvMap = new Map<string, CopyNumberAlteration>();
1182
+ const fusionMap = new Map<string, GeneFusion>();
1183
+ let latestMsi: MSIResult | undefined;
1184
+ let latestTmb: TMBResult | undefined;
1185
+ let latestHrd: HRDResult | undefined;
1186
+ let latestReportDate = new Date(0);
1187
+
1188
+ for (const { results } of allResults) {
1189
+ for (const result of results) {
1190
+ // Track latest report for biomarkers
1191
+ if (result.reportDate > latestReportDate) {
1192
+ latestReportDate = result.reportDate;
1193
+ latestMsi = result.signatures?.microsatelliteInstability;
1194
+ latestTmb = result.signatures?.tumorMutationalBurden;
1195
+ latestHrd = result.signatures?.homologousRecombinationDeficiency;
1196
+ }
1197
+
1198
+ // Deduplicate variants by gene + protein change
1199
+ for (const variant of result.variants) {
1200
+ const key = `${variant.gene}:${variant.hgvsP || variant.hgvsC}`;
1201
+ const existing = variantMap.get(key);
1202
+
1203
+ // Keep the variant with higher VAF or better evidence
1204
+ if (!existing || variant.variantAlleleFrequency > existing.variantAlleleFrequency) {
1205
+ variantMap.set(key, variant);
1206
+ }
1207
+ }
1208
+
1209
+ // Deduplicate CNVs
1210
+ for (const cnv of result.copyNumberAlterations) {
1211
+ const key = `${cnv.gene}:${cnv.type}`;
1212
+ if (!cnvMap.has(key)) {
1213
+ cnvMap.set(key, cnv);
1214
+ }
1215
+ }
1216
+
1217
+ // Deduplicate fusions
1218
+ for (const fusion of result.fusions) {
1219
+ const key = fusion.fusionName;
1220
+ if (!fusionMap.has(key)) {
1221
+ fusionMap.set(key, fusion);
1222
+ }
1223
+ }
1224
+ }
1225
+ }
1226
+
1227
+ return {
1228
+ variants: Array.from(variantMap.values()),
1229
+ cnvs: Array.from(cnvMap.values()),
1230
+ fusions: Array.from(fusionMap.values()),
1231
+ biomarkers: {
1232
+ msi: latestMsi,
1233
+ tmb: latestTmb,
1234
+ hrd: latestHrd
1235
+ }
1236
+ };
1237
+ }
1238
+
1239
+ /**
1240
+ * Match patient genomics to actionable therapies
1241
+ */
1242
+ async matchToTherapies(patientId: string, cancerType: string): Promise<TherapyMatch[]> {
1243
+ const genomics = await this.getAggregatedVariants(patientId);
1244
+ const therapyMatches: TherapyMatch[] = [];
1245
+
1246
+ // FDA-approved biomarker-drug matches
1247
+ const fdaMatches = this.getFDAApprovedMatches(genomics, cancerType);
1248
+ therapyMatches.push(...fdaMatches);
1249
+
1250
+ // NCCN guideline matches
1251
+ const nccnMatches = this.getNCCNMatches(genomics, cancerType);
1252
+ therapyMatches.push(...nccnMatches);
1253
+
1254
+ // Deduplicate and sort by evidence level
1255
+ const evidenceOrder = {
1256
+ 'FDA-approved': 0,
1257
+ 'NCCN-guideline': 1,
1258
+ 'clinical-evidence': 2,
1259
+ 'case-report': 3,
1260
+ 'preclinical': 4
1261
+ };
1262
+
1263
+ const uniqueMatches = new Map<string, TherapyMatch>();
1264
+ for (const match of therapyMatches) {
1265
+ const key = `${match.therapy}:${match.biomarkers.join(',')}`;
1266
+ const existing = uniqueMatches.get(key);
1267
+ if (!existing || evidenceOrder[match.evidenceLevel] < evidenceOrder[existing.evidenceLevel]) {
1268
+ uniqueMatches.set(key, match);
1269
+ }
1270
+ }
1271
+
1272
+ return Array.from(uniqueMatches.values())
1273
+ .sort((a, b) => evidenceOrder[a.evidenceLevel] - evidenceOrder[b.evidenceLevel]);
1274
+ }
1275
+
1276
+ private getFDAApprovedMatches(genomics: {
1277
+ variants: GenomicVariant[];
1278
+ cnvs: CopyNumberAlteration[];
1279
+ fusions: GeneFusion[];
1280
+ biomarkers: { msi?: MSIResult; tmb?: TMBResult; hrd?: HRDResult };
1281
+ }, cancerType: string): TherapyMatch[] {
1282
+ const matches: TherapyMatch[] = [];
1283
+
1284
+ // Check MSI-H for pembrolizumab (tumor-agnostic)
1285
+ if (genomics.biomarkers.msi?.status === 'MSI-H') {
1286
+ matches.push({
1287
+ therapy: 'Pembrolizumab',
1288
+ drugs: ['Pembrolizumab'],
1289
+ biomarkers: ['MSI-H'],
1290
+ evidenceLevel: 'FDA-approved',
1291
+ cancerType: 'Tumor-agnostic',
1292
+ approvalStatus: 'FDA-approved for MSI-H/dMMR solid tumors',
1293
+ references: ['KEYNOTE-158', 'KEYNOTE-177']
1294
+ });
1295
+ }
1296
+
1297
+ // Check TMB-H for pembrolizumab
1298
+ if (genomics.biomarkers.tmb?.status === 'high' && genomics.biomarkers.tmb.value >= 10) {
1299
+ matches.push({
1300
+ therapy: 'Pembrolizumab',
1301
+ drugs: ['Pembrolizumab'],
1302
+ biomarkers: ['TMB-H (' + genomics.biomarkers.tmb.value + ' mut/Mb)'],
1303
+ evidenceLevel: 'FDA-approved',
1304
+ cancerType: 'Tumor-agnostic',
1305
+ approvalStatus: 'FDA-approved for TMB-H solid tumors',
1306
+ references: ['KEYNOTE-158']
1307
+ });
1308
+ }
1309
+
1310
+ // Check NTRK fusions
1311
+ const ntrkFusion = genomics.fusions.find(f =>
1312
+ f.gene5Prime.includes('NTRK') || f.gene3Prime.includes('NTRK')
1313
+ );
1314
+ if (ntrkFusion) {
1315
+ matches.push({
1316
+ therapy: 'Larotrectinib or Entrectinib',
1317
+ drugs: ['Larotrectinib', 'Entrectinib'],
1318
+ biomarkers: [ntrkFusion.fusionName],
1319
+ evidenceLevel: 'FDA-approved',
1320
+ cancerType: 'Tumor-agnostic',
1321
+ approvalStatus: 'FDA-approved for NTRK fusion-positive solid tumors',
1322
+ references: ['NAVIGATE', 'STARTRK-2']
1323
+ });
1324
+ }
1325
+
1326
+ // Check BRAF V600E
1327
+ const brafV600 = genomics.variants.find(v =>
1328
+ v.gene === 'BRAF' && v.hgvsP?.includes('V600')
1329
+ );
1330
+ if (brafV600) {
1331
+ if (cancerType.toLowerCase().includes('melanoma')) {
1332
+ matches.push({
1333
+ therapy: 'Dabrafenib + Trametinib',
1334
+ drugs: ['Dabrafenib', 'Trametinib'],
1335
+ biomarkers: ['BRAF V600E/K'],
1336
+ evidenceLevel: 'FDA-approved',
1337
+ cancerType: 'Melanoma',
1338
+ references: ['COMBI-d', 'COMBI-v']
1339
+ });
1340
+ } else if (cancerType.toLowerCase().includes('nsclc') || cancerType.toLowerCase().includes('lung')) {
1341
+ matches.push({
1342
+ therapy: 'Dabrafenib + Trametinib',
1343
+ drugs: ['Dabrafenib', 'Trametinib'],
1344
+ biomarkers: ['BRAF V600E'],
1345
+ evidenceLevel: 'FDA-approved',
1346
+ cancerType: 'NSCLC',
1347
+ references: ['BRF113928']
1348
+ });
1349
+ }
1350
+ }
1351
+
1352
+ // Check EGFR mutations
1353
+ const egfrMut = genomics.variants.find(v => v.gene === 'EGFR');
1354
+ if (egfrMut && (cancerType.toLowerCase().includes('nsclc') || cancerType.toLowerCase().includes('lung'))) {
1355
+ matches.push({
1356
+ therapy: 'Osimertinib',
1357
+ drugs: ['Osimertinib'],
1358
+ biomarkers: [`EGFR ${egfrMut.hgvsP || 'mutation'}`],
1359
+ evidenceLevel: 'FDA-approved',
1360
+ cancerType: 'NSCLC',
1361
+ references: ['FLAURA', 'ADAURA']
1362
+ });
1363
+ }
1364
+
1365
+ // Check ALK fusions
1366
+ const alkFusion = genomics.fusions.find(f =>
1367
+ f.gene5Prime === 'ALK' || f.gene3Prime === 'ALK'
1368
+ );
1369
+ if (alkFusion && (cancerType.toLowerCase().includes('nsclc') || cancerType.toLowerCase().includes('lung'))) {
1370
+ matches.push({
1371
+ therapy: 'Alectinib',
1372
+ drugs: ['Alectinib'],
1373
+ biomarkers: [alkFusion.fusionName],
1374
+ evidenceLevel: 'FDA-approved',
1375
+ cancerType: 'NSCLC',
1376
+ references: ['ALEX', 'J-ALEX']
1377
+ });
1378
+ }
1379
+
1380
+ // Check HER2 amplification
1381
+ const her2Amp = genomics.cnvs.find(c => c.gene === 'HER2' || c.gene === 'ERBB2');
1382
+ if (her2Amp && cancerType.toLowerCase().includes('breast')) {
1383
+ matches.push({
1384
+ therapy: 'Trastuzumab + Pertuzumab',
1385
+ drugs: ['Trastuzumab', 'Pertuzumab'],
1386
+ biomarkers: ['HER2 amplification'],
1387
+ evidenceLevel: 'FDA-approved',
1388
+ cancerType: 'Breast',
1389
+ references: ['CLEOPATRA', 'APHINITY']
1390
+ });
1391
+ }
1392
+
1393
+ // Check BRCA1/2 mutations
1394
+ const brcaMut = genomics.variants.find(v =>
1395
+ (v.gene === 'BRCA1' || v.gene === 'BRCA2') &&
1396
+ (v.clinicalSignificance === 'pathogenic' || v.clinicalSignificance === 'likely-pathogenic')
1397
+ );
1398
+ if (brcaMut || genomics.biomarkers.hrd?.status === 'positive') {
1399
+ const biomarker = brcaMut ? `${brcaMut.gene} ${brcaMut.hgvsP || 'mutation'}` : 'HRD-positive';
1400
+
1401
+ if (cancerType.toLowerCase().includes('ovarian')) {
1402
+ matches.push({
1403
+ therapy: 'Olaparib',
1404
+ drugs: ['Olaparib'],
1405
+ biomarkers: [biomarker],
1406
+ evidenceLevel: 'FDA-approved',
1407
+ cancerType: 'Ovarian',
1408
+ references: ['SOLO-1', 'PAOLA-1']
1409
+ });
1410
+ } else if (cancerType.toLowerCase().includes('breast')) {
1411
+ matches.push({
1412
+ therapy: 'Olaparib or Talazoparib',
1413
+ drugs: ['Olaparib', 'Talazoparib'],
1414
+ biomarkers: [biomarker],
1415
+ evidenceLevel: 'FDA-approved',
1416
+ cancerType: 'Breast',
1417
+ references: ['OlympiAD', 'EMBRACA']
1418
+ });
1419
+ } else if (cancerType.toLowerCase().includes('prostate')) {
1420
+ matches.push({
1421
+ therapy: 'Olaparib or Rucaparib',
1422
+ drugs: ['Olaparib', 'Rucaparib'],
1423
+ biomarkers: [biomarker],
1424
+ evidenceLevel: 'FDA-approved',
1425
+ cancerType: 'Prostate',
1426
+ references: ['PROfound', 'TRITON2']
1427
+ });
1428
+ }
1429
+ }
1430
+
1431
+ // Check KRAS G12C
1432
+ const krasG12C = genomics.variants.find(v =>
1433
+ v.gene === 'KRAS' && v.hgvsP?.includes('G12C')
1434
+ );
1435
+ if (krasG12C) {
1436
+ if (cancerType.toLowerCase().includes('nsclc') || cancerType.toLowerCase().includes('lung')) {
1437
+ matches.push({
1438
+ therapy: 'Sotorasib or Adagrasib',
1439
+ drugs: ['Sotorasib', 'Adagrasib'],
1440
+ biomarkers: ['KRAS G12C'],
1441
+ evidenceLevel: 'FDA-approved',
1442
+ cancerType: 'NSCLC',
1443
+ references: ['CodeBreaK 100', 'KRYSTAL-1']
1444
+ });
1445
+ }
1446
+ }
1447
+
1448
+ return matches;
1449
+ }
1450
+
1451
+ private getNCCNMatches(genomics: {
1452
+ variants: GenomicVariant[];
1453
+ cnvs: CopyNumberAlteration[];
1454
+ fusions: GeneFusion[];
1455
+ biomarkers: { msi?: MSIResult; tmb?: TMBResult; hrd?: HRDResult };
1456
+ }, cancerType: string): TherapyMatch[] {
1457
+ // Additional NCCN guideline-based matches would go here
1458
+ // This is a simplified version
1459
+ return [];
1460
+ }
1461
+ }
1462
+
1463
+ // ═══════════════════════════════════════════════════════════════════════════════
1464
+ // FACTORY FUNCTION
1465
+ // ═══════════════════════════════════════════════════════════════════════════════
1466
+
1467
+ export function createGenomicClient(config: GenomicPlatformConfig): GenomicPlatformClient {
1468
+ switch (config.platform) {
1469
+ case 'foundation-medicine':
1470
+ return new FoundationMedicineClient(config);
1471
+ case 'guardant':
1472
+ return new GuardantHealthClient(config);
1473
+ case 'tempus':
1474
+ return new TempusClient(config);
1475
+ default:
1476
+ throw new Error(`Unsupported genomic platform: ${config.platform}`);
1477
+ }
1478
+ }
1479
+
1480
+ export default UnifiedGenomicsService;