CLAUDE CODE HANDOFF: Saiakoda Location Intelligence App
What to build
A single-page Vite + React + Leaflet app that combines an interactive map of Tallinn with a full location intelligence dashboard for Saiakoda bakery-café. The map and panels are fully interconnected — clicking a neighborhood on the map highlights it in the panel, toggling a data layer updates both, adjusting scoring weights recolors the map in real-time.
Tech stack
- Vite + React (TypeScript preferred)
- Leaflet via
react-leaflet(NOT CDN — install as npm package) - Tailwind CSS for styling
- No backend needed — all data is hardcoded JSON
- Tile provider:
https://a.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}@2x.png(works without referer restrictions, unlike OSM tiles)
App layout
Split screen: map (left, ~60%) + panel (right, ~40%). Full viewport height. Header bar with layer toggles spanning full width. The panel has tabs that switch between different views.
DATA: Competitors & Signal Locations (26 points)
[
{"name": "RØST Bakery", "lat": 59.4383, "lng": 24.7548, "type": "bakery", "hood": "Rotermann", "rev": 450000, "tier": "A", "note": "Scandinavian sourdough, always queuing, #29 TripAdvisor"},
{"name": "Karjase Sai / Barbarea", "lat": 59.452, "lng": 24.721, "type": "bakery", "hood": "Kopli", "rev": 380000, "tier": "A", "note": "Michelin-noted, dual bakery/bistro concept"},
{"name": "Saiakoda (Härjapea 17)", "lat": 59.4377051, "lng": 24.7162575, "type": "current", "hood": "Pelgulinn", "rev": 74000, "tier": "—", "note": "CURRENT LOCATION. Saturday-only, pre-order window, zero foot traffic"},
{"name": "Maison François", "lat": 59.437, "lng": 24.744, "type": "bakery", "hood": "Old Town", "rev": 180000, "tier": "A", "note": "François partnership w/ Burman Hotel, Dunkri 8"},
{"name": "La Boulangerie", "lat": 59.4435, "lng": 24.762, "type": "bakery", "hood": "Sadama", "rev": 220000, "tier": "B", "note": "French-style, Poordi 3, full café"},
{"name": "SUMI by Põhjala", "lat": 59.4495, "lng": 24.7185, "type": "bakery", "hood": "Krulli", "rev": 200000, "tier": "A", "note": "Opened Jan 2025, French/American pastries + Japanese grill"},
{"name": "Bekker Pagariäri", "lat": 59.4465, "lng": 24.725, "type": "bakery", "hood": "Kopli", "rev": 150000, "tier": "B", "note": "Mid-premium neighborhood bakery"},
{"name": "Pagar Võtaks", "lat": 59.435, "lng": 24.753, "type": "bakery", "hood": "Kesklinn", "rev": 80000, "tier": "C", "note": "Only all-organic bakery, counter inside Biomarket"},
{"name": "The Brick Coffee", "lat": 59.4402, "lng": 24.7295, "type": "coffee", "hood": "Telliskivi", "rev": 180000, "note": "ECT listed, moved across from Apollo/Literata"},
{"name": "Café KIOSK No1", "lat": 59.44, "lng": 24.728, "type": "coffee", "hood": "Telliskivi", "rev": 120000, "note": "2025 ECT Winner"},
{"name": "KOKOMO Roasters", "lat": 59.449, "lng": 24.72, "type": "coffee", "hood": "Põhjala", "rev": 160000, "note": "ECT listed, roastery + café"},
{"name": "Gourmet Coffee", "lat": 59.4385, "lng": 24.785, "type": "coffee", "hood": "Kadriorg", "rev": 130000, "note": "ECT listed, Köleri 8"},
{"name": "NOP Café", "lat": 59.438, "lng": 24.783, "type": "organic", "hood": "Kadriorg", "rev": 250000, "note": "18+ years organic café, THE benchmark"},
{"name": "Kalve (Kadriorg)", "lat": 59.4378, "lng": 24.7845, "type": "coffee", "hood": "Kadriorg", "rev": 140000, "note": "Latvian roaster, opened 2025"},
{"name": "Kalve (Rotermann)", "lat": 59.4388, "lng": 24.756, "type": "coffee", "hood": "Rotermann", "rev": 140000, "note": "Latvian roaster, opened 2025"},
{"name": "Paper Mill Coffee", "lat": 59.434, "lng": 24.762, "type": "coffee", "hood": "Kesklinn", "rev": 110000, "note": "Roastery, Pärnu mnt area"},
{"name": "Biomarket (BJT)", "lat": 59.4408, "lng": 24.734, "type": "organic", "hood": "Telliskivi", "rev": 500000, "note": "Inside Balti Jaama Turg, major organic hub"},
{"name": "Biomarket (Solaris)", "lat": 59.4328, "lng": 24.7445, "type": "organic", "hood": "Kesklinn", "rev": 400000, "note": "Solaris Centre location"},
{"name": "Kringel", "lat": 59.4295, "lng": 24.737, "type": "organic", "hood": "Uus Maailm", "rev": 60000, "note": "100% plant-based, only specialty food in Uus Maailm"},
{"name": "CrossFit KeKo", "lat": 59.442, "lng": 24.731, "type": "fitness", "hood": "Kalamaja", "note": "Soo tn 4, exactly the target demo"},
{"name": "Inbodhi Yoga", "lat": 59.4375, "lng": 24.778, "type": "fitness", "hood": "Kadriorg", "note": "Expat-friendly yoga"},
{"name": "Reach Studio", "lat": 59.4355, "lng": 24.75, "type": "fitness", "hood": "Kesklinn", "note": "Reformer Pilates, Rotermann area"},
{"name": "Roseni Pilates", "lat": 59.4385, "lng": 24.7555, "type": "fitness", "hood": "Rotermann", "note": "Pilates studio"}
]
Layer types and their visual treatment:
bakery→ red circle markers, these are direct competitioncoffee→ blue circle markers, audience clustering signalorganic→ green circle markers, health-conscious consumer hubsfitness→ purple circle markers, demographic behavioral proxycurrent→ orange house icon, Saiakoda's current (bad) location
Revenue data source: EMTA quarterly tax filings + Inforegister annual reports.
DATA: Neighborhoods (10 candidates)
[
{"id": "pelgulinn", "name": "Pelgulinn", "lat": 59.4377, "lng": 24.7163, "pop500": 6800, "income": 1900, "rent": 11, "traffic": 35, "specialty": 1, "demo": 72, "growth": "emerging", "transit": 65, "note": "15,950 residents, massively underserved, Saiakoda home turf"},
{"id": "uusmaailm", "name": "Uus Maailm", "lat": 59.4295, "lng": 24.737, "pop500": 5500, "income": 2100, "rent": 13, "traffic": 30, "specialty": 1, "demo": 82, "growth": "emerging", "transit": 70, "note": "Bohemian, sustainability-minded, NO artisan bakery exists"},
{"id": "nomme", "name": "Nõmme", "lat": 59.392, "lng": 24.692, "pop500": 5200, "income": 2500, "rent": 8, "traffic": 40, "specialty": 0, "demo": 60, "growth": "stable", "transit": 45, "note": "Forest suburb, organic families, zero specialty bakeries"},
{"id": "kalamaja", "name": "Kalamaja Core", "lat": 59.4445, "lng": 24.735, "pop500": 5100, "income": 2200, "rent": 14, "traffic": 65, "specialty": 3, "demo": 88, "growth": "mature", "transit": 75, "note": "Best audience fit, young professionals + families"},
{"id": "kadriorg", "name": "Kadriorg", "lat": 59.438, "lng": 24.784, "pop500": 4500, "income": 3200, "rent": 13, "traffic": 55, "specialty": 4, "demo": 85, "growth": "stable_premium", "transit": 70, "note": "NOP proves organic demand, affluent families"},
{"id": "kopli", "name": "Kopli/Põhjala", "lat": 59.45, "lng": 24.72, "pop500": 3200, "income": 1700, "rent": 9, "traffic": 30, "specialty": 4, "demo": 65, "growth": "emerging", "transit": 55, "note": "Karjase Sai territory, getting crowded"},
{"id": "kesklinn", "name": "Kesklinn", "lat": 59.433, "lng": 24.745, "pop500": 3800, "income": 2600, "rent": 22, "traffic": 85, "specialty": 3, "demo": 60, "growth": "stable", "transit": 95, "note": "High traffic but generic, not audience-aligned"},
{"id": "telliskivi", "name": "Telliskivi", "lat": 59.4405, "lng": 24.73, "pop500": 4200, "income": 2400, "rent": 18.5, "traffic": 95, "specialty": 8, "demo": 90, "growth": "mature", "transit": 95, "note": "Peak saturation — 8 specialty spots in 300m"},
{"id": "noblessner", "name": "Noblessner", "lat": 59.452, "lng": 24.736, "pop500": 1800, "income": 3000, "rent": 18, "traffic": 45, "specialty": 2, "demo": 70, "growth": "rapid", "transit": 50, "note": "2-Michelin-star 180°, 300 new apts incoming"},
{"id": "rotermann", "name": "Rotermann", "lat": 59.4385, "lng": 24.7555, "pop500": 2800, "income": 2800, "rent": 20, "traffic": 90, "specialty": 5, "demo": 75, "growth": "mature", "transit": 90, "note": "RØST already here, office + tourist mix"}
]
Data sources: Statistics Estonia 100m population grid, Colliers Baltic Market Reports 2025-2026, KV.ee market data, European Coffee Trip listings.
DATA: Available Property Listings (8 real listings, March 2026)
[
{"name": "Vana-Kalamaja 21", "hoodId": "kalamaja", "lat": 59.4425, "lng": 24.7385, "size": 102.6, "rent": "~€1,100", "sqm": "~€11", "kitchen": true, "fit": 5, "note": "Former Mooni Pagar. Full exhaust + 50A power. Terrace on pedestrian promenade. BEST PICK.", "url": "https://www.kv.ee/uurile-anda-suurte-akendega-avar-aripind-vanakalam-3642145.html"},
{"name": "Volta Galerii (Uus-Volta 7)", "hoodId": "kalamaja", "lat": 59.4448, "lng": 24.7275, "size": 183.2, "rent": "Ask Endover", "sqm": "~€14-18", "kitchen": true, "fit": 4, "note": "1912 factory, 5.2m ceilings, limestone walls. 2 months free. Explicitly marketed for bakeries.", "url": "https://soov.ee/25744787"},
{"name": "Volta HUB", "hoodId": "kalamaja", "lat": 59.4455, "lng": 24.729, "size": 152, "rent": "1st mo free", "sqm": "~€12-16", "kitchen": true, "fit": 4, "note": "Kitchen cube, 5m ceilings, sea views. Wise HQ moving nearby.", "url": "https://soov.ee/25638741"},
{"name": "Kopli 6", "hoodId": "kalamaja", "lat": 59.4418, "lng": 24.7305, "size": 186.5, "rent": "€1,800", "sqm": "€9.65", "kitchen": true, "fit": 3, "note": "2 levels, display windows. Best €/m² in Kalamaja.", "url": "https://uusmaa.ee/pakkumine/10233"},
{"name": "Manufaktuuri 7", "hoodId": "pelgulinn", "lat": 59.4395, "lng": 24.7205, "size": 55.3, "rent": "€553", "sqm": "€10", "kitchen": false, "fit": 3, "note": "New 2024 build, 78m² terrace, 500+ apts nearby. Cheapest entry point.", "url": "https://www.city24.ee/real-estate/commercials-for-rent/tallinn-pohja-tallinna-linnaosa-manufaktuuri-tn/4276142"},
{"name": "Kalaranna 8", "hoodId": "kalamaja", "lat": 59.4485, "lng": 24.738, "size": 131.9, "rent": "€1,847", "sqm": "€14", "kitchen": false, "fit": 3, "note": "Premium waterfront. Neighbors: Chin-Chin, Oda, ROO Café.", "url": "https://uusmaa.ee/pakkumine/18074"},
{"name": "Koidu 101, Uus Maailm", "hoodId": "uusmaailm", "lat": 59.4302, "lng": 24.7385, "size": 63.7, "rent": "2 mo free", "sqm": "Unknown", "kitchen": false, "fit": 2, "note": "Corner unit in Uus Maailm. BUT: 2.6m ceilings (tight!). Needs full buildout.", "url": "https://www.kv.ee/voimalik-tutvuda-uuripinnaga-ka-videovahendusel-20-3219824.html"},
{"name": "Noblessner Main Square", "hoodId": "noblessner", "lat": 59.4528, "lng": 24.7352, "size": null, "rent": "From €12/m²", "sqm": "€12+", "kitchen": true, "fit": 3, "note": "Historic limestone, high ceilings, kitchen readiness. Near 180°, Põhjala.", "url": "https://soov.ee/25615699"}
]
SCORING MODEL
7 weighted factors, all adjustable via sliders (0-50 range each). Weights are normalized to sum to 1.0 before scoring.
Default weights:
population: 15 // Catchment population within 500m
demo: 20 // Target demographic alignment (0-100 scale, already normalized)
gap: 25 // Competition gap (FEWER competitors = HIGHER score, inverted)
rent: 15 // Rent efficiency (LOWER rent = HIGHER score, inverted)
traffic: 10 // Foot traffic index
transit: 5 // Public transport accessibility
growth: 10 // Neighborhood growth trajectory
Growth trajectory mapping:
emerging: 90, rapid: 85, mature: 50, stable_premium: 60, stable: 40
Normalization: min-max to 0-100 for each factor. demo is already 0-100. specialty and rent are inverted (lower = better).
Composite score = weighted sum of all normalized factors.
Saturation ratio = pop500 / max(specialty, 1). Thresholds:
-
4000: 🟢 UNDERSERVED
- 2500-4000: 🟡 OPPORTUNITY
- 1500-2500: 🟠 BALANCED
- <1500: 🔴 SATURATED
Rent feasibility = (rent_per_sqm × 60m² × 12) / 180000 × 100. Must be <10-15%.
PANEL TABS
Tab 1: 📊 Neighborhood Scoring
- Weight sliders at top (7 sliders, 0-50 each, reset button)
- Ranked list of neighborhoods with score badges
- Click to expand → shows full factor breakdown with score bars
- Shows saturation ratio badge and rent feasibility %
- Clicking a neighborhood also selects it on the map (flies to location, highlights catchment)
Tab 2: 🗺️ Data Layers
- Each layer (bakery, coffee, organic, fitness, current, listings, catchments) shown as a card
- Card shows: layer description, source info, list of all locations in that layer with revenue
- Toggle button on each card syncs with the header layer toggles
Tab 3: 🏠 Available Spaces
- 8 listing cards with rent, size, kitchen-ready badge, fit stars (1-5), notes, link to listing
- Top pick (Vana-Kalamaja 21) highlighted with accent border
- Clicking a listing selects its neighborhood on the map
Tab 4: 👥 Target Audience
- 4 psychographic segments: Gut-Health Recoverer (35%, red), Ancestral Wisdom Keeper (25%, orange), Health Optimizer (20%, blue), Conscious Parent (20%, green)
- Each with age range, gender skew, description
- Market sizing: 35-55K potential buyers, 2-5K regulars, €252K projected annual rev
- Behavioral signals list (where they go in Tallinn)
Tab 5: 📋 Site Visit Checklist
This is the micro-level decision framework for the HUMAN to use on foot. Display as an interactive checklist:
The 10-Point Site Visit Scorecard (rate each 1-10, weighted): | Category | Weight | |----------|--------| | Pedestrian traffic volume at key hours | 20% | | Pedestrian traffic quality (demographic fit) | 10% | | Visibility and sightlines (100m test) | 12% | | Accessibility (entrance level, parking, transit) | 10% | | Co-tenancy and neighboring businesses | 8% | | Competitive landscape | 8% | | Delivery and operational access | 7% | | Lease terms and cost | 10% | | Physical space (layout, oven placement potential) | 8% | | Sun exposure and outdoor seating potential | 7% |
Each item should be a slider (1-10) that the user can score per listing. Calculate weighted total. Minimum threshold: no item below 4, total above 6.5.
Fatal Flaw Checklist (binary yes/no — any YES = disqualify):
- Wrong side of a major road with no pedestrian crossing nearby?
- Steps UP required to enter? (not street-level)
- Past the natural pedestrian turning point? (energy of street dies before reaching you)
- On a dead-end street or pedestrian dead zone?
- Entrance faces away from main pedestrian flow?
- In a wind tunnel between tall buildings?
- Both neighbors are blank walls / shuttered / dead?
- Area is dead during bakery operating hours (7am-3pm)?
- No visibility from 50+ meters on primary approach?
- No delivery vehicle access within 50 feet?
Five-Visit Protocol — display as a progress tracker:
- Visit 1: Weekday morning (Tue-Thu, 7:00-9:30am) — count pedestrians per 15min
- Visit 2: Weekday lunch (Tue-Thu, 11:30am-1:30pm)
- Visit 3: Weekday afternoon (Wed-Fri, 4:00-7:00pm)
- Visit 4: Saturday mid-morning (9:00am-12:00pm)
- Visit 5: Bad weather or early darkness visit
Tab 6: 🧠 Methodology
- 6 frameworks displayed as cards:
- Huff Gravity Model — attractiveness ÷ distance decay
- Trade Area Analysis — 500m primary (5 min walk), 800m secondary (10 min)
- Saturation Ratio — pop ÷ competitors, thresholds
- Rent-to-Revenue Rule — rent must be <10-15% of revenue
- Starbucks Analog — street frontage, commute-side, visibility, signage
- Micro-Level Decision Tree — the human observation framework
- Decision tree displayed visually:
- <2 specialty competitors? → proceed / differentiation needed
- Catchment >3,000 within 500m? → viable / need destination appeal
- Demo score >75? → aligned / heavier marketing needed
- Rent <10% of €180K? → economically viable / negotiate
- Kitchen infrastructure? → move fast / add €20-50K buildout cost
MAP FEATURES
Layers (all toggleable from header bar):
- Competitor markers: colored circle markers by type, tooltip with name + revenue
- Saiakoda current: orange house icon at 59.4377051, 24.7162575
- Available spaces: pink numbered markers (number = fit score 1-5), tooltip with details
- Catchment circles: 500m (solid) and 800m (dashed) around top 5 scored neighborhoods
- Score labels: floating badges on each neighborhood showing rank + score, colored by score range
Interactions:
- Click neighborhood label on map → selects in panel, panel scrolls to it
- Click neighborhood in panel → map flies to it, highlights its catchment
- Hover any marker → tooltip with details
- Click listing in Spaces tab → map flies to listing location
- Adjust weight sliders → scores recalculate → map labels update colors/rankings instantly
Map defaults:
- Center: [59.438, 24.74]
- Zoom: 13
- Attribution: © OpenStreetMap contributors, © CARTO
BONUS: Live Estonian Data Pull (optional, network-dependent)
If you want to add a "Refresh Data" feature that pulls live from Estonian APIs:
-
Maa-amet Geocoding:
https://inaadress.maaamet.ee/inaadress/gazetteer?address=ADDRESS&results=1&output=jsonReturnsviitepunkt_b(lat) andviitepunkt_l(lng) -
EMTA Revenue Data: Download CSV from
https://ncfailid.emta.ee/eng_tasutud_maksud.csv(~150MB) Columns include registry code, company name, EMTAK code, turnover, employee count -
Business Registry: Bulk download from
https://avaandmed.ariregister.rik.ee/en/downloading-open-dataContains all companies with EMTAK codes and addresses -
OpenRouteService Isochrones:
POST https://api.openrouteservice.org/v2/isochrones/foot-walkingFree key from openrouteservice.org, 500 isochrones/day Body:{"locations": [[lng, lat]], "range": [300, 600], "range_type": "time"} -
BestTime.app: Foot traffic forecasts for Tallinn venues, API access with paid plan
Context
This app is for helping a Belgian artisan baker (François Arnould, runs Saiakoda / François Boulangerie) find the optimal location for a new bakery-café in Tallinn, Estonia. His current bakery is at Härjapea 17 in residential Pelgulinn — Saturday-only, pre-order through a window, zero walk-in traffic. He needs a real café location.
The target audience is NOT generic foodies — it's health-conscious consumers who specifically seek stone-milled, long-fermented, wood-fired sourdough bread for gut health, ancestral health, or clean-ingredient parenting reasons. They cluster around specialty coffee shops, organic food stores, CrossFit/yoga studios, and neighborhoods like Kalamaja, Kadriorg, and Uus Maailm.
The app should feel like a professional location intelligence tool, not a pretty map. Every recommendation must be traceable to data, and every data layer should be toggleable so a human can override the model's assumptions.