mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
fix(voice): resolve areas per view, without accents and per natural place
From review:
- An answer the view picked ("Springfield" over Illinois) is reused or
shared only within the same ~2° view cell, so asking again elsewhere no
longer gets the first state's area.
- Admin names compare with accents folded on Latin letters only: "Quebec"
matches "Québec" and "Cordoba" asks between Argentina and Spain, while a
Thai or Devanagari mark still tells names apart.
- A Natural Earth name used by several features is one place with every
part when they touch (the Great Rift Valley, the Lau Group across 180°)
and a question when they are far apart (Flores); natural places can now
be candidates.
- The AREAS rule and the area tool descriptions are tighter, leaving a
~270-character margin under the 55,500 prompt ceiling, and resolve_area
no longer names osm_query when that tool is not offered.
This commit is contained in:
1 parent
fb74354d30
commit
207373389a
12 files changed
+328
-22
No files matched your search
@@ -53,8 +53,9 @@ test('the complete Realtime tool payload pins the manifest-generated layer relea
|
||||
// The Operator HUD layout leaves set_hud's layout enum.
|
||||
// The layer alias hint leaves out aliases the layer id already spells.
|
||||
// Voice geometry adds resolve_area, find_imagery, osm_query, the analyst
|
||||
// area scopes and the osm-places layer.
|
||||
'6a60cb849fe6835295354b0a6711c9d2b6428114f66d14690430f1732070b59c',
|
||||
// area scopes and the osm-places layer; natural-region candidates and the
|
||||
// tightened area tool wording follow review.
|
||||
'1e07573229f7901148a8e2d30d5111dc359ed35156aa7878afac8f0d940badd8',
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
*/
|
||||
|
||||
import { createAreaStore, summarizeArea } from '../data/areaStore.js';
|
||||
import { createAreaResolver } from '../data/areaResolver.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';
|
||||
@@ -91,7 +91,10 @@ function administrativeIdentity(value) {
|
||||
/\b(country|nation|province|state|region|zone|oblast|prefecture|canton|governorate|county|district|department)$/,
|
||||
);
|
||||
return {
|
||||
name: match ? normalized.slice(0, -match[0].length).trim() : normalized,
|
||||
// Compared, never shown: "Quebec" as spoken names "Québec".
|
||||
name: foldLatinMarks(
|
||||
match ? normalized.slice(0, -match[0].length).trim() : normalized,
|
||||
),
|
||||
level: match ? ADMIN_IDENTITY_LEVEL[match[1]] || null : null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -412,6 +412,27 @@ test('a current Zone the source verified is not refused as retired', async () =>
|
||||
assert.equal(gamo.areas.store.size, 1);
|
||||
});
|
||||
|
||||
test('an outline named with accents is accepted for the same name spoken without them', async () => {
|
||||
const quebec = makeAreas({
|
||||
annotationResolver: fakeLadder({
|
||||
label: 'Québec',
|
||||
match: /quebec/i,
|
||||
outline: {
|
||||
ring: BAGMATI_POLYGONS[0][0],
|
||||
polygons: BAGMATI_POLYGONS,
|
||||
adminArea: 'Québec',
|
||||
adminLevel: 'admin1',
|
||||
synthesized: false,
|
||||
outlineSource: 'nominatim',
|
||||
},
|
||||
}),
|
||||
});
|
||||
const result = await quebec.areas.resolveAreaAction({
|
||||
query: 'Quebec province',
|
||||
});
|
||||
assert.equal(result.ok, true, result.error);
|
||||
});
|
||||
|
||||
test('a place no outline source can trace is refused, not guessed', async () => {
|
||||
const miss = makeAreas({
|
||||
annotationResolver: fakeLadder({ outline: null }),
|
||||
|
||||
@@ -5,10 +5,10 @@ export const MAX_AREA_CANDIDATE_ID_LENGTH = 80;
|
||||
|
||||
/** JSON Schema-compatible grammar for bundled candidate handles. */
|
||||
export const AREA_CANDIDATE_ID_PATTERN =
|
||||
'^ne:(?:country|state|county):[\\p{L}\\p{M}\\p{N}]+(?:-[\\p{L}\\p{M}\\p{N}]+)*$';
|
||||
'^ne:(?:country|state|county|natural):[\\p{L}\\p{M}\\p{N}]+(?:-[\\p{L}\\p{M}\\p{N}]+)*$';
|
||||
|
||||
const AREA_CANDIDATE_ID_RE =
|
||||
/^ne:(?:country|state|county):[\p{L}\p{M}\p{N}]+(?:-[\p{L}\p{M}\p{N}]+)*$/u;
|
||||
/^ne:(?:country|state|county|natural):[\p{L}\p{M}\p{N}]+(?:-[\p{L}\p{M}\p{N}]+)*$/u;
|
||||
|
||||
export function isAreaCandidateId(value) {
|
||||
const candidateId = String(value || '');
|
||||
|
||||
@@ -695,6 +695,11 @@ test('boundaries route to resolve_area; annotate_map is for marks', () => {
|
||||
test('without an operator Overpass the prompt never offers place search', () => {
|
||||
const plain = realtimeInstructions();
|
||||
assert.doesNotMatch(plain, /osm_query/);
|
||||
assert.doesNotMatch(
|
||||
JSON.stringify(realtimeTools()),
|
||||
/osm_query/,
|
||||
'no other tool description points at it either',
|
||||
);
|
||||
assert.equal(
|
||||
realtimeTools().some((tool) => tool.name === 'osm_query'),
|
||||
false,
|
||||
|
||||
@@ -344,7 +344,9 @@ test('deixis prompt lint: one short rule, and every sentinel it names exists in
|
||||
// characters. It was 56,308 after the area/imagery/OSM merge; stating each
|
||||
// policy once (WHERE precedence, AREAS routing, AFTER TOOLS speech) brought
|
||||
// it to 51,646. Voice geometry (resolve_area, find_imagery, osm_query and
|
||||
// the analyst area scopes) raised the ceiling to 55,500. A new tool or rule
|
||||
// the analyst area scopes) raised the ceiling to 55,500; tightening the
|
||||
// AREAS rule and the area tool descriptions left it at 55,233 with an
|
||||
// operator Overpass, a deliberate ~270-character margin. A new tool or rule
|
||||
// has to fit under this ceiling.
|
||||
const size = instructions.length + JSON.stringify(GEV_REALTIME_TOOLS).length;
|
||||
assert.ok(size <= 55_500, `prompt size ${size}`);
|
||||
|
||||
Reference in new issue
Block a user