using MeetingAssistant.MeetingNotes; namespace MeetingAssistant.Speakers; internal static class SpeakerIdentityNaming { public static IReadOnlySet GetAcceptedNames(SpeakerIdentity identity) { return new[] { identity.CanonicalName } .Concat(identity.Aliases.Select(alias => alias.Name)) .Concat(identity.CandidateNames.Select(candidate => candidate.Name)) .Where(name => !string.IsNullOrWhiteSpace(name)) .Select(name => name!.Trim()) .ToHashSet(StringComparer.OrdinalIgnoreCase); } public static bool MatchesAnyAcceptedName( SpeakerIdentity identity, IReadOnlySet names) { return GetAcceptedNames(identity).Any(names.Contains); } public static bool MatchesAttendees( SpeakerIdentity identity, IReadOnlyList attendees) { var attendeeSet = attendees.ToHashSet(StringComparer.OrdinalIgnoreCase); return GetAcceptedNames(identity).Any(attendeeSet.Contains); } public static IReadOnlyList NormalizeAttendees(IEnumerable attendees) { return attendees .Select(MeetingAttendeeNames.NormalizeDisplayName) .Where(attendee => !string.IsNullOrWhiteSpace(attendee)) .Distinct(StringComparer.OrdinalIgnoreCase) .Order(StringComparer.OrdinalIgnoreCase) .ToList(); } public static bool IsDiarizedSpeakerLabel(string speaker) { var normalized = speaker.Trim(); return normalized.Equals("Unknown", StringComparison.OrdinalIgnoreCase) || normalized.StartsWith("Guest", StringComparison.OrdinalIgnoreCase) || normalized.StartsWith("Speaker", StringComparison.OrdinalIgnoreCase); } public static bool UpdateCandidateNames( SpeakerIdentity identity, IReadOnlyList attendees) { if (!string.IsNullOrWhiteSpace(identity.CanonicalName) || attendees.Count == 0) { return false; } var currentCandidates = identity.CandidateNames .Select(candidate => candidate.Name) .ToHashSet(StringComparer.OrdinalIgnoreCase); var fallbackAliasCandidate = currentCandidates.Count == 1 ? currentCandidates.Single() : null; var aliasToCandidate = identity.Aliases .Where(alias => !string.IsNullOrWhiteSpace(alias.Name)) .SelectMany(alias => currentCandidates.Select(candidate => new { Alias = alias.Name, Candidate = candidate })) .Where(pair => string.Equals(pair.Alias, pair.Candidate, StringComparison.OrdinalIgnoreCase) || pair.Alias.Contains(pair.Candidate, StringComparison.OrdinalIgnoreCase) || pair.Candidate.Contains(pair.Alias, StringComparison.OrdinalIgnoreCase)) .ToDictionary(pair => pair.Alias, pair => pair.Candidate, StringComparer.OrdinalIgnoreCase); var intersection = attendees .Select(attendee => currentCandidates.Contains(attendee) ? attendee : aliasToCandidate.GetValueOrDefault(attendee) ?? (identity.Aliases.Any(alias => string.Equals(alias.Name, attendee, StringComparison.OrdinalIgnoreCase)) ? fallbackAliasCandidate : null)) .Where(candidate => !string.IsNullOrWhiteSpace(candidate)) .Select(candidate => candidate!) .Distinct(StringComparer.OrdinalIgnoreCase) .Order(StringComparer.OrdinalIgnoreCase) .ToList(); var resetToAttendees = intersection.Count == 0; SetCandidates(identity, resetToAttendees ? attendees : intersection); if (intersection.Count == 1) { identity.CanonicalName = intersection[0]; } return resetToAttendees; } public static void SetCandidates( SpeakerIdentity identity, IEnumerable candidates) { identity.CandidateNames.Clear(); identity.CandidateNames.AddRange(candidates .Where(candidate => !string.IsNullOrWhiteSpace(candidate)) .Distinct(StringComparer.OrdinalIgnoreCase) .Order(StringComparer.OrdinalIgnoreCase) .Select(candidate => new SpeakerCandidateName { Name = candidate })); } }