Skip to content

feat(3d): infer room placements and stop dead-ending on missing footprints - #795

Draft
smmariquit wants to merge 1 commit into
stagingfrom
feat/3d-room-inference
Draft

feat(3d): infer room placements and stop dead-ending on missing footprints#795
smmariquit wants to merge 1 commit into
stagingfrom
feat/3d-room-inference

Conversation

@smmariquit

Copy link
Copy Markdown
Collaborator

Two goals: every class-hosting building should have a working 3D view, and rooms should land inside the model automatically instead of only by hand.

Part 1 — coverage

I replayed fetchBuildingFootprint's exact selection rule (way["building"] within 35 m, prefer the polygon containing the point, else nearest centroid) against live OSM data for all 41 class-hosting buildings on prod. Read-only queries; nothing was written to any database.

38 of 41 resolve to a footprint. 3 do not — these are the buildings to map in OSM:

Building Rooms with classes Coordinates
CVM-IAS Communal Building 55 14.157707, 121.243408
CFNR Admin Building 21 14.154922, 121.235261
Electrical Engineering Building 10 14.161581, 121.247840

A second, quieter failure mode showed up while measuring: 18 of the 38 "successful" buildings match a polygon that does not contain their own coordinates — the viewer was silently rendering a neighbour. DAAE Building → "UPLB CEM Alumni Gazebo", LHKCB Building → "Francisco O. Santos Hall", PTCF Building → "ICropS Tissue Culture Laboratory", Baker Hall Pool → "Charles Fuller Baker Memorial Hall", and so on. Full list: AFBED, Agricultural Systems Institute, Baker Hall Pool, CAS Annex 1, CDC, CEAT, CEM, DAAE, Forest Biological Sciences, Graduate School, IABE, ICropS, LHKCB, New Math, PHTRC, Physical Sciences, Temporary Common Classrooms, UPRHS.

Both cases now degrade honestly instead of dead-ending or bluffing:

  • No footprint → a plainly-labelled approximate box around the building's own lat/lon, so rooms can still be placed and browsed. The banner says the outline is not the real building and links straight into the OSM editor at that location.
  • Footprint does not contain the point → a banner naming the OSM building we actually drew, with the same edit link.
  • OsmBuildingFootprint.containsPoint carries the signal; the localStorage cache version was bumped to 2 so stale entries cannot fake it.

Part 2 — automatic room placement

src/lib/room-placement.ts — pure, no DB, no fetch. inferRoomSignal / inferBuildingPlacements / inferRoomPlacement return { floor, posX, posY, confidence, reason } or null.

  • Floor is the trustworthy part. First digit of a 3–4 digit room number (PS B-203 → 2, CEM 116 → 1), corroborated or overridden by directions prose ("2nd floor of Physical Sciences Building"). confidence rates the floor only, and reason always states where it came from.
  • x/y is a heuristic and says so. Rooms are spread along the building's long axis (the corridor), split by wing letter, odd and even numbers on opposite sides — the convention the room directions themselves describe ("odd- and even-numbered rooms alternate sides of the hallway"). Every reason ends with "Position is a corridor estimate (…), not a surveyed location."
  • Null rather than a wrong pin. CDC DECIMU, CEAT SHOP RM, CHE 1, ASLH 2, TCC-01 all return null. Basement codes (CAS B05) return null too: room_positions.floor cannot express "below ground", and floor 1 would be a lie.
  • Confidence degrades honestly — high for coded floors, medium for prose-only, low when code and directions disagree (prose wins, conflict named in the reason) or when a floor has to be clamped to the model's height.

Coverage on prod data: 304 of 638 class-hosting rooms can be placed — 286 high confidence, 18 medium, 0 low. The other 334 are named labs, lecture halls and shops with no numeric signal; they still appear in the model at the old seeded-random spot, which is now the fallback rather than the default.

Building3DViewer uses the inference for display, so the improvement shows up without anyone accepting anything.

Editor assist, not an automatic prod mutation

In edit mode the sidebar lists every room the inference can place that has no saved position, each with floor, confidence and reason, and offers "Save all N" or per-room save. Saves go through the existing versioned PATCH /api/admin/rooms/:id, so they get the same optimistic concurrency, editor history and sync-key refresh as a drag.

