fix: harden SGS hall POI matching diagnostics
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -76,10 +76,28 @@ const findMatchedHallSpace = (place, spaces) => {
|
||||
const placeName = normalizedHallName(placeHallName(place))
|
||||
if (!placeName) return undefined
|
||||
|
||||
return spaces.find((space) => {
|
||||
const spaceName = normalizedHallName(space.name)
|
||||
return spaceName && (placeName.includes(spaceName) || spaceName.includes(placeName))
|
||||
})
|
||||
const candidates = spaces
|
||||
.map((space) => {
|
||||
const spaceName = normalizedHallName(space.name)
|
||||
if (!spaceName) return null
|
||||
|
||||
const exact = placeName === spaceName
|
||||
const overlaps = exact || placeName.includes(spaceName) || spaceName.includes(placeName)
|
||||
if (!overlaps) return null
|
||||
|
||||
return {
|
||||
space,
|
||||
exact,
|
||||
specificity: spaceName.length
|
||||
}
|
||||
})
|
||||
.filter(Boolean)
|
||||
.sort((a, b) => {
|
||||
if (a.exact !== b.exact) return a.exact ? -1 : 1
|
||||
return b.specificity - a.specificity
|
||||
})
|
||||
|
||||
return candidates[0]?.space
|
||||
}
|
||||
|
||||
const normalizePositionSource = (source) => {
|
||||
@@ -101,8 +119,9 @@ const normalizeSpaceCenter = (space) => normalizePositionSource(space.center ||
|
||||
const createHallPoiId = (space, place, fallbackIndex) => {
|
||||
if (space?.id) return `hall-${space.id}`
|
||||
const ownerName = normalizedHallName(placeHallName(place))
|
||||
if (ownerName) return `hall-place-${ownerName}`
|
||||
return `hall-place-${place.id || fallbackIndex + 1}`
|
||||
const floorId = normalizedHallName(String(place.floorId || 'unknown-floor'))
|
||||
if (ownerName) return `hall-place-${floorId}-${ownerName}`
|
||||
return `hall-place-${floorId}-${place.id || fallbackIndex + 1}`
|
||||
}
|
||||
|
||||
const toHallPois = (spaces, navigablePlaces, fallbackFloorId) => {
|
||||
@@ -166,42 +185,51 @@ const toHallPois = (spaces, navigablePlaces, fallbackFloorId) => {
|
||||
return Array.from(halls.values())
|
||||
}
|
||||
|
||||
const createHallPoiDiagnostics = (spaces, navigablePlaces, hallPois) => {
|
||||
const hallSpaces = spaces.filter(isEligiblePublicExhibitionSpace)
|
||||
const hallPlaceCount = navigablePlaces.filter(isExhibitionHallNavigablePlace).length
|
||||
const hallPoiIds = new Set(hallPois.map((poi) => poi.id))
|
||||
|
||||
return {
|
||||
skippedSpaceCount: hallSpaces.filter((space) => !hallPoiIds.has(`hall-${space.id}`)).length,
|
||||
skippedPlaceCount: Math.max(hallPlaceCount - hallPois.reduce((count, poi) => count + (poi.entrances?.length || 0), 0), 0)
|
||||
}
|
||||
}
|
||||
|
||||
const spaces = [
|
||||
{ id: 1, name: '展厅1宇宙厅', type: 'exhibition_hall', floorId: 'B2', center: { x: 10, y: 0, z: 20 } },
|
||||
{ id: 2, name: '巨幕影院', type: 'theater', floorId: 'L1', center: { x: 30, y: 0, z: 40 } },
|
||||
{ id: 3, name: '博物馆之友活动室', type: 'education_activity', floorId: 'L4', center: { x: 50, y: 0, z: 60 } },
|
||||
{ id: 4, name: '展览坡道', type: 'ramp', floorId: 'L1.5', center: { x: 70, y: 0, z: 80 } },
|
||||
{ id: 5, name: '茶水间', type: 'service', floorId: 'L1', center: { x: 90, y: 0, z: 100 } },
|
||||
// Finding 3: unmatched exhibition space with center coordinates emits fallback POI
|
||||
{ id: 6, name: '自然科学展厅', type: 'exhibition_hall', floorId: 'L2', center: { x: 110, y: 0, z: 120 } },
|
||||
// Finding 4: space that will match when entrance has no coordinates
|
||||
{ id: 7, name: '地球科学展厅', type: 'exhibition_hall', floorId: 'L3', center: { x: 130, y: 0, z: 140 } },
|
||||
// Finding 1: space matched by entrance with nodeId for sourceConfidence assertion
|
||||
{ id: 8, name: '古生物展厅', type: 'exhibition_hall', floorId: 'L2', center: { x: 150, y: 0, z: 160 } }
|
||||
{ id: 8, name: '古生物展厅', type: 'exhibition_hall', floorId: 'L2', center: { x: 150, y: 0, z: 160 } },
|
||||
{ id: 9, name: '古生物展厅A', type: 'exhibition_hall', floorId: 'L2', center: { x: 170, y: 0, z: 180 } },
|
||||
{ id: 10, name: '星空展厅', type: 'exhibition_hall', floorId: 'L5', center: { x: 190, y: 0, z: 200 } }
|
||||
]
|
||||
|
||||
const places = [
|
||||
{ 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' },
|
||||
// Finding 3: entrance with missing coordinates falls back to matched space.center (creates POI with space-center sourceConfidence)
|
||||
{ id: 104, name: '地球科学展厅 出入口', ownerName: '地球科学展厅', floorId: 'L3' },
|
||||
// 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' },
|
||||
// Finding 2: entrance WITHOUT nodeId that matches a space should use space-center semantics (no position provided)
|
||||
{ id: 107, name: '自然科学展厅 出入口', ownerName: '自然科学展厅', floorId: 'L2' },
|
||||
// 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 } },
|
||||
// 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' },
|
||||
{ id: 110, name: '古生物展厅A 出入口', ownerName: '古生物展厅A', floorId: 'L2', position: { x: 171, y: 0, z: 181 }, nodeId: 'n110' },
|
||||
{ id: 111, name: '共享展厅 出入口', ownerName: '共享展厅', floorId: 'L6', position: { x: 210, y: 0, z: 220 }, nodeId: 'n111' },
|
||||
{ id: 112, name: '共享展厅 出入口', ownerName: '共享展厅', floorId: 'L7', position: { x: 230, y: 0, z: 240 }, nodeId: 'n112' }
|
||||
]
|
||||
|
||||
const result = toHallPois(spaces, places, 'B2')
|
||||
const diagnostics = createHallPoiDiagnostics(spaces, places, result)
|
||||
const byName = new Map(result.map((poi) => [poi.name, poi]))
|
||||
const byId = new Map(result.map((poi) => [poi.id, poi]))
|
||||
|
||||
// All fixtures merge into existing POIs, maintaining 7 unique halls (verified by ID-based map)
|
||||
assert.equal(result.length, 7, 'result contains 7 POIs')
|
||||
assert.equal(byId.size, 7, 'all POI IDs are unique')
|
||||
// All fixtures merge into existing POIs, maintaining 11 unique halls (verified by ID-based map)
|
||||
assert.equal(result.length, 11, 'result contains 11 POIs')
|
||||
assert.equal(byId.size, 11, 'all POI IDs are unique')
|
||||
assert.deepEqual(byId.get('hall-1').positionGltf, [11, 0, 21])
|
||||
assert.deepEqual(byId.get('hall-2').positionGltf, [31, 0, 41])
|
||||
assert.deepEqual(byId.get('hall-3').positionGltf, [50, 0, 60])
|
||||
@@ -263,7 +291,16 @@ assert.equal(earthSciencePoi.entrances.length, 2, 'both place 104 and 109 entran
|
||||
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, 'place 109 normalized entrance recorded')
|
||||
|
||||
// Issue 4: keep unmatched exhibition space fallback coverage
|
||||
assert.equal(byName.has('自然科学展厅'), true, 'unmatched space-center POI still emitted')
|
||||
// Finding 2 re-review: prefer exact match over shorter substring matches
|
||||
assert.equal(byId.get('hall-9').name, '古生物展厅A', 'exact normalized match beats overlapping shorter hall names')
|
||||
assert.deepEqual(byId.get('hall-9').positionGltf, [171, 0, 181], 'exact matched hall uses its own entrance position')
|
||||
|
||||
// Finding 3 re-review: place-only fallback IDs include floor identity to avoid collisions across floors
|
||||
assert.equal(byId.has('hall-place-L6-共享展厅'), true, 'floor-scoped fallback ID created for L6 place-only hall')
|
||||
assert.equal(byId.has('hall-place-L7-共享展厅'), true, 'floor-scoped fallback ID created for L7 place-only hall')
|
||||
assert.notEqual(byId.get('hall-place-L6-共享展厅').id, byId.get('hall-place-L7-共享展厅').id, 'same-named place-only halls on different floors stay distinct')
|
||||
|
||||
// Finding 1 re-review: diagnostics count skipped spaces by missing hall-{space.id}, not by total hall POI count
|
||||
assert.equal(diagnostics.skippedSpaceCount, 0, 'place-only halls do not inflate skipped-space diagnostics when all eligible spaces have hall-{space.id}')
|
||||
assert.equal(diagnostics.skippedPlaceCount, 0, 'all hall places are represented by entrances')
|
||||
|
||||
console.log('SGS hall POI adapter smoke check passed')
|
||||
|
||||
Reference in New Issue
Block a user