correlation upgrades
This commit is contained in:
@@ -17,8 +17,9 @@ class Settings(BaseSettings):
|
|||||||
# Node health
|
# Node health
|
||||||
node_offline_threshold: int = 90 # seconds without checkin before marking offline
|
node_offline_threshold: int = 90 # seconds without checkin before marking offline
|
||||||
|
|
||||||
# OpenAI (Whisper STT)
|
# OpenAI (STT + intelligence)
|
||||||
openai_api_key: Optional[str] = None
|
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 (intelligence extraction, embeddings, incident summaries)
|
||||||
gemini_api_key: Optional[str] = None
|
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
|
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
|
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_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 learning
|
||||||
vocabulary_induction_interval_hours: int = 24 # how often the induction loop runs
|
vocabulary_induction_interval_hours: int = 24 # how often the induction loop runs
|
||||||
|
|||||||
@@ -249,8 +249,25 @@ async def correlate_call(
|
|||||||
|
|
||||||
if tg_recent and is_thin_call:
|
if tg_recent and is_thin_call:
|
||||||
# Status/ack call — no scene data to reason about.
|
# Status/ack call — no scene data to reason about.
|
||||||
# Attach to whichever recent incident was most recently active on this TGID.
|
# On dispatch channels (shared backbone), apply a much tighter idle gate so
|
||||||
matched_incident = max(tg_recent, key=lambda inc: inc.get("updated_at", ""))
|
# 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_debug = {
|
||||||
"corr_path": "fast/thin",
|
"corr_path": "fast/thin",
|
||||||
"corr_incident_idle_min": round(_incident_idle_minutes(matched_incident, now), 1),
|
"corr_incident_idle_min": round(_incident_idle_minutes(matched_incident, now), 1),
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ def _sync_transcribe(
|
|||||||
openai_client = OpenAI(api_key=settings.openai_api_key)
|
openai_client = OpenAI(api_key=settings.openai_api_key)
|
||||||
with open(tmp_path, "rb") as f:
|
with open(tmp_path, "rb") as f:
|
||||||
response = openai_client.audio.transcriptions.create(
|
response = openai_client.audio.transcriptions.create(
|
||||||
model="whisper-1",
|
model=settings.stt_model,
|
||||||
file=f,
|
file=f,
|
||||||
language="en",
|
language="en",
|
||||||
prompt=prompt,
|
prompt=prompt,
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import asyncio
|
|||||||
import difflib
|
import difflib
|
||||||
import json
|
import json
|
||||||
import random
|
import random
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone, timedelta
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from app.internal.logger import logger
|
from app.internal.logger import logger
|
||||||
from app.internal import firestore as fstore
|
from app.internal import firestore as fstore
|
||||||
|
|||||||
@@ -85,9 +85,29 @@ function CorrelationDebugTab() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleCopy() {
|
function handleCopy() {
|
||||||
if (!data) return;
|
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);
|
setCopied(true);
|
||||||
setTimeout(() => setCopied(false), 2000);
|
setTimeout(() => setCopied(false), 2000);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user