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-rules-x10.ts

301 lines
8.8 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/**
* MotherShip 登录态 ×10遵守官网硬限禁周末 / 单件≤5000lb
* DIAG_HEADED=true npx tsx scripts/diag-mothership-rules-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 = "80";
}
if (!wantHeaded) delete process.env.RPA_SLOW_MO_MS;
import { resolveAxelMothershipSuggest } from "@/lib/axel/candidates";
import {
snapMothershipReadyDateToWeekday,
MOTHERSHIP_MAX_WEIGHT_EACH_LB,
} from "@/lib/mothership/logged-in-constraints";
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 = snapMothershipReadyDateToWeekday(
process.env.DIAG_READY_DATE?.trim() || "2026-07-20",
);
const READY_TIME = process.env.DIAG_READY_TIME?.trim() || "12:00 PM";
/** 10 组线路;单件重量均 ≤5000 */
const LANES = [
{
name: "Brea→Fairhope",
pickup: "789 East Elm Street suite 301, Brea, CA, USA",
delivery: "456 Oak Avenue unit 207, Fairhope, AL, USA",
weightEach: 250,
qty: 2,
dims: [60, 60, 60] as const,
},
{
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",
weightEach: 480,
qty: 2,
dims: [48, 40, 48] as const,
},
{
name: "Albany→SF",
pickup: "101 South Pine Avenue, Albany, NY 12208, USA",
delivery: "555 Market St ste 1001, San Francisco, CA, USA",
weightEach: 120,
qty: 2,
dims: [48, 40, 48] as const,
},
{
name: "MA→OR",
pickup: "789 Elm Street, East Bridgewater, MA, USA",
delivery: "888 Liberty Street Northeast, Salem, OR, USA",
weightEach: 200,
qty: 1,
dims: [48, 40, 48] as const,
},
{
name: "Dallas→Chicago",
pickup: "1234 Main Street, Dallas, TX, USA",
delivery: "200 East Randolph Street, Chicago, IL, USA",
weightEach: 500,
qty: 2,
dims: [48, 40, 40] as const,
},
{
name: "Atlanta→Denver",
pickup: "100 Peachtree Street NW, Atlanta, GA, USA",
delivery: "1701 California Street, Denver, CO, USA",
weightEach: 300,
qty: 2,
dims: [48, 40, 48] as const,
},
{
name: "Brea→LA",
pickup: "789 East Elm Street, Brea, CA, USA",
delivery:
"2020 West Washington Boulevard floor 5 rm 508, Los Angeles, CA, USA",
weightEach: 50,
qty: 1,
dims: [40, 40, 40] as const,
},
{
name: "SF→Albany",
pickup: "555 Market St ste 1001, San Francisco, CA, USA",
delivery: "101 South Pine Avenue, Albany, NY 12208, USA",
weightEach: 150,
qty: 2,
dims: [48, 40, 48] as const,
},
{
name: "Chicago→Dallas",
pickup: "200 East Randolph Street, Chicago, IL, USA",
delivery: "1234 Main Street, Dallas, TX, USA",
weightEach: 400,
qty: 1,
dims: [48, 40, 48] as const,
},
{
name: "Denver→Atlanta",
pickup: "1701 California Street, Denver, CO, USA",
delivery: "100 Peachtree Street NW, Atlanta, GA, USA",
weightEach: 220,
qty: 2,
dims: [48, 40, 40] as const,
},
] as const;
type Row = {
n: number;
name: string;
ok: boolean;
ms: number;
lastStep: string;
detail: 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("still no rate-card") ||
line.includes("patched details") ||
line.includes("rate-card ready") ||
line.includes("FAILED") ||
line.includes("weekend")
) {
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 lastStep(): 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";
}
return "(none)";
}
async function main(): Promise<void> {
for (const lane of LANES) {
if (lane.weightEach > MOTHERSHIP_MAX_WEIGHT_EACH_LB) {
throw new Error(`lane ${lane.name} weightEach 超限`);
}
}
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("=== MotherShip rules ×10禁周末 / 单件≤5000===");
console.log(
`headed=${wantHeaded} ready=${READY_DATE} ${READY_TIME} maxWeightEach=${MOTHERSHIP_MAX_WEIGHT_EACH_LB}`,
);
const rows: Row[] = [];
for (let n = 0; n < LANES.length; n += 1) {
const lane = LANES[n]!;
timingBuf.length = 0;
const t0 = Date.now();
console.log(`\n---------- ${n + 1}/10 ${lane.name} wt=${lane.weightEach} ----------`);
try {
const [p, d] = await Promise.all([
pickFirst(lane.pickup),
pickFirst(lane.delivery),
]);
const req: QuoteRequest = {
cargoHash: `diag-rules-${n}-${Date.now()}`,
pickup: toAddr(p),
delivery: toAddr(d),
weightLb: lane.weightEach,
dimsIn: { l: lane.dims[0], w: lane.dims[1], h: lane.dims[2] },
palletCount: lane.qty,
cargoType: "general_freight",
readyDate: READY_DATE,
readyTime: READY_TIME,
cargoLines: [
{
cargoType: "pallet",
quantity: lane.qty,
weightLb: lane.weightEach,
lengthIn: lane.dims[0],
widthIn: lane.dims[1],
heightIn: lane.dims[2],
},
],
};
console.log(
`[diag] ${buildLoggedInAddressSearchQuery(req.pickup)}${buildLoggedInAddressSearchQuery(req.delivery)}`,
);
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: n + 1,
name: lane.name,
ok: true,
ms,
lastStep: lastStep(),
detail: carriers,
});
console.log(`[diag] OK ${ms}ms ${carriers}`);
} catch (err) {
const ms = Date.now() - t0;
const msg = err instanceof Error ? err.message : String(err);
rows.push({
n: n + 1,
name: lane.name,
ok: false,
ms,
lastStep: lastStep(),
detail: msg.slice(0, 220),
});
console.log(`[diag] FAIL ${ms}ms step=${lastStep()} ${msg.slice(0, 160)}`);
}
}
const okN = rows.filter((r) => r.ok).length;
const reportPath = path.join(
process.cwd(),
".rpa",
"diag",
`rules-x10-${new Date().toISOString().replace(/[:.]/g, "-")}.md`,
);
const md = [
`# MotherShip rules ×10`,
"",
`- ready=${READY_DATE}(已 snap 工作日)`,
`- maxWeightEach=${MOTHERSHIP_MAX_WEIGHT_EACH_LB}`,
`- ok=${okN}/10`,
"",
"| # | lane | ok | ms | lastStep | detail |",
"|---|------|----|----|----------|--------|",
...rows.map(
(r) =>
`| ${r.n} | ${r.name} | ${r.ok ? "Y" : "N"} | ${r.ms} | ${r.lastStep} | ${r.detail.replace(/\|/g, "/")} |`,
),
"",
].join("\n");
fs.mkdirSync(path.dirname(reportPath), { recursive: true });
fs.writeFileSync(reportPath, md, "utf8");
console.log(`\n=== SUMMARY ok=${okN}/10 report=${reportPath} ===`);
for (const r of rows) {
console.log(
` #${r.n} ${r.ok ? "OK" : "FAIL"} ${r.name} ${r.ms}ms ${r.lastStep}`,
);
}
process.exit(okN === 10 ? 0 : 1);
}
main().catch((e) => {
console.error(e);
process.exit(1);
});