You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
chajia/scripts/diag-mothership-timing-5cas...

319 lines
9.0 KiB

/**
* 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<ReturnType<typeof pickFirst>>["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<CaseResult> {
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<void> {
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);
});