forked from Yara724/api
Compare commits
2 Commits
0eaf55e73c
...
6a26811555
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a26811555 | ||
|
|
f9d4b5df9a |
52
src/expert-insurer/expert-insurer.file-summary.spec.ts
Normal file
52
src/expert-insurer/expert-insurer.file-summary.spec.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { ExpertInsurerService } from "./expert-insurer.service";
|
||||
|
||||
describe("insurer expert file summaries", () => {
|
||||
const service = Object.create(
|
||||
ExpertInsurerService.prototype,
|
||||
) as ExpertInsurerService;
|
||||
|
||||
it("calculates blame review duration in minutes on the backend", () => {
|
||||
const summary = (service as any).mapBlameFileSummaryForInsurerExpert({
|
||||
_id: "6aa50ce70545fc1906433c85",
|
||||
publicId: "RPT832-00049",
|
||||
requestNo: "BL-RPT832-000049",
|
||||
type: "THIRD_PARTY",
|
||||
status: "WAITING_FOR_SIGNATURES",
|
||||
blameStatus: "AGREED",
|
||||
createdAt: new Date("2026-06-21T12:42:28.000Z"),
|
||||
updatedAt: new Date("2026-06-21T15:32:28.000Z"),
|
||||
});
|
||||
|
||||
expect(summary.reviewDurationMinutes).toBe(170);
|
||||
});
|
||||
|
||||
it("calculates claim review duration with the same contract", () => {
|
||||
const summary = (service as any).mapClaimFileSummaryForInsurerExpert({
|
||||
_id: "6aa50ce70545fc1906433c86",
|
||||
publicId: "RPT832-00049",
|
||||
createdAt: "2026-06-21T12:42:28.000Z",
|
||||
updatedAt: "2026-06-21T12:43:58.000Z",
|
||||
});
|
||||
|
||||
expect(summary.reviewDurationMinutes).toBe(1.5);
|
||||
});
|
||||
|
||||
it("returns null instead of NaN when a duration timestamp is unavailable", () => {
|
||||
const summary = (service as any).mapBlameFileSummaryForInsurerExpert({
|
||||
_id: "6aa50ce70545fc1906433c85",
|
||||
createdAt: "invalid",
|
||||
});
|
||||
|
||||
expect(summary.reviewDurationMinutes).toBeNull();
|
||||
});
|
||||
|
||||
it("clamps malformed negative durations to zero", () => {
|
||||
const summary = (service as any).mapClaimFileSummaryForInsurerExpert({
|
||||
_id: "6aa50ce70545fc1906433c86",
|
||||
createdAt: "2026-06-21T15:32:28.000Z",
|
||||
updatedAt: "2026-06-21T12:42:28.000Z",
|
||||
});
|
||||
|
||||
expect(summary.reviewDurationMinutes).toBe(0);
|
||||
});
|
||||
});
|
||||
@@ -264,6 +264,18 @@ export class ExpertInsurerService {
|
||||
return String(aid);
|
||||
}
|
||||
|
||||
private calculateReviewDurationMinutes(
|
||||
createdAt: Date | string | undefined,
|
||||
updatedAt: Date | string | undefined,
|
||||
): number | null {
|
||||
const startedAtMs = new Date(createdAt as any).getTime();
|
||||
const reviewedAtMs = new Date(updatedAt as any).getTime();
|
||||
if (Number.isNaN(startedAtMs) || Number.isNaN(reviewedAtMs)) return null;
|
||||
|
||||
const durationMinutes = Math.max(reviewedAtMs - startedAtMs, 0) / 60_000;
|
||||
return Math.round(durationMinutes * 100) / 100;
|
||||
}
|
||||
|
||||
private mapBlameFileSummaryForInsurerExpert(b: any) {
|
||||
return {
|
||||
kind: "blame" as const,
|
||||
@@ -275,6 +287,10 @@ export class ExpertInsurerService {
|
||||
blameStatus: b.blameStatus,
|
||||
createdAt: b.createdAt,
|
||||
updatedAt: b.updatedAt,
|
||||
reviewDurationMinutes: this.calculateReviewDurationMinutes(
|
||||
b.createdAt,
|
||||
b.updatedAt,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -288,6 +304,10 @@ export class ExpertInsurerService {
|
||||
claimStatus: c.claimStatus,
|
||||
createdAt: c.createdAt,
|
||||
updatedAt: c.updatedAt,
|
||||
reviewDurationMinutes: this.calculateReviewDurationMinutes(
|
||||
c.createdAt,
|
||||
c.updatedAt,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -89,4 +89,49 @@ describe("insurer timeline Persian labels", () => {
|
||||
captureKey: "front",
|
||||
});
|
||||
});
|
||||
|
||||
it("localizes the auto-resolved first-party confession reason", () => {
|
||||
expect(
|
||||
localizeTimelineMetadata({
|
||||
type: "AUTO_CONFESSION_SKIPPED",
|
||||
metadata: {
|
||||
reason:
|
||||
"IN_PERSON expert-initiated: first party is always guilty; confession auto-resolved",
|
||||
stepKey: "FIRST_BLAME_CONFESSION",
|
||||
advancedTo: "FIRST_VIDEO",
|
||||
},
|
||||
}),
|
||||
).toEqual({
|
||||
reason:
|
||||
"در پرونده حضوری ایجادشده توسط کارشناس، طرف اول همیشه مقصر است؛ بنابراین مرحله اقرار بهصورت خودکار تعیین تکلیف شد.",
|
||||
stepKey: "FIRST_BLAME_CONFESSION",
|
||||
advancedTo: "FIRST_VIDEO",
|
||||
});
|
||||
});
|
||||
|
||||
it("localizes the call-center auto-confession reason", () => {
|
||||
expect(
|
||||
localizeTimelineMetadata({
|
||||
type: "AUTO_CONFESSION_SKIPPED",
|
||||
metadata: {
|
||||
reason:
|
||||
"V6 call-center initiated: first party is always guilty; confession auto-resolved",
|
||||
advancedTo: "FIRST_VIDEO",
|
||||
},
|
||||
}),
|
||||
).toEqual({
|
||||
reason:
|
||||
"در پرونده ایجادشده توسط مرکز تماس، طرف اول همیشه مقصر است؛ بنابراین مرحله اقرار بهصورت خودکار تعیین تکلیف شد.",
|
||||
advancedTo: "FIRST_VIDEO",
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves user-entered reasons that are not system messages", () => {
|
||||
expect(
|
||||
localizeTimelineMetadata({
|
||||
type: "V5_FILE_MAKER_REJECTED",
|
||||
metadata: { reason: "تصویر کارت خودرو خوانا نیست" },
|
||||
}),
|
||||
).toEqual({ reason: "تصویر کارت خودرو خوانا نیست" });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -190,6 +190,13 @@ export const STEP_KEY_FA_LABELS: Record<string, string> = {
|
||||
const GENERIC_EVENT_FA_LABEL = "رویداد پرونده";
|
||||
const GENERIC_EVENT_FA_DESCRIPTION = "رویدادی در روند پرونده ثبت شد.";
|
||||
|
||||
const SYSTEM_REASON_FA_TRANSLATIONS: Record<string, string> = {
|
||||
"IN_PERSON expert-initiated: first party is always guilty; confession auto-resolved":
|
||||
"در پرونده حضوری ایجادشده توسط کارشناس، طرف اول همیشه مقصر است؛ بنابراین مرحله اقرار بهصورت خودکار تعیین تکلیف شد.",
|
||||
"V6 call-center initiated: first party is always guilty; confession auto-resolved":
|
||||
"در پرونده ایجادشده توسط مرکز تماس، طرف اول همیشه مقصر است؛ بنابراین مرحله اقرار بهصورت خودکار تعیین تکلیف شد.",
|
||||
};
|
||||
|
||||
/**
|
||||
* Persian descriptions for workflow-step events. These are deliberately
|
||||
* complete sentences because the insurer timeline exposes them as the event
|
||||
@@ -276,10 +283,15 @@ export function localizeTimelineMetadata(event: {
|
||||
}): Record<string, unknown> | null {
|
||||
if (!event.metadata) return null;
|
||||
|
||||
const reason = event.metadata.reason;
|
||||
const localizedReason =
|
||||
typeof reason === "string" ? SYSTEM_REASON_FA_TRANSLATIONS[reason] : null;
|
||||
|
||||
return {
|
||||
...event.metadata,
|
||||
...(Object.prototype.hasOwnProperty.call(event.metadata, "description")
|
||||
? { description: getEventFaDescription(event) }
|
||||
: {}),
|
||||
...(localizedReason ? { reason: localizedReason } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user