@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.
Files changed (295) hide show
  1. package/dist/admin/index.d.mts +20 -1
  2. package/dist/admin/index.d.ts +20 -1
  3. package/dist/admin/index.js +217 -1
  4. package/dist/admin/index.mjs +217 -1
  5. package/dist/index.d.mts +26 -3
  6. package/dist/index.d.ts +26 -3
  7. package/dist/index.js +168 -6
  8. package/dist/index.mjs +168 -6
  9. package/package.json +121 -121
  10. package/src/__mocks__/firstore.ts +10 -10
  11. package/src/admin/aggregation/README.md +79 -79
  12. package/src/admin/aggregation/appointment/README.md +128 -128
  13. package/src/admin/aggregation/appointment/appointment.aggregation.service.ts +1984 -1984
  14. package/src/admin/aggregation/appointment/index.ts +1 -1
  15. package/src/admin/aggregation/clinic/README.md +52 -52
  16. package/src/admin/aggregation/clinic/clinic.aggregation.service.ts +966 -703
  17. package/src/admin/aggregation/clinic/index.ts +1 -1
  18. package/src/admin/aggregation/forms/README.md +13 -13
  19. package/src/admin/aggregation/forms/filled-forms.aggregation.service.ts +322 -322
  20. package/src/admin/aggregation/forms/index.ts +1 -1
  21. package/src/admin/aggregation/index.ts +8 -8
  22. package/src/admin/aggregation/patient/README.md +27 -27
  23. package/src/admin/aggregation/patient/index.ts +1 -1
  24. package/src/admin/aggregation/patient/patient.aggregation.service.ts +141 -141
  25. package/src/admin/aggregation/practitioner/README.md +42 -42
  26. package/src/admin/aggregation/practitioner/index.ts +1 -1
  27. package/src/admin/aggregation/practitioner/practitioner.aggregation.service.ts +433 -433
  28. package/src/admin/aggregation/practitioner-invite/index.ts +1 -1
  29. package/src/admin/aggregation/practitioner-invite/practitioner-invite.aggregation.service.ts +961 -961
  30. package/src/admin/aggregation/procedure/README.md +43 -43
  31. package/src/admin/aggregation/procedure/index.ts +1 -1
  32. package/src/admin/aggregation/procedure/procedure.aggregation.service.ts +702 -702
  33. package/src/admin/aggregation/reviews/index.ts +1 -1
  34. package/src/admin/aggregation/reviews/reviews.aggregation.service.ts +689 -689
  35. package/src/admin/analytics/analytics.admin.service.ts +278 -278
  36. package/src/admin/analytics/index.ts +2 -2
  37. package/src/admin/booking/README.md +125 -125
  38. package/src/admin/booking/booking.admin.ts +1037 -1037
  39. package/src/admin/booking/booking.calculator.ts +712 -712
  40. package/src/admin/booking/booking.types.ts +59 -59
  41. package/src/admin/booking/index.ts +3 -3
  42. package/src/admin/booking/timezones-problem.md +185 -185
  43. package/src/admin/calendar/README.md +7 -7
  44. package/src/admin/calendar/calendar.admin.service.ts +345 -345
  45. package/src/admin/calendar/index.ts +1 -1
  46. package/src/admin/documentation-templates/document-manager.admin.ts +260 -260
  47. package/src/admin/documentation-templates/index.ts +1 -1
  48. package/src/admin/free-consultation/free-consultation-utils.admin.ts +148 -148
  49. package/src/admin/free-consultation/index.ts +1 -1
  50. package/src/admin/index.ts +81 -81
  51. package/src/admin/logger/index.ts +78 -78
  52. package/src/admin/mailing/README.md +95 -95
  53. package/src/admin/mailing/appointment/appointment.mailing.service.ts +732 -732
  54. package/src/admin/mailing/appointment/index.ts +1 -1
  55. package/src/admin/mailing/appointment/templates/patient/appointment-confirmed.html +40 -40
  56. package/src/admin/mailing/base.mailing.service.ts +208 -208
  57. package/src/admin/mailing/index.ts +3 -3
  58. package/src/admin/mailing/practitionerInvite/existing-practitioner-invite.mailing.ts +611 -611
  59. package/src/admin/mailing/practitionerInvite/index.ts +2 -2
  60. package/src/admin/mailing/practitionerInvite/practitionerInvite.mailing.ts +395 -395
  61. package/src/admin/mailing/practitionerInvite/templates/existing-practitioner-invitation.template.ts +155 -155
  62. package/src/admin/mailing/practitionerInvite/templates/invitation.template.ts +101 -101
  63. package/src/admin/mailing/practitionerInvite/templates/invite-accepted-notification.template.ts +228 -228
  64. package/src/admin/mailing/practitionerInvite/templates/invite-rejected-notification.template.ts +242 -242
  65. package/src/admin/notifications/index.ts +1 -1
  66. package/src/admin/notifications/notifications.admin.ts +710 -710
  67. package/src/admin/requirements/README.md +128 -128
  68. package/src/admin/requirements/index.ts +1 -1
  69. package/src/admin/requirements/patient-requirements.admin.service.ts +475 -475
  70. package/src/admin/users/index.ts +1 -1
  71. package/src/admin/users/user-profile.admin.ts +405 -405
  72. package/src/backoffice/constants/certification.constants.ts +13 -13
  73. package/src/backoffice/constants/index.ts +1 -1
  74. package/src/backoffice/errors/backoffice.errors.ts +181 -181
  75. package/src/backoffice/errors/index.ts +1 -1
  76. package/src/backoffice/expo-safe/README.md +26 -26
  77. package/src/backoffice/expo-safe/index.ts +41 -41
  78. package/src/backoffice/index.ts +5 -5
  79. package/src/backoffice/services/FIXES_README.md +102 -102
  80. package/src/backoffice/services/README.md +57 -57
  81. package/src/backoffice/services/analytics.service.proposal.md +863 -863
  82. package/src/backoffice/services/analytics.service.summary.md +143 -143
  83. package/src/backoffice/services/brand.service.ts +256 -256
  84. package/src/backoffice/services/category.service.ts +384 -384
  85. package/src/backoffice/services/constants.service.ts +385 -385
  86. package/src/backoffice/services/documentation-template.service.ts +202 -202
  87. package/src/backoffice/services/index.ts +10 -10
  88. package/src/backoffice/services/migrate-products.ts +116 -116
  89. package/src/backoffice/services/product.service.ts +553 -553
  90. package/src/backoffice/services/requirement.service.ts +235 -235
  91. package/src/backoffice/services/subcategory.service.ts +461 -461
  92. package/src/backoffice/services/technology.service.ts +1151 -1151
  93. package/src/backoffice/types/README.md +12 -12
  94. package/src/backoffice/types/admin-constants.types.ts +69 -69
  95. package/src/backoffice/types/brand.types.ts +29 -29
  96. package/src/backoffice/types/category.types.ts +67 -67
  97. package/src/backoffice/types/documentation-templates.types.ts +28 -28
  98. package/src/backoffice/types/index.ts +10 -10
  99. package/src/backoffice/types/procedure-product.types.ts +38 -38
  100. package/src/backoffice/types/product.types.ts +240 -240
  101. package/src/backoffice/types/requirement.types.ts +63 -63
  102. package/src/backoffice/types/static/README.md +18 -18
  103. package/src/backoffice/types/static/blocking-condition.types.ts +21 -21
  104. package/src/backoffice/types/static/certification.types.ts +37 -37
  105. package/src/backoffice/types/static/contraindication.types.ts +19 -19
  106. package/src/backoffice/types/static/index.ts +6 -6
  107. package/src/backoffice/types/static/pricing.types.ts +16 -16
  108. package/src/backoffice/types/static/procedure-family.types.ts +14 -14
  109. package/src/backoffice/types/static/treatment-benefit.types.ts +22 -22
  110. package/src/backoffice/types/subcategory.types.ts +34 -34
  111. package/src/backoffice/types/technology.types.ts +168 -168
  112. package/src/backoffice/validations/index.ts +1 -1
  113. package/src/backoffice/validations/schemas.ts +164 -164
  114. package/src/config/__mocks__/firebase.ts +99 -99
  115. package/src/config/firebase.ts +78 -78
  116. package/src/config/index.ts +9 -9
  117. package/src/errors/auth.error.ts +6 -6
  118. package/src/errors/auth.errors.ts +211 -200
  119. package/src/errors/clinic.errors.ts +32 -32
  120. package/src/errors/firebase.errors.ts +47 -47
  121. package/src/errors/user.errors.ts +99 -99
  122. package/src/index.backup.ts +407 -407
  123. package/src/index.ts +6 -6
  124. package/src/locales/en.ts +31 -31
  125. package/src/recommender/admin/index.ts +1 -1
  126. package/src/recommender/admin/services/recommender.service.admin.ts +5 -5
  127. package/src/recommender/front/index.ts +1 -1
  128. package/src/recommender/front/services/onboarding.service.ts +5 -5
  129. package/src/recommender/front/services/recommender.service.ts +3 -3
  130. package/src/recommender/index.ts +1 -1
  131. package/src/services/PATIENTAUTH.MD +197 -197
  132. package/src/services/README.md +106 -106
  133. package/src/services/__tests__/auth/auth.mock.test.ts +17 -17
  134. package/src/services/__tests__/auth/auth.setup.ts +293 -293
  135. package/src/services/__tests__/auth.service.test.ts +346 -346
  136. package/src/services/__tests__/base.service.test.ts +77 -77
  137. package/src/services/__tests__/user.service.test.ts +528 -528
  138. package/src/services/analytics/ARCHITECTURE.md +199 -199
  139. package/src/services/analytics/CLOUD_FUNCTIONS.md +225 -225
  140. package/src/services/analytics/GROUPED_ANALYTICS.md +501 -501
  141. package/src/services/analytics/QUICK_START.md +393 -393
  142. package/src/services/analytics/README.md +304 -304
  143. package/src/services/analytics/SUMMARY.md +141 -141
  144. package/src/services/analytics/TRENDS.md +380 -380
  145. package/src/services/analytics/USAGE_GUIDE.md +518 -518
  146. package/src/services/analytics/analytics-cloud.service.ts +222 -222
  147. package/src/services/analytics/analytics.service.ts +2142 -2142
  148. package/src/services/analytics/index.ts +4 -4
  149. package/src/services/analytics/review-analytics.service.ts +941 -941
  150. package/src/services/analytics/utils/appointment-filtering.utils.ts +138 -138
  151. package/src/services/analytics/utils/cost-calculation.utils.ts +182 -182
  152. package/src/services/analytics/utils/grouping.utils.ts +434 -434
  153. package/src/services/analytics/utils/stored-analytics.utils.ts +347 -347
  154. package/src/services/analytics/utils/time-calculation.utils.ts +186 -186
  155. package/src/services/analytics/utils/trend-calculation.utils.ts +200 -200
  156. package/src/services/appointment/README.md +17 -17
  157. package/src/services/appointment/appointment.service.ts +2558 -2558
  158. package/src/services/appointment/index.ts +1 -1
  159. package/src/services/appointment/utils/appointment.utils.ts +552 -552
  160. package/src/services/appointment/utils/extended-procedure.utils.ts +314 -314
  161. package/src/services/appointment/utils/form-initialization.utils.ts +225 -225
  162. package/src/services/appointment/utils/recommended-procedure.utils.ts +195 -195
  163. package/src/services/appointment/utils/zone-management.utils.ts +353 -353
  164. package/src/services/appointment/utils/zone-photo.utils.ts +152 -152
  165. package/src/services/auth/auth.service.ts +1043 -989
  166. package/src/services/auth/auth.v2.service.ts +961 -961
  167. package/src/services/auth/index.ts +7 -7
  168. package/src/services/auth/utils/error.utils.ts +90 -90
  169. package/src/services/auth/utils/firebase.utils.ts +49 -49
  170. package/src/services/auth/utils/index.ts +21 -21
  171. package/src/services/auth/utils/practitioner.utils.ts +125 -125
  172. package/src/services/base.service.ts +41 -41
  173. package/src/services/calendar/calendar.service.ts +1077 -1077
  174. package/src/services/calendar/calendar.v2.service.ts +1683 -1683
  175. package/src/services/calendar/calendar.v3.service.ts +313 -313
  176. package/src/services/calendar/externalCalendar.service.ts +178 -178
  177. package/src/services/calendar/index.ts +5 -5
  178. package/src/services/calendar/synced-calendars.service.ts +743 -743
  179. package/src/services/calendar/utils/appointment.utils.ts +265 -265
  180. package/src/services/calendar/utils/calendar-event.utils.ts +646 -646
  181. package/src/services/calendar/utils/clinic.utils.ts +237 -237
  182. package/src/services/calendar/utils/docs.utils.ts +157 -157
  183. package/src/services/calendar/utils/google-calendar.utils.ts +697 -697
  184. package/src/services/calendar/utils/index.ts +8 -8
  185. package/src/services/calendar/utils/patient.utils.ts +198 -198
  186. package/src/services/calendar/utils/practitioner.utils.ts +221 -221
  187. package/src/services/calendar/utils/synced-calendar.utils.ts +472 -472
  188. package/src/services/clinic/README.md +204 -204
  189. package/src/services/clinic/__tests__/clinic-admin.service.test.ts +287 -287
  190. package/src/services/clinic/__tests__/clinic-group.service.test.ts +352 -352
  191. package/src/services/clinic/__tests__/clinic.service.test.ts +354 -354
  192. package/src/services/clinic/billing-transactions.service.ts +217 -217
  193. package/src/services/clinic/clinic-admin.service.ts +202 -202
  194. package/src/services/clinic/clinic-group.service.ts +310 -310
  195. package/src/services/clinic/clinic.service.ts +708 -708
  196. package/src/services/clinic/index.ts +5 -5
  197. package/src/services/clinic/practitioner-invite.service.ts +519 -519
  198. package/src/services/clinic/utils/admin.utils.ts +551 -551
  199. package/src/services/clinic/utils/clinic-group.utils.ts +646 -646
  200. package/src/services/clinic/utils/clinic.utils.ts +949 -949
  201. package/src/services/clinic/utils/filter.utils.d.ts +23 -23
  202. package/src/services/clinic/utils/filter.utils.ts +446 -446
  203. package/src/services/clinic/utils/index.ts +11 -11
  204. package/src/services/clinic/utils/photos.utils.ts +188 -188
  205. package/src/services/clinic/utils/search.utils.ts +84 -84
  206. package/src/services/clinic/utils/tag.utils.ts +124 -124
  207. package/src/services/documentation-templates/documentation-template.service.ts +537 -537
  208. package/src/services/documentation-templates/filled-document.service.ts +587 -587
  209. package/src/services/documentation-templates/index.ts +2 -2
  210. package/src/services/index.ts +14 -14
  211. package/src/services/media/index.ts +1 -1
  212. package/src/services/media/media.service.ts +418 -418
  213. package/src/services/notifications/__tests__/notification.service.test.ts +242 -242
  214. package/src/services/notifications/index.ts +1 -1
  215. package/src/services/notifications/notification.service.ts +215 -215
  216. package/src/services/patient/README.md +48 -48
  217. package/src/services/patient/To-Do.md +43 -43
  218. package/src/services/patient/__tests__/patient.service.test.ts +294 -294
  219. package/src/services/patient/index.ts +2 -2
  220. package/src/services/patient/patient.service.ts +883 -883
  221. package/src/services/patient/patientRequirements.service.ts +285 -285
  222. package/src/services/patient/utils/aesthetic-analysis.utils.ts +176 -176
  223. package/src/services/patient/utils/clinic.utils.ts +80 -80
  224. package/src/services/patient/utils/docs.utils.ts +142 -142
  225. package/src/services/patient/utils/index.ts +9 -9
  226. package/src/services/patient/utils/location.utils.ts +126 -126
  227. package/src/services/patient/utils/medical-stuff.utils.ts +143 -143
  228. package/src/services/patient/utils/medical.utils.ts +458 -458
  229. package/src/services/patient/utils/practitioner.utils.ts +260 -260
  230. package/src/services/patient/utils/profile.utils.ts +510 -510
  231. package/src/services/patient/utils/sensitive.utils.ts +260 -260
  232. package/src/services/patient/utils/token.utils.ts +211 -211
  233. package/src/services/practitioner/README.md +145 -145
  234. package/src/services/practitioner/index.ts +1 -1
  235. package/src/services/practitioner/practitioner.service.ts +1799 -1742
  236. package/src/services/procedure/README.md +163 -163
  237. package/src/services/procedure/index.ts +1 -1
  238. package/src/services/procedure/procedure.service.ts +2307 -2200
  239. package/src/services/reviews/index.ts +1 -1
  240. package/src/services/reviews/reviews.service.ts +734 -734
  241. package/src/services/user/index.ts +1 -1
  242. package/src/services/user/user.service.ts +489 -489
  243. package/src/services/user/user.v2.service.ts +466 -466
  244. package/src/types/analytics/analytics.types.ts +597 -597
  245. package/src/types/analytics/grouped-analytics.types.ts +173 -173
  246. package/src/types/analytics/index.ts +4 -4
  247. package/src/types/analytics/stored-analytics.types.ts +137 -137
  248. package/src/types/appointment/index.ts +480 -480
  249. package/src/types/calendar/index.ts +258 -258
  250. package/src/types/calendar/synced-calendar.types.ts +66 -66
  251. package/src/types/clinic/index.ts +498 -498
  252. package/src/types/clinic/practitioner-invite.types.ts +91 -91
  253. package/src/types/clinic/preferences.types.ts +159 -159
  254. package/src/types/clinic/to-do +3 -3
  255. package/src/types/documentation-templates/index.ts +308 -308
  256. package/src/types/index.ts +47 -47
  257. package/src/types/notifications/README.md +77 -77
  258. package/src/types/notifications/index.ts +286 -286
  259. package/src/types/patient/aesthetic-analysis.types.ts +66 -66
  260. package/src/types/patient/allergies.ts +58 -58
  261. package/src/types/patient/index.ts +275 -275
  262. package/src/types/patient/medical-info.types.ts +152 -152
  263. package/src/types/patient/patient-requirements.ts +92 -92
  264. package/src/types/patient/token.types.ts +61 -61
  265. package/src/types/practitioner/index.ts +206 -206
  266. package/src/types/procedure/index.ts +181 -181
  267. package/src/types/profile/index.ts +39 -39
  268. package/src/types/reviews/index.ts +132 -132
  269. package/src/types/tz-lookup.d.ts +4 -4
  270. package/src/types/user/index.ts +38 -38
  271. package/src/utils/TIMESTAMPS.md +176 -176
  272. package/src/utils/TimestampUtils.ts +241 -241
  273. package/src/utils/index.ts +1 -1
  274. package/src/validations/appointment.schema.ts +574 -574
  275. package/src/validations/calendar.schema.ts +225 -225
  276. package/src/validations/clinic.schema.ts +494 -494
  277. package/src/validations/common.schema.ts +25 -25
  278. package/src/validations/documentation-templates/index.ts +1 -1
  279. package/src/validations/documentation-templates/template.schema.ts +220 -220
  280. package/src/validations/documentation-templates.schema.ts +10 -10
  281. package/src/validations/index.ts +20 -20
  282. package/src/validations/media.schema.ts +10 -10
  283. package/src/validations/notification.schema.ts +90 -90
  284. package/src/validations/patient/aesthetic-analysis.schema.ts +55 -55
  285. package/src/validations/patient/medical-info.schema.ts +125 -125
  286. package/src/validations/patient/patient-requirements.schema.ts +84 -84
  287. package/src/validations/patient/token.schema.ts +29 -29
  288. package/src/validations/patient.schema.ts +217 -217
  289. package/src/validations/practitioner.schema.ts +222 -222
  290. package/src/validations/procedure-product.schema.ts +41 -41
  291. package/src/validations/procedure.schema.ts +124 -124
  292. package/src/validations/profile-info.schema.ts +41 -41
  293. package/src/validations/reviews.schema.ts +195 -195
  294. package/src/validations/schemas.ts +104 -104
  295. 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
+