import { ClaimVehicleTypeV2, } from "src/static/outer-car-parts-catalog"; import { catalogPartIdFromSelectedPart, parseCatalogPartIdInput, resolvePartForExpertReply, type DamageSelectedPartV2, } from "src/helpers/outer-damage-parts"; /** Canonical severity values for expert input (API). */ export type PriceDropSeverity = "Minor" | "Moderate" | "Severe"; export const PRICE_DROP_SEVERITY_FA: Record = { Minor: "جزیی", Moderate: "متوسط", Severe: "شدید", }; export const PRICE_DROP_SEVERITY_OPTIONS = ( Object.entries(PRICE_DROP_SEVERITY_FA) as [PriceDropSeverity, string][] ).map(([value, label_fa]) => ({ value, label_fa })); /** Part → severity → coefficient (same table as V1 expert-claim). */ export const PRICE_DROP_PART_TABLE: Record< string, Partial> > = { backFender: { Minor: 2, Moderate: 3, Severe: 5 }, backDoor: { Minor: 1, Moderate: 2, Severe: 3 }, frontDoor: { Minor: 1, Moderate: 2, Severe: 3 }, frontFender: { Minor: 1, Moderate: 2, Severe: 3 }, frontBumper: { Minor: 1, Moderate: 2, Severe: 3 }, Hood: { Minor: 2, moderate: 3, Severe: 4 }, Trunk: { minor: 1, moderate: 3, Severe: 5 }, Roof: { Minor: 3, Moderate: 5, Severe: 7 }, coil: { Minor: 2, Moderate: 3, Severe: 4 }, column: { Minor: 2, Moderate: 3, Severe: 4 }, frontTray: { Minor: 1, Moderate: 2, Severe: 3 }, backTray: { Minor: 2, moderate: 4, Severe: 5 }, frontChassis: { Minor: 3, Moderate: 5, Severe: 7 }, backChassis: { Minor: 2, Moderate: 4, Severe: 6 }, carFootrest: { Minor: 1, Moderate: 2, Severe: 3 }, carFloor: { Minor: 4, moderate: 6, Severe: 8 }, }; /** Jalali model year → year coefficient (V1). */ export const PRICE_DROP_YEAR_COEFFICIENT: Record = { 1393: 2.05, 1394: 2.1, 1395: 2.2, 1396: 2.3, 1397: 2.4, 1398: 2.5, 1399: 2.6, 1400: 2.7, 1401: 2.8, 1402: 2.9, 1403: 3, 1404: 3, }; const PRICE_DROP_PART_LABEL_FA: Record = { backFender: "گلگیر عقب", backDoor: "در عقب", frontDoor: "در جلو", frontFender: "گلگیر جلو", frontBumper: "سپر جلو", Hood: "کاپوت", Trunk: "صندوق عقب", Roof: "سقف", coil: "لوله", column: "ستون", frontTray: "سینی جلو", backTray: "سینی عقب", frontChassis: "شاسی جلو", backChassis: "شاسی عقب", carFootrest: "پا رکاب", carFloor: "کف خودرو", }; /** Catalog / damage `name` segments → price-drop part key. */ const SEGMENT_ALIASES: Record = { backfender: "backFender", backdoor: "backDoor", frontdoor: "frontDoor", frontfender: "frontFender", frontbumper: "frontBumper", backbumper: "frontBumper", carhood: "Hood", hood: "Hood", cartrunk: "Trunk", trunk: "Trunk", roof: "Roof", coil: "coil", column: "column", fronttray: "frontTray", backtray: "backTray", frontchassis: "frontChassis", backchassis: "backChassis", carfootrest: "carFootrest", carfloor: "carFloor", }; const NORM_TO_PART_KEY = new Map(); for (const key of Object.keys(PRICE_DROP_PART_TABLE)) { NORM_TO_PART_KEY.set(normalizePriceDropKey(key), key); } for (const [seg, key] of Object.entries(SEGMENT_ALIASES)) { NORM_TO_PART_KEY.set(seg, key); } export function normalizePriceDropKey(str: string): string { return String(str ?? "") .toLowerCase() .replace(/[^a-z0-9]/g, ""); } export function parsePriceDropNumber(input: number | string): number { if (typeof input === "number" && Number.isFinite(input)) return input; return Number( String(input) .replace(/[۰-۹]/g, (d) => String.fromCharCode(d.charCodeAt(0) - 1728)) .replace(/,/g, "") .trim(), ); } export function resolveSeverityCoefficient( partKey: string, severity: string, ): number | undefined { const row = PRICE_DROP_PART_TABLE[partKey]; if (!row) return undefined; const sev = String(severity ?? "").trim(); if (row[sev] !== undefined) return row[sev]; const cap = sev.charAt(0).toUpperCase() + sev.slice(1).toLowerCase(); if (row[cap] !== undefined) return row[cap]; const low = sev.toLowerCase(); if (row[low] !== undefined) return row[low]; return undefined; } export function calculateClaimPriceDrop( carPrice: number | string, carModelYear: number, coefficients: number[], ): { carPrice: number; carModel: number; carValue: number[]; sumOfSeverity: number; coefficientYear: number; total: number; } { const normalizePrice = parsePriceDropNumber(carPrice); const coefficient = PRICE_DROP_YEAR_COEFFICIENT[carModelYear] ?? 1; const carValue = coefficients.filter((n) => Number.isFinite(n)); const sumOfSeverity = carValue.reduce((a, c) => a + c, 0); const total = (normalizePrice * coefficient * sumOfSeverity) / 400; return { carPrice: normalizePrice, carModel: carModelYear, carValue, sumOfSeverity, coefficientYear: coefficient, total: total || 0, }; } export function buildPriceDropCatalogForApi(): Array<{ key: string; label_fa: string; severities: Record; }> { return Object.keys(PRICE_DROP_PART_TABLE).map((key) => { const row = PRICE_DROP_PART_TABLE[key]; return { key, label_fa: PRICE_DROP_PART_LABEL_FA[key] ?? key, severities: { Minor: resolveSeverityCoefficient(key, "Minor"), Moderate: resolveSeverityCoefficient(key, "Moderate"), Severe: resolveSeverityCoefficient(key, "Severe"), }, }; }); } export function resolvePriceDropPartKeyFromDamagePart(part: { name?: string; catalogKey?: string; }): string | null { const candidates: string[] = []; if (part.catalogKey) { for (const seg of String(part.catalogKey).split("_")) { if (seg) candidates.push(seg); } } if (part.name) candidates.push(String(part.name)); for (const raw of candidates) { const norm = normalizePriceDropKey(raw); const hit = NORM_TO_PART_KEY.get(norm); if (hit && PRICE_DROP_PART_TABLE[hit]) return hit; } return null; } export function buildDamagedPartsPriceDropLines( selectedParts: DamageSelectedPartV2[], ): Array<{ partId: number | null; name: string; side: string; label_fa: string; priceDropPartKey: string | null; mappable: boolean; }> { return selectedParts.map((sp) => { const priceDropPartKey = resolvePriceDropPartKeyFromDamagePart(sp); return { partId: catalogPartIdFromSelectedPart(sp), name: sp.name, side: sp.side, label_fa: sp.label_fa, priceDropPartKey, mappable: priceDropPartKey != null, }; }); } /** Resolve Jalali year from linked blame party inquiry (owner party preferred). */ export function resolveVehicleModelYearFromBlame( claim: { owner?: { userId?: unknown } }, blame: { parties?: Array<{ role?: string; person?: { userId?: unknown }; vehicle?: { inquiry?: { mapped?: { ModelField?: unknown; ModelCii?: unknown }; ModelField?: unknown; ModelCii?: unknown; }; }; }>; } | null, ): number | null { if (!blame?.parties?.length) return null; const ownerId = claim.owner?.userId != null ? String(claim.owner.userId) : ""; let party = blame.parties.find( (p) => ownerId && p.person?.userId != null && String(p.person.userId) === ownerId, ) ?? blame.parties.find((p) => p.role === "FIRST"); if (!party) party = blame.parties[0]; const inquiry = party?.vehicle?.inquiry; const mapped = inquiry?.mapped ?? inquiry; const raw = (mapped as { ModelField?: unknown })?.ModelField ?? (mapped as { ModelCii?: unknown })?.ModelCii; if (raw == null || raw === "") return null; const n = typeof raw === "number" ? raw : parseInt(String(raw).trim(), 10); return Number.isFinite(n) ? n : null; } export interface PriceDropPartSeverityInput { partId: number | string; severity: PriceDropSeverity; } export function buildCoefficientsFromPartSeverities( selectedParts: DamageSelectedPartV2[], partSeverities: PriceDropPartSeverityInput[], carType?: ClaimVehicleTypeV2, ): { coefficients: number[]; lines: Array<{ partId: number; priceDropPartKey: string; label_fa: string; severity: PriceDropSeverity; coefficient: number; }>; errors: string[]; } { const coefficients: number[] = []; const lines: Array<{ partId: number; priceDropPartKey: string; label_fa: string; severity: PriceDropSeverity; coefficient: number; }> = []; const errors: string[] = []; for (const row of partSeverities) { const catalogId = parseCatalogPartIdInput(row.partId); if (catalogId == null) { errors.push(`Unknown partId "${row.partId}".`); continue; } const sp = resolvePartForExpertReply( catalogId, selectedParts, carType, ); if (!sp) { errors.push(`Unknown partId "${row.partId}".`); continue; } const partKey = resolvePriceDropPartKeyFromDamagePart(sp); if (!partKey) { errors.push( `Part "${row.partId}" (${sp.label_fa}) has no price-drop mapping.`, ); continue; } const coef = resolveSeverityCoefficient(partKey, row.severity); if (coef === undefined) { errors.push( `Invalid severity "${row.severity}" for part key "${partKey}".`, ); continue; } coefficients.push(coef); const linePartId = catalogPartIdFromSelectedPart(sp); if (linePartId == null) { errors.push(`Part "${row.partId}" has no catalog id.`); continue; } lines.push({ partId: linePartId, priceDropPartKey: partKey, label_fa: sp.label_fa, severity: row.severity, coefficient: coef, }); } return { coefficients, lines, errors }; }