@blackcode_sa/metaestetics-api 1.12.67 → 1.12.69

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.
@@ -0,0 +1,859 @@
1
+ # Analytics Service Proposal
2
+
3
+ ## Overview
4
+
5
+ This document proposes the design and implementation of an `analytics.service.ts` service for the Clinic Admin app. This service will provide comprehensive financial and analytical intelligence about doctors, procedures, appointments, patients, products, and clinic operations.
6
+
7
+ ## Data Sources & Connections
8
+
9
+ ### Primary Data Sources
10
+
11
+ 1. **Appointments Collection** (`appointments`)
12
+ - Status tracking (pending, confirmed, completed, canceled, no-show)
13
+ - Time tracking (booked time vs actual time)
14
+ - Cost and billing information
15
+ - Procedure and product usage
16
+ - Patient and practitioner associations
17
+
18
+ 2. **Procedures Collection** (`procedures`)
19
+ - Procedure metadata (category, subcategory, technology, family)
20
+ - Pricing information
21
+ - Duration information
22
+ - Product associations
23
+
24
+ 3. **Practitioners Collection** (`practitioners`)
25
+ - Practitioner information
26
+ - Clinic associations
27
+ - Procedure associations
28
+
29
+ 4. **Patients Collection** (`patients`)
30
+ - Patient profiles
31
+ - Appointment history
32
+ - Clinic and practitioner associations
33
+
34
+ 5. **Clinics Collection** (`clinics`)
35
+ - Clinic information
36
+ - Branch information
37
+ - Practitioner associations
38
+
39
+ 6. **Products Collection** (`products`)
40
+ - Product information
41
+ - Brand associations
42
+ - Pricing information
43
+
44
+ ### Key Data Relationships
45
+
46
+ ```
47
+ Clinic → Practitioners → Procedures → Appointments → Patients
48
+
49
+ Products (via metadata.zonesData)
50
+ ```
51
+
52
+ ### Critical Appointment Fields for Analytics
53
+
54
+ From `Appointment` type:
55
+ - `status`: AppointmentStatus (for filtering completed/canceled/no-show)
56
+ - `appointmentStartTime`, `appointmentEndTime`: Booked time slots
57
+ - `procedureActualStartTime`, `actualDurationMinutes`: Actual time spent
58
+ - `cost`, `currency`: Base appointment cost
59
+ - `paymentStatus`: Payment tracking
60
+ - `metadata.finalbilling`: Final billing calculations
61
+ - `metadata.zonesData`: Product usage per zone
62
+ - `metadata.extendedProcedures`: Additional procedures performed
63
+ - `metadata.appointmentProducts`: Products used
64
+ - `clinicBranchId`, `practitionerId`, `patientId`: Entity associations
65
+ - `cancellationTime`, `canceledBy`: Cancellation tracking
66
+ - `procedureId`: Primary procedure reference
67
+
68
+ ## Proposed Analytics Categories
69
+
70
+ ### 1. Doctor/Practitioner Analytics
71
+
72
+ #### Metrics to Calculate:
73
+ - **Total appointments** by practitioner
74
+ - **Completed appointments** count
75
+ - **Cancellation rate** (by practitioner)
76
+ - **No-show rate** (by practitioner)
77
+ - **Average booked time** per appointment
78
+ - **Average actual time** per appointment
79
+ - **Time efficiency** (actual vs booked time ratio)
80
+ - **Total revenue** generated
81
+ - **Average revenue** per appointment
82
+ - **Most performed procedures**
83
+ - **Patient retention rate**
84
+
85
+ #### Data Sources:
86
+ - Appointments filtered by `practitionerId`
87
+ - Compare `appointmentEndTime - appointmentStartTime` vs `actualDurationMinutes`
88
+ - Count appointments by `status`
89
+ - Sum `metadata.finalbilling.finalPrice` or `cost`
90
+
91
+ ### 2. Procedure Analytics
92
+
93
+ #### Metrics to Calculate:
94
+ - **Total appointments** per procedure
95
+ - **Average cost** per procedure
96
+ - **Total revenue** per procedure
97
+ - **Average duration** (booked vs actual)
98
+ - **Cancellation rate** by procedure
99
+ - **No-show rate** by procedure
100
+ - **Most popular procedures** (by count)
101
+ - **Most profitable procedures** (by revenue)
102
+ - **Procedure performance** by category/subcategory/technology
103
+ - **Product usage** per procedure
104
+
105
+ #### Data Sources:
106
+ - Appointments filtered by `procedureId`
107
+ - `procedureInfo` and `procedureExtendedInfo` for procedure details
108
+ - `metadata.extendedProcedures` for additional procedures
109
+ - `metadata.zonesData` for product usage
110
+
111
+ ### 3. Appointment Time Analytics
112
+
113
+ #### Metrics to Calculate:
114
+ - **Booked time** vs **Actual time** comparison
115
+ - **Time efficiency** percentage
116
+ - **Average overrun** time
117
+ - **Average underutilization** time
118
+ - **Peak hours** analysis
119
+ - **Time distribution** by day of week
120
+ - **Appointment duration trends**
121
+
122
+ #### Calculation Formula:
123
+ ```typescript
124
+ bookedDuration = appointmentEndTime - appointmentStartTime (minutes)
125
+ actualDuration = actualDurationMinutes || bookedDuration
126
+ efficiency = (actualDuration / bookedDuration) * 100
127
+ overrun = actualDuration > bookedDuration ? actualDuration - bookedDuration : 0
128
+ underutilization = bookedDuration > actualDuration ? bookedDuration - actualDuration : 0
129
+ ```
130
+
131
+ ### 4. Cancellation & No-Show Analytics
132
+
133
+ #### Metrics to Calculate:
134
+ - **Cancellation rate** (overall, by clinic, by practitioner, by patient)
135
+ - **No-show rate** (overall, by clinic, by practitioner, by patient)
136
+ - **Cancellation reasons** breakdown
137
+ - **Cancellation patterns** (by time of day, day of week)
138
+ - **Patient cancellation history**
139
+ - **Clinic cancellation trends**
140
+ - **Average cancellation lead time** (time between booking and cancellation)
141
+
142
+ #### Status Mapping:
143
+ ```typescript
144
+ Cancellation Statuses:
145
+ - AppointmentStatus.CANCELED_PATIENT
146
+ - AppointmentStatus.CANCELED_CLINIC
147
+ - AppointmentStatus.CANCELED_PATIENT_RESCHEDULED
148
+ - AppointmentStatus.NO_SHOW
149
+ ```
150
+
151
+ #### Calculation Formula:
152
+ ```typescript
153
+ cancellationRate = (canceledCount / totalAppointments) * 100
154
+ noShowRate = (noShowCount / totalAppointments) * 100
155
+ ```
156
+
157
+ ### 5. Financial Analytics
158
+
159
+ #### Metrics to Calculate:
160
+ - **Total revenue** (overall, by clinic, by practitioner, by procedure, by patient)
161
+ - **Average cost** per appointment
162
+ - **Average cost** per patient
163
+ - **Total cost** per patient
164
+ - **Revenue by procedure**
165
+ - **Revenue by product**
166
+ - **Revenue trends** (daily, weekly, monthly)
167
+ - **Payment status** breakdown
168
+ - **Unpaid appointments** value
169
+ - **Refunded appointments** value
170
+ - **Product cost** analysis
171
+ - **Tax calculations** (from `metadata.finalbilling.taxPrice`)
172
+
173
+ #### Cost Calculation Sources:
174
+ 1. **Primary**: `metadata.finalbilling.finalPrice` (if available)
175
+ 2. **Fallback**: Sum of `metadata.zonesData` subtotals
176
+ 3. **Base**: `appointment.cost` (if no zone data)
177
+
178
+ #### Formula:
179
+ ```typescript
180
+ totalRevenue = sum(metadata.finalbilling?.finalPrice || calculateFromZones(appointment) || appointment.cost)
181
+ averageCostPerAppointment = totalRevenue / completedAppointmentsCount
182
+ averageCostPerPatient = totalRevenue / uniquePatientsCount
183
+ ```
184
+
185
+ ### 6. Product Usage Analytics
186
+
187
+ #### Metrics to Calculate:
188
+ - **Products used** per appointment
189
+ - **Total quantity** per product
190
+ - **Product revenue** contribution
191
+ - **Most used products**
192
+ - **Product usage** by procedure
193
+ - **Product usage** by zone
194
+ - **Average product cost** per appointment
195
+ - **Product cost trends**
196
+
197
+ #### Data Sources:
198
+ - `metadata.zonesData`: Zone-specific product usage with quantities and prices
199
+ - `metadata.appointmentProducts`: Product metadata
200
+ - `metadata.finalbilling`: Final billing calculations
201
+
202
+ #### Extraction Logic:
203
+ ```typescript
204
+ // From zonesData
205
+ products = []
206
+ for each zone in zonesData:
207
+ for each item in zone.items:
208
+ if item.type === 'item' and item.productId:
209
+ products.push({
210
+ productId: item.productId,
211
+ productName: item.productName,
212
+ quantity: item.quantity,
213
+ price: item.priceOverrideAmount || item.price,
214
+ subtotal: item.subtotal,
215
+ currency: item.currency
216
+ })
217
+ ```
218
+
219
+ ### 7. Patient Analytics
220
+
221
+ #### Metrics to Calculate:
222
+ - **Total patients** (unique count)
223
+ - **New patients** vs **Returning patients**
224
+ - **Average appointments** per patient
225
+ - **Patient lifetime value** (total revenue per patient)
226
+ - **Patient retention rate**
227
+ - **Average cancellation rate** per patient
228
+ - **Average no-show rate** per patient
229
+ - **Most valuable patients** (by revenue)
230
+ - **Patient appointment frequency**
231
+
232
+ #### Calculation:
233
+ ```typescript
234
+ uniquePatients = distinct(appointments.map(a => a.patientId))
235
+ newPatients = patients with firstAppointmentDate in dateRange
236
+ returningPatients = patients with multiple appointments
237
+ averageAppointmentsPerPatient = totalAppointments / uniquePatients
238
+ patientLifetimeValue = sum(revenue for patient) / uniquePatients
239
+ ```
240
+
241
+ ### 8. Clinic Analytics
242
+
243
+ #### Metrics to Calculate:
244
+ - **Total appointments** per clinic
245
+ - **Total revenue** per clinic
246
+ - **Average revenue** per appointment (by clinic)
247
+ - **Cancellation rate** by clinic
248
+ - **No-show rate** by clinic
249
+ - **Practitioner performance** by clinic
250
+ - **Procedure popularity** by clinic
251
+ - **Clinic efficiency** metrics
252
+
253
+ ## Proposed Service Methods
254
+
255
+ ### Service Structure
256
+
257
+ ```typescript
258
+ export class AnalyticsService extends BaseService {
259
+ // Constructor and initialization
260
+
261
+ // ==========================================
262
+ // Practitioner Analytics
263
+ // ==========================================
264
+
265
+ /**
266
+ * Get practitioner performance metrics
267
+ * @param practitionerId - ID of the practitioner
268
+ * @param dateRange - Optional date range filter
269
+ * @returns Practitioner analytics object
270
+ */
271
+ async getPractitionerAnalytics(
272
+ practitionerId: string,
273
+ dateRange?: { start: Date; end: Date }
274
+ ): Promise<PractitionerAnalytics>
275
+
276
+ /**
277
+ * Get practitioner cancellation and no-show rates
278
+ * @param practitionerId - ID of the practitioner
279
+ * @param dateRange - Optional date range filter
280
+ * @returns Cancellation and no-show metrics
281
+ */
282
+ async getPractitionerCancellationMetrics(
283
+ practitionerId: string,
284
+ dateRange?: { start: Date; end: Date }
285
+ ): Promise<CancellationMetrics>
286
+
287
+ /**
288
+ * Get practitioner time efficiency metrics
289
+ * @param practitionerId - ID of the practitioner
290
+ * @param dateRange - Optional date range filter
291
+ * @returns Time efficiency metrics
292
+ */
293
+ async getPractitionerTimeMetrics(
294
+ practitionerId: string,
295
+ dateRange?: { start: Date; end: Date }
296
+ ): Promise<TimeEfficiencyMetrics>
297
+
298
+ /**
299
+ * Get practitioner revenue metrics
300
+ * @param practitionerId - ID of the practitioner
301
+ * @param dateRange - Optional date range filter
302
+ * @returns Revenue metrics
303
+ */
304
+ async getPractitionerRevenueMetrics(
305
+ practitionerId: string,
306
+ dateRange?: { start: Date; end: Date }
307
+ ): Promise<RevenueMetrics>
308
+
309
+ // ==========================================
310
+ // Procedure Analytics
311
+ // ==========================================
312
+
313
+ /**
314
+ * Get procedure performance metrics
315
+ * @param procedureId - ID of the procedure (optional, if not provided returns all)
316
+ * @param dateRange - Optional date range filter
317
+ * @returns Procedure analytics object
318
+ */
319
+ async getProcedureAnalytics(
320
+ procedureId?: string,
321
+ dateRange?: { start: Date; end: Date }
322
+ ): Promise<ProcedureAnalytics | ProcedureAnalytics[]>
323
+
324
+ /**
325
+ * Get procedure popularity metrics
326
+ * @param dateRange - Optional date range filter
327
+ * @param limit - Number of top procedures to return
328
+ * @returns Array of procedure popularity metrics
329
+ */
330
+ async getProcedurePopularity(
331
+ dateRange?: { start: Date; end: Date },
332
+ limit?: number
333
+ ): Promise<ProcedurePopularity[]>
334
+
335
+ /**
336
+ * Get procedure profitability metrics
337
+ * @param dateRange - Optional date range filter
338
+ * @param limit - Number of top procedures to return
339
+ * @returns Array of procedure profitability metrics
340
+ */
341
+ async getProcedureProfitability(
342
+ dateRange?: { start: Date; end: Date },
343
+ limit?: number
344
+ ): Promise<ProcedureProfitability[]>
345
+
346
+ // ==========================================
347
+ // Appointment Time Analytics
348
+ // ==========================================
349
+
350
+ /**
351
+ * Get time efficiency metrics for appointments
352
+ * @param filters - Optional filters (clinicId, practitionerId, procedureId)
353
+ * @param dateRange - Optional date range filter
354
+ * @returns Time efficiency metrics
355
+ */
356
+ async getTimeEfficiencyMetrics(
357
+ filters?: {
358
+ clinicBranchId?: string;
359
+ practitionerId?: string;
360
+ procedureId?: string;
361
+ },
362
+ dateRange?: { start: Date; end: Date }
363
+ ): Promise<TimeEfficiencyMetrics>
364
+
365
+ /**
366
+ * Get appointment duration trends
367
+ * @param filters - Optional filters
368
+ * @param dateRange - Date range for trend analysis
369
+ * @param groupBy - Grouping period ('day' | 'week' | 'month')
370
+ * @returns Duration trends
371
+ */
372
+ async getDurationTrends(
373
+ filters?: {
374
+ clinicBranchId?: string;
375
+ practitionerId?: string;
376
+ procedureId?: string;
377
+ },
378
+ dateRange?: { start: Date; end: Date },
379
+ groupBy?: 'day' | 'week' | 'month'
380
+ ): Promise<DurationTrend[]>
381
+
382
+ // ==========================================
383
+ // Cancellation & No-Show Analytics
384
+ // ==========================================
385
+
386
+ /**
387
+ * Get cancellation metrics
388
+ * @param groupBy - Group by 'clinic' | 'practitioner' | 'patient' | 'procedure'
389
+ * @param dateRange - Optional date range filter
390
+ * @returns Cancellation metrics grouped by specified entity
391
+ */
392
+ async getCancellationMetrics(
393
+ groupBy: 'clinic' | 'practitioner' | 'patient' | 'procedure',
394
+ dateRange?: { start: Date; end: Date }
395
+ ): Promise<CancellationMetrics | CancellationMetrics[]>
396
+
397
+ /**
398
+ * Get no-show metrics
399
+ * @param groupBy - Group by 'clinic' | 'practitioner' | 'patient' | 'procedure'
400
+ * @param dateRange - Optional date range filter
401
+ * @returns No-show metrics grouped by specified entity
402
+ */
403
+ async getNoShowMetrics(
404
+ groupBy: 'clinic' | 'practitioner' | 'patient' | 'procedure',
405
+ dateRange?: { start: Date; end: Date }
406
+ ): Promise<NoShowMetrics | NoShowMetrics[]>
407
+
408
+ /**
409
+ * Get cancellation reasons breakdown
410
+ * @param dateRange - Optional date range filter
411
+ * @returns Cancellation reasons statistics
412
+ */
413
+ async getCancellationReasons(
414
+ dateRange?: { start: Date; end: Date }
415
+ ): Promise<CancellationReasonStats[]>
416
+
417
+ // ==========================================
418
+ // Financial Analytics
419
+ // ==========================================
420
+
421
+ /**
422
+ * Get revenue metrics
423
+ * @param filters - Optional filters
424
+ * @param dateRange - Optional date range filter
425
+ * @returns Revenue metrics
426
+ */
427
+ async getRevenueMetrics(
428
+ filters?: {
429
+ clinicBranchId?: string;
430
+ practitionerId?: string;
431
+ procedureId?: string;
432
+ patientId?: string;
433
+ },
434
+ dateRange?: { start: Date; end: Date }
435
+ ): Promise<RevenueMetrics>
436
+
437
+ /**
438
+ * Get revenue trends over time
439
+ * @param filters - Optional filters
440
+ * @param dateRange - Date range for trend analysis
441
+ * @param groupBy - Grouping period ('day' | 'week' | 'month')
442
+ * @returns Revenue trends
443
+ */
444
+ async getRevenueTrends(
445
+ filters?: {
446
+ clinicBranchId?: string;
447
+ practitionerId?: string;
448
+ procedureId?: string;
449
+ },
450
+ dateRange?: { start: Date; end: Date },
451
+ groupBy?: 'day' | 'week' | 'month'
452
+ ): Promise<RevenueTrend[]>
453
+
454
+ /**
455
+ * Get cost per patient metrics
456
+ * @param patientId - Optional patient ID (if not provided, returns average)
457
+ * @param dateRange - Optional date range filter
458
+ * @returns Cost per patient metrics
459
+ */
460
+ async getCostPerPatient(
461
+ patientId?: string,
462
+ dateRange?: { start: Date; end: Date }
463
+ ): Promise<CostPerPatientMetrics>
464
+
465
+ /**
466
+ * Get payment status breakdown
467
+ * @param filters - Optional filters
468
+ * @param dateRange - Optional date range filter
469
+ * @returns Payment status statistics
470
+ */
471
+ async getPaymentStatusBreakdown(
472
+ filters?: {
473
+ clinicBranchId?: string;
474
+ practitionerId?: string;
475
+ },
476
+ dateRange?: { start: Date; end: Date }
477
+ ): Promise<PaymentStatusBreakdown>
478
+
479
+ // ==========================================
480
+ // Product Usage Analytics
481
+ // ==========================================
482
+
483
+ /**
484
+ * Get product usage metrics
485
+ * @param productId - Optional product ID (if not provided, returns all products)
486
+ * @param dateRange - Optional date range filter
487
+ * @returns Product usage metrics
488
+ */
489
+ async getProductUsageMetrics(
490
+ productId?: string,
491
+ dateRange?: { start: Date; end: Date }
492
+ ): Promise<ProductUsageMetrics | ProductUsageMetrics[]>
493
+
494
+ /**
495
+ * Get product revenue contribution
496
+ * @param dateRange - Optional date range filter
497
+ * @param limit - Number of top products to return
498
+ * @returns Product revenue metrics
499
+ */
500
+ async getProductRevenueMetrics(
501
+ dateRange?: { start: Date; end: Date },
502
+ limit?: number
503
+ ): Promise<ProductRevenueMetrics[]>
504
+
505
+ /**
506
+ * Get product usage by procedure
507
+ * @param procedureId - Optional procedure ID
508
+ * @param dateRange - Optional date range filter
509
+ * @returns Product usage by procedure
510
+ */
511
+ async getProductUsageByProcedure(
512
+ procedureId?: string,
513
+ dateRange?: { start: Date; end: Date }
514
+ ): Promise<ProductUsageByProcedure[]>
515
+
516
+ // ==========================================
517
+ // Patient Analytics
518
+ // ==========================================
519
+
520
+ /**
521
+ * Get patient analytics
522
+ * @param patientId - Optional patient ID (if not provided, returns aggregate)
523
+ * @param dateRange - Optional date range filter
524
+ * @returns Patient analytics
525
+ */
526
+ async getPatientAnalytics(
527
+ patientId?: string,
528
+ dateRange?: { start: Date; end: Date }
529
+ ): Promise<PatientAnalytics | PatientAnalytics[]>
530
+
531
+ /**
532
+ * Get patient lifetime value
533
+ * @param patientId - Optional patient ID
534
+ * @param dateRange - Optional date range filter
535
+ * @returns Patient lifetime value metrics
536
+ */
537
+ async getPatientLifetimeValue(
538
+ patientId?: string,
539
+ dateRange?: { start: Date; end: Date }
540
+ ): Promise<PatientLifetimeValueMetrics>
541
+
542
+ /**
543
+ * Get patient retention metrics
544
+ * @param dateRange - Optional date range filter
545
+ * @returns Patient retention metrics
546
+ */
547
+ async getPatientRetentionMetrics(
548
+ dateRange?: { start: Date; end: Date }
549
+ ): Promise<PatientRetentionMetrics>
550
+
551
+ // ==========================================
552
+ // Clinic Analytics
553
+ // ==========================================
554
+
555
+ /**
556
+ * Get clinic analytics
557
+ * @param clinicBranchId - Optional clinic branch ID (if not provided, returns all)
558
+ * @param dateRange - Optional date range filter
559
+ * @returns Clinic analytics
560
+ */
561
+ async getClinicAnalytics(
562
+ clinicBranchId?: string,
563
+ dateRange?: { start: Date; end: Date }
564
+ ): Promise<ClinicAnalytics | ClinicAnalytics[]>
565
+
566
+ /**
567
+ * Get clinic performance comparison
568
+ * @param clinicBranchIds - Array of clinic branch IDs to compare
569
+ * @param dateRange - Optional date range filter
570
+ * @returns Clinic comparison metrics
571
+ */
572
+ async getClinicComparison(
573
+ clinicBranchIds: string[],
574
+ dateRange?: { start: Date; end: Date }
575
+ ): Promise<ClinicComparisonMetrics[]>
576
+
577
+ // ==========================================
578
+ // Comprehensive Dashboard Data
579
+ // ==========================================
580
+
581
+ /**
582
+ * Get comprehensive dashboard data
583
+ * @param filters - Optional filters
584
+ * @param dateRange - Optional date range filter
585
+ * @returns Complete dashboard analytics
586
+ */
587
+ async getDashboardData(
588
+ filters?: {
589
+ clinicBranchId?: string;
590
+ practitionerId?: string;
591
+ },
592
+ dateRange?: { start: Date; end: Date }
593
+ ): Promise<DashboardAnalytics>
594
+ }
595
+ ```
596
+
597
+ ## Type Definitions
598
+
599
+ ### Core Types
600
+
601
+ ```typescript
602
+ // Base metrics interface
603
+ interface BaseMetrics {
604
+ total: number;
605
+ dateRange?: { start: Date; end: Date };
606
+ }
607
+
608
+ // Practitioner Analytics
609
+ interface PractitionerAnalytics extends BaseMetrics {
610
+ practitionerId: string;
611
+ practitionerName: string;
612
+ totalAppointments: number;
613
+ completedAppointments: number;
614
+ canceledAppointments: number;
615
+ noShowAppointments: number;
616
+ cancellationRate: number; // percentage
617
+ noShowRate: number; // percentage
618
+ averageBookedTime: number; // minutes
619
+ averageActualTime: number; // minutes
620
+ timeEfficiency: number; // percentage
621
+ totalRevenue: number;
622
+ averageRevenuePerAppointment: number;
623
+ topProcedures: Array<{
624
+ procedureId: string;
625
+ procedureName: string;
626
+ count: number;
627
+ }>;
628
+ patientRetentionRate: number; // percentage
629
+ }
630
+
631
+ // Procedure Analytics
632
+ interface ProcedureAnalytics extends BaseMetrics {
633
+ procedureId: string;
634
+ procedureName: string;
635
+ totalAppointments: number;
636
+ completedAppointments: number;
637
+ canceledAppointments: number;
638
+ noShowAppointments: number;
639
+ cancellationRate: number;
640
+ noShowRate: number;
641
+ averageCost: number;
642
+ totalRevenue: number;
643
+ averageBookedDuration: number; // minutes
644
+ averageActualDuration: number; // minutes
645
+ categoryName: string;
646
+ subcategoryName: string;
647
+ technologyName: string;
648
+ productUsage: Array<{
649
+ productId: string;
650
+ productName: string;
651
+ totalQuantity: number;
652
+ totalRevenue: number;
653
+ }>;
654
+ }
655
+
656
+ // Time Efficiency Metrics
657
+ interface TimeEfficiencyMetrics {
658
+ totalAppointments: number;
659
+ averageBookedDuration: number; // minutes
660
+ averageActualDuration: number; // minutes
661
+ averageEfficiency: number; // percentage
662
+ totalOverrun: number; // minutes
663
+ totalUnderutilization: number; // minutes
664
+ averageOverrun: number; // minutes
665
+ averageUnderutilization: number; // minutes
666
+ efficiencyDistribution: Array<{
667
+ range: string; // e.g., "0-50%", "50-75%", "75-100%", "100%+"
668
+ count: number;
669
+ }>;
670
+ }
671
+
672
+ // Cancellation Metrics
673
+ interface CancellationMetrics {
674
+ entityId: string;
675
+ entityName: string;
676
+ entityType: 'clinic' | 'practitioner' | 'patient' | 'procedure';
677
+ totalAppointments: number;
678
+ canceledAppointments: number;
679
+ cancellationRate: number; // percentage
680
+ canceledByPatient: number;
681
+ canceledByClinic: number;
682
+ canceledByPractitioner: number;
683
+ averageCancellationLeadTime: number; // hours
684
+ cancellationReasons: Array<{
685
+ reason: string;
686
+ count: number;
687
+ }>;
688
+ }
689
+
690
+ // No-Show Metrics
691
+ interface NoShowMetrics {
692
+ entityId: string;
693
+ entityName: string;
694
+ entityType: 'clinic' | 'practitioner' | 'patient' | 'procedure';
695
+ totalAppointments: number;
696
+ noShowAppointments: number;
697
+ noShowRate: number; // percentage
698
+ }
699
+
700
+ // Revenue Metrics
701
+ interface RevenueMetrics {
702
+ totalRevenue: number;
703
+ averageRevenuePerAppointment: number;
704
+ totalAppointments: number;
705
+ completedAppointments: number;
706
+ currency: string;
707
+ revenueByStatus: Record<AppointmentStatus, number>;
708
+ revenueByPaymentStatus: Record<PaymentStatus, number>;
709
+ unpaidRevenue: number;
710
+ refundedRevenue: number;
711
+ }
712
+
713
+ // Product Usage Metrics
714
+ interface ProductUsageMetrics {
715
+ productId: string;
716
+ productName: string;
717
+ brandId: string;
718
+ brandName: string;
719
+ totalQuantity: number;
720
+ totalRevenue: number;
721
+ averagePrice: number;
722
+ usageCount: number; // number of appointments using this product
723
+ averageQuantityPerAppointment: number;
724
+ }
725
+
726
+ // Patient Analytics
727
+ interface PatientAnalytics {
728
+ patientId: string;
729
+ patientName: string;
730
+ totalAppointments: number;
731
+ completedAppointments: number;
732
+ canceledAppointments: number;
733
+ noShowAppointments: number;
734
+ cancellationRate: number;
735
+ noShowRate: number;
736
+ totalRevenue: number;
737
+ averageRevenuePerAppointment: number;
738
+ lifetimeValue: number;
739
+ firstAppointmentDate: Date;
740
+ lastAppointmentDate: Date;
741
+ averageDaysBetweenAppointments: number;
742
+ }
743
+
744
+ // Clinic Analytics
745
+ interface ClinicAnalytics {
746
+ clinicBranchId: string;
747
+ clinicName: string;
748
+ totalAppointments: number;
749
+ completedAppointments: number;
750
+ canceledAppointments: number;
751
+ noShowAppointments: number;
752
+ cancellationRate: number;
753
+ noShowRate: number;
754
+ totalRevenue: number;
755
+ averageRevenuePerAppointment: number;
756
+ practitionerCount: number;
757
+ patientCount: number;
758
+ procedureCount: number;
759
+ }
760
+
761
+ // Dashboard Analytics
762
+ interface DashboardAnalytics {
763
+ overview: {
764
+ totalAppointments: number;
765
+ completedAppointments: number;
766
+ canceledAppointments: number;
767
+ noShowAppointments: number;
768
+ totalRevenue: number;
769
+ averageRevenuePerAppointment: number;
770
+ uniquePatients: number;
771
+ uniquePractitioners: number;
772
+ };
773
+ practitionerMetrics: PractitionerAnalytics[];
774
+ procedureMetrics: ProcedureAnalytics[];
775
+ cancellationMetrics: CancellationMetrics;
776
+ noShowMetrics: NoShowMetrics;
777
+ revenueTrends: RevenueTrend[];
778
+ timeEfficiency: TimeEfficiencyMetrics;
779
+ topProducts: ProductUsageMetrics[];
780
+ recentActivity: Array<{
781
+ type: 'appointment' | 'cancellation' | 'completion';
782
+ date: Date;
783
+ description: string;
784
+ }>;
785
+ }
786
+ ```
787
+
788
+ ## Implementation Strategy
789
+
790
+ ### Phase 1: Core Infrastructure
791
+ 1. Create `analytics.service.ts` with base structure
792
+ 2. Implement appointment querying utilities
793
+ 3. Implement cost calculation utilities (handling finalbilling, zonesData, base cost)
794
+ 4. Implement time calculation utilities
795
+
796
+ ### Phase 2: Basic Metrics
797
+ 1. Implement practitioner analytics
798
+ 2. Implement procedure analytics
799
+ 3. Implement basic financial metrics
800
+ 4. Implement cancellation/no-show metrics
801
+
802
+ ### Phase 3: Advanced Analytics
803
+ 1. Implement time efficiency metrics
804
+ 2. Implement product usage analytics
805
+ 3. Implement patient analytics
806
+ 4. Implement clinic analytics
807
+
808
+ ### Phase 4: Dashboard & Trends
809
+ 1. Implement dashboard data aggregation
810
+ 2. Implement trend analysis
811
+ 3. Implement comparison metrics
812
+
813
+ ## Performance Considerations
814
+
815
+ ### Query Optimization
816
+ - Use Firestore composite indexes for common query patterns
817
+ - Implement pagination for large datasets
818
+ - Cache frequently accessed metrics
819
+ - Use aggregation queries where possible
820
+
821
+ ### Data Processing
822
+ - Process data in batches for large date ranges
823
+ - Use server-side calculations (Cloud Functions) for complex aggregations
824
+ - Consider pre-aggregating common metrics in Firestore documents
825
+
826
+ ### Caching Strategy
827
+ - Cache dashboard data for short periods (5-15 minutes)
828
+ - Invalidate cache on appointment updates
829
+ - Use Firestore real-time listeners for live updates
830
+
831
+ ## Data Validation & Edge Cases
832
+
833
+ ### Cost Calculation Priority
834
+ 1. Use `metadata.finalbilling.finalPrice` if available
835
+ 2. Calculate from `metadata.zonesData` if finalbilling not available
836
+ 3. Fall back to `appointment.cost` if no zone data
837
+
838
+ ### Time Calculation
839
+ - Handle missing `actualDurationMinutes` gracefully
840
+ - Use booked duration as fallback
841
+ - Account for timezone differences
842
+
843
+ ### Status Filtering
844
+ - Completed: `AppointmentStatus.COMPLETED`
845
+ - Canceled: `CANCELED_PATIENT`, `CANCELED_CLINIC`, `CANCELED_PATIENT_RESCHEDULED`
846
+ - No-show: `AppointmentStatus.NO_SHOW`
847
+ - Active: All statuses except canceled and no-show
848
+
849
+ ## Next Steps
850
+
851
+ 1. Review and approve this proposal
852
+ 2. Create TypeScript type definitions file
853
+ 3. Implement core service structure
854
+ 4. Implement utility functions for cost and time calculations
855
+ 5. Implement basic metrics methods
856
+ 6. Add comprehensive tests
857
+ 7. Create API documentation
858
+ 8. Integrate with Clinic Admin app
859
+