Independent maritime OSINT dashboard for industrial krill, whaling, and IUU fishing fleets.
Disclaimer. Personal project. Built by a supporter of the Captain Paul Watson Foundation and Sea Shepherd, not affiliated with either organisation. The donate CTA routes to CPWF.
Four campaigns share one pipeline.
- Antarctic krill in the CCAMLR Convention Area. Aker BioMarine and the Chinese fleet.
- Commercial whaling. Kangei Maru in Japan, Hvalur 8 and 9 in Iceland.
- Galápagos. Chinese squid jiggers massed at the reserve boundary.
- West Africa. Chinese and Russian super-trawlers in the Senegal, Mauritania, and Guinea EEZs.
The tool ingests public AIS, Global Fishing Watch encounters and gaps, and official registries. It flags vessels that cross protected-area boundaries, runs a six-signal transhipment scorer over rolling encounters, and publishes the result on a French and English Next.js dashboard.
Three files carry most of the technical signal:
calibration.md: frozen scoring weights v0.4.2 with per-signal justification against CM 10-09 / 10-04 / 10-06 and a validation case set.INVESTIGATOR_PLAYBOOK.md: three worked walkthroughs for CPWF analysts (Monday triage, encounter deep dive, dossier prep).python main.py --mode transhipment-review --since 7d: generates the weekly investigator HTML report. Self-contained, send by email, print to A4, archive next to an investigation.
git clone https://github.com/breaching/krill-watch.git
cd krill-watch
# Python deps (FastAPI, DuckDB, shapely)
pip install -r requirements-core.txt
# Node deps (root launcher and web/)
npm run setup
# Boot both: API on :8000, UI on :3000
npm run devOpen http://localhost:3000/fr.
Optional environment variables. The UI degrades to seeded data without them.
| Variable | Purpose |
|---|---|
GFW_API_KEY |
Global Fishing Watch v3. Enables encounter and gap enrichment. |
AISSTREAM_API_KEY |
AISStream.io WebSocket. Enables live AIS. |
DISCORD_WEBHOOK_URL |
Discord alert dispatch. |
SLACK_WEBHOOK_URL |
Slack alert dispatch. |
GFW_REFRESH_HOURS, DISABLE_WORKERS, and LOG_LEVEL are documented in server/workers.py.
API only: npm run dev:api. UI against an existing API: npm run dev:web-only.
Backend. FastAPI plus DuckDB on Python 3.11. One file at data/krill_watch.duckdb holds positions, zone transitions, vessel status, auto-incidents, and contributions. Background workers cover AISStream subscription and GFW profile refresh.
Key files: server/app.py, server/store.py, server/workers.py.
Frontend. Next.js 15 App Router with TypeScript. MapLibre GL for maps, dark-matter basemap. Server components fetch the API on page load. Client components subscribe to /api/stream for SSE updates.
Key files: web/app/[locale]/page.tsx, web/lib/krill-watch.ts.
Detector. src/transhipment_detector.py and src/reefer_watch.py. Calibration v0.4.2. Weights and validation cases in calibration.md.
Local dev needs no external service. Vessel registry, CCAMLR zone GeoJSON, and watchlists ship as static JSON in config/.
krill-watch/
├── server/ FastAPI app, DuckDB store, AIS and GFW workers
├── src/ Shared library: detector, monitor, profiler, notifier
├── web/ Next.js investigator UI
├── config/ Vessel registry, zone GeoJSONs, watchlists
├── tests/ pytest suite
├── scripts/ Utilities (zone builder, fleet verifier, seed)
├── docs/ Screenshots, audit notes, integration plans
└── data/ Runtime DuckDB and cache (gitignored)
The weekly investigator deliverable. A multi-signal review of suspect at-sea transhipments inside the CCAMLR Convention Area.
python main.py --mode transhipment-review --since 7d --out output/transhipments_$(date +%Y-W%V).htmlThe HTML report is self-contained. Send it as an email attachment, print to A4, or archive it next to an investigation. The /transhipments page gives the same view interactively, with date and tier filters.
Scoring is investigative, not evidentiary. A score of 0.78 is a lead, not a verdict. Every flagged encounter needs independent verification (photographs, manifests, witness statements) before any public action. Scoring follows CCAMLR Conservation Measures 10-04, 10-06, 10-07, and 10-09.
Tier thresholds. Score at or above 0.50 is FLAGGED. Score at or above 0.75 is CRITICAL. Maximum reachable: 0.90.
Signal weights (v0.4.2):
| Signal | Weight | Reference |
|---|---|---|
| Reefer not on CM 10-09 authorized list | 0.25 | CM 10-09 |
| AIS gap during encounter | 0.20 | CM 10-04 |
| IUU list or sanctions hit | 0.20 | CM 10-06/07, OFAC SDN |
| Outside authorized transhipment zone | 0.15 | CM 10-09 spatial |
| Flag of Convenience | 0.05 | ITF FOC list |
| Duration over 6 hours | 0.05 | Operational context |
Weight justifications, validation cases, and the re-calibration procedure live in calibration.md.
Live sources powering the scorer.
- OpenSanctions (
src/sanctions_client.py). Queried at vessel-build time for SDN, EU, UK, UN, FOC, and NCP-IUU matches. The frozenconfig/ofac_fishing_sanctions.jsonis an offline fallback when the OpenSanctions API is unreachable (air-gapped runs, weekly digest jobs without network). - GFW v3 events regions block (
src/osint_profiler._summarise_event_regions). Classifies fishing events against MPA, no-take, FAO, and RFMO geofences. The simplified bboxes inconfig/ccamlr_areas.geojsonare kept as a fallback for AIS-gap zone classification only. - Marine Regions EEZ, GMR, and IWC sanctuary GeoJSONs in
config/maritime_zones/. Covers Galápagos, Senegal, Mauritania, Guinea, and the IWC Southern Ocean Whale Sanctuary.
Known gap. config/ccamlr_transhipment_authorized.json is empty because the CM 10-09 authorized-reefer list lives behind the CCAMLR member-state portal. config/reefer_watchlist.json covers the Aker BioMarine declared reefers used in our test cases. When an encounter cannot be matched against either list, the report shows a warning banner instead of asserting unauthorized status.
INVESTIGATOR_PLAYBOOK.md covers weekly fleet triage, encounter deep dives, and dossier preparation for external partners.
| Feature | Status | Notes |
|---|---|---|
| Live AIS triage dashboard | Shipped | Triage, fleet map, vessel detail, transhipments UI |
| Transhipment detector | Shipped | v0.4.2, six signals, 107 tests, weekly HTML report |
| Weekly HTML review report | Shipped | python main.py --mode transhipment-review |
Investigator UI (web/) |
Shipped | Home triage, /vessels, /vessels/{slug}, /transhipments |
| Datasets compilation | Live and curated | OpenSanctions live, GFW-native MPA/RFMO regions, Marine Regions EEZs. CM 10-09 authorized reefer list still requires CCAMLR member-state access. |
| RPi / offline vessel mode | Deferred | Architecture documented, hardware integration not implemented |
Open an issue or pull request. Follow Conventional Commits (feat:, fix:, docs:, chore:). Before submitting: npx tsc --noEmit in web/ and python -m pytest tests/ must pass. The CI gate enforces both.
Refresh README screenshots when the UI changes. With npm run dev running:
cd web
node scripts/capture-readme-screenshots.mjsOutput lands in docs/screenshots/. Commit the regenerated PNGs in the same PR. The script is documented in docs/SCREENSHOTS.md.
Personal, independent project. Built by a supporter of CPWF and Sea Shepherd. Not officially affiliated with either organisation.
