@blackcode_sa/metaestetics-api 1.13.4 → 1.13.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/index.d.mts +15 -28
- package/dist/admin/index.d.ts +15 -28
- package/dist/index.d.mts +16 -29
- package/dist/index.d.ts +16 -29
- package/dist/index.js +1 -0
- package/dist/index.mjs +1 -0
- package/package.json +121 -119
- package/src/__mocks__/firstore.ts +10 -10
- package/src/admin/aggregation/README.md +79 -79
- package/src/admin/aggregation/appointment/README.md +128 -128
- package/src/admin/aggregation/appointment/appointment.aggregation.service.ts +1984 -1984
- package/src/admin/aggregation/appointment/index.ts +1 -1
- package/src/admin/aggregation/clinic/README.md +52 -52
- package/src/admin/aggregation/clinic/clinic.aggregation.service.ts +703 -703
- package/src/admin/aggregation/clinic/index.ts +1 -1
- package/src/admin/aggregation/forms/README.md +13 -13
- package/src/admin/aggregation/forms/filled-forms.aggregation.service.ts +322 -322
- package/src/admin/aggregation/forms/index.ts +1 -1
- package/src/admin/aggregation/index.ts +8 -8
- package/src/admin/aggregation/patient/README.md +27 -27
- package/src/admin/aggregation/patient/index.ts +1 -1
- package/src/admin/aggregation/patient/patient.aggregation.service.ts +141 -141
- package/src/admin/aggregation/practitioner/README.md +42 -42
- package/src/admin/aggregation/practitioner/index.ts +1 -1
- package/src/admin/aggregation/practitioner/practitioner.aggregation.service.ts +433 -433
- package/src/admin/aggregation/practitioner-invite/index.ts +1 -1
- package/src/admin/aggregation/practitioner-invite/practitioner-invite.aggregation.service.ts +961 -961
- package/src/admin/aggregation/procedure/README.md +43 -43
- package/src/admin/aggregation/procedure/index.ts +1 -1
- package/src/admin/aggregation/procedure/procedure.aggregation.service.ts +702 -702
- package/src/admin/aggregation/reviews/index.ts +1 -1
- package/src/admin/aggregation/reviews/reviews.aggregation.service.ts +689 -689
- package/src/admin/analytics/analytics.admin.service.ts +278 -278
- package/src/admin/analytics/index.ts +2 -2
- package/src/admin/booking/README.md +125 -125
- package/src/admin/booking/booking.admin.ts +1037 -1037
- package/src/admin/booking/booking.calculator.ts +712 -712
- package/src/admin/booking/booking.types.ts +59 -59
- package/src/admin/booking/index.ts +3 -3
- package/src/admin/booking/timezones-problem.md +185 -185
- package/src/admin/calendar/README.md +7 -7
- package/src/admin/calendar/calendar.admin.service.ts +345 -345
- package/src/admin/calendar/index.ts +1 -1
- package/src/admin/documentation-templates/document-manager.admin.ts +260 -260
- package/src/admin/documentation-templates/index.ts +1 -1
- package/src/admin/free-consultation/free-consultation-utils.admin.ts +148 -148
- package/src/admin/free-consultation/index.ts +1 -1
- package/src/admin/index.ts +81 -81
- package/src/admin/logger/index.ts +78 -78
- package/src/admin/mailing/README.md +95 -95
- package/src/admin/mailing/appointment/appointment.mailing.service.ts +732 -732
- package/src/admin/mailing/appointment/index.ts +1 -1
- package/src/admin/mailing/appointment/templates/patient/appointment-confirmed.html +40 -40
- package/src/admin/mailing/base.mailing.service.ts +208 -208
- package/src/admin/mailing/index.ts +3 -3
- package/src/admin/mailing/practitionerInvite/existing-practitioner-invite.mailing.ts +611 -611
- package/src/admin/mailing/practitionerInvite/index.ts +2 -2
- package/src/admin/mailing/practitionerInvite/practitionerInvite.mailing.ts +395 -395
- package/src/admin/mailing/practitionerInvite/templates/existing-practitioner-invitation.template.ts +155 -155
- package/src/admin/mailing/practitionerInvite/templates/invitation.template.ts +101 -101
- package/src/admin/mailing/practitionerInvite/templates/invite-accepted-notification.template.ts +228 -228
- package/src/admin/mailing/practitionerInvite/templates/invite-rejected-notification.template.ts +242 -242
- package/src/admin/notifications/index.ts +1 -1
- package/src/admin/notifications/notifications.admin.ts +710 -710
- package/src/admin/requirements/README.md +128 -128
- package/src/admin/requirements/index.ts +1 -1
- package/src/admin/requirements/patient-requirements.admin.service.ts +475 -475
- package/src/admin/users/index.ts +1 -1
- package/src/admin/users/user-profile.admin.ts +405 -405
- package/src/backoffice/constants/certification.constants.ts +13 -13
- package/src/backoffice/constants/index.ts +1 -1
- package/src/backoffice/errors/backoffice.errors.ts +181 -181
- package/src/backoffice/errors/index.ts +1 -1
- package/src/backoffice/expo-safe/README.md +26 -26
- package/src/backoffice/expo-safe/index.ts +41 -41
- package/src/backoffice/index.ts +5 -5
- package/src/backoffice/services/FIXES_README.md +102 -102
- package/src/backoffice/services/README.md +57 -57
- package/src/backoffice/services/analytics.service.proposal.md +863 -863
- package/src/backoffice/services/analytics.service.summary.md +143 -143
- package/src/backoffice/services/brand.service.ts +256 -256
- package/src/backoffice/services/category.service.ts +384 -384
- package/src/backoffice/services/constants.service.ts +385 -385
- package/src/backoffice/services/documentation-template.service.ts +202 -202
- package/src/backoffice/services/index.ts +10 -10
- package/src/backoffice/services/migrate-products.ts +116 -116
- package/src/backoffice/services/product.service.ts +553 -553
- package/src/backoffice/services/requirement.service.ts +235 -235
- package/src/backoffice/services/subcategory.service.ts +461 -461
- package/src/backoffice/services/technology.service.ts +1151 -1151
- package/src/backoffice/types/README.md +12 -12
- package/src/backoffice/types/admin-constants.types.ts +69 -69
- package/src/backoffice/types/brand.types.ts +29 -29
- package/src/backoffice/types/category.types.ts +67 -67
- package/src/backoffice/types/documentation-templates.types.ts +28 -28
- package/src/backoffice/types/index.ts +10 -10
- package/src/backoffice/types/procedure-product.types.ts +38 -38
- package/src/backoffice/types/product.types.ts +240 -240
- package/src/backoffice/types/requirement.types.ts +63 -63
- package/src/backoffice/types/static/README.md +18 -18
- package/src/backoffice/types/static/blocking-condition.types.ts +21 -21
- package/src/backoffice/types/static/certification.types.ts +37 -37
- package/src/backoffice/types/static/contraindication.types.ts +19 -19
- package/src/backoffice/types/static/index.ts +6 -6
- package/src/backoffice/types/static/pricing.types.ts +16 -16
- package/src/backoffice/types/static/procedure-family.types.ts +14 -14
- package/src/backoffice/types/static/treatment-benefit.types.ts +22 -22
- package/src/backoffice/types/subcategory.types.ts +34 -34
- package/src/backoffice/types/technology.types.ts +168 -168
- package/src/backoffice/validations/index.ts +1 -1
- package/src/backoffice/validations/schemas.ts +164 -164
- package/src/config/__mocks__/firebase.ts +99 -99
- package/src/config/firebase.ts +78 -78
- package/src/config/index.ts +9 -9
- package/src/errors/auth.error.ts +6 -6
- package/src/errors/auth.errors.ts +200 -200
- package/src/errors/clinic.errors.ts +32 -32
- package/src/errors/firebase.errors.ts +47 -47
- package/src/errors/user.errors.ts +99 -99
- package/src/index.backup.ts +407 -407
- package/src/index.ts +6 -6
- package/src/locales/en.ts +31 -31
- package/src/recommender/admin/index.ts +1 -1
- package/src/recommender/admin/services/recommender.service.admin.ts +5 -5
- package/src/recommender/front/index.ts +1 -1
- package/src/recommender/front/services/onboarding.service.ts +5 -5
- package/src/recommender/front/services/recommender.service.ts +3 -3
- package/src/recommender/index.ts +1 -1
- package/src/services/PATIENTAUTH.MD +197 -197
- package/src/services/README.md +106 -106
- package/src/services/__tests__/auth/auth.mock.test.ts +17 -17
- package/src/services/__tests__/auth/auth.setup.ts +293 -293
- package/src/services/__tests__/auth.service.test.ts +346 -346
- package/src/services/__tests__/base.service.test.ts +77 -77
- package/src/services/__tests__/user.service.test.ts +528 -528
- package/src/services/analytics/ARCHITECTURE.md +199 -199
- package/src/services/analytics/CLOUD_FUNCTIONS.md +225 -225
- package/src/services/analytics/GROUPED_ANALYTICS.md +501 -501
- package/src/services/analytics/QUICK_START.md +393 -393
- package/src/services/analytics/README.md +304 -304
- package/src/services/analytics/SUMMARY.md +141 -141
- package/src/services/analytics/TRENDS.md +380 -380
- package/src/services/analytics/USAGE_GUIDE.md +518 -518
- package/src/services/analytics/analytics-cloud.service.ts +222 -222
- package/src/services/analytics/analytics.service.ts +2142 -2142
- package/src/services/analytics/index.ts +4 -4
- package/src/services/analytics/review-analytics.service.ts +941 -941
- package/src/services/analytics/utils/appointment-filtering.utils.ts +138 -138
- package/src/services/analytics/utils/cost-calculation.utils.ts +182 -182
- package/src/services/analytics/utils/grouping.utils.ts +434 -434
- package/src/services/analytics/utils/stored-analytics.utils.ts +347 -347
- package/src/services/analytics/utils/time-calculation.utils.ts +186 -186
- package/src/services/analytics/utils/trend-calculation.utils.ts +200 -200
- package/src/services/appointment/README.md +17 -17
- package/src/services/appointment/appointment.service.ts +2558 -2558
- package/src/services/appointment/index.ts +1 -1
- package/src/services/appointment/utils/appointment.utils.ts +552 -552
- package/src/services/appointment/utils/extended-procedure.utils.ts +314 -314
- package/src/services/appointment/utils/form-initialization.utils.ts +225 -225
- package/src/services/appointment/utils/recommended-procedure.utils.ts +195 -195
- package/src/services/appointment/utils/zone-management.utils.ts +353 -353
- package/src/services/appointment/utils/zone-photo.utils.ts +152 -152
- package/src/services/auth/auth.service.ts +989 -989
- package/src/services/auth/auth.v2.service.ts +961 -961
- package/src/services/auth/index.ts +7 -7
- package/src/services/auth/utils/error.utils.ts +90 -90
- package/src/services/auth/utils/firebase.utils.ts +49 -49
- package/src/services/auth/utils/index.ts +21 -21
- package/src/services/auth/utils/practitioner.utils.ts +125 -125
- package/src/services/base.service.ts +41 -41
- package/src/services/calendar/calendar.service.ts +1077 -1077
- package/src/services/calendar/calendar.v2.service.ts +1683 -1683
- package/src/services/calendar/calendar.v3.service.ts +313 -313
- package/src/services/calendar/externalCalendar.service.ts +178 -178
- package/src/services/calendar/index.ts +5 -5
- package/src/services/calendar/synced-calendars.service.ts +743 -743
- package/src/services/calendar/utils/appointment.utils.ts +265 -265
- package/src/services/calendar/utils/calendar-event.utils.ts +646 -646
- package/src/services/calendar/utils/clinic.utils.ts +237 -237
- package/src/services/calendar/utils/docs.utils.ts +157 -157
- package/src/services/calendar/utils/google-calendar.utils.ts +697 -697
- package/src/services/calendar/utils/index.ts +8 -8
- package/src/services/calendar/utils/patient.utils.ts +198 -198
- package/src/services/calendar/utils/practitioner.utils.ts +221 -221
- package/src/services/calendar/utils/synced-calendar.utils.ts +472 -472
- package/src/services/clinic/README.md +204 -204
- package/src/services/clinic/__tests__/clinic-admin.service.test.ts +287 -287
- package/src/services/clinic/__tests__/clinic-group.service.test.ts +352 -352
- package/src/services/clinic/__tests__/clinic.service.test.ts +354 -354
- package/src/services/clinic/billing-transactions.service.ts +217 -217
- package/src/services/clinic/clinic-admin.service.ts +202 -202
- package/src/services/clinic/clinic-group.service.ts +310 -310
- package/src/services/clinic/clinic.service.ts +708 -708
- package/src/services/clinic/index.ts +5 -5
- package/src/services/clinic/practitioner-invite.service.ts +519 -519
- package/src/services/clinic/utils/admin.utils.ts +551 -551
- package/src/services/clinic/utils/clinic-group.utils.ts +646 -646
- package/src/services/clinic/utils/clinic.utils.ts +949 -949
- package/src/services/clinic/utils/filter.utils.d.ts +23 -23
- package/src/services/clinic/utils/filter.utils.ts +446 -446
- package/src/services/clinic/utils/index.ts +11 -11
- package/src/services/clinic/utils/photos.utils.ts +188 -188
- package/src/services/clinic/utils/search.utils.ts +84 -84
- package/src/services/clinic/utils/tag.utils.ts +124 -124
- package/src/services/documentation-templates/documentation-template.service.ts +537 -537
- package/src/services/documentation-templates/filled-document.service.ts +587 -587
- package/src/services/documentation-templates/index.ts +2 -2
- package/src/services/index.ts +14 -14
- package/src/services/media/index.ts +1 -1
- package/src/services/media/media.service.ts +418 -418
- package/src/services/notifications/__tests__/notification.service.test.ts +242 -242
- package/src/services/notifications/index.ts +1 -1
- package/src/services/notifications/notification.service.ts +215 -215
- package/src/services/patient/README.md +48 -48
- package/src/services/patient/To-Do.md +43 -43
- package/src/services/patient/__tests__/patient.service.test.ts +294 -294
- package/src/services/patient/index.ts +2 -2
- package/src/services/patient/patient.service.ts +883 -883
- package/src/services/patient/patientRequirements.service.ts +285 -285
- package/src/services/patient/utils/aesthetic-analysis.utils.ts +176 -176
- package/src/services/patient/utils/clinic.utils.ts +80 -80
- package/src/services/patient/utils/docs.utils.ts +142 -142
- package/src/services/patient/utils/index.ts +9 -9
- package/src/services/patient/utils/location.utils.ts +126 -126
- package/src/services/patient/utils/medical-stuff.utils.ts +143 -143
- package/src/services/patient/utils/medical.utils.ts +458 -458
- package/src/services/patient/utils/practitioner.utils.ts +260 -260
- package/src/services/patient/utils/profile.utils.ts +510 -510
- package/src/services/patient/utils/sensitive.utils.ts +260 -260
- package/src/services/patient/utils/token.utils.ts +211 -211
- package/src/services/practitioner/README.md +145 -145
- package/src/services/practitioner/index.ts +1 -1
- package/src/services/practitioner/practitioner.service.ts +1742 -1742
- package/src/services/procedure/README.md +163 -163
- package/src/services/procedure/index.ts +1 -1
- package/src/services/procedure/procedure.service.ts +2200 -2200
- package/src/services/reviews/index.ts +1 -1
- package/src/services/reviews/reviews.service.ts +734 -734
- package/src/services/user/index.ts +1 -1
- package/src/services/user/user.service.ts +489 -489
- package/src/services/user/user.v2.service.ts +466 -466
- package/src/types/analytics/analytics.types.ts +597 -597
- package/src/types/analytics/grouped-analytics.types.ts +173 -173
- package/src/types/analytics/index.ts +4 -4
- package/src/types/analytics/stored-analytics.types.ts +137 -137
- package/src/types/appointment/index.ts +480 -480
- package/src/types/calendar/index.ts +258 -258
- package/src/types/calendar/synced-calendar.types.ts +66 -66
- package/src/types/clinic/index.ts +498 -489
- package/src/types/clinic/practitioner-invite.types.ts +91 -91
- package/src/types/clinic/preferences.types.ts +159 -159
- package/src/types/clinic/to-do +3 -3
- package/src/types/documentation-templates/index.ts +308 -308
- package/src/types/index.ts +47 -47
- package/src/types/notifications/README.md +77 -77
- package/src/types/notifications/index.ts +286 -286
- package/src/types/patient/aesthetic-analysis.types.ts +66 -66
- package/src/types/patient/allergies.ts +58 -58
- package/src/types/patient/index.ts +275 -275
- package/src/types/patient/medical-info.types.ts +152 -152
- package/src/types/patient/patient-requirements.ts +92 -92
- package/src/types/patient/token.types.ts +61 -61
- package/src/types/practitioner/index.ts +206 -206
- package/src/types/procedure/index.ts +181 -181
- package/src/types/profile/index.ts +39 -39
- package/src/types/reviews/index.ts +132 -132
- package/src/types/tz-lookup.d.ts +4 -4
- package/src/types/user/index.ts +38 -38
- package/src/utils/TIMESTAMPS.md +176 -176
- package/src/utils/TimestampUtils.ts +241 -241
- package/src/utils/index.ts +1 -1
- package/src/validations/appointment.schema.ts +574 -574
- package/src/validations/calendar.schema.ts +225 -225
- package/src/validations/clinic.schema.ts +494 -493
- package/src/validations/common.schema.ts +25 -25
- package/src/validations/documentation-templates/index.ts +1 -1
- package/src/validations/documentation-templates/template.schema.ts +220 -220
- package/src/validations/documentation-templates.schema.ts +10 -10
- package/src/validations/index.ts +20 -20
- package/src/validations/media.schema.ts +10 -10
- package/src/validations/notification.schema.ts +90 -90
- package/src/validations/patient/aesthetic-analysis.schema.ts +55 -55
- package/src/validations/patient/medical-info.schema.ts +125 -125
- package/src/validations/patient/patient-requirements.schema.ts +84 -84
- package/src/validations/patient/token.schema.ts +29 -29
- package/src/validations/patient.schema.ts +217 -217
- package/src/validations/practitioner.schema.ts +222 -222
- package/src/validations/procedure-product.schema.ts +41 -41
- package/src/validations/procedure.schema.ts +124 -124
- package/src/validations/profile-info.schema.ts +41 -41
- package/src/validations/reviews.schema.ts +195 -195
- package/src/validations/schemas.ts +104 -104
- package/src/validations/shared.schema.ts +78 -78
|
@@ -1,347 +1,347 @@
|
|
|
1
|
-
import { Firestore, Timestamp, doc, getDoc } from 'firebase/firestore';
|
|
2
|
-
import {
|
|
3
|
-
ANALYTICS_COLLECTION,
|
|
4
|
-
PRACTITIONER_ANALYTICS_SUBCOLLECTION,
|
|
5
|
-
PROCEDURE_ANALYTICS_SUBCOLLECTION,
|
|
6
|
-
CLINIC_ANALYTICS_SUBCOLLECTION,
|
|
7
|
-
DASHBOARD_ANALYTICS_SUBCOLLECTION,
|
|
8
|
-
TIME_EFFICIENCY_ANALYTICS_SUBCOLLECTION,
|
|
9
|
-
CANCELLATION_ANALYTICS_SUBCOLLECTION,
|
|
10
|
-
NO_SHOW_ANALYTICS_SUBCOLLECTION,
|
|
11
|
-
REVENUE_ANALYTICS_SUBCOLLECTION,
|
|
12
|
-
AnalyticsPeriod,
|
|
13
|
-
ReadStoredAnalyticsOptions,
|
|
14
|
-
StoredPractitionerAnalytics,
|
|
15
|
-
StoredProcedureAnalytics,
|
|
16
|
-
StoredClinicAnalytics,
|
|
17
|
-
StoredDashboardAnalytics,
|
|
18
|
-
StoredTimeEfficiencyMetrics,
|
|
19
|
-
StoredCancellationMetrics,
|
|
20
|
-
StoredNoShowMetrics,
|
|
21
|
-
StoredRevenueMetrics,
|
|
22
|
-
} from '../../../types/analytics';
|
|
23
|
-
import { CLINICS_COLLECTION } from '../../../types/clinic';
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Checks if stored analytics data is fresh enough to use
|
|
27
|
-
*
|
|
28
|
-
* @param computedAt - Timestamp when analytics were computed
|
|
29
|
-
* @param maxAgeHours - Maximum age in hours
|
|
30
|
-
* @returns True if data is fresh enough
|
|
31
|
-
*/
|
|
32
|
-
export function isAnalyticsDataFresh(computedAt: Timestamp, maxAgeHours: number): boolean {
|
|
33
|
-
const now = Timestamp.now();
|
|
34
|
-
const ageMs = now.toMillis() - computedAt.toMillis();
|
|
35
|
-
const ageHours = ageMs / (1000 * 60 * 60);
|
|
36
|
-
return ageHours <= maxAgeHours;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Reads stored analytics document
|
|
41
|
-
*
|
|
42
|
-
* @param db - Firestore instance
|
|
43
|
-
* @param clinicBranchId - Clinic branch ID
|
|
44
|
-
* @param subcollection - Analytics subcollection name
|
|
45
|
-
* @param documentId - Document ID
|
|
46
|
-
* @param period - Analytics period
|
|
47
|
-
* @returns Stored analytics document or null
|
|
48
|
-
*/
|
|
49
|
-
async function readStoredAnalytics<T>(
|
|
50
|
-
db: Firestore,
|
|
51
|
-
clinicBranchId: string,
|
|
52
|
-
subcollection: string,
|
|
53
|
-
documentId: string,
|
|
54
|
-
period: AnalyticsPeriod,
|
|
55
|
-
): Promise<T | null> {
|
|
56
|
-
try {
|
|
57
|
-
const docRef = doc(
|
|
58
|
-
db,
|
|
59
|
-
CLINICS_COLLECTION,
|
|
60
|
-
clinicBranchId,
|
|
61
|
-
ANALYTICS_COLLECTION,
|
|
62
|
-
subcollection,
|
|
63
|
-
period,
|
|
64
|
-
documentId,
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
const docSnap = await getDoc(docRef);
|
|
68
|
-
if (!docSnap.exists()) {
|
|
69
|
-
return null;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return docSnap.data() as T;
|
|
73
|
-
} catch (error) {
|
|
74
|
-
console.error(`[StoredAnalytics] Error reading ${subcollection}/${period}/${documentId}:`, error);
|
|
75
|
-
return null;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Reads stored practitioner analytics
|
|
81
|
-
*/
|
|
82
|
-
export async function readStoredPractitionerAnalytics(
|
|
83
|
-
db: Firestore,
|
|
84
|
-
clinicBranchId: string,
|
|
85
|
-
practitionerId: string,
|
|
86
|
-
options: ReadStoredAnalyticsOptions = {},
|
|
87
|
-
): Promise<StoredPractitionerAnalytics | null> {
|
|
88
|
-
const { useCache = true, maxCacheAgeHours = 12, period = 'all_time' } = options;
|
|
89
|
-
|
|
90
|
-
if (!useCache) {
|
|
91
|
-
return null;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const stored = await readStoredAnalytics<StoredPractitionerAnalytics>(
|
|
95
|
-
db,
|
|
96
|
-
clinicBranchId,
|
|
97
|
-
PRACTITIONER_ANALYTICS_SUBCOLLECTION,
|
|
98
|
-
practitionerId,
|
|
99
|
-
period,
|
|
100
|
-
);
|
|
101
|
-
|
|
102
|
-
if (!stored) {
|
|
103
|
-
return null;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Check if data is fresh enough
|
|
107
|
-
if (!isAnalyticsDataFresh(stored.metadata.computedAt, maxCacheAgeHours)) {
|
|
108
|
-
return null;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return stored;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Reads stored procedure analytics
|
|
116
|
-
*/
|
|
117
|
-
export async function readStoredProcedureAnalytics(
|
|
118
|
-
db: Firestore,
|
|
119
|
-
clinicBranchId: string,
|
|
120
|
-
procedureId: string,
|
|
121
|
-
options: ReadStoredAnalyticsOptions = {},
|
|
122
|
-
): Promise<StoredProcedureAnalytics | null> {
|
|
123
|
-
const { useCache = true, maxCacheAgeHours = 12, period = 'all_time' } = options;
|
|
124
|
-
|
|
125
|
-
if (!useCache) {
|
|
126
|
-
return null;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const stored = await readStoredAnalytics<StoredProcedureAnalytics>(
|
|
130
|
-
db,
|
|
131
|
-
clinicBranchId,
|
|
132
|
-
PROCEDURE_ANALYTICS_SUBCOLLECTION,
|
|
133
|
-
procedureId,
|
|
134
|
-
period,
|
|
135
|
-
);
|
|
136
|
-
|
|
137
|
-
if (!stored) {
|
|
138
|
-
return null;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
if (!isAnalyticsDataFresh(stored.metadata.computedAt, maxCacheAgeHours)) {
|
|
142
|
-
return null;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return stored;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Reads stored clinic analytics
|
|
150
|
-
*/
|
|
151
|
-
export async function readStoredClinicAnalytics(
|
|
152
|
-
db: Firestore,
|
|
153
|
-
clinicBranchId: string,
|
|
154
|
-
options: ReadStoredAnalyticsOptions = {},
|
|
155
|
-
): Promise<StoredClinicAnalytics | null> {
|
|
156
|
-
const { useCache = true, maxCacheAgeHours = 12, period = 'all_time' } = options;
|
|
157
|
-
|
|
158
|
-
if (!useCache) {
|
|
159
|
-
return null;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const stored = await readStoredAnalytics<StoredClinicAnalytics>(
|
|
163
|
-
db,
|
|
164
|
-
clinicBranchId,
|
|
165
|
-
CLINIC_ANALYTICS_SUBCOLLECTION,
|
|
166
|
-
'current',
|
|
167
|
-
period,
|
|
168
|
-
);
|
|
169
|
-
|
|
170
|
-
if (!stored) {
|
|
171
|
-
return null;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if (!isAnalyticsDataFresh(stored.metadata.computedAt, maxCacheAgeHours)) {
|
|
175
|
-
return null;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
return stored;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Reads stored dashboard analytics
|
|
183
|
-
*/
|
|
184
|
-
export async function readStoredDashboardAnalytics(
|
|
185
|
-
db: Firestore,
|
|
186
|
-
clinicBranchId: string,
|
|
187
|
-
options: ReadStoredAnalyticsOptions = {},
|
|
188
|
-
): Promise<StoredDashboardAnalytics | null> {
|
|
189
|
-
const { useCache = true, maxCacheAgeHours = 12, period = 'all_time' } = options;
|
|
190
|
-
|
|
191
|
-
if (!useCache) {
|
|
192
|
-
return null;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
const stored = await readStoredAnalytics<StoredDashboardAnalytics>(
|
|
196
|
-
db,
|
|
197
|
-
clinicBranchId,
|
|
198
|
-
DASHBOARD_ANALYTICS_SUBCOLLECTION,
|
|
199
|
-
'current',
|
|
200
|
-
period,
|
|
201
|
-
);
|
|
202
|
-
|
|
203
|
-
if (!stored) {
|
|
204
|
-
return null;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
if (!isAnalyticsDataFresh(stored.metadata.computedAt, maxCacheAgeHours)) {
|
|
208
|
-
return null;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
return stored;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Reads stored time efficiency metrics
|
|
216
|
-
*/
|
|
217
|
-
export async function readStoredTimeEfficiencyMetrics(
|
|
218
|
-
db: Firestore,
|
|
219
|
-
clinicBranchId: string,
|
|
220
|
-
options: ReadStoredAnalyticsOptions = {},
|
|
221
|
-
): Promise<StoredTimeEfficiencyMetrics | null> {
|
|
222
|
-
const { useCache = true, maxCacheAgeHours = 12, period = 'all_time' } = options;
|
|
223
|
-
|
|
224
|
-
if (!useCache) {
|
|
225
|
-
return null;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
const stored = await readStoredAnalytics<StoredTimeEfficiencyMetrics>(
|
|
229
|
-
db,
|
|
230
|
-
clinicBranchId,
|
|
231
|
-
TIME_EFFICIENCY_ANALYTICS_SUBCOLLECTION,
|
|
232
|
-
'current',
|
|
233
|
-
period,
|
|
234
|
-
);
|
|
235
|
-
|
|
236
|
-
if (!stored) {
|
|
237
|
-
return null;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
if (!isAnalyticsDataFresh(stored.metadata.computedAt, maxCacheAgeHours)) {
|
|
241
|
-
return null;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
return stored;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* Reads stored revenue metrics
|
|
249
|
-
*/
|
|
250
|
-
export async function readStoredRevenueMetrics(
|
|
251
|
-
db: Firestore,
|
|
252
|
-
clinicBranchId: string,
|
|
253
|
-
options: ReadStoredAnalyticsOptions = {},
|
|
254
|
-
): Promise<StoredRevenueMetrics | null> {
|
|
255
|
-
const { useCache = true, maxCacheAgeHours = 12, period = 'all_time' } = options;
|
|
256
|
-
|
|
257
|
-
if (!useCache) {
|
|
258
|
-
return null;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
const stored = await readStoredAnalytics<StoredRevenueMetrics>(
|
|
262
|
-
db,
|
|
263
|
-
clinicBranchId,
|
|
264
|
-
REVENUE_ANALYTICS_SUBCOLLECTION,
|
|
265
|
-
'current',
|
|
266
|
-
period,
|
|
267
|
-
);
|
|
268
|
-
|
|
269
|
-
if (!stored) {
|
|
270
|
-
return null;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
if (!isAnalyticsDataFresh(stored.metadata.computedAt, maxCacheAgeHours)) {
|
|
274
|
-
return null;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
return stored;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* Reads stored cancellation metrics
|
|
282
|
-
*/
|
|
283
|
-
export async function readStoredCancellationMetrics(
|
|
284
|
-
db: Firestore,
|
|
285
|
-
clinicBranchId: string,
|
|
286
|
-
entityType: string,
|
|
287
|
-
options: ReadStoredAnalyticsOptions = {},
|
|
288
|
-
): Promise<StoredCancellationMetrics | null> {
|
|
289
|
-
const { useCache = true, maxCacheAgeHours = 12, period = 'all_time' } = options;
|
|
290
|
-
|
|
291
|
-
if (!useCache) {
|
|
292
|
-
return null;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
const stored = await readStoredAnalytics<StoredCancellationMetrics>(
|
|
296
|
-
db,
|
|
297
|
-
clinicBranchId,
|
|
298
|
-
CANCELLATION_ANALYTICS_SUBCOLLECTION,
|
|
299
|
-
entityType,
|
|
300
|
-
period,
|
|
301
|
-
);
|
|
302
|
-
|
|
303
|
-
if (!stored) {
|
|
304
|
-
return null;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
if (!isAnalyticsDataFresh(stored.metadata.computedAt, maxCacheAgeHours)) {
|
|
308
|
-
return null;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
return stored;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
/**
|
|
315
|
-
* Reads stored no-show metrics
|
|
316
|
-
*/
|
|
317
|
-
export async function readStoredNoShowMetrics(
|
|
318
|
-
db: Firestore,
|
|
319
|
-
clinicBranchId: string,
|
|
320
|
-
entityType: string,
|
|
321
|
-
options: ReadStoredAnalyticsOptions = {},
|
|
322
|
-
): Promise<StoredNoShowMetrics | null> {
|
|
323
|
-
const { useCache = true, maxCacheAgeHours = 12, period = 'all_time' } = options;
|
|
324
|
-
|
|
325
|
-
if (!useCache) {
|
|
326
|
-
return null;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
const stored = await readStoredAnalytics<StoredNoShowMetrics>(
|
|
330
|
-
db,
|
|
331
|
-
clinicBranchId,
|
|
332
|
-
NO_SHOW_ANALYTICS_SUBCOLLECTION,
|
|
333
|
-
entityType,
|
|
334
|
-
period,
|
|
335
|
-
);
|
|
336
|
-
|
|
337
|
-
if (!stored) {
|
|
338
|
-
return null;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
if (!isAnalyticsDataFresh(stored.metadata.computedAt, maxCacheAgeHours)) {
|
|
342
|
-
return null;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
return stored;
|
|
346
|
-
}
|
|
347
|
-
|
|
1
|
+
import { Firestore, Timestamp, doc, getDoc } from 'firebase/firestore';
|
|
2
|
+
import {
|
|
3
|
+
ANALYTICS_COLLECTION,
|
|
4
|
+
PRACTITIONER_ANALYTICS_SUBCOLLECTION,
|
|
5
|
+
PROCEDURE_ANALYTICS_SUBCOLLECTION,
|
|
6
|
+
CLINIC_ANALYTICS_SUBCOLLECTION,
|
|
7
|
+
DASHBOARD_ANALYTICS_SUBCOLLECTION,
|
|
8
|
+
TIME_EFFICIENCY_ANALYTICS_SUBCOLLECTION,
|
|
9
|
+
CANCELLATION_ANALYTICS_SUBCOLLECTION,
|
|
10
|
+
NO_SHOW_ANALYTICS_SUBCOLLECTION,
|
|
11
|
+
REVENUE_ANALYTICS_SUBCOLLECTION,
|
|
12
|
+
AnalyticsPeriod,
|
|
13
|
+
ReadStoredAnalyticsOptions,
|
|
14
|
+
StoredPractitionerAnalytics,
|
|
15
|
+
StoredProcedureAnalytics,
|
|
16
|
+
StoredClinicAnalytics,
|
|
17
|
+
StoredDashboardAnalytics,
|
|
18
|
+
StoredTimeEfficiencyMetrics,
|
|
19
|
+
StoredCancellationMetrics,
|
|
20
|
+
StoredNoShowMetrics,
|
|
21
|
+
StoredRevenueMetrics,
|
|
22
|
+
} from '../../../types/analytics';
|
|
23
|
+
import { CLINICS_COLLECTION } from '../../../types/clinic';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Checks if stored analytics data is fresh enough to use
|
|
27
|
+
*
|
|
28
|
+
* @param computedAt - Timestamp when analytics were computed
|
|
29
|
+
* @param maxAgeHours - Maximum age in hours
|
|
30
|
+
* @returns True if data is fresh enough
|
|
31
|
+
*/
|
|
32
|
+
export function isAnalyticsDataFresh(computedAt: Timestamp, maxAgeHours: number): boolean {
|
|
33
|
+
const now = Timestamp.now();
|
|
34
|
+
const ageMs = now.toMillis() - computedAt.toMillis();
|
|
35
|
+
const ageHours = ageMs / (1000 * 60 * 60);
|
|
36
|
+
return ageHours <= maxAgeHours;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Reads stored analytics document
|
|
41
|
+
*
|
|
42
|
+
* @param db - Firestore instance
|
|
43
|
+
* @param clinicBranchId - Clinic branch ID
|
|
44
|
+
* @param subcollection - Analytics subcollection name
|
|
45
|
+
* @param documentId - Document ID
|
|
46
|
+
* @param period - Analytics period
|
|
47
|
+
* @returns Stored analytics document or null
|
|
48
|
+
*/
|
|
49
|
+
async function readStoredAnalytics<T>(
|
|
50
|
+
db: Firestore,
|
|
51
|
+
clinicBranchId: string,
|
|
52
|
+
subcollection: string,
|
|
53
|
+
documentId: string,
|
|
54
|
+
period: AnalyticsPeriod,
|
|
55
|
+
): Promise<T | null> {
|
|
56
|
+
try {
|
|
57
|
+
const docRef = doc(
|
|
58
|
+
db,
|
|
59
|
+
CLINICS_COLLECTION,
|
|
60
|
+
clinicBranchId,
|
|
61
|
+
ANALYTICS_COLLECTION,
|
|
62
|
+
subcollection,
|
|
63
|
+
period,
|
|
64
|
+
documentId,
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const docSnap = await getDoc(docRef);
|
|
68
|
+
if (!docSnap.exists()) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return docSnap.data() as T;
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.error(`[StoredAnalytics] Error reading ${subcollection}/${period}/${documentId}:`, error);
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Reads stored practitioner analytics
|
|
81
|
+
*/
|
|
82
|
+
export async function readStoredPractitionerAnalytics(
|
|
83
|
+
db: Firestore,
|
|
84
|
+
clinicBranchId: string,
|
|
85
|
+
practitionerId: string,
|
|
86
|
+
options: ReadStoredAnalyticsOptions = {},
|
|
87
|
+
): Promise<StoredPractitionerAnalytics | null> {
|
|
88
|
+
const { useCache = true, maxCacheAgeHours = 12, period = 'all_time' } = options;
|
|
89
|
+
|
|
90
|
+
if (!useCache) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const stored = await readStoredAnalytics<StoredPractitionerAnalytics>(
|
|
95
|
+
db,
|
|
96
|
+
clinicBranchId,
|
|
97
|
+
PRACTITIONER_ANALYTICS_SUBCOLLECTION,
|
|
98
|
+
practitionerId,
|
|
99
|
+
period,
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
if (!stored) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Check if data is fresh enough
|
|
107
|
+
if (!isAnalyticsDataFresh(stored.metadata.computedAt, maxCacheAgeHours)) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return stored;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Reads stored procedure analytics
|
|
116
|
+
*/
|
|
117
|
+
export async function readStoredProcedureAnalytics(
|
|
118
|
+
db: Firestore,
|
|
119
|
+
clinicBranchId: string,
|
|
120
|
+
procedureId: string,
|
|
121
|
+
options: ReadStoredAnalyticsOptions = {},
|
|
122
|
+
): Promise<StoredProcedureAnalytics | null> {
|
|
123
|
+
const { useCache = true, maxCacheAgeHours = 12, period = 'all_time' } = options;
|
|
124
|
+
|
|
125
|
+
if (!useCache) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const stored = await readStoredAnalytics<StoredProcedureAnalytics>(
|
|
130
|
+
db,
|
|
131
|
+
clinicBranchId,
|
|
132
|
+
PROCEDURE_ANALYTICS_SUBCOLLECTION,
|
|
133
|
+
procedureId,
|
|
134
|
+
period,
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
if (!stored) {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (!isAnalyticsDataFresh(stored.metadata.computedAt, maxCacheAgeHours)) {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return stored;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Reads stored clinic analytics
|
|
150
|
+
*/
|
|
151
|
+
export async function readStoredClinicAnalytics(
|
|
152
|
+
db: Firestore,
|
|
153
|
+
clinicBranchId: string,
|
|
154
|
+
options: ReadStoredAnalyticsOptions = {},
|
|
155
|
+
): Promise<StoredClinicAnalytics | null> {
|
|
156
|
+
const { useCache = true, maxCacheAgeHours = 12, period = 'all_time' } = options;
|
|
157
|
+
|
|
158
|
+
if (!useCache) {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const stored = await readStoredAnalytics<StoredClinicAnalytics>(
|
|
163
|
+
db,
|
|
164
|
+
clinicBranchId,
|
|
165
|
+
CLINIC_ANALYTICS_SUBCOLLECTION,
|
|
166
|
+
'current',
|
|
167
|
+
period,
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
if (!stored) {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (!isAnalyticsDataFresh(stored.metadata.computedAt, maxCacheAgeHours)) {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return stored;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Reads stored dashboard analytics
|
|
183
|
+
*/
|
|
184
|
+
export async function readStoredDashboardAnalytics(
|
|
185
|
+
db: Firestore,
|
|
186
|
+
clinicBranchId: string,
|
|
187
|
+
options: ReadStoredAnalyticsOptions = {},
|
|
188
|
+
): Promise<StoredDashboardAnalytics | null> {
|
|
189
|
+
const { useCache = true, maxCacheAgeHours = 12, period = 'all_time' } = options;
|
|
190
|
+
|
|
191
|
+
if (!useCache) {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const stored = await readStoredAnalytics<StoredDashboardAnalytics>(
|
|
196
|
+
db,
|
|
197
|
+
clinicBranchId,
|
|
198
|
+
DASHBOARD_ANALYTICS_SUBCOLLECTION,
|
|
199
|
+
'current',
|
|
200
|
+
period,
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
if (!stored) {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (!isAnalyticsDataFresh(stored.metadata.computedAt, maxCacheAgeHours)) {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return stored;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Reads stored time efficiency metrics
|
|
216
|
+
*/
|
|
217
|
+
export async function readStoredTimeEfficiencyMetrics(
|
|
218
|
+
db: Firestore,
|
|
219
|
+
clinicBranchId: string,
|
|
220
|
+
options: ReadStoredAnalyticsOptions = {},
|
|
221
|
+
): Promise<StoredTimeEfficiencyMetrics | null> {
|
|
222
|
+
const { useCache = true, maxCacheAgeHours = 12, period = 'all_time' } = options;
|
|
223
|
+
|
|
224
|
+
if (!useCache) {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const stored = await readStoredAnalytics<StoredTimeEfficiencyMetrics>(
|
|
229
|
+
db,
|
|
230
|
+
clinicBranchId,
|
|
231
|
+
TIME_EFFICIENCY_ANALYTICS_SUBCOLLECTION,
|
|
232
|
+
'current',
|
|
233
|
+
period,
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
if (!stored) {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (!isAnalyticsDataFresh(stored.metadata.computedAt, maxCacheAgeHours)) {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return stored;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Reads stored revenue metrics
|
|
249
|
+
*/
|
|
250
|
+
export async function readStoredRevenueMetrics(
|
|
251
|
+
db: Firestore,
|
|
252
|
+
clinicBranchId: string,
|
|
253
|
+
options: ReadStoredAnalyticsOptions = {},
|
|
254
|
+
): Promise<StoredRevenueMetrics | null> {
|
|
255
|
+
const { useCache = true, maxCacheAgeHours = 12, period = 'all_time' } = options;
|
|
256
|
+
|
|
257
|
+
if (!useCache) {
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const stored = await readStoredAnalytics<StoredRevenueMetrics>(
|
|
262
|
+
db,
|
|
263
|
+
clinicBranchId,
|
|
264
|
+
REVENUE_ANALYTICS_SUBCOLLECTION,
|
|
265
|
+
'current',
|
|
266
|
+
period,
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
if (!stored) {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (!isAnalyticsDataFresh(stored.metadata.computedAt, maxCacheAgeHours)) {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return stored;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Reads stored cancellation metrics
|
|
282
|
+
*/
|
|
283
|
+
export async function readStoredCancellationMetrics(
|
|
284
|
+
db: Firestore,
|
|
285
|
+
clinicBranchId: string,
|
|
286
|
+
entityType: string,
|
|
287
|
+
options: ReadStoredAnalyticsOptions = {},
|
|
288
|
+
): Promise<StoredCancellationMetrics | null> {
|
|
289
|
+
const { useCache = true, maxCacheAgeHours = 12, period = 'all_time' } = options;
|
|
290
|
+
|
|
291
|
+
if (!useCache) {
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const stored = await readStoredAnalytics<StoredCancellationMetrics>(
|
|
296
|
+
db,
|
|
297
|
+
clinicBranchId,
|
|
298
|
+
CANCELLATION_ANALYTICS_SUBCOLLECTION,
|
|
299
|
+
entityType,
|
|
300
|
+
period,
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
if (!stored) {
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (!isAnalyticsDataFresh(stored.metadata.computedAt, maxCacheAgeHours)) {
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return stored;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Reads stored no-show metrics
|
|
316
|
+
*/
|
|
317
|
+
export async function readStoredNoShowMetrics(
|
|
318
|
+
db: Firestore,
|
|
319
|
+
clinicBranchId: string,
|
|
320
|
+
entityType: string,
|
|
321
|
+
options: ReadStoredAnalyticsOptions = {},
|
|
322
|
+
): Promise<StoredNoShowMetrics | null> {
|
|
323
|
+
const { useCache = true, maxCacheAgeHours = 12, period = 'all_time' } = options;
|
|
324
|
+
|
|
325
|
+
if (!useCache) {
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const stored = await readStoredAnalytics<StoredNoShowMetrics>(
|
|
330
|
+
db,
|
|
331
|
+
clinicBranchId,
|
|
332
|
+
NO_SHOW_ANALYTICS_SUBCOLLECTION,
|
|
333
|
+
entityType,
|
|
334
|
+
period,
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
if (!stored) {
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (!isAnalyticsDataFresh(stored.metadata.computedAt, maxCacheAgeHours)) {
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return stored;
|
|
346
|
+
}
|
|
347
|
+
|