import type { QuoteRecord } from "@prisma/client"; import type { AlertType } from "@/modules/alert/types"; import { formatQuoteErrorMessage } from "@/modules/quote/quote-error-messages"; const CARGO_TYPE_ZH: Record = { general_freight: "普通货物", machinery: "机械设备", furniture: "家具", electronics: "电子产品", building_materials: "建材", auto_parts: "汽车配件", food_nonperishable: "非生鲜食品", apparel: "服装", other: "其他", }; function asRecord(value: unknown): Record | null { if (value && typeof value === "object" && !Array.isArray(value)) { return value as Record; } return null; } /** 客户填写的地址(街道/城市/州/邮编) */ export function formatCustomerAddressForAdmin(addr: unknown): string { const o = asRecord(addr); if (!o) { return "—"; } const formatted = typeof o.formatted_address === "string" ? o.formatted_address.trim() : ""; const parts = [ typeof o.street === "string" ? o.street.trim() : "", typeof o.city === "string" ? o.city.trim() : "", typeof o.state === "string" ? o.state.trim() : "", typeof o.zip === "string" ? o.zip.trim() : "", ].filter(Boolean); if (parts.length > 0) { return parts.join(", "); } return formatted || "—"; } /** MotherShip 联想确认后的精确地址 */ export function formatSelectedAddressForAdmin(addr: unknown): string { const o = asRecord(addr); if (!o) { return "—"; } const label = (typeof o.mothership_display_label === "string" && o.mothership_display_label.trim()) || ""; return label || formatCustomerAddressForAdmin(addr); } /** @deprecated 使用 formatSelectedAddressForAdmin */ export function formatAddressForAdmin(addr: unknown): string { return formatSelectedAddressForAdmin(addr); } export function formatCargoForAdmin( cargo: unknown, record?: Pick< QuoteRecord, "weightLb" | "palletCount" | "cargoType" | "dimLIn" | "dimWIn" | "dimHIn" > | null, ): string { const o = asRecord(cargo); const pallets = record?.palletCount ?? o?.pallet_count; const cargoType = record?.cargoType ?? o?.cargo_type; const weightLb = record?.weightLb != null ? Number(record.weightLb) : typeof o?.weight_lb === "number" ? o.weight_lb : null; const parts: string[] = []; if (pallets != null) { parts.push(`${pallets} 托`); } if (weightLb != null && Number.isFinite(weightLb)) { parts.push(`${weightLb.toFixed(2)} lb`); } if (record) { parts.push( `${Number(record.dimLIn)}×${Number(record.dimWIn)}×${Number(record.dimHIn)} in`, ); } else if (o?.dimensions && typeof o.dimensions === "object") { const d = o.dimensions as Record; const unit = d.unit === "cm" ? "cm" : "in"; parts.push( `${d.length}×${d.width}×${d.height} ${unit}`, ); } if (typeof cargoType === "string") { parts.push(CARGO_TYPE_ZH[cargoType] ?? cargoType); } return parts.length > 0 ? parts.join(" · ") : "—"; } function normalizeTechnicalReason(raw: string): string { const text = raw.trim(); if (!text) { return ""; } if (/browserType\.launch/i.test(text)) { return "RPA Worker 无法启动浏览器(Playwright),请检查 Worker 容器资源、Chromium 依赖与进程日志"; } if (/Executable doesn't exist|chromium_headless_shell/i.test(text)) { return "服务器未安装 Playwright 浏览器,请在 Worker 镜像中执行浏览器安装"; } if (/SESSION_EXPIRED|storageState|会话/i.test(text)) { return "MotherShip 登录会话失效,需重新录制 .rpa/mothership-storage.json"; } if (/RPA_DATA_INVALID|无法解析.*报价/i.test(text)) { return "MotherShip 返回的报价数据无法解析为完整档位"; } if (/QUOTE_ENTRY_UNAVAILABLE|报价入口/i.test(text)) { return "MotherShip 报价页面无法打开或被重定向到登录页"; } if (/ADDRESS_NOT_CONFIRMED|地址.*无法定位/i.test(text)) { return "已选地址在 MotherShip 无法定位,需重新联想并确认"; } if (/processing 超过|QUOTE_TIMEOUT/i.test(text)) { return `询价超时(超过 ${Math.round(Number(process.env.QUOTE_TIMEOUT_MS ?? 420_000) / 1000)} 秒未完成)`; } return text; } const ALERT_TYPE_DEFAULT_CAUSE: Partial> = { RPA_FAILED: "询价自动化失败,未能获取 MotherShip 实时报价", RPA_CAPTCHA: "MotherShip 触发人机验证,RPA 已暂停", STRUCT_CHANGE: "MotherShip 页面结构变更,自动化脚本需更新", SESSION_EXPIRED: "MotherShip 会话过期,需重新登录或录制 cookies", STALE_FALLBACK: "实时询价失败,已降级使用历史缓存报价", PRICE_DEVIATION: "报价与历史基准偏差超过阈值", MANUAL_FALLBACK: "已触发人工兜底询价流程", SECURITY: "检测到安全相关异常请求", }; /** 管理端展示用:准确中文根因,避免直接展示 JSON */ export function resolveAlertRootCause( alertType: AlertType, detail: unknown, quote?: QuoteRecord | null, ): string { const d = asRecord(detail); const reason = typeof d?.reason === "string" ? normalizeTechnicalReason(d.reason) : typeof d?.error === "string" ? normalizeTechnicalReason(d.error) : ""; if (quote?.errorMessage?.trim()) { return normalizeTechnicalReason(quote.errorMessage.trim()); } if (quote?.errorCode) { return formatQuoteErrorMessage(quote.errorCode, quote.errorMessage); } if (reason) { return reason; } return ALERT_TYPE_DEFAULT_CAUSE[alertType] ?? "系统预警,请结合报价单号追查"; } export type AlertPresentation = { root_cause: string; customer_id: string | null; pickup_customer: string; pickup_selected: string; delivery_customer: string; delivery_selected: string; cargo_summary: string; }; export function buildAlertPresentation( alertType: AlertType, detail: unknown, quote?: QuoteRecord | null, ): AlertPresentation { const pickup = quote?.pickupJson ?? null; const delivery = quote?.deliveryJson ?? null; return { root_cause: resolveAlertRootCause(alertType, detail, quote), customer_id: quote?.customerId ?? (typeof asRecord(detail)?.customer_id === "string" ? (asRecord(detail)!.customer_id as string) : null), pickup_customer: formatCustomerAddressForAdmin(pickup), pickup_selected: formatSelectedAddressForAdmin(pickup), delivery_customer: formatCustomerAddressForAdmin(delivery), delivery_selected: formatSelectedAddressForAdmin(delivery), cargo_summary: formatCargoForAdmin(null, quote ?? null), }; }