/** * 5 组登录态 MotherShip 查价计时 * DIAG_HEADED=false npx tsx scripts/diag-mothership-timing-5cases.ts */ import fs from "node:fs"; import path from "node:path"; import { loadDevEnv } from "@/lib/dev-env"; loadDevEnv(); const wantHeaded = process.env.DIAG_HEADED === "true"; process.env.RPA_HEADED = wantHeaded ? "true" : "false"; process.env.RPA_HEADLESS = wantHeaded ? "false" : "true"; if (!wantHeaded) delete process.env.RPA_SLOW_MO_MS; else if (!process.env.RPA_SLOW_MO_MS?.trim()) process.env.RPA_SLOW_MO_MS = "0"; import { resolveAxelMothershipSuggest } from "@/lib/axel/candidates"; import { runWithMothershipLoginContext } from "@/lib/rpa/mothership-login-context"; import { getCustomerProviderLogin } from "@/modules/customer/provider-credentials"; import type { QuoteRequest } from "@/modules/providers/quote-provider"; import { buildLoggedInAddressSearchQuery, runMothershipLoggedInDashboardQuote, } from "@/workers/rpa/mothership-logged-in-quote"; const READY_DATE = process.env.DIAG_READY_DATE?.trim() || "2026-07-20"; const READY_TIME = process.env.DIAG_READY_TIME?.trim() || "12:00 PM"; const LANES = [ { id: 1, name: "Brea→Fairhope", pickup: "789 East Elm Street suite 301, Brea, CA, USA", delivery: "456 Oak Avenue unit 207, Fairhope, AL, USA", }, { id: 2, name: "LA→SF", pickup: "2020 West Washington Boulevard floor 5 rm 508, Los Angeles, CA, USA", delivery: "555 Market St ste 1001, San Francisco, CA, USA", }, { id: 3, name: "MA→OR", pickup: "789 Elm Street, East Bridgewater, MA, USA", delivery: "888 Liberty Street Northeast, Salem, OR, USA", }, { id: 4, name: "Dallas→Chicago", pickup: "1234 Main Street, Dallas, TX, USA", delivery: "200 East Randolph Street, Chicago, IL, USA", }, { id: 5, name: "Atlanta→Denver", pickup: "100 Peachtree Street NW, Atlanta, GA, USA", delivery: "1701 California Street, Denver, CO, USA", }, ] as const; type StepTiming = { step: string; ms: number; totalMs: number }; type CaseResult = { id: number; name: string; ok: boolean; suggestMs: number; rpaMs: number; wallMs: number; carrierCount: number; carriers: string; error?: string; steps: StepTiming[]; addressPickMs: { pickup?: number; delivery?: number }; }; const timingBuf: string[] = []; const origLog = console.log.bind(console); console.log = (...args: unknown[]) => { const line = args.map(String).join(" "); if ( line.includes("[rpa] logged-in timing:") || line.includes("[rpa] logged-in: address picked") ) { timingBuf.push(line); } origLog(...args); }; async function pickFirst(query: string) { const t0 = Date.now(); const list = await resolveAxelMothershipSuggest(query); const hit = list.find((c) => c.selectable !== false && c.option_id.trim()); if (!hit) throw new Error(`无联想: ${query.slice(0, 50)}`); return { hit, ms: Date.now() - t0 }; } function toAddr(c: Awaited>["hit"]) { return { street: c.street, city: c.city, state: c.state, zip: c.zip || "", placeId: c.option_id, formattedAddress: c.formatted_address || c.display_label, selectedFromSuggestions: true, mothershipOptionId: c.option_id, mothershipDisplayLabel: c.display_label, selectedFromMothership: true, }; } function parseTimings(lines: string[]): { steps: StepTiming[]; addressPickMs: { pickup?: number; delivery?: number }; totalMs?: number; } { const steps: StepTiming[] = []; const addressPickMs: { pickup?: number; delivery?: number } = {}; let totalMs: number | undefined; for (const line of lines) { const stepM = line.match( /timing: step="([^"]+)" ms=(\d+) totalMs=(\d+)/, ); if (stepM) { steps.push({ step: stepM[1]!, ms: Number(stepM[2]), totalMs: Number(stepM[3]), }); continue; } const doneM = line.match(/timing: DONE label=\w+ totalMs=(\d+)/); if (doneM) { totalMs = Number(doneM[1]); continue; } const pickM = line.match( /address picked side=(pickup|delivery) via=\w+ ms=(\d+)/, ); if (pickM) { addressPickMs[pickM[1] as "pickup" | "delivery"] = Number(pickM[2]); } } return { steps, addressPickMs, totalMs }; } async function runCase( login: { email: string; password: string }, lane: (typeof LANES)[number], ): Promise { timingBuf.length = 0; const wall0 = Date.now(); const suggest0 = Date.now(); const [p, d] = await Promise.all([ pickFirst(lane.pickup), pickFirst(lane.delivery), ]); const suggestMs = Date.now() - suggest0; const req: QuoteRequest = { cargoHash: `timing5-${lane.id}-${Date.now()}`, pickup: toAddr(p.hit), delivery: toAddr(d.hit), weightLb: 250, dimsIn: { l: 60, w: 50, h: 60 }, palletCount: 2, cargoType: "general_freight", readyDate: READY_DATE, readyTime: READY_TIME, cargoLines: [ { cargoType: "general_freight", quantity: 2, weightLb: 250, lengthIn: 60, widthIn: 50, heightIn: 60, }, ], }; console.log( `\n=== #${lane.id} ${lane.name} ===\npickup=${buildLoggedInAddressSearchQuery(req.pickup)}\ndelivery=${buildLoggedInAddressSearchQuery(req.delivery)}`, ); const rpa0 = Date.now(); try { const items = await runWithMothershipLoginContext(login, "customer", () => runMothershipLoggedInDashboardQuote(req), ); const rpaMs = Date.now() - rpa0; const wallMs = Date.now() - wall0; const parsed = parseTimings([...timingBuf]); return { id: lane.id, name: lane.name, ok: true, suggestMs, rpaMs, wallMs, carrierCount: items.length, carriers: items.map((i) => i.carrier).join(", "), steps: parsed.steps, addressPickMs: parsed.addressPickMs, }; } catch (e) { const msg = e instanceof Error ? e.message : String(e); const parsed = parseTimings([...timingBuf]); return { id: lane.id, name: lane.name, ok: false, suggestMs, rpaMs: Date.now() - rpa0, wallMs: Date.now() - wall0, carrierCount: 0, carriers: "", error: msg.slice(0, 200), steps: parsed.steps, addressPickMs: parsed.addressPickMs, }; } } function fmtMs(ms: number): string { return (ms / 1000).toFixed(1) + "s"; } function stepMs(steps: StepTiming[], needle: string): string { const hit = steps.find((s) => s.step.includes(needle)); return hit ? String(hit.ms) : "-"; } async function main(): Promise { const login = await getCustomerProviderLogin("CUST_001", "mothership"); if (!login) { console.error("无 MotherShip 账密"); process.exit(1); } console.log("=== MotherShip 5 组计时 ==="); console.log(`headed=${wantHeaded} ready=${READY_DATE} ${READY_TIME}`); console.log("cargo=Pallet qty=2 wt=250 dims=60x50x60\n"); const batch0 = Date.now(); const results: CaseResult[] = []; for (const lane of LANES) { results.push(await runCase(login, lane)); } const batchMs = Date.now() - batch0; const lines: string[] = [ "# MotherShip 登录态 5 组计时", "", `批次总耗时: **${fmtMs(batchMs)}** (${batchMs}ms)`, `提货日: ${READY_DATE} ${READY_TIME}`, "", "## 汇总", "", "| # | 车道 | 结果 | 联想 | RPA | 全程 | 承运商数 | 提货址 | 派送址 |", "|---|------|------|------|-----|------|----------|--------|--------|", ]; for (const r of results) { lines.push( `| ${r.id} | ${r.name} | ${r.ok ? "OK" : "FAIL"} | ${fmtMs(r.suggestMs)} | ${fmtMs(r.rpaMs)} | ${fmtMs(r.wallMs)} | ${r.carrierCount} | ${r.addressPickMs.pickup ?? "-"}ms | ${r.addressPickMs.delivery ?? "-"}ms |`, ); } lines.push("", "## 逐步耗时 (ms)", ""); for (const r of results) { lines.push(`### #${r.id} ${r.name} ${r.ok ? "OK" : "FAIL"}`); if (r.error) lines.push(`- 错误: ${r.error}`); lines.push( `- 联想 ${r.suggestMs}ms | RPA ${r.rpaMs}ms | 全程 ${r.wallMs}ms`, ); if (r.carriers) lines.push(`- 承运商: ${r.carriers}`); lines.push(""); lines.push("| 步骤 | 本步 ms | 累计 ms |"); lines.push("|------|---------|---------|"); for (const s of r.steps) { lines.push(`| ${s.step} | ${s.ms} | ${s.totalMs} |`); } lines.push(""); } const report = lines.join("\n"); const outDir = path.join(process.cwd(), ".rpa", "diag"); fs.mkdirSync(outDir, { recursive: true }); const stamp = new Date().toISOString().replace(/[:.]/g, "-"); const mdPath = path.join(outDir, `timing-5cases-${stamp}.md`); const jsonPath = path.join(outDir, `timing-5cases-${stamp}.json`); fs.writeFileSync(mdPath, report, "utf8"); fs.writeFileSync( jsonPath, JSON.stringify({ batchMs, results }, null, 2), "utf8", ); console.log("\n" + report); console.log(`\n报告: ${mdPath}`); const fails = results.filter((r) => !r.ok); if (fails.length) { console.error(`失败 ${fails.length}/5: ${fails.map((f) => f.name).join(", ")}`); process.exit(1); } } main().catch((e) => { console.error(e); process.exit(1); });