Fix speaker sample extraction after WS reconnect#5989
Conversation
Rehydrate current_session_segments from persisted conversation transcript_segments when resuming a conversation after WebSocket reconnection. Previously, this session-local dict was always empty on reconnect, causing can_assign to return false and blocking speaker sample extraction for all users who experience WS reconnect during recording. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
7 tests covering: dict segment rehydration, empty segments, missing fields, can_assign after rehydration, cross-conversation leakage prevention, and large segment list handling. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Greptile SummaryThis PR fixes a real bug where The fix is a small, well-scoped addition inside Key points:
Confidence Score: 5/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant Client
participant StreamHandler as _stream_handler()
participant PrepareConv as _prepare_in_progress_conversations()
participant DB as conversations_db
Note over StreamHandler: current_session_segments = {} (fresh on every WS connect)
Client->>StreamHandler: WebSocket reconnect
StreamHandler->>PrepareConv: await _prepare_in_progess_conversations()
PrepareConv->>DB: retrieve_in_progress_conversation(uid)
DB-->>PrepareConv: existing_conversation (with transcript_segments)
alt seconds_since_last_segment < timeout (resume path)
PrepareConv->>StreamHandler: current_conversation_id = existing_conversation['id']
loop for each seg in transcript_segments (NEW in this PR)
PrepareConv->>StreamHandler: current_session_segments[seg.id] = seg.speech_profile_processed
end
PrepareConv->>PrepareConv: log rehydration count
else timed out
PrepareConv->>StreamHandler: _create_new_in_progress_conversation()
Note over StreamHandler: current_session_segments stays empty (correct)
end
Client->>StreamHandler: speaker_assigned event (segment_ids)
StreamHandler->>StreamHandler: can_assign check against current_session_segments
Note over StreamHandler: Now finds pre-reconnect segment IDs → can_assign = True
Reviews (1): Last reviewed commit: "Add speaker segment reconnect tests to t..." | Re-trigger Greptile |
| ] | ||
| current_session_segments = self._rehydrate(segments) | ||
|
|
||
| # Simulate the can_assign check from transcribe.py:2509-2514 |
There was a problem hiding this comment.
The comment references transcribe.py:2509-2514, but the can_assign logic is currently around lines 2536–2541. This is a minor stale reference that may confuse future readers.
| # Simulate the can_assign check from transcribe.py:2509-2514 | |
| # Simulate the can_assign check from transcribe.py (speaker_assigned handler) |
| class TestCurrentSessionSegmentsRehydration: | ||
| """Tests for rehydrating current_session_segments from conversation data.""" | ||
|
|
||
| @staticmethod | ||
| def _rehydrate(existing_segments): | ||
| """Simulate the rehydration logic from _prepare_in_progess_conversations.""" | ||
| current_session_segments = {} | ||
| for seg in existing_segments: | ||
| sid = seg.get('id') if isinstance(seg, dict) else getattr(seg, 'id', None) | ||
| if sid: | ||
| spp = ( | ||
| seg.get('speech_profile_processed', True) | ||
| if isinstance(seg, dict) | ||
| else getattr(seg, 'speech_profile_processed', True) | ||
| ) | ||
| current_session_segments[sid] = spp | ||
| return current_session_segments |
There was a problem hiding this comment.
No test coverage for object-attribute segment path
The production code in transcribe.py handles both dict segments and model-object segments via the isinstance(seg, dict) check:
sid = seg.get('id') if isinstance(seg, dict) else getattr(seg, 'id', None)All seven tests only exercise the dict path. The getattr branch (reached when transcript_segments contains Pydantic/dataclass model instances) is entirely untested. Adding a simple object-based fixture (e.g. a SimpleNamespace or a minimal stub) would complete coverage of that branch and guard against regressions if the storage layer ever returns model objects instead of raw dicts.
Move rehydration logic from inline code in transcribe.py to a shared function in utils/speaker_assignment.py for testability. Tests now import and test the actual production function directly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace inline rehydration with call to the shared utility function. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tests now call the actual production function instead of a local helper. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@beastoin I re-reviewed the latest diff for #5989 and the previous concern is addressed: by AI for @beastoin |
Covers the non-dict segment branch in rehydrate_session_segments using real TranscriptSegment objects. Addresses tester feedback. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CP9 Changed-Path Coverage Checklist
L1 SynthesisAll 5 changed paths (P1-P5) proven by 8 unit tests (N/A sequences, path-only mode). Non-happy paths proven: empty segments, missing id, missing speech_profile_processed, can_assign false for unknown/unprocessed segments. 22/22 tests pass including pre-existing speaker_assignment tests. Backend utility imports and tests cleanly. L2 SynthesisP1-P5 are UNTESTED at L2 level. The rehydration code path triggers only on WS reconnection that resumes an existing conversation — requires simulating network disruption during active recording, which is not feasible in local integrated testing without infrastructure support. The fix is a pure data transformation (read segments → populate dict) with no cross-service boundary change — the WS protocol, Pusher communication, and Firestore schema are all unchanged. L1 unit tests provide complete coverage of the rehydration logic. by AI for @beastoin |
CP8 Test Detail Table
by AI for @beastoin |
✅ All Checkpoints Passed — Ready for Merge
Summary: Fixes #5949 — rehydrates Changes: 4 files, 163 additions — Awaiting human merge approval. by AI for @beastoin |
|
Hey @beastoin 👋 Thank you so much for taking the time to contribute to Omi! We truly appreciate you putting in the effort to submit this pull request. After careful review, we've decided not to merge this particular PR. Please don't take this personally — we genuinely try to merge as many contributions as possible, but sometimes we have to make tough calls based on:
Your contribution is still valuable to us, and we'd love to see you contribute again in the future! If you'd like feedback on how to improve this PR or want to discuss alternative approaches, please don't hesitate to reach out. Thank you for being part of the Omi community! 💜 |
Summary
Fixes #5949 (scoped to the real backend bug — see analysis below).
current_session_segmentsdict intranscribe.pyis declared inside_stream_handler()and resets on every new WebSocket connection. After WS reconnect,can_assignreturnsfalsebecause segment IDs from the previous session are gone, blocking speaker sample extraction for all users who experience WS reconnect during recording (not just TestFlight).rehydrate_session_segments()toutils/speaker_assignment.pyand call it fromtranscribe.pywhen resuming a conversation after reconnect. Uses defensive reads (seg.get('id'),seg.get('speech_profile_processed', True)) and logs the rehydration count.Env.overrideApiBaseUrlis set once at init, no mid-session mutation exists. The staging banner claim was already fixed by commit2f2b2e01d. This PR fixes the one real bug: session-local segment state loss on reconnect.Changes
backend/utils/speaker_assignment.pyrehydrate_session_segments()shared utility functionbackend/routers/transcribe.pyrehydrate_session_segments()in_prepare_in_progess_conversations()resume pathbackend/tests/unit/test_speaker_segment_reconnect.pybackend/test.shTesting
Review Cycle
Risks / Edge Cases
current_session_segmentsis initialized empty at_stream_handler()entry; rehydration only on resumespeech_profile_processed: Defaults toTrue(matching model default)speaker_to_person_map/segment_person_assignment_map—speaker_idcontinuity not guaranteed across reconnectIssue Analysis (Codex-verified)
testFlightApiEnvironmentdefaults to stagingmain.dartoverridesapiBaseUrlfor TestFlightapiBaseUrlcurrent_session_segmentsresets on reconnectisUsingStagingApi)Closes #5949
by AI for @beastoin