UrbanTwin: Market-Sim
Nuremberg's weekly vegetable market has to leave the Hauptmarkt. UrbanTwin is a digital twin of the Altstadt that simulates a full market Saturday at every candidate site, shows exactly who wins and who loses, and lets Claude explain the trade-offs and propose fixes that the engine re-tests.
What it does
A site decision that would normally take surveys and years of debate becomes something a planner can explore in seconds.
🔎 Finds candidate sites
Scans the inner city for squares, pedestrian areas and vacant ground floors, alongside the three organizer sites: Hauptmarkt, St. Lorenzkirche and the former Kaufhof.
🚫 Filters with reasons
Rejects sites that are too small, too far from transit, or unreachable by delivery vans, and shows each rejection on the map with its reason.
🚶 Simulates a Saturday
Seniors, vendors, commuters, tourists and vans move through the real street network from 05:30 to 15:00 in 30-second ticks.
⚖️ Scores fairly
A weighted MarketScore ranks sites, but four persona scores always sit beside it, so a high total can't hide a group that loses.
🤖 Explains with Claude
Claude writes a council brief, a first-person verdict for each persona, and mitigations such as express kiosks or delivery windows.
🌧️ Asks “what if?”
Switch to a rainy Saturday, a Christmas market that blocks the Hauptmarkt, or different city priorities, and the ranking updates live.
Who it is for
The primary users are city planners and the market organizers who must pick and defend a new site in front of the city council. The tool is built around the people affected by that choice, modeled as four personas drawn from census and timetable data.
👵 Oma Helga, 78
Uses a rollator and takes the U-Bahn.
- Steps without an elevator block her
- Cobblestones and slopes cost extra
- Walking budget about 250 m
🚚 Markus, 45
Arrives in a 3.5 t Sprinter at 05:30 with 40 crates.
- Needs a legal van route to a loading point within 80 m
- Bollards and pedestrian zones block him
- Unloading must finish by 07:00
💼 Lukas, 29
Office worker with a 30-minute lunch break.
- The round trip from the U-Bahn exit must fit his break
- Otherwise he doesn't go
🛍️ Frau Weber, 52
Owns a boutique nearby.
- Gains from extra footfall past her shop
- Loses from blocked windows and produce waste
The persona mix isn't made up: the senior share comes from the Zensus 2022 65+ grid around each site, and commuter waves come from the VGN timetable.
How it works
An eight-step pipeline. Steps 1–6 are deterministic code; Claude only enters at step 7.
↺ Propose → simulate → judge: when Claude proposes a mitigation, the engine re-runs the day with it applied, and the personas judge the new result.
Inside the simulation
Routing. Each agent takes the cheapest path through the real street graph, using a cost function for its persona:
Cobblestones count ×2.5 for seniors, and steps without an elevator are impassable for them. Rain adds ×1.3 on open streets. Vans may only use vehicle-legal roads and are stopped by bollards.
Why rule-based movement? Research shows LLM-generated mobility can look plausible but fail to match real movement statistics. So agents move by seeded, testable rules. The same seed always gives the same result, and one site × scenario runs in under 0.25 s.
Where Claude fits. One Claude call per persona, not per agent, turns that persona's computed metrics into a verdict. A forced tool schema shapes the brief, and a number check rejects any figure that isn't in the engine output.
Scoring
These are the default weights; planners can adjust them or pick a preset (Accessibility first, Trader fairness first, Local business first). Fairness = 100 × (1 − Gini of visitors per stall). Guard: any site where a persona scores below 40 is flagged “fails a stakeholder group”, whatever its total.
Example output
Persona scores from npm run sim:report (sunny Saturday, seed 42). Each site wins for someone and costs someone else. At Lorenzkirche the nearest legal van stop is 94 m from the stalls, beyond the 80 m limit, so vendors score 5.
| Persona | Hauptmarkt | Lorenzkirche | Kaufhof |
|---|---|---|---|
| 👵 Senior | |||
| 🚚 Vendor | |||
| 💼 Commuter | |||
| 🛍️ Retailer |
Values depend on the data and model version. Run the report yourself for current numbers.
Architecture
Everything except the Claude call runs in the browser, so the demo works offline using cached briefs.
Geodata prep (Python)
Converts OSM, LoD2, DGM1, Zensus and GTFS into compact JSON: candidates, street graph, population, arrivals.
Discovery & ranking
Filters candidates with reasons and ranks them by indicator score.
Pedestrian & delivery twin
Seeded Monte Carlo agents, Dijkstra routing and 30 s ticks, running in a Web Worker.
Scoring
MarketScore, the fairness Gini and the stakeholder guard.
Map & cockpit
React with deck.gl on MapLibre: 3D buildings, trips, heatmap, ranking sliders, what-if bar, persona cards.
Claude brief server
Calls claude-sonnet-5-5 with the API key kept server-side, checks numbers against the engine output, and caches briefs.
Real, open data
| Dataset | Used for |
|---|---|
| OpenStreetMap Altstadt extract | Candidates, walk and drive graph, bollards, steps, U-Bahn entrances, elevators, shops |
| LoD2 CityGML (LDBV) | Building footprints and heights, free area, 3D view |
| DGM1 1 m terrain | Slope per street segment |
| DOP20 aerial photo + ALKIS parcels | Base map and checking site outlines |
| Zensus 2022 100 m grid | Population, share aged 65+, average age |
| VGN GTFS timetable | Stop frequency and U-Bahn arrival waves |
Try it
npm run dev # the app (Vite)
npm run server # Claude brief server (needs ANTHROPIC_API_KEY in .env)
npm test # unit and integration tests
npm run sim:report # 3 benchmark sites × 3 scenarios as a table
Requires Node 22.18+. The full plan is in IMPLEMENTATION_PLAN.md.