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.
319 lines
9.0 KiB
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);
|
|
});
|