@blackcode_sa/metaestetics-api 1.12.67 → 1.12.69
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 +34 -1
- package/dist/admin/index.d.ts +34 -1
- package/dist/admin/index.js +104 -0
- package/dist/admin/index.mjs +104 -0
- package/dist/backoffice/index.d.mts +40 -0
- package/dist/backoffice/index.d.ts +40 -0
- package/dist/backoffice/index.js +118 -18
- package/dist/backoffice/index.mjs +118 -20
- package/dist/index.d.mts +61 -2
- package/dist/index.d.ts +61 -2
- package/dist/index.js +249 -37
- package/dist/index.mjs +249 -39
- package/package.json +1 -1
- package/src/admin/aggregation/appointment/appointment.aggregation.service.ts +140 -0
- package/src/backoffice/services/README.md +17 -0
- package/src/backoffice/services/analytics.service.proposal.md +859 -0
- package/src/backoffice/services/analytics.service.summary.md +143 -0
- package/src/backoffice/services/category.service.ts +49 -6
- package/src/backoffice/services/subcategory.service.ts +50 -6
- package/src/backoffice/services/technology.service.ts +53 -6
- package/src/services/appointment/appointment.service.ts +59 -6
- package/src/services/procedure/procedure.service.ts +120 -7
- package/src/types/notifications/index.ts +21 -0
|
@@ -37,6 +37,7 @@ import { AppointmentMailingService } from '../../mailing/appointment/appointment
|
|
|
37
37
|
import { Logger } from '../../logger';
|
|
38
38
|
import { UserRole } from '../../../types';
|
|
39
39
|
import { CalendarEventStatus } from '../../../types/calendar';
|
|
40
|
+
import { NotificationType } from '../../../types/notifications';
|
|
40
41
|
|
|
41
42
|
// Mailgun client will be injected via constructor
|
|
42
43
|
|
|
@@ -540,6 +541,13 @@ export class AppointmentAggregationService {
|
|
|
540
541
|
await this.handleZonePhotosUpdate(before, after);
|
|
541
542
|
}
|
|
542
543
|
|
|
544
|
+
// Handle Recommended Procedures Added
|
|
545
|
+
const recommendationsChanged = this.hasRecommendationsChanged(before, after);
|
|
546
|
+
if (recommendationsChanged) {
|
|
547
|
+
Logger.info(`[AggService] Recommended procedures changed for appointment ${after.id}`);
|
|
548
|
+
await this.handleRecommendedProceduresUpdate(before, after, patientProfile);
|
|
549
|
+
}
|
|
550
|
+
|
|
543
551
|
// TODO: Handle Review Added
|
|
544
552
|
// const reviewAdded = !before.reviewInfo && after.reviewInfo;
|
|
545
553
|
// if (reviewAdded) { ... }
|
|
@@ -1841,4 +1849,136 @@ export class AppointmentAggregationService {
|
|
|
1841
1849
|
// Don't throw - this is a side effect and shouldn't break the main update flow
|
|
1842
1850
|
}
|
|
1843
1851
|
}
|
|
1852
|
+
|
|
1853
|
+
/**
|
|
1854
|
+
* Checks if recommended procedures have changed between two appointment states
|
|
1855
|
+
* @param before - The appointment state before update
|
|
1856
|
+
* @param after - The appointment state after update
|
|
1857
|
+
* @returns True if recommendations have changed, false otherwise
|
|
1858
|
+
*/
|
|
1859
|
+
private hasRecommendationsChanged(before: Appointment, after: Appointment): boolean {
|
|
1860
|
+
const beforeRecommendations = before.metadata?.recommendedProcedures || [];
|
|
1861
|
+
const afterRecommendations = after.metadata?.recommendedProcedures || [];
|
|
1862
|
+
|
|
1863
|
+
// If lengths differ, there's a change
|
|
1864
|
+
if (beforeRecommendations.length !== afterRecommendations.length) {
|
|
1865
|
+
return true;
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
// Compare each recommendation (simple comparison - if any differ, return true)
|
|
1869
|
+
// For simplicity, we compare by procedure ID and note
|
|
1870
|
+
for (let i = 0; i < afterRecommendations.length; i++) {
|
|
1871
|
+
const beforeRec = beforeRecommendations[i];
|
|
1872
|
+
const afterRec = afterRecommendations[i];
|
|
1873
|
+
|
|
1874
|
+
if (!beforeRec || !afterRec) {
|
|
1875
|
+
return true;
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
if (
|
|
1879
|
+
beforeRec.procedure.procedureId !== afterRec.procedure.procedureId ||
|
|
1880
|
+
beforeRec.note !== afterRec.note ||
|
|
1881
|
+
beforeRec.timeframe.value !== afterRec.timeframe.value ||
|
|
1882
|
+
beforeRec.timeframe.unit !== afterRec.timeframe.unit
|
|
1883
|
+
) {
|
|
1884
|
+
return true;
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1888
|
+
return false;
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
/**
|
|
1892
|
+
* Handles recommended procedures update - creates notifications for newly added recommendations
|
|
1893
|
+
* @param before - The appointment state before update
|
|
1894
|
+
* @param after - The appointment state after update
|
|
1895
|
+
* @param patientProfile - The patient profile (for expo tokens)
|
|
1896
|
+
*/
|
|
1897
|
+
private async handleRecommendedProceduresUpdate(
|
|
1898
|
+
before: Appointment,
|
|
1899
|
+
after: Appointment,
|
|
1900
|
+
patientProfile: PatientProfile | null,
|
|
1901
|
+
): Promise<void> {
|
|
1902
|
+
try {
|
|
1903
|
+
const beforeRecommendations = before.metadata?.recommendedProcedures || [];
|
|
1904
|
+
const afterRecommendations = after.metadata?.recommendedProcedures || [];
|
|
1905
|
+
|
|
1906
|
+
// Find newly added recommendations
|
|
1907
|
+
const newRecommendations = afterRecommendations.slice(beforeRecommendations.length);
|
|
1908
|
+
|
|
1909
|
+
if (newRecommendations.length === 0) {
|
|
1910
|
+
Logger.info(
|
|
1911
|
+
`[AggService] No new recommendations detected for appointment ${after.id}`,
|
|
1912
|
+
);
|
|
1913
|
+
return;
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
Logger.info(
|
|
1917
|
+
`[AggService] Found ${newRecommendations.length} new recommendation(s) for appointment ${after.id}`,
|
|
1918
|
+
);
|
|
1919
|
+
|
|
1920
|
+
// Create notifications for each new recommendation
|
|
1921
|
+
for (let i = 0; i < newRecommendations.length; i++) {
|
|
1922
|
+
const recommendation = newRecommendations[i];
|
|
1923
|
+
const recommendationIndex = beforeRecommendations.length + i;
|
|
1924
|
+
const recommendationId = `${after.id}:${recommendationIndex}`;
|
|
1925
|
+
|
|
1926
|
+
// Format timeframe for display
|
|
1927
|
+
const timeframeText = `${recommendation.timeframe.value} ${recommendation.timeframe.unit}${recommendation.timeframe.value > 1 ? 's' : ''}`;
|
|
1928
|
+
|
|
1929
|
+
// Create notification
|
|
1930
|
+
const notificationPayload: Omit<
|
|
1931
|
+
any,
|
|
1932
|
+
'id' | 'createdAt' | 'updatedAt' | 'status' | 'isRead'
|
|
1933
|
+
> = {
|
|
1934
|
+
userId: after.patientId,
|
|
1935
|
+
userRole: UserRole.PATIENT,
|
|
1936
|
+
notificationType: NotificationType.PROCEDURE_RECOMMENDATION,
|
|
1937
|
+
notificationTime: admin.firestore.Timestamp.now(),
|
|
1938
|
+
notificationTokens: patientProfile?.expoTokens || [],
|
|
1939
|
+
title: 'New Procedure Recommendation',
|
|
1940
|
+
body: `${after.practitionerInfo?.name || 'Your doctor'} recommended "${recommendation.procedure.procedureName}" for you. Suggested timeframe: in ${timeframeText}`,
|
|
1941
|
+
appointmentId: after.id,
|
|
1942
|
+
recommendationId,
|
|
1943
|
+
procedureId: recommendation.procedure.procedureId,
|
|
1944
|
+
procedureName: recommendation.procedure.procedureName,
|
|
1945
|
+
practitionerName: after.practitionerInfo?.name || 'Unknown Practitioner',
|
|
1946
|
+
clinicName: after.clinicInfo?.name || 'Unknown Clinic',
|
|
1947
|
+
note: recommendation.note,
|
|
1948
|
+
timeframe: recommendation.timeframe,
|
|
1949
|
+
};
|
|
1950
|
+
|
|
1951
|
+
try {
|
|
1952
|
+
const notificationId = await this.notificationsAdmin.createNotification(
|
|
1953
|
+
notificationPayload as any,
|
|
1954
|
+
);
|
|
1955
|
+
|
|
1956
|
+
Logger.info(
|
|
1957
|
+
`[AggService] Created notification ${notificationId} for recommendation ${recommendationId}`,
|
|
1958
|
+
);
|
|
1959
|
+
|
|
1960
|
+
// Send push notification immediately if patient has tokens
|
|
1961
|
+
if (patientProfile?.expoTokens && patientProfile.expoTokens.length > 0) {
|
|
1962
|
+
const notification = await this.notificationsAdmin.getNotification(notificationId);
|
|
1963
|
+
if (notification) {
|
|
1964
|
+
await this.notificationsAdmin.sendPushNotification(notification);
|
|
1965
|
+
Logger.info(
|
|
1966
|
+
`[AggService] Sent push notification for recommendation ${recommendationId}`,
|
|
1967
|
+
);
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
} catch (error) {
|
|
1971
|
+
Logger.error(
|
|
1972
|
+
`[AggService] Error creating notification for recommendation ${recommendationId}:`,
|
|
1973
|
+
error,
|
|
1974
|
+
);
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
} catch (error) {
|
|
1978
|
+
Logger.error(
|
|
1979
|
+
`[AggService] Error handling recommended procedures update for appointment ${after.id}:`,
|
|
1980
|
+
error,
|
|
1981
|
+
);
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1844
1984
|
}
|
|
@@ -38,3 +38,20 @@ Manages administrative constants like treatment benefits and contraindications.
|
|
|
38
38
|
- **`addContraindication(contraindication)`**: Adds a new contraindication.
|
|
39
39
|
- **`updateContraindication(contraindication)`**: Updates an existing contraindication.
|
|
40
40
|
- **`deleteContraindication(contraindicationId)`**: Deletes a contraindication.
|
|
41
|
+
|
|
42
|
+
### `AnalyticsService` (Proposed)
|
|
43
|
+
|
|
44
|
+
Comprehensive financial and analytical intelligence service for the Clinic Admin app. Provides insights about doctors, procedures, appointments, patients, products, and clinic operations.
|
|
45
|
+
|
|
46
|
+
**Status**: Proposal phase - See [analytics.service.proposal.md](./analytics.service.proposal.md) for detailed design and implementation plan.
|
|
47
|
+
|
|
48
|
+
**Planned Features**:
|
|
49
|
+
- Practitioner performance analytics (appointments, cancellations, time efficiency, revenue)
|
|
50
|
+
- Procedure analytics (popularity, profitability, product usage)
|
|
51
|
+
- Appointment time analytics (booked vs actual time, efficiency metrics)
|
|
52
|
+
- Cancellation & no-show analytics (by clinic, practitioner, patient, procedure)
|
|
53
|
+
- Financial analytics (revenue, costs, payment status, trends)
|
|
54
|
+
- Product usage analytics (usage patterns, revenue contribution)
|
|
55
|
+
- Patient analytics (lifetime value, retention, appointment frequency)
|
|
56
|
+
- Clinic analytics (performance metrics, comparisons)
|
|
57
|
+
- Comprehensive dashboard data aggregation
|