@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.
@@ -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