@erosolaraijs/cure 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/dist/bin/cure.d.ts +2 -5
  2. package/dist/bin/cure.d.ts.map +1 -1
  3. package/dist/bin/cure.js +285 -124
  4. package/dist/bin/cure.js.map +1 -1
  5. package/dist/clinician/decisionSupport.d.ts +325 -0
  6. package/dist/clinician/decisionSupport.d.ts.map +1 -0
  7. package/dist/clinician/decisionSupport.js +604 -0
  8. package/dist/clinician/decisionSupport.js.map +1 -0
  9. package/dist/clinician/index.d.ts +5 -0
  10. package/dist/clinician/index.d.ts.map +1 -0
  11. package/dist/clinician/index.js +5 -0
  12. package/dist/clinician/index.js.map +1 -0
  13. package/dist/compliance/index.d.ts +5 -0
  14. package/dist/compliance/index.d.ts.map +1 -0
  15. package/dist/compliance/index.js +5 -0
  16. package/dist/compliance/index.js.map +1 -0
  17. package/dist/integrations/clinicalTrials/index.d.ts +5 -0
  18. package/dist/integrations/clinicalTrials/index.d.ts.map +1 -0
  19. package/dist/integrations/clinicalTrials/index.js +5 -0
  20. package/dist/integrations/clinicalTrials/index.js.map +1 -0
  21. package/dist/integrations/ehr/index.d.ts +5 -0
  22. package/dist/integrations/ehr/index.d.ts.map +1 -0
  23. package/dist/integrations/ehr/index.js +5 -0
  24. package/dist/integrations/ehr/index.js.map +1 -0
  25. package/dist/integrations/genomics/index.d.ts +5 -0
  26. package/dist/integrations/genomics/index.d.ts.map +1 -0
  27. package/dist/integrations/genomics/index.js +5 -0
  28. package/dist/integrations/genomics/index.js.map +1 -0
  29. package/dist/integrations/index.d.ts +7 -0
  30. package/dist/integrations/index.d.ts.map +1 -0
  31. package/dist/integrations/index.js +10 -0
  32. package/dist/integrations/index.js.map +1 -0
  33. package/dist/ml/index.d.ts +5 -0
  34. package/dist/ml/index.d.ts.map +1 -0
  35. package/dist/ml/index.js +5 -0
  36. package/dist/ml/index.js.map +1 -0
  37. package/dist/ml/outcomePredictor.d.ts +297 -0
  38. package/dist/ml/outcomePredictor.d.ts.map +1 -0
  39. package/dist/ml/outcomePredictor.js +823 -0
  40. package/dist/ml/outcomePredictor.js.map +1 -0
  41. package/dist/patient/index.d.ts +5 -0
  42. package/dist/patient/index.d.ts.map +1 -0
  43. package/dist/patient/index.js +5 -0
  44. package/dist/patient/index.js.map +1 -0
  45. package/dist/patient/patientPortal.d.ts +337 -0
  46. package/dist/patient/patientPortal.d.ts.map +1 -0
  47. package/dist/patient/patientPortal.js +667 -0
  48. package/dist/patient/patientPortal.js.map +1 -0
  49. package/dist/safety/drugInteractions.d.ts +230 -0
  50. package/dist/safety/drugInteractions.d.ts.map +1 -0
  51. package/dist/safety/drugInteractions.js +697 -0
  52. package/dist/safety/drugInteractions.js.map +1 -0
  53. package/dist/safety/index.d.ts +5 -0
  54. package/dist/safety/index.d.ts.map +1 -0
  55. package/dist/safety/index.js +5 -0
  56. package/dist/safety/index.js.map +1 -0
  57. package/dist/validation/index.d.ts +5 -0
  58. package/dist/validation/index.d.ts.map +1 -0
  59. package/dist/validation/index.js +5 -0
  60. package/dist/validation/index.js.map +1 -0
  61. package/dist/validation/retrospectiveValidator.d.ts +246 -0
  62. package/dist/validation/retrospectiveValidator.d.ts.map +1 -0
  63. package/dist/validation/retrospectiveValidator.js +602 -0
  64. package/dist/validation/retrospectiveValidator.js.map +1 -0
  65. package/package.json +1 -1
  66. package/src/bin/cure.ts +331 -140
  67. package/src/clinician/decisionSupport.ts +949 -0
  68. package/src/patient/patientPortal.ts +1039 -0
