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.
The chokepoints
Four corridor polygons ship as seed data inpublic.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. |
SELECT * FROM public.chokepoints.
The transit lifecycle
Every predicted or observed transit is one row inpublic.vessel_transits. The row moves through a strict status lifecycle:
dark_transitrows must have asar_scene_idandpixel_match_lat/pixel_match_lon, and nopredicted_imo(the vessel was not predicted by AIS).- All other statuses (
predicted,confirmed,missed,cancelled) must have apredicted_imoandpredicted_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:
timestampis withinp_lookback_hoursofnow()speed >= 1.0knots (drifting / stationary vessels are rejected)course IS NOT NULLand between 0 and 360 degreeslatitudeandlongitudeare present
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.
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_zoneafter itspredicted_etais promoted toconfirmed(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', orcombinedif both signals agree). - A row whose
predicted_eta + 96hhas elapsed with no reappearance and no SAR match is promoted tomissed. - A vessel that exits the predicted approach without entering the corridor is
cancelled.
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.
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. |
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. |
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 bysource = 'monitor_corridor_sar',populate_predicted_transits,reconcile_vessel_transits,verify_dark_fleet_sar, orrefresh_mv_chokepoint_transits_weeklyto 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. chokepointsandvessel_transitsare 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_SECRETenabling pixel-level CFAR.