diff --git a/src/claim-request-management/entites/schema/claim-request-management.schema.ts b/src/claim-request-management/entites/schema/claim-request-management.schema.ts index 77d68a1..2afc867 100644 --- a/src/claim-request-management/entites/schema/claim-request-management.schema.ts +++ b/src/claim-request-management/entites/schema/claim-request-management.schema.ts @@ -211,6 +211,9 @@ export class FileRating { @Prop({ type: Number, min: 0, max: 5 }) guiltyVehicleIdentification: number; + + @Prop({ type: Number, min: 0, max: 5 }) + botRating: number; } export class PriceDrop { diff --git a/src/expert-insurer/expert-insurer.controller.ts b/src/expert-insurer/expert-insurer.controller.ts index b16e826..1cfea41 100644 --- a/src/expert-insurer/expert-insurer.controller.ts +++ b/src/expert-insurer/expert-insurer.controller.ts @@ -69,6 +69,11 @@ export class ExpertInsurerController { ); } + @Get("statistics") + async getExpertStatistics(@CurrentUser() actor) { + return await this.expertInsurerService.getExpertStatisticsReport(actor); + } + @ApiParam({ name: "expertId" }) @ApiQuery({ name: "role", enum: ["claim", "blame"] }) @Get("/:expertId") @@ -120,13 +125,28 @@ export class ExpertInsurerController { example: 4, description: "تشخیص درست وسیله نقلیه مقصر", }, + botRating: { + type: "number", + minimum: 0, + maximum: 5, + example: 4, + description: "برای امتیاز دادن به بات", + }, }, required: [ "collisionMethodAccuracy", "evaluationTimeliness", "accidentCauseAccuracy", "guiltyVehicleIdentification", + "botRating", ], + example: { + collisionMethodAccuracy: 4, + evaluationTimeliness: 3, + accidentCauseAccuracy: 5, + guiltyVehicleIdentification: 4, + botRating: 4, + }, }, }) @ApiParam({ name: "requestId" }) diff --git a/src/expert-insurer/expert-insurer.service.ts b/src/expert-insurer/expert-insurer.service.ts index b1f8d21..7035a83 100644 --- a/src/expert-insurer/expert-insurer.service.ts +++ b/src/expert-insurer/expert-insurer.service.ts @@ -110,7 +110,7 @@ export class ExpertInsurerService { const insurerValues = insurerRating ? Object.values(insurerRating).filter( - (val): val is number => typeof val === "number" && !isNaN(val), + (val): val is number => typeof val === "number" && !isNaN(val), ) : []; const insurerAvg = @@ -768,4 +768,141 @@ export class ExpertInsurerService { return newBranch; } + + /** + * Get comprehensive statistics for all experts of a client + * Returns: + * - Percentage of insurer's rating to BOT + * - Percentage of expert's rating given by users + * - Percentage of files that have objection + * - Number of files created in the current month + */ + async getExpertStatisticsReport(actor: any) { + const { clientKey } = actor; + if (!clientKey) { + throw new BadRequestException("Client key is required"); + } + + const clientObjectId = new Types.ObjectId(clientKey); + + // Get all claim files for this client + const claimFiles = await this.claimRequestManagementModel + .find({ + userClientKey: clientObjectId, + }) + .lean(); + + // Calculate current month date range + const now = new Date(); + const monthStart = new Date(now.getFullYear(), now.getMonth(), 1); + const monthEnd = new Date(now.getFullYear(), now.getMonth() + 1, 0, 23, 59, 59); + + // Filter files created this month + const filesThisMonth = claimFiles.filter((file) => { + const createdAt = new Date(file.createdAt); + return createdAt >= monthStart && createdAt <= monthEnd; + }); + + // Calculate statistics + let totalInsurerRatings = 0; + let totalBotRatings = 0; + let filesWithInsurerRating = 0; + let filesWithBotRating = 0; + let filesWithUserRating = 0; + let filesWithObjection = 0; + + for (const file of claimFiles) { + const insurerRating = file?.rating; + const userRating = file?.userRating; + const objection = file?.objection; + + // Check for insurer rating (excluding botRating) + if (insurerRating) { + const insurerValues = [ + insurerRating.collisionMethodAccuracy, + insurerRating.evaluationTimeliness, + insurerRating.accidentCauseAccuracy, + insurerRating.guiltyVehicleIdentification, + ].filter((val): val is number => typeof val === "number" && !isNaN(val)); + + if (insurerValues.length > 0) { + filesWithInsurerRating++; + const insurerAvg = + insurerValues.reduce((a, b) => a + b, 0) / insurerValues.length; + totalInsurerRatings += insurerAvg; + } + } + + // Check for bot rating (if botRating field exists in rating object) + const botRating = (insurerRating as any)?.botRating; + if (botRating !== undefined && !isNaN(botRating) && typeof botRating === "number") { + filesWithBotRating++; + totalBotRatings += botRating; + } + + // Check for user rating + if (userRating) { + filesWithUserRating++; + } + + // Check for objection + if (objection) { + filesWithObjection++; + } + } + + // Calculate percentages + const totalFiles = claimFiles.length; + + // Calculate average insurer rating (excluding botRating) and average bot rating + const averageInsurerRating = + filesWithInsurerRating > 0 + ? totalInsurerRatings / filesWithInsurerRating + : 0; + const averageBotRating = + filesWithBotRating > 0 ? totalBotRatings / filesWithBotRating : 0; + + // Calculate percentage: (averageInsurerRating / averageBotRating) * 100 + // This shows what percentage the insurer rating is compared to bot rating + const insurerToBotPercentage = + averageBotRating > 0 && averageInsurerRating > 0 + ? parseFloat( + ((averageInsurerRating / averageBotRating) * 100).toFixed(2), + ) + : 0; + + const userRatingPercentage = + totalFiles > 0 + ? parseFloat(((filesWithUserRating / totalFiles) * 100).toFixed(2)) + : 0; + + const objectionPercentage = + totalFiles > 0 + ? parseFloat(((filesWithObjection / totalFiles) * 100).toFixed(2)) + : 0; + + return { + insurerToBotRatingPercentage: insurerToBotPercentage, + userRatingPercentage: userRatingPercentage, + objectionPercentage: objectionPercentage, + filesCreatedThisMonth: filesThisMonth.length, + totalFiles: totalFiles, + breakdown: { + filesWithInsurerRating, + filesWithBotRating, + filesWithUserRating, + filesWithObjection, + averageInsurerRating: + filesWithInsurerRating > 0 + ? parseFloat( + (totalInsurerRatings / filesWithInsurerRating).toFixed(2), + ) + : 0, + averageBotRating: + filesWithBotRating > 0 + ? parseFloat((totalBotRatings / filesWithBotRating).toFixed(2)) + : 0, + }, + }; + } } diff --git a/src/request-management/entities/schema/request-management.schema.ts b/src/request-management/entities/schema/request-management.schema.ts index ed054fa..e611065 100644 --- a/src/request-management/entities/schema/request-management.schema.ts +++ b/src/request-management/entities/schema/request-management.schema.ts @@ -346,6 +346,9 @@ export class FileRating { @Prop({ type: Number, min: 0, max: 5 }) guiltyVehicleIdentification: number; + + @Prop({ type: Number, min: 0, max: 5 }) + botRating: number; } // TODO clean all sub Object to other files