feat: SAR sensor fusion — unmatched_sar_detections_30d (#84)#126
Merged
feat: SAR sensor fusion — unmatched_sar_detections_30d (#84)#126
Conversation
Add a Sentinel-1 SAR vessel detection subsystem that counts how many
SAR radar detections per vessel occurred during AIS gaps and were
spatially close to the vessel's last known position (dark vessel signal).
New files:
- src/ingest/sar.py – ingest SAR detections from CSV or records
- src/features/sar_detections.py – compute unmatched_sar_detections_30d
- tests/test_sar_detections.py – 6 unit tests covering match/no-match/
attribution/window/multi-detection cases
Modified files:
- src/ingest/schema.py – add sar_detections table + feature column
- src/features/build_matrix.py – wire SAR feature into matrix pipeline
- src/score/anomaly.py – include feature in Isolation Forest training
- tests/test_schema.py – update expected table set
- tests/test_enhancements_cap_vista.py – sync local ANOMALY_FEATURE_COLUMNS
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Seeds a fresh DuckDB with a vessel AIS gap + 3 unmatched SAR detections, runs compute_unmatched_sar_detections, and asserts count=3. Useful for verifying issue #84 locally without needing real SAR data. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add to FEATURE_VALUE_COLUMNS so raw value is stored in watchlist
- Add valueLabel formatter ("3 detections") in index.html SHAP table
- Extend ops shell option 11 to optionally run full pipeline + print
dashboard verification steps
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… Forest Isolation Forest needs ≥4 samples to train. The smoke test previously seeded only 1 vessel, causing composite scoring to fail. Now seeds 5 normal vessels (frequent pings, no gaps) as background population. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…t it - Change default DB path from /tmp/ to data/processed/sar_smoke_test.duckdb - Remove COPY data/ from Dockerfile (data/ is a runtime volume mount) - Add .dockerignore to exclude data/, .git/, .venv/ from build context Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…o overrides Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ections_30d - Add ALTER TABLE ... ADD COLUMN IF NOT EXISTS in init_schema so existing DBs get the column without needing a full rebuild - Delete existing DB file before seeding in smoke test (it's explicitly fresh) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
yohei1126
added a commit
that referenced
this pull request
Apr 9, 2026
Adds `device_scale_factor=2` to the Playwright browser context so all screenshots — including `05_sar_shap.png` — render at 2× pixel density on retina displays. The screenshot now shows `unmatched_sar_detections_30d` as the primary SHAP signal (SHAP +0.24, rank #1) for SARI NOUR (MMSI 613115678), satisfying the Annex A PoC Week 7 deliverable for the Cap Vista submission. Supporting code shipped in prior PRs: - `capture_sar_shap()` and `seed_demo_sar.py` — #135 - `unmatched_sar_detections_30d` SAR feature — #126 - `annex-a-submission.md` SAR section updated locally (gitignored _outputs/) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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.
Summary
sar_detectionstable to store Sentinel-1 radar vessel detections (lat/lon/time, no MMSI)unmatched_sar_detections_30dfeature: counts SAR detections per vessel that occurred during an AIS gap and were spatially close to the vessel's last known position — a direct dark-vessel signalAlgorithm:
unmatched_sar_detections_30dAll spatial joins are done in DuckDB (in-memory) with a bounding-box pre-filter + haversine verification.
Files changed
src/ingest/sar.pysar_detectionssrc/ingest/schema.pysar_detectionstable +vessel_featurescolumnsrc/features/sar_detections.pycompute_unmatched_sar_detections()src/features/build_matrix.pysrc/score/anomaly.pyANOMALY_FEATURE_COLUMNStests/test_sar_detections.pytests/test_schema.pytests/test_enhancements_cap_vista.pyANOMALY_FEATURE_COLUMNSTest plan
test_empty_sar_table_returns_empty— no SAR data → empty resulttest_matched_sar_detection_not_counted— AIS-matched detections excludedtest_unmatched_sar_during_gap_counts— gap + nearby SAR → countedtest_unmatched_sar_far_from_vessel_not_counted— 500 km away → not attributedtest_multiple_unmatched_detections_counted— 3 detections → count=3test_sar_outside_window_not_counted— 45-day old detection → excludedCloses #84
🤖 Generated with Claude Code