Staging to Production #51

Merged
claude merged 165 commits from main into production 2026-06-08 18:28:46 +00:00
5 changed files with 49 additions and 27 deletions
Showing only changes of commit 87de7d3f77 - Show all commits
+1 -1
View File
@@ -36,7 +36,7 @@ public sealed class CvMatcherDbContext : DbContext
entity.Property(x => x.JobDocumentId).HasMaxLength(64).IsRequired(); entity.Property(x => x.JobDocumentId).HasMaxLength(64).IsRequired();
entity.Property(x => x.ResultJson).IsRequired(); entity.Property(x => x.ResultJson).IsRequired();
entity.Property(x => x.CreatedAt).HasDefaultValueSql("SYSUTCDATETIME()"); entity.Property(x => x.CreatedAt).HasDefaultValueSql("SYSUTCDATETIME()");
entity.HasIndex(x => new { x.CvDocumentId, x.JobDocumentId }).IsUnique(); entity.HasIndex(x => new { x.CvDocumentId, x.JobDocumentId, x.Language }).IsUnique();
}); });
modelBuilder.Entity<CvMatcherChatCacheEntity>(entity => modelBuilder.Entity<CvMatcherChatCacheEntity>(entity =>
@@ -1,4 +1,4 @@
// <auto-generated /> // <auto-generated />
using System; using System;
using CvMatcher.Data; using CvMatcher.Data;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@@ -12,7 +12,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace CvMatcher.Data.Migrations namespace CvMatcher.Data.Migrations
{ {
[DbContext(typeof(CvMatcherDbContext))] [DbContext(typeof(CvMatcherDbContext))]
[Migration("20260529111000_UpdateResultsUniqueConstraintToIncludeLanguage")] [Migration("20260601131043_UpdateResultsUniqueConstraintToIncludeLanguage")]
partial class UpdateResultsUniqueConstraintToIncludeLanguage partial class UpdateResultsUniqueConstraintToIncludeLanguage
{ {
/// <inheritdoc /> /// <inheritdoc />
@@ -80,7 +80,7 @@ namespace CvMatcher.Data.Migrations
b.Property<string>("Language") b.Property<string>("Language")
.IsRequired() .IsRequired()
.HasColumnType("nvarchar(max)"); .HasColumnType("nvarchar(450)");
b.Property<string>("ResultJson") b.Property<string>("ResultJson")
.IsRequired() .IsRequired()
@@ -1,5 +1,4 @@
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
using CvMatcher.Data;
#nullable disable #nullable disable
@@ -11,18 +10,23 @@ namespace CvMatcher.Data.Migrations
/// <inheritdoc /> /// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder) protected override void Up(MigrationBuilder migrationBuilder)
{ {
// The Language column was added in migration 20260524140335, but the unique constraint
// was never updated from (CvDocumentId, JobDocumentId) to include Language.
// This caused duplicate key violations when matching the same CV+Job in different languages.
migrationBuilder.DropIndex( migrationBuilder.DropIndex(
name: "IX_Results_CvDocumentId_JobDocumentId", name: "IX_Results_CvDocumentId_JobDocumentId",
schema: MigrationConstants.SchemaName, schema: "cvMatcher",
table: "Results"); table: "Results");
migrationBuilder.AlterColumn<string>(
name: "Language",
schema: "cvMatcher",
table: "Results",
type: "nvarchar(450)",
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(max)");
migrationBuilder.CreateIndex( migrationBuilder.CreateIndex(
name: "IX_Results_CvDocumentId_JobDocumentId_Language", name: "IX_Results_CvDocumentId_JobDocumentId_Language",
schema: MigrationConstants.SchemaName, schema: "cvMatcher",
table: "Results", table: "Results",
columns: new[] { "CvDocumentId", "JobDocumentId", "Language" }, columns: new[] { "CvDocumentId", "JobDocumentId", "Language" },
unique: true); unique: true);
@@ -33,12 +37,21 @@ namespace CvMatcher.Data.Migrations
{ {
migrationBuilder.DropIndex( migrationBuilder.DropIndex(
name: "IX_Results_CvDocumentId_JobDocumentId_Language", name: "IX_Results_CvDocumentId_JobDocumentId_Language",
schema: MigrationConstants.SchemaName, schema: "cvMatcher",
table: "Results"); table: "Results");
migrationBuilder.AlterColumn<string>(
name: "Language",
schema: "cvMatcher",
table: "Results",
type: "nvarchar(max)",
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(450)");
migrationBuilder.CreateIndex( migrationBuilder.CreateIndex(
name: "IX_Results_CvDocumentId_JobDocumentId", name: "IX_Results_CvDocumentId_JobDocumentId",
schema: MigrationConstants.SchemaName, schema: "cvMatcher",
table: "Results", table: "Results",
columns: new[] { "CvDocumentId", "JobDocumentId" }, columns: new[] { "CvDocumentId", "JobDocumentId" },
unique: true); unique: true);
@@ -1,4 +1,4 @@
// <auto-generated /> // <auto-generated />
using System; using System;
using CvMatcher.Data; using CvMatcher.Data;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@@ -77,7 +77,7 @@ namespace CvMatcher.Data.Migrations
b.Property<string>("Language") b.Property<string>("Language")
.IsRequired() .IsRequired()
.HasColumnType("nvarchar(max)"); .HasColumnType("nvarchar(450)");
b.Property<string>("ResultJson") b.Property<string>("ResultJson")
.IsRequired() .IsRequired()
@@ -88,7 +88,7 @@ namespace CvMatcher.Data.Migrations
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("CvDocumentId", "JobDocumentId") b.HasIndex("CvDocumentId", "JobDocumentId", "Language")
.IsUnique(); .IsUnique();
b.ToTable("Results", "cvMatcher"); b.ToTable("Results", "cvMatcher");
@@ -48,18 +48,27 @@ public sealed class EfMatcherRepository : IMatcherRepository
if (exists) return; if (exists) return;
_db.CvMatchResults.Add(new CvMatchResultEntity try
{ {
Id = Guid.NewGuid().ToString("N"), _db.CvMatchResults.Add(new CvMatchResultEntity
CvDocumentId = cvDocumentId, {
JobDocumentId = jobDocumentId, Id = Guid.NewGuid().ToString("N"),
Language = language, CvDocumentId = cvDocumentId,
ResultJson = JsonSerializer.Serialize(response, new JsonSerializerOptions(JsonSerializerDefaults.Web)), JobDocumentId = jobDocumentId,
Score = response.Score, Language = language,
CreatedAt = DateTime.UtcNow ResultJson = JsonSerializer.Serialize(response, new JsonSerializerOptions(JsonSerializerDefaults.Web)),
}); Score = response.Score,
CreatedAt = DateTime.UtcNow
});
await _db.SaveChangesAsync(ct); await _db.SaveChangesAsync(ct);
}
catch (DbUpdateException ex) when (ex.InnerException?.Message.Contains("IX_Results_CvDocumentId_JobDocumentId_Language") == true
|| ex.InnerException?.Message.Contains("unique") == true)
{
// Duplicate key violation: record was inserted between the AnyAsync check and SaveChangesAsync.
// This is safe to ignore — the match result already exists in the database.
}
} }
public async Task<string?> GetChatCompletionAsync(string cacheKey, CancellationToken ct) public async Task<string?> GetChatCompletionAsync(string cacheKey, CancellationToken ct)