Make meeting lifecycle stateful

This commit is contained in:
2026-05-27 12:55:17 +02:00
parent d607b957bb
commit e85274829a
91 changed files with 4076 additions and 479 deletions
+79 -2
View File
@@ -1,4 +1,5 @@
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations.Schema;
namespace MeetingAssistant.Speakers;
@@ -8,8 +9,6 @@ public sealed class SpeakerIdentity
public string? CanonicalName { get; set; }
public int TranscriptionCount { get; set; }
public DateTimeOffset CreatedAt { get; set; }
public DateTimeOffset UpdatedAt { get; set; }
@@ -20,6 +19,11 @@ public sealed class SpeakerIdentity
public List<SpeakerSnippet> Snippets { get; set; } = [];
public List<SpeakerIdentityReference> References { get; set; } = [];
[NotMapped]
public int ReferenceCount => References.Count;
public string? GetDisplayName()
{
if (!string.IsNullOrWhiteSpace(CanonicalName))
@@ -35,6 +39,65 @@ public sealed class SpeakerIdentity
}
}
public sealed class SpeakerIdentityReference
{
public int Id { get; set; }
public int SpeakerIdentityId { get; set; }
public SpeakerIdentity? SpeakerIdentity { get; set; }
public string MeetingNotePath { get; set; } = "";
public string TranscriptPath { get; set; } = "";
public DateTimeOffset CreatedAt { get; set; }
}
public static class SpeakerIdentityReferences
{
public static SpeakerIdentityReference Create(
string meetingNotePath,
string transcriptPath,
DateTimeOffset createdAt)
{
return new SpeakerIdentityReference
{
MeetingNotePath = meetingNotePath,
TranscriptPath = transcriptPath,
CreatedAt = createdAt
};
}
public static void AddIfMissing(
SpeakerIdentity identity,
SpeakerIdentityReference reference,
DateTimeOffset? createdAt = null)
{
if (IsEmpty(reference) || identity.References.Any(existing => IsSame(existing, reference)))
{
return;
}
identity.References.Add(Create(
reference.MeetingNotePath,
reference.TranscriptPath,
createdAt ?? reference.CreatedAt));
}
private static bool IsEmpty(SpeakerIdentityReference reference)
{
return string.IsNullOrWhiteSpace(reference.MeetingNotePath) &&
string.IsNullOrWhiteSpace(reference.TranscriptPath);
}
private static bool IsSame(SpeakerIdentityReference first, SpeakerIdentityReference second)
{
return string.Equals(first.MeetingNotePath, second.MeetingNotePath, StringComparison.OrdinalIgnoreCase) &&
string.Equals(first.TranscriptPath, second.TranscriptPath, StringComparison.OrdinalIgnoreCase);
}
}
public sealed class SpeakerCandidateName
{
public int Id { get; set; }
@@ -85,10 +148,15 @@ public sealed class SpeakerIdentityDbContext : DbContext
public DbSet<SpeakerSnippet> SpeakerSnippets => Set<SpeakerSnippet>();
public DbSet<SpeakerIdentityReference> SpeakerIdentityReferences => Set<SpeakerIdentityReference>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<SpeakerIdentity>(entity =>
{
entity.Property<int>("TranscriptionCount")
.HasDefaultValue(0);
entity.HasMany(identity => identity.CandidateNames)
.WithOne(candidate => candidate.SpeakerIdentity)
.HasForeignKey(candidate => candidate.SpeakerIdentityId)
@@ -103,6 +171,11 @@ public sealed class SpeakerIdentityDbContext : DbContext
.WithOne(snippet => snippet.SpeakerIdentity)
.HasForeignKey(snippet => snippet.SpeakerIdentityId)
.OnDelete(DeleteBehavior.Cascade);
entity.HasMany(identity => identity.References)
.WithOne(reference => reference.SpeakerIdentity)
.HasForeignKey(reference => reference.SpeakerIdentityId)
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity<SpeakerCandidateName>()
@@ -112,5 +185,9 @@ public sealed class SpeakerIdentityDbContext : DbContext
modelBuilder.Entity<SpeakerAlias>()
.HasIndex(alias => new { alias.SpeakerIdentityId, alias.Name })
.IsUnique();
modelBuilder.Entity<SpeakerIdentityReference>()
.HasIndex(reference => new { reference.SpeakerIdentityId, reference.MeetingNotePath, reference.TranscriptPath })
.IsUnique();
}
}