feat: persist interview prep instead of regenerating on every open

Interview prep re-ran its AI call every time the tab opened -- flagged
in the product teardown as work evaporating on every re-open (cost,
latency, and non-determinism for no reason). GetInterviewPrep now
persists one note per job application and reuses it on subsequent
reads, only regenerating when the selected attachment context changes
or a refresh is explicitly requested.

- InterviewPrepNote: one row per (owner, job), keyed additionally by
  an attachment-selection fingerprint so picking different attachments
  correctly triggers a fresh brief without needing an explicit flag.
- GetInterviewPrep gained a `refresh` query param; the frontend adds a
  small "Regenerate" button as the explicit escape hatch for when the
  underlying job/notes have changed since the note was written.
- Both SQLite (dev) and MySQL/MariaDB (prod) reconciler dialects.
- 3 new tests: reuse across calls, refresh regenerates, attachment
  context change regenerates. Verified against the real dev DB.
This commit is contained in:
cesnimda
2026-07-12 15:34:07 +02:00
parent 102938c28b
commit 5916f09852
6 changed files with 287 additions and 2 deletions
+15
View File
@@ -30,6 +30,7 @@ namespace JobTrackerApi.Data
public DbSet<TailoredCvDraft> TailoredCvDrafts => Set<TailoredCvDraft>();
public DbSet<CareerProfile> CareerProfiles => Set<CareerProfile>();
public DbSet<CareerProfileVersion> CareerProfileVersions => Set<CareerProfileVersion>();
public DbSet<InterviewPrepNote> InterviewPrepNotes => Set<InterviewPrepNote>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
@@ -164,6 +165,20 @@ namespace JobTrackerApi.Data
.WithMany()
.HasForeignKey(x => x.CareerProfileId)
.OnDelete(DeleteBehavior.Cascade);
// Interview prep persistence (career-workspace-implementation-roadmap.md Phase F5).
modelBuilder.Entity<InterviewPrepNote>()
.HasQueryFilter(x => CurrentUserId != null && x.OwnerUserId == CurrentUserId);
modelBuilder.Entity<InterviewPrepNote>()
.HasIndex(x => new { x.OwnerUserId, x.JobApplicationId })
.IsUnique();
modelBuilder.Entity<InterviewPrepNote>()
.HasOne(x => x.JobApplication)
.WithMany()
.HasForeignKey(x => x.JobApplicationId)
.OnDelete(DeleteBehavior.Cascade);
}
}
}