fix(area): accept "State of Texas" and "USA" as the units they name

resolve_area compared the spoken name with the unit's name literally, so
"State of Texas" (admin1) and "USA" or "UK" (country) were refused with
AREA_IDENTITY_MISMATCH although the bundled matcher found exactly those
units. Both identity checks, the bundled one and the outline ladder's, now
also accept the prefix forms and same-unit country aliases the matcher
reads, at the level they imply, so the level check still applies.
"Great Britain", "Britain", "America" and "Korea" still need their own
names, and the natural-region check stays literal.
This commit is contained in:
Sameh Khamis committed 2026-11-03 09:12:28 -08:00
1 parent 540629a0fc
commit 68667e2e3c
5 files changed
+128 -6

No files matched your search

+43
View File
@@ -421,6 +421,49 @@ const COUNTRY_TARGET_ALIASES = new Map([
['drc', 'democratic republic of the congo'],
]);
/**
* Country aliases that are another name for the very same unit. "Great
* Britain" and "Britain" are islands, "America" is also two continents and
* "Korea" two countries, so those find a country without naming it.
*/
const COUNTRY_IDENTITY_ALIASES = new Set([
'usa',
'us',
'u s',
'u s a',
'united states',
'uk',
'u k',
'russian federation',
'czechia',
'tanzania',
'drc',
]);
/**
* The unit an ask names in the matcher's own terms, when its words differ
* from the unit's name: "State of Texas" names Texas at admin level 1, and
* "USA" or "Country of USA" names United States of America. Null when the
* ask is just a name. Names are folded with `normalizeAdminName`.
* @param {string} text
* @returns {{name: string, level: 'country'|'admin1'}|null}
*/
export function adminQueryIdentity(text) {
const parsed = parseAdminQuery(text);
if (!parsed?.name) return null;
if (parsed.kind === 'state' && parsed.form === 'prefix')
return { name: parsed.name, level: 'admin1' };
if (parsed.kind !== 'country' && parsed.kind !== 'any') return null;
if (COUNTRY_IDENTITY_ALIASES.has(parsed.name))
return {
name: COUNTRY_TARGET_ALIASES.get(parsed.name),
level: 'country',
};
return parsed.kind === 'country'
? { name: parsed.name, level: 'country' }
: null;
}
const COUNTRY_ALIASES = new Map([
...COUNTRY_TARGET_ALIASES,
['england', 'united kingdom'],
+26 -4
View File
@@ -25,6 +25,7 @@
import { AREA_LEVELS } from './areaStore.js';
import { pointInPreparedArea, prepareArea } from './areaGeometry.js';
import { adminQueryIdentity, normalizeAdminName } from './adminBoundaries.js';
import {
isAreaCandidateId,
MAX_AREA_CANDIDATE_ID_LENGTH,
@@ -149,8 +150,18 @@ function explicitAdminName(text) {
};
}
/** Fail closed when a bundled unit's own name contradicts the requested unit. */
function bundledAdminIdentityRefusal(admin, query, wantedLevel) {
/**
* Fail closed when a bundled unit's own name contradicts the requested unit.
* With `aliases`, an ask the administrative matcher reads as naming this very
* unit at its level ("State of Texas", "USA") also matches; the natural-region
* check leaves it off, since "Great Britain" is not the United Kingdom.
*/
function bundledAdminIdentityRefusal(
admin,
query,
wantedLevel,
{ aliases = true } = {},
) {
const source = explicitAdminName(admin?.name || '');
const requested = explicitAdminName(query);
const expectedLevel = wantedLevel || requested.level;
@@ -161,8 +172,16 @@ function bundledAdminIdentityRefusal(admin, query, wantedLevel) {
code: 'AREA_IDENTITY_UNVERIFIED',
error: `The bundled boundary is ${admin?.name || 'an unverified administrative unit'}, not a verified current match for "${query}".`,
};
const alias = aliases ? adminQueryIdentity(query) : null;
const named =
source.bare === requested.bare ||
Boolean(
alias &&
alias.level === emittedLevel &&
alias.name === normalizeAdminName(admin.name),
);
if (
source.bare !== requested.bare ||
!named ||
(source.level && expectedLevel && source.level !== expectedLevel) ||
(expectedLevel && emittedLevel !== expectedLevel)
)
@@ -343,7 +362,10 @@ export function createAreaResolver({
places.length > 0 &&
level !== 'natural' &&
(await findAdminCandidates(query, { signal }).catch(() => [])).some(
(admin) => !bundledAdminIdentityRefusal(admin, query, null),
(admin) =>
!bundledAdminIdentityRefusal(admin, query, null, {
aliases: false,
}),
);
signal?.throwIfAborted?.();
if (places.length > 1 && !shadowsAdmin) {