feat: cron jobs — daily summary, speaker clustering, clip purge
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
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()
|
||||
Reference in New Issue
Block a user