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

+14 -1
View File
@@ -21,7 +21,10 @@ import { createAreaStore, summarizeArea } from '../data/areaStore.js';
import { createAreaResolver, foldLatinMarks } from '../data/areaResolver.js';
import { circleRing, displayParts } from '../data/areaGeometry.js';
import { findNaturalRegion } from '../data/naturalEarthRegions.js';
import { findAdminCandidates as findBundledAdminCandidates } from '../data/adminBoundaries.js';
import {
adminQueryIdentity,
findAdminCandidates as findBundledAdminCandidates,
} from '../data/adminBoundaries.js';
import {
NATURAL_EARTH_CREDIT,
US_CENSUS_CREDIT,
@@ -87,6 +90,16 @@ function administrativeIdentity(value) {
.toLocaleLowerCase()
.replace(/[^\p{L}\p{M}\p{N}]+/gu, ' ')
.trim();
// Forms the administrative matcher reads as one unit's name: "State of
// Texas" is Texas at admin level 1, "USA" is United States of America.
const named = adminQueryIdentity(value);
if (named?.level === 'admin1')
return {
name: foldLatinMarks(normalized.replace(/^(?:the )?.+? of /, '')),
level: 'admin1',
};
if (named?.level === 'country')
return { name: foldLatinMarks(named.name), level: 'country' };
const match = normalized.match(
/\b(country|nation|province|state|region|zone|oblast|prefecture|canton|governorate|county|district|department)$/,
);
+44
View File
@@ -8,6 +8,7 @@ import assert from 'node:assert/strict';
import { createVoiceAreas, spokenArea } from './areaActions.js';
import { pointInPreparedArea } from '../data/areaGeometry.js';
import { presentResult } from './resultDisplay.js';
import { findAdminCandidates as findBundledAdminCandidates } from '../data/adminBoundaries.js';
const square = (w, s, e, n) => [
[w, s],
@@ -392,6 +393,49 @@ test('a verified province accepts a bare query with a separate level', async ()
assert.equal(retired.areas.store.size, 0);
});
test('an explicit level accepts the names the administrative matcher reads as that unit', async () => {
const bundled = () =>
makeAreas({ findAdminCandidates: findBundledAdminCandidates }).areas;
for (const [query, level, name] of [
['State of Texas', 'admin1', 'Texas'],
['State of Texas', undefined, 'Texas'],
['USA', 'country', 'United States of America'],
['UK', 'country', 'United Kingdom'],
]) {
const result = await bundled().resolveAreaAction({ query, level });
assert.equal(result.ok, true, `${query} (${level}): ${result.error}`);
assert.equal(result.name, name);
assert.equal(result.level, level || 'admin1');
}
// The level still has to match the unit.
for (const [query, level] of [
['State of Texas', 'country'],
['USA', 'admin1'],
]) {
const areas = bundled();
const result = await areas.resolveAreaAction({ query, level });
assert.equal(result.ok, false, `${query} is not a ${level}`);
assert.equal(areas.store.size, 0);
}
// The outline ladder's identity check reads the same forms.
const prefixed = makeAreas().areas;
const bagmati = await prefixed.resolveAreaAction({
query: 'Province of Bagmati',
level: 'admin1',
});
assert.equal(bagmati.ok, true, bagmati.error);
assert.equal(bagmati.name, 'Bagmati Province');
const wrongLevel = makeAreas().areas;
const county = await wrongLevel.resolveAreaAction({
query: 'Province of Bagmati',
level: 'admin2',
});
assert.equal(county.ok, false);
assert.equal(county.code, 'AREA_IDENTITY_MISMATCH');
assert.equal(wrongLevel.store.size, 0);
});
test('a current Zone the source verified is not refused as retired', async () => {
const gamo = makeAreas({
annotationResolver: fakeLadder({