v2.2.28-beta2 — voiceprint identity fixes + selfcheck threshold tune
Follows the 2026-09-18 voiceprint DB audit which found 23 base names
duplicated as 'Name / Name 2 / Name 3' across meetings. Root cause:
same-person WeSpeaker embeddings drift 0.05-0.55 cos across sessions
(mic/room/codec differences), well below the 0.72 hard-match threshold
— so auto-enroll silently spawned fresh guest-name records with
numeric suffixes on every meeting.
Four coordinated fixes:
Embedding stability
Canonical pre-WeSpeaker normalization: every embed() runs input
through HP-60Hz + energy-gated RMS to -20 dBFS + safety clip.
Decouples voice identity from recording gain / rumble / room —
same voice through headset vs loopback vs conf-room mic should
now produce embeddings that cluster tighter.
Matching
Multi-centroid via top-K mean of per-sample cosines instead of
centroid-only. K=3 (env SCRIBEAIR_MATCH_TOPK). Safety guards:
min 8 samples required before multi kicks in; boost capped at
+0.20 above the aggregate cos so a polluted DB record can't
false-positive uncapped. identify_clusters + new identify_loose
both use it. Env SCRIBEAIR_MATCH_MULTI=0 for legacy behaviour.
Enrollment
Identify-before-enroll gate: before store.enroll() creates a new
speaker record, ask identify_loose(centroid, 0.45) whether an
existing voice matches at a loose threshold (below the 0.72 hard
tier). If yes, append the cluster's samples to that speaker via
add_sample() instead of spawning 'Name 2'. Threshold sits above
typical different-speaker cos (0.05-0.30 on our DB) and catches
drift-affected same-person matches like Максим ↔ Максим 2 (0.549).
Env SCRIBEAIR_ENROLL_LOOSE_THR=0 disables.
Diarizer split guard
Cluster-merge threshold in _resolve_speaker_names lowered
0.85 → 0.75. Within a single session the audio path is identical,
so same-speaker clusters group tighter than the cross-session
0.85 anticipated. Catches same-day splits like Павел 2 + Павел 3
(measured cos 0.875) without collapsing distinct in-session voices.
Also
* selfcheck C7 (Stage O RTF) gate 0.05 → 0.06 to eliminate the
intermittent false-fail hovering at 0.049-0.053 under ambient
GPU load. Still 4-12× headroom over healthy CUDA.
* tools/consolidate_voiceprints.py — one-shot cleanup for
existing duplicate speaker records. dry-run by default; --apply
backs up the DB and merges only same-name pairs with cos ≥ 0.72.
Verified
* pytest 47/47 pass on affected test modules (test_diarization,
test_transcriber, test_finalize_v2_dual_track, test_audio_processor)
* dev selfcheck --strict 8/8 pass on venv-gpu (CUDA)