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