@blackcode_sa/metaestetics-api 1.12.59 → 1.12.62
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 +38 -4
- package/dist/admin/index.d.ts +38 -4
- package/dist/admin/index.js +192 -30
- package/dist/admin/index.mjs +192 -30
- package/dist/index.d.mts +11 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.js +53 -11
- package/dist/index.mjs +53 -11
- package/package.json +5 -5
- package/src/admin/aggregation/appointment/appointment.aggregation.service.ts +183 -28
- package/src/admin/aggregation/reviews/reviews.aggregation.service.ts +59 -11
- package/src/services/reviews/reviews.service.ts +48 -1
- package/src/types/patient/patient-requirements.ts +11 -0
- package/src/types/reviews/index.ts +3 -1
- package/src/validations/patient/patient-requirements.schema.ts +9 -0
- package/src/validations/reviews.schema.ts +8 -2
package/dist/index.mjs
CHANGED
|
@@ -4669,6 +4669,7 @@ var reviewSchema = z8.object({
|
|
|
4669
4669
|
clinicReview: clinicReviewSchema.optional(),
|
|
4670
4670
|
practitionerReview: practitionerReviewSchema.optional(),
|
|
4671
4671
|
procedureReview: procedureReviewSchema.optional(),
|
|
4672
|
+
extendedProcedureReviews: z8.array(procedureReviewSchema).optional(),
|
|
4672
4673
|
overallComment: z8.string().min(1).max(2e3),
|
|
4673
4674
|
overallRating: z8.number().min(1).max(5)
|
|
4674
4675
|
});
|
|
@@ -4677,13 +4678,14 @@ var createReviewSchema = z8.object({
|
|
|
4677
4678
|
clinicReview: createClinicReviewSchema.optional(),
|
|
4678
4679
|
practitionerReview: createPractitionerReviewSchema.optional(),
|
|
4679
4680
|
procedureReview: createProcedureReviewSchema.optional(),
|
|
4681
|
+
extendedProcedureReviews: z8.array(createProcedureReviewSchema).optional(),
|
|
4680
4682
|
overallComment: z8.string().min(1).max(2e3)
|
|
4681
4683
|
}).refine(
|
|
4682
4684
|
(data) => {
|
|
4683
|
-
return data.clinicReview || data.practitionerReview || data.procedureReview;
|
|
4685
|
+
return data.clinicReview || data.practitionerReview || data.procedureReview || data.extendedProcedureReviews && data.extendedProcedureReviews.length > 0;
|
|
4684
4686
|
},
|
|
4685
4687
|
{
|
|
4686
|
-
message: "At least one review type (clinic, practitioner, or procedure) must be provided",
|
|
4688
|
+
message: "At least one review type (clinic, practitioner, procedure, or extended procedure) must be provided",
|
|
4687
4689
|
path: ["reviewType"]
|
|
4688
4690
|
}
|
|
4689
4691
|
);
|
|
@@ -17916,16 +17918,17 @@ var ReviewService = class extends BaseService {
|
|
|
17916
17918
|
* @returns The created review
|
|
17917
17919
|
*/
|
|
17918
17920
|
async createReview(data, appointmentId) {
|
|
17919
|
-
var _a, _b, _c, _d, _e, _f;
|
|
17921
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
17920
17922
|
try {
|
|
17921
17923
|
console.log("\u{1F50D} ReviewService.createReview - Input data:", {
|
|
17922
17924
|
appointmentId,
|
|
17923
17925
|
hasClinicReview: !!data.clinicReview,
|
|
17924
17926
|
hasPractitionerReview: !!data.practitionerReview,
|
|
17925
17927
|
hasProcedureReview: !!data.procedureReview,
|
|
17926
|
-
|
|
17927
|
-
|
|
17928
|
-
|
|
17928
|
+
extendedProcedureReviewsCount: ((_a = data.extendedProcedureReviews) == null ? void 0 : _a.length) || 0,
|
|
17929
|
+
practitionerId: (_b = data.practitionerReview) == null ? void 0 : _b.practitionerId,
|
|
17930
|
+
clinicId: (_c = data.clinicReview) == null ? void 0 : _c.clinicId,
|
|
17931
|
+
procedureId: (_d = data.procedureReview) == null ? void 0 : _d.procedureId
|
|
17929
17932
|
});
|
|
17930
17933
|
const validatedData = createReviewSchema.parse(data);
|
|
17931
17934
|
const ratings = [];
|
|
@@ -17965,6 +17968,20 @@ var ReviewService = class extends BaseService {
|
|
|
17965
17968
|
data.procedureReview.overallRating = procedureAverage;
|
|
17966
17969
|
ratings.push(procedureAverage);
|
|
17967
17970
|
}
|
|
17971
|
+
if (data.extendedProcedureReviews && data.extendedProcedureReviews.length > 0) {
|
|
17972
|
+
data.extendedProcedureReviews.forEach((extendedReview) => {
|
|
17973
|
+
const extendedRatings = [
|
|
17974
|
+
extendedReview.effectivenessOfTreatment,
|
|
17975
|
+
extendedReview.outcomeExplanation,
|
|
17976
|
+
extendedReview.painManagement,
|
|
17977
|
+
extendedReview.followUpCare,
|
|
17978
|
+
extendedReview.valueForMoney
|
|
17979
|
+
];
|
|
17980
|
+
const extendedAverage = this.calculateAverage(extendedRatings);
|
|
17981
|
+
extendedReview.overallRating = extendedAverage;
|
|
17982
|
+
ratings.push(extendedAverage);
|
|
17983
|
+
});
|
|
17984
|
+
}
|
|
17968
17985
|
const overallRating = this.calculateAverage(ratings);
|
|
17969
17986
|
const reviewId = this.generateId();
|
|
17970
17987
|
if (data.clinicReview) {
|
|
@@ -17980,6 +17997,14 @@ var ReviewService = class extends BaseService {
|
|
|
17980
17997
|
data.procedureReview.fullReviewId = reviewId;
|
|
17981
17998
|
}
|
|
17982
17999
|
const now = /* @__PURE__ */ new Date();
|
|
18000
|
+
if (data.extendedProcedureReviews && data.extendedProcedureReviews.length > 0) {
|
|
18001
|
+
data.extendedProcedureReviews.forEach((extendedReview) => {
|
|
18002
|
+
extendedReview.id = this.generateId();
|
|
18003
|
+
extendedReview.fullReviewId = reviewId;
|
|
18004
|
+
extendedReview.createdAt = now;
|
|
18005
|
+
extendedReview.updatedAt = now;
|
|
18006
|
+
});
|
|
18007
|
+
}
|
|
17983
18008
|
const review = {
|
|
17984
18009
|
id: reviewId,
|
|
17985
18010
|
appointmentId,
|
|
@@ -17987,6 +18012,7 @@ var ReviewService = class extends BaseService {
|
|
|
17987
18012
|
clinicReview: data.clinicReview,
|
|
17988
18013
|
practitionerReview: data.practitionerReview,
|
|
17989
18014
|
procedureReview: data.procedureReview,
|
|
18015
|
+
extendedProcedureReviews: data.extendedProcedureReviews,
|
|
17990
18016
|
overallComment: data.overallComment,
|
|
17991
18017
|
overallRating,
|
|
17992
18018
|
createdAt: now,
|
|
@@ -18001,9 +18027,10 @@ var ReviewService = class extends BaseService {
|
|
|
18001
18027
|
});
|
|
18002
18028
|
console.log("\u2705 ReviewService.createReview - Review saved to Firestore:", {
|
|
18003
18029
|
reviewId,
|
|
18004
|
-
practitionerId: (
|
|
18005
|
-
clinicId: (
|
|
18006
|
-
procedureId: (
|
|
18030
|
+
practitionerId: (_e = review.practitionerReview) == null ? void 0 : _e.practitionerId,
|
|
18031
|
+
clinicId: (_f = review.clinicReview) == null ? void 0 : _f.clinicId,
|
|
18032
|
+
procedureId: (_g = review.procedureReview) == null ? void 0 : _g.procedureId,
|
|
18033
|
+
extendedProcedureReviewsCount: ((_h = review.extendedProcedureReviews) == null ? void 0 : _h.length) || 0
|
|
18007
18034
|
});
|
|
18008
18035
|
return review;
|
|
18009
18036
|
} catch (error) {
|
|
@@ -18019,7 +18046,7 @@ var ReviewService = class extends BaseService {
|
|
|
18019
18046
|
* @returns The review with entity names if found, null otherwise
|
|
18020
18047
|
*/
|
|
18021
18048
|
async getReview(reviewId) {
|
|
18022
|
-
var _a, _b, _c;
|
|
18049
|
+
var _a, _b, _c, _d, _e;
|
|
18023
18050
|
console.log("\u{1F50D} ReviewService.getReview - Getting review:", reviewId);
|
|
18024
18051
|
const docRef = doc37(this.db, REVIEWS_COLLECTION, reviewId);
|
|
18025
18052
|
const docSnap = await getDoc38(docRef);
|
|
@@ -18053,12 +18080,27 @@ var ReviewService = class extends BaseService {
|
|
|
18053
18080
|
procedureName: appointment.procedureInfo.name
|
|
18054
18081
|
};
|
|
18055
18082
|
}
|
|
18083
|
+
if (enhancedReview.extendedProcedureReviews && enhancedReview.extendedProcedureReviews.length > 0) {
|
|
18084
|
+
const extendedProcedures = ((_a = appointment.metadata) == null ? void 0 : _a.extendedProcedures) || [];
|
|
18085
|
+
enhancedReview.extendedProcedureReviews = enhancedReview.extendedProcedureReviews.map((extendedReview) => {
|
|
18086
|
+
const procedureInfo = extendedProcedures.find(
|
|
18087
|
+
(ep) => ep.procedureId === extendedReview.procedureId
|
|
18088
|
+
);
|
|
18089
|
+
if (procedureInfo) {
|
|
18090
|
+
return {
|
|
18091
|
+
...extendedReview,
|
|
18092
|
+
procedureName: procedureInfo.procedureName
|
|
18093
|
+
};
|
|
18094
|
+
}
|
|
18095
|
+
return extendedReview;
|
|
18096
|
+
});
|
|
18097
|
+
}
|
|
18056
18098
|
if (appointment.patientInfo) {
|
|
18057
18099
|
enhancedReview.patientName = appointment.patientInfo.fullName;
|
|
18058
18100
|
}
|
|
18059
18101
|
console.log("\u2705 ReviewService.getReview - Enhanced review:", {
|
|
18060
18102
|
reviewId,
|
|
18061
|
-
hasEntityNames: !!(((
|
|
18103
|
+
hasEntityNames: !!(((_b = enhancedReview.clinicReview) == null ? void 0 : _b.clinicName) || ((_c = enhancedReview.practitionerReview) == null ? void 0 : _c.practitionerName) || ((_d = enhancedReview.procedureReview) == null ? void 0 : _d.procedureName) || enhancedReview.patientName || ((_e = enhancedReview.extendedProcedureReviews) == null ? void 0 : _e.some((epr) => epr.procedureName)))
|
|
18062
18104
|
});
|
|
18063
18105
|
return enhancedReview;
|
|
18064
18106
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blackcode_sa/metaestetics-api",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "1.12.
|
|
4
|
+
"version": "1.12.62",
|
|
5
5
|
"description": "Firebase authentication service with anonymous upgrade support",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"module": "dist/index.mjs",
|
|
@@ -56,16 +56,16 @@
|
|
|
56
56
|
"author": "Blackcode SA",
|
|
57
57
|
"license": "MIT",
|
|
58
58
|
"scripts": {
|
|
59
|
-
"clean": "
|
|
59
|
+
"clean": "node -e \"const fs=require('fs');const path=require('path');const dist='dist';if(fs.existsSync(dist))fs.rmSync(dist,{recursive:true,force:true})\"",
|
|
60
60
|
"build:main": "tsup src/index.ts --format cjs,esm --dts --external react-native",
|
|
61
61
|
"build:admin": "tsup src/admin/index.ts --format cjs,esm --dts --out-dir dist/admin --external react-native",
|
|
62
62
|
"build:backoffice": "tsup src/backoffice/index.ts --format cjs,esm --dts --out-dir dist/backoffice",
|
|
63
63
|
"build": "npm run clean && npm run build:main && npm run build:admin && npm run build:backoffice",
|
|
64
64
|
"dev": "tsup src/index.ts --format cjs,esm --dts --watch --external react-native",
|
|
65
65
|
"prepublishOnly": "npm run build",
|
|
66
|
-
"publish:patch": "
|
|
67
|
-
"publish:minor": "
|
|
68
|
-
"publish:major": "
|
|
66
|
+
"publish:patch": "node scripts/publish.js patch",
|
|
67
|
+
"publish:minor": "node scripts/publish.js minor",
|
|
68
|
+
"publish:major": "node scripts/publish.js major",
|
|
69
69
|
"test": "jest",
|
|
70
70
|
"test:watch": "jest --watch",
|
|
71
71
|
"test:coverage": "jest --coverage"
|
|
@@ -25,6 +25,8 @@ import {
|
|
|
25
25
|
} from '../../../types/patient';
|
|
26
26
|
import { Practitioner, PRACTITIONERS_COLLECTION } from '../../../types/practitioner';
|
|
27
27
|
import { Clinic, CLINICS_COLLECTION } from '../../../types/clinic';
|
|
28
|
+
import { Procedure, PROCEDURES_COLLECTION } from '../../../types/procedure';
|
|
29
|
+
import { RequirementSourceProcedure } from '../../../types/patient/patient-requirements';
|
|
28
30
|
// import { UserRole } from "../../../types"; // Not directly used
|
|
29
31
|
|
|
30
32
|
// Dependent Admin Services
|
|
@@ -38,6 +40,14 @@ import { CalendarEventStatus } from '../../../types/calendar';
|
|
|
38
40
|
|
|
39
41
|
// Mailgun client will be injected via constructor
|
|
40
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Type for requirement with source procedure tracking
|
|
45
|
+
*/
|
|
46
|
+
type RequirementWithSource = {
|
|
47
|
+
requirement: RequirementTemplate;
|
|
48
|
+
sourceProcedures: RequirementSourceProcedure[];
|
|
49
|
+
};
|
|
50
|
+
|
|
41
51
|
/**
|
|
42
52
|
* @class AppointmentAggregationService
|
|
43
53
|
* @description Handles aggregation tasks and side effects related to appointment lifecycle events.
|
|
@@ -861,11 +871,141 @@ export class AppointmentAggregationService {
|
|
|
861
871
|
}
|
|
862
872
|
}
|
|
863
873
|
|
|
874
|
+
/**
|
|
875
|
+
* Fetches post-requirements from a procedure document
|
|
876
|
+
* @param procedureId - The procedure ID to fetch requirements from
|
|
877
|
+
* @returns Promise resolving to array of post-requirements with source procedure info
|
|
878
|
+
*/
|
|
879
|
+
private async fetchPostRequirementsFromProcedure(
|
|
880
|
+
procedureId: string,
|
|
881
|
+
): Promise<RequirementWithSource[]> {
|
|
882
|
+
try {
|
|
883
|
+
const procedureDoc = await this.db.collection(PROCEDURES_COLLECTION).doc(procedureId).get();
|
|
884
|
+
if (!procedureDoc.exists) {
|
|
885
|
+
Logger.warn(`[AggService] Procedure ${procedureId} not found when fetching requirements`);
|
|
886
|
+
return [];
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
const procedure = procedureDoc.data() as Procedure;
|
|
890
|
+
const postRequirements = procedure.postRequirements || [];
|
|
891
|
+
|
|
892
|
+
if (postRequirements.length === 0) {
|
|
893
|
+
return [];
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
return postRequirements.map(req => ({
|
|
897
|
+
requirement: req,
|
|
898
|
+
sourceProcedures: [
|
|
899
|
+
{
|
|
900
|
+
procedureId: procedure.id,
|
|
901
|
+
procedureName: procedure.name,
|
|
902
|
+
},
|
|
903
|
+
],
|
|
904
|
+
}));
|
|
905
|
+
} catch (error) {
|
|
906
|
+
Logger.error(
|
|
907
|
+
`[AggService] Error fetching post-requirements from procedure ${procedureId}:`,
|
|
908
|
+
error,
|
|
909
|
+
);
|
|
910
|
+
return [];
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
/**
|
|
915
|
+
* Collects all post-requirements from primary and extended procedures
|
|
916
|
+
* @param appointment - The appointment to collect requirements for
|
|
917
|
+
* @returns Promise resolving to array of requirements with source procedures
|
|
918
|
+
*/
|
|
919
|
+
private async collectAllPostRequirements(
|
|
920
|
+
appointment: Appointment,
|
|
921
|
+
): Promise<RequirementWithSource[]> {
|
|
922
|
+
const allRequirements: RequirementWithSource[] = [];
|
|
923
|
+
|
|
924
|
+
// Fetch from primary procedure
|
|
925
|
+
if (appointment.procedureId) {
|
|
926
|
+
const primaryRequirements = await this.fetchPostRequirementsFromProcedure(
|
|
927
|
+
appointment.procedureId,
|
|
928
|
+
);
|
|
929
|
+
allRequirements.push(...primaryRequirements);
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
// Fetch from extended procedures
|
|
933
|
+
const extendedProcedures = appointment.metadata?.extendedProcedures || [];
|
|
934
|
+
if (extendedProcedures.length > 0) {
|
|
935
|
+
Logger.info(
|
|
936
|
+
`[AggService] Fetching post-requirements from ${extendedProcedures.length} extended procedures`,
|
|
937
|
+
);
|
|
938
|
+
|
|
939
|
+
const extendedRequirementsPromises = extendedProcedures.map(extProc =>
|
|
940
|
+
this.fetchPostRequirementsFromProcedure(extProc.procedureId),
|
|
941
|
+
);
|
|
942
|
+
|
|
943
|
+
const extendedRequirementsArrays = await Promise.all(extendedRequirementsPromises);
|
|
944
|
+
extendedRequirementsArrays.forEach(reqs => {
|
|
945
|
+
allRequirements.push(...reqs);
|
|
946
|
+
});
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
return allRequirements;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
/**
|
|
953
|
+
* Generates a unique key for a requirement based on ID and timeframe
|
|
954
|
+
* @param requirement - The requirement to generate a key for
|
|
955
|
+
* @returns Unique key string
|
|
956
|
+
*/
|
|
957
|
+
private getRequirementKey(requirement: RequirementTemplate): string {
|
|
958
|
+
const timeframeSig = JSON.stringify({
|
|
959
|
+
duration: requirement.timeframe?.duration || 0,
|
|
960
|
+
unit: requirement.timeframe?.unit || '',
|
|
961
|
+
notifyAt: (requirement.timeframe?.notifyAt || []).slice().sort((a, b) => a - b),
|
|
962
|
+
});
|
|
963
|
+
return `${requirement.id}:${timeframeSig}`;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
/**
|
|
967
|
+
* Deduplicates requirements based on requirement ID and timeframe
|
|
968
|
+
* Merges source procedures when requirements match
|
|
969
|
+
* @param requirements - Array of requirements with sources
|
|
970
|
+
* @returns Deduplicated array of requirements
|
|
971
|
+
*/
|
|
972
|
+
private deduplicateRequirements(
|
|
973
|
+
requirements: RequirementWithSource[],
|
|
974
|
+
): RequirementWithSource[] {
|
|
975
|
+
const requirementMap = new Map<string, RequirementWithSource>();
|
|
976
|
+
|
|
977
|
+
for (const reqWithSource of requirements) {
|
|
978
|
+
const key = this.getRequirementKey(reqWithSource.requirement);
|
|
979
|
+
|
|
980
|
+
if (requirementMap.has(key)) {
|
|
981
|
+
// Merge source procedures
|
|
982
|
+
const existing = requirementMap.get(key)!;
|
|
983
|
+
const existingProcedureIds = new Set(
|
|
984
|
+
existing.sourceProcedures.map(sp => sp.procedureId),
|
|
985
|
+
);
|
|
986
|
+
|
|
987
|
+
// Add new source procedures that don't already exist
|
|
988
|
+
reqWithSource.sourceProcedures.forEach(sp => {
|
|
989
|
+
if (!existingProcedureIds.has(sp.procedureId)) {
|
|
990
|
+
existing.sourceProcedures.push(sp);
|
|
991
|
+
}
|
|
992
|
+
});
|
|
993
|
+
} else {
|
|
994
|
+
// New requirement, add it
|
|
995
|
+
requirementMap.set(key, {
|
|
996
|
+
requirement: reqWithSource.requirement,
|
|
997
|
+
sourceProcedures: [...reqWithSource.sourceProcedures],
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
return Array.from(requirementMap.values());
|
|
1003
|
+
}
|
|
1004
|
+
|
|
864
1005
|
/**
|
|
865
1006
|
* Creates POST_APPOINTMENT PatientRequirementInstance documents for a given appointment.
|
|
866
|
-
*
|
|
867
|
-
*
|
|
868
|
-
* with derived instructions and batch writes them to Firestore under the patient's `patient_requirements` subcollection.
|
|
1007
|
+
* Fetches requirements from primary and extended procedures, deduplicates them,
|
|
1008
|
+
* and creates requirement instances with source procedure tracking.
|
|
869
1009
|
*
|
|
870
1010
|
* @param {Appointment} appointment - The appointment for which to create post-requirement instances.
|
|
871
1011
|
* @returns {Promise<void>} A promise that resolves when the operation is complete.
|
|
@@ -882,39 +1022,49 @@ export class AppointmentAggregationService {
|
|
|
882
1022
|
return;
|
|
883
1023
|
}
|
|
884
1024
|
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
1025
|
+
try {
|
|
1026
|
+
// Collect all post-requirements from primary and extended procedures
|
|
1027
|
+
const allRequirements = await this.collectAllPostRequirements(appointment);
|
|
1028
|
+
|
|
1029
|
+
if (allRequirements.length === 0) {
|
|
1030
|
+
Logger.info(
|
|
1031
|
+
`[AggService] No post-requirements found from any procedures for appointment ${appointment.id}. Nothing to create.`,
|
|
1032
|
+
);
|
|
1033
|
+
return;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
// Deduplicate requirements based on ID + timeframe
|
|
1037
|
+
const deduplicatedRequirements = this.deduplicateRequirements(allRequirements);
|
|
1038
|
+
|
|
889
1039
|
Logger.info(
|
|
890
|
-
`[AggService]
|
|
1040
|
+
`[AggService] Found ${allRequirements.length} total post-requirements, ${deduplicatedRequirements.length} after deduplication`,
|
|
891
1041
|
);
|
|
892
|
-
return;
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
try {
|
|
896
|
-
const batch = this.db.batch();
|
|
897
|
-
let instancesCreatedCount = 0;
|
|
898
|
-
// Store created instances for fallback direct creation if needed
|
|
899
|
-
let createdInstances = [];
|
|
900
1042
|
|
|
901
|
-
// Log
|
|
1043
|
+
// Log details about the deduplicated requirements
|
|
902
1044
|
Logger.info(
|
|
903
|
-
`[AggService]
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
1045
|
+
`[AggService] Processing deduplicated post-requirements: ${JSON.stringify(
|
|
1046
|
+
deduplicatedRequirements.map(r => ({
|
|
1047
|
+
id: r.requirement.id,
|
|
1048
|
+
name: r.requirement.name,
|
|
1049
|
+
type: r.requirement.type,
|
|
1050
|
+
isActive: r.requirement.isActive,
|
|
1051
|
+
hasTimeframe: !!r.requirement.timeframe,
|
|
1052
|
+
notifyAtLength: r.requirement.timeframe?.notifyAt?.length || 0,
|
|
1053
|
+
sourceProcedures: r.sourceProcedures.map(sp => ({
|
|
1054
|
+
procedureId: sp.procedureId,
|
|
1055
|
+
procedureName: sp.procedureName,
|
|
1056
|
+
})),
|
|
913
1057
|
})),
|
|
914
1058
|
)}`,
|
|
915
1059
|
);
|
|
916
1060
|
|
|
917
|
-
|
|
1061
|
+
const batch = this.db.batch();
|
|
1062
|
+
let instancesCreatedCount = 0;
|
|
1063
|
+
// Store created instances for fallback direct creation if needed
|
|
1064
|
+
let createdInstances = [];
|
|
1065
|
+
|
|
1066
|
+
for (const reqWithSource of deduplicatedRequirements) {
|
|
1067
|
+
const template = reqWithSource.requirement;
|
|
918
1068
|
if (!template) {
|
|
919
1069
|
Logger.warn(
|
|
920
1070
|
`[AggService] Found null/undefined template in postProcedureRequirements array`,
|
|
@@ -1000,6 +1150,7 @@ export class AppointmentAggregationService {
|
|
|
1000
1150
|
requirementImportance: template.importance,
|
|
1001
1151
|
overallStatus: PatientRequirementOverallStatus.ACTIVE,
|
|
1002
1152
|
instructions: instructions,
|
|
1153
|
+
sourceProcedures: reqWithSource.sourceProcedures, // Track which procedures this requirement comes from
|
|
1003
1154
|
createdAt: admin.firestore.FieldValue.serverTimestamp() as any,
|
|
1004
1155
|
updatedAt: admin.firestore.FieldValue.serverTimestamp() as any,
|
|
1005
1156
|
};
|
|
@@ -1012,6 +1163,10 @@ export class AppointmentAggregationService {
|
|
|
1012
1163
|
appointmentId: newInstanceData.appointmentId,
|
|
1013
1164
|
requirementName: newInstanceData.requirementName,
|
|
1014
1165
|
instructionsCount: newInstanceData.instructions.length,
|
|
1166
|
+
sourceProcedures: newInstanceData.sourceProcedures?.map(sp => ({
|
|
1167
|
+
procedureId: sp.procedureId,
|
|
1168
|
+
procedureName: sp.procedureName,
|
|
1169
|
+
})) || [],
|
|
1015
1170
|
})}`,
|
|
1016
1171
|
);
|
|
1017
1172
|
|
|
@@ -65,6 +65,18 @@ export class ReviewsAggregationService {
|
|
|
65
65
|
);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
// Update extended procedures if they exist
|
|
69
|
+
if (review.extendedProcedureReviews && review.extendedProcedureReviews.length > 0) {
|
|
70
|
+
console.log(
|
|
71
|
+
`[ReviewsAggregationService] Processing ${review.extendedProcedureReviews.length} extended procedure reviews`
|
|
72
|
+
);
|
|
73
|
+
review.extendedProcedureReviews.forEach((extendedReview) => {
|
|
74
|
+
updatePromises.push(
|
|
75
|
+
this.updateProcedureReviewInfo(extendedReview.procedureId)
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
68
80
|
// Wait for all updates to complete
|
|
69
81
|
await Promise.all(updatePromises);
|
|
70
82
|
console.log(
|
|
@@ -117,6 +129,22 @@ export class ReviewsAggregationService {
|
|
|
117
129
|
);
|
|
118
130
|
}
|
|
119
131
|
|
|
132
|
+
// Update extended procedures if they exist
|
|
133
|
+
if (review.extendedProcedureReviews && review.extendedProcedureReviews.length > 0) {
|
|
134
|
+
console.log(
|
|
135
|
+
`[ReviewsAggregationService] Processing deletion of ${review.extendedProcedureReviews.length} extended procedure reviews`
|
|
136
|
+
);
|
|
137
|
+
review.extendedProcedureReviews.forEach((extendedReview) => {
|
|
138
|
+
updatePromises.push(
|
|
139
|
+
this.updateProcedureReviewInfo(
|
|
140
|
+
extendedReview.procedureId,
|
|
141
|
+
extendedReview,
|
|
142
|
+
true
|
|
143
|
+
)
|
|
144
|
+
);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
120
148
|
// Wait for all updates to complete
|
|
121
149
|
await Promise.all(updatePromises);
|
|
122
150
|
console.log(
|
|
@@ -430,14 +458,32 @@ export class ReviewsAggregationService {
|
|
|
430
458
|
recommendationPercentage: 0,
|
|
431
459
|
};
|
|
432
460
|
|
|
433
|
-
// Get all reviews for this procedure
|
|
434
|
-
|
|
461
|
+
// Get all reviews for this procedure (both main and extended)
|
|
462
|
+
// We need to check both procedureReview.procedureId and extendedProcedureReviews array
|
|
463
|
+
const allReviewsQuery = await this.db
|
|
435
464
|
.collection(REVIEWS_COLLECTION)
|
|
436
|
-
.where("procedureReview.procedureId", "==", procedureId)
|
|
437
465
|
.get();
|
|
438
466
|
|
|
439
|
-
//
|
|
440
|
-
|
|
467
|
+
// Filter reviews that contain this procedure (either as main or extended)
|
|
468
|
+
const reviews = allReviewsQuery.docs.map((doc) => doc.data() as Review);
|
|
469
|
+
const procedureReviews: ProcedureReview[] = [];
|
|
470
|
+
|
|
471
|
+
reviews.forEach((review) => {
|
|
472
|
+
// Check if this is the main procedure
|
|
473
|
+
if (review.procedureReview && review.procedureReview.procedureId === procedureId) {
|
|
474
|
+
procedureReviews.push(review.procedureReview);
|
|
475
|
+
}
|
|
476
|
+
// Check if this is in extended procedures
|
|
477
|
+
if (review.extendedProcedureReviews && review.extendedProcedureReviews.length > 0) {
|
|
478
|
+
const matchingExtended = review.extendedProcedureReviews.filter(
|
|
479
|
+
(extReview) => extReview.procedureId === procedureId
|
|
480
|
+
);
|
|
481
|
+
procedureReviews.push(...matchingExtended);
|
|
482
|
+
}
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
// If there are no reviews, set default values
|
|
486
|
+
if (procedureReviews.length === 0) {
|
|
441
487
|
const updatedReviewInfo: ProcedureReviewInfo = {
|
|
442
488
|
totalReviews: 0,
|
|
443
489
|
averageRating: 0,
|
|
@@ -460,12 +506,6 @@ export class ReviewsAggregationService {
|
|
|
460
506
|
return updatedReviewInfo;
|
|
461
507
|
}
|
|
462
508
|
|
|
463
|
-
// Calculate new averages from all reviews
|
|
464
|
-
const reviews = reviewsQuery.docs.map((doc) => doc.data() as Review);
|
|
465
|
-
const procedureReviews = reviews
|
|
466
|
-
.map((review) => review.procedureReview)
|
|
467
|
-
.filter((review): review is ProcedureReview => review !== undefined);
|
|
468
|
-
|
|
469
509
|
// Calculate averages
|
|
470
510
|
let totalRating = 0;
|
|
471
511
|
let totalEffectivenessOfTreatment = 0;
|
|
@@ -599,11 +639,19 @@ export class ReviewsAggregationService {
|
|
|
599
639
|
review.procedureReview.isVerified = true;
|
|
600
640
|
}
|
|
601
641
|
|
|
642
|
+
// Update extended procedure reviews if they exist
|
|
643
|
+
if (review.extendedProcedureReviews && review.extendedProcedureReviews.length > 0) {
|
|
644
|
+
review.extendedProcedureReviews.forEach((extReview) => {
|
|
645
|
+
extReview.isVerified = true;
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
|
|
602
649
|
// Update the review
|
|
603
650
|
batch.update(reviewRef, {
|
|
604
651
|
clinicReview: review.clinicReview,
|
|
605
652
|
practitionerReview: review.practitionerReview,
|
|
606
653
|
procedureReview: review.procedureReview,
|
|
654
|
+
extendedProcedureReviews: review.extendedProcedureReviews,
|
|
607
655
|
updatedAt: admin.firestore.FieldValue.serverTimestamp(),
|
|
608
656
|
});
|
|
609
657
|
|
|
@@ -45,6 +45,7 @@ export class ReviewService extends BaseService {
|
|
|
45
45
|
hasClinicReview: !!data.clinicReview,
|
|
46
46
|
hasPractitionerReview: !!data.practitionerReview,
|
|
47
47
|
hasProcedureReview: !!data.procedureReview,
|
|
48
|
+
extendedProcedureReviewsCount: data.extendedProcedureReviews?.length || 0,
|
|
48
49
|
practitionerId: data.practitionerReview?.practitionerId,
|
|
49
50
|
clinicId: data.clinicReview?.clinicId,
|
|
50
51
|
procedureId: data.procedureReview?.procedureId,
|
|
@@ -95,6 +96,22 @@ export class ReviewService extends BaseService {
|
|
|
95
96
|
ratings.push(procedureAverage);
|
|
96
97
|
}
|
|
97
98
|
|
|
99
|
+
// Process extended procedure reviews
|
|
100
|
+
if (data.extendedProcedureReviews && data.extendedProcedureReviews.length > 0) {
|
|
101
|
+
data.extendedProcedureReviews.forEach((extendedReview) => {
|
|
102
|
+
const extendedRatings = [
|
|
103
|
+
extendedReview.effectivenessOfTreatment,
|
|
104
|
+
extendedReview.outcomeExplanation,
|
|
105
|
+
extendedReview.painManagement,
|
|
106
|
+
extendedReview.followUpCare,
|
|
107
|
+
extendedReview.valueForMoney,
|
|
108
|
+
];
|
|
109
|
+
const extendedAverage = this.calculateAverage(extendedRatings);
|
|
110
|
+
extendedReview.overallRating = extendedAverage;
|
|
111
|
+
ratings.push(extendedAverage);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
98
115
|
const overallRating = this.calculateAverage(ratings);
|
|
99
116
|
|
|
100
117
|
// Generate a unique ID for the main review
|
|
@@ -118,6 +135,16 @@ export class ReviewService extends BaseService {
|
|
|
118
135
|
|
|
119
136
|
// Create the review object with timestamps
|
|
120
137
|
const now = new Date();
|
|
138
|
+
|
|
139
|
+
// Add IDs to extended procedure reviews
|
|
140
|
+
if (data.extendedProcedureReviews && data.extendedProcedureReviews.length > 0) {
|
|
141
|
+
data.extendedProcedureReviews.forEach((extendedReview) => {
|
|
142
|
+
extendedReview.id = this.generateId();
|
|
143
|
+
extendedReview.fullReviewId = reviewId;
|
|
144
|
+
extendedReview.createdAt = now;
|
|
145
|
+
extendedReview.updatedAt = now;
|
|
146
|
+
});
|
|
147
|
+
}
|
|
121
148
|
const review: Review = {
|
|
122
149
|
id: reviewId,
|
|
123
150
|
appointmentId,
|
|
@@ -125,6 +152,7 @@ export class ReviewService extends BaseService {
|
|
|
125
152
|
clinicReview: data.clinicReview,
|
|
126
153
|
practitionerReview: data.practitionerReview,
|
|
127
154
|
procedureReview: data.procedureReview,
|
|
155
|
+
extendedProcedureReviews: data.extendedProcedureReviews,
|
|
128
156
|
overallComment: data.overallComment,
|
|
129
157
|
overallRating,
|
|
130
158
|
createdAt: now,
|
|
@@ -147,6 +175,7 @@ export class ReviewService extends BaseService {
|
|
|
147
175
|
practitionerId: review.practitionerReview?.practitionerId,
|
|
148
176
|
clinicId: review.clinicReview?.clinicId,
|
|
149
177
|
procedureId: review.procedureReview?.procedureId,
|
|
178
|
+
extendedProcedureReviewsCount: review.extendedProcedureReviews?.length || 0,
|
|
150
179
|
});
|
|
151
180
|
|
|
152
181
|
// Note: Related entity updates (clinic, practitioner, procedure) are now handled
|
|
@@ -212,6 +241,23 @@ export class ReviewService extends BaseService {
|
|
|
212
241
|
};
|
|
213
242
|
}
|
|
214
243
|
|
|
244
|
+
// Enhance extended procedure reviews with names
|
|
245
|
+
if (enhancedReview.extendedProcedureReviews && enhancedReview.extendedProcedureReviews.length > 0) {
|
|
246
|
+
const extendedProcedures = appointment.metadata?.extendedProcedures || [];
|
|
247
|
+
enhancedReview.extendedProcedureReviews = enhancedReview.extendedProcedureReviews.map((extendedReview) => {
|
|
248
|
+
const procedureInfo = extendedProcedures.find(
|
|
249
|
+
(ep) => ep.procedureId === extendedReview.procedureId
|
|
250
|
+
);
|
|
251
|
+
if (procedureInfo) {
|
|
252
|
+
return {
|
|
253
|
+
...extendedReview,
|
|
254
|
+
procedureName: procedureInfo.procedureName,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
return extendedReview;
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
215
261
|
// Add patient name to the main review object
|
|
216
262
|
if (appointment.patientInfo) {
|
|
217
263
|
enhancedReview.patientName = appointment.patientInfo.fullName;
|
|
@@ -223,7 +269,8 @@ export class ReviewService extends BaseService {
|
|
|
223
269
|
enhancedReview.clinicReview?.clinicName ||
|
|
224
270
|
enhancedReview.practitionerReview?.practitionerName ||
|
|
225
271
|
enhancedReview.procedureReview?.procedureName ||
|
|
226
|
-
enhancedReview.patientName
|
|
272
|
+
enhancedReview.patientName ||
|
|
273
|
+
enhancedReview.extendedProcedureReviews?.some(epr => epr.procedureName)
|
|
227
274
|
),
|
|
228
275
|
});
|
|
229
276
|
|
|
@@ -50,6 +50,14 @@ export enum PatientRequirementOverallStatus {
|
|
|
50
50
|
FAILED_TO_PROCESS = "failedToProcess", // An error occurred during its creation or initial processing.
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Represents source procedure information for a requirement instance
|
|
55
|
+
*/
|
|
56
|
+
export interface RequirementSourceProcedure {
|
|
57
|
+
procedureId: string;
|
|
58
|
+
procedureName: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
53
61
|
/**
|
|
54
62
|
* Represents an instance of a backoffice Requirement, tailored to a specific patient and appointment.
|
|
55
63
|
* This document lives in the patient's subcollection: `patients/{patientId}/patientRequirements/{instanceId}`.
|
|
@@ -70,6 +78,9 @@ export interface PatientRequirementInstance {
|
|
|
70
78
|
// Contains each specific timed instruction derived from the Requirement's timeframe.notifyAt
|
|
71
79
|
instructions: PatientRequirementInstruction[];
|
|
72
80
|
|
|
81
|
+
// NEW: Track which procedure(s) this requirement comes from
|
|
82
|
+
sourceProcedures?: RequirementSourceProcedure[];
|
|
83
|
+
|
|
73
84
|
// Timestamps for the instance itself
|
|
74
85
|
createdAt: Timestamp;
|
|
75
86
|
updatedAt: Timestamp;
|