/** * 从 MotherShip dashboard 页面正文提取「官网业务拒价/阻断」文案,供前端如实展示。 * 非 RPA/系统错误,而是承运商或官网规则导致无法出价。 * 对外统一译为中文,避免英文原文直出给客户。 */ const ACCESSORIAL_BLOCK_PATTERNS: RegExp[] = [ /不支持附加服务[^\n。]{0,160}/, /(?:承运|运输)合作伙伴[^\n。]{0,80}不提供[^\n。]{0,120}/, /住宅地址[^\n。]{0,80}上门取件[^\n。]{0,80}/, /unsupported accessorial[^\n.]{0,200}/i, /accessorial(?:s)?[^\n.]{0,120}(?:not supported|not available|cannot be)/i, /our carrier partners? do not provide[^\n.]{0,220}/i, /carrier partners? (?:don'?t|do not) provide[^\n.]{0,220}/i, /(?:pickup|pick-up)[^\n.]{0,80}residential[^\n.]{0,120}/i, /residential[^\n.]{0,80}(?:pickup|pick-up)[^\n.]{0,120}/i, ]; /** 官网英文阻断 → 中文(客户可见) */ const PORTAL_EN_TO_ZH: Array<[RegExp, string]> = [ [ /no rates found[\s\S]*please review all required fields to proceed/i, "未找到可用报价。请检查并填写所有必填项后再继续", ], [/no rates found/i, "未找到可用报价"], [ /please review all required fields to proceed/i, "请检查并填写所有必填项后再继续", ], [/unsupported accessorial/i, "不支持该附加服务"], [ /our carrier partners? do not provide[^\n.]*/i, "承运合作伙伴不提供此项服务", ], [ /carrier partners? (?:don'?t|do not) provide[^\n.]*/i, "承运合作伙伴不提供此项服务", ], [ /(?:pickup|pick-up)[^\n.]{0,80}residential|residential[^\n.]{0,80}(?:pickup|pick-up)/i, "不支持住宅地址上门取件", ], [/accessorial(?:s)?[^\n.]{0,80}(?:not supported|not available)/i, "不支持该附加服务"], ]; function normalizePortalLine(line: string, keepNewlines = false): string { if (keepNewlines) { return line .replace(/\r/g, "") .replace(/[ \t]+/g, " ") .replace(/\n+/g, "\n") .trim(); } return line.replace(/\s+/g, " ").trim(); } /** 将单条官网提示译为中文;已是中文则原样返回 */ export function translatePortalMessageToZh(message: string): string { const trimmed = message.trim(); if (!trimmed) return trimmed; if (/[\u4e00-\u9fff]/.test(trimmed)) { return trimmed; } for (const [pattern, zh] of PORTAL_EN_TO_ZH) { if (pattern.test(trimmed)) { return zh; } } return "承运商官网提示:暂无法完成报价,请检查地址、货物或附加服务后重试"; } /** 提取页面可见的官网阻断/无价提示(去重、保序) */ export function extractMothershipPortalQuoteMessages(body: string): string[] { const text = body.replace(/\r/g, ""); const found: string[] = []; const push = (msg: string, keepNewlines = false) => { const n = normalizePortalLine(msg, keepNewlines); if (n && !found.includes(n)) found.push(n); }; const hasNoRates = /No rates found/i.test(text); const hasRequired = /Please review all required fields to proceed/i.test(text); if (hasNoRates && hasRequired) { push( "No rates found\nPlease review all required fields to proceed", true, ); } else if (hasNoRates) { push("No rates found"); } else if (hasRequired) { push("Please review all required fields to proceed"); } for (const p of ACCESSORIAL_BLOCK_PATTERNS) { const m = text.match(p); if (m?.[0]) push(m[0]); } return found; } /** 附加服务/承运商规则类阻断(应立刻如实返回,不等 patch/超时) */ export function extractMothershipAccessorialBlockMessages( body: string, ): string[] { const found: string[] = []; const push = (msg: string) => { const n = normalizePortalLine(msg); if (n && !found.includes(n)) found.push(n); }; for (const p of ACCESSORIAL_BLOCK_PATTERNS) { const m = body.replace(/\r/g, "").match(p); if (m?.[0]) push(m[0]); } return found; } export function formatMothershipPortalQuoteMessage( messages: string[], ): string | null { if (messages.length === 0) return null; return messages.map(translatePortalMessageToZh).join("\n\n"); } /** 从整页正文解析;无则 null */ export function parseMothershipPortalQuoteMessageFromBody( body: string, ): string | null { return formatMothershipPortalQuoteMessage( extractMothershipPortalQuoteMessages(body), ); }