@blackcode_sa/metaestetics-api 1.13.3 → 1.13.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/index.d.mts +15 -28
- package/dist/admin/index.d.ts +15 -28
- package/dist/index.d.mts +18 -30
- package/dist/index.d.ts +18 -30
- package/dist/index.js +11 -3
- package/dist/index.mjs +11 -3
- package/package.json +121 -119
- package/src/__mocks__/firstore.ts +10 -10
- package/src/admin/aggregation/README.md +79 -79
- package/src/admin/aggregation/appointment/README.md +128 -128
- package/src/admin/aggregation/appointment/appointment.aggregation.service.ts +1984 -1984
- package/src/admin/aggregation/appointment/index.ts +1 -1
- package/src/admin/aggregation/clinic/README.md +52 -52
- package/src/admin/aggregation/clinic/clinic.aggregation.service.ts +703 -703
- package/src/admin/aggregation/clinic/index.ts +1 -1
- package/src/admin/aggregation/forms/README.md +13 -13
- package/src/admin/aggregation/forms/filled-forms.aggregation.service.ts +322 -322
- package/src/admin/aggregation/forms/index.ts +1 -1
- package/src/admin/aggregation/index.ts +8 -8
- package/src/admin/aggregation/patient/README.md +27 -27
- package/src/admin/aggregation/patient/index.ts +1 -1
- package/src/admin/aggregation/patient/patient.aggregation.service.ts +141 -141
- package/src/admin/aggregation/practitioner/README.md +42 -42
- package/src/admin/aggregation/practitioner/index.ts +1 -1
- package/src/admin/aggregation/practitioner/practitioner.aggregation.service.ts +433 -433
- package/src/admin/aggregation/practitioner-invite/index.ts +1 -1
- package/src/admin/aggregation/practitioner-invite/practitioner-invite.aggregation.service.ts +961 -961
- package/src/admin/aggregation/procedure/README.md +43 -43
- package/src/admin/aggregation/procedure/index.ts +1 -1
- package/src/admin/aggregation/procedure/procedure.aggregation.service.ts +702 -702
- package/src/admin/aggregation/reviews/index.ts +1 -1
- package/src/admin/aggregation/reviews/reviews.aggregation.service.ts +689 -689
- package/src/admin/analytics/analytics.admin.service.ts +278 -278
- package/src/admin/analytics/index.ts +2 -2
- package/src/admin/booking/README.md +125 -125
- package/src/admin/booking/booking.admin.ts +1037 -1037
- package/src/admin/booking/booking.calculator.ts +712 -712
- package/src/admin/booking/booking.types.ts +59 -59
- package/src/admin/booking/index.ts +3 -3
- package/src/admin/booking/timezones-problem.md +185 -185
- package/src/admin/calendar/README.md +7 -7
- package/src/admin/calendar/calendar.admin.service.ts +345 -345
- package/src/admin/calendar/index.ts +1 -1
- package/src/admin/documentation-templates/document-manager.admin.ts +260 -260
- package/src/admin/documentation-templates/index.ts +1 -1
- package/src/admin/free-consultation/free-consultation-utils.admin.ts +148 -148
- package/src/admin/free-consultation/index.ts +1 -1
- package/src/admin/index.ts +81 -81
- package/src/admin/logger/index.ts +78 -78
- package/src/admin/mailing/README.md +95 -95
- package/src/admin/mailing/appointment/appointment.mailing.service.ts +732 -732
- package/src/admin/mailing/appointment/index.ts +1 -1
- package/src/admin/mailing/appointment/templates/patient/appointment-confirmed.html +40 -40
- package/src/admin/mailing/base.mailing.service.ts +208 -208
- package/src/admin/mailing/index.ts +3 -3
- package/src/admin/mailing/practitionerInvite/existing-practitioner-invite.mailing.ts +611 -611
- package/src/admin/mailing/practitionerInvite/index.ts +2 -2
- package/src/admin/mailing/practitionerInvite/practitionerInvite.mailing.ts +395 -395
- package/src/admin/mailing/practitionerInvite/templates/existing-practitioner-invitation.template.ts +155 -155
- package/src/admin/mailing/practitionerInvite/templates/invitation.template.ts +101 -101
- package/src/admin/mailing/practitionerInvite/templates/invite-accepted-notification.template.ts +228 -228
- package/src/admin/mailing/practitionerInvite/templates/invite-rejected-notification.template.ts +242 -242
- package/src/admin/notifications/index.ts +1 -1
- package/src/admin/notifications/notifications.admin.ts +710 -710
- package/src/admin/requirements/README.md +128 -128
- package/src/admin/requirements/index.ts +1 -1
- package/src/admin/requirements/patient-requirements.admin.service.ts +475 -475
- package/src/admin/users/index.ts +1 -1
- package/src/admin/users/user-profile.admin.ts +405 -405
- package/src/backoffice/constants/certification.constants.ts +13 -13
- package/src/backoffice/constants/index.ts +1 -1
- package/src/backoffice/errors/backoffice.errors.ts +181 -181
- package/src/backoffice/errors/index.ts +1 -1
- package/src/backoffice/expo-safe/README.md +26 -26
- package/src/backoffice/expo-safe/index.ts +41 -41
- package/src/backoffice/index.ts +5 -5
- package/src/backoffice/services/FIXES_README.md +102 -102
- package/src/backoffice/services/README.md +57 -57
- package/src/backoffice/services/analytics.service.proposal.md +863 -863
- package/src/backoffice/services/analytics.service.summary.md +143 -143
- package/src/backoffice/services/brand.service.ts +256 -256
- package/src/backoffice/services/category.service.ts +384 -384
- package/src/backoffice/services/constants.service.ts +385 -385
- package/src/backoffice/services/documentation-template.service.ts +202 -202
- package/src/backoffice/services/index.ts +10 -10
- package/src/backoffice/services/migrate-products.ts +116 -116
- package/src/backoffice/services/product.service.ts +553 -553
- package/src/backoffice/services/requirement.service.ts +235 -235
- package/src/backoffice/services/subcategory.service.ts +461 -461
- package/src/backoffice/services/technology.service.ts +1151 -1151
- package/src/backoffice/types/README.md +12 -12
- package/src/backoffice/types/admin-constants.types.ts +69 -69
- package/src/backoffice/types/brand.types.ts +29 -29
- package/src/backoffice/types/category.types.ts +67 -67
- package/src/backoffice/types/documentation-templates.types.ts +28 -28
- package/src/backoffice/types/index.ts +10 -10
- package/src/backoffice/types/procedure-product.types.ts +38 -38
- package/src/backoffice/types/product.types.ts +240 -240
- package/src/backoffice/types/requirement.types.ts +63 -63
- package/src/backoffice/types/static/README.md +18 -18
- package/src/backoffice/types/static/blocking-condition.types.ts +21 -21
- package/src/backoffice/types/static/certification.types.ts +37 -37
- package/src/backoffice/types/static/contraindication.types.ts +19 -19
- package/src/backoffice/types/static/index.ts +6 -6
- package/src/backoffice/types/static/pricing.types.ts +16 -16
- package/src/backoffice/types/static/procedure-family.types.ts +14 -14
- package/src/backoffice/types/static/treatment-benefit.types.ts +22 -22
- package/src/backoffice/types/subcategory.types.ts +34 -34
- package/src/backoffice/types/technology.types.ts +168 -168
- package/src/backoffice/validations/index.ts +1 -1
- package/src/backoffice/validations/schemas.ts +164 -164
- package/src/config/__mocks__/firebase.ts +99 -99
- package/src/config/firebase.ts +78 -78
- package/src/config/index.ts +9 -9
- package/src/errors/auth.error.ts +6 -6
- package/src/errors/auth.errors.ts +200 -200
- package/src/errors/clinic.errors.ts +32 -32
- package/src/errors/firebase.errors.ts +47 -47
- package/src/errors/user.errors.ts +99 -99
- package/src/index.backup.ts +407 -407
- package/src/index.ts +6 -6
- package/src/locales/en.ts +31 -31
- package/src/recommender/admin/index.ts +1 -1
- package/src/recommender/admin/services/recommender.service.admin.ts +5 -5
- package/src/recommender/front/index.ts +1 -1
- package/src/recommender/front/services/onboarding.service.ts +5 -5
- package/src/recommender/front/services/recommender.service.ts +3 -3
- package/src/recommender/index.ts +1 -1
- package/src/services/PATIENTAUTH.MD +197 -197
- package/src/services/README.md +106 -106
- package/src/services/__tests__/auth/auth.mock.test.ts +17 -17
- package/src/services/__tests__/auth/auth.setup.ts +293 -293
- package/src/services/__tests__/auth.service.test.ts +346 -346
- package/src/services/__tests__/base.service.test.ts +77 -77
- package/src/services/__tests__/user.service.test.ts +528 -528
- package/src/services/analytics/ARCHITECTURE.md +199 -199
- package/src/services/analytics/CLOUD_FUNCTIONS.md +225 -225
- package/src/services/analytics/GROUPED_ANALYTICS.md +501 -501
- package/src/services/analytics/QUICK_START.md +393 -393
- package/src/services/analytics/README.md +304 -304
- package/src/services/analytics/SUMMARY.md +141 -141
- package/src/services/analytics/TRENDS.md +380 -380
- package/src/services/analytics/USAGE_GUIDE.md +518 -518
- package/src/services/analytics/analytics-cloud.service.ts +222 -222
- package/src/services/analytics/analytics.service.ts +2142 -2142
- package/src/services/analytics/index.ts +4 -4
- package/src/services/analytics/review-analytics.service.ts +941 -941
- package/src/services/analytics/utils/appointment-filtering.utils.ts +138 -138
- package/src/services/analytics/utils/cost-calculation.utils.ts +182 -182
- package/src/services/analytics/utils/grouping.utils.ts +434 -434
- package/src/services/analytics/utils/stored-analytics.utils.ts +347 -347
- package/src/services/analytics/utils/time-calculation.utils.ts +186 -186
- package/src/services/analytics/utils/trend-calculation.utils.ts +200 -200
- package/src/services/appointment/README.md +17 -17
- package/src/services/appointment/appointment.service.ts +2558 -2558
- package/src/services/appointment/index.ts +1 -1
- package/src/services/appointment/utils/appointment.utils.ts +552 -552
- package/src/services/appointment/utils/extended-procedure.utils.ts +314 -314
- package/src/services/appointment/utils/form-initialization.utils.ts +225 -225
- package/src/services/appointment/utils/recommended-procedure.utils.ts +195 -195
- package/src/services/appointment/utils/zone-management.utils.ts +353 -353
- package/src/services/appointment/utils/zone-photo.utils.ts +152 -152
- package/src/services/auth/auth.service.ts +989 -989
- package/src/services/auth/auth.v2.service.ts +961 -961
- package/src/services/auth/index.ts +7 -7
- package/src/services/auth/utils/error.utils.ts +90 -90
- package/src/services/auth/utils/firebase.utils.ts +49 -49
- package/src/services/auth/utils/index.ts +21 -21
- package/src/services/auth/utils/practitioner.utils.ts +125 -125
- package/src/services/base.service.ts +41 -41
- package/src/services/calendar/calendar.service.ts +1077 -1077
- package/src/services/calendar/calendar.v2.service.ts +1683 -1683
- package/src/services/calendar/calendar.v3.service.ts +313 -313
- package/src/services/calendar/externalCalendar.service.ts +178 -178
- package/src/services/calendar/index.ts +5 -5
- package/src/services/calendar/synced-calendars.service.ts +743 -743
- package/src/services/calendar/utils/appointment.utils.ts +265 -265
- package/src/services/calendar/utils/calendar-event.utils.ts +646 -646
- package/src/services/calendar/utils/clinic.utils.ts +237 -237
- package/src/services/calendar/utils/docs.utils.ts +157 -157
- package/src/services/calendar/utils/google-calendar.utils.ts +697 -697
- package/src/services/calendar/utils/index.ts +8 -8
- package/src/services/calendar/utils/patient.utils.ts +198 -198
- package/src/services/calendar/utils/practitioner.utils.ts +221 -221
- package/src/services/calendar/utils/synced-calendar.utils.ts +472 -472
- package/src/services/clinic/README.md +204 -204
- package/src/services/clinic/__tests__/clinic-admin.service.test.ts +287 -287
- package/src/services/clinic/__tests__/clinic-group.service.test.ts +352 -352
- package/src/services/clinic/__tests__/clinic.service.test.ts +354 -354
- package/src/services/clinic/billing-transactions.service.ts +217 -217
- package/src/services/clinic/clinic-admin.service.ts +202 -202
- package/src/services/clinic/clinic-group.service.ts +310 -310
- package/src/services/clinic/clinic.service.ts +708 -708
- package/src/services/clinic/index.ts +5 -5
- package/src/services/clinic/practitioner-invite.service.ts +519 -519
- package/src/services/clinic/utils/admin.utils.ts +551 -551
- package/src/services/clinic/utils/clinic-group.utils.ts +646 -646
- package/src/services/clinic/utils/clinic.utils.ts +949 -949
- package/src/services/clinic/utils/filter.utils.d.ts +23 -23
- package/src/services/clinic/utils/filter.utils.ts +446 -446
- package/src/services/clinic/utils/index.ts +11 -11
- package/src/services/clinic/utils/photos.utils.ts +188 -188
- package/src/services/clinic/utils/search.utils.ts +84 -84
- package/src/services/clinic/utils/tag.utils.ts +124 -124
- package/src/services/documentation-templates/documentation-template.service.ts +537 -537
- package/src/services/documentation-templates/filled-document.service.ts +587 -587
- package/src/services/documentation-templates/index.ts +2 -2
- package/src/services/index.ts +14 -14
- package/src/services/media/index.ts +1 -1
- package/src/services/media/media.service.ts +418 -418
- package/src/services/notifications/__tests__/notification.service.test.ts +242 -242
- package/src/services/notifications/index.ts +1 -1
- package/src/services/notifications/notification.service.ts +215 -215
- package/src/services/patient/README.md +48 -48
- package/src/services/patient/To-Do.md +43 -43
- package/src/services/patient/__tests__/patient.service.test.ts +294 -294
- package/src/services/patient/index.ts +2 -2
- package/src/services/patient/patient.service.ts +883 -883
- package/src/services/patient/patientRequirements.service.ts +285 -285
- package/src/services/patient/utils/aesthetic-analysis.utils.ts +176 -176
- package/src/services/patient/utils/clinic.utils.ts +80 -80
- package/src/services/patient/utils/docs.utils.ts +142 -142
- package/src/services/patient/utils/index.ts +9 -9
- package/src/services/patient/utils/location.utils.ts +126 -126
- package/src/services/patient/utils/medical-stuff.utils.ts +143 -143
- package/src/services/patient/utils/medical.utils.ts +458 -458
- package/src/services/patient/utils/practitioner.utils.ts +260 -260
- package/src/services/patient/utils/profile.utils.ts +510 -510
- package/src/services/patient/utils/sensitive.utils.ts +260 -260
- package/src/services/patient/utils/token.utils.ts +211 -211
- package/src/services/practitioner/README.md +145 -145
- package/src/services/practitioner/index.ts +1 -1
- package/src/services/practitioner/practitioner.service.ts +1742 -1742
- package/src/services/procedure/README.md +163 -163
- package/src/services/procedure/index.ts +1 -1
- package/src/services/procedure/procedure.service.ts +2200 -2191
- package/src/services/reviews/index.ts +1 -1
- package/src/services/reviews/reviews.service.ts +734 -734
- package/src/services/user/index.ts +1 -1
- package/src/services/user/user.service.ts +489 -489
- package/src/services/user/user.v2.service.ts +466 -466
- package/src/types/analytics/analytics.types.ts +597 -597
- package/src/types/analytics/grouped-analytics.types.ts +173 -173
- package/src/types/analytics/index.ts +4 -4
- package/src/types/analytics/stored-analytics.types.ts +137 -137
- package/src/types/appointment/index.ts +480 -480
- package/src/types/calendar/index.ts +258 -258
- package/src/types/calendar/synced-calendar.types.ts +66 -66
- package/src/types/clinic/index.ts +498 -489
- package/src/types/clinic/practitioner-invite.types.ts +91 -91
- package/src/types/clinic/preferences.types.ts +159 -159
- package/src/types/clinic/to-do +3 -3
- package/src/types/documentation-templates/index.ts +308 -308
- package/src/types/index.ts +47 -47
- package/src/types/notifications/README.md +77 -77
- package/src/types/notifications/index.ts +286 -286
- package/src/types/patient/aesthetic-analysis.types.ts +66 -66
- package/src/types/patient/allergies.ts +58 -58
- package/src/types/patient/index.ts +275 -275
- package/src/types/patient/medical-info.types.ts +152 -152
- package/src/types/patient/patient-requirements.ts +92 -92
- package/src/types/patient/token.types.ts +61 -61
- package/src/types/practitioner/index.ts +206 -206
- package/src/types/procedure/index.ts +181 -181
- package/src/types/profile/index.ts +39 -39
- package/src/types/reviews/index.ts +132 -132
- package/src/types/tz-lookup.d.ts +4 -4
- package/src/types/user/index.ts +38 -38
- package/src/utils/TIMESTAMPS.md +176 -176
- package/src/utils/TimestampUtils.ts +241 -241
- package/src/utils/index.ts +1 -1
- package/src/validations/appointment.schema.ts +574 -574
- package/src/validations/calendar.schema.ts +225 -225
- package/src/validations/clinic.schema.ts +494 -493
- package/src/validations/common.schema.ts +25 -25
- package/src/validations/documentation-templates/index.ts +1 -1
- package/src/validations/documentation-templates/template.schema.ts +220 -220
- package/src/validations/documentation-templates.schema.ts +10 -10
- package/src/validations/index.ts +20 -20
- package/src/validations/media.schema.ts +10 -10
- package/src/validations/notification.schema.ts +90 -90
- package/src/validations/patient/aesthetic-analysis.schema.ts +55 -55
- package/src/validations/patient/medical-info.schema.ts +125 -125
- package/src/validations/patient/patient-requirements.schema.ts +84 -84
- package/src/validations/patient/token.schema.ts +29 -29
- package/src/validations/patient.schema.ts +217 -217
- package/src/validations/practitioner.schema.ts +222 -222
- package/src/validations/procedure-product.schema.ts +41 -41
- package/src/validations/procedure.schema.ts +124 -124
- package/src/validations/profile-info.schema.ts +41 -41
- package/src/validations/reviews.schema.ts +195 -195
- package/src/validations/schemas.ts +104 -104
- package/src/validations/shared.schema.ts +78 -78
|
@@ -1,186 +1,186 @@
|
|
|
1
|
-
import { Appointment, AppointmentStatus } from '../../../types/appointment';
|
|
2
|
-
import { Timestamp } from 'firebase/firestore';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Calculates time efficiency metrics for an appointment
|
|
6
|
-
*
|
|
7
|
-
* @param appointment - The appointment to calculate metrics for
|
|
8
|
-
* @returns Time efficiency data or null if insufficient data
|
|
9
|
-
*/
|
|
10
|
-
export function calculateTimeEfficiency(appointment: Appointment): {
|
|
11
|
-
bookedDuration: number; // minutes
|
|
12
|
-
actualDuration: number; // minutes
|
|
13
|
-
efficiency: number; // percentage
|
|
14
|
-
overrun: number; // minutes (positive if actual > booked)
|
|
15
|
-
underutilization: number; // minutes (positive if booked > actual)
|
|
16
|
-
} | null {
|
|
17
|
-
const startTime = appointment.appointmentStartTime;
|
|
18
|
-
const endTime = appointment.appointmentEndTime;
|
|
19
|
-
|
|
20
|
-
if (!startTime || !endTime) {
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// Calculate booked duration in minutes
|
|
25
|
-
const bookedDurationMs = endTime.toMillis() - startTime.toMillis();
|
|
26
|
-
const bookedDuration = Math.round(bookedDurationMs / (1000 * 60));
|
|
27
|
-
|
|
28
|
-
// Use actual duration if available, otherwise use booked duration
|
|
29
|
-
const actualDuration = appointment.actualDurationMinutes || bookedDuration;
|
|
30
|
-
|
|
31
|
-
// Calculate efficiency percentage
|
|
32
|
-
const efficiency = bookedDuration > 0 ? (actualDuration / bookedDuration) * 100 : 100;
|
|
33
|
-
|
|
34
|
-
// Calculate overrun (positive if actual > booked)
|
|
35
|
-
const overrun = actualDuration > bookedDuration ? actualDuration - bookedDuration : 0;
|
|
36
|
-
|
|
37
|
-
// Calculate underutilization (positive if booked > actual)
|
|
38
|
-
const underutilization = bookedDuration > actualDuration ? bookedDuration - actualDuration : 0;
|
|
39
|
-
|
|
40
|
-
return {
|
|
41
|
-
bookedDuration,
|
|
42
|
-
actualDuration,
|
|
43
|
-
efficiency,
|
|
44
|
-
overrun,
|
|
45
|
-
underutilization,
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Calculates average time metrics from an array of appointments
|
|
51
|
-
*
|
|
52
|
-
* @param appointments - Array of appointments
|
|
53
|
-
* @returns Average time metrics
|
|
54
|
-
*/
|
|
55
|
-
export function calculateAverageTimeMetrics(appointments: Appointment[]): {
|
|
56
|
-
averageBookedDuration: number;
|
|
57
|
-
averageActualDuration: number;
|
|
58
|
-
averageEfficiency: number;
|
|
59
|
-
totalOverrun: number;
|
|
60
|
-
totalUnderutilization: number;
|
|
61
|
-
averageOverrun: number;
|
|
62
|
-
averageUnderutilization: number;
|
|
63
|
-
appointmentsWithActualTime: number;
|
|
64
|
-
} {
|
|
65
|
-
if (appointments.length === 0) {
|
|
66
|
-
return {
|
|
67
|
-
averageBookedDuration: 0,
|
|
68
|
-
averageActualDuration: 0,
|
|
69
|
-
averageEfficiency: 0,
|
|
70
|
-
totalOverrun: 0,
|
|
71
|
-
totalUnderutilization: 0,
|
|
72
|
-
averageOverrun: 0,
|
|
73
|
-
averageUnderutilization: 0,
|
|
74
|
-
appointmentsWithActualTime: 0,
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
let totalBookedDuration = 0;
|
|
79
|
-
let totalActualDuration = 0;
|
|
80
|
-
let totalOverrun = 0;
|
|
81
|
-
let totalUnderutilization = 0;
|
|
82
|
-
let appointmentsWithActualTime = 0;
|
|
83
|
-
|
|
84
|
-
appointments.forEach(appointment => {
|
|
85
|
-
const timeData = calculateTimeEfficiency(appointment);
|
|
86
|
-
if (timeData) {
|
|
87
|
-
totalBookedDuration += timeData.bookedDuration;
|
|
88
|
-
totalActualDuration += timeData.actualDuration;
|
|
89
|
-
totalOverrun += timeData.overrun;
|
|
90
|
-
totalUnderutilization += timeData.underutilization;
|
|
91
|
-
|
|
92
|
-
if (appointment.actualDurationMinutes !== undefined) {
|
|
93
|
-
appointmentsWithActualTime++;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
const count = appointments.length;
|
|
99
|
-
const averageBookedDuration = count > 0 ? totalBookedDuration / count : 0;
|
|
100
|
-
const averageActualDuration = count > 0 ? totalActualDuration / count : 0;
|
|
101
|
-
const averageEfficiency = averageBookedDuration > 0 ? (averageActualDuration / averageBookedDuration) * 100 : 0;
|
|
102
|
-
const averageOverrun = count > 0 ? totalOverrun / count : 0;
|
|
103
|
-
const averageUnderutilization = count > 0 ? totalUnderutilization / count : 0;
|
|
104
|
-
|
|
105
|
-
return {
|
|
106
|
-
averageBookedDuration: Math.round(averageBookedDuration),
|
|
107
|
-
averageActualDuration: Math.round(averageActualDuration),
|
|
108
|
-
averageEfficiency: Math.round(averageEfficiency * 100) / 100,
|
|
109
|
-
totalOverrun,
|
|
110
|
-
totalUnderutilization,
|
|
111
|
-
averageOverrun: Math.round(averageOverrun),
|
|
112
|
-
averageUnderutilization: Math.round(averageUnderutilization),
|
|
113
|
-
appointmentsWithActualTime,
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Groups appointments by efficiency ranges
|
|
119
|
-
*
|
|
120
|
-
* @param appointments - Array of appointments
|
|
121
|
-
* @returns Distribution of appointments by efficiency range
|
|
122
|
-
*/
|
|
123
|
-
export function calculateEfficiencyDistribution(appointments: Appointment[]): Array<{
|
|
124
|
-
range: string;
|
|
125
|
-
count: number;
|
|
126
|
-
percentage: number;
|
|
127
|
-
}> {
|
|
128
|
-
const ranges = [
|
|
129
|
-
{ label: '0-50%', min: 0, max: 50 },
|
|
130
|
-
{ label: '50-75%', min: 50, max: 75 },
|
|
131
|
-
{ label: '75-100%', min: 75, max: 100 },
|
|
132
|
-
{ label: '100%+', min: 100, max: Infinity },
|
|
133
|
-
];
|
|
134
|
-
|
|
135
|
-
const distribution = ranges.map(range => ({
|
|
136
|
-
range: range.label,
|
|
137
|
-
count: 0,
|
|
138
|
-
percentage: 0,
|
|
139
|
-
}));
|
|
140
|
-
|
|
141
|
-
let validCount = 0;
|
|
142
|
-
|
|
143
|
-
appointments.forEach(appointment => {
|
|
144
|
-
const timeData = calculateTimeEfficiency(appointment);
|
|
145
|
-
if (timeData) {
|
|
146
|
-
validCount++;
|
|
147
|
-
const efficiency = timeData.efficiency;
|
|
148
|
-
|
|
149
|
-
for (let i = 0; i < ranges.length; i++) {
|
|
150
|
-
if (efficiency >= ranges[i].min && efficiency < ranges[i].max) {
|
|
151
|
-
distribution[i].count++;
|
|
152
|
-
break;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
// Calculate percentages
|
|
159
|
-
if (validCount > 0) {
|
|
160
|
-
distribution.forEach(item => {
|
|
161
|
-
item.percentage = Math.round((item.count / validCount) * 100 * 100) / 100;
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return distribution;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Calculates cancellation lead time in hours
|
|
170
|
-
*
|
|
171
|
-
* @param appointment - The appointment to calculate lead time for
|
|
172
|
-
* @returns Lead time in hours, or null if insufficient data
|
|
173
|
-
*/
|
|
174
|
-
export function calculateCancellationLeadTime(appointment: Appointment): number | null {
|
|
175
|
-
if (!appointment.cancellationTime || !appointment.appointmentStartTime) {
|
|
176
|
-
return null;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
const cancellationTime = appointment.cancellationTime.toMillis();
|
|
180
|
-
const appointmentTime = appointment.appointmentStartTime.toMillis();
|
|
181
|
-
const diffMs = appointmentTime - cancellationTime;
|
|
182
|
-
|
|
183
|
-
// Return positive hours (time before appointment)
|
|
184
|
-
return Math.max(0, diffMs / (1000 * 60 * 60));
|
|
185
|
-
}
|
|
186
|
-
|
|
1
|
+
import { Appointment, AppointmentStatus } from '../../../types/appointment';
|
|
2
|
+
import { Timestamp } from 'firebase/firestore';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Calculates time efficiency metrics for an appointment
|
|
6
|
+
*
|
|
7
|
+
* @param appointment - The appointment to calculate metrics for
|
|
8
|
+
* @returns Time efficiency data or null if insufficient data
|
|
9
|
+
*/
|
|
10
|
+
export function calculateTimeEfficiency(appointment: Appointment): {
|
|
11
|
+
bookedDuration: number; // minutes
|
|
12
|
+
actualDuration: number; // minutes
|
|
13
|
+
efficiency: number; // percentage
|
|
14
|
+
overrun: number; // minutes (positive if actual > booked)
|
|
15
|
+
underutilization: number; // minutes (positive if booked > actual)
|
|
16
|
+
} | null {
|
|
17
|
+
const startTime = appointment.appointmentStartTime;
|
|
18
|
+
const endTime = appointment.appointmentEndTime;
|
|
19
|
+
|
|
20
|
+
if (!startTime || !endTime) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Calculate booked duration in minutes
|
|
25
|
+
const bookedDurationMs = endTime.toMillis() - startTime.toMillis();
|
|
26
|
+
const bookedDuration = Math.round(bookedDurationMs / (1000 * 60));
|
|
27
|
+
|
|
28
|
+
// Use actual duration if available, otherwise use booked duration
|
|
29
|
+
const actualDuration = appointment.actualDurationMinutes || bookedDuration;
|
|
30
|
+
|
|
31
|
+
// Calculate efficiency percentage
|
|
32
|
+
const efficiency = bookedDuration > 0 ? (actualDuration / bookedDuration) * 100 : 100;
|
|
33
|
+
|
|
34
|
+
// Calculate overrun (positive if actual > booked)
|
|
35
|
+
const overrun = actualDuration > bookedDuration ? actualDuration - bookedDuration : 0;
|
|
36
|
+
|
|
37
|
+
// Calculate underutilization (positive if booked > actual)
|
|
38
|
+
const underutilization = bookedDuration > actualDuration ? bookedDuration - actualDuration : 0;
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
bookedDuration,
|
|
42
|
+
actualDuration,
|
|
43
|
+
efficiency,
|
|
44
|
+
overrun,
|
|
45
|
+
underutilization,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Calculates average time metrics from an array of appointments
|
|
51
|
+
*
|
|
52
|
+
* @param appointments - Array of appointments
|
|
53
|
+
* @returns Average time metrics
|
|
54
|
+
*/
|
|
55
|
+
export function calculateAverageTimeMetrics(appointments: Appointment[]): {
|
|
56
|
+
averageBookedDuration: number;
|
|
57
|
+
averageActualDuration: number;
|
|
58
|
+
averageEfficiency: number;
|
|
59
|
+
totalOverrun: number;
|
|
60
|
+
totalUnderutilization: number;
|
|
61
|
+
averageOverrun: number;
|
|
62
|
+
averageUnderutilization: number;
|
|
63
|
+
appointmentsWithActualTime: number;
|
|
64
|
+
} {
|
|
65
|
+
if (appointments.length === 0) {
|
|
66
|
+
return {
|
|
67
|
+
averageBookedDuration: 0,
|
|
68
|
+
averageActualDuration: 0,
|
|
69
|
+
averageEfficiency: 0,
|
|
70
|
+
totalOverrun: 0,
|
|
71
|
+
totalUnderutilization: 0,
|
|
72
|
+
averageOverrun: 0,
|
|
73
|
+
averageUnderutilization: 0,
|
|
74
|
+
appointmentsWithActualTime: 0,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let totalBookedDuration = 0;
|
|
79
|
+
let totalActualDuration = 0;
|
|
80
|
+
let totalOverrun = 0;
|
|
81
|
+
let totalUnderutilization = 0;
|
|
82
|
+
let appointmentsWithActualTime = 0;
|
|
83
|
+
|
|
84
|
+
appointments.forEach(appointment => {
|
|
85
|
+
const timeData = calculateTimeEfficiency(appointment);
|
|
86
|
+
if (timeData) {
|
|
87
|
+
totalBookedDuration += timeData.bookedDuration;
|
|
88
|
+
totalActualDuration += timeData.actualDuration;
|
|
89
|
+
totalOverrun += timeData.overrun;
|
|
90
|
+
totalUnderutilization += timeData.underutilization;
|
|
91
|
+
|
|
92
|
+
if (appointment.actualDurationMinutes !== undefined) {
|
|
93
|
+
appointmentsWithActualTime++;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const count = appointments.length;
|
|
99
|
+
const averageBookedDuration = count > 0 ? totalBookedDuration / count : 0;
|
|
100
|
+
const averageActualDuration = count > 0 ? totalActualDuration / count : 0;
|
|
101
|
+
const averageEfficiency = averageBookedDuration > 0 ? (averageActualDuration / averageBookedDuration) * 100 : 0;
|
|
102
|
+
const averageOverrun = count > 0 ? totalOverrun / count : 0;
|
|
103
|
+
const averageUnderutilization = count > 0 ? totalUnderutilization / count : 0;
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
averageBookedDuration: Math.round(averageBookedDuration),
|
|
107
|
+
averageActualDuration: Math.round(averageActualDuration),
|
|
108
|
+
averageEfficiency: Math.round(averageEfficiency * 100) / 100,
|
|
109
|
+
totalOverrun,
|
|
110
|
+
totalUnderutilization,
|
|
111
|
+
averageOverrun: Math.round(averageOverrun),
|
|
112
|
+
averageUnderutilization: Math.round(averageUnderutilization),
|
|
113
|
+
appointmentsWithActualTime,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Groups appointments by efficiency ranges
|
|
119
|
+
*
|
|
120
|
+
* @param appointments - Array of appointments
|
|
121
|
+
* @returns Distribution of appointments by efficiency range
|
|
122
|
+
*/
|
|
123
|
+
export function calculateEfficiencyDistribution(appointments: Appointment[]): Array<{
|
|
124
|
+
range: string;
|
|
125
|
+
count: number;
|
|
126
|
+
percentage: number;
|
|
127
|
+
}> {
|
|
128
|
+
const ranges = [
|
|
129
|
+
{ label: '0-50%', min: 0, max: 50 },
|
|
130
|
+
{ label: '50-75%', min: 50, max: 75 },
|
|
131
|
+
{ label: '75-100%', min: 75, max: 100 },
|
|
132
|
+
{ label: '100%+', min: 100, max: Infinity },
|
|
133
|
+
];
|
|
134
|
+
|
|
135
|
+
const distribution = ranges.map(range => ({
|
|
136
|
+
range: range.label,
|
|
137
|
+
count: 0,
|
|
138
|
+
percentage: 0,
|
|
139
|
+
}));
|
|
140
|
+
|
|
141
|
+
let validCount = 0;
|
|
142
|
+
|
|
143
|
+
appointments.forEach(appointment => {
|
|
144
|
+
const timeData = calculateTimeEfficiency(appointment);
|
|
145
|
+
if (timeData) {
|
|
146
|
+
validCount++;
|
|
147
|
+
const efficiency = timeData.efficiency;
|
|
148
|
+
|
|
149
|
+
for (let i = 0; i < ranges.length; i++) {
|
|
150
|
+
if (efficiency >= ranges[i].min && efficiency < ranges[i].max) {
|
|
151
|
+
distribution[i].count++;
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Calculate percentages
|
|
159
|
+
if (validCount > 0) {
|
|
160
|
+
distribution.forEach(item => {
|
|
161
|
+
item.percentage = Math.round((item.count / validCount) * 100 * 100) / 100;
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return distribution;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Calculates cancellation lead time in hours
|
|
170
|
+
*
|
|
171
|
+
* @param appointment - The appointment to calculate lead time for
|
|
172
|
+
* @returns Lead time in hours, or null if insufficient data
|
|
173
|
+
*/
|
|
174
|
+
export function calculateCancellationLeadTime(appointment: Appointment): number | null {
|
|
175
|
+
if (!appointment.cancellationTime || !appointment.appointmentStartTime) {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const cancellationTime = appointment.cancellationTime.toMillis();
|
|
180
|
+
const appointmentTime = appointment.appointmentStartTime.toMillis();
|
|
181
|
+
const diffMs = appointmentTime - cancellationTime;
|
|
182
|
+
|
|
183
|
+
// Return positive hours (time before appointment)
|
|
184
|
+
return Math.max(0, diffMs / (1000 * 60 * 60));
|
|
185
|
+
}
|
|
186
|
+
|