test: prefer SGS hall entrance anchors

This commit is contained in:
lyf
2026-06-30 00:24:08 +08:00
parent c2800bb277
commit e826c92486
2 changed files with 55 additions and 48 deletions

View File

@@ -4,40 +4,41 @@ Status: DONE
Files changed: Files changed:
- `scripts/check-sgs-hall-poi-adapter.mjs` - `scripts/check-sgs-hall-poi-adapter.mjs`
- `.superpowers/sdd/task-1-report.md`
Commands run: Commands run:
- `node scripts/check-sgs-hall-poi-adapter.mjs` - `node scripts/check-sgs-hall-poi-adapter.mjs`
Output summary: Output summary:
- Smoke script passed. Script now validates: - Smoke script passed. Script now validates:
1. Finding 1: entrance-derived sourceConfidence branch — entrance-backed halls with nodeId must be `backend-sgs-sdk-hall-entrance` 1. **Finding 1 (FIXED)**: Existing hall POI created from space.center is upgraded when a later entrance has BOTH position and nodeId. Main position, sourceConfidence, and sourceObjectName reflect the real entrance. Upgrade logic added to existing POI handler.
2. Finding 2: enforce entrance/reachable-node semantics — entrances without nodeId that match spaces use space-center sourceConfidence, not hall-entrance 2. **Finding 2 (FIXED)**: Removed over-spec assertion requiring globally unique POI names. Now uses ID-based assertions and maps (byId) to avoid hiding duplicates by name. Assertions reference byId instead of byName for structure validation.
3. Finding 3: avoid hiding duplicate names via Map-by-name only — added assertion that result names are unique, indexed by both name and id for duplicate detection 3. **Finding 3 (FIXED)**: Adjusted fixtures so 地球科学展厅 first has a no-coordinate entrance (place 104, fallback to space-center), then a later normalized name entrance (place 109, with nodeId and coordinates) that upgrades to entrance-backed sourceConfidence.
4. Whitelist enforcement prevents unrelated types 4. Whitelist enforcement prevents unrelated types
5. Unmatched exhibition space with center emits fallback POI 5. Unmatched exhibition space with center emits fallback POI
6. Entrance with missing coordinates falls back to matched space.center 6. Entrance with missing coordinates falls back to matched space.center
7. Entrance still recorded even with undefined positionGltf (no coordinates) 7. Entrance still recorded even with undefined positionGltf (no coordinates)
8. **Issue 1 fix**: `backend-sgs-sdk-hall-entrance` sourceConfidence now requires BOTH valid entrance position AND nodeId. Places with coordinates but no nodeId use `backend-sgs-sdk-space-center` instead. 8. POI upgrade mechanism: entrance position, sourceConfidence, and sourceObjectName updated on second entrance if it has BOTH nodeId and coordinates
9. **Issue 2 fixture**: Added place 108 (自然科学展厅【主】出入口1) with coordinates but no nodeId, verifying it matches space center [110, 0, 120] and uses `backend-sgs-sdk-space-center` sourceConfidence, not entrance coordinates. 9. Place 108 (自然科学展厅【主】出入口1) with coordinates but no nodeId merges without changing sourceConfidence
10. **Issue 3 fixture**: Added place 109 (地球 科学 展厅 (主)出入口 1) with spacing and brackets to test name normalization matching — successfully matches space 7 despite formatting differences. 10. Place 109 (地球 科学 展厅 (主)出入口 1) with spacing/brackets and nodeId/coordinates successfully upgrades 地球科学展厅 POI
11. **Issue 4 coverage**: Unmatched exhibition space fallback (space 4: 展览坡道) still emits POI with space-center semantics. 11. ID-based uniqueness validation prevents silent duplicate masking
Commits created: Commits created:
- `test: refine SGS hall POI source semantics` - `test: prefer SGS hall entrance anchors`
Self-review notes: Implementation details:
- The semantic gap fix ensures sourceConfidence correctly requires BOTH position and nodeId for hall-entrance classification - Added upgrade logic in toHallPois: when existing POI is found and later entrance has entrancePosition && place.nodeId, update positionGltf, sourceConfidence, and sourceObjectName
- Fixture 108 validates the space-center fallback for entries with coordinates but no routing identity - sourceObjectName field added to POI object to track the entrance that provided/upgraded the position
- Fixture 109 demonstrates that name normalization handles real-world formatting variations (spacing, brackets, punctuation) - Replaced name-based assertions with ID-based assertions for 7 expected halls
- All fixtures consolidate into existing POIs, maintaining clean POI count (7) while improving semantic coverage - Fixture order: place 104 (no-coord) creates POI with space-center, then place 109 (with nodeId) upgrades it to entrance-backed
- The script is standalone Node coverage for the intended adapter rules because this repository has no configured test runner
- The production adapter implementation is covered by Task 2 in the plan Test results:
- Fixtures now include: - All 7 halls correctly identified and merged
- Space 8 (古生物展厅) with entrance 105 that has nodeId n105 for backend-sgs-sdk-hall-entrance assertion - 地球科学展厅 correctly upgraded from space-center to backend-sgs-sdk-hall-entrance on place 109
- Place 107 (自然科学展厅 出入口) without nodeId to assert space-center semantics when entrance lacks routing identity - Both entrances (104 and 109) recorded in entrances array
- Place 108 (自然科学展厅【主】出入口1) with coordinates but no nodeId verifying space.center position precedence - sourceConfidence and positionGltf reflect entrance 109 after upgrade
- Place 109 (地球 科学 展厅 (主)出入口 1) with spacing/brackets demonstrating name matching robustness - ID uniqueness maintained across all POIs
- Name uniqueness validation prevents silent duplicate masking
Concerns: Concerns:
- None. - None.

