namespace MeetingAssistant.Speakers; internal static class SpeakerIdentityMerger { public static void MergeIntoAndPrune( SpeakerIdentity target, SpeakerIdentity source, int maxSnippets, int maxVoiceVectors, ResemblyzerVoiceVectorOutlierPruner outlierPruner) { outlierPruner.Prune(target); outlierPruner.Prune(source); MergeInto(target, source, maxSnippets, maxVoiceVectors); outlierPruner.Prune(target); } public static void MergeInto( SpeakerIdentity target, SpeakerIdentity source, int maxSnippets, int maxVoiceVectors = int.MaxValue) { AddAlias(target, source.CanonicalName); foreach (var alias in source.Aliases) { AddAlias(target, alias.Name); } foreach (var candidate in source.CandidateNames) { AddAlias(target, candidate.Name); } foreach (var reference in source.References) { SpeakerIdentityReferences.AddIfMissing(target, reference); } var retainedSnippets = target.Snippets .Concat(source.Snippets) .OrderBy(snippet => StableSnippetKey(snippet.WavBytes)) .Take(Math.Max(1, maxSnippets)) .Select(snippet => new SpeakerSnippet { WavBytes = snippet.WavBytes, CreatedAt = snippet.CreatedAt }) .ToList(); target.Snippets.Clear(); target.Snippets.AddRange(retainedSnippets); var retainedVectors = target.VoiceVectors .Concat(source.VoiceVectors) .GroupBy(vector => vector.Fingerprint, StringComparer.Ordinal) .Select(group => group.OrderByDescending(vector => vector.CreatedAt).First()) .OrderByDescending(vector => vector.CreatedAt) .ThenBy(vector => vector.Fingerprint, StringComparer.Ordinal) .Take(Math.Max(1, maxVoiceVectors)) .Select(vector => new SpeakerVoiceVector { SpeakerIdentity = target, ModelId = vector.ModelId, Dimensions = vector.Dimensions, VectorBytes = vector.VectorBytes.ToArray(), Fingerprint = vector.Fingerprint, CreatedAt = vector.CreatedAt }) .ToList(); target.VoiceVectors.Clear(); target.VoiceVectors.AddRange(retainedVectors); target.UpdatedAt = DateTimeOffset.UtcNow; } private static void AddAlias(SpeakerIdentity identity, string? alias) { if (string.IsNullOrWhiteSpace(alias) || string.Equals(identity.CanonicalName, alias, StringComparison.OrdinalIgnoreCase) || identity.Aliases.Any(existing => string.Equals(existing.Name, alias, StringComparison.OrdinalIgnoreCase))) { return; } identity.Aliases.Add(new SpeakerAlias { Name = alias.Trim() }); } private static int StableSnippetKey(byte[] snippet) { var hash = new HashCode(); foreach (var value in snippet) { hash.Add(value); } return hash.ToHashCode(); } }