@ctchealth/plato-sdk 0.0.15 → 0.0.17

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/README.md ADDED
@@ -0,0 +1,1420 @@
1
+ # Plato SDK
2
+
3
+ A TypeScript SDK for interacting with the Plato API to create and manage AI-powered medical training simulations.
4
+
5
+ ## Overview
6
+
7
+ The Plato SDK provides a simple and type-safe way to integrate with the Plato platform, allowing you to create medical training simulations with AI personas and manage voice-based interactions.
8
+
9
+ ## Authentication
10
+
11
+ All API requests require authentication using a Bearer token. Include your JWT token in the `API-AUTH` header:
12
+
13
+ ```
14
+ API-AUTH: Bearer <your-jwt-token>
15
+ ```
16
+
17
+ Requests without a valid JWT token will be rejected with a `401 Unauthorized` response.
18
+
19
+ ## Features
20
+
21
+ - Create AI personas with customizable professional profiles and personalities
22
+ - Real-time voice interactions with AI assistants
23
+ - Comprehensive event system for call management
24
+ - Type-safe API with full TypeScript support
25
+ - Medical training simulation configuration
26
+ - Simulation persistence and recovery across page reloads
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ npm install plato-sdk
32
+ ```
33
+
34
+ ## Quick Start
35
+
36
+ ```typescript
37
+ import { PlatoApiClient, AvatarLanguage } from 'plato-sdk';
38
+
39
+ // Initialize the client
40
+ const client = new PlatoApiClient({
41
+ baseUrl: 'https://your-plato-api.com',
42
+ token: 'your-api-token',
43
+ user: 'test@test.test',
44
+ });
45
+
46
+ // Get available assistant images
47
+ const images = await client.getAssistantImages();
48
+
49
+ // Create a simulation
50
+ const simulation = await client.createSimulation({
51
+ persona: {
52
+ professionalProfile: {
53
+ location: 'City Hospital, New York',
54
+ practiceSettings: 'Outpatient Clinic',
55
+ yearOfExperience: 12,
56
+ specialityAndDepartment: 'Cardiology',
57
+ },
58
+ segment: SegmentType.CostConsciousPrescriber,
59
+ assistantGender: AssistantVoiceGender.Male,
60
+ name: 'Dr Vegapunk',
61
+ personalityAndBehaviour: {
62
+ riskTolerance: 40,
63
+ researchOrientation: 70,
64
+ recognitionNeed: 60,
65
+ brandLoyalty: 55,
66
+ patientEmpathy: 80,
67
+ },
68
+ context: {
69
+ subSpecialityOrTherapyFocus: 'Hypertension management',
70
+ typicalPatientMix: 'Elderly with comorbidities',
71
+ keyClinicalDrivers: 'Reducing cardiovascular risk',
72
+ },
73
+ },
74
+ product: {
75
+ name: 'Ibuprofen 1000mg',
76
+ description:
77
+ 'A nonsteroidal anti-inflammatory drug used to reduce pain, inflammation, and fever',
78
+ },
79
+ presentation: 'Oral tablets, 10 tablets per pack',
80
+ scenario: 'Discussing treatment options for an elderly patient with chronic arthritis',
81
+ objectives:
82
+ 'Demonstrate efficacy of Ibuprofen in pain management Highlight safety profile and contraindications Encourage patient adherence',
83
+ anticipatedObjections:
84
+ 'Concerns about gastrointestinal side effects Preference for lower-cost generic alternatives Potential interactions with other medications',
85
+ imageId: images[0]._id,
86
+ avatarLanguage: AvatarLanguage.English,
87
+ });
88
+
89
+ // Check the simulation status - If simulation creation phase is equals to FINISHED, the simulation is ready to use
90
+ // If the simulation creation phase is ERROR the simulation creation failed and you need to retry creating a new one
91
+ // tip: you can also check the simulation status periodically using a polling mechanism
92
+ const status = await client.getSimulationStatus(simulation.simulationId);
93
+
94
+ // Start a voice call
95
+ const call = await client.startCall(simulation.simulationId);
96
+
97
+ // Listen to call events
98
+ call.on('call-start', () => {
99
+ console.log('Call started');
100
+ });
101
+
102
+ call.on('message', message => {
103
+ console.log('Message received:', message.transcript);
104
+ });
105
+
106
+ call.on('call-end', () => {
107
+ console.log('Call ended - processing feedback...');
108
+ });
109
+
110
+ // Automatically receive post-call feedback when ready
111
+ call.on('call-details-ready', callDetails => {
112
+ console.log('Call Summary:', callDetails.summary);
113
+ console.log('Score:', callDetails.score);
114
+ console.log('Strengths:', callDetails.strengths);
115
+ console.log('Areas to improve:', callDetails.weaknesses);
116
+ });
117
+
118
+ // Stop the call when done
119
+ call.stopCall();
120
+ ```
121
+
122
+ ## API Reference
123
+
124
+ ### PlatoApiClient
125
+
126
+ The main class for interacting with the Plato API.
127
+
128
+ #### Constructor
129
+
130
+ ```typescript
131
+ new PlatoApiClient(config: ApiClientConfig)
132
+ ```
133
+
134
+ **Parameters:**
135
+
136
+ - `config.baseUrl` (string): The base URL of the Plato API
137
+ - `config.token` (string): Your API authentication token
138
+ - `config.user` (string): Your user identifier
139
+ - `config.jwtToken` (string, optional): A per-user JWT token sent as the `x-client-token` header on every request
140
+
141
+ **Example with JWT token:**
142
+
143
+ ```typescript
144
+ const client = new PlatoApiClient({
145
+ baseUrl: 'https://your-plato-api.com',
146
+ token: 'your-api-key',
147
+ user: 'user@example.com',
148
+ jwtToken: 'eyJhbGciOiJSUzI1NiIs...', // optional, per-user token
149
+ });
150
+ ```
151
+
152
+ #### Methods
153
+
154
+ ##### setJwtToken(jwtToken: string)
155
+
156
+ Updates the JWT token for all subsequent requests. Use this when the token is refreshed or when switching user context.
157
+
158
+ ```typescript
159
+ client.setJwtToken('new-jwt-token');
160
+ ```
161
+
162
+ ##### clearJwtToken()
163
+
164
+ Removes the JWT token from subsequent requests.
165
+
166
+ ```typescript
167
+ client.clearJwtToken();
168
+ ```
169
+
170
+ ##### createSimulation(params: CreateSimulationDto)
171
+
172
+ Creates a new medical training simulation. It may take a few minutes for the simulation to be ready for use.
173
+
174
+ **Returns:** `Promise<{ simulationId: string, phase: CreationPhase }>`
175
+
176
+ ##### startCall(simulationId: string)
177
+
178
+ Starts a voice call with the AI persona for the specified simulation.
179
+
180
+ **Returns:** Object with:
181
+
182
+ - `stopCall()`: Function to end the call
183
+ - `callId`: Unique identifier for the call
184
+ - `on<K>(event: K, listener: CallEventListener<K>)`: Subscribe to call events
185
+ - `off<K>(event: K, listener: CallEventListener<K>)`: Unsubscribe from call events
186
+
187
+ ##### getCallDetails(callId: string)
188
+
189
+ Retrieves detailed information about a completed call, including transcript, summary, recording URL, ratings, and evaluation metrics.
190
+
191
+ **Parameters:**
192
+
193
+ - `callId` (string): The MongoDB `_id` of the call
194
+
195
+ **Returns:** `Promise<CallDTO>` — An object containing:
196
+
197
+ - `_id`: MongoDB ID of the call
198
+ - `summary`: Summary of the conversation
199
+ - `transcript`: Full transcript of the call
200
+ - `recordingUrl`: URL to access the call recording
201
+ - `rating`: User-provided rating (0-5)
202
+ - `successEvaluation`: Boolean indicating if the call was successful
203
+ - `score`: Overall score for the call
204
+ - `strengths`: Array of identified strengths
205
+ - `weaknesses`: Array of identified weaknesses
206
+ - `metric1`, `metric2`, `metric3`: Evaluation metric names
207
+ - `metric1Value`, `metric2Value`, `metric3Value`: Values for each metric
208
+ - `createdAt`: Timestamp when the call was created
209
+ - `endedAt`: Timestamp when the call ended
210
+ - `callDurationMs`: Duration of the call in milliseconds
211
+ - And other call-related fields
212
+
213
+ **Example:**
214
+
215
+ ```typescript
216
+ // After a call has ended, retrieve its details
217
+ const callDetails = await client.getCallDetails(call._id);
218
+ console.log('Call Summary:', callDetails.summary);
219
+ ```
220
+
221
+ ##### getCallRecordings(queryParams: SimulationRecordingsQueryDto)
222
+
223
+ Retrieves a paginated list of call recordings for the authenticated user.
224
+
225
+ **Parameters:**
226
+
227
+ - `queryParams` (SimulationRecordingsQueryDto): Query parameters for filtering and pagination
228
+ - `limit` (optional): Number of recordings per page (`5`, `10`, or `25`)
229
+ - `page` (optional): Page number for pagination
230
+ - `sort` (optional): Sort order (`'asc'` or `'desc'`)
231
+
232
+ **Returns:** `Promise<SimulationRecordingsDto[]>` — An array of recording objects, each containing:
233
+
234
+ - `_id`: MongoDB ID of the call
235
+ - `createdAt`: Timestamp when the call was created
236
+ - `recordingStatus`: Status of the recording (`'STARTED'`, `'PROCESSING'`, `'FINISHED'`, or `'FAILED'`)
237
+
238
+ **Example:**
239
+
240
+ ```typescript
241
+ // Get the 10 most recent call recordings
242
+ const recordings = await client.getCallRecordings({
243
+ limit: 10,
244
+ page: 1,
245
+ sort: 'desc',
246
+ });
247
+
248
+ recordings.forEach(recording => {
249
+ console.log(`Call ${recording._id} - Status: ${recording.recordingStatus}`);
250
+ });
251
+ ```
252
+
253
+ ##### getCallRecording(callId: string)
254
+
255
+ Retrieves the recording URL for a specific call.
256
+
257
+ **Parameters:**
258
+
259
+ - `callId` (string): The MongoDB `_id` of the call
260
+
261
+ **Returns:** `Promise<string>` — The URL to access the call recording
262
+
263
+ **Example:**
264
+
265
+ ```typescript
266
+ // Get the recording URL for a specific call
267
+ const recordingUrl = await client.getCallRecording(call._id);
268
+ console.log('Recording URL:', recordingUrl);
269
+ ```
270
+
271
+ ##### uploadPdfSlides(file: File | Blob)
272
+
273
+ Uploads a PDF file for slide analysis. The file will be uploaded to S3 and analyzed asynchronously.
274
+
275
+ **Parameters:**
276
+
277
+ - `file` (File | Blob): The PDF file to upload
278
+
279
+ **Returns:** `Promise<string>` — The MongoDB ID of the created PDF record, which can be used to track the analysis status.
280
+
281
+ **Limitations:**
282
+
283
+ - **Latin-only file names:** The PDF file name must contain only Latin characters. Non-latin characters in the file name are not supported.
284
+ - **Maximum 100 pages:** PDF files cannot exceed 100 pages.
285
+ - **No duplicate content:** Uploading a PDF with identical content to an already uploaded file is not allowed. Duplicates are detected based on file content, not file name.
286
+
287
+ **Example:**
288
+
289
+ ```typescript
290
+ // Upload a PDF file from an input element
291
+ const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
292
+ const file = fileInput.files?.[0];
293
+
294
+ if (file) {
295
+ const pdfId = await client.uploadPdfSlides(file);
296
+ console.log('PDF upload initiated, ID:', pdfId);
297
+
298
+ // Now you can immediately check the status using the returned ID
299
+ const status = await client.checkPdfStatus(pdfId);
300
+ console.log('Initial status:', status);
301
+ }
302
+ ```
303
+
304
+ ##### getSlidesAnalysis(queryParams: PdfSlidesAnalysisQueryDto)
305
+
306
+ Retrieves a paginated and sorted list of PDF slide analysis records.
307
+
308
+ **Parameters:**
309
+
310
+ - `queryParams` (PdfSlidesAnalysisQueryDto):
311
+ - `limit` (optional): Number of records per page
312
+ - `page` (optional): Page number
313
+ - `sort` (optional): Sort order (`'asc'` or `'desc'`)
314
+ - `sortBy` (optional): Field to sort by (`'createdAt'`, `'originalFilename'`, or `'totalSlides'`)
315
+
316
+ **Returns:** `Promise<PdfSlidesDto[]>`
317
+
318
+ **Example:**
319
+
320
+ ```typescript
321
+ const analysisList = await client.getSlidesAnalysis({
322
+ limit: 10,
323
+ page: 0,
324
+ sort: 'desc',
325
+ sortBy: 'createdAt',
326
+ });
327
+ ```
328
+
329
+ ##### getSlideAnalysis(id: string)
330
+
331
+ Retrieves the detailed analysis data for a specific PDF slide record, including the overview and individual slide analysis.
332
+
333
+ **Parameters:**
334
+
335
+ - `id` (string): The unique ID of the PDF slide record
336
+
337
+ **Returns:** `Promise<PdfSlideDto>`
338
+
339
+ **Example:**
340
+
341
+ ```typescript
342
+ const details = await client.getSlideAnalysis('pdf-slide-id');
343
+ console.log('Lesson Overview:', details.lessonOverview);
344
+ ```
345
+
346
+ ##### deleteSlideAnalysis(id: string)
347
+
348
+ Deletes a PDF slide analysis record from the database and removes the corresponding file from S3 storage.
349
+
350
+ **Parameters:**
351
+
352
+ - `id` (string): The unique ID of the PDF slide record to delete
353
+
354
+ **Returns:** `Promise<void>`
355
+
356
+ **Example:**
357
+
358
+ ```typescript
359
+ await client.deleteSlideAnalysis('pdf-slide-id');
360
+ console.log('Analysis deleted successfully');
361
+ ```
362
+
363
+ ##### checkPdfStatus(id: string)
364
+
365
+ Checks the current processing status of a PDF slide analysis. Use this to monitor the progress of PDF analysis workflows.
366
+
367
+ **Parameters:**
368
+
369
+ - `id` (string): The unique ID of the PDF slide record
370
+
371
+ **Returns:** `Promise<{ status: PdfSlidesStatus }>` — An object containing the current status
372
+
373
+ **Available Status Values:**
374
+
375
+ - `started` - PDF uploaded and record created
376
+ - `processing` - Receiving batches of analyzed slides
377
+ - `overview` - All slides analyzed, generating lesson overview
378
+ - `completed` - Analysis complete and ready to view
379
+ - `failed` - Analysis failed (currently not implemented)
380
+
381
+ **Example:**
382
+
383
+ ```typescript
384
+ // Check PDF analysis status
385
+ const { status } = await client.checkPdfStatus('pdf-slide-id');
386
+
387
+ console.log('Current status:', status);
388
+ ```
389
+
390
+ **Typical Workflow:**
391
+
392
+ ```typescript
393
+ // 1. Upload PDF and get the ID immediately
394
+ const pdfId = await client.uploadPdfSlides(file);
395
+ console.log('PDF uploaded with ID:', pdfId);
396
+
397
+ // 2. Poll status until complete
398
+ const checkStatus = async () => {
399
+ const { status } = await client.checkPdfStatus(pdfId);
400
+
401
+ switch (status) {
402
+ case 'pending':
403
+ console.log('Waiting for upload confirmation...');
404
+ break;
405
+ case 'started':
406
+ console.log('Upload confirmed, starting analysis...');
407
+ break;
408
+ case 'processing':
409
+ console.log('Analyzing slides...');
410
+ break;
411
+ case 'overview':
412
+ console.log('Generating lesson overview...');
413
+ break;
414
+ case 'completed':
415
+ console.log('Analysis complete!');
416
+ // Now fetch the full analysis
417
+ const analysis = await client.getSlideAnalysis(pdfId);
418
+ console.log('Lesson overview:', analysis.lessonOverview);
419
+ return; // Stop polling
420
+ case 'failed':
421
+ console.log('Analysis failed');
422
+ return; // Stop polling
423
+ }
424
+
425
+ // Check again in 3 seconds
426
+ setTimeout(checkStatus, 3000);
427
+ };
428
+
429
+ checkStatus();
430
+ ```
431
+
432
+ ##### getAssistantImages()
433
+
434
+ Retrieves all available assistant images that can be used when creating a simulation.
435
+
436
+ **Returns:** `Promise<AssistantImageDto[]>` — An array of assistant image objects, each containing:
437
+
438
+ - `_id`: Unique identifier for the image
439
+ - `imageUrl`: URL of the assistant image
440
+
441
+ **Example:**
442
+
443
+ ```typescript
444
+ // Get all available assistant images
445
+ const images = await client.getAssistantImages();
446
+ ```
447
+
448
+ ##### deleteSimulation(simulationId: string)
449
+
450
+ Deletes a simulation and all associated data. This permanently removes the simulation, all its call records, and excludes it from future recommendations analysis.
451
+
452
+ **Parameters:**
453
+
454
+ - `simulationId` (string): The MongoDB `_id` of the simulation to delete
455
+
456
+ **Returns:** `Promise<void>`
457
+
458
+ **Example:**
459
+
460
+ ```typescript
461
+ await client.deleteSimulation('507f1f77bcf86cd799439011');
462
+ ```
463
+
464
+ **Note:** This action is irreversible. All calls and recordings linked to the simulation will also be deleted.
465
+
466
+ ##### getRecommendations()
467
+
468
+ Retrieves recommendations based on the user's recent call performance. Analyzes the 5 most recent calls using a sliding window of 3 calls to identify patterns and provide actionable insights. Calls shorter than 2 minutes are excluded from analysis. No new recommendations are generated if fewer than 3 eligible calls are available.
469
+
470
+ **Returns:** `Promise<RecommendationsResponseDto>` — An object containing:
471
+
472
+ - `recommendations`: Array of recommendation objects, each with:
473
+ - `title`: Brief title of the recommendation
474
+ - `description`: Detailed, actionable description
475
+ - `priority`: Priority level (`'high'`, `'medium'`, or `'low'`)
476
+ - `strengths`: Array of identified strengths, each with:
477
+ - `strength`: Description of the strength
478
+ - `frequency`: Number of calls where this strength appeared
479
+ - `improvementAreas`: Array of areas needing improvement, each with:
480
+ - `area`: Description of the improvement area
481
+ - `frequency`: Number of calls where this weakness appeared
482
+ - `summary`: A 2-3 sentence overall summary of performance trends
483
+ - `callsAnalyzed`: Number of calls that were analyzed
484
+ - `generatedAt`: Timestamp when the recommendations were generated
485
+
486
+ **Example:**
487
+
488
+ ```typescript
489
+ // Get personalized recommendations based on call history
490
+ const recommendations = await client.getRecommendations();
491
+
492
+ console.log(`Analyzed ${recommendations.callsAnalyzed} calls`);
493
+ console.log('Summary:', recommendations.summary);
494
+
495
+ // Display high-priority recommendations
496
+ recommendations.recommendations
497
+ .filter(rec => rec.priority === 'high')
498
+ .forEach(rec => {
499
+ console.log(`[HIGH] ${rec.title}: ${rec.description}`);
500
+ });
501
+
502
+ // Show recurring strengths
503
+ recommendations.strengths.forEach(s => {
504
+ console.log(`Strength (${s.frequency} calls): ${s.strength}`);
505
+ });
506
+
507
+ // Show areas for improvement
508
+ recommendations.improvementAreas.forEach(area => {
509
+ console.log(`Needs work (${area.frequency} calls): ${area.area}`);
510
+ });
511
+ ```
512
+
513
+ **Note:** This method requires the user to have at least one completed call. If no calls are found, a `BadRequestException` will be thrown.
514
+
515
+ ## Checking Simulation Status
516
+
517
+ You can check the current phase/status of a simulation using the `checkSimulationStatus` method. This is useful for polling the simulation creation process until it is ready or has failed.
518
+
519
+ ```typescript
520
+ const status = await client.checkSimulationStatus(simulation.simulationId);
521
+ console.log('Current phase:', status.phase); // e.g., 'FINISHED', 'ERROR', etc.
522
+ ```
523
+
524
+ - **Returns:** `{ phase: CreationPhase }` — The current phase of the simulation creation process.
525
+ - **Typical usage:** Poll this method until the phase is `FINISHED` or `ERROR` before starting a call.
526
+
527
+ ## Simulation Persistence
528
+
529
+ Simulations are persisted server-side and can be recovered after page reloads. Store the simulation ID client-side (e.g., localStorage) for quick recovery.
530
+
531
+ ### getSimulationDetails(simulationId: string)
532
+
533
+ Retrieves detailed information about a simulation, including its original configuration.
534
+
535
+ **Returns:** `Promise<SimulationDetailsDto>` containing:
536
+
537
+ - `simulationId`: MongoDB ID
538
+ - `phase`: Current creation phase
539
+ - `assistantId`: The assistant ID (available after creation)
540
+ - `configuration`: Original `CreateSimulationDto`
541
+ - `simulationBriefing`: Optional `SimulationBriefingDto` with persona description, scenario context, and training objectives (available after creation phase reaches `FINISHED`)
542
+ - `createdAt`, `updatedAt`: Timestamps
543
+
544
+ **Example:**
545
+
546
+ ```typescript
547
+ // Store simulation ID after creation
548
+ const simulation = await client.createSimulation(config);
549
+ localStorage.setItem('plato_simulation_id', simulation.simulationId);
550
+
551
+ // Recover simulation on page reload
552
+ const simulationId = localStorage.getItem('plato_simulation_id');
553
+ if (simulationId) {
554
+ const details = await client.getSimulationDetails(simulationId);
555
+
556
+ if (details.phase !== CreationPhase.ERROR) {
557
+ // Resume polling if still in progress
558
+ if (details.phase !== CreationPhase.FINISHED) {
559
+ startPolling(details.simulationId);
560
+ }
561
+
562
+ // Access simulation briefing when available
563
+ if (details.simulationBriefing) {
564
+ console.log('Objective:', details.simulationBriefing.objectiveTag);
565
+ console.log('Persona:', details.simulationBriefing.personaDescription.summary);
566
+ console.log('Scenario:', details.simulationBriefing.scenarioContext.summary);
567
+ console.log('Training Goals:', details.simulationBriefing.trainingObjective.keyObjectives);
568
+ }
569
+ } else {
570
+ localStorage.removeItem('plato_simulation_id');
571
+ }
572
+ }
573
+ ```
574
+
575
+ ### getUserSimulations()
576
+
577
+ Retrieves all simulations for the authenticated user. Useful when the simulation ID is not stored locally.
578
+
579
+ **Returns:** `Promise<Array<{ simulationId: string; phase: CreationPhase }>>`
580
+
581
+ **Example:**
582
+
583
+ ```typescript
584
+ const simulations = await client.getUserSimulations();
585
+ const inProgress = simulations.find(
586
+ sim => sim.phase !== CreationPhase.FINISHED && sim.phase !== CreationPhase.ERROR
587
+ );
588
+ ```
589
+
590
+ ## Call Recovery
591
+
592
+ The SDK automatically handles call recovery in case of page refreshes or browser closures during active calls. This ensures that call data is never lost and all calls get properly processed by the backend, even if the page is refreshed while a call is in progress.
593
+
594
+ ### How It Works
595
+
596
+ When you start a call, the SDK stores minimal call state in the browser's localStorage. If the page is refreshed:
597
+
598
+ 1. **On app initialization**: The `recoverAbandonedCall()` method checks for any stored call state
599
+ 2. **Age-based recovery**: Calls older than 5 minutes are considered abandoned and automatically recovered
600
+ 3. **Backend notification**: The backend is notified to process the call's transcript, recording, and analytics
601
+ 4. **Automatic cleanup**: The stored state is cleared after recovery or when a call ends naturally
602
+
603
+ This recovery mechanism works in **two stages** to ensure complete data integrity:
604
+
605
+ - **Stage 1 (App Init)**: Recovers truly abandoned calls (>5 minutes old)
606
+ - **Stage 2 (New Call Start)**: Notifies backend of ANY previous call before starting a new one
607
+
608
+ This dual approach ensures no call data is ever lost, regardless of user behavior patterns.
609
+
610
+ ### Integration
611
+
612
+ #### Angular
613
+
614
+ Call `recoverAbandonedCall()` during component initialization:
615
+
616
+ ```typescript
617
+ import { Component, OnInit } from '@angular/core';
618
+ import { PlatoApiClient } from 'plato-sdk';
619
+
620
+ @Component({
621
+ selector: 'app-training',
622
+ templateUrl: './training.component.html',
623
+ })
624
+ export class TrainingComponent implements OnInit {
625
+ constructor(private platoClient: PlatoApiClient) {}
626
+
627
+ async ngOnInit(): Promise<void> {
628
+ // Recover any abandoned calls from previous session
629
+ const recovered = await this.platoClient.recoverAbandonedCall();
630
+
631
+ if (recovered) {
632
+ console.log('Recovered abandoned call from previous session');
633
+ // Optional: Show notification to user
634
+ this.showNotification('Previous call data recovered and processed');
635
+ }
636
+
637
+ // Continue with normal initialization
638
+ await this.loadSimulations();
639
+ }
640
+
641
+ showNotification(message: string) {
642
+ // Your notification logic here
643
+ }
644
+ }
645
+ ```
646
+
647
+ #### React
648
+
649
+ Call `recoverAbandonedCall()` in a useEffect hook:
650
+
651
+ ```typescript
652
+ import { useEffect, useState } from 'react';
653
+ import { PlatoApiClient } from 'plato-sdk';
654
+
655
+ function TrainingApp() {
656
+ const [platoClient] = useState(() => new PlatoApiClient({
657
+ baseUrl: 'https://your-api.com',
658
+ token: 'your-token',
659
+ user: 'your-user',
660
+ }));
661
+
662
+ useEffect(() => {
663
+ const recoverCall = async () => {
664
+ try {
665
+ const recovered = await platoClient.recoverAbandonedCall();
666
+
667
+ if (recovered) {
668
+ console.log('Recovered abandoned call');
669
+ // Optional: Show toast notification
670
+ showToast('Previous call data recovered');
671
+ }
672
+ } catch (error) {
673
+ console.error('Recovery error:', error);
674
+ }
675
+ };
676
+
677
+ recoverCall();
678
+ }, [platoClient]);
679
+
680
+ return (
681
+ // Your app UI
682
+ );
683
+ }
684
+ ```
685
+
686
+ ### recoverAbandonedCall()
687
+
688
+ Recovers and processes any abandoned calls from previous sessions.
689
+
690
+ **Returns:** `Promise<boolean>`
691
+
692
+ - `true` if an abandoned call was found and recovered
693
+ - `false` if no abandoned call exists or the call is too recent (<5 minutes)
694
+
695
+ **Example:**
696
+
697
+ ```typescript
698
+ // Check if any calls were recovered
699
+ const wasRecovered = await client.recoverAbandonedCall();
700
+
701
+ if (wasRecovered) {
702
+ // An abandoned call was processed
703
+ console.log('Successfully recovered abandoned call');
704
+ } else {
705
+ // No recovery needed
706
+ console.log('No abandoned calls to recover');
707
+ }
708
+ ```
709
+
710
+ ### Behavior Details
711
+
712
+ #### When Recovery Happens
713
+
714
+ **Scenario 1: Abandoned Call (>5 minutes)**
715
+
716
+ ```
717
+ 10:00:00 - User starts call → State stored
718
+ 10:02:00 - User closes browser
719
+ 10:08:00 - User returns and opens app
720
+ 10:08:01 - recoverAbandonedCall() detects call (>5 min old)
721
+ 10:08:01 - Backend notified → Call processed ✓
722
+ ```
723
+
724
+ **Scenario 2: Recent Call (<5 minutes)**
725
+
726
+ ```
727
+ 10:00:00 - User starts call → State stored
728
+ 10:01:00 - User refreshes page
729
+ 10:01:01 - recoverAbandonedCall() checks call (1 min old)
730
+ 10:01:01 - Too recent, skip recovery
731
+ 10:03:00 - User starts new call
732
+ 10:03:00 - startCall() detects previous call
733
+ 10:03:00 - Backend notified of previous call → Processed ✓
734
+ 10:03:01 - New call starts
735
+ ```
736
+
737
+ #### Automatic Cleanup
738
+
739
+ The SDK automatically clears stored call state when:
740
+
741
+ 1. **Call ends naturally**: When `call-end` event fires
742
+ 2. **User stops call**: When `stopCall()` is called manually
743
+ 3. **New call starts**: Before starting a new call (after notifying backend of previous call)
744
+ 4. **After recovery**: After successfully notifying backend of abandoned call
745
+
746
+ ### Technical Details
747
+
748
+ #### What Gets Stored
749
+
750
+ The SDK stores minimal state in `localStorage` under the key `plato_active_call`:
751
+
752
+ ```typescript
753
+ {
754
+ callId: "mongodb-call-id", // MongoDB ID for backend notification
755
+ externalCallId: "external-provider-id", // External provider's call ID
756
+ simulationId: "simulation-id", // Associated simulation
757
+ startedAt: "2025-01-15T10:00:00Z", // ISO 8601 timestamp
758
+ version: 1 // Schema version for future compatibility
759
+ }
760
+ ```
761
+
762
+ #### Privacy & Storage
763
+
764
+ - **Storage location**: Browser localStorage (persists across sessions)
765
+ - **Data size**: ~300 bytes (minimal footprint)
766
+ - **Privacy**: Stored locally, never sent to third parties
767
+ - **Graceful degradation**: If localStorage is disabled (e.g., private browsing), the SDK continues to work normally but recovery won't be available
768
+
769
+ #### Backend Idempotency
770
+
771
+ The backend `/api/v1/postcall/call-ended` endpoint is idempotent, meaning:
772
+
773
+ - ✅ Safe to call multiple times for the same call
774
+ - ✅ No duplicate processing or data corruption
775
+ - ✅ Recovery logic can safely notify backend even if uncertain
776
+
777
+ This design ensures **data integrity** over potential duplicate notifications.
778
+
779
+ ### Edge Cases
780
+
781
+ #### Multiple Tabs
782
+
783
+ If you have multiple tabs open:
784
+
785
+ - Only the most recent call state is stored (single localStorage key)
786
+ - Each new call in any tab overwrites previous state
787
+ - The most recent call is recovered if needed
788
+
789
+ **Impact**: Minimal - calls typically happen one at a time, and the backend handles all notifications properly.
790
+
791
+ #### Rapid Actions
792
+
793
+ If the user refreshes immediately after starting a call:
794
+
795
+ - The call state is stored but not considered abandoned (<5 minutes)
796
+ - When the user starts a new call later, the previous call is automatically notified to backend
797
+ - No data loss occurs
798
+
799
+ #### Browser Closure
800
+
801
+ If the browser is force-closed during a call:
802
+
803
+ - State persists in localStorage
804
+ - On next app launch, recovery detects and processes the call
805
+ - Call data is recovered successfully
806
+
807
+ #### localStorage Disabled
808
+
809
+ If localStorage is disabled (e.g., private browsing mode):
810
+
811
+ - All storage operations fail silently
812
+ - No errors thrown
813
+ - SDK continues to work for normal call flow
814
+ - Recovery simply won't be available
815
+
816
+ **This is acceptable** because localStorage is enabled in 99%+ of browsers.
817
+
818
+ ### Best Practices
819
+
820
+ 1. **Always call `recoverAbandonedCall()`** during app initialization to ensure data integrity
821
+ 2. **Call it early** in your initialization flow, before other operations
822
+ 3. **Handle the return value** to show user notifications when calls are recovered
823
+ 4. **Don't block UI** - recovery is fast (<500ms typically) but async
824
+ 5. **Trust the system** - the SDK and backend handle all edge cases automatically
825
+
826
+ ### Example: Complete Integration
827
+
828
+ ```typescript
829
+ // Angular Component
830
+ @Component({
831
+ selector: 'app-root',
832
+ templateUrl: './app.component.html',
833
+ })
834
+ export class AppComponent implements OnInit {
835
+ constructor(private platoClient: PlatoApiClient, private toastr: ToastrService) {}
836
+
837
+ async ngOnInit(): Promise<void> {
838
+ try {
839
+ // Step 1: Recover any abandoned calls
840
+ const recovered = await this.platoClient.recoverAbandonedCall();
841
+
842
+ if (recovered) {
843
+ this.toastr.info('Previous call data was recovered and processed');
844
+ }
845
+
846
+ // Step 2: Continue with normal app initialization
847
+ await this.loadUserData();
848
+ await this.loadSimulations();
849
+ } catch (error) {
850
+ console.error('Initialization error:', error);
851
+ }
852
+ }
853
+
854
+ async startCall(simulationId: string): Promise<void> {
855
+ try {
856
+ // The SDK automatically handles any previous call state
857
+ const call = await this.platoClient.startCall(simulationId);
858
+
859
+ call.on('call-end', () => {
860
+ console.log('Call ended');
861
+ // State automatically cleared
862
+ });
863
+
864
+ call.on('call-details-ready', callDetails => {
865
+ console.log('Feedback ready:', callDetails);
866
+ });
867
+ } catch (error) {
868
+ console.error('Call error:', error);
869
+ }
870
+ }
871
+ }
872
+ ```
873
+
874
+ ### Troubleshooting
875
+
876
+ **Q: What if I forget to call `recoverAbandonedCall()`?**
877
+
878
+ A: The SDK still protects against data loss through Stage 2 recovery - when you start a new call, it automatically notifies the backend of any previous call. However, it's still recommended to call `recoverAbandonedCall()` for optimal behavior.
879
+
880
+ **Q: Can I disable call recovery?**
881
+
882
+ A: While you can skip calling `recoverAbandonedCall()`, the Stage 2 recovery (in `startCall()`) always runs to prevent data loss. This is by design to ensure data integrity.
883
+
884
+ **Q: How do I know if a call was recovered?**
885
+
886
+ A: The `recoverAbandonedCall()` method returns `true` when a call is recovered. Use this to show user notifications or update UI accordingly.
887
+
888
+ **Q: What if the backend is down during recovery?**
889
+
890
+ A: The recovery attempt is logged and the stored state is cleared to prevent infinite retries. The SDK continues working normally. Recovery failures are graceful and don't break the app.
891
+
892
+ ## Event System
893
+
894
+ The SDK provides a comprehensive event system for managing voice calls:
895
+
896
+ ### Available Events
897
+
898
+ - `call-start`: Triggered when a call begins
899
+ - `call-end`: Triggered when a call ends
900
+ - `speech-start`: Triggered when speech detection starts
901
+ - `speech-end`: Triggered when speech detection ends
902
+ - `message`: Triggered when a message is received (contains transcript and metadata)
903
+ - `volume-level`: Triggered with volume level updates (number)
904
+ - `error`: Triggered when an error occurs
905
+ - `call-details-ready`: Triggered when post-call processing completes and call details are available (includes full `CallDTO` with transcript, summary, ratings, and evaluation)
906
+
907
+ ### Event Usage
908
+
909
+ ```typescript
910
+ // Subscribe to events
911
+ call.on('message', message => {
912
+ console.log('Transcript:', message.transcript);
913
+ console.log('Type:', message.transcriptType); // 'final' or 'partial'
914
+ });
915
+
916
+ call.on('volume-level', level => {
917
+ console.log('Volume level:', level);
918
+ });
919
+
920
+ call.on('error', error => {
921
+ console.error('Call error:', error);
922
+ });
923
+
924
+ // Listen for call details after post-call processing
925
+ call.on('call-details-ready', callDetails => {
926
+ console.log('Post-call feedback ready:', callDetails);
927
+ });
928
+ ```
929
+
930
+ ## Automatic Post-Call Feedback
931
+
932
+ The SDK automatically fetches detailed call information after each call ends and completes post-call processing. This includes comprehensive feedback such as transcript, summary, evaluation metrics, strengths, weaknesses, and ratings.
933
+
934
+ ### How It Works
935
+
936
+ When a call ends, the SDK automatically:
937
+
938
+ 1. Triggers the `call-end` event
939
+ 2. Processes the call on the backend (post-call analysis)
940
+ 3. Fetches complete call details
941
+ 4. Emits the `call-details-ready` event with full `CallDTO` data
942
+
943
+ This happens automatically—you don't need to manually call `getCallDetails()` after each call.
944
+
945
+ ### Event Lifecycle
946
+
947
+ ```typescript
948
+ const call = await client.startCall(simulationId);
949
+
950
+ // 1. Call begins
951
+ call.on('call-start', () => {
952
+ console.log('Call started');
953
+ });
954
+
955
+ // 2. Real-time transcript messages during the call
956
+ call.on('message', message => {
957
+ console.log('Real-time:', message.transcript);
958
+ });
959
+
960
+ // 3. Call ends
961
+ call.on('call-end', () => {
962
+ console.log('Call ended - processing feedback...');
963
+ });
964
+
965
+ // 4. Post-call details are automatically fetched and ready
966
+ call.on('call-details-ready', callDetails => {
967
+ console.log('Post-call feedback is ready!');
968
+
969
+ // Access all call details
970
+ console.log('Summary:', callDetails.summary);
971
+ console.log('Full Transcript:', callDetails.transcript);
972
+ console.log('Call Duration:', callDetails.callDurationMs, 'ms');
973
+ console.log('Success:', callDetails.successEvaluation);
974
+ console.log('Score:', callDetails.score);
975
+
976
+ // Display evaluation metrics
977
+ console.log(`${callDetails.metric1}: ${callDetails.metric1Value}`);
978
+ console.log(`${callDetails.metric2}: ${callDetails.metric2Value}`);
979
+ console.log(`${callDetails.metric3}: ${callDetails.metric3Value}`);
980
+
981
+ // Show strengths and areas for improvement
982
+ callDetails.strengths?.forEach(strength => {
983
+ console.log('✓ Strength:', strength);
984
+ });
985
+
986
+ callDetails.weaknesses?.forEach(weakness => {
987
+ console.log('⚠ Area to improve:', weakness);
988
+ });
989
+
990
+ // Access recording
991
+ if (callDetails.recordingUrl) {
992
+ console.log('Recording:', callDetails.recordingUrl);
993
+ }
994
+ });
995
+ ```
996
+
997
+ ### Practical Use Cases
998
+
999
+ #### 1. Display Post-Call Feedback in UI
1000
+
1001
+ ```typescript
1002
+ // Angular/React example
1003
+ async startCall() {
1004
+ const call = await this.client.startCall(this.simulationId);
1005
+
1006
+ // Automatically update UI when feedback is ready
1007
+ call.on('call-details-ready', callDetails => {
1008
+ this.callSummary = callDetails.summary;
1009
+ this.callScore = callDetails.score;
1010
+ this.strengths = callDetails.strengths;
1011
+ this.weaknesses = callDetails.weaknesses;
1012
+ this.showFeedbackModal = true; // Display feedback to user
1013
+ });
1014
+ }
1015
+ ```
1016
+
1017
+ #### 2. Track Performance Analytics
1018
+
1019
+ ```typescript
1020
+ call.on('call-details-ready', callDetails => {
1021
+ // Send analytics to tracking service
1022
+ analytics.track('call_completed', {
1023
+ callId: callDetails._id,
1024
+ duration: callDetails.callDurationMs,
1025
+ score: callDetails.score,
1026
+ success: callDetails.successEvaluation,
1027
+ metrics: {
1028
+ [callDetails.metric1]: callDetails.metric1Value,
1029
+ [callDetails.metric2]: callDetails.metric2Value,
1030
+ [callDetails.metric3]: callDetails.metric3Value,
1031
+ },
1032
+ });
1033
+ });
1034
+ ```
1035
+
1036
+ #### 3. Store Call History
1037
+
1038
+ ```typescript
1039
+ call.on('call-details-ready', async callDetails => {
1040
+ // Save to local storage or database
1041
+ const callHistory = JSON.parse(localStorage.getItem('call_history') || '[]');
1042
+ callHistory.push({
1043
+ id: callDetails._id,
1044
+ date: callDetails.createdAt,
1045
+ summary: callDetails.summary,
1046
+ score: callDetails.score,
1047
+ });
1048
+ localStorage.setItem('call_history', JSON.stringify(callHistory));
1049
+ });
1050
+ ```
1051
+
1052
+ #### 4. Generate Learning Insights
1053
+
1054
+ ```typescript
1055
+ call.on('call-details-ready', callDetails => {
1056
+ // Aggregate insights across multiple calls
1057
+ if (callDetails.successEvaluation) {
1058
+ this.successfulCalls++;
1059
+ this.successStrategies.push(...(callDetails.strengths || []));
1060
+ } else {
1061
+ this.areasToImprove.push(...(callDetails.weaknesses || []));
1062
+ }
1063
+
1064
+ this.updateLearningDashboard();
1065
+ });
1066
+ ```
1067
+
1068
+ ### Manual Fetching (Alternative Approach)
1069
+
1070
+ While the SDK automatically fetches call details after each call, you can also manually retrieve them later using the call ID:
1071
+
1072
+ ```typescript
1073
+ // Get call details at any time
1074
+ const callDetails = await client.getCallDetails(callId);
1075
+ ```
1076
+
1077
+ This is useful when:
1078
+
1079
+ - Viewing historical calls
1080
+ - Refreshing data after updates
1081
+ - Building a call history view
1082
+
1083
+ ### Error Handling
1084
+
1085
+ If fetching call details fails after a call ends, the error is logged but does not prevent the `call-end` event from completing. The call will still end gracefully.
1086
+
1087
+ ```typescript
1088
+ call.on('call-end', () => {
1089
+ console.log('Call ended successfully');
1090
+ // This will always fire, even if call-details-ready fails
1091
+ });
1092
+
1093
+ call.on('call-details-ready', callDetails => {
1094
+ // This may not fire if post-call processing fails
1095
+ // In that case, you can manually fetch later using getCallDetails()
1096
+ });
1097
+ ```
1098
+
1099
+ ### Best Practices
1100
+
1101
+ 1. **Always subscribe to `call-details-ready`** to capture post-call feedback automatically
1102
+ 2. **Show loading state** between `call-end` and `call-details-ready` events
1103
+ 3. **Handle missing data gracefully** - some fields may be `null` or `undefined` depending on call status
1104
+ 4. **Store call IDs** for later retrieval using `getCallDetails()` if needed
1105
+ 5. **Use the event data** to provide immediate feedback to users rather than making additional API calls
1106
+
1107
+ ### Timing Considerations
1108
+
1109
+ - `call-end`: Fires immediately when the call stops
1110
+ - `call-details-ready`: Fires after backend processing completes (typically 2-5 seconds after call ends)
1111
+
1112
+ Plan your UX accordingly—show a loading/processing state between these two events.
1113
+
1114
+ ## Data Types
1115
+
1116
+ ### CallDTO
1117
+
1118
+ Complete call details returned by `call-details-ready` event and `getCallDetails()` method:
1119
+
1120
+ ```typescript
1121
+ interface CallDTO {
1122
+ _id: string; // MongoDB ID of the call
1123
+ callId: string; // Vapi call ID
1124
+ assistantId: string; // ID of the AI assistant used
1125
+ summary?: string; // AI-generated summary of the conversation
1126
+ transcript?: string; // Full transcript of the call
1127
+ recordingUrl?: string; // URL to the call recording
1128
+ rating?: number; // User-provided rating (0-5)
1129
+ successEvaluation?: boolean; // Whether the call was successful
1130
+ score?: number; // Overall score for the call
1131
+ strengths?: string[]; // Array of identified strengths
1132
+ weaknesses?: string[]; // Array of areas for improvement
1133
+ metric1?: string; // Name of evaluation metric 1
1134
+ metric1Value?: number; // Value for metric 1
1135
+ metric2?: string; // Name of evaluation metric 2
1136
+ metric2Value?: number; // Value for metric 2
1137
+ metric3?: string; // Name of evaluation metric 3
1138
+ metric3Value?: number; // Value for metric 3
1139
+ createdAt: Date; // When the call was created
1140
+ endedAt?: Date; // When the call ended
1141
+ callDurationMs?: number; // Duration in milliseconds
1142
+ // Additional fields may be present depending on call status
1143
+ }
1144
+ ```
1145
+
1146
+ ### CreateSimulationDto
1147
+
1148
+ Configuration for creating a simulation:
1149
+
1150
+ ```typescript
1151
+ interface CreateSimulationDto {
1152
+ persona: CharacterCreateDto;
1153
+ product: ProductConfig;
1154
+ presentation?: string;
1155
+ scenario: string;
1156
+ objectives?: string;
1157
+ anticipatedObjections?: string;
1158
+ trainingConfiguration: TrainingConfigurationDto;
1159
+ imageId: string;
1160
+ avatarLanguage: AvatarLanguage;
1161
+ }
1162
+ ```
1163
+
1164
+ ### CharacterCreateDto
1165
+
1166
+ AI persona configuration:
1167
+
1168
+ ```typescript
1169
+ interface CharacterCreateDto {
1170
+ name: string;
1171
+ professionalProfile: ProfessionalProfileDto;
1172
+ segment: SegmentType;
1173
+ personalityAndBehaviour: PersonalityAndBehaviourDto;
1174
+ context: ContextDto;
1175
+ assistantGender?: AssistantVoiceGender;
1176
+ }
1177
+ ```
1178
+
1179
+ ### SimulationDetailsDto
1180
+
1181
+ Detailed simulation information returned by `getSimulationDetails()`:
1182
+
1183
+ ```typescript
1184
+ interface SimulationDetailsDto {
1185
+ simulationId: string;
1186
+ phase: CreationPhase;
1187
+ assistantId: string;
1188
+ configuration?: CreateSimulationDto;
1189
+ simulationBriefing?: SimulationBriefingDto;
1190
+ createdAt: Date;
1191
+ updatedAt: Date;
1192
+ }
1193
+ ```
1194
+
1195
+ ### SimulationBriefingDto
1196
+
1197
+ Structured briefing data generated by the multi-agent system. This data provides context about the simulation's objectives, persona, and scenario:
1198
+
1199
+ ```typescript
1200
+ interface SimulationBriefingDto {
1201
+ objectiveTag: string;
1202
+ personaDescription: PersonaDescriptionDto;
1203
+ scenarioContext: ScenarioContextDto;
1204
+ trainingObjective: TrainingObjectiveDto;
1205
+ }
1206
+
1207
+ interface PersonaDescriptionDto {
1208
+ summary: string;
1209
+ details: string[];
1210
+ }
1211
+
1212
+ interface ScenarioContextDto {
1213
+ summary: string;
1214
+ steps: string[];
1215
+ }
1216
+
1217
+ interface TrainingObjectiveDto {
1218
+ summary: string;
1219
+ keyObjectives: string[];
1220
+ }
1221
+ ```
1222
+
1223
+ **Example:**
1224
+
1225
+ ```typescript
1226
+ const details = await client.getSimulationDetails(simulationId);
1227
+
1228
+ if (details.simulationBriefing) {
1229
+ console.log('Objective:', details.simulationBriefing.objectiveTag);
1230
+ console.log('Persona:', details.simulationBriefing.personaDescription.summary);
1231
+ console.log('Scenario:', details.simulationBriefing.scenarioContext.summary);
1232
+ console.log('Training Goals:', details.simulationBriefing.trainingObjective.keyObjectives);
1233
+ }
1234
+ ```
1235
+
1236
+ **Note:** The `simulationBriefing` field is optional and will only be present for simulations created after the briefing feature was implemented.
1237
+
1238
+ ### CreationPhase
1239
+
1240
+ Enum representing the simulation creation phases:
1241
+
1242
+ ```typescript
1243
+ enum CreationPhase {
1244
+ STARTING = 'STARTING',
1245
+ BUILD_HEADER = 'BUILD_HEADER',
1246
+ SEGMENT_SELECTED = 'SEGMENT_SELECTED',
1247
+ BUILD_CONTEXT = 'BUILD_CONTEXT',
1248
+ BUILD_PSYCHOGRAPHICS = 'BUILD_PSYCHOGRAPHICS',
1249
+ BUILD_OBJECTIVES = 'BUILD_OBJECTIVES',
1250
+ PERSONA_ASSEMBLED = 'PERSONA_ASSEMBLED',
1251
+ CORE = 'CORE',
1252
+ BOUNDARIES = 'BOUNDARIES',
1253
+ SPEECH_AND_THOUGHT = 'SPEECH_AND_THOUGHT',
1254
+ CONVERSATION_EVOLUTION = 'CONVERSATION_EVOLUTION',
1255
+ MEMORY = 'MEMORY',
1256
+ OBJECTION_HANDLING = 'OBJECTION_HANDLING',
1257
+ PERFORMANCE_EVALUATION = 'PERFORMANCE_EVALUATION',
1258
+ BEHAVIORAL_FRAMEWORKS = 'BEHAVIORAL_FRAMEWORKS',
1259
+ FINISHED = 'FINISHED',
1260
+ ERROR = 'ERROR',
1261
+ }
1262
+ ```
1263
+
1264
+ ### RecommendationsResponseDto
1265
+
1266
+ Response object from `getRecommendations()`:
1267
+
1268
+ ```typescript
1269
+ interface RecommendationsResponseDto {
1270
+ recommendations: RecommendationItemDto[];
1271
+ strengths: PerformancePatternDto[];
1272
+ improvementAreas: ImprovementAreaDto[];
1273
+ summary: string;
1274
+ callsAnalyzed: number;
1275
+ generatedAt: Date;
1276
+ }
1277
+
1278
+ interface RecommendationItemDto {
1279
+ title: string;
1280
+ description: string;
1281
+ priority: 'high' | 'medium' | 'low';
1282
+ }
1283
+
1284
+ interface PerformancePatternDto {
1285
+ strength: string;
1286
+ frequency: number;
1287
+ }
1288
+
1289
+ interface ImprovementAreaDto {
1290
+ area: string;
1291
+ frequency: number;
1292
+ }
1293
+ ```
1294
+
1295
+ ### PdfSlidesDto
1296
+
1297
+ Simplified data structure for PDF slide records (used in lists):
1298
+
1299
+ ```typescript
1300
+ interface PdfSlidesDto {
1301
+ _id: string;
1302
+ status: PdfSlidesStatus;
1303
+ originalFilename: string;
1304
+ totalSlides?: number;
1305
+ lessonOverview?: string;
1306
+ createdAt: Date;
1307
+ updatedAt: Date;
1308
+ }
1309
+ ```
1310
+
1311
+ ### PdfSlideDto
1312
+
1313
+ Detailed data structure for a single PDF slide analysis:
1314
+
1315
+ ```typescript
1316
+ interface PdfSlideDto {
1317
+ _id: string;
1318
+ status: PdfSlidesStatus;
1319
+ originalFilename: string;
1320
+ totalSlides?: number;
1321
+ lessonOverview?: string;
1322
+ slideAnalysis?: Array<{
1323
+ slideNumber: number;
1324
+ title: string;
1325
+ textExtraction: string;
1326
+ }>;
1327
+ createdAt: Date;
1328
+ updatedAt: Date;
1329
+ }
1330
+ ```
1331
+
1332
+ ### PdfSlidesStatus
1333
+
1334
+ Enum representing the PDF analysis workflow status:
1335
+
1336
+ ```typescript
1337
+ enum PdfSlidesStatus {
1338
+ STARTED = 'started',
1339
+ PROCESSING = 'processing',
1340
+ OVERVIEW = 'overview',
1341
+ COMPLETED = 'completed',
1342
+ FAILED = 'failed',
1343
+ }
1344
+ ```
1345
+
1346
+ ### PdfSlidesAnalysisQueryDto
1347
+
1348
+ Query parameters for retrieving slide analysis records:
1349
+
1350
+ ```typescript
1351
+ interface PdfSlidesAnalysisQueryDto {
1352
+ limit?: 5 | 10 | 25;
1353
+ page?: number;
1354
+ sort?: 'asc' | 'desc';
1355
+ sortBy?: 'createdAt' | 'originalFilename' | 'totalSlides';
1356
+ }
1357
+ ```
1358
+
1359
+ ### AssistantImageDto
1360
+
1361
+ Data structure for assistant images:
1362
+
1363
+ ```typescript
1364
+ interface AssistantImageDto {
1365
+ _id: string;
1366
+ imageUrl: string;
1367
+ }
1368
+ ```
1369
+
1370
+ ### SegmentType
1371
+
1372
+ Enum representing available doctor persona segments:
1373
+
1374
+ ```typescript
1375
+ enum SegmentType {
1376
+ Traditionalist = 'The Traditionalist',
1377
+ Innovator = 'The Innovator',
1378
+ PatientOrientedPhysician = 'The Patient-Oriented Physician',
1379
+ FinanciallyDrivenPrescriber = 'The Financially-Driven Prescriber',
1380
+ EvidencePurist = 'The Evidence-Purist',
1381
+ CostConsciousPrescriber = 'The Cost-Conscious Prescriber',
1382
+ }
1383
+ ```
1384
+
1385
+ ### AvatarLanguage
1386
+
1387
+ Enum representing available avatar languages:
1388
+
1389
+ ```typescript
1390
+ enum AvatarLanguage {
1391
+ English = 'en',
1392
+ German = 'de',
1393
+ Spanish = 'es',
1394
+ Italian = 'it',
1395
+ French = 'fr',
1396
+ }
1397
+ ```
1398
+
1399
+ ## Error Handling
1400
+
1401
+ The SDK throws errors for invalid configurations and API failures:
1402
+
1403
+ ```typescript
1404
+ try {
1405
+ const client = new PlatoApiClient({
1406
+ baseUrl: 'https://api.plato.com',
1407
+ token: 'your-token',
1408
+ user: 'your-user',
1409
+ });
1410
+
1411
+ const simulation = await client.createSimulation(simulationConfig);
1412
+ const call = await client.startCall(simulation.simulationId);
1413
+ } catch (error) {
1414
+ console.error('Error:', error.message);
1415
+ }
1416
+ ```
1417
+
1418
+ ## Support
1419
+
1420
+ For support and questions, please contact the development team.