/** Priority1 公开询价页演示/批量测试输入 */ import type { ExpeditedTrailerType, FtlAdditionalService, FtlTrailerType, Priority1ShipmentType, } from "@/workers/rpa/priority1/shipment-types"; import { EXPEDITED_TRAILER, FTL_ADDITIONAL, FTL_TRAILER, getShipmentConfig, } from "@/workers/rpa/priority1/shipment-types"; export type Priority1DemoInput = { shipmentType: Priority1ShipmentType; originZip: string; destinationZip: string; shipmentFrequency: string; pickupDate: string; email: string; phone: string; phoneCountry: string; /** LTL */ lengthIn: string; widthIn: string; heightIn: string; weightLb: string; palletCount: string; freightClass: string; packaging: string; pickupLocation: string; deliveryLocation: string; pickupServices: string[]; deliveryServices: string[]; /** FTL / Expedited 共用 */ commodity: string; /** FTL */ ftlTrailer: FtlTrailerType; ftlAdditionalServices: FtlAdditionalService[]; /** Expedited 主类型 Step 2 拖车 */ expeditedTrailer: ExpeditedTrailerType; /** FTL Open Deck 子类型 */ openDeckSubtype: string; /** FTL 温控 */ tempMinF: string; tempMaxF: string; /** FTL 选 Expedited 拖车后的子车型 */ ftlExpeditedTrailer: ExpeditedTrailerType; /** 仅 Large Straight 可选 Liftgate */ expeditedLiftgate: boolean; /** FTL 内嵌 Expedited — 特殊处理要求(可选) */ specialHandlingServices: string[]; }; const COMMON_STEP1 = { shipmentFrequency: "One Time Shipment", email: "niqijiti542@gmail.com", }; /** P08 canonical — LTL 全附加服务(11 项) */ export const P08_LTL_ALL_ACCESSORIALS = { pickup: [ "Liftgate at Pickup", "Inside Pickup", "Limited Access Pickup", "Residential Pickup", ], delivery: [ "Liftgate at Delivery", "Inside Delivery", "Delivery Appointment", "Call Before Delivery", "Limited Access Delivery", "Hazardous Material", "Residential Delivery", ], } as const; export const P08_LTL_ACCESSORIAL_LABELS: readonly string[] = [ ...P08_LTL_ALL_ACCESSORIALS.pickup, ...P08_LTL_ALL_ACCESSORIALS.delivery, ]; export const DEFAULT_PRIORITY1_DEMO: Priority1DemoInput = { shipmentType: "ltl", originZip: "10118", destinationZip: "90017", pickupDate: "07/15/2026", phone: "2125550198", phoneCountry: "US", ...COMMON_STEP1, lengthIn: "48", widthIn: "40", heightIn: "48", weightLb: "30", palletCount: "2", freightClass: "70", packaging: "Pallet", pickupLocation: "Residence / Home Business", deliveryLocation: "Residence / Home Business", pickupServices: ["Liftgate at Pickup", "Residential Pickup"], deliveryServices: ["Liftgate at Delivery", "Residential Delivery"], commodity: "General Freight", ftlTrailer: "dry_van", ftlAdditionalServices: [], expeditedTrailer: "sprinter_van", openDeckSubtype: "48 Foot Flatbed", tempMinF: "32", tempMaxF: "40", ftlExpeditedTrailer: "sprinter_van", expeditedLiftgate: false, specialHandlingServices: [], }; export const DEFAULT_FTL_DEMO: Priority1DemoInput = { ...DEFAULT_PRIORITY1_DEMO, shipmentType: "ftl", originZip: "32801", destinationZip: "28202", pickupDate: "07/14/2026", phone: "7536407420", commodity: "Electronics", weightLb: "15000", palletCount: "18", ftlTrailer: "dry_van", ftlAdditionalServices: ["full_ftl"], }; /** FTL Open Deck — 见 form-logic-map FTL_CONDITIONAL_BRANCHES.ftl_open_deck */ export const DEFAULT_FTL_OPEN_DECK_DEMO: Priority1DemoInput = { ...DEFAULT_FTL_DEMO, ftlTrailer: "open_deck", openDeckSubtype: "48 Foot Flatbed", ftlAdditionalServices: ["full_ftl"], commodity: "Electronics", weightLb: "12000", palletCount: "14", }; /** FTL Temperature Controlled — 见 form-logic-map FTL_CONDITIONAL_BRANCHES.ftl_temp */ export const DEFAULT_FTL_TEMP_DEMO: Priority1DemoInput = { ...DEFAULT_FTL_DEMO, ftlTrailer: "temperature_controlled", ftlAdditionalServices: ["full_ftl"], commodity: "Electronics", tempMinF: "32", tempMaxF: "40", weightLb: "10000", palletCount: "16", }; export const DEFAULT_EXPEDITED_DEMO: Priority1DemoInput = { ...DEFAULT_PRIORITY1_DEMO, shipmentType: "expedited", originZip: "32801", destinationZip: "28202", pickupDate: "07/14/2026", phone: "7536407420", commodity: "Electronics", weightLb: "800", expeditedTrailer: "sprinter_van", }; /** FTL 内嵌 Expedited — 见 form-logic-map FTL_CONDITIONAL_BRANCHES.ftl_expedited_sub */ export const DEFAULT_FTL_FTL_EXPEDITED_DEMO: Priority1DemoInput = { ...DEFAULT_FTL_DEMO, ftlTrailer: "ftl_expedited", ftlAdditionalServices: [], ftlExpeditedTrailer: "sprinter_van", commodity: "Electronics", weightLb: "2500", expeditedLiftgate: false, }; export type Priority1CanonicalPath = { id: string; title: string; /** 用户画像 / 需求简述 */ persona: string; input: Priority1DemoInput; /** recorded=已录制或 visual chain 跑通;logic-only=仅逻辑映射待录制 */ verified: "recorded" | "logic-only"; }; /** * 全部 canonical 路径 — 覆盖 Priority1 表单各决策分支与代表性选项组合。 * 录制:npm run record:priority1 -- 后按各路径 id 手动走一遍。 */ export const PRIORITY1_CANONICAL_PATHS: Priority1CanonicalPath[] = [ { id: "P01-ltl-residential", title: "LTL 住宅 + 常用附加", persona: "电商卖家,住宅提货派送,需尾板", input: DEFAULT_PRIORITY1_DEMO, verified: "recorded", }, { id: "P02-ltl-business-dock", title: "LTL 商业有装卸台", persona: "仓库出货,双方均有 dock/forklift", input: { ...DEFAULT_PRIORITY1_DEMO, pickupLocation: "Business (with loading docks or forklift)", deliveryLocation: "Business (with loading docks or forklift)", pickupServices: [], deliveryServices: [], }, verified: "recorded", }, { id: "P03-ltl-business-no-dock", title: "LTL 商业无 dock", persona: "小型门店,需尾板提货/派送", input: { ...DEFAULT_PRIORITY1_DEMO, pickupLocation: "Business (no loading dock, no forklift)", deliveryLocation: "Business (no loading dock, no forklift)", pickupServices: ["Liftgate at Pickup"], deliveryServices: ["Liftgate at Delivery"], }, verified: "recorded", }, { id: "P04-ltl-crate", title: "LTL 包装 Crate", persona: "机械零件,木箱包装非托盘", input: { ...DEFAULT_PRIORITY1_DEMO, packaging: "Crate", freightClass: "85", }, verified: "recorded", }, { id: "P05-ltl-box", title: "LTL 包装 Box", persona: "小件快递式纸箱散货", input: { ...DEFAULT_PRIORITY1_DEMO, packaging: "Box", palletCount: "1" }, verified: "recorded", }, { id: "P06-ltl-bundle", title: "LTL 包装 Bundle", persona: "管材/长条物料捆扎", input: { ...DEFAULT_PRIORITY1_DEMO, packaging: "Bundle", freightClass: "60" }, verified: "recorded", }, { id: "P07-ltl-not-sure-class", title: "LTL Freight Class 不确定", persona: "首次发货,不清楚 NMFC 等级", input: { ...DEFAULT_PRIORITY1_DEMO, freightClass: "Not Sure" }, verified: "recorded", }, { id: "P08-ltl-all-accessorials", title: "LTL 全附加服务", persona: "限制进入区 + 室内 + 预约通知", input: { ...DEFAULT_PRIORITY1_DEMO, pickupServices: [...P08_LTL_ALL_ACCESSORIALS.pickup], deliveryServices: [...P08_LTL_ALL_ACCESSORIALS.delivery], }, verified: "recorded", }, { id: "P09-ltl-frequent-shipper", title: "LTL 高频发货", persona: "月发 11+ 票的大客户", input: { ...DEFAULT_PRIORITY1_DEMO, shipmentFrequency: "100+" }, verified: "recorded", }, { id: "F01-ftl-dry-van-full", title: "FTL Dry Van + Full FTL", persona: "整车普货,53ft 干柜", input: DEFAULT_FTL_DEMO, verified: "recorded", }, { id: "F02-ftl-dry-van-partial", title: "FTL Dry Van + Partial PTL", persona: "拼车/部分整车", input: { ...DEFAULT_FTL_DEMO, ftlAdditionalServices: ["partial_ptl"], weightLb: "8000", palletCount: "10", }, verified: "recorded", }, { id: "F03-ftl-dry-van-team", title: "FTL Dry Van + Team Drivers", persona: "跨州加急,双驾不换班", input: { ...DEFAULT_FTL_DEMO, ftlAdditionalServices: ["team_drivers"], weightLb: "38000", palletCount: "22", }, verified: "recorded", }, { id: "F04-ftl-open-deck-flatbed", title: "FTL Open Deck Flatbed", persona: "超宽设备,平板车", input: DEFAULT_FTL_OPEN_DECK_DEMO, verified: "recorded", }, { id: "F05-ftl-open-deck-step", title: "FTL Open Deck Step Deck", persona: "较高货物,阶梯平板", input: { ...DEFAULT_FTL_OPEN_DECK_DEMO, openDeckSubtype: "48 Foot Step Deck", }, verified: "recorded", }, { id: "F06-ftl-open-deck-conestoga", title: "FTL Open Deck Conestoga", persona: "防雨布侧帘平板", input: { ...DEFAULT_FTL_OPEN_DECK_DEMO, openDeckSubtype: "Conestoga", }, verified: "recorded", }, { id: "F07-ftl-open-deck-rgn", title: "FTL Open Deck Goose Neck RGN", persona: "重型机械低板", input: { ...DEFAULT_FTL_OPEN_DECK_DEMO, openDeckSubtype: "Removable Gooseneck", weightLb: "35000", }, verified: "recorded", }, { id: "F08-ftl-temperature", title: "FTL Temperature Controlled", persona: "冷链食品 32–40°F", input: DEFAULT_FTL_TEMP_DEMO, verified: "recorded", }, { id: "F09-ftl-expedited-sprinter", title: "FTL 内嵌 Expedited Sprinter", persona: "FTL 页选第四卡 Expedited + Sprinter", input: DEFAULT_FTL_FTL_EXPEDITED_DEMO, verified: "recorded", }, { id: "F10-ftl-expedited-small", title: "FTL 内嵌 Expedited Small Straight", persona: "FTL Expedited 小直卡", input: { ...DEFAULT_FTL_FTL_EXPEDITED_DEMO, ftlExpeditedTrailer: "small_straight", weightLb: "5000", }, verified: "recorded", }, { id: "F11-ftl-expedited-large-liftgate", title: "FTL 内嵌 Expedited Large + Liftgate", persona: "大直卡 + 尾板卸货", input: { ...DEFAULT_FTL_FTL_EXPEDITED_DEMO, ftlExpeditedTrailer: "large_straight", weightLb: "10000", expeditedLiftgate: true, }, verified: "recorded", }, { id: "E01-expedited-sprinter", title: "Expedited 主类型 Sprinter", persona: "加急小件 ≤3500 lb", input: DEFAULT_EXPEDITED_DEMO, verified: "recorded", }, { id: "E02-expedited-small", title: "Expedited Small Straight", persona: "加急中型直卡", input: { ...DEFAULT_EXPEDITED_DEMO, expeditedTrailer: "small_straight", weightLb: "4500", }, verified: "recorded", }, { id: "E03-expedited-large", title: "Expedited Large Straight", persona: "加急大型直卡", input: { ...DEFAULT_EXPEDITED_DEMO, expeditedTrailer: "large_straight", weightLb: "10000", }, verified: "recorded", }, { id: "E04-expedited-liftgate", title: "Expedited Large Straight + Liftgate", persona: "加急大直卡需尾板", input: { ...DEFAULT_EXPEDITED_DEMO, expeditedTrailer: "large_straight", expeditedLiftgate: true, weightLb: "8000", }, verified: "recorded", }, ]; /** 24 条 canonical 路径预期 RPA 终态(批量验收结论,供系统联调) */ export const PRIORITY1_CANONICAL_EXPECTED_OUTCOME: Record< string, "instant_quote" | "manual_followup" > = { "P01-ltl-residential": "instant_quote", "P02-ltl-business-dock": "instant_quote", "P03-ltl-business-no-dock": "instant_quote", "P04-ltl-crate": "instant_quote", "P05-ltl-box": "instant_quote", "P06-ltl-bundle": "instant_quote", "P07-ltl-not-sure-class": "instant_quote", "P08-ltl-all-accessorials": "instant_quote", "P09-ltl-frequent-shipper": "instant_quote", "F01-ftl-dry-van-full": "instant_quote", "F02-ftl-dry-van-partial": "instant_quote", "F03-ftl-dry-van-team": "instant_quote", "F04-ftl-open-deck-flatbed": "instant_quote", "F05-ftl-open-deck-step": "instant_quote", "F06-ftl-open-deck-conestoga": "manual_followup", "F07-ftl-open-deck-rgn": "manual_followup", "F08-ftl-temperature": "manual_followup", "F09-ftl-expedited-sprinter": "manual_followup", "F10-ftl-expedited-small": "manual_followup", "F11-ftl-expedited-large-liftgate": "manual_followup", "E01-expedited-sprinter": "manual_followup", "E02-expedited-small": "manual_followup", "E03-expedited-large": "manual_followup", "E04-expedited-liftgate": "manual_followup", }; export type Priority1CanonicalTestCase = { index: number; id: string; title: string; category: "LTL" | "FTL" | "Expedited"; persona: string; verified: "recorded" | "logic-only"; expectedOutcome: "instant_quote" | "manual_followup"; expectedMessage: string | null; filter: string; apiPayload: { request_id: string; customer_id: string; priority1_input: Priority1DemoInput; }; }; const MANUAL_FOLLOWUP_MSG = "表单已提交,站点判定需人工确认细节后报价"; /** 生成 24 条系统联调用例(POST /api/priority1/quotes) */ export function buildPriority1CanonicalTestCases(opts: { customerId?: string; requestIdPrefix?: string; } = {}): Priority1CanonicalTestCase[] { const customerId = opts.customerId ?? "CUST_PRIORITY1_TEST"; const prefix = opts.requestIdPrefix ?? "p1-matrix"; return PRIORITY1_CANONICAL_PATHS.map((path, index) => { const expectedOutcome = PRIORITY1_CANONICAL_EXPECTED_OUTCOME[path.id] ?? "instant_quote"; return { index: index + 1, id: path.id, title: path.title, category: path.id.startsWith("P") ? "LTL" : path.id.startsWith("F") ? "FTL" : "Expedited", persona: path.persona, verified: path.verified, expectedOutcome, expectedMessage: expectedOutcome === "manual_followup" ? MANUAL_FOLLOWUP_MSG : null, filter: path.id.replace(/-.*$/, ""), apiPayload: { request_id: `${prefix}-${path.id}`, customer_id: customerId, priority1_input: path.input, }, }; }); } const ZIP_POOL = [ "10118", "90017", "60601", "77002", "33131", "98101", "80202", "19107", "02108", "75201", "30303", "48226", "44114", "85004", "37203", "63101", "97201", "89101", "32801", "28202", ]; const FREIGHT_CLASSES = ["50", "55", "60", "65", "70", "77.5", "85", "92.5", "100"]; const FTL_TRAILERS = Object.keys(FTL_TRAILER) as FtlTrailerType[]; const EXP_TRAILERS = Object.keys(EXPEDITED_TRAILER) as ExpeditedTrailerType[]; const COMMODITIES = [ "General Freight", "Palletized Goods", "Electronics", "Machinery", "Furniture", "Food Products", ]; function mulberry32(seed: number): () => number { let s = seed >>> 0; return () => { s = (s + 0x6d2b79f5) >>> 0; let t = Math.imul(s ^ (s >>> 15), 1 | s); t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 4294967296; }; } function pick(rng: () => number, arr: T[]): T { return arr[Math.floor(rng() * arr.length)]!; } function randInt(rng: () => number, min: number, max: number): number { return Math.floor(rng() * (max - min + 1)) + min; } function futurePickupDate(rng: () => number, base = new Date()): string { const d = new Date(base); d.setDate(d.getDate() + randInt(rng, 3, 21)); const mm = String(d.getMonth() + 1).padStart(2, "0"); const dd = String(d.getDate()).padStart(2, "0"); return `${mm}/${dd}/${d.getFullYear()}`; } function randomUsPhone(rng: () => number): string { const area = randInt(rng, 201, 989); const exchange = randInt(rng, 201, 999); const line = randInt(rng, 1000, 9999); return `${area}${exchange}${line}`.slice(0, 10); } function randomZipPair(rng: () => number): { originZip: string; destinationZip: string } { let originZip = pick(rng, ZIP_POOL); let destinationZip = pick(rng, ZIP_POOL); let guard = 0; while (destinationZip === originZip && guard < 20) { destinationZip = pick(rng, ZIP_POOL); guard += 1; } return { originZip, destinationZip }; } function baseForType(type: Priority1ShipmentType): Priority1DemoInput { if (type === "ftl") return { ...DEFAULT_FTL_DEMO }; if (type === "expedited") return { ...DEFAULT_EXPEDITED_DEMO }; return { ...DEFAULT_PRIORITY1_DEMO }; } export function parsePriority1DemoFromEnv( raw: string | undefined, ): Priority1DemoInput { if (!raw?.trim()) return DEFAULT_PRIORITY1_DEMO; try { const parsed = JSON.parse(raw) as Partial; const base = baseForType(parsed.shipmentType ?? "ltl"); return { ...base, ...parsed }; } catch { return DEFAULT_PRIORITY1_DEMO; } } export function generateRandomPriority1Cases( count: number, seed = Date.now(), ): Array<{ id: string; input: Priority1DemoInput }> { const rng = mulberry32(seed); const out: Array<{ id: string; input: Priority1DemoInput }> = []; for (let i = 0; i < count; i += 1) { const zips = randomZipPair(rng); const length = randInt(rng, 36, 96); const width = randInt(rng, 36, 48); const height = randInt(rng, 36, 72); out.push({ id: `P${String(i + 1).padStart(2, "0")}`, input: { ...DEFAULT_PRIORITY1_DEMO, ...zips, pickupDate: futurePickupDate(rng), phone: randomUsPhone(rng), lengthIn: String(length), widthIn: String(width), heightIn: String(height), weightLb: String(randInt(rng, 150, 1800)), palletCount: String(randInt(rng, 1, 5)), freightClass: pick(rng, FREIGHT_CLASSES), }, }); } return out; } export function generateRandomFtlCases( count: number, seed = Date.now(), ): Array<{ id: string; input: Priority1DemoInput }> { const rng = mulberry32(seed); const out: Array<{ id: string; input: Priority1DemoInput }> = []; for (let i = 0; i < count; i += 1) { const zips = randomZipPair(rng); const services: FtlAdditionalService[] = []; if (rng() > 0.5) services.push("full_ftl"); if (rng() > 0.7) services.push("partial_ptl"); out.push({ id: `F${String(i + 1).padStart(2, "0")}`, input: { ...DEFAULT_FTL_DEMO, ...zips, pickupDate: futurePickupDate(rng), phone: randomUsPhone(rng), commodity: pick(rng, COMMODITIES), weightLb: String(randInt(rng, 5000, 42000)), palletCount: String(randInt(rng, 4, 26)), ftlTrailer: pick(rng, FTL_TRAILERS), ftlAdditionalServices: services, }, }); } return out; } export function generateRandomExpeditedCases( count: number, seed = Date.now(), ): Array<{ id: string; input: Priority1DemoInput }> { const rng = mulberry32(seed + 17); const out: Array<{ id: string; input: Priority1DemoInput }> = []; for (let i = 0; i < count; i += 1) { const zips = randomZipPair(rng); out.push({ id: `E${String(i + 1).padStart(2, "0")}`, input: { ...DEFAULT_EXPEDITED_DEMO, ...zips, pickupDate: futurePickupDate(rng), phone: randomUsPhone(rng), commodity: pick(rng, COMMODITIES), weightLb: String(randInt(rng, 200, 3500)), expeditedTrailer: pick(rng, EXP_TRAILERS), }, }); } return out; } export function formatPriority1InputSummary(input: Priority1DemoInput): string { const cfg = getShipmentConfig(input.shipmentType); const lines = [ `Shipment Type: ${cfg.displayName}`, `起运 ZIP: ${input.originZip}`, `目的 ZIP: ${input.destinationZip}`, `发货频次: ${input.shipmentFrequency}`, `提货日: ${input.pickupDate}`, `邮箱: ${input.email}`, `电话: +1 ${input.phone}`, ]; if (input.shipmentType === "ltl") { lines.push( `尺寸: ${input.lengthIn} × ${input.widthIn} × ${input.heightIn} in`, `总重量: ${input.weightLb} lb`, `托盘数: ${input.palletCount}`, `包装: ${input.packaging}`, `货运等级 Freight Class: ${input.freightClass}`, `提货地点类型: ${input.pickupLocation}`, `派送地点类型: ${input.deliveryLocation}`, `提货附加(勾选): ${input.pickupServices.join("、")}`, `派送附加(勾选): ${input.deliveryServices.join("、")}`, ); } if (input.shipmentType === "ftl") { lines.push( `主拖车类型: ${FTL_TRAILER[input.ftlTrailer].label}`, ); if (input.ftlTrailer === "open_deck") { lines.push(`Open Deck 子类型: ${input.openDeckSubtype}`); } if (input.ftlTrailer === "temperature_controlled") { lines.push(`温控: ${input.tempMinF}°F ~ ${input.tempMaxF}°F`); } if (input.ftlTrailer === "ftl_expedited") { lines.push( `Expedited 子车型: ${EXPEDITED_TRAILER[input.ftlExpeditedTrailer].label}`, ); if (input.expeditedLiftgate && input.ftlExpeditedTrailer === "large_straight") { lines.push("附加: Liftgate"); } } lines.push( `附加服务: ${ input.ftlAdditionalServices.length ? input.ftlAdditionalServices .map((s) => FTL_ADDITIONAL[s].label) .join("、") : "(无)" }`, `货描 Commodity: ${input.commodity}`, `总重量: ${input.weightLb} lb`, ); if (input.ftlTrailer !== "ftl_expedited") { lines.push(`托盘数: ${input.palletCount}`); } } if (input.shipmentType === "expedited") { lines.push( `拖车类型: ${EXPEDITED_TRAILER[input.expeditedTrailer].label}`, `货描 Commodity: ${input.commodity}`, `总重量: ${input.weightLb} lb`, ); if (input.expeditedLiftgate && input.expeditedTrailer === "large_straight") { lines.push("附加: Liftgate"); } } return lines.join("\n"); }