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