""" server-26#114 — the incident summarizer used to read doc["transcript"] (the WHOLE call, raw) for every linked call, so a multi-scene call contributed text from scenes it wasn't part of into an incident's summary, and transcript_corrected was never consulted at all. Fix: _scene_text_for_incident reads the server-26#96 `scenes` map to find the scene(s) that actually resolved into a given incident_id, and falls back to transcript_corrected-or-transcript for a call doc with no `scenes` field (predates #96). """ import pytest from unittest.mock import AsyncMock, patch from app.internal import summarizer from app.internal.summarizer import _scene_text_for_incident # --------------------------------------------------------------------------- # _scene_text_for_incident — pure function, no Firestore # --------------------------------------------------------------------------- def test_picks_the_scene_that_linked_to_this_incident(): doc = { "transcript": "whole raw transcript blend", "transcript_corrected": "whole corrected transcript blend", "scenes": { "0": {"transcript": "scene zero text", "incident_id": "inc-A", "corr_debug": {}}, "1": {"transcript": "scene one text", "incident_id": "inc-B", "corr_debug": {}}, }, } assert _scene_text_for_incident(doc, "inc-A") == "scene zero text" assert _scene_text_for_incident(doc, "inc-B") == "scene one text" def test_joins_multiple_scenes_linked_to_the_same_incident_in_scene_order(): doc = { "scenes": { "1": {"transcript": "second", "incident_id": "inc-A"}, "0": {"transcript": "first", "incident_id": "inc-A"}, }, } assert _scene_text_for_incident(doc, "inc-A") == "first\nsecond" def test_old_schema_doc_falls_back_to_transcript_corrected_over_transcript(): doc = {"transcript": "raw", "transcript_corrected": "corrected"} assert _scene_text_for_incident(doc, "inc-A") == "corrected" def test_old_schema_doc_with_only_raw_transcript_still_returns_it(): doc = {"transcript": "raw only"} assert _scene_text_for_incident(doc, "inc-A") == "raw only" def test_scenes_present_but_none_match_falls_back_defensively(): """Should not happen for a call_id genuinely in this incident's call_ids, but silently dropping the call's contribution would be worse than a whole-call fallback.""" doc = { "transcript": "raw", "transcript_corrected": "corrected", "scenes": {"0": {"transcript": "x", "incident_id": "inc-OTHER"}}, } assert _scene_text_for_incident(doc, "inc-A") == "corrected" # --------------------------------------------------------------------------- # _summarize_incident — end to end with fstore/Gemini mocked # --------------------------------------------------------------------------- @pytest.mark.asyncio async def test_summarize_incident_uses_scene_specific_text_for_a_multiscene_call(): """ call-1 is a 2-scene call: scene 0 linked into inc-OTHER, scene 1 linked into inc-1 (the incident being summarized). Only scene 1's text may reach the model. """ call_1 = { "call_id": "call-1", "transcript": "scene zero text scene one text", # the old, wrong, whole-call blend "scenes": { "0": {"transcript": "scene zero text", "incident_id": "inc-OTHER"}, "1": {"transcript": "scene one text", "incident_id": "inc-1"}, }, } async def fake_doc_get(collection, doc_id): assert collection == "calls" return call_1 if doc_id == "call-1" else None with patch("app.internal.feature_flags.get_flags", AsyncMock(return_value={"summaries_enabled": True})), \ patch.object(summarizer, "fstore") as fs, \ patch.object(summarizer, "_sync_summarize", return_value="a summary") as sync: fs.doc_get = AsyncMock(side_effect=fake_doc_get) fs.doc_set = AsyncMock() await summarizer._summarize_incident({"incident_id": "inc-1", "call_ids": ["call-1"]}) sync.assert_called_once() _inc_arg, transcripts_arg = sync.call_args.args assert transcripts_arg == ["scene one text"] assert "scene zero text scene one text" not in transcripts_arg @pytest.mark.asyncio async def test_summarize_incident_falls_back_for_old_schema_call_doc(): """A call doc with no `scenes` field at all — summarizer must still work, using transcript_corrected over raw transcript.""" call_1 = {"call_id": "call-1", "transcript": "raw", "transcript_corrected": "corrected"} async def fake_doc_get(collection, doc_id): return call_1 if doc_id == "call-1" else None with patch("app.internal.feature_flags.get_flags", AsyncMock(return_value={"summaries_enabled": True})), \ patch.object(summarizer, "fstore") as fs, \ patch.object(summarizer, "_sync_summarize", return_value="a summary") as sync: fs.doc_get = AsyncMock(side_effect=fake_doc_get) fs.doc_set = AsyncMock() await summarizer._summarize_incident({"incident_id": "inc-1", "call_ids": ["call-1"]}) _inc_arg, transcripts_arg = sync.call_args.args assert transcripts_arg == ["corrected"]