feat: persist candidate fit and focus plan, stop re-running on every open
Extends the interview-prep persistence pattern (previous commit) to the other two AI-generated per-job outputs that were re-running their full AI call chain on every tab open: candidate-fit (4 AI calls) and focus-plan (4 AI calls). Across all three tabs that's 9 AI calls fired every single time a user revisits a job's AI workspace tabs. Generalized into AiWorkspaceNote (OwnerUserId, JobApplicationId, NoteType, ResultJson) rather than duplicating InterviewPrepNote's per-field-column shape: CandidateFitDto and FocusPlanDto are irregular and nested (up to 13 fields including a nested guidance object), where per-field columns would be unreasonable. One table, keyed by note type, serving both. Same rules as interview prep: reuse across calls, regenerate when the attachment selection changes, regenerate on explicit refresh. Frontend gets the same "Regenerate" button on both tabs. 3 new tests (persist+reuse for both, refresh for candidate-fit). Verified against the real dev DB.
This commit is contained in:
@@ -680,12 +680,34 @@ public static class StartupInitializationExtensions
|
||||
Exec(c, """CREATE UNIQUE INDEX IF NOT EXISTS "IX_InterviewPrepNotes_OwnerUserId_JobApplicationId" ON "InterviewPrepNotes" ("OwnerUserId", "JobApplicationId");""");
|
||||
}
|
||||
|
||||
// Generic AI workspace note persistence (candidate fit, focus plan) -- same
|
||||
// rationale as EnsureInterviewPrepNotesTable, generalized for DTOs too irregular
|
||||
// for per-field columns.
|
||||
static void EnsureAiWorkspaceNotesTable(DbConnection c)
|
||||
{
|
||||
Exec(c, """
|
||||
CREATE TABLE IF NOT EXISTS "AiWorkspaceNotes" (
|
||||
"Id" INTEGER NOT NULL CONSTRAINT "PK_AiWorkspaceNotes" PRIMARY KEY AUTOINCREMENT,
|
||||
"OwnerUserId" TEXT NOT NULL,
|
||||
"JobApplicationId" INTEGER NOT NULL,
|
||||
"NoteType" TEXT NOT NULL,
|
||||
"AttachmentContextSignature" TEXT NOT NULL,
|
||||
"ResultJson" TEXT NOT NULL,
|
||||
"GeneratedAtUtc" TEXT NOT NULL,
|
||||
CONSTRAINT "FK_AiWorkspaceNotes_JobApplications_JobApplicationId" FOREIGN KEY ("JobApplicationId") REFERENCES "JobApplications" ("Id") ON DELETE CASCADE
|
||||
);
|
||||
""");
|
||||
|
||||
Exec(c, """CREATE UNIQUE INDEX IF NOT EXISTS "IX_AiWorkspaceNotes_OwnerUserId_JobApplicationId_NoteType" ON "AiWorkspaceNotes" ("OwnerUserId", "JobApplicationId", "NoteType");""");
|
||||
}
|
||||
|
||||
EnsureGmailConnectionsTable(conn);
|
||||
EnsureMicrosoftGraphConnectionsTable(conn);
|
||||
EnsureImapConnectionsTable(conn);
|
||||
EnsureCvTables(conn);
|
||||
EnsureCareerProfileTables(conn);
|
||||
EnsureInterviewPrepNotesTable(conn);
|
||||
EnsureAiWorkspaceNotesTable(conn);
|
||||
|
||||
// Legacy DB signature: migration history exists (AddCorrespondence applied), but 20260310195000 not recorded,
|
||||
// and at least one of the new columns already exists.
|
||||
@@ -837,6 +859,15 @@ public static class StartupInitializationExtensions
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
EnsureMySqlAutoIncrementPrimaryKey(conn, "AiWorkspaceNotes", "Id");
|
||||
|
||||
if (!MySqlIndexExists(conn, "AiWorkspaceNotes", "IX_AiWorkspaceNotes_OwnerUserId_JobApplicationId_NoteType"))
|
||||
{
|
||||
using var cmd = conn.CreateCommand();
|
||||
cmd.CommandText = "CREATE UNIQUE INDEX `IX_AiWorkspaceNotes_OwnerUserId_JobApplicationId_NoteType` ON `AiWorkspaceNotes` (`OwnerUserId`, `JobApplicationId`, `NoteType`);";
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
// Ad-hoc columns for the tables Migrate() creates (Companies/JobApplications/
|
||||
// Correspondences/Attachments) -- re-run once more after Migrate() below via
|
||||
// ReconcileCoreAppColumnsMySql, in case this is a brand-new database.
|
||||
@@ -1116,6 +1147,24 @@ public static class StartupInitializationExtensions
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
// Generic AI workspace note persistence (candidate fit, focus plan).
|
||||
if (!HasMySqlTable(conn, "AiWorkspaceNotes"))
|
||||
{
|
||||
using var cmd = conn.CreateCommand();
|
||||
cmd.CommandText = @"CREATE TABLE IF NOT EXISTS `AiWorkspaceNotes` (
|
||||
`Id` int NOT NULL AUTO_INCREMENT,
|
||||
`OwnerUserId` varchar(255) NOT NULL,
|
||||
`JobApplicationId` int NOT NULL,
|
||||
`NoteType` varchar(50) NOT NULL,
|
||||
`AttachmentContextSignature` longtext NOT NULL,
|
||||
`ResultJson` longtext NOT NULL,
|
||||
`GeneratedAtUtc` datetime(6) NOT NULL,
|
||||
PRIMARY KEY (`Id`),
|
||||
CONSTRAINT `FK_AiWorkspaceNotes_JobApplications_JobApplicationId` FOREIGN KEY (`JobApplicationId`) REFERENCES `JobApplications` (`Id`) ON DELETE CASCADE
|
||||
);";
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
if (!MySqlIndexExists(conn, "Companies", "IX_Companies_OwnerUserId"))
|
||||
{
|
||||
using var cmd = conn.CreateCommand();
|
||||
|
||||
Reference in New Issue
Block a user