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