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.

94 lines
2.3 KiB

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;
}