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()