|
|
/**
|
|
|
* 可视化: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<ReturnType<typeof pickFirst>>) {
|
|
|
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<void> {
|
|
|
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);
|
|
|
});
|