@blackcode_sa/metaestetics-api 1.13.3 → 1.13.5
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 +15 -28
- package/dist/admin/index.d.ts +15 -28
- package/dist/index.d.mts +18 -30
- package/dist/index.d.ts +18 -30
- package/dist/index.js +11 -3
- package/dist/index.mjs +11 -3
- package/package.json +121 -119
- 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 +703 -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 +200 -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 +989 -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 +1742 -1742
- package/src/services/procedure/README.md +163 -163
- package/src/services/procedure/index.ts +1 -1
- package/src/services/procedure/procedure.service.ts +2200 -2191
- 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 -489
- 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 -493
- 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,393 +1,393 @@
|
|
|
1
|
-
# Analytics Service - Quick Start Guide
|
|
2
|
-
|
|
3
|
-
## 🚀 What We Can Do
|
|
4
|
-
|
|
5
|
-
The Analytics Service provides **comprehensive insights** into your clinic operations:
|
|
6
|
-
|
|
7
|
-
- **Financial Intelligence**: Revenue, costs, payment tracking
|
|
8
|
-
- **Practitioner Performance**: Doctor efficiency, cancellations, revenue
|
|
9
|
-
- **Procedure Analytics**: Popularity, profitability, product usage
|
|
10
|
-
- **Time Management**: Booked vs actual time, efficiency
|
|
11
|
-
- **Cancellation & No-Show Analysis**: Rates by clinic/doctor/patient/procedure
|
|
12
|
-
- **Patient Insights**: Lifetime value, retention, behavior
|
|
13
|
-
- **Product Usage**: Usage patterns, revenue contribution
|
|
14
|
-
- **Clinic Performance**: Overall metrics and comparisons
|
|
15
|
-
|
|
16
|
-
---
|
|
17
|
-
|
|
18
|
-
## 🔄 How It Works: Computed vs On-Demand
|
|
19
|
-
|
|
20
|
-
### **Pre-Computed Analytics** (Default - Recommended) ⚡
|
|
21
|
-
|
|
22
|
-
**How:**
|
|
23
|
-
1. Cloud Function runs **every 12 hours**
|
|
24
|
-
2. Computes analytics for all clinics
|
|
25
|
-
3. Stores in Firestore: `clinics/{clinicBranchId}/analytics/`
|
|
26
|
-
4. Client reads cached data (instant!)
|
|
27
|
-
|
|
28
|
-
**Benefits:**
|
|
29
|
-
- ⚡ **Fast**: 1 document read = instant response
|
|
30
|
-
- 💰 **Cheap**: 1 read vs hundreds/thousands
|
|
31
|
-
- 📈 **Scalable**: Works with large datasets
|
|
32
|
-
|
|
33
|
-
**Example:**
|
|
34
|
-
```typescript
|
|
35
|
-
// Automatically uses cached data if fresh (< 12 hours)
|
|
36
|
-
const dashboard = await analyticsService.getDashboardData(
|
|
37
|
-
{ clinicBranchId: 'clinic-123' },
|
|
38
|
-
{ start: new Date('2024-01-01'), end: new Date('2024-12-31') }
|
|
39
|
-
);
|
|
40
|
-
// Returns instantly! ⚡
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
### **On-Demand Calculation** (Fallback) 🔄
|
|
44
|
-
|
|
45
|
-
**When:**
|
|
46
|
-
- No cached data exists
|
|
47
|
-
- Cache is stale (> 12 hours)
|
|
48
|
-
- You set `useCache: false`
|
|
49
|
-
- Custom date ranges
|
|
50
|
-
|
|
51
|
-
**How:**
|
|
52
|
-
1. Queries all appointments
|
|
53
|
-
2. Calculates metrics in real-time
|
|
54
|
-
3. Returns results
|
|
55
|
-
|
|
56
|
-
**Example:**
|
|
57
|
-
```typescript
|
|
58
|
-
// Force on-demand calculation
|
|
59
|
-
const dashboard = await analyticsService.getDashboardData(
|
|
60
|
-
{ clinicBranchId: 'clinic-123' },
|
|
61
|
-
dateRange,
|
|
62
|
-
{ useCache: false } // Force calculation
|
|
63
|
-
);
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
---
|
|
67
|
-
|
|
68
|
-
## 📊 Available Analytics
|
|
69
|
-
|
|
70
|
-
### 1. **Practitioner Analytics** 👨⚕️
|
|
71
|
-
```typescript
|
|
72
|
-
const metrics = await analyticsService.getPractitionerAnalytics(
|
|
73
|
-
'practitioner-id',
|
|
74
|
-
dateRange
|
|
75
|
-
);
|
|
76
|
-
// Returns: appointments, cancellations, no-shows, revenue, time efficiency, etc.
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
### 2. **Procedure Analytics** 🏥
|
|
80
|
-
```typescript
|
|
81
|
-
// Single procedure
|
|
82
|
-
const procedure = await analyticsService.getProcedureAnalytics('procedure-id', dateRange);
|
|
83
|
-
|
|
84
|
-
// All procedures
|
|
85
|
-
const all = await analyticsService.getProcedureAnalytics(undefined, dateRange);
|
|
86
|
-
|
|
87
|
-
// Most popular
|
|
88
|
-
const popular = await analyticsService.getProcedurePopularity(dateRange, 10);
|
|
89
|
-
|
|
90
|
-
// Most profitable
|
|
91
|
-
const profitable = await analyticsService.getProcedureProfitability(dateRange, 10);
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
### 3. **Time Efficiency** ⏱️
|
|
95
|
-
```typescript
|
|
96
|
-
const time = await analyticsService.getTimeEfficiencyMetrics(
|
|
97
|
-
{ clinicBranchId: 'clinic-123' },
|
|
98
|
-
dateRange
|
|
99
|
-
);
|
|
100
|
-
// Returns: efficiency %, overrun, underutilization, etc.
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
### 4. **Cancellation Metrics** ❌
|
|
104
|
-
```typescript
|
|
105
|
-
const cancellations = await analyticsService.getCancellationMetrics(
|
|
106
|
-
'practitioner', // or 'clinic', 'patient', 'procedure'
|
|
107
|
-
dateRange
|
|
108
|
-
);
|
|
109
|
-
// Returns array with cancellation rates, reasons, lead times
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
### 5. **No-Show Metrics** 🚫
|
|
113
|
-
```typescript
|
|
114
|
-
const noShows = await analyticsService.getNoShowMetrics(
|
|
115
|
-
'practitioner', // or 'clinic', 'patient', 'procedure'
|
|
116
|
-
dateRange
|
|
117
|
-
);
|
|
118
|
-
// Returns array with no-show rates per entity
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
### 6. **Revenue Metrics** 💰
|
|
122
|
-
```typescript
|
|
123
|
-
const revenue = await analyticsService.getRevenueMetrics(
|
|
124
|
-
{ clinicBranchId: 'clinic-123' },
|
|
125
|
-
dateRange
|
|
126
|
-
);
|
|
127
|
-
// Returns: total revenue, by status, by payment status, unpaid, etc.
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
### 7. **Product Usage** 📦
|
|
131
|
-
```typescript
|
|
132
|
-
const products = await analyticsService.getProductUsageMetrics(
|
|
133
|
-
undefined, // or 'product-id'
|
|
134
|
-
dateRange
|
|
135
|
-
);
|
|
136
|
-
// Returns: usage counts, revenue, quantities per product
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
### 8. **Patient Analytics** 👥
|
|
140
|
-
```typescript
|
|
141
|
-
const patient = await analyticsService.getPatientAnalytics(
|
|
142
|
-
'patient-id', // or undefined for all
|
|
143
|
-
dateRange
|
|
144
|
-
);
|
|
145
|
-
// Returns: lifetime value, retention, frequency, etc.
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
### 9. **Dashboard Data** 📊
|
|
149
|
-
```typescript
|
|
150
|
-
const dashboard = await analyticsService.getDashboardData(
|
|
151
|
-
{ clinicBranchId: 'clinic-123' },
|
|
152
|
-
dateRange
|
|
153
|
-
);
|
|
154
|
-
// Returns: comprehensive dashboard with all metrics
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
---
|
|
158
|
-
|
|
159
|
-
## 🎯 Specific Use Case: Patient No-Show Per Doctor
|
|
160
|
-
|
|
161
|
-
### **Question**: "Which patients have the highest no-show rate for each doctor?"
|
|
162
|
-
|
|
163
|
-
### **Solution 1: No-Show Rates by Practitioner** ✅
|
|
164
|
-
|
|
165
|
-
```typescript
|
|
166
|
-
// Get no-show metrics grouped by practitioner (doctor)
|
|
167
|
-
const noShowByPractitioner = await analyticsService.getNoShowMetrics(
|
|
168
|
-
'practitioner',
|
|
169
|
-
{ start: new Date('2024-01-01'), end: new Date('2024-12-31') }
|
|
170
|
-
);
|
|
171
|
-
|
|
172
|
-
// Returns array:
|
|
173
|
-
// [
|
|
174
|
-
// {
|
|
175
|
-
// entityId: 'practitioner-123',
|
|
176
|
-
// entityName: 'Dr. Smith',
|
|
177
|
-
// entityType: 'practitioner',
|
|
178
|
-
// totalAppointments: 100,
|
|
179
|
-
// noShowAppointments: 15,
|
|
180
|
-
// noShowRate: 15.0 // 15%
|
|
181
|
-
// },
|
|
182
|
-
// ...
|
|
183
|
-
// ]
|
|
184
|
-
|
|
185
|
-
// Sort by no-show rate (highest first)
|
|
186
|
-
const sorted = noShowByPractitioner
|
|
187
|
-
.sort((a, b) => b.noShowRate - a.noShowRate);
|
|
188
|
-
|
|
189
|
-
console.log('Doctors ranked by no-show rate:');
|
|
190
|
-
sorted.forEach((doctor, index) => {
|
|
191
|
-
console.log(`${index + 1}. ${doctor.entityName}: ${doctor.noShowRate}%`);
|
|
192
|
-
});
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
### **Solution 2: Patient No-Shows for a Specific Doctor** ✅
|
|
196
|
-
|
|
197
|
-
```typescript
|
|
198
|
-
// Step 1: Get all appointments for a specific practitioner
|
|
199
|
-
const practitionerId = 'practitioner-123';
|
|
200
|
-
const dateRange = { start: new Date('2024-01-01'), end: new Date('2024-12-31') };
|
|
201
|
-
|
|
202
|
-
// Get practitioner analytics (includes no-show count)
|
|
203
|
-
const practitionerMetrics = await analyticsService.getPractitionerAnalytics(
|
|
204
|
-
practitionerId,
|
|
205
|
-
dateRange
|
|
206
|
-
);
|
|
207
|
-
|
|
208
|
-
console.log(`Dr. ${practitionerMetrics.practitionerName}:`);
|
|
209
|
-
console.log(` Total Appointments: ${practitionerMetrics.totalAppointments}`);
|
|
210
|
-
console.log(` No-Shows: ${practitionerMetrics.noShowAppointments}`);
|
|
211
|
-
console.log(` No-Show Rate: ${practitionerMetrics.noShowRate}%`);
|
|
212
|
-
|
|
213
|
-
// Step 2: Get patient-level breakdown for this doctor
|
|
214
|
-
// (You can filter appointments manually or use patient analytics)
|
|
215
|
-
|
|
216
|
-
// Get patient analytics filtered by practitioner
|
|
217
|
-
const allPatients = await analyticsService.getPatientAnalytics(undefined, dateRange);
|
|
218
|
-
|
|
219
|
-
// Filter patients who had appointments with this practitioner
|
|
220
|
-
// and calculate their no-show rates
|
|
221
|
-
const patientsWithThisDoctor = allPatients
|
|
222
|
-
.filter(patient => {
|
|
223
|
-
// Check if patient had appointments with this practitioner
|
|
224
|
-
// (You'd need to query appointments or enhance patient analytics)
|
|
225
|
-
return true; // Placeholder
|
|
226
|
-
})
|
|
227
|
-
.sort((a, b) => b.noShowRate - a.noShowRate);
|
|
228
|
-
|
|
229
|
-
console.log('\nPatients with highest no-show rates for this doctor:');
|
|
230
|
-
patientsWithThisDoctor.slice(0, 10).forEach((patient, index) => {
|
|
231
|
-
console.log(`${index + 1}. ${patient.patientName}: ${patient.noShowRate}%`);
|
|
232
|
-
});
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
### **Solution 3: Cross-Analysis (No-Shows by Patient AND Doctor)** ✅
|
|
236
|
-
|
|
237
|
-
```typescript
|
|
238
|
-
// Get no-shows grouped by patient
|
|
239
|
-
const noShowByPatient = await analyticsService.getNoShowMetrics(
|
|
240
|
-
'patient',
|
|
241
|
-
dateRange
|
|
242
|
-
);
|
|
243
|
-
|
|
244
|
-
// Get no-shows grouped by practitioner
|
|
245
|
-
const noShowByPractitioner = await analyticsService.getNoShowMetrics(
|
|
246
|
-
'practitioner',
|
|
247
|
-
dateRange
|
|
248
|
-
);
|
|
249
|
-
|
|
250
|
-
// For detailed cross-analysis, you can:
|
|
251
|
-
// 1. Get all appointments
|
|
252
|
-
// 2. Filter no-shows
|
|
253
|
-
// 3. Group by practitioner AND patient
|
|
254
|
-
const appointments = await analyticsService['fetchAppointments'](
|
|
255
|
-
undefined,
|
|
256
|
-
dateRange
|
|
257
|
-
);
|
|
258
|
-
|
|
259
|
-
const noShows = appointments.filter(a => a.status === AppointmentStatus.NO_SHOW);
|
|
260
|
-
|
|
261
|
-
// Create a map: practitioner -> patients -> no-show count
|
|
262
|
-
const practitionerPatientMap = new Map<
|
|
263
|
-
string,
|
|
264
|
-
Map<string, { patientName: string; noShowCount: number; totalAppointments: number }>
|
|
265
|
-
>();
|
|
266
|
-
|
|
267
|
-
appointments.forEach(appointment => {
|
|
268
|
-
const practitionerId = appointment.practitionerId;
|
|
269
|
-
const patientId = appointment.patientId;
|
|
270
|
-
const patientName = appointment.patientInfo?.fullName || 'Unknown';
|
|
271
|
-
|
|
272
|
-
if (!practitionerPatientMap.has(practitionerId)) {
|
|
273
|
-
practitionerPatientMap.set(practitionerId, new Map());
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
const patientMap = practitionerPatientMap.get(practitionerId)!;
|
|
277
|
-
if (!patientMap.has(patientId)) {
|
|
278
|
-
patientMap.set(patientId, { patientName, noShowCount: 0, totalAppointments: 0 });
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
const patientData = patientMap.get(patientId)!;
|
|
282
|
-
patientData.totalAppointments++;
|
|
283
|
-
|
|
284
|
-
if (appointment.status === AppointmentStatus.NO_SHOW) {
|
|
285
|
-
patientData.noShowCount++;
|
|
286
|
-
}
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
// Display results
|
|
290
|
-
practitionerPatientMap.forEach((patientMap, practitionerId) => {
|
|
291
|
-
const practitioner = appointments.find(a => a.practitionerId === practitionerId);
|
|
292
|
-
const practitionerName = practitioner?.practitionerInfo?.name || 'Unknown';
|
|
293
|
-
|
|
294
|
-
console.log(`\n${practitionerName}:`);
|
|
295
|
-
|
|
296
|
-
const patientRates = Array.from(patientMap.entries())
|
|
297
|
-
.map(([patientId, data]) => ({
|
|
298
|
-
patientId,
|
|
299
|
-
patientName: data.patientName,
|
|
300
|
-
noShowRate: (data.noShowCount / data.totalAppointments) * 100,
|
|
301
|
-
noShowCount: data.noShowCount,
|
|
302
|
-
totalAppointments: data.totalAppointments,
|
|
303
|
-
}))
|
|
304
|
-
.sort((a, b) => b.noShowRate - a.noShowRate);
|
|
305
|
-
|
|
306
|
-
patientRates.forEach(patient => {
|
|
307
|
-
console.log(` ${patient.patientName}: ${patient.noShowRate.toFixed(1)}% (${patient.noShowCount}/${patient.totalAppointments})`);
|
|
308
|
-
});
|
|
309
|
-
});
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
---
|
|
313
|
-
|
|
314
|
-
## 📝 Quick Examples
|
|
315
|
-
|
|
316
|
-
### Example 1: Doctor Performance Dashboard
|
|
317
|
-
```typescript
|
|
318
|
-
const practitionerMetrics = await analyticsService.getPractitionerAnalytics(
|
|
319
|
-
'practitioner-id',
|
|
320
|
-
dateRange
|
|
321
|
-
);
|
|
322
|
-
|
|
323
|
-
console.log(`Doctor: ${practitionerMetrics.practitionerName}`);
|
|
324
|
-
console.log(`Appointments: ${practitionerMetrics.totalAppointments}`);
|
|
325
|
-
console.log(`Cancellation Rate: ${practitionerMetrics.cancellationRate}%`);
|
|
326
|
-
console.log(`No-Show Rate: ${practitionerMetrics.noShowRate}%`);
|
|
327
|
-
console.log(`Time Efficiency: ${practitionerMetrics.timeEfficiency}%`);
|
|
328
|
-
console.log(`Total Revenue: ${practitionerMetrics.totalRevenue} ${practitionerMetrics.currency}`);
|
|
329
|
-
```
|
|
330
|
-
|
|
331
|
-
### Example 2: Top Procedures by Revenue
|
|
332
|
-
```typescript
|
|
333
|
-
const topProcedures = await analyticsService.getProcedureProfitability(
|
|
334
|
-
dateRange,
|
|
335
|
-
10
|
|
336
|
-
);
|
|
337
|
-
|
|
338
|
-
topProcedures.forEach((procedure, index) => {
|
|
339
|
-
console.log(`${index + 1}. ${procedure.procedureName}`);
|
|
340
|
-
console.log(` Revenue: ${procedure.totalRevenue}`);
|
|
341
|
-
console.log(` Appointments: ${procedure.appointmentCount}`);
|
|
342
|
-
});
|
|
343
|
-
```
|
|
344
|
-
|
|
345
|
-
### Example 3: Clinic Comparison
|
|
346
|
-
```typescript
|
|
347
|
-
const clinic1 = await analyticsService.getClinicAnalytics('clinic-1', dateRange);
|
|
348
|
-
const clinic2 = await analyticsService.getClinicAnalytics('clinic-2', dateRange);
|
|
349
|
-
|
|
350
|
-
console.log('Clinic Comparison:');
|
|
351
|
-
console.log(`Clinic 1 Revenue: ${clinic1.totalRevenue}`);
|
|
352
|
-
console.log(`Clinic 2 Revenue: ${clinic2.totalRevenue}`);
|
|
353
|
-
console.log(`Clinic 1 Cancellation Rate: ${clinic1.cancellationRate}%`);
|
|
354
|
-
console.log(`Clinic 2 Cancellation Rate: ${clinic2.cancellationRate}%`);
|
|
355
|
-
```
|
|
356
|
-
|
|
357
|
-
---
|
|
358
|
-
|
|
359
|
-
## 🎛️ Configuration
|
|
360
|
-
|
|
361
|
-
### Change Cache Freshness
|
|
362
|
-
```typescript
|
|
363
|
-
// Use cache if data is less than 6 hours old
|
|
364
|
-
const metrics = await analyticsService.getPractitionerAnalytics(
|
|
365
|
-
practitionerId,
|
|
366
|
-
dateRange,
|
|
367
|
-
{ maxCacheAgeHours: 6 }
|
|
368
|
-
);
|
|
369
|
-
```
|
|
370
|
-
|
|
371
|
-
### Force On-Demand Calculation
|
|
372
|
-
```typescript
|
|
373
|
-
const metrics = await analyticsService.getPractitionerAnalytics(
|
|
374
|
-
practitionerId,
|
|
375
|
-
dateRange,
|
|
376
|
-
{ useCache: false }
|
|
377
|
-
);
|
|
378
|
-
```
|
|
379
|
-
|
|
380
|
-
### Change Cloud Function Schedule
|
|
381
|
-
Edit `Cloud/functions/src/analytics/computeAnalytics.ts`:
|
|
382
|
-
```typescript
|
|
383
|
-
schedule: "every 6 hours" // or "every 24 hours"
|
|
384
|
-
```
|
|
385
|
-
|
|
386
|
-
---
|
|
387
|
-
|
|
388
|
-
## 📚 Full Documentation
|
|
389
|
-
|
|
390
|
-
- **Usage Guide**: `USAGE_GUIDE.md` - Complete guide with all use cases
|
|
391
|
-
- **Architecture**: `ARCHITECTURE.md` - How computed vs on-demand works
|
|
392
|
-
- **README**: `README.md` - API reference
|
|
393
|
-
|
|
1
|
+
# Analytics Service - Quick Start Guide
|
|
2
|
+
|
|
3
|
+
## 🚀 What We Can Do
|
|
4
|
+
|
|
5
|
+
The Analytics Service provides **comprehensive insights** into your clinic operations:
|
|
6
|
+
|
|
7
|
+
- **Financial Intelligence**: Revenue, costs, payment tracking
|
|
8
|
+
- **Practitioner Performance**: Doctor efficiency, cancellations, revenue
|
|
9
|
+
- **Procedure Analytics**: Popularity, profitability, product usage
|
|
10
|
+
- **Time Management**: Booked vs actual time, efficiency
|
|
11
|
+
- **Cancellation & No-Show Analysis**: Rates by clinic/doctor/patient/procedure
|
|
12
|
+
- **Patient Insights**: Lifetime value, retention, behavior
|
|
13
|
+
- **Product Usage**: Usage patterns, revenue contribution
|
|
14
|
+
- **Clinic Performance**: Overall metrics and comparisons
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 🔄 How It Works: Computed vs On-Demand
|
|
19
|
+
|
|
20
|
+
### **Pre-Computed Analytics** (Default - Recommended) ⚡
|
|
21
|
+
|
|
22
|
+
**How:**
|
|
23
|
+
1. Cloud Function runs **every 12 hours**
|
|
24
|
+
2. Computes analytics for all clinics
|
|
25
|
+
3. Stores in Firestore: `clinics/{clinicBranchId}/analytics/`
|
|
26
|
+
4. Client reads cached data (instant!)
|
|
27
|
+
|
|
28
|
+
**Benefits:**
|
|
29
|
+
- ⚡ **Fast**: 1 document read = instant response
|
|
30
|
+
- 💰 **Cheap**: 1 read vs hundreds/thousands
|
|
31
|
+
- 📈 **Scalable**: Works with large datasets
|
|
32
|
+
|
|
33
|
+
**Example:**
|
|
34
|
+
```typescript
|
|
35
|
+
// Automatically uses cached data if fresh (< 12 hours)
|
|
36
|
+
const dashboard = await analyticsService.getDashboardData(
|
|
37
|
+
{ clinicBranchId: 'clinic-123' },
|
|
38
|
+
{ start: new Date('2024-01-01'), end: new Date('2024-12-31') }
|
|
39
|
+
);
|
|
40
|
+
// Returns instantly! ⚡
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### **On-Demand Calculation** (Fallback) 🔄
|
|
44
|
+
|
|
45
|
+
**When:**
|
|
46
|
+
- No cached data exists
|
|
47
|
+
- Cache is stale (> 12 hours)
|
|
48
|
+
- You set `useCache: false`
|
|
49
|
+
- Custom date ranges
|
|
50
|
+
|
|
51
|
+
**How:**
|
|
52
|
+
1. Queries all appointments
|
|
53
|
+
2. Calculates metrics in real-time
|
|
54
|
+
3. Returns results
|
|
55
|
+
|
|
56
|
+
**Example:**
|
|
57
|
+
```typescript
|
|
58
|
+
// Force on-demand calculation
|
|
59
|
+
const dashboard = await analyticsService.getDashboardData(
|
|
60
|
+
{ clinicBranchId: 'clinic-123' },
|
|
61
|
+
dateRange,
|
|
62
|
+
{ useCache: false } // Force calculation
|
|
63
|
+
);
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 📊 Available Analytics
|
|
69
|
+
|
|
70
|
+
### 1. **Practitioner Analytics** 👨⚕️
|
|
71
|
+
```typescript
|
|
72
|
+
const metrics = await analyticsService.getPractitionerAnalytics(
|
|
73
|
+
'practitioner-id',
|
|
74
|
+
dateRange
|
|
75
|
+
);
|
|
76
|
+
// Returns: appointments, cancellations, no-shows, revenue, time efficiency, etc.
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 2. **Procedure Analytics** 🏥
|
|
80
|
+
```typescript
|
|
81
|
+
// Single procedure
|
|
82
|
+
const procedure = await analyticsService.getProcedureAnalytics('procedure-id', dateRange);
|
|
83
|
+
|
|
84
|
+
// All procedures
|
|
85
|
+
const all = await analyticsService.getProcedureAnalytics(undefined, dateRange);
|
|
86
|
+
|
|
87
|
+
// Most popular
|
|
88
|
+
const popular = await analyticsService.getProcedurePopularity(dateRange, 10);
|
|
89
|
+
|
|
90
|
+
// Most profitable
|
|
91
|
+
const profitable = await analyticsService.getProcedureProfitability(dateRange, 10);
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 3. **Time Efficiency** ⏱️
|
|
95
|
+
```typescript
|
|
96
|
+
const time = await analyticsService.getTimeEfficiencyMetrics(
|
|
97
|
+
{ clinicBranchId: 'clinic-123' },
|
|
98
|
+
dateRange
|
|
99
|
+
);
|
|
100
|
+
// Returns: efficiency %, overrun, underutilization, etc.
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### 4. **Cancellation Metrics** ❌
|
|
104
|
+
```typescript
|
|
105
|
+
const cancellations = await analyticsService.getCancellationMetrics(
|
|
106
|
+
'practitioner', // or 'clinic', 'patient', 'procedure'
|
|
107
|
+
dateRange
|
|
108
|
+
);
|
|
109
|
+
// Returns array with cancellation rates, reasons, lead times
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### 5. **No-Show Metrics** 🚫
|
|
113
|
+
```typescript
|
|
114
|
+
const noShows = await analyticsService.getNoShowMetrics(
|
|
115
|
+
'practitioner', // or 'clinic', 'patient', 'procedure'
|
|
116
|
+
dateRange
|
|
117
|
+
);
|
|
118
|
+
// Returns array with no-show rates per entity
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### 6. **Revenue Metrics** 💰
|
|
122
|
+
```typescript
|
|
123
|
+
const revenue = await analyticsService.getRevenueMetrics(
|
|
124
|
+
{ clinicBranchId: 'clinic-123' },
|
|
125
|
+
dateRange
|
|
126
|
+
);
|
|
127
|
+
// Returns: total revenue, by status, by payment status, unpaid, etc.
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### 7. **Product Usage** 📦
|
|
131
|
+
```typescript
|
|
132
|
+
const products = await analyticsService.getProductUsageMetrics(
|
|
133
|
+
undefined, // or 'product-id'
|
|
134
|
+
dateRange
|
|
135
|
+
);
|
|
136
|
+
// Returns: usage counts, revenue, quantities per product
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### 8. **Patient Analytics** 👥
|
|
140
|
+
```typescript
|
|
141
|
+
const patient = await analyticsService.getPatientAnalytics(
|
|
142
|
+
'patient-id', // or undefined for all
|
|
143
|
+
dateRange
|
|
144
|
+
);
|
|
145
|
+
// Returns: lifetime value, retention, frequency, etc.
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### 9. **Dashboard Data** 📊
|
|
149
|
+
```typescript
|
|
150
|
+
const dashboard = await analyticsService.getDashboardData(
|
|
151
|
+
{ clinicBranchId: 'clinic-123' },
|
|
152
|
+
dateRange
|
|
153
|
+
);
|
|
154
|
+
// Returns: comprehensive dashboard with all metrics
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## 🎯 Specific Use Case: Patient No-Show Per Doctor
|
|
160
|
+
|
|
161
|
+
### **Question**: "Which patients have the highest no-show rate for each doctor?"
|
|
162
|
+
|
|
163
|
+
### **Solution 1: No-Show Rates by Practitioner** ✅
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
// Get no-show metrics grouped by practitioner (doctor)
|
|
167
|
+
const noShowByPractitioner = await analyticsService.getNoShowMetrics(
|
|
168
|
+
'practitioner',
|
|
169
|
+
{ start: new Date('2024-01-01'), end: new Date('2024-12-31') }
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
// Returns array:
|
|
173
|
+
// [
|
|
174
|
+
// {
|
|
175
|
+
// entityId: 'practitioner-123',
|
|
176
|
+
// entityName: 'Dr. Smith',
|
|
177
|
+
// entityType: 'practitioner',
|
|
178
|
+
// totalAppointments: 100,
|
|
179
|
+
// noShowAppointments: 15,
|
|
180
|
+
// noShowRate: 15.0 // 15%
|
|
181
|
+
// },
|
|
182
|
+
// ...
|
|
183
|
+
// ]
|
|
184
|
+
|
|
185
|
+
// Sort by no-show rate (highest first)
|
|
186
|
+
const sorted = noShowByPractitioner
|
|
187
|
+
.sort((a, b) => b.noShowRate - a.noShowRate);
|
|
188
|
+
|
|
189
|
+
console.log('Doctors ranked by no-show rate:');
|
|
190
|
+
sorted.forEach((doctor, index) => {
|
|
191
|
+
console.log(`${index + 1}. ${doctor.entityName}: ${doctor.noShowRate}%`);
|
|
192
|
+
});
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### **Solution 2: Patient No-Shows for a Specific Doctor** ✅
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
// Step 1: Get all appointments for a specific practitioner
|
|
199
|
+
const practitionerId = 'practitioner-123';
|
|
200
|
+
const dateRange = { start: new Date('2024-01-01'), end: new Date('2024-12-31') };
|
|
201
|
+
|
|
202
|
+
// Get practitioner analytics (includes no-show count)
|
|
203
|
+
const practitionerMetrics = await analyticsService.getPractitionerAnalytics(
|
|
204
|
+
practitionerId,
|
|
205
|
+
dateRange
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
console.log(`Dr. ${practitionerMetrics.practitionerName}:`);
|
|
209
|
+
console.log(` Total Appointments: ${practitionerMetrics.totalAppointments}`);
|
|
210
|
+
console.log(` No-Shows: ${practitionerMetrics.noShowAppointments}`);
|
|
211
|
+
console.log(` No-Show Rate: ${practitionerMetrics.noShowRate}%`);
|
|
212
|
+
|
|
213
|
+
// Step 2: Get patient-level breakdown for this doctor
|
|
214
|
+
// (You can filter appointments manually or use patient analytics)
|
|
215
|
+
|
|
216
|
+
// Get patient analytics filtered by practitioner
|
|
217
|
+
const allPatients = await analyticsService.getPatientAnalytics(undefined, dateRange);
|
|
218
|
+
|
|
219
|
+
// Filter patients who had appointments with this practitioner
|
|
220
|
+
// and calculate their no-show rates
|
|
221
|
+
const patientsWithThisDoctor = allPatients
|
|
222
|
+
.filter(patient => {
|
|
223
|
+
// Check if patient had appointments with this practitioner
|
|
224
|
+
// (You'd need to query appointments or enhance patient analytics)
|
|
225
|
+
return true; // Placeholder
|
|
226
|
+
})
|
|
227
|
+
.sort((a, b) => b.noShowRate - a.noShowRate);
|
|
228
|
+
|
|
229
|
+
console.log('\nPatients with highest no-show rates for this doctor:');
|
|
230
|
+
patientsWithThisDoctor.slice(0, 10).forEach((patient, index) => {
|
|
231
|
+
console.log(`${index + 1}. ${patient.patientName}: ${patient.noShowRate}%`);
|
|
232
|
+
});
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### **Solution 3: Cross-Analysis (No-Shows by Patient AND Doctor)** ✅
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
// Get no-shows grouped by patient
|
|
239
|
+
const noShowByPatient = await analyticsService.getNoShowMetrics(
|
|
240
|
+
'patient',
|
|
241
|
+
dateRange
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
// Get no-shows grouped by practitioner
|
|
245
|
+
const noShowByPractitioner = await analyticsService.getNoShowMetrics(
|
|
246
|
+
'practitioner',
|
|
247
|
+
dateRange
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
// For detailed cross-analysis, you can:
|
|
251
|
+
// 1. Get all appointments
|
|
252
|
+
// 2. Filter no-shows
|
|
253
|
+
// 3. Group by practitioner AND patient
|
|
254
|
+
const appointments = await analyticsService['fetchAppointments'](
|
|
255
|
+
undefined,
|
|
256
|
+
dateRange
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
const noShows = appointments.filter(a => a.status === AppointmentStatus.NO_SHOW);
|
|
260
|
+
|
|
261
|
+
// Create a map: practitioner -> patients -> no-show count
|
|
262
|
+
const practitionerPatientMap = new Map<
|
|
263
|
+
string,
|
|
264
|
+
Map<string, { patientName: string; noShowCount: number; totalAppointments: number }>
|
|
265
|
+
>();
|
|
266
|
+
|
|
267
|
+
appointments.forEach(appointment => {
|
|
268
|
+
const practitionerId = appointment.practitionerId;
|
|
269
|
+
const patientId = appointment.patientId;
|
|
270
|
+
const patientName = appointment.patientInfo?.fullName || 'Unknown';
|
|
271
|
+
|
|
272
|
+
if (!practitionerPatientMap.has(practitionerId)) {
|
|
273
|
+
practitionerPatientMap.set(practitionerId, new Map());
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const patientMap = practitionerPatientMap.get(practitionerId)!;
|
|
277
|
+
if (!patientMap.has(patientId)) {
|
|
278
|
+
patientMap.set(patientId, { patientName, noShowCount: 0, totalAppointments: 0 });
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const patientData = patientMap.get(patientId)!;
|
|
282
|
+
patientData.totalAppointments++;
|
|
283
|
+
|
|
284
|
+
if (appointment.status === AppointmentStatus.NO_SHOW) {
|
|
285
|
+
patientData.noShowCount++;
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// Display results
|
|
290
|
+
practitionerPatientMap.forEach((patientMap, practitionerId) => {
|
|
291
|
+
const practitioner = appointments.find(a => a.practitionerId === practitionerId);
|
|
292
|
+
const practitionerName = practitioner?.practitionerInfo?.name || 'Unknown';
|
|
293
|
+
|
|
294
|
+
console.log(`\n${practitionerName}:`);
|
|
295
|
+
|
|
296
|
+
const patientRates = Array.from(patientMap.entries())
|
|
297
|
+
.map(([patientId, data]) => ({
|
|
298
|
+
patientId,
|
|
299
|
+
patientName: data.patientName,
|
|
300
|
+
noShowRate: (data.noShowCount / data.totalAppointments) * 100,
|
|
301
|
+
noShowCount: data.noShowCount,
|
|
302
|
+
totalAppointments: data.totalAppointments,
|
|
303
|
+
}))
|
|
304
|
+
.sort((a, b) => b.noShowRate - a.noShowRate);
|
|
305
|
+
|
|
306
|
+
patientRates.forEach(patient => {
|
|
307
|
+
console.log(` ${patient.patientName}: ${patient.noShowRate.toFixed(1)}% (${patient.noShowCount}/${patient.totalAppointments})`);
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
## 📝 Quick Examples
|
|
315
|
+
|
|
316
|
+
### Example 1: Doctor Performance Dashboard
|
|
317
|
+
```typescript
|
|
318
|
+
const practitionerMetrics = await analyticsService.getPractitionerAnalytics(
|
|
319
|
+
'practitioner-id',
|
|
320
|
+
dateRange
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
console.log(`Doctor: ${practitionerMetrics.practitionerName}`);
|
|
324
|
+
console.log(`Appointments: ${practitionerMetrics.totalAppointments}`);
|
|
325
|
+
console.log(`Cancellation Rate: ${practitionerMetrics.cancellationRate}%`);
|
|
326
|
+
console.log(`No-Show Rate: ${practitionerMetrics.noShowRate}%`);
|
|
327
|
+
console.log(`Time Efficiency: ${practitionerMetrics.timeEfficiency}%`);
|
|
328
|
+
console.log(`Total Revenue: ${practitionerMetrics.totalRevenue} ${practitionerMetrics.currency}`);
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### Example 2: Top Procedures by Revenue
|
|
332
|
+
```typescript
|
|
333
|
+
const topProcedures = await analyticsService.getProcedureProfitability(
|
|
334
|
+
dateRange,
|
|
335
|
+
10
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
topProcedures.forEach((procedure, index) => {
|
|
339
|
+
console.log(`${index + 1}. ${procedure.procedureName}`);
|
|
340
|
+
console.log(` Revenue: ${procedure.totalRevenue}`);
|
|
341
|
+
console.log(` Appointments: ${procedure.appointmentCount}`);
|
|
342
|
+
});
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### Example 3: Clinic Comparison
|
|
346
|
+
```typescript
|
|
347
|
+
const clinic1 = await analyticsService.getClinicAnalytics('clinic-1', dateRange);
|
|
348
|
+
const clinic2 = await analyticsService.getClinicAnalytics('clinic-2', dateRange);
|
|
349
|
+
|
|
350
|
+
console.log('Clinic Comparison:');
|
|
351
|
+
console.log(`Clinic 1 Revenue: ${clinic1.totalRevenue}`);
|
|
352
|
+
console.log(`Clinic 2 Revenue: ${clinic2.totalRevenue}`);
|
|
353
|
+
console.log(`Clinic 1 Cancellation Rate: ${clinic1.cancellationRate}%`);
|
|
354
|
+
console.log(`Clinic 2 Cancellation Rate: ${clinic2.cancellationRate}%`);
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
## 🎛️ Configuration
|
|
360
|
+
|
|
361
|
+
### Change Cache Freshness
|
|
362
|
+
```typescript
|
|
363
|
+
// Use cache if data is less than 6 hours old
|
|
364
|
+
const metrics = await analyticsService.getPractitionerAnalytics(
|
|
365
|
+
practitionerId,
|
|
366
|
+
dateRange,
|
|
367
|
+
{ maxCacheAgeHours: 6 }
|
|
368
|
+
);
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
### Force On-Demand Calculation
|
|
372
|
+
```typescript
|
|
373
|
+
const metrics = await analyticsService.getPractitionerAnalytics(
|
|
374
|
+
practitionerId,
|
|
375
|
+
dateRange,
|
|
376
|
+
{ useCache: false }
|
|
377
|
+
);
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### Change Cloud Function Schedule
|
|
381
|
+
Edit `Cloud/functions/src/analytics/computeAnalytics.ts`:
|
|
382
|
+
```typescript
|
|
383
|
+
schedule: "every 6 hours" // or "every 24 hours"
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
## 📚 Full Documentation
|
|
389
|
+
|
|
390
|
+
- **Usage Guide**: `USAGE_GUIDE.md` - Complete guide with all use cases
|
|
391
|
+
- **Architecture**: `ARCHITECTURE.md` - How computed vs on-demand works
|
|
392
|
+
- **README**: `README.md` - API reference
|
|
393
|
+
|