diff --git a/drb-c2-core/app/internal/transcription.py b/drb-c2-core/app/internal/transcription.py index 6f8513c..313ffed 100644 --- a/drb-c2-core/app/internal/transcription.py +++ b/drb-c2-core/app/internal/transcription.py @@ -93,6 +93,42 @@ def _is_degenerate(text: str, segments: list[dict]) -> bool: return False +_billing_reported = False + + +def _log_transcribe_failure(call_id: str, exc: Exception) -> None: + """ + Log a transcription failure, escalating an unpayable account to ERROR once. + + Transcription failing returns None and the pipeline carries on by design, so + a per-call WARNING is invisible: no transcript means no extraction, which + means no incident, and the only symptom is calls quietly arriving empty. A + network blip is genuinely a warning. An exhausted balance is not -- it will + not fix itself and it takes the whole pipeline down with it, so it says so + once, loudly, and names the fix. + + The same failure mode already bit the Gemini correlator twice (a retired + model ID, then a depleted balance), which is why this is worth the code. + """ + global _billing_reported + text = str(exc) + low = text.lower() + + if ("insufficient_quota" in low or "billing" in low + or "credit" in low or "exceeded your current quota" in low): + if not _billing_reported: + _billing_reported = True + logger.error( + "Transcription: the OpenAI account cannot be billed -- EVERY call is " + "now stored with no transcript, so extraction, correlation and " + "incidents are all dead downstream. Top up at " + f"https://platform.openai.com/settings/organization/billing. API said: {text}" + ) + return + + logger.warning(f"Transcription failed for call {call_id}: {text}") + + async def transcribe_call( call_id: str, gcs_uri: str, @@ -114,7 +150,7 @@ async def transcribe_call( _sync_transcribe, gcs_uri, talkgroup_name ) except Exception as e: - logger.warning(f"Transcription failed for call {call_id}: {e}") + _log_transcribe_failure(call_id, e) return None, [] if transcript: