|
|
import type { Page } from "playwright";
|
|
|
import type { QuoteRequest } from "@/modules/providers/quote-provider";
|
|
|
import { pageLocator } from "@/lib/rpa/locator";
|
|
|
import { DETECT_MOTHERSHIP_ADDRESS_CHIP } from "@/workers/rpa/kernel/browser-scripts";
|
|
|
|
|
|
type AddressFields = QuoteRequest["pickup"];
|
|
|
|
|
|
export type AddressChipVisualProbe = {
|
|
|
committed: boolean;
|
|
|
reason: string;
|
|
|
streetLine?: string;
|
|
|
cityLine?: string;
|
|
|
preview?: string;
|
|
|
};
|
|
|
|
|
|
/** 纯函数:截图 chip 两行布局(门牌街名行 + City, ST 行) */
|
|
|
export function evaluateMotherShipAddressChipLines(
|
|
|
lines: string[],
|
|
|
streetNum: string,
|
|
|
cityToken: string,
|
|
|
): AddressChipVisualProbe {
|
|
|
const num = streetNum.trim();
|
|
|
const cityWord = cityToken.trim().split(/\s+/)[0]?.toLowerCase() ?? "";
|
|
|
if (!num) {
|
|
|
return { committed: false, reason: "no-street-num" };
|
|
|
}
|
|
|
|
|
|
for (let i = 0; i < lines.length - 1; i++) {
|
|
|
const streetLine = lines[i]!.trim();
|
|
|
const cityLine = lines[i + 1]!.trim();
|
|
|
if (!streetLine.includes(num)) {
|
|
|
continue;
|
|
|
}
|
|
|
// 未点选:整段查询串仍在同一行(含多个逗号分隔段)
|
|
|
if (streetLine.split(",").filter((p) => p.trim().length > 0).length > 1) {
|
|
|
continue;
|
|
|
}
|
|
|
if (!/,\s*[A-Z]{2}\b/.test(cityLine)) {
|
|
|
continue;
|
|
|
}
|
|
|
if (cityWord && !cityLine.toLowerCase().includes(cityWord)) {
|
|
|
continue;
|
|
|
}
|
|
|
return {
|
|
|
committed: true,
|
|
|
reason: "two-line-chip",
|
|
|
streetLine,
|
|
|
cityLine,
|
|
|
};
|
|
|
}
|
|
|
|
|
|
return { committed: false, reason: "no-two-line-chip" };
|
|
|
}
|
|
|
|
|
|
function chipProbeArgs(address: AddressFields, widgetSelector: string) {
|
|
|
return {
|
|
|
widgetSelector,
|
|
|
streetNum: address.street.match(/^\d+/)?.[0] ?? "",
|
|
|
cityToken: address.city ?? "",
|
|
|
streetCore: address.street.split(",")[0]?.trim() ?? address.street.trim(),
|
|
|
};
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 唯一联想确认标准:widget 内出现截图同款两行 chip(1234 Warehouse Street / Los Angeles, CA)。
|
|
|
* 仍在 Search 输入框内打字(多逗号长串)一律视为未确认。
|
|
|
*/
|
|
|
export async function probeAddressChipVisual(
|
|
|
page: Page,
|
|
|
widgetSelector: string,
|
|
|
address: AddressFields,
|
|
|
): Promise<AddressChipVisualProbe> {
|
|
|
const args = chipProbeArgs(address, widgetSelector);
|
|
|
const raw = (await page
|
|
|
.evaluate(DETECT_MOTHERSHIP_ADDRESS_CHIP, args)
|
|
|
.catch(() => null)) as AddressChipVisualProbe | null;
|
|
|
if (raw && typeof raw.committed === "boolean") {
|
|
|
return raw;
|
|
|
}
|
|
|
return { committed: false, reason: "probe-failed" };
|
|
|
}
|
|
|
|
|
|
export async function isAddressChipVisuallyCommitted(
|
|
|
page: Page,
|
|
|
widgetSelector: string,
|
|
|
address: AddressFields,
|
|
|
): Promise<boolean> {
|
|
|
const probe = await probeAddressChipVisual(page, widgetSelector, address);
|
|
|
if (probe.committed) {
|
|
|
console.log(
|
|
|
`[rpa] chip-visual: committed reason=${probe.reason} ` +
|
|
|
`street="${probe.streetLine?.slice(0, 48) ?? ""}" ` +
|
|
|
`city="${probe.cityLine?.slice(0, 32) ?? ""}"`,
|
|
|
);
|
|
|
} else {
|
|
|
console.log(
|
|
|
`[rpa] chip-visual: not-committed reason=${probe.reason}` +
|
|
|
(probe.preview ? ` preview="${probe.preview.slice(0, 60)}"` : ""),
|
|
|
);
|
|
|
}
|
|
|
return probe.committed;
|
|
|
}
|
|
|
|
|
|
/** 提货 chip 视觉确认 + 「Deliver to」占位可见(截图态) */
|
|
|
export async function isPickupChipVisuallyReady(
|
|
|
page: Page,
|
|
|
widgetSelector: string,
|
|
|
pickup: AddressFields,
|
|
|
): Promise<boolean> {
|
|
|
if (!(await isAddressChipVisuallyCommitted(page, widgetSelector, pickup))) {
|
|
|
return false;
|
|
|
}
|
|
|
return pageLocator(page, widgetSelector)
|
|
|
.getByText(/^Deliver to$/i)
|
|
|
.first()
|
|
|
.isVisible()
|
|
|
.catch(() => false);
|
|
|
}
|