Public Access
Make meeting lifecycle stateful
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user