forked from Yara724/api
YARA-739, YARA-740, YARA-741
This commit is contained in:
@@ -57,6 +57,18 @@ export class ExpertInsurerController {
|
||||
);
|
||||
}
|
||||
|
||||
@Get("top-experts")
|
||||
async getTopExperts(@CurrentUser() actor) {
|
||||
return await this.expertInsurerService.getTopExpertsForClient(actor);
|
||||
}
|
||||
|
||||
@Get("top-files")
|
||||
async getTopFiles(@CurrentUser() insurer) {
|
||||
return await this.expertInsurerService.getTopFilesForClient(
|
||||
insurer.clientKey,
|
||||
);
|
||||
}
|
||||
|
||||
@ApiParam({ name: "expertId" })
|
||||
@ApiQuery({ name: "role", enum: ["claim", "blame"] })
|
||||
@Get("/:expertId")
|
||||
|
||||
@@ -100,20 +100,61 @@ export class ExpertInsurerService {
|
||||
Record<string, number[]>
|
||||
> = {};
|
||||
|
||||
const processRatings = (expertId: string, rating: any) => {
|
||||
if (!expertId || !rating || typeof rating !== "object") {
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Converts insurer FileRating and optional userRating into a single
|
||||
* combined score for a file.
|
||||
*/
|
||||
const getCombinedFileScore = (file: any): number | null => {
|
||||
const insurerRating = file?.rating;
|
||||
const userRating = file?.userRating;
|
||||
|
||||
const allRatingValues = Object.values(rating).filter(
|
||||
(val): val is number => typeof val === "number" && !isNaN(val),
|
||||
const insurerValues = insurerRating
|
||||
? Object.values(insurerRating).filter(
|
||||
(val): val is number => typeof val === "number" && !isNaN(val),
|
||||
)
|
||||
: [];
|
||||
const insurerAvg =
|
||||
insurerValues.length > 0
|
||||
? insurerValues.reduce((a, b) => a + b, 0) / insurerValues.length
|
||||
: null;
|
||||
|
||||
const userValues = userRating
|
||||
? [
|
||||
userRating.progressSpeed,
|
||||
userRating.registrationEase,
|
||||
userRating.overallEvaluation,
|
||||
].filter((val) => typeof val === "number" && !isNaN(val))
|
||||
: [];
|
||||
const userAvg =
|
||||
userValues.length > 0
|
||||
? userValues.reduce((a, b) => a + b, 0) / userValues.length
|
||||
: null;
|
||||
|
||||
const scores = [insurerAvg, userAvg].filter(
|
||||
(v): v is number => typeof v === "number" && !isNaN(v),
|
||||
);
|
||||
if (allRatingValues.length > 0) {
|
||||
if (scores.length === 0) return null;
|
||||
return parseFloat(
|
||||
(scores.reduce((a, b) => a + b, 0) / scores.length).toFixed(2),
|
||||
);
|
||||
};
|
||||
|
||||
const processRatings = (expertId: string, file: any) => {
|
||||
if (!expertId) return;
|
||||
|
||||
const rating = file?.rating;
|
||||
const combinedScore = getCombinedFileScore(file);
|
||||
|
||||
// Aggregate overall combined score for this expert
|
||||
if (combinedScore !== null) {
|
||||
if (!expertTotalRatingsMap[expertId])
|
||||
expertTotalRatingsMap[expertId] = [];
|
||||
expertTotalRatingsMap[expertId].push(...allRatingValues);
|
||||
expertTotalRatingsMap[expertId].push(combinedScore);
|
||||
}
|
||||
|
||||
// Keep backward-compatible per-category insurer ratings
|
||||
if (!rating || typeof rating !== "object") return;
|
||||
|
||||
if (!expertRatingsByCategoryMap[expertId]) {
|
||||
expertRatingsByCategoryMap[expertId] = {};
|
||||
}
|
||||
@@ -129,12 +170,12 @@ export class ExpertInsurerService {
|
||||
|
||||
for (const file of blameFiles) {
|
||||
const expertId = file?.actorLocked?.actorId?.toString();
|
||||
processRatings(expertId, file.rating);
|
||||
processRatings(expertId, file);
|
||||
}
|
||||
|
||||
for (const file of claimFiles) {
|
||||
const expertId = file?.damageExpertReply?.actorDetail?.actorId;
|
||||
processRatings(expertId, file.rating);
|
||||
processRatings(expertId, file);
|
||||
}
|
||||
|
||||
const allExperts = allExpertsRaw.map((expert) => {
|
||||
@@ -182,6 +223,98 @@ export class ExpertInsurerService {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns top 10 experts for the current insurer client based on
|
||||
* combined insurer + user ratings on their handled files.
|
||||
*/
|
||||
async getTopExpertsForClient(actor): Promise<any[]> {
|
||||
const result = await this.retrieveAllExpertsOfClient(actor, 1, 1000);
|
||||
const experts = result?.experts || [];
|
||||
|
||||
const sorted = [...experts].sort((a, b) => {
|
||||
const ar = a.overallAverageRating ?? 0;
|
||||
const br = b.overallAverageRating ?? 0;
|
||||
return br - ar;
|
||||
});
|
||||
|
||||
return sorted.slice(0, 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns top 10 claim files for the current insurer client based on
|
||||
* combined insurer + user ratings.
|
||||
*/
|
||||
async getTopFilesForClient(insurerId: string): Promise<any[]> {
|
||||
const id = new Types.ObjectId(insurerId);
|
||||
|
||||
const claimFiles = await this.claimRequestManagementModel
|
||||
.find(
|
||||
{
|
||||
userClientKey: id,
|
||||
},
|
||||
{
|
||||
requestNumber: 1,
|
||||
userClientKey: 1,
|
||||
userId: 1,
|
||||
fullName: 1,
|
||||
carDetail: 1,
|
||||
carPlate: 1,
|
||||
claimStatus: 1,
|
||||
rating: 1,
|
||||
userRating: 1,
|
||||
damageExpertReply: 1,
|
||||
damageExpertReplyFinal: 1,
|
||||
createdAt: 1,
|
||||
},
|
||||
)
|
||||
.lean();
|
||||
|
||||
const scored = claimFiles
|
||||
.map((file) => {
|
||||
const insurerRating = file?.rating;
|
||||
const userRating = file?.userRating;
|
||||
|
||||
const insurerValues = insurerRating
|
||||
? Object.values(insurerRating).filter(
|
||||
(val): val is number => typeof val === "number" && !isNaN(val),
|
||||
)
|
||||
: [];
|
||||
const insurerAvg =
|
||||
insurerValues.length > 0
|
||||
? insurerValues.reduce((a, b) => a + b, 0) / insurerValues.length
|
||||
: null;
|
||||
|
||||
const userValues = userRating
|
||||
? [
|
||||
userRating.progressSpeed,
|
||||
userRating.registrationEase,
|
||||
userRating.overallEvaluation,
|
||||
].filter((val) => typeof val === "number" && !isNaN(val))
|
||||
: [];
|
||||
const userAvg =
|
||||
userValues.length > 0
|
||||
? userValues.reduce((a, b) => a + b, 0) / userValues.length
|
||||
: null;
|
||||
|
||||
const scores = [insurerAvg, userAvg].filter(
|
||||
(v): v is number => typeof v === "number" && !isNaN(v),
|
||||
);
|
||||
if (scores.length === 0) return null;
|
||||
|
||||
const combinedScore =
|
||||
scores.reduce((a, b) => a + b, 0) / scores.length;
|
||||
|
||||
return {
|
||||
...file,
|
||||
combinedScore: parseFloat(combinedScore.toFixed(2)),
|
||||
};
|
||||
})
|
||||
.filter((f) => f !== null);
|
||||
|
||||
const sorted = scored.sort((a, b) => b.combinedScore - a.combinedScore);
|
||||
return sorted.slice(0, 10);
|
||||
}
|
||||
|
||||
async getAllFilesByExpertAndRole(expertId: string, role: "claim" | "blame") {
|
||||
const expertObjectId = new Types.ObjectId(expertId);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user