Live demo: https://pharos-0y6q.onrender.com (baked synthetic snapshot; run locally for the live Singapore pilot)
Maritime domain awareness & GEOINT — turns free vessel-movement data (AIS) into source-rated, human-review maritime incidents: dark ships, ship-to-ship transfers, loitering / zone incursions, AIS spoofing, and trajectory anomalies — fused into a composite per-vessel threat picture, the way a maritime fusion cell actually works.
The maritime lane of the portfolio. Sibling to HORUS (air / GNSS interference), SENTINEL (cyber threat intelligence), ARGUS (all-source / information defence) and DELPHI (infrastructure capacity). The first three fuse the air, cyber and cognitive pictures; PHAROS fuses the spatiotemporal one — where and when, on the water. DELPHI is the odd one out by design: a decision system rather than a detection one. HORUS watches the sky above the same strait, a deliberate pair: together they give a joint air + sea picture over the world's busiest waterway, and ARGUS consumes both read-only as its Ocean and Sky lanes. Named for the lighthouse of Alexandria: a watcher over the sea.
Status: the full standalone system runs end-to-end. Collection (NOAA bulk AIS loader + AISStream live client + Global Fishing Watch labels + a deterministic labelled synthetic generator), track building, the five-detector ensemble, the flagship trajectory-anomaly model, the composite maritime-threat rollup, the honest eval harness, a hardened read-only API, and a React/Leaflet map dashboard are all in place and browser-verified. The ARGUS-shaped GEOINT evidence bridge is also implemented. The Singapore pilot began on 2026-07-16 and has so far observed 266.7 hours of Class A/B AIS across 4,801 vessels and 10,687 tracks, ingested by a low-priority launch agent while an incremental processor rebuilds dirty track tails every two minutes and scores them with the frozen SHA-pinned GRU. Collection is not continuous — the ledger records 1,091 coverage outages, and the upstream AISStream feed has been refusing the websocket handshake since 2026-08-05, so the observed window is what the numbers above count and the wall-clock window is longer. That gap is the reason coverage outages are recorded and reported rather than smoothed over. Phase 3 (independent labels/evaluation) and Phase 4 (sanitized delayed snapshots / dashboard modes) machinery is built and running, but no blinded review has been completed yet — the queue stands at 0 of 200 tracks and 0 of 50 alerts, so there is no pilot evaluation to report and none is claimed. The baked-demo release is deployed on Render's free tier at https://pharos-0y6q.onrender.com (see
docs/DEPLOY.md). Progress is tracked honestly indocs/ROADMAP.md; every model/detector claim lands indocs/EVAL.mdwith the number that survives scrutiny.
Maritime domain awareness is arguably the most operationally relevant intelligence problem for Singapore: the Strait of Malacca and the Singapore Strait, the world's busiest container port, the RSN Information Fusion Centre, and the current-salience problems of dark-fleet sanctions evasion, illegal fishing, smuggling, and gray-zone maritime activity. PHAROS works this domain on entirely free, open data.
Ingest free AIS → build per-vessel tracks → run a battery of detectors, each covering a different maritime threat, then fuse them into a composite per-vessel maritime-threat rollup (which detectors agree, the zone context, a transparent risk score, and a NATO Admiralty-style reliability grade for AIS confidence):
- Dark-ship / AIS-gap — a vessel that goes silent in a sensitive zone and reappears displaced (the classic smuggling / sanctions-evasion signature).
- Ship-to-ship (STS) transfer — two vessels co-located, slow, offshore, for a sustained window (dark-fleet oil transfers).
- Loitering / zone incursion — dwelling in or entering a geofenced watch area.
- AIS spoofing / identity anomaly — physically impossible kinematics or duplicate identity.
- Trajectory anomaly — the flagship model: a GRU sequence autoencoder that learns pattern-of-life from the ordered track and flags deviations (trained on the unlabeled population with a train/val split, train-only normalization, early stopping, and a recorded learning curve). Training writes a versioned artifact so batch detection and API inference use the same weights, scaler, and threshold.
Two evaluations, and docs/EVAL.md leads with the second:
- Real NOAA AIS is the test that counts. On one real day of LA/Long Beach traffic (352 vessels, 145k reports) the detectors immediately exposed a real weakness — a congested port is full of anchored vessels sitting slow-and-near each other, which naive detectors call ship-to-ship transfers and loitering. Three domain-correct fixes (anchored-vessel exclusion, port-zone awareness, a partner-degree cap) cut false positives ~98% (rendezvous 2,892 → 8, total 2,999 → 51). That measured before/after is the actual result. The flagship GRU, trained on the real pattern-of-life, surfaces the genuinely-distinctive tracks (the Catalina Island high-speed ferries) as the top anomalies — interpretable, real outliers.
- The flagship model beats fair baselines — the depth is necessary. Under the realistic unsupervised setup (train on all tracks, no labels), the compact GRU sequence-AE reaches 0.962 within-region / 0.962 cross-region AUC, ahead of a nonlinear Isolation Forest (0.940 / 0.942) while linear PCA falls below chance (0.273). A 25-run capacity sweep selected 8 hidden units (0.963 / 0.964 mean AUC): slightly stronger transfer with 804 rather than 38,660 parameters. These corrected numbers fit normalization only on the training partition; the earlier optimistic result was discarded after the leakage audit.
- Honest about the synthetic ceiling. The labelled offline gold set can't ship hundreds of MB of NOAA CSVs, so it uses a deterministic simulator (noise, benign confounders, graded anomalies). But self-generated anomalies are separable by construction — so the near-perfect detector P/R and high synthetic AUC are a ceiling, not a capability claim. The one informative synthetic number is the baseline gap above.
- The AIS coverage confound is handled, not hidden. A "dark ship" is often a receiver-coverage gap — handled via a displacement requirement, an AIS reliability grade (on real data a 20 h gap was graded E), a calibration trap (held 5/5), and an operational GFW cross-check. The first LA/LB GFW query returned no candidate events, so external-label precision remains unestimable on that slice; the negative is recorded rather than buried. Incidents are human-review decision support, never automated verdicts.
- External labels now overlap real NOAA traffic. A selected east-Gulf cohort (2023-07-25;
337 vessels, 173k reports) produced vessel/type/time/place agreement with GFW for 4/34
rendezvous and 65/298 loiter calls. These are corroboration rates, not precision: the cohort
is label-enriched and GFW is an incomplete silver label. Its one gap label was offshore beyond
NOAA receiver coverage, a useful limitation recorded in
docs/EVAL.md. - The full Gulf population now runs. A conservative slow-motion space/time index reduces 937,765 possible rendezvous pairs to 51,570 exact candidates (5.50%). The 1.39M-report, 1,982-vessel detector run completes in 7.12s after loading and returns 93 unique pairs; 3 of 186 symmetric calls match the four GFW encounter labels under the same agreement rule.
NOAA Marine Cadastre AIS (bulk historical, no key — the reproducible eval workhorse), AISStream.io (free live global WebSocket — the demo lane, includes Malacca/Singapore), Global Fishing Watch API (free event labels for cross-check), and Natural Earth / EEZ reference geometry.
Python 3.12 (conda) · SQLAlchemy 2.0 / Alembic · PostgreSQL (SQLite for tests — no PostGIS dependency; spatial math in pure numpy) · Prefect · httpx · scikit-learn · torch GRU sequence autoencoder (flagship anomaly model; Isolation Forest + PCA baselines) · FastAPI · React 19 + TypeScript + Leaflet · Docker Compose · GitHub Actions. Mirrors SENTINEL/ARGUS conventions so the three read as one body of work. ruff + mypy (strict) + pytest gate every change.
make env && conda activate pharos && make install # one-time
make up # Postgres + migrations (Docker)
make ingest FILE=data/ais/<slice>.csv REGION=us-west # load a free NOAA AIS slice
make tracks # build per-vessel voyages
make detect # run the detector ensemble → incidents
make eval # score detectors on the gold set → docs/EVAL.md
make benchmark-anomaly # reproduce GRU capacity selection (25 runs/size)
make eval-real FILE=data/ais/<slice>.csv REGION=us-la # the honest test on real AIS
PHAROS_DATABASE_URL=sqlite:///data/sg-live.db make collector # continuous SG lane
PHAROS_DATABASE_URL=sqlite:///data/sg-live.db make process-live # manual incremental cycle
PHAROS_DATABASE_URL=sqlite:///data/sg-live.db make prune # WAL-safe retention pass
make api # read-only API + GeoJSON on :8000
make ui # React map dashboard on :5173The continuous Singapore pilot collector is micro-batched, Class A/B aware, reconnecting, and
coverage-accounted for laptop sleep, and runs under a low-priority macOS launch agent. The bounded
make live capture remains available for manual checks, and the model/threshold freeze is
SHA-pinned and auditable.
Every two minutes the live worker rebuilds only dirty vessel track tails, refreshes affected detectors, and scores eligible tracks with the SHA-pinned frozen GRU—never request-path training or a silent fallback. Known collector outages suppress vessel-gap calls. Live positions retain for 21 days after a successful WAL checkpoint, with 2 GB warning and 5 GB emergency sampling controls.
No NOAA download to try it? Seed a self-contained demo from the deterministic synthetic generator (zones + tracks + the full detector ensemble + the anomaly model) into a SQLite file, then serve the API + dashboard off it:
PHAROS_DATABASE_URL=sqlite:///data/demo.db python -m scripts.seed_demo
PHAROS_DATABASE_URL=sqlite:///data/demo.db make api # then `make ui` in another shell/health, /stats, /vessels (+ /{mmsi}/track as GeoJSON), /incidents (+ /{id} with
evidence), /zones and /tracks (GeoJSON for the map), /maritime-picture (the composite
per-vessel threat rollups), /geoint/evidence (incidents as citable, source-rated GEOINT evidence
— the bridge below), plus one stateless inference route POST /score-track (scores a pasted
track's shape through the persisted detector artifact — inspects only the supplied points, never
fetches a URL, so the API stays effectively read-only). Its response reports
model_source=trained-artifact, or runtime-fallback when no valid artifact is available, so
model drift is visible rather than silent. Public-deploy hardening (CORS, per-client rate limit →
429, request-size cap → 422, bounded concurrency → 503) is tuned via PHAROS_API_*.
GET /geoint/evidence shapes each maritime incident into an evidence item whose fields match
ARGUS's EvidenceItem (doc_id / title / source / NATO-Admiralty reliability A–F / credibility 1–6
/ summary / resolvable url) plus geospatial extras. So the sibling
ARGUS all-source analyst — which already fuses
SENTINEL's cyber campaigns the same read-only way —
can cite PHAROS's maritime picture with no schema translation: one analyst reasoning across the
cyber, cognitive, and geospatial lanes. PHAROS only serves; it never depends on the siblings.
A React 19 + TypeScript + Leaflet dashboard (own maritime-teal identity) with three views: Maritime Picture (a live map of tracks, geofenced zones, and threat markers; click a threat for the evidence chain — which detectors agree → techniques → zone → the transparent composite risk breakdown and AIS reliability grade), Incidents (a filterable ranked feed), and Model Report (the per-threat coverage, the honest-evaluation story, and a live "score a track" panel).
A single free container (Dockerfile.web + render.yaml) builds the dashboard, installs the
slim API runtime, and bakes the demo SQLite seed, serving the read-only API + the built SPA from
one service — no managed database. See docs/DEPLOY.md.
Public / open-source AIS only; vessel-level (never individual persons); defensive and
analytical only. Incidents flag patterns in public broadcast data for human review — they are
decision support, never an automated verdict of illicit activity. See
docs/MODEL_CARD.md.