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/probe-place-diagnostic.ts

233 lines
6.9 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.

/**
* P0/P2 place commit 诊断探针
*
* PowerShell:
* $env:RPA_PLACE_DIAG="true"
* $env:RPA_HEADLESS="false"
* npx tsx scripts/probe-place-diagnostic.ts
*/
process.env.RPA_PLACE_DIAG = "true";
import { randomUUID } from "node:crypto";
import { mkdirSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { loadDevEnv } from "@/lib/dev-env";
import {
hostFetchMothershipCandidates,
} from "@/lib/frontend/api-client";
import { applyMothershipCandidate } from "@/lib/frontend/mothership-address";
import type { QuoteRequest } from "@/modules/providers/quote-provider";
import { getSelector } from "@/lib/rpa/selectors";
import { AxelSelectionBridge } from "@/workers/rpa/axel-selection-bridge";
import {
ensureAddressSuggestions,
} from "@/workers/rpa/address-suggestions";
import { openAddressSide, visibleStreetInput } from "@/workers/rpa/address-side";
import { commitPlaceWithStrategy } from "@/workers/rpa/fix-place";
import {
collectPlaceDiagSnapshot,
interpretPlaceDiagnostic,
setPlaceDiagMark,
} from "@/workers/rpa/fix-place/place-diagnostic-probe";
import {
P0_PLACE_DIAG_INIT,
P2_FETCH_HIJACK_INIT,
} from "@/workers/rpa/fix-place/place-diagnostic-init";
import { RPAContext } from "@/workers/rpa/kernel/context";
import {
stabilizeQuoteLandingPage,
waitForQuoterWidgetHydrated,
} from "@/workers/rpa/page-prep";
import { closeBrowser, openQuotePage } from "@/workers/rpa/session-manager";
import { quotePageAdapter } from "@/workers/rpa/quote-page-adapter";
loadDevEnv();
process.env.RPA_MOCK_MODE = "false";
process.env.RPA_ADDRESS_MODE = "real";
if (!process.env.RPA_HEADLESS) {
process.env.RPA_HEADLESS = "false";
}
const BASE = process.env.PROVE_BASE_URL ?? "http://localhost:3000";
const TOKEN = "demo-host-token";
const CUSTOMER_ID = "CUST_001";
const OUT_DIR = join(process.cwd(), ".rpa", "place-diagnostic");
const PAIRS = {
pickup: {
street: "Washington State Convention Center Main Garage, Pike Street",
city: "Seattle",
state: "WA",
},
delivery: {
street: "Ocean Drive",
city: "Miami Beach",
state: "FL",
},
} as const;
function draftAddress(side: keyof typeof PAIRS) {
const d = PAIRS[side];
return {
street: d.street,
city: d.city,
state: d.state,
zip: "",
place_id: `draft_${side}`,
formatted_address: `${d.street}, ${d.city}, ${d.state}`,
selected_from_suggestions: true,
};
}
async function main(): Promise<void> {
const runId = randomUUID().slice(0, 8);
const side = (process.env.PROBE_PLACE_SIDE === "delivery" ? "delivery" : "pickup") as
| "pickup"
| "delivery";
mkdirSync(OUT_DIR, { recursive: true });
const candRes = await hostFetchMothershipCandidates(
BASE,
TOKEN,
CUSTOMER_ID,
draftAddress("pickup"),
draftAddress("delivery"),
);
if (candRes.code !== 0 || !candRes.data) {
throw new Error(candRes.message ?? "候选 API 失败");
}
const candidate =
side === "pickup"
? candRes.data.pickup_candidates[0]
: candRes.data.delivery_candidates[0];
if (!candidate) {
throw new Error(`${side} 无候选`);
}
const applied = applyMothershipCandidate(draftAddress(side), candidate);
const address: QuoteRequest["pickup"] = {
street: applied.street,
city: applied.city,
state: applied.state,
zip: applied.zip ?? "",
placeId: candidate.option_id,
formattedAddress: applied.formatted_address,
selectedFromSuggestions: true,
mothershipOptionId: candidate.option_id,
mothershipDisplayLabel: candidate.display_label,
selectedFromMothership: true,
};
const queryHint = {
street: address.street,
city: address.city ?? "",
state: address.state ?? "",
zip: address.zip ?? "",
};
console.log(`\n=== place-diagnostic probe run=${runId} side=${side} ===`);
console.log(`label: ${address.mothershipDisplayLabel}`);
console.log(`placeId: ${candidate.option_id?.slice(0, 32)}\n`);
const opened = await openQuotePage(quotePageAdapter, false, undefined, {
freshCargoContext: true,
});
await opened.context.addInitScript({ content: P0_PLACE_DIAG_INIT });
await opened.context.addInitScript({ content: P2_FETCH_HIJACK_INIT });
await opened.page.reload({ waitUntil: "domcontentloaded" });
const ctx = RPAContext.fromSession(opened.page, opened.context);
await stabilizeQuoteLandingPage(ctx);
await waitForQuoterWidgetHydrated(ctx.page, 60_000);
await setPlaceDiagMark(ctx.page, `${runId}-page-ready`);
let baseline = await collectPlaceDiagSnapshot(ctx.page);
if (!baseline?.ok) {
console.warn("[probe] baseline 快照失败P0 可能未就绪", baseline);
baseline = baseline ?? { ok: false, reason: "snapshot-undefined" };
}
writeFileSync(
join(OUT_DIR, `${runId}-baseline.json`),
JSON.stringify(baseline, null, 2),
"utf8",
);
for (const line of interpretPlaceDiagnostic(baseline, "baseline")) {
console.log(line);
}
await openAddressSide(ctx, side);
const street = await visibleStreetInput(ctx, side, {
deliverySectionOpened: side === "delivery",
});
if (!street) {
throw new Error("地址输入框不可见");
}
const bridge = new AxelSelectionBridge();
bridge.attach(ctx.page);
bridge.clearCandidates();
await ensureAddressSuggestions(ctx.page, street, queryHint, {
mothershipLabel: address.mothershipDisplayLabel!,
preferConfirmedLabel: true,
});
await bridge.waitForSearchResults(12_000);
const binding = bridge.resolveSelection(address.mothershipDisplayLabel!, queryHint);
if (!binding?.placeId) {
throw new Error("search 未绑定 placeId");
}
await setPlaceDiagMark(ctx.page, `${runId}-before-fix-place`);
const fixResult = await commitPlaceWithStrategy(
ctx.page,
bridge,
binding.placeId,
binding.description,
side,
undefined,
{ input: street, query: queryHint },
);
const finalSnap = await collectPlaceDiagSnapshot(ctx.page);
const report = {
runId,
side,
address: {
label: address.mothershipDisplayLabel,
placeId: binding.placeId,
},
fixResult,
bridge: {
placeNetworkLog: bridge.getPlaceNetworkLog(),
hasPlaceCommit: bridge.hasPlaceCommit(binding.placeId),
httpOk: bridge.hasSuccessfulPlaceNetworkRequest(binding.placeId),
},
interpretation: interpretPlaceDiagnostic(finalSnap, "after-fix-place"),
snapshot: finalSnap,
finishedAt: new Date().toISOString(),
};
const reportPath = join(OUT_DIR, `${runId}-report.json`);
writeFileSync(reportPath, JSON.stringify(report, null, 2), "utf8");
console.log("\n=== fix-place 结果 ===");
console.log(JSON.stringify(fixResult, null, 2));
console.log("\n=== P0/P2 结论 ===");
for (const line of report.interpretation) {
console.log(line);
}
console.log(`\n报告${reportPath}`);
await opened.page.close().catch(() => undefined);
await opened.context.close().catch(() => undefined);
await closeBrowser();
}
main().catch((error) => {
console.error(error);
process.exit(1);
});