Documentation Index
Fetch the complete documentation index at: https://docs.axiomancer.io/llms.txt
Use this file to discover all available pages before exploring further.
Axiom Overwatch tracks vessel transits through four hand-curated maritime chokepoints — Bab-el-Mandeb, the Strait of Hormuz, the southern Suez approaches, and Cape Agulhas — by fusing live AIS dead-reckoning with cued Sentinel-1 SAR scenes. The pipeline predicts when each vessel will enter a corridor, schedules a radar look at the predicted window, and reconciles the AIS narrative with the SAR observation to flag confirmed transits, dark transits (SAR pixel with no AIS), and missed transits.
This is the free-tier alternative to commercial S-AIS chokepoint coverage. It runs on Copernicus open data and powers the chokepoint widgets on the Overwatch cockpit.
When to use this
- Monitor sanctions-relevant routing (Houthi-rerouting around Bab-el-Mandeb, Cape detour vs. Suez, Hormuz dark-fleet activity).
- Detect vessels that disappeared from AIS but were observed transiting a corridor by SAR.
- Backtest predicted ETA accuracy at a chokepoint over a custom window.
- Power a cockpit widget that lists confirmed / dark / missed transits per corridor for a recent period.
If you only need vessel-level routing without corridor semantics, use Route forecasts. If you only need a coverage check for a single SAR scene, use the SAR coverage API.
The chokepoints
Four corridor polygons ship as seed data in public.chokepoints. Each row carries a corridor geom plus an entry_zone and exit_zone used by the predictor and reconciler.
id | Name | Notes |
|---|
bab-el-mandeb | Bab-el-Mandeb / Red Sea South | Strait between Yemen and Djibouti. Entry from the Gulf of Aden (south), exit into the Red Sea (north). |
hormuz | Strait of Hormuz / N Gulf | Iran / UAE / Oman strait. Entry from the Persian Gulf (west), exit into the Gulf of Oman (east). Reverse direction also valid. |
suez-s | Suez S approaches / Red Sea North | Southern Suez approach. The canal interior is land-locked — surface analytics are limited to the southern entry. |
cape-agulhas | Cape Agulhas / Southern Africa | Atlantic ↔ Indian Ocean rounding point. Sanctions-era detour analytics. |
All four corridor polygons, plus their entry and exit zones, are GIST-indexed and available via SELECT * FROM public.chokepoints.
The transit lifecycle
Every predicted or observed transit is one row in public.vessel_transits. The row moves through a strict status lifecycle:
predicted ──► confirmed (AIS reappearance in exit_zone, or SAR pixel match, or both)
──► dark_transit (SAR pixel match with no prior AIS prediction)
──► missed (96h post-ETA elapsed with no reappearance)
──► cancelled (vessel re-routed away from the corridor before ETA)
A semantic invariant is enforced at the database level:
dark_transit rows must have a sar_scene_id and pixel_match_lat/pixel_match_lon, and no predicted_imo (the vessel was not predicted by AIS).
- All other statuses (
predicted, confirmed, missed, cancelled) must have a predicted_imo and predicted_eta.
confirmed_via records how the confirmation happened: ais_reappear, sar_pixel, or combined.
vessel_transits
| Field | Description |
|---|
id | UUID, primary key. |
predicted_imo | Vessel IMO from the AIS prediction. NULL for dark_transit. |
chokepoint_id | FK to chokepoints(id). |
predicted_eta | Projected entry time at the corridor. NULL for dark_transit. |
eta_uncertainty_hours | One-sigma band around predicted_eta. Floor of 1 hour. |
predicted_at | When the prediction row was inserted. |
last_known_lat / last_known_lon / last_known_speed / last_known_course | AIS snapshot the prediction was projected from, retained for forensic reconstruction. |
sar_scene_id | Copernicus product identifier of the cued SAR scene. Filled by the matcher. |
sar_scene_acquired_at | SAR acquisition timestamp. |
pixel_match_lat / pixel_match_lon | CFAR pixel coordinates that satisfied the match. |
pixel_detection_count | Number of CFAR detections in the matching cluster. |
match_confidence | 0 to 1 confidence score for the AIS↔SAR match. |
status | predicted / confirmed / dark_transit / missed / cancelled. |
confirmed_at | Timestamp the row was promoted out of predicted. |
confirmed_via | ais_reappear / sar_pixel / combined. |
How it’s computed
Five jobs make up the AIS-SAR fusion pipeline. Each one is an independently scheduled cron with its own ingestion-log trail.
| Cron | Schedule | Role |
|---|
populate-predicted-transits-hourly | 5 * * * * | Tip — call predict_chokepoint_eta for every active vessel and upsert predictions. |
monitor-corridor-sar | 15 */6 * * * | Cue — pull Sentinel-1 scenes that intersect upcoming predicted windows. |
reconcile-vessel-transits | 20,50 * * * * | Match — promote predicted rows to confirmed / missed, attach SAR scenes. |
verify-dark-fleet-sar-daily | 0 8 * * * | Daily SAR sweep over high/critical dark events (SAR detections). |
refresh-mv-chokepoint-transits-weekly | 30 3 * * * | Refresh the weekly reporting matview. |
The predictor: predict_chokepoint_eta
predict_chokepoint_eta(p_imo TEXT, p_lookback_hours INTEGER DEFAULT 6) is a STABLE Postgres function that does straight-line dead-reckoning from a vessel’s latest live AIS position toward each chokepoint polygon.
A vessel is eligible for prediction only when its latest position satisfies all of:
timestamp is within p_lookback_hours of now()
speed >= 1.0 knots (drifting / stationary vessels are rejected)
course IS NOT NULL and between 0 and 360 degrees
latitude and longitude are present
The function projects the vessel hourly for 72 hours along its current course at its current speed (using ST_Project on a geography type), builds a track line, and returns one row per chokepoint the track intersects. The ETA is interpolated from the track-line fraction of the first hit; uncertainty is 15% of hours-ahead with a 1-hour floor.
If the vessel is dark, drifting, or has no heading, the function returns no rows — the predictor silently skips it.
-- All chokepoints the vessel is projected to enter in the next 72h.
select chokepoint_id, predicted_eta, eta_uncertainty_hours
from predict_chokepoint_eta('9876543', 6);
The cuer: monitor-corridor-sar
Every 6 hours the monitor-corridor-sar Edge Function reads pending predicted rows whose predicted_eta falls inside the next scan window, queries the Copernicus Data Space Sentinel-1 catalog for IW_GRDH_1S scenes intersecting each chokepoint corridor, and writes back any matched sar_scene_id / sar_scene_acquired_at onto the prediction row. CFAR pixel detection on the matched scene populates pixel_detection_count, pixel_match_lat, pixel_match_lon, and match_confidence.
This source is registered in data_source_catalog as monitor-corridor-sar and shows on the data-source dashboard alongside aishub and fred.
The matcher / reconciler
Twice per hour, reconcile-vessel-transits walks the predicted rows:
- A vessel reappearing inside the corridor’s
exit_zone after its predicted_eta is promoted to confirmed (confirmed_via = 'ais_reappear').
- A SAR pixel match within the corridor and inside the ETA uncertainty band is promoted to
confirmed (confirmed_via = 'sar_pixel', or combined if both signals agree).
- A row whose
predicted_eta + 96h has elapsed with no reappearance and no SAR match is promoted to missed.
- A vessel that exits the predicted approach without entering the corridor is
cancelled.
A separate path inserts dark_transit rows directly when monitor-corridor-sar finds a CFAR cluster inside a corridor that doesn’t match any predicted row.
Reading transits
Two access paths are exposed and both are usable from PostgREST or SQL.
get_chokepoint_transits RPC
Cockpit-friendly RPC for a single chokepoint over a time window. Defaults to the last 14 days. SECURITY DEFINER, granted to authenticated, anon, and service_role.
select id, predicted_imo, predicted_eta, status,
match_confidence, pixel_detection_count,
sar_scene_id, sar_scene_acquired_at,
confirmed_at, confirmed_via
from get_chokepoint_transits('bab-el-mandeb', now() - interval '7 days');
curl -X POST 'https://<project>.supabase.co/rest/v1/rpc/get_chokepoint_transits' \
-H "apikey: $ANON_KEY" \
-H "Content-Type: application/json" \
-d '{"p_chokepoint_id":"hormuz","p_since":"2026-04-01T00:00:00Z"}'
mv_chokepoint_transits_weekly matview
Weekly aggregates per chokepoint per status, with median ETA accuracy. Refreshed nightly at 03:30 UTC (refresh-mv-chokepoint-transits-weekly). Use this for trend dashboards instead of scanning vessel_transits directly.
| Column | Description |
|---|
chokepoint_id | Corridor key. |
week_start | Monday-anchored week start. |
status | predicted / confirmed / dark_transit / missed / cancelled. |
n | Row count for that bucket. |
median_eta_delta_hours | Median (confirmed_at − predicted_eta) in hours, only over confirmed rows. |
sample_ids | Up to N most-recent transit IDs in that bucket, for drilldown. |
-- Confirmation rate per corridor for the last 8 weeks.
select chokepoint_id, week_start,
sum(n) filter (where status = 'confirmed')::float
/ nullif(sum(n) filter (where status in ('confirmed','missed')), 0) as confirm_rate,
sum(n) filter (where status = 'dark_transit') as dark_count
from mv_chokepoint_transits_weekly
where week_start >= now() - interval '56 days'
group by chokepoint_id, week_start
order by week_start desc, chokepoint_id;
SAR detections storage
verify-dark-fleet-sar-daily runs every morning at 08:00 UTC over recent high/critical dark_events. When the Copernicus catalog returns a Sentinel-1 scene over the dark-event location and the CFAR detector returns at least one ship pixel, one row lands in public.sar_detections, FK’d back to the originating dark_event.
| Field | Description |
|---|
dark_event_id | FK to dark_events. Cascades on delete. |
vessel_imo | IMO of the dark-event vessel. |
scene_id / scene_acquired_at | Copernicus product id and acquisition time. |
detection_lat / detection_lon | Pixel-level CFAR detection coordinates. |
distance_to_expected_nm | Distance from the AIS-expected position to the SAR pixel. |
cfar_score / mean_confidence / max_confidence | CFAR scoring outputs. |
ship_size_estimate | Pixel-derived size estimate. |
pixel_detection_count | Number of pixels in the matched cluster. |
detection_method | Versioned method tag. Currently sentinel-1-cfar-v1. |
This source is registered in data_source_catalog as verify-dark-fleet-sar.
Following the risk_tier recalibration and the Yager fusion fix, the daily 08:00 UTC sweep now writes detections to sar_detections over the live high+critical queue. Events with no SAR observation (low/medium coverage potential, missing Copernicus credentials, or a detectShips failure) keep their pre-fusion AIS risk_score rather than being pulled toward Lawful.
Operational notes
- All five crons write one row per run to
ingestion_logs. Filter by source = 'monitor_corridor_sar', populate_predicted_transits, reconcile_vessel_transits, verify_dark_fleet_sar, or refresh_mv_chokepoint_transits_weekly to inspect run history.
- The unique partial index
vessel_transits_predicted_uniq (predicted_imo, chokepoint_id, predicted_eta) makes the predictor idempotent within a single ETA bucket. Re-running the hourly cron is safe.
- The unique partial index
vessel_transits_dark_uniq (sar_scene_id, pixel_match_lat, pixel_match_lon) WHERE status = 'dark_transit' deduplicates dark-transit inserts across SAR scene re-ingestion.
chokepoints and vessel_transits are RLS-protected with read-all policies; writes are service-role only.
- Both new SAR sources use the free Copernicus tier — no credentials required for catalog metadata, with
COPERNICUS_CLIENT_ID / COPERNICUS_CLIENT_SECRET enabling pixel-level CFAR.