CityStrata helps match displaced families and communities to suitable urban areas in Eilat, Israel — combining multi-source urban data, spatial analytics, machine learning, and LLM-assisted recommendations into an interactive decision-support platform.
Academic Final Software Engineering Project · Research & educational use
Overview · Highlights · Architecture · Pipeline · Tech Stack · Quick Start · Setup · API
Large-scale evacuations expose a difficult planning problem: where should displaced families go, and how can urban services support their specific needs? CityStrata addresses this for Eilat by turning fragmented urban data into structured, actionable relocation intelligence.
The system analyzes 25 CBS statistical areas enriched with points of interest — education, housing, community facilities, religious institutions, and more — then classifies the city into four urban clusters using PCA-based features and K-Means clustering. When a family or community submits a profile, CityStrata runs a two-stage recommendation pipeline:
- Macro matching — GPT-4o selects the best-fit urban cluster based on needs and persisted cluster profiles.
- Tactical analysis — tactical agents orchestrate MCP spatial/vector tools (PostGIS hub discovery, pgvector ranking), then invoke GPT-4o to produce a grounded Hebrew explanation report with ranked relocation zones on the map.
What makes CityStrata different from a standard GIS app: spatial computation and vector similarity drive tactical decisions; the LLM handles macro cluster selection and final narrative explanation — not unconstrained geographic guessing.
- 🧠 AI-assisted relocation recommendations — GPT-4o macro matching with explainable reasoning
- 🗺️ GIS-driven tactical analysis — PostGIS hub discovery with adaptive service radii
- 📊 Machine learning urban clustering — PCA features + K-Means on statistical areas
- 🔍 Semantic vector similarity search — pgvector cosine ranking over amenity embeddings
- 📝 Grounded explanation reports — Markdown narratives tied to computed zone scores
- 🌍 Interactive Mapbox visualization — layers, cluster coloring, and recommendation radii
- 🗄️ PostgreSQL + PostGIS + pgvector — unified spatial and vector data store
- ⚙️ MCP tactical pipeline — structured spatial/vector tools orchestrated by tactical agents
Public entry point with project overview and navigation to the operational map.
Mapbox GL map centered on Eilat with statistical areas, POI layers, cluster visualization, and recommendation zone overlays.
Family and community profile management with macro matching, tactical pipeline execution, and Markdown report display.
Role-protected analytics view with area-scoped KPIs, POI distribution charts, and statistical area selection (Leaflet map).
Visitor-facing portal for evacuee profile creation and property listing management.
| Stage | What happens |
|---|---|
| Problem | Emergency evacuations require matching diverse family needs to urban areas with the right services and housing. |
| Solution | CityStrata combines spatial data, ML clustering, vector search, and LLM reasoning into ranked relocation recommendations. |
| Data | CBS statistical areas and multi-source POI layers are loaded into PostGIS; features are engineered for clustering. |
| Spatial intelligence | PCA reduces urban feature dimensions; K-Means groups areas into four semantic cluster profiles. |
| AI recommendation | GPT-4o selects a macro cluster; tactical agents call MCP tools for hub discovery and semantic ranking, then GPT-4o explains the results. |
| Visualization | Results render as interactive map layers, zone radii, and human-readable reports for planners and families. |
CityStrata follows a React frontend + FastAPI backend + PostgreSQL/PostGIS/pgvector database architecture, with offline data preparation feeding an online recommendation pipeline.
┌─────────────────────────────────────────────────────────────────────────┐
│ FRONTEND (React / Vite) │
│ Landing · Map App · Municipality Dashboard · Family Portal │
│ Mapbox GL (operational map) · Leaflet (dashboard) · TanStack Query │
└───────────────────────────────┬─────────────────────────────────────────┘
│ REST / GeoJSON
┌───────────────────────────────▼─────────────────────────────────────────┐
│ BACKEND (FastAPI) │
│ Auth · POI APIs · Clustering · Matching Agent · Tactical Pipeline │
└───────┬─────────────────────────────┬───────────────────────────────────┘
│ │
▼ ▼
┌───────────────────┐ ┌─────────────────────────────────────────────┐
│ Supabase Auth │ │ Tactical Agents (family / multi-family) │
│ JWT + roles │ │ Orchestrate MCP tools · invoke GPT-4o report │
└───────────────────┘ └───────────────────┬─────────────────────────┘
│ stdio tool calls
┌───────────────▼─────────────────────────┐
│ MCP Server │
│ PostGIS + pgvector tools (no LLM) │
└───────────────┬─────────────────────────┘
│
┌───────────────────────────────────────────────▼─────────────────────────┐
│ PostgreSQL + PostGIS + pgvector (Supabase) │
│ Statistical areas · POI layers · Cluster data · Profiles · Results │
└─────────────────────────────────────────────────────────────────────────┘
| Area | Route | Purpose |
|---|---|---|
| Landing | / |
Public project overview |
| Map app | /map |
Operational GIS map, profile intake, recommendations |
| Municipality dashboard | /municipality |
KPIs, area analytics (editor/admin) |
| POI management | /municipality/poi |
CRUD for POI categories |
| Hotel management | /municipality/hotels |
Hotel listing management |
| Family portal | /family/* |
Evacuee profiles and property listings (visitor) |
| Auth | /login, /signup |
Supabase-backed authentication |
Map libraries: Mapbox GL powers the operational map at /map; Leaflet powers the municipality analytics dashboard.
| Module | Location | Role |
|---|---|---|
| API routers | backend/app/api/endpoints/ |
REST handlers under /api |
| Matching agent | backend/app/agents/matchingAgent.py |
GPT-4o macro cluster selection |
| Tactical bridge | backend/app/services/tactical_pipeline.py |
API entry point for tactical agents |
| MCP server | backend/mcp/mcp_server.py |
Exposes PostGIS + pgvector tools over stdio |
| Tactical agents | backend/mcp/family_agent.py, multi_family_agent.py |
Orchestrate MCP tools; call GPT-4o for reports |
| Clustering | backend/ML/clustering.py |
K-Means on preloaded PCA features |
The MCP layer is split into three responsibilities:
| Component | Role |
|---|---|
MCP server (mcp_server.py) |
Exposes structured tools: evacuation context, hub discovery (ST_Union, ST_ClusterKMeans, ST_DWithin), and pgvector semantic scoring. Also generates runtime embeddings via the OpenAI API. |
Tactical agents (family_agent.py, multi_family_agent.py) |
Call MCP tools in sequence via BaseTacticalAgent, then invoke GPT-4o to generate the grounded Markdown report. |
Tactical pipeline service (tactical_pipeline.py) |
Thin FastAPI bridge that validates inputs and delegates to the agent modules. |
GPT-4o reasoning for tactical reports lives in the agents, not in the MCP server itself.
| Service | Usage |
|---|---|
| OpenAI | GPT-4o (matching + tactical reports), text-embedding-3-small (embeddings) |
| Mapbox | Map tiles and GL rendering on the operational map |
| Supabase | PostgreSQL hosting, Auth (JWT), user management |
| Nominatim | Address geocoding for POI and hotel management forms |
CityStrata separates offline data preparation, clustering preparation, and online recommendation generation. Runtime recommendations consume persisted cluster assignments and profiles — they do not recompute PCA features per request.
Run when source data changes, before clustering or recommendations are available.
| Step | What | Where |
|---|---|---|
| 1. Data ingestion | Load CBS statistical areas and POI datasets into PostGIS | backend/scripts/load_*.py |
| 2. Feature engineering | Normalize urban features across semantic blocks (education, tourism, food, community, OSM infrastructure, religious) | backend/collab notebooks/clustering_model.ipynb |
| 3. PCA | Block-wise dimensionality reduction → principal components per area | Notebook output → CSV |
| 4. PCA load | Persist PC features per statistical area | backend/scripts/load_pca_ready_for_clustering.py |
| 5. Embedding ingestion | Generate OpenAI embeddings for POI rows | backend/scripts/vector_ingestion.py |
| 6. Vector indexes | HNSW cosine indexes on embedding columns | SQL scripts in backend/sql/ |
Clustering operates on preloaded PCA data — it does not re-run feature engineering or PCA at request time.
| Aspect | Detail |
|---|---|
| Trigger | POST /api/clustering/run (also available from the map UI) |
| Algorithm | K-Means (default k=4) via backend/ML/clustering.py |
| Output | Persists to clustering_runs, cluster_assignments, and cluster_profiles |
| Caching | Reuses an existing run if the same k was computed within the last 24 hours |
This is an administrative preparation workflow, not part of the per-family runtime path. Recommendations read the latest persisted cluster profiles and assignments.
Persisted cluster labels (assigned by backend/ML/clustering.py and stored in cluster_profiles):
Residential - SecularResidential - Religious/FamilyCommercial CorePeripheral - Sparse
Triggered when a user submits a profile and runs matching + tactical analysis.
Family / Community Profile
│
▼
┌───────────────────────┐
│ GPT-4o Macro Match │ Reads persisted cluster_profiles → matching_results
└───────────┬───────────┘
▼
┌───────────────────────┐
│ Tactical Agent │
│ 1. get_evacuation_context — MCP: profile + cluster boundary
│ 2. discover_optimal_radius — MCP: ST_Union, ST_ClusterKMeans, ST_DWithin
│ 3. semantic_radius_scoring — MCP: pgvector cosine similarity ranking
│ 4. GPT-4o report generation — Agent: grounded Hebrew Markdown narrative
└───────────┬───────────┘
▼
Ranked zones + explanation → Map visualization
| Stage | Technology | Responsibility |
|---|---|---|
| Macro matching | GPT-4o (matchingAgent.py) |
Cluster selection based on profile needs vs. persisted cluster profiles |
| Tool orchestration | Tactical agents (family_agent.py) |
Sequence MCP tool calls and aggregate results |
| Hub discovery | PostGIS via MCP tools | Find service hubs and adaptive radii within the matched cluster |
| Semantic ranking | pgvector + text-embedding-3-small via MCP tools |
Rank hubs by cosine similarity to family needs text |
| Explanation | GPT-4o via tactical agents | Generate grounded Markdown report from computed scores |
| Visualization | Mapbox GL | Render recommendation radii and zone highlights |
Role separation: MCP tools and pgvector perform tactical computation. Tactical agents orchestrate those tools and invoke GPT-4o only for the final narrative. The LLM does not invent locations.
Models used (verified in code): gpt-4o, text-embedding-3-small (1536 dimensions).
Prerequisites: Python 3.10+, Node.js 18+, PostgreSQL with PostGIS + pgvector, Supabase project, OpenAI API key, Mapbox access token.
# 1. Clone
git clone <repository-url>
cd CityStrata
# 2. Create a .env file at the repository root (see Environment Variables)
# Populate DATABASE_URL, SUPABASE_*, OPENAI_API_KEY, VITE_MAPBOX_ACCESS_TOKEN, etc.
# 3. Backend
cd backend
pip install -r requirements.txt
uvicorn app.main:app --reload --port 8000
# 4. Frontend (new terminal)
cd frontend
npm install
npm run dev
# → http://localhost:5173Open /map for the operational GIS interface. Database initialization and offline data preparation are required before recommendations will work — see Local Development Setup.
- Sign up or log in with an editor or admin role.
- Navigate to
/municipalityfor city-wide KPIs and area-scoped analytics. - Click statistical areas on the dashboard map to filter metrics.
- Manage POIs at
/municipality/poiand hotels at/municipality/hotels. - Use
/mapfor operational GIS, family profiles, and recommendation execution.
- Sign up or log in with a visitor role.
- Open
/familyfor the family dashboard. - Create an evacuee profile at
/family/profile/new(7-step wizard). - Optionally submit property listings at
/family/property/new.
- Create profile — family wizard on
/mapsidebar or family portal. - Ensure clustering is prepared — run clustering via the map UI or
POST /api/clustering/runif no recent run exists. - Run macro matching — GPT-4o selects the best cluster for the profile.
- Run tactical analysis — tactical agent orchestrates MCP tools, then GPT-4o generates the report.
- Review results — Markdown explanation in the recommendations panel; ranked radii on the map.
Community groups follow a parallel flow with community profiles and multi-family tactical analysis.
All endpoints are prefixed with /api. Responses for spatial data use GeoJSON where applicable. Route definitions are in backend/app/api/endpoints/ and registered via backend/app/api/router.py.
| Domain | Endpoints | Purpose |
|---|---|---|
| Authentication | POST /auth/signup · POST /auth/login · POST /auth/logout · GET /auth/me · PATCH /auth/me |
Supabase JWT auth and user profile |
| Statistical Areas | GET /statistical-areas · GET /statistical-areas/{stat_2022} · GET /statistical-areas/{stat_2022}/summary |
CBS area polygons and area-scoped statistics |
| Map / POI Layers | GET /institutions · GET /institutions/{institution_code} · GET /airbnb · GET /restaurants · GET /coffee-shops · GET /hotels · GET /matnasim · GET /osm-facilities/types · GET /osm-facilities · GET /synagogues |
Read-only GeoJSON POI layers |
| POI Management | GET /poi/{category} · POST /poi/{category} · PATCH /poi/{category}/{entity_id} · DELETE /poi/{category}/{entity_id} |
CRUD for POI categories (editor/admin) |
| Hotels Management | GET /hotels-management · POST /hotels-management · PATCH /hotels-management/{hotel_uuid} · DELETE /hotels-management/{hotel_uuid} |
Hotel CRUD with geocoding (editor/admin) |
| Family Profiles | GET /evacuee-family-profiles · GET /evacuee-family-profiles/{profile_id} · POST /evacuee-family-profiles · PATCH /evacuee-family-profiles/{profile_id} · DELETE /evacuee-family-profiles/{profile_id} |
Evacuee profile intake and retrieval |
| Community Profiles | GET /communities · GET /communities/{community_id} · POST /communities |
Community/group profile management |
| Family Portal | GET /family/me/dashboard · GET /family/me/profiles · GET /family/me/profiles/{profile_uuid} · POST /family/me/profiles · PATCH /family/me/profiles/{profile_uuid} |
Authenticated family-scoped profile access |
| Clustering | POST /clustering/run · GET /clustering/profiles · GET /clustering/assignments · GET /clustering/latest · GET /clustering/full |
K-Means pipeline and cluster data |
| Matching | POST /matching/cluster · POST /matching/cluster/{profile_id} · POST /matching/cluster/community/{community_id} · GET /matching/result/{profile_uuid} · GET /matching/result/community/{community_id} |
GPT-4o macro cluster selection |
| Recommendations | GET /recommendations/overview · GET /recommendations · POST /recommendations/run/{profile_uuid} · POST /recommendations/community/run · GET /recommendations/{profile_uuid} |
Tactical pipeline and report retrieval |
| Property Listings | GET /property-listings · GET /property-listings/mine · GET /property-listings/units-table · GET /property-listings/{listing_id} · POST /property-listings · PATCH /property-listings/{listing_id} · DELETE /property-listings/{listing_id} |
Family-submitted housing listings |
| Evacuation Analysis | POST /evacuation/analyze |
Capacity vs. need analysis for selected areas |
| Nearby Search | GET /nearby |
Proximity queries for amenities |
CityStrata organizes data around statistical areas as the core spatial unit, with profiles and recommendation results linking families to clusters and tactical zones.
| Entity | Table | Role |
|---|---|---|
| Statistical areas | statistical_areas |
CBS polygons (MultiPolygon), centroids, JSONB metadata — 25 areas for Eilat |
| POI layers | airbnb_listings, restaurants, coffee_shops, hotels_listings, matnasim, educational_institutions, osm_city_facilities, synagogues |
Point geometries with service attributes; embedding columns where ingested |
| PCA features | pca_ready_for_clustering |
Principal components per statistical area (loaded offline) |
| Cluster data | clustering_runs, cluster_assignments, cluster_profiles |
K-Means run metadata, area-to-cluster mapping, semantic profiles |
| Family profiles | evacuee_family_profiles |
JSON intake data — composition, education, religious/cultural, housing needs |
| Community profiles | community_profiles |
Group-level profiles with cohesion and facility requirements |
| Macro results | matching_results, community_matching_results |
GPT-4o cluster selection with confidence and reasoning |
| Tactical results | family_tactical_responses, multi_family_tactical_responses |
Markdown reports + radii_data JSON for map zone rendering |
| Users | municipality_users |
App profiles linked to Supabase Auth with role-based access |
| Property listings | property_listings, property_listing_units |
Family-submitted housing availability |
Required PostgreSQL extensions: postgis, pgcrypto, vector (pgvector).
Schema is defined by ordered SQL scripts in backend/sql/.
CityStrata/
├── frontend/ React/Vite SPA — map, dashboard, family portal
│ └── src/
│ ├── components/ Map layers, forms, recommendations, UI primitives
│ ├── user_dashboard/ Municipality analytics dashboard
│ ├── family_portal/ Family-facing profile and listing management
│ ├── services/ Axios API client and auth
│ └── hooks/ TanStack Query data hooks
│
├── backend/
│ ├── app/ FastAPI application
│ │ ├── api/endpoints/ REST route handlers
│ │ ├── agents/ GPT-4o matching agent
│ │ ├── core/ Config, database pool, auth
│ │ ├── models/ Pydantic schemas
│ │ └── services/ Spatial, geocoding, tactical pipeline bridge
│ ├── mcp/ MCP server and tactical agents
│ ├── ML/ K-Means clustering pipeline
│ ├── sql/ Database schema scripts
│ ├── scripts/ ETL loaders and embedding ingestion
│ ├── collab notebooks/ Offline PCA and clustering research
│ ├── tests/ pytest suite
│ └── run.sh Production start script (Render)
│
├── docs/ Project planning documents
└── create_listing.py Dev utility for seeding property listings
- Python 3.10+ with pip
- Node.js 18+ with npm
- PostgreSQL with PostGIS and pgvector extensions
- Supabase project — for Auth and database hosting
- OpenAI API key — required for matching, tactical reports, and embeddings
- Mapbox access token — required for live map tiles on
/map
Apply SQL scripts from backend/sql/ in numeric filename order against your PostgreSQL instance. Each script is idempotent where possible, but order matters for dependencies.
# Apply all scripts in order (example using psql)
for f in backend/sql/*.sql; do
psql "$DATABASE_URL" -f "$f"
doneThen load spatial data using ETL scripts:
cd backend
python scripts/load_statistical_areas.py
python scripts/load_educational_institutions.py
python scripts/load_airbnb_listings.py
# ... run additional load_*.py scripts as neededFor full recommendation capability, also complete offline ML preparation:
python scripts/load_pca_ready_for_clustering.py # after PCA CSV is prepared
python scripts/vector_ingestion.py # generate POI embeddingsPCA feature engineering is performed in backend/collab notebooks/clustering_model.ipynb and exported as CSV for loading. Clustering can then be triggered via POST /api/clustering/run or the map UI.
cd backend
pip install -r requirements.txt
# Development with auto-reload
uvicorn app.main:app --reload --port 8000
# Production-style (Render)
bash run.shAPI available at http://localhost:8000. OpenAPI docs at /docs.
cd frontend
npm install
npm run dev # http://localhost:5173
npm run build # production build → dist/
npm run preview # preview production buildEnvironment files are read from the repository root (configured in frontend/vite.config.js).
Create a **.env file at the repository root** and populate the values listed below. The file .env.test.example` documents test-only placeholder values and is not intended as a production template.
| Variable | Required | Purpose |
|---|---|---|
DATABASE_URL |
Yes | PostgreSQL connection string (postgresql://...) |
SUPABASE_URL |
Yes | Supabase project URL |
SUPABASE_ANON_KEY |
Yes | Supabase anonymous key (client auth) |
SUPABASE_SERVICE_ROLE_KEY |
Yes | Supabase service role key (admin signup) |
OPENAI_API_KEY |
For AI features | Matching agent, tactical reports, embeddings |
NOMINATIM_USER_AGENT |
No | User-Agent for Nominatim geocoding (has default) |
ENV |
No | Environment label (default: dev) |
PORT |
No | Server port (default: 8000) |
CORS_EXTRA_ORIGINS |
No | Comma-separated additional CORS origins |
CORS_ALLOW_ALL |
No | Set to 1 for unrestricted CORS (debug only) |
Loaded by backend/app/core/config.py.
| Variable | Required | Purpose |
|---|---|---|
VITE_API_URL |
No | Backend URL (default: http://localhost:8000) |
VITE_MAPBOX_ACCESS_TOKEN |
For live map | Mapbox GL access token |
VITE_MAPBOX_USERNAME |
No | Mapbox username |
VITE_MAPBOX_STYLE_ID |
No | Map style ID (default: streets-v12) |
| Command | Description |
|---|---|
npm run dev |
Start Vite dev server (port 5173) |
npm run build |
Production build |
npm run preview |
Preview production build |
npm run test |
Run all Vitest tests |
npm run test:unit |
Unit tests only |
npm run test:integration |
Integration tests only |
npm run test:coverage |
Tests with coverage report |
| Command | Description |
|---|---|
uvicorn app.main:app --reload |
Development server |
bash run.sh |
Production server (Render) |
python -m pytest |
Run tests (excludes slow/integration by default) |
python -m pytest -m slow |
ML clustering tests |
python -m pytest --cov=app |
Tests with coverage |
python scripts/load_*.py |
ETL data loaders |
python scripts/vector_ingestion.py |
Generate POI embeddings |
python mcp/tactical_agent.py --family-id <uuid> |
CLI tactical pipeline |
| Command | Description |
|---|---|
python create_listing.py |
Seed random property listings via API |
- Framework: pytest + pytest-asyncio
- Default run: excludes
slowandintegrationmarkers; uses mocked database pool - Guide:
backend/docs/BACKEND_TESTING.md
cd backend
pip install -r requirements.txt -r requirements-dev.txt
python -m pytest
python -m pytest --cov=app --cov-report=html- Framework: Vitest + happy-dom + MSW (API mocking)
- Map rendering: mocked in tests (no live Mapbox/Leaflet)
- Guide:
frontend/docs/FRONTEND_TESTING.md
cd frontend
npm run test
npm run test:coverageIntegration tests against a live database are placeholder/skipped. No end-to-end browser tests are configured.
Deployment is inferred from configuration artifacts — no live URLs or CI/CD pipelines are documented in the repository.
| Component | Artifact | Notes |
|---|---|---|
| Backend | backend/run.sh |
Uvicorn on $PORT; CORS allows *.vercel.app |
| Frontend | frontend/vercel.json |
SPA rewrite rules for client-side routing |
| Database | Supabase | PostgreSQL with PostGIS + pgvector via DATABASE_URL |
Set CORS_EXTRA_ORIGINS for custom frontend domains. No Dockerfile, render.yaml, or GitHub Actions workflows are present.
- Offline preparation required — recommendations depend on ETL scripts, PCA CSV loading, embedding ingestion, and a clustering run before the full pipeline is operational.
- External API dependencies — OpenAI (matching, reports, embeddings) and Mapbox (map tiles) require valid API keys.
- Database extensions — PostgreSQL must have PostGIS, pgvector, and pgcrypto enabled.
- Dual map libraries — Mapbox GL on the operational map; Leaflet on the municipality dashboard.
- No Alembic migrations — schema is managed via ordered SQL files in
backend/sql/. - Feature engineering source data — PCA input CSV is produced by the clustering notebook and is not committed to the repository.
- Schema coverage — some POI tables (e.g.
synagogues) are used by loaders and API code but may require manual schema verification on a fresh database.
| Source | Role |
|---|---|
| CBS (Central Bureau of Statistics, Israel) | Statistical Areas 2022 shapefile, filtered to Eilat (SEMEL_YISH = 2600, 25 areas) |
| Curated POI datasets | Educational institutions, Airbnb listings, restaurants, coffee shops, hotels, matnasim (community centers), synagogues |
| OpenStreetMap-derived data | OSM city facilities layer (osm_city_facilities) loaded as spatial POI data |
| Nominatim (OSM geocoding API) | Address-to-coordinate geocoding for POI and hotel management forms |
This project is an Academic Final Software Engineering Project intended for research and educational purposes. It is not a production emergency response system.
CityStrata — Geospatial intelligence for informed evacuation planning.