Public Access
125 lines
4.4 KiB
C#
125 lines
4.4 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using MeetingAssistant.MeetingNotes;
|
|
|
|
namespace MeetingAssistant.Speakers;
|
|
|
|
public interface ISpeakerIdentityAttendeeCanonicalizer
|
|
{
|
|
Task<IReadOnlyList<string>> CanonicalizeAsync(
|
|
IReadOnlyList<string> attendees,
|
|
CancellationToken cancellationToken);
|
|
}
|
|
|
|
public sealed class SpeakerIdentityAttendeeCanonicalizer : ISpeakerIdentityAttendeeCanonicalizer
|
|
{
|
|
private readonly IDbContextFactory<SpeakerIdentityDbContext> dbContextFactory;
|
|
|
|
public SpeakerIdentityAttendeeCanonicalizer(IDbContextFactory<SpeakerIdentityDbContext> dbContextFactory)
|
|
{
|
|
this.dbContextFactory = dbContextFactory;
|
|
}
|
|
|
|
public async Task<IReadOnlyList<string>> CanonicalizeAsync(
|
|
IReadOnlyList<string> attendees,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var attendeeEntries = attendees
|
|
.Select(attendee => new
|
|
{
|
|
Raw = attendee.Trim(),
|
|
DisplayName = NormalizeName(attendee)
|
|
})
|
|
.Where(entry => !string.IsNullOrWhiteSpace(entry.Raw) && !string.IsNullOrWhiteSpace(entry.DisplayName))
|
|
.ToList();
|
|
if (attendeeEntries.Count == 0)
|
|
{
|
|
return [];
|
|
}
|
|
|
|
await using var context = await dbContextFactory.CreateDbContextAsync(cancellationToken);
|
|
await SpeakerIdentitySchema.EnsureCreatedOrUpdatedAsync(context, cancellationToken);
|
|
|
|
var identities = await context.SpeakerIdentities
|
|
.Include(identity => identity.Aliases)
|
|
.Include(identity => identity.References)
|
|
.ToListAsync(cancellationToken);
|
|
var identitiesByAcceptedName = identities
|
|
.OrderBy(identity => string.IsNullOrWhiteSpace(identity.CanonicalName))
|
|
.ThenByDescending(identity => identity.ReferenceCount)
|
|
.ThenByDescending(identity => identity.UpdatedAt)
|
|
.ThenBy(identity => identity.Id)
|
|
.SelectMany(identity => GetExactAcceptedNames(identity).Select(name => new { Name = name, Identity = identity }))
|
|
.GroupBy(entry => entry.Name, StringComparer.OrdinalIgnoreCase)
|
|
.ToDictionary(group => group.Key, group => group.First().Identity, StringComparer.OrdinalIgnoreCase);
|
|
|
|
var result = new List<string>();
|
|
var seenNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
var seenIdentityIds = new HashSet<int>();
|
|
foreach (var attendee in attendeeEntries)
|
|
{
|
|
if (identitiesByAcceptedName.TryGetValue(attendee.DisplayName!, out var identity))
|
|
{
|
|
var displayName = identity.GetDisplayName();
|
|
if (string.IsNullOrWhiteSpace(displayName) || !seenIdentityIds.Add(identity.Id))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (seenNames.Add(displayName))
|
|
{
|
|
result.Add(displayName);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if (seenNames.Add(attendee.Raw))
|
|
{
|
|
result.Add(attendee.Raw);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private static IEnumerable<string> GetExactAcceptedNames(SpeakerIdentity identity)
|
|
{
|
|
return new[] { identity.CanonicalName }
|
|
.Concat(identity.Aliases.Select(alias => alias.Name))
|
|
.Select(NormalizeName)
|
|
.Where(name => !string.IsNullOrWhiteSpace(name))
|
|
.Select(name => name!);
|
|
}
|
|
|
|
private static string? NormalizeName(string? name)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(name))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return MeetingAttendeeNames.NormalizeDisplayName(name);
|
|
}
|
|
}
|
|
|
|
public sealed class PassthroughSpeakerIdentityAttendeeCanonicalizer : ISpeakerIdentityAttendeeCanonicalizer
|
|
{
|
|
public static PassthroughSpeakerIdentityAttendeeCanonicalizer Instance { get; } = new();
|
|
|
|
private PassthroughSpeakerIdentityAttendeeCanonicalizer()
|
|
{
|
|
}
|
|
|
|
public Task<IReadOnlyList<string>> CanonicalizeAsync(
|
|
IReadOnlyList<string> attendees,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var distinctAttendees = attendees
|
|
.Select(attendee => attendee.Trim())
|
|
.Where(name => !string.IsNullOrWhiteSpace(name))
|
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
|
.ToList();
|
|
return Task.FromResult<IReadOnlyList<string>>(distinctAttendees);
|
|
}
|
|
}
|