feat: open tracker to non-krill CPWF missions — whaling first (Kangei Maru, Hvalur 8/9) - #15
Merged
Merged
Conversation
Adds a `mission` field to the vessel model and onboards 3 commercial-whaling
targets as a sibling campaign. Demonstrates that the underlying pipeline
(zones, AIS gaps, severity, events) is generic and not krill-specific.
Backend
- src/vessel_db.py: new VesselMission literal ("krill" | "whaling" | "iuu_fishing"),
added to Vessel model with default "krill" so existing entries are unchanged
- server/app.py: vessel summary now exposes the mission field
- config/krill_fleet.json: adds Kangei Maru (Japanese factory whaler, replaces
Nisshin Maru, launched 2024), Hvalur 8 and Hvalur 9 (Icelandic fin-whaling
catchers). All three have IMO/MMSI marked PENDING — operator must populate
from MarineTraffic / Equasis before going live
- config/ccamlr_areas.geojson: adds three whale-sanctuary polygons (IWC
Schedule 7) — Southern Ocean Sanctuary split E/W to avoid the antimeridian,
plus the Indian Ocean Sanctuary. Tagged category="whale_sanctuary" so the
existing severity rules (ais_gap_in_mpa) automatically fire CRITICAL when
a whaler enters one and goes dark
Web
- web/data/vessels.ts: adds the three whaling vessels with bilingual notes
- web/app/[locale]/vessels/page.tsx: groups the catalogue by mission with a
coloured heading per mission (krill = accent, whaling = danger). Whaling
cards get a danger-tinted border + an explicit mission badge so the user
sees at a glance these are not krill-fishery vessels
- web/lib/i18n.ts: adds mission labels in EN and FR; renames the page title
from "Antarctic krill fleet" to "Tracked vessels" since the catalogue now
spans multiple campaigns
Tests
- tests/test_api.py: locks in that /api/vessels exposes the mission field
and includes the whaling mission with Kangei Maru in it (110 passed)
Verification
- python -m pytest tests/ -q → 110 passed
- python -m ruff check → All checks passed
- cd web && npx tsc --noEmit → exit 0
- cd web && npx next build → 6 new SSG pages generated for whaling vessels
Why it's small on purpose
- We deliberately did NOT abstract a "campaign" type yet. With one new mission
in production, the duplication cost is zero and the abstraction shape is
unclear. Wait until the second non-krill mission (Galápagos / West Africa
Chinese fleet) before refactoring config/loaders into per-campaign units
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced Apr 28, 2026
breaching
added a commit
that referenced
this pull request
Apr 28, 2026
…-trawlers (#17) Adds two new IUU-fishing campaigns alongside krill (mission=krill) and whaling (mission=whaling). The detector pipeline (zones, AIS gaps, severity, events) is fully reused — only data is added. Galápagos campaign (mission=iuu_fishing) - 3 representative Chinese squid jiggers / reefers in fleet config: Fu Yuan Yu Leng 999, Long Da 001, Hai Feng 718 - 2 zone polygons added to ccamlr_areas.geojson: GMR-GALAPAGOS — Galápagos Marine Reserve incl. Hermandad (2022 expansion) EEZ-GALAPAGOS-BUFFER — Ecuador 200-nm EEZ where the squid-jigger fleet historically congregates each Aug-Nov - Headline signal: incursion into GMR (foreign-flag commercial fishing prohibited there) or AIS gap inside the buffer West Africa campaign (mission=iuu_fishing) - 3 representative DWF vessels in fleet config: Lurong Yuanyu 956 (CN), Lurong Yuanyu 988 (CN), Saltic Atlas (RU) - 3 zone polygons added (simplified bounding boxes): EEZ-SENEGAL, EEZ-MAURITANIA, EEZ-GUINEA - Headline signal: AIS gap inside any of these EEZs (vessel went dark while presumably trawling) Web (`web/data/vessels.ts`) - All 6 new vessels added to the static catalogue with bilingual notes describing the campaign context (Pingtan reefer 2017 Galápagos arrest; 300+ vessel 2020 stand-off; EJF/Greenpeace West Africa documentation) - They surface automatically in the existing /vessels mission-grouped UI (PR #15) — krill / whaling / IUU fishing sections All vessels have IMO and MMSI marked PENDING — operator must populate from GFW Carrier Vessel Portal / Equasis before deployment. The polygon boundaries are simplified bounding boxes that err conservatively (over- include slightly); operator should refine with official EEZ data (e.g. https://www.marineregions.org/) before deployment. Verification - python -m pytest tests/ -q → 110 passed - python -m ruff check → All checks passed - cd web && npx tsc --noEmit → exit 0 What's deliberately NOT done - No "campaign" abstraction yet. With 4 missions still hanging off krill_fleet.json + ccamlr_areas.geojson, the duplication cost remains near zero. Right time for the refactor is when the 5th mission lands. Co-authored-by: breaching <devsca@protonmail.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
breaching
added a commit
that referenced
this pull request
Apr 28, 2026
…19) INVESTIGATOR_PLAYBOOK.md - New §4 "Multi-mission monitoring" — table of 4 missions, headline signals, tracked zones, per-mission webhook routing, smoke-test recipe via the /api/_dev/test-alert endpoint - New §5 "Weekly operational rhythm" — describes the CI cron + production cron + Monday-morning Discord ping flow - "What this tool is NOT" updated to clarify the live-alerting vs offline- encounter latency distinction, and to flag IUU vessel IMOs as PENDING alongside the existing OFAC/CM-10-09 stub callouts README.md - Subtitle pivoted from "krill fishing fleet" to "CPWF-relevant fleets — Antarctic krill, commercial whaling, and IUU fishing" - New top bullet describing the 4 missions; added "Live alerting" + cron bullets so the README accurately advertises what shipped in PRs #15-18 .env.example - Generic + per-mission webhook URLs documented - WEB_BASE_URL / WEB_DEFAULT_LOCALE for deep links - Production weekly-report runner vars (KRILL_DB_PATH, KRILL_REPORT_OUT_DIR, KRILL_REPORT_PUBLIC_URL, DISCORD_WEBHOOK_URL_REPORTS) - KRILL_ALLOW_DEV_ENDPOINTS for the dev test-alert endpoint Co-authored-by: breaching <devsca@protonmail.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
breaching
pushed a commit
that referenced
this pull request
May 5, 2026
This commit lands the work documented in PROD_READINESS.md and docs/AUDIT_NOTES.md as deferred at the end of the 2026-04-29 audit batch, plus the audit-P19 'Pavillon' i18n leak. Audit bug #14 — FR translations for curated incidents * config/incidents.json: 4 entries gained title_fr/summary_fr * src/incidents.py + web/lib/krill-watch.ts: optional locale fields * web/components/vessels/IncidentList.tsx: prefers locale variant, drops the 'Source · EN' fallback badge when a translation exists PROD #5 — backup status surfaced on /api/health * scripts/backup_duckdb.py writes data/.backup_status.json after a successful upload (timestamp, bucket, key, size, retention counts) * server/app.py /api/health surfaces a `backup` block with a derived `age_seconds` so the frontend can render 'backup ran X hours ago' without re-parsing the timestamp * tests/test_api.py: 2 new cases lock the contract (none + present) PROD #10 — k6 load-test scaffold * scripts/loadtest.k6.js covers /api/health, /api/vessels, /api/positions/live, /api/zones, /api/incidents with three profiles (smoke / press-launch / sustained), p(95) thresholds PROD #15 — Lighthouse CI scaffold * web/lighthouserc.js: soft-asserted budgets (perf 0.6 warn, a11y 0.85 error, SEO 0.9 error) against the production-built bundle * .github/workflows/ci.yml: new lighthouse job, continue-on-error Audit bug #19 — Pavillon i18n leak * vesselDetail.flagLabel added to FR + EN dicts * vessel detail header now reads 'Pavillon: NOR' on /fr instead of leaking the EN literal Verification: 210 pytest pass (+1 health/backup case); next tsc clean; 12 Playwright e2e specs pass; live API serves the new fields end-to-end. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
breaching
pushed a commit
that referenced
this pull request
May 5, 2026
Adds the 6 specs called out in PROD_READINESS #3 + #15: - web/e2e/live-loads.spec.ts: open /en/live, assert >=1 vessel dot, banner shows fleet count. - web/e2e/vessels-loads.spec.ts: /en/vessels mini-map renders with dots, click first dot -> popup with severity + dossier link. - web/e2e/vessel-detail.spec.ts: /en/vessels/antarctic_endurance, history map height >100px (regression for the inset-0 collapse), trail SVG present, scrubber controls visible. - web/e2e/firms-toggle.spec.ts: toggle FIRMS layer, assert /api/firms/recent fires + label updates. - web/e2e/operator-background.spec.ts: vessel detail page, assert "Aker BioMarine" + "Norway" appear (Wikidata cascade integration). - web/e2e/a11y.spec.ts: @axe-core/playwright across 7 routes (home, about, vessels list, campaigns hub, privacy, terms, legal), WCAG 2.1 AA tags. Fails on serious/critical only — minor/moderate tolerated so a single tag-soup edge case doesn't block deploys. - web/playwright.config.ts: chromium project, fullyParallel, CI-aware retries/workers/reporter. We deliberately don't use Playwright's webServer option — orchestration is owned by the root `npm run dev`. - .gitignore: drop web/test-results/, web/playwright-report/, web/playwright/.cache/. Closes prod-P0 #3 (5 specs) and the axe-core half of P2 #15. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
breaching
pushed a commit
that referenced
this pull request
May 5, 2026
Vessel-detail and live-tracker UX improvements that were partly shipped
on disk but never committed:
- web/components/map/LiveTrackerMap.tsx (+541 lines):
* home-port markers (subdued grey diamond, distinct from live AIS dots)
so silent whaling/IUU vessels appear on the map with a clear
"no AIS — last known port" marker. Uses position_source flag.
* NASA FIRMS thermal hot-spots layer (VIIRS NOAA-20) — pulsing red
markers for fishing-vessel "lights at sea" detections.
* NASA GIBS true-colour imagery overlay (VIIRS daily mosaic, ~3 h
latency) with opacity slider so the journalist can see clouds /
sea-ice edge while keeping dot/zone overlays readable. Date defaults
to UTC-yesterday since today's tile is often missing for first 6 h.
* clickAction="popup" mode for /vessels (no side drawer there).
* onAmbientCount + onFirmsCount callbacks so parent UIs can show
"(0)" next to a toggle when the fetch found nothing.
* Extended ALPHA3->ALPHA2 flag map (ISL, FRO, CAN, AUS, etc.).
- web/components/map/VesselsMapClient.tsx: rewire to popup mode +
ambient count.
- web/components/vessels/{Sanctions,OperatorBackground}Section.tsx:
optional `prefetched` prop so the parent vessel page can launch the
three vessel-detail fetches via Promise.all and pass results in,
skipping serial round-trips. Backwards-compatible (still self-fetches
when the prop is undefined). OperatorBackground also migrated to
next/image with explicit dims (no CLS) for Wikimedia thumbnails.
- web/app/[locale]/vessels/[slug]/VesselHistoryMap.tsx: small fix.
- web/app/[locale]/vessels/page.tsx (+34): MISSION_ORDER renders 4
sections (krill / whaling / Galápagos / West Africa) so the Galápagos
+ West Africa missions stop being fused under "IUU FISHING".
- web/app/[locale]/live/{LivePageClient,page.tsx}: layer toggles
reflect the new FIRMS/NASA opacity URL state (?firms=1&nasa=1&op=…).
- web/app/[locale]/campagnes/page.tsx: copy refresh for disaffiliation.
- web/components/live/{FleetSidebar,LiveMobileLayout,MobileFiltersSheet}:
expose the FIRMS / NASA toggles in mobile + desktop sheets.
- web/lib/live-url-state.ts: new firms/nasa/op state knobs.
- web/components/{Acronym,Footer,SupportCTA}: small copy/style tweaks.
- web/app/globals.css: link-cascade fix wrapped in @layer base so
Tailwind utilities still win the cascade (closes a contrast bug
caught by axe-core on the support-CTA button).
Closes prod-P2 #16 (image opt), #17 (parallel SSR), audit #9
(Galápagos/West-Africa fused), #15 (LIVE: 0 banner backed by source
data), and most of the live-tracker polish from sprint-3.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
breaching
pushed a commit
that referenced
this pull request
May 7, 2026
The desktop /live route renders LiveStatsOverlay over the map, which swaps the green LIVE pill for a yellow "AIS FEED QUIET" warning when the freshest position is older than 6 h (audit bug #15 resolution). The mobile layout was missing the same overlay — so a journalist on a phone hit "Live: 0" with no explanation while desktop visitors got the quiet-feed context. Importing LiveStatsOverlay into LiveMobileLayout and dropping it inside the absolute map container (under the top bar). Same component, same threshold, same i18n strings. 12/12 Playwright + a11y still green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
breaching
pushed a commit
that referenced
this pull request
May 8, 2026
The home page card showed a green pulsing 'LIVE' pill plus the relative 'Last position' age, but never warned that the freshest AIS ping was older than 6 h. /live already does this via LiveStatsOverlay; aligning the home banner closes the parallel of audit #15. Same 6 h threshold as LiveStatsOverlay so the home and the tracker can't disagree by more than 30 s of clock skew. Yellow 'AIS FEED QUIET' / 'FLUX AIS CALME' pill, yellow border and gradient. Verified live with the current ~10 d stale ping.
breaching
pushed a commit
that referenced
this pull request
May 8, 2026
This commit lands the work documented in PROD_READINESS.md and docs/AUDIT_NOTES.md as deferred at the end of the 2026-04-29 audit batch, plus the audit-P19 'Pavillon' i18n leak. Audit bug #14 — FR translations for curated incidents * config/incidents.json: 4 entries gained title_fr/summary_fr * src/incidents.py + web/lib/krill-watch.ts: optional locale fields * web/components/vessels/IncidentList.tsx: prefers locale variant, drops the 'Source · EN' fallback badge when a translation exists PROD #5 — backup status surfaced on /api/health * scripts/backup_duckdb.py writes data/.backup_status.json after a successful upload (timestamp, bucket, key, size, retention counts) * server/app.py /api/health surfaces a `backup` block with a derived `age_seconds` so the frontend can render 'backup ran X hours ago' without re-parsing the timestamp * tests/test_api.py: 2 new cases lock the contract (none + present) PROD #10 — k6 load-test scaffold * scripts/loadtest.k6.js covers /api/health, /api/vessels, /api/positions/live, /api/zones, /api/incidents with three profiles (smoke / press-launch / sustained), p(95) thresholds PROD #15 — Lighthouse CI scaffold * web/lighthouserc.js: soft-asserted budgets (perf 0.6 warn, a11y 0.85 error, SEO 0.9 error) against the production-built bundle * .github/workflows/ci.yml: new lighthouse job, continue-on-error Audit bug #19 — Pavillon i18n leak * vesselDetail.flagLabel added to FR + EN dicts * vessel detail header now reads 'Pavillon: NOR' on /fr instead of leaking the EN literal Verification: 210 pytest pass (+1 health/backup case); next tsc clean; 12 Playwright e2e specs pass; live API serves the new fields end-to-end.
breaching
pushed a commit
that referenced
this pull request
May 8, 2026
Adds the 6 specs called out in PROD_READINESS #3 + #15: - web/e2e/live-loads.spec.ts: open /en/live, assert >=1 vessel dot, banner shows fleet count. - web/e2e/vessels-loads.spec.ts: /en/vessels mini-map renders with dots, click first dot -> popup with severity + dossier link. - web/e2e/vessel-detail.spec.ts: /en/vessels/antarctic_endurance, history map height >100px (regression for the inset-0 collapse), trail SVG present, scrubber controls visible. - web/e2e/firms-toggle.spec.ts: toggle FIRMS layer, assert /api/firms/recent fires + label updates. - web/e2e/operator-background.spec.ts: vessel detail page, assert "Aker BioMarine" + "Norway" appear (Wikidata cascade integration). - web/e2e/a11y.spec.ts: @axe-core/playwright across 7 routes (home, about, vessels list, campaigns hub, privacy, terms, legal), WCAG 2.1 AA tags. Fails on serious/critical only — minor/moderate tolerated so a single tag-soup edge case doesn't block deploys. - web/playwright.config.ts: chromium project, fullyParallel, CI-aware retries/workers/reporter. We deliberately don't use Playwright's webServer option — orchestration is owned by the root `npm run dev`. - .gitignore: drop web/test-results/, web/playwright-report/, web/playwright/.cache/. Closes prod-P0 #3 (5 specs) and the axe-core half of P2 #15.
breaching
pushed a commit
that referenced
this pull request
May 8, 2026
Vessel-detail and live-tracker UX improvements that were partly shipped
on disk but never committed:
- web/components/map/LiveTrackerMap.tsx (+541 lines):
* home-port markers (subdued grey diamond, distinct from live AIS dots)
so silent whaling/IUU vessels appear on the map with a clear
"no AIS — last known port" marker. Uses position_source flag.
* NASA FIRMS thermal hot-spots layer (VIIRS NOAA-20) — pulsing red
markers for fishing-vessel "lights at sea" detections.
* NASA GIBS true-colour imagery overlay (VIIRS daily mosaic, ~3 h
latency) with opacity slider so the journalist can see clouds /
sea-ice edge while keeping dot/zone overlays readable. Date defaults
to UTC-yesterday since today's tile is often missing for first 6 h.
* clickAction="popup" mode for /vessels (no side drawer there).
* onAmbientCount + onFirmsCount callbacks so parent UIs can show
"(0)" next to a toggle when the fetch found nothing.
* Extended ALPHA3->ALPHA2 flag map (ISL, FRO, CAN, AUS, etc.).
- web/components/map/VesselsMapClient.tsx: rewire to popup mode +
ambient count.
- web/components/vessels/{Sanctions,OperatorBackground}Section.tsx:
optional `prefetched` prop so the parent vessel page can launch the
three vessel-detail fetches via Promise.all and pass results in,
skipping serial round-trips. Backwards-compatible (still self-fetches
when the prop is undefined). OperatorBackground also migrated to
next/image with explicit dims (no CLS) for Wikimedia thumbnails.
- web/app/[locale]/vessels/[slug]/VesselHistoryMap.tsx: small fix.
- web/app/[locale]/vessels/page.tsx (+34): MISSION_ORDER renders 4
sections (krill / whaling / Galápagos / West Africa) so the Galápagos
+ West Africa missions stop being fused under "IUU FISHING".
- web/app/[locale]/live/{LivePageClient,page.tsx}: layer toggles
reflect the new FIRMS/NASA opacity URL state (?firms=1&nasa=1&op=…).
- web/app/[locale]/campagnes/page.tsx: copy refresh for disaffiliation.
- web/components/live/{FleetSidebar,LiveMobileLayout,MobileFiltersSheet}:
expose the FIRMS / NASA toggles in mobile + desktop sheets.
- web/lib/live-url-state.ts: new firms/nasa/op state knobs.
- web/components/{Acronym,Footer,SupportCTA}: small copy/style tweaks.
- web/app/globals.css: link-cascade fix wrapped in @layer base so
Tailwind utilities still win the cascade (closes a contrast bug
caught by axe-core on the support-CTA button).
Closes prod-P2 #16 (image opt), #17 (parallel SSR), audit #9
(Galápagos/West-Africa fused), #15 (LIVE: 0 banner backed by source
data), and most of the live-tracker polish from sprint-3.
breaching
pushed a commit
that referenced
this pull request
May 8, 2026
The desktop /live route renders LiveStatsOverlay over the map, which swaps the green LIVE pill for a yellow "AIS FEED QUIET" warning when the freshest position is older than 6 h (audit bug #15 resolution). The mobile layout was missing the same overlay — so a journalist on a phone hit "Live: 0" with no explanation while desktop visitors got the quiet-feed context. Importing LiveStatsOverlay into LiveMobileLayout and dropping it inside the absolute map container (under the top bar). Same component, same threshold, same i18n strings. 12/12 Playwright + a11y still green.
breaching
pushed a commit
that referenced
this pull request
May 8, 2026
The home page card showed a green pulsing 'LIVE' pill plus the relative 'Last position' age, but never warned that the freshest AIS ping was older than 6 h. /live already does this via LiveStatsOverlay; aligning the home banner closes the parallel of audit #15. Same 6 h threshold as LiveStatsOverlay so the home and the tracker can't disagree by more than 30 s of clock skew. Yellow 'AIS FEED QUIET' / 'FLUX AIS CALME' pill, yellow border and gradient. Verified live with the current ~10 d stale ping.
breaching
added a commit
that referenced
this pull request
May 11, 2026
Adds a `mission` field to the vessel model and onboards 3 commercial-whaling
targets as a sibling campaign. Demonstrates that the underlying pipeline
(zones, AIS gaps, severity, events) is generic and not krill-specific.
Backend
- src/vessel_db.py: new VesselMission literal ("krill" | "whaling" | "iuu_fishing"),
added to Vessel model with default "krill" so existing entries are unchanged
- server/app.py: vessel summary now exposes the mission field
- config/krill_fleet.json: adds Kangei Maru (Japanese factory whaler, replaces
Nisshin Maru, launched 2024), Hvalur 8 and Hvalur 9 (Icelandic fin-whaling
catchers). All three have IMO/MMSI marked PENDING — operator must populate
from MarineTraffic / Equasis before going live
- config/ccamlr_areas.geojson: adds three whale-sanctuary polygons (IWC
Schedule 7) — Southern Ocean Sanctuary split E/W to avoid the antimeridian,
plus the Indian Ocean Sanctuary. Tagged category="whale_sanctuary" so the
existing severity rules (ais_gap_in_mpa) automatically fire CRITICAL when
a whaler enters one and goes dark
Web
- web/data/vessels.ts: adds the three whaling vessels with bilingual notes
- web/app/[locale]/vessels/page.tsx: groups the catalogue by mission with a
coloured heading per mission (krill = accent, whaling = danger). Whaling
cards get a danger-tinted border + an explicit mission badge so the user
sees at a glance these are not krill-fishery vessels
- web/lib/i18n.ts: adds mission labels in EN and FR; renames the page title
from "Antarctic krill fleet" to "Tracked vessels" since the catalogue now
spans multiple campaigns
Tests
- tests/test_api.py: locks in that /api/vessels exposes the mission field
and includes the whaling mission with Kangei Maru in it (110 passed)
Verification
- python -m pytest tests/ -q → 110 passed
- python -m ruff check → All checks passed
- cd web && npx tsc --noEmit → exit 0
- cd web && npx next build → 6 new SSG pages generated for whaling vessels
Why it's small on purpose
- We deliberately did NOT abstract a "campaign" type yet. With one new mission
in production, the duplication cost is zero and the abstraction shape is
unclear. Wait until the second non-krill mission (Galápagos / West Africa
Chinese fleet) before refactoring config/loaders into per-campaign units
Co-authored-by: breaching <devsca@protonmail.com>
breaching
added a commit
that referenced
this pull request
May 11, 2026
…-trawlers (#17) Adds two new IUU-fishing campaigns alongside krill (mission=krill) and whaling (mission=whaling). The detector pipeline (zones, AIS gaps, severity, events) is fully reused — only data is added. Galápagos campaign (mission=iuu_fishing) - 3 representative Chinese squid jiggers / reefers in fleet config: Fu Yuan Yu Leng 999, Long Da 001, Hai Feng 718 - 2 zone polygons added to ccamlr_areas.geojson: GMR-GALAPAGOS — Galápagos Marine Reserve incl. Hermandad (2022 expansion) EEZ-GALAPAGOS-BUFFER — Ecuador 200-nm EEZ where the squid-jigger fleet historically congregates each Aug-Nov - Headline signal: incursion into GMR (foreign-flag commercial fishing prohibited there) or AIS gap inside the buffer West Africa campaign (mission=iuu_fishing) - 3 representative DWF vessels in fleet config: Lurong Yuanyu 956 (CN), Lurong Yuanyu 988 (CN), Saltic Atlas (RU) - 3 zone polygons added (simplified bounding boxes): EEZ-SENEGAL, EEZ-MAURITANIA, EEZ-GUINEA - Headline signal: AIS gap inside any of these EEZs (vessel went dark while presumably trawling) Web (`web/data/vessels.ts`) - All 6 new vessels added to the static catalogue with bilingual notes describing the campaign context (Pingtan reefer 2017 Galápagos arrest; 300+ vessel 2020 stand-off; EJF/Greenpeace West Africa documentation) - They surface automatically in the existing /vessels mission-grouped UI (PR #15) — krill / whaling / IUU fishing sections All vessels have IMO and MMSI marked PENDING — operator must populate from GFW Carrier Vessel Portal / Equasis before deployment. The polygon boundaries are simplified bounding boxes that err conservatively (over- include slightly); operator should refine with official EEZ data (e.g. https://www.marineregions.org/) before deployment. Verification - python -m pytest tests/ -q → 110 passed - python -m ruff check → All checks passed - cd web && npx tsc --noEmit → exit 0 What's deliberately NOT done - No "campaign" abstraction yet. With 4 missions still hanging off krill_fleet.json + ccamlr_areas.geojson, the duplication cost remains near zero. Right time for the refactor is when the 5th mission lands. Co-authored-by: breaching <devsca@protonmail.com>
breaching
added a commit
that referenced
this pull request
May 11, 2026
…19) INVESTIGATOR_PLAYBOOK.md - New §4 "Multi-mission monitoring" — table of 4 missions, headline signals, tracked zones, per-mission webhook routing, smoke-test recipe via the /api/_dev/test-alert endpoint - New §5 "Weekly operational rhythm" — describes the CI cron + production cron + Monday-morning Discord ping flow - "What this tool is NOT" updated to clarify the live-alerting vs offline- encounter latency distinction, and to flag IUU vessel IMOs as PENDING alongside the existing OFAC/CM-10-09 stub callouts README.md - Subtitle pivoted from "krill fishing fleet" to "CPWF-relevant fleets — Antarctic krill, commercial whaling, and IUU fishing" - New top bullet describing the 4 missions; added "Live alerting" + cron bullets so the README accurately advertises what shipped in PRs #15-18 .env.example - Generic + per-mission webhook URLs documented - WEB_BASE_URL / WEB_DEFAULT_LOCALE for deep links - Production weekly-report runner vars (KRILL_DB_PATH, KRILL_REPORT_OUT_DIR, KRILL_REPORT_PUBLIC_URL, DISCORD_WEBHOOK_URL_REPORTS) - KRILL_ALLOW_DEV_ENDPOINTS for the dev test-alert endpoint Co-authored-by: breaching <devsca@protonmail.com>
breaching
pushed a commit
that referenced
this pull request
May 11, 2026
This commit lands the work documented in PROD_READINESS.md and docs/AUDIT_NOTES.md as deferred at the end of the 2026-04-29 audit batch, plus the audit-P19 'Pavillon' i18n leak. Audit bug #14 — FR translations for curated incidents * config/incidents.json: 4 entries gained title_fr/summary_fr * src/incidents.py + web/lib/krill-watch.ts: optional locale fields * web/components/vessels/IncidentList.tsx: prefers locale variant, drops the 'Source · EN' fallback badge when a translation exists PROD #5 — backup status surfaced on /api/health * scripts/backup_duckdb.py writes data/.backup_status.json after a successful upload (timestamp, bucket, key, size, retention counts) * server/app.py /api/health surfaces a `backup` block with a derived `age_seconds` so the frontend can render 'backup ran X hours ago' without re-parsing the timestamp * tests/test_api.py: 2 new cases lock the contract (none + present) PROD #10 — k6 load-test scaffold * scripts/loadtest.k6.js covers /api/health, /api/vessels, /api/positions/live, /api/zones, /api/incidents with three profiles (smoke / press-launch / sustained), p(95) thresholds PROD #15 — Lighthouse CI scaffold * web/lighthouserc.js: soft-asserted budgets (perf 0.6 warn, a11y 0.85 error, SEO 0.9 error) against the production-built bundle * .github/workflows/ci.yml: new lighthouse job, continue-on-error Audit bug #19 — Pavillon i18n leak * vesselDetail.flagLabel added to FR + EN dicts * vessel detail header now reads 'Pavillon: NOR' on /fr instead of leaking the EN literal Verification: 210 pytest pass (+1 health/backup case); next tsc clean; 12 Playwright e2e specs pass; live API serves the new fields end-to-end.
breaching
pushed a commit
that referenced
this pull request
May 11, 2026
Adds the 6 specs called out in PROD_READINESS #3 + #15: - web/e2e/live-loads.spec.ts: open /en/live, assert >=1 vessel dot, banner shows fleet count. - web/e2e/vessels-loads.spec.ts: /en/vessels mini-map renders with dots, click first dot -> popup with severity + dossier link. - web/e2e/vessel-detail.spec.ts: /en/vessels/antarctic_endurance, history map height >100px (regression for the inset-0 collapse), trail SVG present, scrubber controls visible. - web/e2e/firms-toggle.spec.ts: toggle FIRMS layer, assert /api/firms/recent fires + label updates. - web/e2e/operator-background.spec.ts: vessel detail page, assert "Aker BioMarine" + "Norway" appear (Wikidata cascade integration). - web/e2e/a11y.spec.ts: @axe-core/playwright across 7 routes (home, about, vessels list, campaigns hub, privacy, terms, legal), WCAG 2.1 AA tags. Fails on serious/critical only — minor/moderate tolerated so a single tag-soup edge case doesn't block deploys. - web/playwright.config.ts: chromium project, fullyParallel, CI-aware retries/workers/reporter. We deliberately don't use Playwright's webServer option — orchestration is owned by the root `npm run dev`. - .gitignore: drop web/test-results/, web/playwright-report/, web/playwright/.cache/. Closes prod-P0 #3 (5 specs) and the axe-core half of P2 #15.
breaching
pushed a commit
that referenced
this pull request
May 11, 2026
Vessel-detail and live-tracker UX improvements that were partly shipped
on disk but never committed:
- web/components/map/LiveTrackerMap.tsx (+541 lines):
* home-port markers (subdued grey diamond, distinct from live AIS dots)
so silent whaling/IUU vessels appear on the map with a clear
"no AIS — last known port" marker. Uses position_source flag.
* NASA FIRMS thermal hot-spots layer (VIIRS NOAA-20) — pulsing red
markers for fishing-vessel "lights at sea" detections.
* NASA GIBS true-colour imagery overlay (VIIRS daily mosaic, ~3 h
latency) with opacity slider so the journalist can see clouds /
sea-ice edge while keeping dot/zone overlays readable. Date defaults
to UTC-yesterday since today's tile is often missing for first 6 h.
* clickAction="popup" mode for /vessels (no side drawer there).
* onAmbientCount + onFirmsCount callbacks so parent UIs can show
"(0)" next to a toggle when the fetch found nothing.
* Extended ALPHA3->ALPHA2 flag map (ISL, FRO, CAN, AUS, etc.).
- web/components/map/VesselsMapClient.tsx: rewire to popup mode +
ambient count.
- web/components/vessels/{Sanctions,OperatorBackground}Section.tsx:
optional `prefetched` prop so the parent vessel page can launch the
three vessel-detail fetches via Promise.all and pass results in,
skipping serial round-trips. Backwards-compatible (still self-fetches
when the prop is undefined). OperatorBackground also migrated to
next/image with explicit dims (no CLS) for Wikimedia thumbnails.
- web/app/[locale]/vessels/[slug]/VesselHistoryMap.tsx: small fix.
- web/app/[locale]/vessels/page.tsx (+34): MISSION_ORDER renders 4
sections (krill / whaling / Galápagos / West Africa) so the Galápagos
+ West Africa missions stop being fused under "IUU FISHING".
- web/app/[locale]/live/{LivePageClient,page.tsx}: layer toggles
reflect the new FIRMS/NASA opacity URL state (?firms=1&nasa=1&op=…).
- web/app/[locale]/campagnes/page.tsx: copy refresh for disaffiliation.
- web/components/live/{FleetSidebar,LiveMobileLayout,MobileFiltersSheet}:
expose the FIRMS / NASA toggles in mobile + desktop sheets.
- web/lib/live-url-state.ts: new firms/nasa/op state knobs.
- web/components/{Acronym,Footer,SupportCTA}: small copy/style tweaks.
- web/app/globals.css: link-cascade fix wrapped in @layer base so
Tailwind utilities still win the cascade (closes a contrast bug
caught by axe-core on the support-CTA button).
Closes prod-P2 #16 (image opt), #17 (parallel SSR), audit #9
(Galápagos/West-Africa fused), #15 (LIVE: 0 banner backed by source
data), and most of the live-tracker polish from sprint-3.
breaching
pushed a commit
that referenced
this pull request
May 11, 2026
The desktop /live route renders LiveStatsOverlay over the map, which swaps the green LIVE pill for a yellow "AIS FEED QUIET" warning when the freshest position is older than 6 h (audit bug #15 resolution). The mobile layout was missing the same overlay — so a journalist on a phone hit "Live: 0" with no explanation while desktop visitors got the quiet-feed context. Importing LiveStatsOverlay into LiveMobileLayout and dropping it inside the absolute map container (under the top bar). Same component, same threshold, same i18n strings. 12/12 Playwright + a11y still green.
breaching
pushed a commit
that referenced
this pull request
May 11, 2026
The home page card showed a green pulsing 'LIVE' pill plus the relative 'Last position' age, but never warned that the freshest AIS ping was older than 6 h. /live already does this via LiveStatsOverlay; aligning the home banner closes the parallel of audit #15. Same 6 h threshold as LiveStatsOverlay so the home and the tracker can't disagree by more than 30 s of clock skew. Yellow 'AIS FEED QUIET' / 'FLUX AIS CALME' pill, yellow border and gradient. Verified live with the current ~10 d stale ping.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Context
The user asked: "is it possible to add other missions than Krill Wars, are there other sensitive zones on Earth to track?" This PR is the first step — opens the data model to multi-mission and onboards 3 commercial-whaling targets as a sibling campaign, demonstrating that the existing pipeline (zones, AIS gaps, severity, events) is generic and not krill-specific.
What changes
Vessel model (
src/vessel_db.py+config/krill_fleet.json)missionfield onVessel:"krill"(default, existing behaviour) |"whaling"|"iuu_fishing"mission="whaling":Zones (
config/ccamlr_areas.geojson)Three new polygons tagged
category="whale_sanctuary":The existing severity rule
ais_gap_in_mpa(hard_critical=true) automatically fires CRITICAL when a tracked whaler goes dark inside one of these polygons. No new code path needed — this is the pipeline's existing zone-event detection working on a new dataset.API (
server/app.py)/api/vesselsnow exposesmissionper vessel (no schema break — it's an additive field)Web (
web/)data/vessels.ts: adds the three whaling vessels with bilingual notesapp/[locale]/vessels/page.tsx: groups catalogue by mission (Krill / Whaling / IUU), each section with a coloured heading. Whaling cards get a danger-tinted border + explicit "Commercial whaling (IWC sanctuaries)" badgelib/i18n.ts: adds mission labels in EN/FR; renames page title from "Antarctic krill fleet" → "Tracked vessels"Why it's small on purpose
We deliberately did NOT abstract a "campaign" type yet. With one new mission in production, the duplication cost is zero and the abstraction shape is unclear. The right time to refactor config/loaders into per-campaign units is when the third mission lands (Galápagos Chinese fleet / West Africa super-trawlers / Vaquita), not now.
Verification
python -m pytest tests/ -q→ 110 passed (addedtest_vessels_includes_whaling_mission)python -m ruff check→ All checks passedcd web && npx tsc --noEmit→ exit 0cd web && npx next build→ 6 new SSG pages generated (/{en,fr}/vessels/{kangei_maru,hvalur_8,hvalur_9})Operator's checklist before deployment
imoandmmsifrom MarineTraffic / Equasisimoandmmsifrom Icelandic vessel registryDISCORD_WEBHOOK_URL/SLACK_WEBHOOK_URLas krill alerts)What's next (if this lands)
Two strong candidates for the next mission:
After mission #3, the abstraction shape will be obvious and we refactor.