Track 2 · Fraunhofer IIS Challenge · Claude Impact Lab #2, Nürnberg

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.

Rule zero: every number on screen is computed by the engine from real Nuremberg data. Claude explains and proposes. It never invents a number, and the server rejects any brief that does.

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.

Senior

👵 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
Vendor

🚚 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
Commuter

💼 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
Retailer

🛍️ 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.

DiscoverSquares, pedestrian areas and vacant ground floors from OSM and LoD2
FilterSpace, transit and delivery access, each with a stated reason
RankIndicator score, then a shortlist plus the benchmarks
SimulateSeeded persona agents in a Web Worker
Visualize3D map with agent trails, heatmap and bottlenecks
ScoreMarketScore and per-persona scores
RecommendClaude writes the brief, verdicts and mitigations
What-ifRain, Christmas market, or new weights

↺ 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:

cost = length × M_surface × M_slope × M_rain

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

MarketScore = 0.30·Accessibility + 0.25·Footfall + 0.20·Fairness + 0.15·LocalBusiness + 0.10·Walkability

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.

PersonaHauptmarktLorenzkircheKaufhof
👵 Senior
100
100
70.4
🚚 Vendor
92.6
5.0
61.0
💼 Commuter
71.4
91.0
73.8
🛍️ Retailer
59.2
54.1
64.2

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.

prep/

Geodata prep (Python)

Converts OSM, LoD2, DGM1, Zensus and GTFS into compact JSON: candidates, street graph, population, arrivals.

src/rank/

Discovery & ranking

Filters candidates with reasons and ranks them by indicator score.

src/sim/

Pedestrian & delivery twin

Seeded Monte Carlo agents, Dijkstra routing and 30 s ticks, running in a Web Worker.

src/score/

Scoring

MarketScore, the fairness Gini and the stakeholder guard.

src/ui/

Map & cockpit

React with deck.gl on MapLibre: 3D buildings, trips, heatmap, ranking sliders, what-if bar, persona cards.

server/

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

DatasetUsed for
OpenStreetMap Altstadt extractCandidates, 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 terrainSlope per street segment
DOP20 aerial photo + ALKIS parcelsBase map and checking site outlines
Zensus 2022 100 m gridPopulation, share aged 65+, average age
VGN GTFS timetableStop frequency and U-Bahn arrival waves

Try it

npm install
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.