|
|
/**
|
|
|
* Flock Freight 登录态 ×20:多线路 / 多货物 / 多地点类型与选项
|
|
|
* DIAG_HEADED=false npx tsx scripts/diag-flock-logged-in-x20.ts
|
|
|
* DIAG_ONLY=3 — 仅跑第 3 组
|
|
|
*
|
|
|
* 报告:.rpa/diag/flock-logged-in-x20-*.md
|
|
|
*/
|
|
|
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;
|
|
|
process.env.FLOCK_WORKER_REUSE_SESSION =
|
|
|
process.env.FLOCK_WORKER_REUSE_SESSION?.trim() || "false";
|
|
|
|
|
|
import { fitsFlockTrailerCargo } from "@/lib/constants/flock-limits";
|
|
|
import {
|
|
|
defaultFlockPickupDateIso,
|
|
|
flockPickupDisplayFromIso,
|
|
|
} from "@/lib/flock/pickup-date";
|
|
|
import {
|
|
|
isMothershipWeekendIso,
|
|
|
snapMothershipReadyDateToWeekday,
|
|
|
toIsoDateLocal,
|
|
|
} from "@/lib/mothership/logged-in-constraints";
|
|
|
import { getCustomerProviderLogin } from "@/modules/customer/provider-credentials";
|
|
|
import { runFlockQuoteRpa } from "@/workers/rpa/flock/run-quote";
|
|
|
import type { FlockQuoteInput } from "@/workers/rpa/flock/types";
|
|
|
|
|
|
const PICKUP_BASE = snapMothershipReadyDateToWeekday(
|
|
|
process.env.DIAG_READY_DATE?.trim() || defaultFlockPickupDateIso(),
|
|
|
);
|
|
|
|
|
|
const DELAY_MS = Number(process.env.DIAG_DELAY_MS ?? "2000");
|
|
|
|
|
|
type CaseDef = {
|
|
|
name: string;
|
|
|
pickupZip: string;
|
|
|
deliveryZip: string;
|
|
|
palletCount: number;
|
|
|
totalWeightLb: number;
|
|
|
lengthIn: number;
|
|
|
widthIn: number;
|
|
|
heightIn: number;
|
|
|
pickupLocationType: string;
|
|
|
deliveryLocationType: string;
|
|
|
packagingType: string;
|
|
|
freightClass?: string;
|
|
|
description?: string;
|
|
|
stackable?: boolean;
|
|
|
turnable?: boolean;
|
|
|
additionalServices?: string[];
|
|
|
vehicleTypes?: string[];
|
|
|
pickupLiftgate?: boolean;
|
|
|
pickupInside?: boolean;
|
|
|
pickupPalletJack?: boolean;
|
|
|
deliveryLiftgate?: boolean;
|
|
|
deliveryInside?: boolean;
|
|
|
deliveryPalletJack?: boolean;
|
|
|
callBeforePickup?: boolean;
|
|
|
callBeforeDelivery?: boolean;
|
|
|
/** 相对 PICKUP_BASE 再偏移工作日天数 */
|
|
|
readyOffsetDays?: number;
|
|
|
};
|
|
|
|
|
|
/** 20 组:跨区 ZIP + 货物 + 地点类型 / 包装 / 附加服务差异 */
|
|
|
export const FLOCK_LOGGED_IN_X20_CASES: CaseDef[] = [
|
|
|
{
|
|
|
name: "Gardena→Pittsburgh 基线",
|
|
|
pickupZip: "90248",
|
|
|
deliveryZip: "15222",
|
|
|
palletCount: 2,
|
|
|
totalWeightLb: 500,
|
|
|
lengthIn: 48,
|
|
|
widthIn: 40,
|
|
|
heightIn: 48,
|
|
|
pickupLocationType: "business_with_dock",
|
|
|
deliveryLocationType: "business_with_dock",
|
|
|
packagingType: "bags",
|
|
|
freightClass: "density",
|
|
|
description: "papers",
|
|
|
additionalServices: ["unloading"],
|
|
|
stackable: true,
|
|
|
},
|
|
|
{
|
|
|
name: "LA→SF + 无月台尾板",
|
|
|
pickupZip: "90001",
|
|
|
deliveryZip: "94102",
|
|
|
palletCount: 4,
|
|
|
totalWeightLb: 2_800,
|
|
|
lengthIn: 48,
|
|
|
widthIn: 40,
|
|
|
heightIn: 48,
|
|
|
pickupLocationType: "business_with_dock",
|
|
|
deliveryLocationType: "business_without_dock",
|
|
|
packagingType: "pallets_48x40",
|
|
|
freightClass: "70",
|
|
|
deliveryLiftgate: true,
|
|
|
},
|
|
|
{
|
|
|
name: "Atlanta→Baltimore 住宅派送",
|
|
|
pickupZip: "30326",
|
|
|
deliveryZip: "21230",
|
|
|
palletCount: 3,
|
|
|
totalWeightLb: 1_200,
|
|
|
lengthIn: 48,
|
|
|
widthIn: 40,
|
|
|
heightIn: 60,
|
|
|
pickupLocationType: "business_with_dock",
|
|
|
deliveryLocationType: "residential",
|
|
|
packagingType: "boxes",
|
|
|
freightClass: "85",
|
|
|
deliveryLiftgate: true,
|
|
|
deliveryInside: true,
|
|
|
},
|
|
|
{
|
|
|
name: "Boston→Chicago bags",
|
|
|
pickupZip: "02109",
|
|
|
deliveryZip: "60611",
|
|
|
palletCount: 6,
|
|
|
totalWeightLb: 5_500,
|
|
|
lengthIn: 48,
|
|
|
widthIn: 40,
|
|
|
heightIn: 72,
|
|
|
pickupLocationType: "business_with_dock",
|
|
|
deliveryLocationType: "business_with_dock",
|
|
|
packagingType: "bags",
|
|
|
freightClass: "density",
|
|
|
description: "papers",
|
|
|
stackable: true,
|
|
|
additionalServices: ["unloading"],
|
|
|
},
|
|
|
{
|
|
|
name: "Dallas→Philly + 卸货服务",
|
|
|
pickupZip: "75201",
|
|
|
deliveryZip: "19106",
|
|
|
palletCount: 8,
|
|
|
totalWeightLb: 9_200,
|
|
|
lengthIn: 48,
|
|
|
widthIn: 40,
|
|
|
heightIn: 60,
|
|
|
pickupLocationType: "business_with_dock",
|
|
|
deliveryLocationType: "business_with_dock",
|
|
|
packagingType: "pallets_48x40",
|
|
|
freightClass: "65",
|
|
|
additionalServices: ["unloading"],
|
|
|
vehicleTypes: ["box_truck", "dry_van"],
|
|
|
},
|
|
|
{
|
|
|
name: "Austin→Denver crates",
|
|
|
pickupZip: "78701",
|
|
|
deliveryZip: "80202",
|
|
|
palletCount: 2,
|
|
|
totalWeightLb: 800,
|
|
|
lengthIn: 48,
|
|
|
widthIn: 48,
|
|
|
heightIn: 48,
|
|
|
pickupLocationType: "business_without_dock",
|
|
|
deliveryLocationType: "business_with_dock",
|
|
|
packagingType: "crates",
|
|
|
freightClass: "100",
|
|
|
pickupLiftgate: true,
|
|
|
pickupPalletJack: true,
|
|
|
},
|
|
|
{
|
|
|
name: "Seattle→Miami 大票",
|
|
|
pickupZip: "98101",
|
|
|
deliveryZip: "33130",
|
|
|
palletCount: 10,
|
|
|
totalWeightLb: 14_000,
|
|
|
lengthIn: 48,
|
|
|
widthIn: 40,
|
|
|
heightIn: 72,
|
|
|
pickupLocationType: "business_with_dock",
|
|
|
deliveryLocationType: "business_with_dock",
|
|
|
packagingType: "pallets_48x40",
|
|
|
freightClass: "70",
|
|
|
callBeforePickup: true,
|
|
|
},
|
|
|
{
|
|
|
name: "Phoenix→Houston 无月台派送",
|
|
|
pickupZip: "85004",
|
|
|
deliveryZip: "77002",
|
|
|
palletCount: 4,
|
|
|
totalWeightLb: 3_200,
|
|
|
lengthIn: 48,
|
|
|
widthIn: 40,
|
|
|
heightIn: 48,
|
|
|
pickupLocationType: "business_with_dock",
|
|
|
deliveryLocationType: "business_without_dock",
|
|
|
packagingType: "pallets_48x40",
|
|
|
freightClass: "85",
|
|
|
deliveryLiftgate: true,
|
|
|
deliveryPalletJack: true,
|
|
|
},
|
|
|
{
|
|
|
name: "Portland→Dallas 长托",
|
|
|
pickupZip: "97201",
|
|
|
deliveryZip: "75201",
|
|
|
palletCount: 4,
|
|
|
totalWeightLb: 8_000,
|
|
|
lengthIn: 96,
|
|
|
widthIn: 48,
|
|
|
heightIn: 72,
|
|
|
pickupLocationType: "business_with_dock",
|
|
|
deliveryLocationType: "business_with_dock",
|
|
|
packagingType: "pallets_custom",
|
|
|
freightClass: "density",
|
|
|
turnable: true,
|
|
|
},
|
|
|
{
|
|
|
name: "Denver→NYC drums",
|
|
|
pickupZip: "80202",
|
|
|
deliveryZip: "10001",
|
|
|
palletCount: 5,
|
|
|
totalWeightLb: 4_000,
|
|
|
lengthIn: 48,
|
|
|
widthIn: 40,
|
|
|
heightIn: 48,
|
|
|
pickupLocationType: "business_with_dock",
|
|
|
deliveryLocationType: "business_with_dock",
|
|
|
packagingType: "drums",
|
|
|
freightClass: "92.5",
|
|
|
description: "drums",
|
|
|
},
|
|
|
{
|
|
|
name: "Chicago→Atlanta 学校派送",
|
|
|
pickupZip: "60611",
|
|
|
deliveryZip: "30326",
|
|
|
palletCount: 3,
|
|
|
totalWeightLb: 1_500,
|
|
|
lengthIn: 48,
|
|
|
widthIn: 40,
|
|
|
heightIn: 48,
|
|
|
pickupLocationType: "business_with_dock",
|
|
|
deliveryLocationType: "limited_school",
|
|
|
packagingType: "boxes",
|
|
|
freightClass: "110",
|
|
|
deliveryLiftgate: true,
|
|
|
deliveryInside: true,
|
|
|
callBeforeDelivery: true,
|
|
|
},
|
|
|
{
|
|
|
name: "BeverlyHills→Baltimore + load_to_ride",
|
|
|
pickupZip: "90212",
|
|
|
deliveryZip: "21230",
|
|
|
palletCount: 6,
|
|
|
totalWeightLb: 6_000,
|
|
|
lengthIn: 48,
|
|
|
widthIn: 40,
|
|
|
heightIn: 60,
|
|
|
pickupLocationType: "business_with_dock",
|
|
|
deliveryLocationType: "business_with_dock",
|
|
|
packagingType: "pallets_48x40",
|
|
|
freightClass: "70",
|
|
|
additionalServices: ["load_to_ride"],
|
|
|
vehicleTypes: ["box_truck", "dry_van", "refrigerated"],
|
|
|
},
|
|
|
{
|
|
|
name: "Philly→Boston 双侧无月台",
|
|
|
pickupZip: "19106",
|
|
|
deliveryZip: "02109",
|
|
|
palletCount: 2,
|
|
|
totalWeightLb: 700,
|
|
|
lengthIn: 48,
|
|
|
widthIn: 40,
|
|
|
heightIn: 48,
|
|
|
pickupLocationType: "business_without_dock",
|
|
|
deliveryLocationType: "business_without_dock",
|
|
|
packagingType: "pallets_48x40",
|
|
|
freightClass: "85",
|
|
|
pickupLiftgate: true,
|
|
|
deliveryLiftgate: true,
|
|
|
},
|
|
|
{
|
|
|
name: "Pittsburgh→LA 展会",
|
|
|
pickupZip: "15222",
|
|
|
deliveryZip: "90001",
|
|
|
palletCount: 4,
|
|
|
totalWeightLb: 2_400,
|
|
|
lengthIn: 48,
|
|
|
widthIn: 40,
|
|
|
heightIn: 48,
|
|
|
pickupLocationType: "business_with_dock",
|
|
|
deliveryLocationType: "trade_show",
|
|
|
packagingType: "crates",
|
|
|
freightClass: "125",
|
|
|
deliveryPalletJack: true,
|
|
|
},
|
|
|
{
|
|
|
name: "Houston→Seattle 48x48",
|
|
|
pickupZip: "77002",
|
|
|
deliveryZip: "98101",
|
|
|
palletCount: 8,
|
|
|
totalWeightLb: 12_000,
|
|
|
lengthIn: 48,
|
|
|
widthIn: 48,
|
|
|
heightIn: 60,
|
|
|
pickupLocationType: "business_with_dock",
|
|
|
deliveryLocationType: "business_with_dock",
|
|
|
packagingType: "pallets_48x48",
|
|
|
freightClass: "65",
|
|
|
stackable: true,
|
|
|
},
|
|
|
{
|
|
|
name: "Miami→Phoenix boxes",
|
|
|
pickupZip: "33130",
|
|
|
deliveryZip: "85004",
|
|
|
palletCount: 2,
|
|
|
totalWeightLb: 900,
|
|
|
lengthIn: 48,
|
|
|
widthIn: 40,
|
|
|
heightIn: 48,
|
|
|
pickupLocationType: "business_with_dock",
|
|
|
deliveryLocationType: "business_with_dock",
|
|
|
packagingType: "boxes",
|
|
|
freightClass: "150",
|
|
|
description: "cartons",
|
|
|
turnable: false,
|
|
|
},
|
|
|
{
|
|
|
name: "NYC→Dallas 住宅派送",
|
|
|
pickupZip: "10001",
|
|
|
deliveryZip: "75201",
|
|
|
palletCount: 5,
|
|
|
totalWeightLb: 3_500,
|
|
|
lengthIn: 48,
|
|
|
widthIn: 40,
|
|
|
heightIn: 48,
|
|
|
pickupLocationType: "business_with_dock",
|
|
|
deliveryLocationType: "residential",
|
|
|
packagingType: "boxes",
|
|
|
freightClass: "70",
|
|
|
deliveryLiftgate: true,
|
|
|
deliveryInside: true,
|
|
|
},
|
|
|
{
|
|
|
name: "SF→Austin + 预约前电联",
|
|
|
pickupZip: "94102",
|
|
|
deliveryZip: "78701",
|
|
|
palletCount: 3,
|
|
|
totalWeightLb: 1_800,
|
|
|
lengthIn: 48,
|
|
|
widthIn: 40,
|
|
|
heightIn: 48,
|
|
|
pickupLocationType: "residential",
|
|
|
deliveryLocationType: "business_with_dock",
|
|
|
packagingType: "boxes",
|
|
|
freightClass: "92.5",
|
|
|
pickupLiftgate: true,
|
|
|
pickupInside: true,
|
|
|
callBeforePickup: true,
|
|
|
callBeforeDelivery: true,
|
|
|
readyOffsetDays: 1,
|
|
|
},
|
|
|
{
|
|
|
name: "Baltimore→Gardena coils",
|
|
|
pickupZip: "21230",
|
|
|
deliveryZip: "90248",
|
|
|
palletCount: 4,
|
|
|
totalWeightLb: 6_500,
|
|
|
lengthIn: 48,
|
|
|
widthIn: 40,
|
|
|
heightIn: 48,
|
|
|
pickupLocationType: "business_with_dock",
|
|
|
deliveryLocationType: "business_with_dock",
|
|
|
packagingType: "coils",
|
|
|
freightClass: "60",
|
|
|
additionalServices: ["unloading"],
|
|
|
},
|
|
|
{
|
|
|
name: "Chicago→Denver 轻货偏移日",
|
|
|
pickupZip: "60611",
|
|
|
deliveryZip: "80202",
|
|
|
palletCount: 2,
|
|
|
totalWeightLb: 600,
|
|
|
lengthIn: 40,
|
|
|
widthIn: 36,
|
|
|
heightIn: 36,
|
|
|
pickupLocationType: "business_with_dock",
|
|
|
deliveryLocationType: "business_with_dock",
|
|
|
packagingType: "boxes",
|
|
|
freightClass: "density",
|
|
|
stackable: true,
|
|
|
additionalServices: ["unloading"],
|
|
|
readyOffsetDays: 2,
|
|
|
},
|
|
|
];
|
|
|
|
|
|
function offsetPickupIso(baseIso: string, offsetDays: number): string {
|
|
|
if (!offsetDays) return baseIso;
|
|
|
const [y, m, d] = baseIso.split("-").map(Number);
|
|
|
const dt = new Date(y!, m! - 1, d!);
|
|
|
let left = offsetDays;
|
|
|
while (left > 0) {
|
|
|
dt.setDate(dt.getDate() + 1);
|
|
|
const iso = toIsoDateLocal(dt);
|
|
|
if (!isMothershipWeekendIso(iso)) left -= 1;
|
|
|
}
|
|
|
return snapMothershipReadyDateToWeekday(toIsoDateLocal(dt));
|
|
|
}
|
|
|
|
|
|
function sleep(ms: number): Promise<void> {
|
|
|
return new Promise((r) => setTimeout(r, ms));
|
|
|
}
|
|
|
|
|
|
type Row = {
|
|
|
n: number;
|
|
|
name: string;
|
|
|
ok: boolean;
|
|
|
ms: number;
|
|
|
tiers: number;
|
|
|
detail: string;
|
|
|
code?: string;
|
|
|
};
|
|
|
|
|
|
function assertCasesValid(cases: CaseDef[]): void {
|
|
|
for (const c of cases) {
|
|
|
if (c.palletCount < 1) throw new Error(`case ${c.name}: palletCount`);
|
|
|
if (c.totalWeightLb > 45_000) throw new Error(`case ${c.name}: weight`);
|
|
|
if (c.pickupZip === c.deliveryZip) {
|
|
|
throw new Error(`case ${c.name}: same zip`);
|
|
|
}
|
|
|
if (
|
|
|
!fitsFlockTrailerCargo({
|
|
|
palletCount: c.palletCount,
|
|
|
lengthIn: c.lengthIn,
|
|
|
widthIn: c.widthIn,
|
|
|
})
|
|
|
) {
|
|
|
throw new Error(`case ${c.name}: linear feet`);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
async function main(): Promise<void> {
|
|
|
assertCasesValid(FLOCK_LOGGED_IN_X20_CASES);
|
|
|
|
|
|
const onlyRaw = process.env.DIAG_ONLY?.trim() ?? "";
|
|
|
const onlyNums = onlyRaw
|
|
|
? onlyRaw
|
|
|
.split(/[,,\s]+/)
|
|
|
.map((s) => s.trim())
|
|
|
.filter((s) => /^\d+$/.test(s))
|
|
|
.map(Number)
|
|
|
: [];
|
|
|
const selected = FLOCK_LOGGED_IN_X20_CASES.map((c, i) => ({ c, n: i })).filter(
|
|
|
({ c, n }) => {
|
|
|
if (!onlyRaw) return true;
|
|
|
if (onlyNums.length > 0) return onlyNums.includes(n + 1);
|
|
|
return c.name.includes(onlyRaw);
|
|
|
},
|
|
|
);
|
|
|
if (selected.length === 0) {
|
|
|
throw new Error(`DIAG_ONLY=${onlyRaw} 无匹配用例`);
|
|
|
}
|
|
|
|
|
|
const customerId = process.env.DIAG_CUSTOMER_ID?.trim() || "CUST_001";
|
|
|
const login = await getCustomerProviderLogin(customerId, "flock");
|
|
|
if (!login) {
|
|
|
throw new Error(`客户 ${customerId} 无可用 Flock 账密`);
|
|
|
}
|
|
|
|
|
|
console.log("=== Flock Freight 登录态 ×20 多线路探测 ===");
|
|
|
console.log(
|
|
|
`headed=${wantHeaded} customer=${customerId} email=${login.email.slice(0, 2)}*** ` +
|
|
|
`pickupBase=${PICKUP_BASE} cases=${selected.length}/${FLOCK_LOGGED_IN_X20_CASES.length}` +
|
|
|
(onlyRaw ? ` only=${onlyRaw}` : "") +
|
|
|
` delayMs=${DELAY_MS}`,
|
|
|
);
|
|
|
|
|
|
const rows: Row[] = [];
|
|
|
for (let i = 0; i < selected.length; i += 1) {
|
|
|
const { c, n } = selected[i]!;
|
|
|
if (i > 0 && DELAY_MS > 0) await sleep(DELAY_MS);
|
|
|
|
|
|
const pickupIso = offsetPickupIso(PICKUP_BASE, c.readyOffsetDays ?? 0);
|
|
|
const pickupDate = flockPickupDisplayFromIso(pickupIso);
|
|
|
const t0 = Date.now();
|
|
|
console.log(
|
|
|
`\n---------- ${n + 1}/20 ${c.name} ${c.pickupZip}→${c.deliveryZip} ` +
|
|
|
`${c.palletCount}pl ${c.totalWeightLb}lb ----------`,
|
|
|
);
|
|
|
console.log(
|
|
|
` loc=${c.pickupLocationType}/${c.deliveryLocationType} pack=${c.packagingType} ` +
|
|
|
`svc=[${(c.additionalServices ?? []).join(",")}] date=${pickupDate}`,
|
|
|
);
|
|
|
|
|
|
const input: FlockQuoteInput = {
|
|
|
formMode: "logged_in_quick",
|
|
|
pickupDate,
|
|
|
pickupZip: c.pickupZip,
|
|
|
deliveryZip: c.deliveryZip,
|
|
|
palletCount: c.palletCount,
|
|
|
totalWeightLb: c.totalWeightLb,
|
|
|
lengthIn: c.lengthIn,
|
|
|
widthIn: c.widthIn,
|
|
|
heightIn: c.heightIn,
|
|
|
pickupLocationType: c.pickupLocationType,
|
|
|
deliveryLocationType: c.deliveryLocationType,
|
|
|
packagingType: c.packagingType,
|
|
|
freightClass: c.freightClass,
|
|
|
description: c.description,
|
|
|
stackable: c.stackable,
|
|
|
turnable: c.turnable,
|
|
|
additionalServices: c.additionalServices,
|
|
|
vehicleTypes: c.vehicleTypes,
|
|
|
pickupLiftgate: c.pickupLiftgate,
|
|
|
pickupInside: c.pickupInside,
|
|
|
pickupPalletJack: c.pickupPalletJack,
|
|
|
deliveryLiftgate: c.deliveryLiftgate,
|
|
|
deliveryInside: c.deliveryInside,
|
|
|
deliveryPalletJack: c.deliveryPalletJack,
|
|
|
callBeforePickup: c.callBeforePickup,
|
|
|
callBeforeDelivery: c.callBeforeDelivery,
|
|
|
};
|
|
|
|
|
|
const result = await runFlockQuoteRpa(input, { customerId });
|
|
|
const ms = Date.now() - t0;
|
|
|
if (result.ok && result.quotes.length > 0) {
|
|
|
const preview = result.quotes
|
|
|
.slice(0, 4)
|
|
|
.map((q) => `${q.label}=$${q.totalUsd}`)
|
|
|
.join(", ");
|
|
|
rows.push({
|
|
|
n: n + 1,
|
|
|
name: c.name,
|
|
|
ok: true,
|
|
|
ms,
|
|
|
tiers: result.quotes.length,
|
|
|
detail: `${result.quotes.length}档 ${preview}${result.quotes.length > 4 ? "…" : ""}`,
|
|
|
});
|
|
|
console.log(`[OK] ${ms}ms tiers=${result.quotes.length} ${preview}`);
|
|
|
} else {
|
|
|
const msg = (result.errorMessage ?? "no quotes").slice(0, 240);
|
|
|
const codeMatch = /^([A-Z][A-Z0-9_]+):/.exec(msg);
|
|
|
rows.push({
|
|
|
n: n + 1,
|
|
|
name: c.name,
|
|
|
ok: false,
|
|
|
ms,
|
|
|
tiers: 0,
|
|
|
detail: msg,
|
|
|
code: codeMatch?.[1],
|
|
|
});
|
|
|
console.log(`[FAIL] ${ms}ms ${msg.slice(0, 180)}`);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
const okN = rows.filter((r) => r.ok).length;
|
|
|
const failN = rows.length - okN;
|
|
|
const avgOkMs =
|
|
|
okN > 0
|
|
|
? Math.round(
|
|
|
rows.filter((r) => r.ok).reduce((s, r) => s + r.ms, 0) / okN,
|
|
|
)
|
|
|
: 0;
|
|
|
|
|
|
const outDir = path.join(process.cwd(), ".rpa", "diag");
|
|
|
fs.mkdirSync(outDir, { recursive: true });
|
|
|
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
|
const reportPath = path.join(outDir, `flock-logged-in-x20-${stamp}.md`);
|
|
|
const lines = [
|
|
|
`# Flock Freight 登录态 ×20`,
|
|
|
``,
|
|
|
`- customer: ${customerId}`,
|
|
|
`- pickupBase: ${PICKUP_BASE}`,
|
|
|
`- ok=${okN} fail=${failN} avgOkMs=${avgOkMs}`,
|
|
|
``,
|
|
|
`| # | 结果 | 用例 | ms | tiers | 详情 |`,
|
|
|
`|---|------|------|----|-------|------|`,
|
|
|
...rows.map(
|
|
|
(r) =>
|
|
|
`| ${r.n} | ${r.ok ? "OK" : "FAIL"} | ${r.name} | ${r.ms} | ${r.tiers} | ${(r.code ? `[${r.code}] ` : "") + r.detail.replace(/\|/g, "/")} |`,
|
|
|
),
|
|
|
];
|
|
|
fs.writeFileSync(reportPath, lines.join("\n"), "utf8");
|
|
|
|
|
|
console.log(
|
|
|
`\n=== SUMMARY ok=${okN}/${FLOCK_LOGGED_IN_X20_CASES.length} fail=${failN} avgOkMs=${avgOkMs} ===`,
|
|
|
);
|
|
|
console.log(`report=${reportPath}`);
|
|
|
for (const r of rows) {
|
|
|
console.log(
|
|
|
` #${r.n} ${r.ok ? "OK " : "FAIL"} ${r.name} ${r.ms}ms tiers=${r.tiers}` +
|
|
|
(r.ok ? "" : ` [${r.code ?? "-"}]`),
|
|
|
);
|
|
|
}
|
|
|
|
|
|
if (failN > 0) process.exitCode = 1;
|
|
|
}
|
|
|
|
|
|
const isDirectRun =
|
|
|
typeof process.argv[1] === "string" &&
|
|
|
/diag-flock-logged-in-x20\.(ts|js|mts|mjs)$/i.test(
|
|
|
process.argv[1].replace(/\\/g, "/"),
|
|
|
);
|
|
|
|
|
|
if (isDirectRun) {
|
|
|
main().catch((err) => {
|
|
|
console.error(err);
|
|
|
process.exit(1);
|
|
|
});
|
|
|
} |