Listing aggregator + neighborhood insights for Bangkok, Thailand. Combines a Bun + Effect.ts backend (plain Postgres, no PostGIS) with a single long-running ETL process (Crawlee, RSS, GISTDA, PCD, Longdo) and a React 19 frontend (Deck.gl maps, TanStack Router).
No ML, no agent, no SHAP, no Treasury data, no Python runtime.
- Map of 4 listing sources — Baania, Hipflat, DDproperty, ZmyHome. Same property on multiple sites = multiple markers. No dedup.
- 5 environmental overlays — news (RSS), flood (GISTDA), sound (gov data), air quality (PCD Thailand), traffic (Longdo). Toggleable in the layer panel.
- Transit layer — Bangkok GTFS, refreshed quarterly.
- Important POI layer — schools, healthcare, rail stations, daily needs, parks, and safety locations; refreshed from a versioned OSM snapshot plus curated government/GTFS data.
- 8 PMTiles map layers — pre-generated offline with tippecanoe. No runtime MVT endpoint.
- Source filter — show All / Baania / Hipflat / DDproperty / ZmyHome on the map.
- Insights page — news list + small map view of recent items.
The previous "AI valuation" framing (LightGBM C5 model, SHAP, LangGraph agent) is cut from the active path. The
gis-server/directory is kept in the repo for historical reference but is not run. The D1–D33 decision log is inline indb/schema.tscomments.
borbann-core/
├── .opencode/ # Local docs home (gitignored): TASKS.html, plans/, notes/
├── backend/ # NEW (M1) — Bun + Effect.ts + Drizzle. 12 routes by M2.
├── etl/ # NEW (M1) — Bun + Effect Schedule + Crawlee. Single long-running process.
├── frontend/ # React 19 + Vite + TanStack Router. Frontend cuts done; M3 adds /insights.
├── gis-server/ # LEGACY — FastAPI + PostGIS + ML. Not run, not maintained.
├── agent-patterns/ # Canonical Effect idioms (read before writing Effect code)
├── docker-compose.yml # NEW (M1) — postgres:16-alpine, redis, backend, etl, frontend
├── AGENTS.md # ← Agent conventions (read this first)
└── README.md # Top-level product description
# From repo root
make stack-upThis brings up Postgres 16, Redis 7, the new Bun + Effect.ts backend on http://localhost:8000, the ETL process, and the Vite frontend on http://localhost:3000.
cd backend
# Install dependencies
bun install
# Run development server
bun run dev
# Run tests
bun test
# Lint
bun run lintStack: Bun, Effect.ts, @effect/platform, Effect Schema, Drizzle ORM (postgres-js driver), ioredis.
cd etl
# Install dependencies
bun install
# Run (long-running; SIGTERM to stop)
bun run start
# Run with file watch
bun run devPOI refresh is repeatable and uses the same path locally or in a scheduled job:
make db-up
DATABASE_URL=postgres://borbann:borbann@localhost:55432/borbann bun run db:migrate
make poi-refresh
make poi-checkThe poi-prep/ job converts a versioned OSM PBF into a dated normalized
snapshot. The Bun ETL validates all inputs before replacing the pois table;
an invalid or missing snapshot leaves the previous POI data intact.
Stack: Bun, Effect Schedule, Crawlee (Cheerio + Playwright), rss-parser, tippecanoe subprocess.
cd frontend
# Install dependencies
bun install
# Run development server
bun run devmake stack-up # Start full stack (db + redis + backend + etl + frontend)
make stack-down # Stop full stack
make db-up # Start Postgres + Redis only
make db-reset # Recreate DB volume (DESTRUCTIVE)
make backend-dev # Run backend dev server (port 8000)
make etl-dev # Run ETL dev process
make frontend-dev # Run frontend dev server (port 3000)
make test # Run backend + frontend tests
make lint # Run backend + frontend lint
make format # Format backend + frontendOnce the backend is running (M2 will fill in the 12 routes):
GET /healthz— livenessGET /readyz— readiness (checks DB + Redis)GET /metrics— Prometheus metricsGET /listings— paginated listing searchGET /listings/:key— listing detail (key:source:source_id)GET /listings/sources— source statsGET /transit— transit linesGET /news— recent newsGET /flood— flood zonesGET /sound— sound zonesGET /air-quality— air quality stationsGET /traffic— traffic incidentsGET /api/v1/pois— curated residential POIs, with category/type/bbox filtersGET /api/v1/pois/nearby— curated POIs within a radius of a house
| Layer | Source | Cadence |
|---|---|---|
| listings | 4 crawlers | weekly |
| transit_lines | GTFS | quarterly |
| pois | curated government/GTFS data + versioned OSM snapshot | monthly |
| news | RSS | 30 min |
| flood | GISTDA | 15 min |
| sound | gov data | daily |
| air_quality | PCD Thailand | 30 min |
| traffic | Longdo | 5 min |
- 4 listing crawlers (Crawlee, mixed Cheerio/Playwright): Baania, Hipflat, DDproperty, ZmyHome
- RSS feeds for news (~50 feeds)
- GISTDA live API for flood zones
- Government open data for sound
- PCD Thailand for air quality
- Longdo Map for traffic
- Bangkok GTFS for transit
backend/.env and etl/.env (separate files):
DATABASE_URL=postgres://borbann:borbann@localhost:5432/borbann
REDIS_URL=redis://localhost:6379
PORT=8000
LOG_LEVEL=infoObject storage (R2/S3) is wired in M2 but not yet active.
# Format code
make format
# Run linter
make lint
# Run all tests
make testLocal development only. There is no production deployment, no infrastructure to manage, and no CI/CD for releases. Don't introduce deployment scaffolding unless explicitly asked.
For educational purposes.
Developed by Kasetsart University students for the Borbann platform.