import { scoreSuggestionLabelMatch } from "@/workers/rpa/address-suggestions"; import { normalizeAxelPlaceId } from "@/workers/rpa/axel-selection-bridge"; export type AxelSearchCandidate = { placeId: string; description: string; mainText?: string; secondaryText?: string; types?: string[]; }; const STANDARD_PLACE_ID_RE = /^ChIJ[A-Za-z0-9_-]{23}$/; function extractStreetNumber(query: string): string | null { return query.trim().match(/^\d+/)?.[0] ?? null; } function mainTextHasStreetNumber( streetNum: string, row: AxelSearchCandidate, ): boolean { const blob = `${row.mainText ?? ""} ${row.description}`.replace(/\s+/g, " "); return new RegExp(`\\b${streetNum}\\b`).test(blob); } function isRouteOnlyCandidate(row: AxelSearchCandidate): boolean { const types = row.types ?? []; return types.includes("route") && !types.includes("street_address"); } function scoreCandidate( query: string, row: AxelSearchCandidate, streetNum: string | null, ): number { const label = row.description.trim(); let score = scoreSuggestionLabelMatch(query, label); if (streetNum) { if (mainTextHasStreetNumber(streetNum, row)) { score += 80; } else { score -= 60; } if (isRouteOnlyCandidate(row)) { score -= 50; } } const normalized = normalizeAxelPlaceId(row.placeId); if (STANDARD_PLACE_ID_RE.test(normalized)) { score += 10; } else if (!row.placeId.startsWith("ChIJ")) { score -= 20; } return score; } /** 从 axel search 结果中选取与用户查询最匹配的候选 */ export function pickBestSearchCandidate( query: string, candidates: AxelSearchCandidate[], ): AxelSearchCandidate | null { if (candidates.length === 0) { return null; } const streetNum = extractStreetNumber(query); if (streetNum) { const withNumber = candidates.filter((row) => mainTextHasStreetNumber(streetNum, row), ); if (withNumber.length > 0) { return rankCandidates(query, withNumber, streetNum); } } return rankCandidates(query, candidates, streetNum); } function rankCandidates( query: string, candidates: AxelSearchCandidate[], streetNum: string | null, ): AxelSearchCandidate | null { const scored = candidates.map((row) => ({ row, score: scoreCandidate(query, row, streetNum), })); scored.sort((a, b) => b.score - a.score); return scored[0]?.row ?? null; }