8a3e7e2b53
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
65 lines
2.2 KiB
Python
65 lines
2.2 KiB
Python
import sqlite3
|
|
import numpy as np
|
|
from app.shared.config import Settings
|
|
from app.pipeline.recognition import unpack_embedding, cosine_similarity
|
|
|
|
|
|
def cluster_unknown_speakers(settings: Settings, db: sqlite3.Connection) -> None:
|
|
"""
|
|
Groups unknown/ambiguous utterances from the last 7 days by voice similarity.
|
|
Assigns speaker_id from an existing 'Unknown #N' speaker or creates a new one.
|
|
Threshold: 0.70 cosine similarity.
|
|
"""
|
|
CLUSTER_THRESHOLD = 0.70
|
|
|
|
rows = db.execute("""
|
|
SELECT id, embedding FROM utterances
|
|
WHERE match_status IN ('unknown', 'ambiguous')
|
|
AND embedding IS NOT NULL
|
|
AND date(start_time) >= date('now', '-7 days')
|
|
ORDER BY start_time ASC
|
|
""").fetchall()
|
|
|
|
if not rows:
|
|
return
|
|
|
|
clusters: list = []
|
|
cluster_centroids: list = []
|
|
|
|
for row in rows:
|
|
emb = unpack_embedding(row["embedding"])
|
|
best_cluster = None
|
|
best_sim = 0.0
|
|
for i, centroid in enumerate(cluster_centroids):
|
|
sim = cosine_similarity(emb, centroid)
|
|
if sim > best_sim and sim >= CLUSTER_THRESHOLD:
|
|
best_sim = sim
|
|
best_cluster = i
|
|
if best_cluster is not None:
|
|
clusters[best_cluster].append(row["id"])
|
|
n = len(clusters[best_cluster])
|
|
cluster_centroids[best_cluster] = (
|
|
(cluster_centroids[best_cluster] * (n - 1) + emb) / n
|
|
)
|
|
else:
|
|
clusters.append([row["id"]])
|
|
cluster_centroids.append(emb)
|
|
|
|
for i, cluster in enumerate(clusters):
|
|
if len(cluster) < 2:
|
|
continue
|
|
name = f"Unknown #{i + 1}"
|
|
existing = db.execute("SELECT id FROM speakers WHERE name=?", (name,)).fetchone()
|
|
if existing:
|
|
speaker_id = existing["id"]
|
|
else:
|
|
cur = db.execute("INSERT INTO speakers (name) VALUES (?)", (name,))
|
|
db.commit()
|
|
speaker_id = cur.lastrowid
|
|
for utt_id in cluster:
|
|
db.execute(
|
|
"UPDATE utterances SET speaker_id=?, match_status='ambiguous' WHERE id=?",
|
|
(speaker_id, utt_id),
|
|
)
|
|
db.commit()
|