Photo gallery: multi-select upload wrongly groups unrelated photos as a burst #891

Closed
opened 2026-07-29 05:48:00 +00:00 by spikerj · 1 comment
Owner

Uploading multiple photos via one multi-select groups them ALL as one burst, even when the pictures are unrelated (observed: two completely different photos from different times shown as a burst).

Root cause (verified): the client mints one shared sessionId for ANY multi-select (photo-gallery.component.ts:254), the server persists it unvalidated, and BurstGrouping.Decide explicitly returns null for frames with a SessionId — the EXIF gap/same-camera rule never runs (contract #779 §3 "client sessionId always wins").

Fix: server treats the client sessionId as a hint. After EXIF extraction, a new pure rule BurstGrouping.ValidateClientSession checks chain-adjacency (sequence-ordered, same camera, |Δt| ≤ max(1,Δseq)×gap; new config Photography:ClientSessionMaxGapSeconds default 10.0). Frames failing validation are evicted (SessionId cleared) and flow through the existing EXIF auto-grouping. Untimed frames (no TakenAtPrecise) are never evicted (original focus-stack use case). Backfill phase added to tools/BackfillPhotographBursts heals existing bad prod sessions.

Backend-only; no Angular changes.

Uploading multiple photos via one multi-select groups them ALL as one burst, even when the pictures are unrelated (observed: two completely different photos from different times shown as a burst). Root cause (verified): the client mints one shared sessionId for ANY multi-select (photo-gallery.component.ts:254), the server persists it unvalidated, and BurstGrouping.Decide explicitly returns null for frames with a SessionId — the EXIF gap/same-camera rule never runs (contract #779 §3 "client sessionId always wins"). Fix: server treats the client sessionId as a hint. After EXIF extraction, a new pure rule BurstGrouping.ValidateClientSession checks chain-adjacency (sequence-ordered, same camera, |Δt| ≤ max(1,Δseq)×gap; new config Photography:ClientSessionMaxGapSeconds default 10.0). Frames failing validation are evicted (SessionId cleared) and flow through the existing EXIF auto-grouping. Untimed frames (no TakenAtPrecise) are never evicted (original focus-stack use case). Backfill phase added to tools/BackfillPhotographBursts heals existing bad prod sessions. Backend-only; no Angular changes.
Author
Owner

Resolved in spikersoft-backend PR #505 (merged to master, e8bf57e0). Verified against origin/master — every bullet landed:

  • BurstGrouping.ValidateClientSession (SpikerSoft.Data/Mongos/Photography/BurstGrouping.cs:92) implements the adjacency rule as specified: order by SequenceIndex, deltaSeq = Math.Max(1, cur - prev), chain only when IsSameCamera(...) && deltaSeconds <= deltaSeq * maxGapSeconds (:108-114). Keeper run is the lowest SequenceIndex (:122), so repeated passes converge.
  • Untimed frames are never evicted — :97 filters to TakenAtPrecise is not null before building runs.
  • Photography:ClientSessionMaxGapSeconds: 10.0 is wired, not an orphan key: set at PhotographProcessor/appsettings.json:69, read at PhotographConsumers.cs:296.
  • Evicted frames re-flow through EXIF grouping — PhotographConsumers.cs:255-258 branches on SessionId is not null; TryValidateClientSessionAsync clears SessionId/SequenceIndex (:313-317) then feeds each evicted frame to TryAutoGroupAsync (:325-326).
  • Backfill for already-ingested sets: tools/BackfillPhotographBursts/Program.cs "Phase 1.5" (:95-130) calls the same pure rule, idempotent by construction.
  • Tests: ClientSessionValidationTests.cs, 15 cases including untimed/deleted/adjacency.

Closing.

Resolved in spikersoft-backend PR #505 (merged to `master`, `e8bf57e0`). Verified against `origin/master` — every bullet landed: - `BurstGrouping.ValidateClientSession` (`SpikerSoft.Data/Mongos/Photography/BurstGrouping.cs:92`) implements the adjacency rule as specified: order by `SequenceIndex`, `deltaSeq = Math.Max(1, cur - prev)`, chain only when `IsSameCamera(...) && deltaSeconds <= deltaSeq * maxGapSeconds` (`:108-114`). Keeper run is the lowest `SequenceIndex` (`:122`), so repeated passes converge. - Untimed frames are never evicted — `:97` filters to `TakenAtPrecise is not null` before building runs. - `Photography:ClientSessionMaxGapSeconds: 10.0` is wired, not an orphan key: set at `PhotographProcessor/appsettings.json:69`, read at `PhotographConsumers.cs:296`. - Evicted frames re-flow through EXIF grouping — `PhotographConsumers.cs:255-258` branches on `SessionId is not null`; `TryValidateClientSessionAsync` clears `SessionId`/`SequenceIndex` (`:313-317`) then feeds each evicted frame to `TryAutoGroupAsync` (`:325-326`). - Backfill for already-ingested sets: `tools/BackfillPhotographBursts/Program.cs` "Phase 1.5" (`:95-130`) calls the same pure rule, idempotent by construction. - Tests: `ClientSessionValidationTests.cs`, 15 cases including untimed/deleted/adjacency. Closing.
Sign in to join this conversation.