diff --git a/drb-c2-core/app/config.py b/drb-c2-core/app/config.py index 922576d..265fa63 100644 --- a/drb-c2-core/app/config.py +++ b/drb-c2-core/app/config.py @@ -17,8 +17,9 @@ class Settings(BaseSettings): # Node health node_offline_threshold: int = 90 # seconds without checkin before marking offline - # OpenAI (Whisper STT) + # OpenAI (STT + intelligence) openai_api_key: Optional[str] = None + stt_model: str = "gpt-4o-transcribe" # whisper-1 | gpt-4o-mini-transcribe | gpt-4o-transcribe # Gemini (intelligence extraction, embeddings, incident summaries) gemini_api_key: Optional[str] = None @@ -31,6 +32,7 @@ class Settings(BaseSettings): incident_auto_resolve_minutes: int = 90 # auto-resolve after N minutes with no new calls recorrelation_scan_minutes: int = 60 # re-examine orphaned calls ended within this window tg_fast_path_idle_minutes: int = 90 # fast path: max minutes since incident last updated + tg_dispatch_thin_idle_minutes: int = 10 # dispatch channels only: thin calls only attach to incidents idle < this many minutes # Vocabulary learning vocabulary_induction_interval_hours: int = 24 # how often the induction loop runs diff --git a/drb-c2-core/app/internal/incident_correlator.py b/drb-c2-core/app/internal/incident_correlator.py index 249e60c..c873129 100644 --- a/drb-c2-core/app/internal/incident_correlator.py +++ b/drb-c2-core/app/internal/incident_correlator.py @@ -249,16 +249,33 @@ async def correlate_call( if tg_recent and is_thin_call: # Status/ack call — no scene data to reason about. - # Attach to whichever recent incident was most recently active on this TGID. - matched_incident = max(tg_recent, key=lambda inc: inc.get("updated_at", "")) - corr_debug = { - "corr_path": "fast/thin", - "corr_incident_idle_min": round(_incident_idle_minutes(matched_incident, now), 1), - } - logger.info( - f"Correlator fast-path (thin→last TGID incident): " - f"call {call_id} → {matched_incident['incident_id']}" - ) + # On dispatch channels (shared backbone), apply a much tighter idle gate so + # a "10-4" or "Dispatch." doesn't re-activate an incident that's been quiet + # for an hour and then absorb the next unrelated dispatch on the same TGID. + if is_dispatch: + thin_pool = [ + inc for inc in tg_recent + if _incident_idle_minutes(inc, now) <= settings.tg_dispatch_thin_idle_minutes + ] + else: + thin_pool = tg_recent + + if not thin_pool: + logger.info( + f"Correlator fast-path thin: dispatch channel idle > " + f"{settings.tg_dispatch_thin_idle_minutes}min, skipping thin call {call_id}" + ) + else: + # Attach to whichever pool incident was most recently active on this TGID. + matched_incident = max(thin_pool, key=lambda inc: inc.get("updated_at", "")) + corr_debug = { + "corr_path": "fast/thin", + "corr_incident_idle_min": round(_incident_idle_minutes(matched_incident, now), 1), + } + logger.info( + f"Correlator fast-path (thin→last TGID incident): " + f"call {call_id} → {matched_incident['incident_id']}" + ) elif len(tg_recent) == 1: candidate = tg_recent[0] if _call_fits_incident( diff --git a/drb-c2-core/app/internal/transcription.py b/drb-c2-core/app/internal/transcription.py index 3dff828..c7a37d8 100644 --- a/drb-c2-core/app/internal/transcription.py +++ b/drb-c2-core/app/internal/transcription.py @@ -116,7 +116,7 @@ def _sync_transcribe( openai_client = OpenAI(api_key=settings.openai_api_key) with open(tmp_path, "rb") as f: response = openai_client.audio.transcriptions.create( - model="whisper-1", + model=settings.stt_model, file=f, language="en", prompt=prompt, diff --git a/drb-c2-core/app/internal/vocabulary_learner.py b/drb-c2-core/app/internal/vocabulary_learner.py index 960f270..be120cb 100644 --- a/drb-c2-core/app/internal/vocabulary_learner.py +++ b/drb-c2-core/app/internal/vocabulary_learner.py @@ -18,7 +18,7 @@ import asyncio import difflib import json import random -from datetime import datetime, timezone +from datetime import datetime, timezone, timedelta from typing import Optional from app.internal.logger import logger from app.internal import firestore as fstore diff --git a/drb-frontend/app/admin/page.tsx b/drb-frontend/app/admin/page.tsx index bd85041..c05f65e 100644 --- a/drb-frontend/app/admin/page.tsx +++ b/drb-frontend/app/admin/page.tsx @@ -85,9 +85,29 @@ function CorrelationDebugTab() { } } - async function handleCopy() { + function handleCopy() { if (!data) return; - await navigator.clipboard.writeText(JSON.stringify(data, null, 2)); + const text = JSON.stringify(data, null, 2); + if (navigator.clipboard) { + navigator.clipboard.writeText(text).then(() => { + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }).catch(() => fallbackCopy(text)); + } else { + fallbackCopy(text); + } + } + + function fallbackCopy(text: string) { + const ta = document.createElement("textarea"); + ta.value = text; + ta.style.position = "fixed"; + ta.style.opacity = "0"; + document.body.appendChild(ta); + ta.focus(); + ta.select(); + document.execCommand("copy"); + document.body.removeChild(ta); setCopied(true); setTimeout(() => setCopied(false), 2000); }