This PR writes nothing to any database. No bulk INSERT was run anywhere; the maintainer decides what to apply, building by building, from the editor.

room_positions.source

drizzle/0042_room_position_source.sql adds source varchar(16) NOT NULL DEFAULT 'manual' (0041 is taken by #794, so this is 0042). Existing rows baseline to manual, protecting the 40 hand-placed positions. updateRoomPosition refuses an inferred write over a manual row, and returns without bumping the room version so a refused write does not pretend it happened. Registered in scripts/e2e-reset-db.ts; PGlite schema regenerated.

Per AGENTS.md, 0042 must be hand-applied to prod before the release that ships this. The admin position path selects the new column, so the migration has to land first.

Verification

  • bun run test — 471 pass. 40 new unit tests over the real room codes above: floor extraction, the null cases, confidence degradation, pins staying inside the polygon, odd/even sides, determinism, and that skipped (human-placed) codes never appear in a suggestion.
  • bunx vitest run — 43 files, 152 pass.
  • bunx biome check on every touched file — clean.
  • bun run build and bun run build:e2e — clean.
  • Browser (Playwright, production build, real OSM): opened the Physical Sciences Building viewer. The "outline may belong to a neighbouring building (Francisco O. Santos Hall)" banner renders with the correct OSM edit link, floors 1–3 populate, and EAA LH — a code with no digits — lands on floor 2 from its directions text.
  • Not browser-verified: the approximate-box path (none of the 3 unmapped buildings are in the local dataset) and the editor save flow (needs an editor session).

CI

Gated e2e is pending — no run/e2e label on purpose. An E2E run was already in flight on feat/today-screen while I worked, and the shared Supabase pooler cannot take two. The new integration test (integration/services/admin.integration.test.ts, "an inferred room position never overwrites a manual one") has not run yet for the same reason.

Deliberately left out

  • No floor-plan geometry: rooms are markers on a corridor model, not rooms with walls. That needs real floor plans, not more heuristics.
  • No basement support: room_positions.floor is a positive int, so the ~10 CAS B0x rooms return null instead of being pinned to the ground floor.
  • No per-building override table for buildings that break the numbering convention; RoomPlacementInput.buildingName is threaded through so such rules have somewhere to land if one is ever needed.

…rints

Every class-hosting building now renders a 3D view, and rooms land where
their code says instead of at a seeded random point.

- src/lib/room-placement.ts: pure inference. Floor from the room code
  (first digit of a 3-4 digit number) or from directions prose, wing letter
  and odd/even side for the corridor x/y estimate, and null whenever the
  code carries no signal (CDC DECIMU, CHE 1, TCC-01, CAS B05 basements).
  Every result states its basis and its confidence in `reason`.
- building-3d: `placeRooms` uses the inference, keeping the seeded-random
  fallback only for codes that say nothing; `approximateFootprint` draws a
  labelled stand-in box when OSM has no building.
- overpass: report whether the chosen polygon actually contains the
  building's coordinates (cache version bumped), so the viewer can say the
  outline may belong to a neighbour instead of implying precision.
- viewer: provisional banners with an OSM edit link, plus an editor-only
  "save suggested positions" list (per-room or bulk) that writes through the
  existing versioned room PATCH.
- room_positions gains `source`; an inferred write never overwrites a
  manual one and never bumps the room version when it declines.

Measured against prod (read-only): 38 of 41 class buildings resolve to an
OSM footprint, 18 of those match a polygon that does not contain their
coordinates, and the inference can place 304 of 638 class-hosting rooms
(286 high confidence, 18 medium).
@vercel

vercel Bot commented Jul 26, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
saan-ang-room Ready Ready Preview, Comment Jul 26, 2026 11:13am

Request Review

@smmariquit
smmariquit marked this pull request as draft July 26, 2026 11:13
@smmariquit

Copy link
Copy Markdown
Collaborator Author

Opened non-draft by mistake, which auto-triggered the gated E2E stack; both E2E runs were cancelled within a minute and the PR is back to draft. The release on feat/today-screen had the shared E2E pooler at the time — if its run went red in that window, it was contention, not this branch. Mark ready / add run/e2e once that clears.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant