Problem
Global maritime shipping suffers from opaque risk signals — vessel delays, port congestion, and weather hazards compound in ways operators only notice after the fact. Off-the-shelf trackers show positions but not composite risk in real time.
Approach
- 1
Ingested live global AIS (Automatic Identification System) feeds and normalized them into a TimescaleDB schema with hypertables for vessel positions, PostGIS spatial indexes for geo queries, and continuous aggregates for congestion metrics.
- 2
Streamed real-time vessel positions to a React + MapLibre GL map via WebSocket, handling backpressure so a single connection never drops even under 1,600+ concurrent vessel updates.
- 3
Wrote a composite risk scoring engine: ETA deviation × port congestion × live weather hazards. A Celery Beat scheduler runs it every 10 minutes; results classify shipments as LOW / MEDIUM / HIGH and fire Slack + email alerts with per-shipment cooldown to prevent alert fatigue.
- 4
Packaged the whole system as an 8-service Docker Compose stack: FastAPI, Celery worker, Celery beat, AIS consumer, vessel simulator, Redis, TimescaleDB, and React frontend.
- 5
Deployed the frontend on Vercel and exposed the local API globally via Cloudflare Tunnel — a full production topology with zero cloud server cost.
Outcomes
- Tracks 1,600+ active vessels in real time with sub-second WebSocket latency.
- Analytical queries over millions of rows return in under 100ms thanks to hypertables + continuous aggregates.
- Manual monitoring overhead reduced to zero — the risk engine only pages operators when a shipment actually crosses a threshold.
Learnings
TimescaleDB's hypertables + continuous aggregates make analytical queries on time-series data scale trivially without sharding. Cloudflare Tunnel is an underrated way to expose local services globally without paying for cloud compute. Celery Beat + per-shipment cooldowns is the right primitive for alert engines — don't reinvent scheduling.