Fix over-eager dive matching (time gate) + Consolidate for file imports#494
Merged
Conversation
…gged as duplicates
… they default to skip
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Contributor
There was a problem hiding this comment.
Pull request overview
This PR tightens duplicate-dive detection for file imports by making time proximity a hard prerequisite in DiveMatcher, and it wires manual “Consolidate” support into the routed file-import adapter (UniversalAdapter) so file imports can fold duplicates the same way dive-computer downloads can.
Changes:
- Make time a necessary gate in
DiveMatcher.calculateMatchScoreso far-apart dives can’t reach duplicate thresholds via depth+duration alone. - Flag exact
source_uuidre-import matches asmatchedExistingSourceand enable manualDuplicateAction.consolidatefor file imports, including post-import folding and corrected summary counts. - Add targeted regression and wiring tests, plus design/spec documentation under
docs/superpowers/.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| test/features/universal_import/data/services/import_duplicate_checker_test.dart | Adds tests for matchedExistingSource semantics and far-apart dives no longer matching. |
| test/features/import_wizard/data/adapters/universal_adapter_test.dart | Verifies consolidate is supported and performImport delegates fold + adjusts counts. |
| test/features/dive_import/domain/services/dive_matcher_test.dart | Adds time-gate regression/boundary tests for matcher scoring. |
| lib/features/universal_import/data/services/import_duplicate_checker.dart | Sets matchedExistingSource: true for exact source_uuid matches. |
| lib/features/import_wizard/data/adapters/universal_adapter.dart | Enables consolidate for file imports and performs post-import consolidation with updated result accounting. |
| lib/features/dive_import/domain/services/dive_matcher.dart | Implements the time-as-gate short-circuit in calculateMatchScore. |
| docs/superpowers/specs/2026-07-06-dive-matching-time-gate-and-file-import-consolidation-design.md | Design spec documenting the two fixes and rationale. |
| docs/superpowers/plans/2026-07-06-dive-matching-time-gate-and-file-import-consolidation.md | Implementation plan for the work (for traceability/process). |
…dation fold and cleanup both fail
ericgriffin
added a commit
that referenced
this pull request
Jul 6, 2026
…anup Cleanup: shared MatchScorer + remove dead import code (follow-up to #494)
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.
Addresses a tester report on ScubaBoard (user stiebs): a Subsurface import was flagged as a 50% "possible duplicate" of an existing dive that was months apart, ~4.5 km away, at a different time of day — sharing only its 47-minute duration. The same report noted that Combine/Consolidate only worked for direct dive-computer downloads, not file imports.
Part 1 — Time is now a necessary gate in the matcher
File imports score candidates with
DiveMatcher.calculateMatchScore, a weighted sum:time*0.50 + depth*0.30 + duration*0.20. WithtimeScore = 0(starts >= 15 min apart), depth (0.30) + duration (0.20) alone summed to exactly 0.50 — theisPossibleDuplicatethreshold — so two unrelated dives with a similar profile got flagged.A dive is a single event in time; two recordings that do not line up in time cannot be the same dive. The fix makes time a necessary condition: once time evidence is zero, the pair is not a match, before the weighted sum runs. The dive-computer download path already gated on time via SQL, so it is unchanged.
stiebs's pair now scores
0.0instead of0.50.Part 2 — Manual Consolidate for file imports
The consolidation capability already existed but was only wired into the dive-computer download adapter; the live file-import adapter (
UniversalAdapter) offered only skip / import-as-new and hard-codedconsolidatedCount: 0. This ports the proven wiring (performConsolidations+DiveConsolidationService) intoUniversalAdapter:consolidateadded tosupportedDuplicateActions→ the review UI renders the button automatically (one declarative set drives every layer of the UI).source_uuidre-imports are flaggedmatchedExistingSourceso they default to skip (never consolidated).performImportfolds consolidate-flagged dives after import and reports an accurate count — folded dives are subtracted from the "imported as new" count instead of being double-counted.File imports are manual-consolidate only: a file has no single "current computer" to prove a cross-computer match, so the user chooses. Consolidating onto a same-computer target fails gracefully (the fold is rejected, the standalone import is tombstoned via the existing compensating-delete path, and it is reported as skipped).
Testing
DiveMatcher: gate tests incl. the exact ScubaBoard regression (months apart, identical depth + duration →0.0), same-day-hours-apart, the 15-minute boundary, and a within-window guard.ImportDuplicateChecker:matchedExistingSourcetrue for source_uuid / false for content matches; far-apart dives no longer matched.UniversalAdapter: supports consolidate;performImportfolds a consolidate-flagged dive and adjusts counts (wiring test).flutter analyzeclean (whole project); macOS build succeeds.Design spec and implementation plan are included under
docs/superpowers/.