Find the perfect location for your business.
Three AI agents analyze foot traffic, demographics, competition, rent, and walkability to rank the best locations.
Opening a small business means picking a location based on gut feeling, expensive consultants, or driving around town. Commercial real estate data is fragmented, paywalled, and not designed for someone opening their first shop.
Zonar gives any entrepreneur the same site intelligence that big chains use internally:
- Zone-level scoring across every Census block group in a city
- Revenue and rent estimates calibrated to your business type
- Demographic fit analysis — income, age distribution, population density
- Competition mapping — how saturated is this area for your category?
- AI-generated insights explaining why a zone scores well or poorly
- Describe your business — "bubble tea shop targeting college students" or "pizza restaurant with a $200K budget"
- AI parses your concept — Gemini extracts business type, target demographics, search terms, and priorities
- Every neighborhood gets scored — 175 Census block groups scored in parallel using cached Census + OSM data
- Explore the heatmap — color-coded zones from red (poor fit) to green (strong fit), with a ranked sidebar
- Drill into any zone — see financials, demographics, competition count, and an AI narrative
- Adjust priorities — drag weight sliders to re-rank zones instantly (no API call, client-side rescoring)
- Mapbox fill-layer heatmap colored by PlotScore (0–100)
- 3D building extrusion toggle
- Click any zone for detailed breakdown
- Ranked sidebar with revenue estimates and population
- Weight sliders for real-time rescoring (foot traffic, demographics, competition, rent, walkability)
| Agent | What it does | AI? |
|---|---|---|
| Query Parser | Natural language → structured business profile | Gemini |
| Zone Scorer | Scores all 175 block groups using financial + location heuristics | No |
| Financial Modeler | Revenue, rent, breakeven, and profit estimates per zone | No |
| Location Scorer | PlotScore from 5 weighted subscores | No |
| Insight Explainer | 3–4 sentence narrative per zone with specific numbers | Gemini |
| Web Researcher | Market intel via Google Search grounding (rent ranges, closures, developments) | Gemini + Search |
PlotScore (0–100) = weighted sum of 5 subscores:
| Subscore | What it measures |
|---|---|
| Foot Traffic | POI density, population, transit access |
| Demographic Fit | Age bracket alignment, income match |
| Competition Gap | Fewer competitors = higher score |
| Rent Efficiency | Revenue-to-rent ratio, breakeven speed |
| Walkability | Inferred from nearby POI count |
Weights shift based on user priorities (e.g. selecting "Low Competition" boosts the competition_gap weight).
Per zone, calibrated by business type:
| Metric | Source |
|---|---|
| Monthly Revenue | Foot traffic × capture rate × avg ticket |
| Monthly Rent | Census median rent × sqft estimate |
| Monthly Profit | Revenue − rent − COGS |
| Startup Cost | Industry benchmark × location factor |
| Breakeven | Startup cost / monthly profit |
| Confidence | Data completeness score (0–100%) |
Benchmarks cover 18 business types (coffee shop, bubble tea, restaurant, gym, salon, retail, etc.).
| Data | Source | How |
|---|---|---|
| Demographics | U.S. Census ACS 5-Year | Bulk fetch, cached in S3 |
| POIs | OpenStreetMap Overpass | Citywide query, cached in S3 |
| Block group boundaries | Census TIGER/Line shapefiles | Pre-processed GeoJSON |
| Market intel | Google Search via Gemini | On-demand per zone click |
- Runtime: Python 3.11 on AWS Lambda (FastAPI + Mangum)
- AI: Google Gemini 2.5 Flash Lite (query parsing, insights, web research with search grounding)
- Data cache: S3 for bulk Census + citywide OSM (in-memory after cold start)
- Auth: JWT + bcrypt, DynamoDB user table
- Framework: Next.js 16 + React 19 + TypeScript
- Map: Mapbox GL JS (fill layers, 3D buildings)
- Styling: Tailwind CSS v4
- Animations: Framer Motion
| Component | Service |
|---|---|
| API | AWS Lambda Function URL (120s timeout) |
| Cache | S3 — bulk Census demographics + citywide OSM POIs |
| Auth | DynamoDB user table + JWT tokens |
| Frontend | Next.js (Vercel or static) |
Zonar/
├── backend/
│ ├── main.py # FastAPI app — /api/zones, /api/insight, /api/web-research
│ ├── agents/
│ │ ├── query_parser.py # Gemini: natural language → structured query
│ │ ├── financial_modeler.py # Revenue/rent/breakeven heuristics
│ │ ├── location_scorer.py # PlotScore computation
│ │ ├── insight_explainer.py # Gemini: per-zone AI narrative
│ │ └── web_researcher.py # Gemini + Search: market intel
│ └── services/
│ ├── census.py # Census ACS bulk S3 loader
│ ├── places.py # OSM citywide S3 loader
│ └── zone_scorer.py # Score all block groups (calls financial_modeler + location_scorer)
│
├── frontend/
│ ├── src/app/
│ │ ├── page.tsx # Single-page app — onboarding, map, zone heatmap
│ │ ├── layout.tsx # Metadata + fonts
│ │ └── globals.css # Theme + component styles
│ └── public/
│ └── providence_block_groups.geojson # Pre-processed Census block group boundaries
│
└── scripts/
├── prepare_geojson.py # One-time: TIGER shapefile → GeoJSON
├── refresh_census_cache.py # Periodic: bulk Census ACS → S3
└── refresh_osm_cache.py # Periodic: citywide Overpass → S3
- Node.js 18+
- Python 3.10+
- AWS account with S3 + DynamoDB
- Google AI API key (Gemini)
- Mapbox access token
cd frontend
npm installCreate .env.local:
NEXT_PUBLIC_MAPBOX_TOKEN=your_mapbox_token
NEXT_PUBLIC_API_URL=http://localhost:8000
npm run devcd backend
pip install -r requirements.txtCreate .env:
GEMINI_API_KEY=your_gemini_key
CENSUS_API_KEY=your_census_key
OSM_S3_BUCKET=your_bucket
OSM_S3_KEY=osm/providence.json
CENSUS_S3_BUCKET=your_bucket
CENSUS_S3_KEY=cache/bulk_census_44_007.json
DDB_DEMOGRAPHICS_TABLE=plotline-demographics
DDB_USERS_TABLE=plotline-users
JWT_SECRET=your_secret
uvicorn main:app --reload --port 8000# Generate block group GeoJSON
pip install geopandas shapely requests
python scripts/prepare_geojson.py
# Populate S3 caches
CENSUS_API_KEY=xxx CENSUS_S3_BUCKET=xxx python scripts/refresh_census_cache.py
OSM_S3_BUCKET=xxx python scripts/refresh_osm_cache.pyCache data, score per request. Census and OSM data change slowly. Cache them once in S3, score dynamically based on business type and priorities.
No black boxes. Every PlotScore decomposes into 5 subscores. Every financial estimate shows its inputs. Weight sliders let users express what matters to them.
AI where it helps, heuristics where it doesn't. Gemini parses queries and writes narratives. Scoring and financial modeling are deterministic — same inputs, same outputs.
MIT