View File

@@ -121,6 +121,12 @@ const toHallPois = (spaces, navigablePlaces, fallbackFloorId) => {
const existing = halls.get(poiId) const existing = halls.get(poiId)
if (existing) { if (existing) {
// Finding 1: Upgrade existing POI if later entrance has BOTH position and nodeId
if (entrancePosition && place.nodeId) {
existing.positionGltf = entrancePosition
existing.sourceConfidence = 'backend-sgs-sdk-hall-entrance'
existing.sourceObjectName = place.name
}
existing.entrances.push({ name: place.name, positionGltf: entrancePosition }) existing.entrances.push({ name: place.name, positionGltf: entrancePosition })
return return
} }
@@ -136,6 +142,7 @@ const toHallPois = (spaces, navigablePlaces, fallbackFloorId) => {
primaryCategory: 'exhibition_hall', primaryCategory: 'exhibition_hall',
positionGltf: position, positionGltf: position,
sourceConfidence, sourceConfidence,
sourceObjectName: place.name,
entrances: [{ name: place.name, positionGltf: entrancePosition }] entrances: [{ name: place.name, positionGltf: entrancePosition }]
}) })
}) })
@@ -176,15 +183,15 @@ const spaces = [
const places = [ const places = [
{ id: 101, name: '展厅1宇宙厅 出入口 1', ownerName: '展厅1宇宙厅', floorId: 'B2', position: { x: 11, y: 0, z: 21 }, nodeId: 'n101' }, { id: 101, name: '展厅1宇宙厅 出入口 1', ownerName: '展厅1宇宙厅', floorId: 'B2', position: { x: 11, y: 0, z: 21 }, nodeId: 'n101' },
{ id: 102, name: '巨幕影院 出入口 1', ownerName: '巨幕影院', floorId: 'L1', position: { x: 31, y: 0, z: 41 }, nodeId: 'n102' }, { id: 102, name: '巨幕影院 出入口 1', ownerName: '巨幕影院', floorId: 'L1', position: { x: 31, y: 0, z: 41 }, nodeId: 'n102' },
// Finding 4: entrance with missing coordinates falls back to matched space.center // Finding 3: entrance with missing coordinates falls back to matched space.center (creates POI with space-center sourceConfidence)
{ id: 104, name: '地球科学展厅 出入口', ownerName: '地球科学展厅', floorId: 'L3' }, { id: 104, name: '地球科学展厅 出入口', ownerName: '地球科学展厅', floorId: 'L3' },
// Finding 1: entrance-backed hall (with nodeId) must have backend-sgs-sdk-hall-entrance // Finding 1: entrance-backed hall (with nodeId) must have backend-sgs-sdk-hall-entrance
{ id: 105, name: '古生物展厅 出入口', ownerName: '古生物展厅', floorId: 'L2', position: { x: 151, y: 0, z: 161 }, nodeId: 'n105' }, { id: 105, name: '古生物展厅 出入口', ownerName: '古生物展厅', floorId: 'L2', position: { x: 151, y: 0, z: 161 }, nodeId: 'n105' },
// Finding 2: entrance WITHOUT nodeId that matches a space should use space-center semantics (no position provided) // Finding 2: entrance WITHOUT nodeId that matches a space should use space-center semantics (no position provided)
{ id: 107, name: '自然科学展厅 出入口', ownerName: '自然科学展厅', floorId: 'L2' }, { id: 107, name: '自然科学展厅 出入口', ownerName: '自然科学展厅', floorId: 'L2' },
// Issue 2: place with coordinates but no nodeId should match space center, not entrance position // Finding 2: place with coordinates but no nodeId should match space center, not entrance position
{ id: 108, name: '自然科学展厅【主】出入口1', ownerName: '自然科学展厅', floorId: 'L2', position: { x: 111, y: 0, z: 121 } }, { id: 108, name: '自然科学展厅【主】出入口1', ownerName: '自然科学展厅', floorId: 'L2', position: { x: 111, y: 0, z: 121 } },
// Issue 3: normalization fixture with spacing and brackets — matches space 7 with normalized name // Finding 1: later entrance with BOTH nodeId and position upgrades POI from space-center to entrance-backed
{ id: 109, name: '地球 科学 展厅 (主)出入口 1', ownerName: '地球科学展厅', floorId: 'L3', position: { x: 131, y: 0, z: 141 }, nodeId: 'n109' } { id: 109, name: '地球 科学 展厅 (主)出入口 1', ownerName: '地球科学展厅', floorId: 'L3', position: { x: 131, y: 0, z: 141 }, nodeId: 'n109' }
] ]
@@ -192,19 +199,15 @@ const result = toHallPois(spaces, places, 'B2')
const byName = new Map(result.map((poi) => [poi.name, poi])) const byName = new Map(result.map((poi) => [poi.name, poi]))
const byId = new Map(result.map((poi) => [poi.id, poi])) const byId = new Map(result.map((poi) => [poi.id, poi]))
// Finding 3: Ensure result names are unique (no duplicates hidden by Map-by-name) // All fixtures merge into existing POIs, maintaining 7 unique halls (verified by ID-based map)
const nameList = result.map((poi) => poi.name) assert.equal(result.length, 7, 'result contains 7 POIs')
const uniqueNames = new Set(nameList) assert.equal(byId.size, 7, 'all POI IDs are unique')
assert.equal(nameList.length, uniqueNames.size, `POI names must be unique; found ${nameList.length} POIs but only ${uniqueNames.size} unique names`) assert.deepEqual(byId.get('hall-1').positionGltf, [11, 0, 21])
assert.deepEqual(byId.get('hall-2').positionGltf, [31, 0, 41])
// All fixtures merge into existing POIs, maintaining 7 unique halls assert.deepEqual(byId.get('hall-3').positionGltf, [50, 0, 60])
assert.equal(result.length, 7) assert.deepEqual(byId.get('hall-4').positionGltf, [70, 0, 80])
assert.deepEqual(byName.get('展厅1宇宙厅').positionGltf, [11, 0, 21]) assert.equal(byId.has('hall-5'), false)
assert.deepEqual(byName.get('巨幕影院').positionGltf, [31, 0, 41]) assert.equal(byId.get('hall-3').sourceConfidence, 'backend-sgs-sdk-space-center')
assert.deepEqual(byName.get('博物馆之友活动室').positionGltf, [50, 0, 60])
assert.deepEqual(byName.get('展览坡道').positionGltf, [70, 0, 80])
assert.equal(byName.has('茶水间'), false)
assert.equal(byName.get('博物馆之友活动室').sourceConfidence, 'backend-sgs-sdk-space-center')
// Finding 2: whitelist enforcement prevents unrelated types // Finding 2: whitelist enforcement prevents unrelated types
assert.equal(byName.has('茶水间'), false, 'service type rejected by whitelist') assert.equal(byName.has('茶水间'), false, 'service type rejected by whitelist')
@@ -221,11 +224,14 @@ assert.equal(byName.get('自然科学展厅').entrances.some((e) => e.name === '
// Finding 4: entrance with missing coordinates falls back to matched space.center // Finding 4: entrance with missing coordinates falls back to matched space.center
assert.equal(byName.has('地球科学展厅'), true, 'entrance fallback to space.center') assert.equal(byName.has('地球科学展厅'), true, 'entrance fallback to space.center')
assert.deepEqual(byName.get('地球科学展厅').positionGltf, [130, 0, 140]) // Finding 1: later entrance with BOTH nodeId and position upgrades POI to entrance-backed
assert.equal(byName.get('地球科学展厅').sourceConfidence, 'backend-sgs-sdk-space-center', 'Finding 5: sourceConfidence from space-center not entrance') assert.deepEqual(byName.get('地球科学展厅').positionGltf, [131, 0, 141], 'upgraded to entrance position [131, 0, 141]')
// Finding 4: entrance still recorded even with undefined positionGltf (no coordinates) assert.equal(byName.get('地球科学展厅').sourceConfidence, 'backend-sgs-sdk-hall-entrance', 'Finding 1: upgraded to hall-entrance sourceConfidence')
// Finding 3: both entrances recorded, including the no-coordinate one
assert.equal(byName.get('地球科学展厅').entrances.length, 2, 'both place 104 (no-coord) and 109 (with nodeId) recorded')
assert.equal(byName.get('地球科学展厅').entrances[0].name, '地球科学展厅 出入口') assert.equal(byName.get('地球科学展厅').entrances[0].name, '地球科学展厅 出入口')
assert.equal(byName.get('地球科学展厅').entrances[0].positionGltf, undefined) assert.equal(byName.get('地球科学展厅').entrances[0].positionGltf, undefined, 'first entrance has no coordinates')
assert.equal(byName.get('地球科学展厅').entrances.some((e) => e.name === '地球 科学 展厅 (主)出入口 1'), true, 'normalized entrance name recorded')
// Finding 1: entrance-derived sourceConfidence branch — entrance-backed halls must be backend-sgs-sdk-hall-entrance // Finding 1: entrance-derived sourceConfidence branch — entrance-backed halls must be backend-sgs-sdk-hall-entrance
assert.equal(byName.has('古生物展厅'), true, 'entrance with nodeId creates hall POI') assert.equal(byName.has('古生物展厅'), true, 'entrance with nodeId creates hall POI')
@@ -249,13 +255,13 @@ const place109 = places.find((p) => p.id === 109)
assert.equal(place109.nodeId, 'n109', 'fixture 109 has nodeId') assert.equal(place109.nodeId, 'n109', 'fixture 109 has nodeId')
assert.equal(byName.has('地球科学展厅'), true, 'normalized name matches despite spacing and brackets') assert.equal(byName.has('地球科学展厅'), true, 'normalized name matches despite spacing and brackets')
const earthSciencePoi = byName.get('地球科学展厅') const earthSciencePoi = byName.get('地球科学展厅')
// Place 104 (no nodeId, no position) created the POI with space-center // Place 104 (no nodeId, no position) created the POI with space-center sourceConfidence
// Place 109 (has nodeId and position) merges as another entrance but doesn't change sourceConfidence // Place 109 (has nodeId and position) upgrades the POI to entrance-backed with new position
// sourceConfidence is determined by the first POI creator, not subsequent entrances assert.equal(earthSciencePoi.sourceConfidence, 'backend-sgs-sdk-hall-entrance', 'sourceConfidence upgraded by place 109 with nodeId and position')
assert.equal(earthSciencePoi.sourceConfidence, 'backend-sgs-sdk-space-center', 'sourceConfidence set by first place (104) without nodeId') assert.deepEqual(earthSciencePoi.positionGltf, [131, 0, 141], 'position upgraded to place 109 entrance coordinates')
// Place 109's entrance should be recorded despite no nodeId requirement for sourceConfidence assert.equal(earthSciencePoi.entrances.length, 2, 'both place 104 and 109 entrances recorded')
assert.equal(earthSciencePoi.entrances.length >= 2, true, 'both place 104 and 109 entrances recorded') assert.equal(earthSciencePoi.entrances.some((e) => e.name === '地球科学展厅 出入口'), true, 'place 104 no-coordinate entrance recorded')
assert.equal(earthSciencePoi.entrances.some((e) => e.name === '地球 科学 展厅 (主)出入口 1'), true, 'normalized entrance name recorded') assert.equal(earthSciencePoi.entrances.some((e) => e.name === '地球 科学 展厅 (主)出入口 1'), true, 'place 109 normalized entrance recorded')
// Issue 4: keep unmatched exhibition space fallback coverage // Issue 4: keep unmatched exhibition space fallback coverage
assert.equal(byName.has('自然科学展厅'), true, 'unmatched space-center POI still emitted') assert.equal(byName.has('自然科学展厅'), true, 'unmatched space-center POI still emitted')