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);
|
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) {
|
private mapBlameFileSummaryForInsurerExpert(b: any) {
|
||||||
return {
|
return {
|
||||||
kind: "blame" as const,
|
kind: "blame" as const,
|
||||||
@@ -275,6 +287,10 @@ export class ExpertInsurerService {
|
|||||||
blameStatus: b.blameStatus,
|
blameStatus: b.blameStatus,
|
||||||
createdAt: b.createdAt,
|
createdAt: b.createdAt,
|
||||||
updatedAt: b.updatedAt,
|
updatedAt: b.updatedAt,
|
||||||
|
reviewDurationMinutes: this.calculateReviewDurationMinutes(
|
||||||
|
b.createdAt,
|
||||||
|
b.updatedAt,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,6 +304,10 @@ export class ExpertInsurerService {
|
|||||||
claimStatus: c.claimStatus,
|
claimStatus: c.claimStatus,
|
||||||
createdAt: c.createdAt,
|
createdAt: c.createdAt,
|
||||||
updatedAt: c.updatedAt,
|
updatedAt: c.updatedAt,
|
||||||
|
reviewDurationMinutes: this.calculateReviewDurationMinutes(
|
||||||
|
c.createdAt,
|
||||||
|
c.updatedAt,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -89,4 +89,49 @@ describe("insurer timeline Persian labels", () => {
|
|||||||
captureKey: "front",
|
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_LABEL = "رویداد پرونده";
|
||||||
const GENERIC_EVENT_FA_DESCRIPTION = "رویدادی در روند پرونده ثبت شد.";
|
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
|
* Persian descriptions for workflow-step events. These are deliberately
|
||||||
* complete sentences because the insurer timeline exposes them as the event
|
* complete sentences because the insurer timeline exposes them as the event
|
||||||
@@ -276,10 +283,15 @@ export function localizeTimelineMetadata(event: {
|
|||||||
}): Record<string, unknown> | null {
|
}): Record<string, unknown> | null {
|
||||||
if (!event.metadata) return null;
|
if (!event.metadata) return null;
|
||||||
|
|
||||||
|
const reason = event.metadata.reason;
|
||||||
|
const localizedReason =
|
||||||
|
typeof reason === "string" ? SYSTEM_REASON_FA_TRANSLATIONS[reason] : null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...event.metadata,
|
...event.metadata,
|
||||||
...(Object.prototype.hasOwnProperty.call(event.metadata, "description")
|
...(Object.prototype.hasOwnProperty.call(event.metadata, "description")
|
||||||
? { description: getEventFaDescription(event) }
|
? { description: getEventFaDescription(event) }
|
||||||
: {}),
|
: {}),
|
||||||
|
...(localizedReason ? { reason: localizedReason } : {}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user