/** * 可视化:Albany NY → SF 登录态查价连跑 10 次(复现卡住) * DIAG_HEADED=true npx tsx scripts/diag-mothership-albany-sf-x10.ts */ import fs from "node:fs"; import path from "node:path"; import { loadDevEnv } from "@/lib/dev-env"; loadDevEnv(); const wantHeaded = process.env.DIAG_HEADED !== "false"; process.env.RPA_HEADED = wantHeaded ? "true" : "false"; process.env.RPA_HEADLESS = wantHeaded ? "false" : "true"; if (wantHeaded && !process.env.RPA_SLOW_MO_MS?.trim()) { process.env.RPA_SLOW_MO_MS = "120"; } if (!wantHeaded) delete process.env.RPA_SLOW_MO_MS; 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 RUNS = Math.max(1, Number(process.env.DIAG_RUNS || "10") || 10); const PICKUP_Q = "101 South Pine Avenue, Albany, NY 12208, USA"; const DELIVERY_Q = "555 Market St ste 1001, San Francisco, CA, USA"; const READY_DATE = process.env.DIAG_READY_DATE?.trim() || "2026-07-20"; const READY_TIME = process.env.DIAG_READY_TIME?.trim() || "12:00 PM"; type RunRow = { n: number; ok: boolean; ms: number; lastStep: string; carriers: string; error?: string; failShot?: string; }; 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 step:") || line.includes("[rpa] logged-in: still no rate-card") || line.includes("[rpa] logged-in: patched") || line.includes("[rpa] logged-in: rate-card ready") || line.includes("[rpa] logged-in FAILED") ) { timingBuf.push(line); } origLog(...args); }; async function pickFirst(query: string) { 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; } function toAddr(c: Awaited>) { 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 lastStepFromBuf(): string { for (let i = timingBuf.length - 1; i >= 0; i -= 1) { const m = timingBuf[i]!.match(/step="([^"]+)"/); if (m) return m[1]!; if (timingBuf[i]!.includes("still no rate-card")) return "wait rate-card (stuck)"; if (timingBuf[i]!.includes("FAILED")) return "FAILED"; } return "(none)"; } async function main(): Promise { const customerId = process.env.DIAG_CUSTOMER_ID?.trim() || "CUST_001"; const login = await getCustomerProviderLogin(customerId, "mothership"); if (!login) { console.error(`[diag] 无 MotherShip 账密 customer=${customerId}`); process.exit(1); } console.log("=== Albany→SF 可视化 ×" + RUNS + " ==="); console.log( `headed=${wantHeaded} slowMo=${process.env.RPA_SLOW_MO_MS ?? 0} ready=${READY_DATE} ${READY_TIME}`, ); const [pickupC, deliveryC] = await Promise.all([ pickFirst(PICKUP_Q), pickFirst(DELIVERY_Q), ]); console.log( `[diag] pickup=${buildLoggedInAddressSearchQuery(toAddr(pickupC))}`, ); console.log( `[diag] delivery=${buildLoggedInAddressSearchQuery(toAddr(deliveryC))}`, ); const rows: RunRow[] = []; for (let n = 1; n <= RUNS; n += 1) { timingBuf.length = 0; const t0 = Date.now(); console.log(`\n---------- RUN ${n}/${RUNS} ----------`); const req: QuoteRequest = { cargoHash: `diag-albany-sf-${n}-${Date.now()}`, pickup: toAddr(pickupC), delivery: toAddr(deliveryC), weightLb: 120, dimsIn: { l: 48, w: 40, h: 48 }, palletCount: 2, cargoType: "box", readyDate: READY_DATE, readyTime: READY_TIME, pickupAccessorials: ["residential", "liftgate"], deliveryAccessorials: ["cfs", "fbaAppointment"], cargoLines: [ { cargoType: "box", quantity: 2, weightLb: 120, lengthIn: 48, widthIn: 40, heightIn: 48, }, ], }; try { const items = await runWithMothershipLoginContext(login, "customer", () => runMothershipLoggedInDashboardQuote(req), ); const ms = Date.now() - t0; const carriers = items.map((i) => `${i.carrier}=$${i.rawTotal}`).join(", "); rows.push({ n, ok: true, ms, lastStep: lastStepFromBuf(), carriers, }); console.log(`[diag] RUN ${n} OK ${ms}ms ${carriers}`); } catch (err) { const ms = Date.now() - t0; const msg = err instanceof Error ? err.message : String(err); const diagDir = path.join(process.cwd(), ".rpa", "diag"); const shots = fs.existsSync(diagDir) ? fs .readdirSync(diagDir) .filter((f) => f.startsWith("logged-in-fail-") && f.endsWith(".png")) .sort() : []; rows.push({ n, ok: false, ms, lastStep: lastStepFromBuf(), carriers: "", error: msg.slice(0, 200), failShot: shots.at(-1), }); console.log(`[diag] RUN ${n} FAIL ${ms}ms step=${lastStepFromBuf()} err=${msg.slice(0, 160)}`); } } const okN = rows.filter((r) => r.ok).length; const reportPath = path.join( process.cwd(), ".rpa", "diag", `albany-sf-x10-${new Date().toISOString().replace(/[:.]/g, "-")}.md`, ); const md = [ `# Albany→SF ×${RUNS} headed`, "", `- ok=${okN}/${RUNS}`, `- pickup=${PICKUP_Q}`, `- delivery=${DELIVERY_Q}`, "", "| # | ok | ms | lastStep | carriers/error |", "|---|----|----|----------|----------------|", ...rows.map((r) => { const detail = r.ok ? r.carriers : `${r.error ?? ""}${r.failShot ? ` shot=${r.failShot}` : ""}`; return `| ${r.n} | ${r.ok ? "Y" : "N"} | ${r.ms} | ${r.lastStep} | ${detail.replace(/\|/g, "/")} |`; }), "", ].join("\n"); fs.mkdirSync(path.dirname(reportPath), { recursive: true }); fs.writeFileSync(reportPath, md, "utf8"); console.log("\n=== SUMMARY ==="); console.log(`ok=${okN}/${RUNS} report=${reportPath}`); for (const r of rows) { console.log( ` #${r.n} ${r.ok ? "OK" : "FAIL"} ${r.ms}ms last=${r.lastStep}${r.error ? " " + r.error.slice(0, 80) : ""}`, ); } process.exit(okN === RUNS ? 0 : 1); } main().catch((e) => { console.error(e); process.exit(1); });