@@ -0,0 +1,667 @@
1
+ /**
2
+ * Patient-Facing Portal Interfaces
3
+ *
4
+ * Provides patient engagement features:
5
+ * - Treatment plan viewing
6
+ * - Side effect tracking
7
+ * - Medication adherence monitoring
8
+ * - Quality of life assessments
9
+ * - Secure messaging with care team
10
+ * - Educational content delivery
11
+ * - Appointment management
12
+ *
13
+ * All interfaces designed for health literacy and accessibility.
14
+ */
15
+ import { EventEmitter } from 'events';
16
+ // ═══════════════════════════════════════════════════════════════════════════════
17
+ // PATIENT PORTAL SERVICE
18
+ // ═══════════════════════════════════════════════════════════════════════════════
19
+ export class PatientPortalService extends EventEmitter {
20
+ accounts = new Map();
21
+ treatmentSummaries = new Map();
22
+ symptomReports = new Map();
23
+ adherenceLogs = new Map();
24
+ qolAssessments = new Map();
25
+ messages = new Map();
26
+ educationalContent = new Map();
27
+ appointmentRequests = new Map();
28
+ constructor() {
29
+ super();
30
+ this.initializeEducationalContent();
31
+ }
32
+ // ═══════════════════════════════════════════════════════════════════════════════
33
+ // ACCOUNT MANAGEMENT
34
+ // ═══════════════════════════════════════════════════════════════════════════════
35
+ /**
36
+ * Create a patient portal account
37
+ */
38
+ async createAccount(accountData) {
39
+ const id = `PAT-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
40
+ const account = {
41
+ ...accountData,
42
+ id,
43
+ createdAt: new Date()
44
+ };
45
+ this.accounts.set(id, account);
46
+ this.emit('account-created', { accountId: id, mrn: account.mrn });
47
+ return account;
48
+ }
49
+ /**
50
+ * Get patient account
51
+ */
52
+ getAccount(patientId) {
53
+ return this.accounts.get(patientId);
54
+ }
55
+ /**
56
+ * Record patient login
57
+ */
58
+ recordLogin(patientId) {
59
+ const account = this.accounts.get(patientId);
60
+ if (account) {
61
+ account.lastLoginAt = new Date();
62
+ this.emit('patient-login', { patientId });
63
+ }
64
+ }
65
+ // ═══════════════════════════════════════════════════════════════════════════════
66
+ // TREATMENT SUMMARY
67
+ // ═══════════════════════════════════════════════════════════════════════════════
68
+ /**
69
+ * Set patient treatment summary
70
+ */
71
+ setTreatmentSummary(summary) {
72
+ this.treatmentSummaries.set(summary.patientId, summary);
73
+ this.emit('treatment-summary-updated', { patientId: summary.patientId });
74
+ }
75
+ /**
76
+ * Get patient-friendly treatment summary
77
+ */
78
+ getTreatmentSummary(patientId) {
79
+ return this.treatmentSummaries.get(patientId);
80
+ }
81
+ /**
82
+ * Generate simple language summary
83
+ */
84
+ generateSimpleSummary(diagnosis, stage, treatment) {
85
+ // Convert medical terminology to patient-friendly language
86
+ const diagnosisExplanation = this.simplifyDiagnosis(diagnosis, stage);
87
+ const treatmentExplanation = this.simplifyTreatment(treatment);
88
+ const whatItMeans = [
89
+ 'Your care team has created a treatment plan specifically for you',
90
+ 'This plan is based on the specific characteristics of your cancer',
91
+ 'Your doctors will monitor how well the treatment is working',
92
+ 'You can ask questions at any time - your care team is here to help'
93
+ ];
94
+ return {
95
+ diagnosisExplanation,
96
+ treatmentExplanation,
97
+ whatItMeans
98
+ };
99
+ }
100
+ simplifyDiagnosis(diagnosis, stage) {
101
+ // Simplified explanations for common diagnoses
102
+ const simplifications = {
103
+ 'NSCLC': 'non-small cell lung cancer, the most common type of lung cancer',
104
+ 'SCLC': 'small cell lung cancer',
105
+ 'Breast Cancer': 'breast cancer',
106
+ 'Adenocarcinoma': 'a type of cancer that starts in gland cells',
107
+ 'Squamous Cell Carcinoma': 'a type of cancer that starts in flat cells'
108
+ };
109
+ let explanation = `You have been diagnosed with ${simplifications[diagnosis] || diagnosis}. `;
110
+ // Stage explanation
111
+ if (stage) {
112
+ const stageNum = stage.replace(/[^IViv0-4]/g, '').toUpperCase();
113
+ if (stageNum.includes('I') && !stageNum.includes('V')) {
114
+ explanation += 'This is an early stage, which often has more treatment options.';
115
+ }
116
+ else if (stageNum.includes('II')) {
117
+ explanation += 'This stage means the cancer has grown but is still considered treatable.';
118
+ }
119
+ else if (stageNum.includes('III')) {
120
+ explanation += 'This is a more advanced stage, but many effective treatments are available.';
121
+ }
122
+ else if (stageNum.includes('IV')) {
123
+ explanation += 'This stage means the cancer has spread, and treatment focuses on controlling the disease and maintaining quality of life.';
124
+ }
125
+ }
126
+ return explanation;
127
+ }
128
+ simplifyTreatment(treatment) {
129
+ const explanations = {
130
+ 'immunotherapy': 'a treatment that helps your immune system fight the cancer',
131
+ 'chemotherapy': 'medicine that kills fast-growing cells, including cancer cells',
132
+ 'targeted therapy': 'medicine that targets specific features of cancer cells',
133
+ 'radiation': 'high-energy beams that destroy cancer cells in a specific area',
134
+ 'surgery': 'an operation to remove the cancer'
135
+ };
136
+ let explanation = treatment;
137
+ for (const [term, simple] of Object.entries(explanations)) {
138
+ if (treatment.toLowerCase().includes(term)) {
139
+ explanation = `Your treatment includes ${simple}.`;
140
+ break;
141
+ }
142
+ }
143
+ return explanation;
144
+ }
145
+ // ═══════════════════════════════════════════════════════════════════════════════
146
+ // SYMPTOM TRACKING
147
+ // ═══════════════════════════════════════════════════════════════════════════════
148
+ /**
149
+ * Submit a symptom report
150
+ */
151
+ async submitSymptomReport(patientId, symptoms, overallFeeling, concerns) {
152
+ const id = `SYM-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
153
+ // Analyze symptoms for follow-up needs
154
+ const { requiresFollowUp, followUpReason } = this.analyzeSymptoms(symptoms, overallFeeling);
155
+ const report = {
156
+ id,
157
+ patientId,
158
+ reportedAt: new Date(),
159
+ symptoms,
160
+ overallFeeling,
161
+ concernsForDoctor: concerns,
162
+ requiresFollowUp,
163
+ followUpReason
164
+ };
165
+ const patientReports = this.symptomReports.get(patientId) || [];
166
+ patientReports.push(report);
167
+ this.symptomReports.set(patientId, patientReports);
168
+ this.emit('symptom-report-submitted', { patientId, reportId: id, requiresFollowUp });
169
+ // Alert care team if needed
170
+ if (requiresFollowUp) {
171
+ this.emit('symptom-alert', {
172
+ patientId,
173
+ reportId: id,
174
+ reason: followUpReason,
175
+ severity: this.determineSeverity(symptoms)
176
+ });
177
+ }
178
+ return report;
179
+ }
180
+ /**
181
+ * Get symptom history for patient
182
+ */
183
+ getSymptomHistory(patientId, days) {
184
+ const reports = this.symptomReports.get(patientId) || [];
185
+ if (!days)
186
+ return reports;
187
+ const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000);
188
+ return reports.filter(r => r.reportedAt >= cutoff);
189
+ }
190
+ /**
191
+ * Get symptom trends
192
+ */
193
+ getSymptomTrends(patientId) {
194
+ const reports = this.getSymptomHistory(patientId, 30);
195
+ const symptomData = new Map();
196
+ for (const report of reports) {
197
+ for (const symptom of report.symptoms) {
198
+ const data = symptomData.get(symptom.symptom) || [];
199
+ data.push(symptom.severity);
200
+ symptomData.set(symptom.symptom, data);
201
+ }
202
+ }
203
+ const trends = [];
204
+ for (const [symptom, severities] of symptomData) {
205
+ const avg = severities.reduce((a, b) => a + b, 0) / severities.length;
206
+ // Determine trend
207
+ let trend = 'stable';
208
+ if (severities.length >= 3) {
209
+ const recent = severities.slice(-3);
210
+ const earlier = severities.slice(0, Math.max(1, severities.length - 3));
211
+ const recentAvg = recent.reduce((a, b) => a + b, 0) / recent.length;
212
+ const earlierAvg = earlier.reduce((a, b) => a + b, 0) / earlier.length;
213
+ if (recentAvg < earlierAvg - 0.5)
214
+ trend = 'improving';
215
+ else if (recentAvg > earlierAvg + 0.5)
216
+ trend = 'worsening';
217
+ }
218
+ trends.push({
219
+ symptom,
220
+ trend,
221
+ averageSeverity: Math.round(avg * 10) / 10,
222
+ occurrences: severities.length
223
+ });
224
+ }
225
+ return trends.sort((a, b) => b.averageSeverity - a.averageSeverity);
226
+ }
227
+ analyzeSymptoms(symptoms, overallFeeling) {
228
+ // Check for concerning symptoms
229
+ const concerningSymptoms = [
230
+ 'fever', 'chest pain', 'shortness of breath', 'severe pain',
231
+ 'bleeding', 'confusion', 'fainting', 'seizure'
232
+ ];
233
+ const severeSymptomsCount = symptoms.filter(s => s.severity >= 4).length;
234
+ const concerningFound = symptoms.find(s => concerningSymptoms.some(cs => s.symptom.toLowerCase().includes(cs)));
235
+ if (concerningFound && concerningFound.severity >= 3) {
236
+ return {
237
+ requiresFollowUp: true,
238
+ followUpReason: `Patient reported ${concerningFound.symptom} with severity ${concerningFound.severity}/5`
239
+ };
240
+ }
241
+ if (severeSymptomsCount >= 2) {
242
+ return {
243
+ requiresFollowUp: true,
244
+ followUpReason: `Multiple severe symptoms reported (${severeSymptomsCount})`
245
+ };
246
+ }
247
+ if (overallFeeling <= 2) {
248
+ return {
249
+ requiresFollowUp: true,
250
+ followUpReason: 'Patient overall feeling is very poor'
251
+ };
252
+ }
253
+ return { requiresFollowUp: false };
254
+ }
255
+ determineSeverity(symptoms) {
256
+ const maxSeverity = Math.max(...symptoms.map(s => s.severity));
257
+ if (maxSeverity >= 4)
258
+ return 'high';
259
+ if (maxSeverity >= 3)
260
+ return 'moderate';
261
+ return 'low';
262
+ }
263
+ // ═══════════════════════════════════════════════════════════════════════════════
264
+ // MEDICATION ADHERENCE
265
+ // ═══════════════════════════════════════════════════════════════════════════════
266
+ /**
267
+ * Log medication taken
268
+ */
269
+ logMedicationTaken(patientId, medication, scheduledTime, taken, takenTime, skippedReason, sideEffects) {
270
+ const logs = this.adherenceLogs.get(patientId) || [];
271
+ let medLog = logs.find(l => l.medication === medication);
272
+ if (!medLog) {
273
+ medLog = {
274
+ patientId,
275
+ medication,
276
+ logs: [],
277
+ adherenceRate: 100,
278
+ missedDoses: 0,
279
+ streakDays: 0
280
+ };
281
+ logs.push(medLog);
282
+ }
283
+ medLog.logs.push({
284
+ scheduledTime,
285
+ takenTime,
286
+ taken,
287
+ skippedReason,
288
+ sideEffects
289
+ });
290
+ // Update adherence metrics
291
+ const totalDoses = medLog.logs.length;
292
+ const takenDoses = medLog.logs.filter(l => l.taken).length;
293
+ medLog.adherenceRate = Math.round((takenDoses / totalDoses) * 100);
294
+ medLog.missedDoses = totalDoses - takenDoses;
295
+ // Calculate streak
296
+ const sortedLogs = [...medLog.logs].sort((a, b) => b.scheduledTime.getTime() - a.scheduledTime.getTime());
297
+ let streak = 0;
298
+ for (const log of sortedLogs) {
299
+ if (log.taken)
300
+ streak++;
301
+ else
302
+ break;
303
+ }
304
+ medLog.streakDays = streak;
305
+ this.adherenceLogs.set(patientId, logs);
306
+ this.emit('medication-logged', { patientId, medication, taken });
307
+ // Alert for poor adherence
308
+ if (medLog.adherenceRate < 80) {
309
+ this.emit('adherence-concern', {
310
+ patientId,
311
+ medication,
312
+ adherenceRate: medLog.adherenceRate
313
+ });
314
+ }
315
+ }
316
+ /**
317
+ * Get adherence summary
318
+ */
319
+ getAdherenceSummary(patientId) {
320
+ const medications = this.adherenceLogs.get(patientId) || [];
321
+ if (medications.length === 0) {
322
+ return {
323
+ medications: [],
324
+ overallAdherence: 100,
325
+ concerns: [],
326
+ encouragement: 'Great start! Remember to log your medications each day.'
327
+ };
328
+ }
329
+ const overallAdherence = Math.round(medications.reduce((sum, m) => sum + m.adherenceRate, 0) / medications.length);
330
+ const concerns = [];
331
+ for (const med of medications) {
332
+ if (med.adherenceRate < 80) {
333
+ concerns.push(`${med.medication} adherence is ${med.adherenceRate}% - please talk to your care team if you're having trouble`);
334
+ }
335
+ }
336
+ let encouragement;
337
+ if (overallAdherence >= 95) {
338
+ encouragement = 'Excellent! You\'re doing a great job staying on track with your medications.';
339
+ }
340
+ else if (overallAdherence >= 80) {
341
+ encouragement = 'Good work! Try to take your medications at the same time each day to build a routine.';
342
+ }
343
+ else {
344
+ encouragement = 'Remember, taking your medications as prescribed gives them the best chance to work. We\'re here to help if you\'re having difficulties.';
345
+ }
346
+ return { medications, overallAdherence, concerns, encouragement };
347
+ }
348
+ // ═══════════════════════════════════════════════════════════════════════════════
349
+ // QUALITY OF LIFE ASSESSMENTS
350
+ // ═══════════════════════════════════════════════════════════════════════════════
351
+ /**
352
+ * Submit quality of life assessment
353
+ */
354
+ async submitQoLAssessment(patientId, assessmentType, responses) {
355
+ const id = `QOL-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
356
+ // Score the assessment
357
+ const summaryScores = this.scoreAssessment(assessmentType, responses, patientId);
358
+ const alerts = this.identifyQoLAlerts(summaryScores);
359
+ const assessment = {
360
+ id,
361
+ patientId,
362
+ assessmentType,
363
+ completedAt: new Date(),
364
+ responses,
365
+ summaryScores,
366
+ alerts
367
+ };
368
+ const patientAssessments = this.qolAssessments.get(patientId) || [];
369
+ patientAssessments.push(assessment);
370
+ this.qolAssessments.set(patientId, patientAssessments);
371
+ this.emit('qol-assessment-completed', { patientId, assessmentId: id });
372
+ // Alert for concerning scores
373
+ if (alerts.some(a => a.severity === 'high')) {
374
+ this.emit('qol-alert', { patientId, assessmentId: id, alerts });
375
+ }
376
+ return assessment;
377
+ }
378
+ /**
379
+ * Get QoL assessment history
380
+ */
381
+ getQoLHistory(patientId) {
382
+ return this.qolAssessments.get(patientId) || [];
383
+ }
384
+ scoreAssessment(type, responses, patientId) {
385
+ // Group responses by category
386
+ const categories = new Map();
387
+ for (const response of responses) {
388
+ const cat = response.category;
389
+ const data = categories.get(cat) || { scores: [], maxScore: 0 };
390
+ data.scores.push(response.score || 0);
391
+ data.maxScore = Math.max(data.maxScore, 5); // Assuming 5-point scale
392
+ categories.set(cat, data);
393
+ }
394
+ // Get previous assessment for comparison
395
+ const history = this.qolAssessments.get(patientId) || [];
396
+ const previous = history.length > 0 ? history[history.length - 1] : null;
397
+ const summaryScores = [];
398
+ for (const [domain, data] of categories) {
399
+ const score = Math.round(data.scores.reduce((a, b) => a + b, 0) / data.scores.length * 10) / 10;
400
+ const maxScore = data.maxScore;
401
+ // Get previous score for this domain
402
+ let changeFromLast;
403
+ if (previous) {
404
+ const prevDomain = previous.summaryScores.find(s => s.domain === domain);
405
+ if (prevDomain) {
406
+ changeFromLast = Math.round((score - prevDomain.score) * 10) / 10;
407
+ }
408
+ }
409
+ summaryScores.push({
410
+ domain,
411
+ score,
412
+ maxScore,
413
+ interpretation: this.interpretScore(score, maxScore),
414
+ changeFromLast
415
+ });
416
+ }
417
+ return summaryScores;
418
+ }
419
+ interpretScore(score, maxScore) {
420
+ const percentage = (score / maxScore) * 100;
421
+ if (percentage >= 80)
422
+ return 'Good - minimal impact on quality of life';
423
+ if (percentage >= 60)
424
+ return 'Moderate - some impact on daily activities';
425
+ if (percentage >= 40)
426
+ return 'Below average - noticeable impact on quality of life';
427
+ return 'Concerning - significant impact, please discuss with care team';
428
+ }
429
+ identifyQoLAlerts(scores) {
430
+ const alerts = [];
431
+ for (const score of scores) {
432
+ const percentage = (score.score / score.maxScore) * 100;
433
+ if (percentage < 40) {
434
+ alerts.push({
435
+ domain: score.domain,
436
+ concern: `${score.domain} score is below 40%`,
437
+ severity: 'high',
438
+ recommendation: 'Discuss with your care team at your next appointment'
439
+ });
440
+ }
441
+ else if (percentage < 60 && score.changeFromLast !== undefined && score.changeFromLast < -1) {
442
+ alerts.push({
443
+ domain: score.domain,
444
+ concern: `${score.domain} has declined since last assessment`,
445
+ severity: 'moderate',
446
+ recommendation: 'Monitor and report if continues to decline'
447
+ });
448
+ }
449
+ }
450
+ return alerts;
451
+ }
452
+ // ═══════════════════════════════════════════════════════════════════════════════
453
+ // SECURE MESSAGING
454
+ // ═══════════════════════════════════════════════════════════════════════════════
455
+ /**
456
+ * Send a message
457
+ */
458
+ async sendMessage(patientId, body, category, priority = 'routine', subject) {
459
+ const id = `MSG-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
460
+ const threadId = `THR-${Date.now()}-${Math.random().toString(36).substring(2, 6)}`;
461
+ const message = {
462
+ id,
463
+ threadId,
464
+ patientId,
465
+ direction: 'inbound',
466
+ sender: {
467
+ type: 'patient',
468
+ name: 'Patient', // Would be fetched from account
469
+ id: patientId
470
+ },
471
+ recipient: {
472
+ type: 'care-team',
473
+ name: 'Care Team'
474
+ },
475
+ subject,
476
+ body,
477
+ sentAt: new Date(),
478
+ priority,
479
+ category,
480
+ requiresResponse: true,
481
+ responseDeadline: priority === 'urgent'
482
+ ? new Date(Date.now() + 4 * 60 * 60 * 1000) // 4 hours for urgent
483
+ : new Date(Date.now() + 48 * 60 * 60 * 1000), // 48 hours for routine
484
+ status: 'unread'
485
+ };
486
+ const patientMessages = this.messages.get(patientId) || [];
487
+ patientMessages.push(message);
488
+ this.messages.set(patientId, patientMessages);
489
+ this.emit('message-sent', { patientId, messageId: id, category, priority });
490
+ return message;
491
+ }
492
+ /**
493
+ * Get messages for patient
494
+ */
495
+ getMessages(patientId, unreadOnly = false) {
496
+ const messages = this.messages.get(patientId) || [];
497
+ if (unreadOnly) {
498
+ return messages.filter(m => m.status === 'unread');
499
+ }
500
+ return messages.sort((a, b) => b.sentAt.getTime() - a.sentAt.getTime());
501
+ }
502
+ /**
503
+ * Mark message as read
504
+ */
505
+ markMessageRead(patientId, messageId) {
506
+ const messages = this.messages.get(patientId) || [];
507
+ const message = messages.find(m => m.id === messageId);
508
+ if (message) {
509
+ message.status = 'read';
510
+ message.readAt = new Date();
511
+ }
512
+ }
513
+ // ═══════════════════════════════════════════════════════════════════════════════
514
+ // EDUCATIONAL CONTENT
515
+ // ═══════════════════════════════════════════════════════════════════════════════
516
+ /**
517
+ * Get recommended educational content
518
+ */
519
+ getRecommendedContent(patientId) {
520
+ const summary = this.treatmentSummaries.get(patientId);
521
+ if (!summary) {
522
+ return Array.from(this.educationalContent.values()).slice(0, 5);
523
+ }
524
+ // Match content based on patient's diagnosis and treatment
525
+ const diagnosis = summary.diagnosis.condition.toLowerCase();
526
+ const treatment = summary.currentTreatment.regimen.toLowerCase();
527
+ const relevantContent = Array.from(this.educationalContent.values()).filter(content => {
528
+ const matchesCancer = content.cancerTypes?.some(ct => diagnosis.includes(ct.toLowerCase()));
529
+ const matchesTreatment = content.treatmentTypes?.some(tt => treatment.includes(tt.toLowerCase()));
530
+ return matchesCancer || matchesTreatment;
531
+ });
532
+ return relevantContent.slice(0, 10);
533
+ }
534
+ /**
535
+ * Search educational content
536
+ */
537
+ searchContent(query) {
538
+ const lowerQuery = query.toLowerCase();
539
+ return Array.from(this.educationalContent.values()).filter(content => content.title.toLowerCase().includes(lowerQuery) ||
540
+ content.description.toLowerCase().includes(lowerQuery) ||
541
+ content.topics.some(t => t.toLowerCase().includes(lowerQuery)));
542
+ }
543
+ initializeEducationalContent() {
544
+ const content = [
545
+ {
546
+ id: 'edu-1',
547
+ title: 'Understanding Your Cancer Treatment',
548
+ description: 'An overview of how cancer treatments work and what to expect.',
549
+ contentType: 'article',
550
+ targetAudience: ['new-patients', 'caregivers'],
551
+ topics: ['treatment', 'basics', 'getting-started'],
552
+ readingLevel: 'basic',
553
+ language: 'en',
554
+ content: '# Understanding Your Cancer Treatment\n\nThis guide explains the different types of cancer treatment...',
555
+ lastUpdated: new Date()
556
+ },
557
+ {
558
+ id: 'edu-2',
559
+ title: 'Managing Side Effects',
560
+ description: 'Tips for managing common side effects from cancer treatment.',
561
+ contentType: 'article',
562
+ targetAudience: ['patients', 'caregivers'],
563
+ topics: ['side-effects', 'self-care', 'management'],
564
+ readingLevel: 'basic',
565
+ language: 'en',
566
+ content: '# Managing Side Effects\n\nMany cancer treatments can cause side effects...',
567
+ lastUpdated: new Date()
568
+ },
569
+ {
570
+ id: 'edu-3',
571
+ title: 'What is Immunotherapy?',
572
+ description: 'Learn how immunotherapy helps your immune system fight cancer.',
573
+ contentType: 'video',
574
+ targetAudience: ['patients'],
575
+ treatmentTypes: ['immunotherapy'],
576
+ topics: ['immunotherapy', 'treatment-types'],
577
+ readingLevel: 'basic',
578
+ language: 'en',
579
+ content: '',
580
+ videoUrl: 'https://example.com/immunotherapy-video',
581
+ duration: 5,
582
+ lastUpdated: new Date()
583
+ },
584
+ {
585
+ id: 'edu-4',
586
+ title: 'Nutrition During Treatment',
587
+ description: 'Healthy eating tips while receiving cancer treatment.',
588
+ contentType: 'article',
589
+ targetAudience: ['patients', 'caregivers'],
590
+ topics: ['nutrition', 'self-care', 'wellness'],
591
+ readingLevel: 'basic',
592
+ language: 'en',
593
+ content: '# Nutrition During Treatment\n\nGood nutrition is important during cancer treatment...',
594
+ lastUpdated: new Date()
595
+ }
596
+ ];
597
+ for (const c of content) {
598
+ this.educationalContent.set(c.id, c);
599
+ }
600
+ }
601
+ // ═══════════════════════════════════════════════════════════════════════════════
602
+ // APPOINTMENTS
603
+ // ═══════════════════════════════════════════════════════════════════════════════
604
+ /**
605
+ * Request an appointment
606
+ */
607
+ async requestAppointment(request) {
608
+ const id = `APT-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
609
+ const fullRequest = {
610
+ ...request,
611
+ id,
612
+ status: 'pending',
613
+ submittedAt: new Date()
614
+ };
615
+ const patientRequests = this.appointmentRequests.get(request.patientId) || [];
616
+ patientRequests.push(fullRequest);
617
+ this.appointmentRequests.set(request.patientId, patientRequests);
618
+ this.emit('appointment-requested', {
619
+ patientId: request.patientId,
620
+ requestId: id,
621
+ type: request.appointmentType,
622
+ urgency: request.urgency
623
+ });
624
+ return id;
625
+ }
626
+ /**
627
+ * Get appointment requests for patient
628
+ */
629
+ getAppointmentRequests(patientId) {
630
+ return this.appointmentRequests.get(patientId) || [];
631
+ }
632
+ /**
633
+ * Get dashboard data for patient
634
+ */
635
+ getDashboard(patientId) {
636
+ const summary = this.getTreatmentSummary(patientId);
637
+ const recentSymptoms = this.getSymptomHistory(patientId, 7);
638
+ const adherence = this.getAdherenceSummary(patientId);
639
+ const messages = this.getMessages(patientId, true);
640
+ const content = this.getRecommendedContent(patientId);
641
+ const alerts = [];
642
+ // Check for concerning symptoms
643
+ const latestSymptom = recentSymptoms[recentSymptoms.length - 1];
644
+ if (latestSymptom?.requiresFollowUp) {
645
+ alerts.push('Please contact your care team about your recent symptoms');
646
+ }
647
+ // Check adherence
648
+ if (adherence.overallAdherence < 80) {
649
+ alerts.push('Your medication adherence has been below target - remember to take medications as prescribed');
650
+ }
651
+ // Check unread messages
652
+ if (messages.length > 0) {
653
+ alerts.push(`You have ${messages.length} unread message(s) from your care team`);
654
+ }
655
+ return {
656
+ summary,
657
+ recentSymptoms: recentSymptoms.slice(-5),
658
+ adherence,
659
+ unreadMessages: messages.length,
660
+ upcomingAppointments: summary?.upcomingAppointments || [],
661
+ recommendedContent: content.slice(0, 3),
662
+ alerts
663
+ };
664
+ }
665
+ }
666
+ export default PatientPortalService;
667
+ //# sourceMappingURL=patientPortal.js.map