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.

119 lines
3.5 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.

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 内出现截图同款两行 chip1234 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);
}