upload: let short transcripts (<=5 words) attempt correlation instead of never linking at all
intelligence.py skips GPT extraction for transcripts <=5 words ("10-8", "show
me clear", a unit check-in) -- real cost/hallucination guard, kept as-is. But
upload.py's no-scenes correlation fallback (the path that thin-links a call
by talkgroup even with zero extracted content) excluded ANY skip_reason,
including transcript_too_short -- so this exact population, brief but real
follow-up and clearance traffic, never even attempted to attach to anything.
Found live: "Live, Ossining." sitting an orphan 6 seconds before a real
incident's founding call, on the same talkgroup.
Now only garbage_transcript (Whisper hallucination, no real content) stays
excluded; transcript_too_short reaches the same thin/fast-path fallback
already trusted for no-transcript calls, gated the same way -- same-talkgroup,
recently-active incident required before anything attaches. No GPT re-invoked,
no new cost.
Verified: 410 pass, 0 fail.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
3d2b722c64
commit
1ffff25cd2
@@ -522,11 +522,18 @@ async def _run_intelligence_pipeline(
|
|||||||
|
|
||||||
# Correlator also runs for calls with no scenes (unclassified) to attempt
|
# Correlator also runs for calls with no scenes (unclassified) to attempt
|
||||||
# talkgroup-based linking even when no transcript could be produced.
|
# talkgroup-based linking even when no transcript could be produced.
|
||||||
# Skip when extraction flagged the call — garbage or too-short transcripts
|
# transcript_too_short (<=5 words: "10-8", "show me clear", a unit
|
||||||
# carry no signal and would only attach spuriously via the thin path.
|
# check-in) still carries a real transcript and talkgroup — exactly the
|
||||||
|
# brief follow-up/clearance traffic an incident needs, and the thin-path
|
||||||
|
# merge below already requires a same-talkgroup, recently-active
|
||||||
|
# incident before attaching anything, same guard already trusted for
|
||||||
|
# no-transcript calls. Previously excluded here, so these calls never
|
||||||
|
# attached to anything at all. garbage_transcript (Whisper
|
||||||
|
# hallucination) has no real content behind it and stays excluded.
|
||||||
if not scenes:
|
if not scenes:
|
||||||
_call_doc = await fstore.doc_get("calls", call_id)
|
_call_doc = await fstore.doc_get("calls", call_id)
|
||||||
if not (_call_doc or {}).get("skip_reason"):
|
skip_reason = (_call_doc or {}).get("skip_reason")
|
||||||
|
if not skip_reason or skip_reason == "transcript_too_short":
|
||||||
incident_id = await _correlate_with_consensus(
|
incident_id = await _correlate_with_consensus(
|
||||||
call_id=call_id,
|
call_id=call_id,
|
||||||
node_id=node_id,
|
node_id=node_id,
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
"""
|
||||||
|
server-26#<pending> — a transcript_too_short call (<=5 words: "10-8", "show me
|
||||||
|
clear", a unit check-in) never reached correlation at all. upload.py's
|
||||||
|
no-scenes fallback (the path that lets a no-transcript call still thin-link
|
||||||
|
by talkgroup) explicitly excluded ANY skip_reason, so short-but-real follow-up
|
||||||
|
and clearance traffic was permanently unlinkable — not just unextracted by
|
||||||
|
GPT, but never even attempted against the fast/thin path that already exists
|
||||||
|
for exactly this kind of content-free signal. garbage_transcript (Whisper
|
||||||
|
hallucination) has no real content behind it and should stay excluded.
|
||||||
|
"""
|
||||||
|
from unittest.mock import AsyncMock, patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from app.routers import upload
|
||||||
|
|
||||||
|
ALL_ON = {
|
||||||
|
"stt_enabled": True,
|
||||||
|
"correlation_enabled": True,
|
||||||
|
"summaries_enabled": True,
|
||||||
|
"vocabulary_learning_enabled": True,
|
||||||
|
"transcript_correction_enabled": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def _run_ingest(skip_reason):
|
||||||
|
with patch("app.internal.feature_flags.get_flags",
|
||||||
|
AsyncMock(return_value=ALL_ON)), \
|
||||||
|
patch("app.internal.firestore.doc_get_cached",
|
||||||
|
AsyncMock(return_value={"system_id": "sys-1", "ai_flags": {}})), \
|
||||||
|
patch.object(upload, "fstore") as fs, \
|
||||||
|
patch.object(upload, "_correlate_with_consensus", AsyncMock(return_value=None)) as corr, \
|
||||||
|
patch("app.internal.transcription.transcribe_call",
|
||||||
|
AsyncMock(return_value=("10-8", []))), \
|
||||||
|
patch("app.internal.intelligence.extract_scenes", AsyncMock(return_value=[])), \
|
||||||
|
patch("app.internal.alerter.check_and_dispatch", AsyncMock()):
|
||||||
|
fs.doc_get = AsyncMock(return_value={"skip_reason": skip_reason} if skip_reason else {})
|
||||||
|
fs.doc_set = AsyncMock()
|
||||||
|
await upload._run_intelligence_pipeline(
|
||||||
|
call_id="call-1", node_id="node-1", system_id="sys-1",
|
||||||
|
talkgroup_id=101, talkgroup_name="PD Dispatch",
|
||||||
|
gcs_uri="gs://bucket/call-1.mp3",
|
||||||
|
)
|
||||||
|
return corr
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_transcript_too_short_now_attempts_correlation():
|
||||||
|
corr = await _run_ingest("transcript_too_short")
|
||||||
|
corr.assert_awaited_once()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_garbage_transcript_still_skips_correlation():
|
||||||
|
corr = await _run_ingest("garbage_transcript")
|
||||||
|
corr.assert_not_awaited()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_no_skip_reason_still_attempts_correlation():
|
||||||
|
corr = await _run_ingest(None)
|
||||||
|
corr.assert_awaited_once()
|
||||||
Reference in New Issue
Block a user