fix(career): preserve reviewed profile values
Keep extraction heuristics out of manual save, version, and import paths so reviewed locations, URLs, dates, and languages round-trip unchanged.
This commit is contained in:
@@ -53,7 +53,7 @@ public sealed class CareerProfileService : ICareerProfileService
|
||||
AssignStableIds(profile);
|
||||
NormalizeDates(profile);
|
||||
|
||||
var json = StructuredCvProfileJson.Serialize(profile);
|
||||
var json = StructuredCvProfileJson.SerializePersisted(profile);
|
||||
|
||||
var existing = await _db.CareerProfiles.FirstOrDefaultAsync(x => x.OwnerUserId == ownerUserId, cancellationToken);
|
||||
if (existing is null)
|
||||
@@ -109,7 +109,7 @@ public sealed class CareerProfileService : ICareerProfileService
|
||||
// Backfill a pre-Phase-3 profile from its blob, once, before reading relationally.
|
||||
if (!hasRelational && !string.IsNullOrWhiteSpace(profile.ProfileJson))
|
||||
{
|
||||
var fromBlob = StructuredCvProfileJson.Deserialize(profile.ProfileJson);
|
||||
var fromBlob = StructuredCvProfileJson.DeserializePersisted(profile.ProfileJson);
|
||||
AssignStableIds(fromBlob);
|
||||
NormalizeDates(fromBlob);
|
||||
await SyncRelationalChildrenAsync(profile.Id, ownerUserId, fromBlob, cancellationToken);
|
||||
@@ -142,7 +142,7 @@ public sealed class CareerProfileService : ICareerProfileService
|
||||
if (experiences.Count == 0 && education.Count == 0 && skills.Count == 0 && projects.Count == 0
|
||||
&& certifications.Count == 0 && languages.Count == 0 && !string.IsNullOrWhiteSpace(profile.ProfileJson))
|
||||
{
|
||||
return StructuredCvProfileJson.Deserialize(profile.ProfileJson);
|
||||
return StructuredCvProfileJson.DeserializePersisted(profile.ProfileJson);
|
||||
}
|
||||
|
||||
return CareerProfileMapper.ToStructured(profile.LongTailJson, experiences, education, skills, projects, certifications, languages);
|
||||
@@ -172,7 +172,7 @@ public sealed class CareerProfileService : ICareerProfileService
|
||||
|
||||
// Re-save the old snapshot as a new version. Non-destructive: the current state stays in
|
||||
// history, so a restore can itself be undone by restoring the version before it.
|
||||
var restored = StructuredCvProfileJson.Deserialize(target.ProfileJson);
|
||||
var restored = StructuredCvProfileJson.DeserializePersisted(target.ProfileJson);
|
||||
return await SaveVersionAsync(ownerUserId, restored, $"restore:v{version}", cancellationToken);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,26 +25,47 @@ public static class CareerProfileValidator
|
||||
|
||||
foreach (var j in p.Jobs)
|
||||
{
|
||||
if (Over(j.Title, MaxShortField) || Over(j.Company, MaxShortField) || Over(j.Location, MaxShortField))
|
||||
if (Over(j.Id, MaxShortField) || Over(j.Title, MaxShortField) || Over(j.Company, MaxShortField)
|
||||
|| Over(j.Location, MaxShortField) || Over(j.Start, MaxShortField) || Over(j.End, MaxShortField))
|
||||
return "An experience field exceeds the allowed length.";
|
||||
if (j.Bullets.Count > MaxListEntries || j.Skills.Count > MaxListEntries) return "An experience has too many bullets/skills.";
|
||||
if (j.Bullets.Any(b => Over(b, MaxLongField))) return "An experience bullet is too long.";
|
||||
if (j.Bullets.Any(b => Over(b, MaxLongField)) || j.Skills.Any(s => Over(s, MaxShortField))) return "An experience bullet or skill is too long.";
|
||||
}
|
||||
foreach (var e in p.Education)
|
||||
{
|
||||
if (Over(e.Qualification, MaxShortField) || Over(e.Institution, MaxShortField)) return "An education field exceeds the allowed length.";
|
||||
if (Over(e.Id, MaxShortField) || Over(e.Qualification, MaxShortField) || Over(e.QualificationLevel, MaxShortField)
|
||||
|| Over(e.Institution, MaxShortField) || Over(e.Location, MaxShortField)
|
||||
|| Over(e.Start, MaxShortField) || Over(e.End, MaxShortField)) return "An education field exceeds the allowed length.";
|
||||
if (e.Details.Count > MaxListEntries) return "An education entry has too many details.";
|
||||
if (e.Details.Any(detail => Over(detail, MaxLongField))) return "An education detail is too long.";
|
||||
}
|
||||
foreach (var pr in p.Projects)
|
||||
{
|
||||
if (Over(pr.Name, MaxShortField) || Over(pr.Role, MaxShortField)) return "A project field exceeds the allowed length.";
|
||||
if (Over(pr.Id, MaxShortField) || Over(pr.Name, MaxShortField) || Over(pr.Role, MaxShortField)
|
||||
|| Over(pr.Location, MaxShortField) || Over(pr.Start, MaxShortField) || Over(pr.End, MaxShortField)) return "A project field exceeds the allowed length.";
|
||||
if (pr.Bullets.Count > MaxListEntries || pr.Skills.Count > MaxListEntries) return "A project has too many bullets/skills.";
|
||||
if (pr.Bullets.Any(bullet => Over(bullet, MaxLongField)) || pr.Skills.Any(skill => Over(skill, MaxShortField))) return "A project bullet or skill is too long.";
|
||||
}
|
||||
foreach (var certification in p.Certifications)
|
||||
{
|
||||
if (Over(certification.Id, MaxShortField) || Over(certification.Name, MaxShortField)
|
||||
|| Over(certification.Issuer, MaxShortField) || Over(certification.Location, MaxShortField)
|
||||
|| Over(certification.Date, MaxShortField)) return "A certification field exceeds the allowed length.";
|
||||
if (certification.Details.Count > MaxListEntries) return "A certification has too many details.";
|
||||
if (certification.Details.Any(detail => Over(detail, MaxLongField))) return "A certification detail is too long.";
|
||||
}
|
||||
foreach (var language in p.Languages)
|
||||
{
|
||||
if (Over(language.Name, MaxShortField) || Over(language.Level, MaxShortField) || Over(language.Notes, MaxLongField))
|
||||
return "A language field exceeds the allowed length.";
|
||||
}
|
||||
foreach (var s in p.Skills)
|
||||
if (Over(s, MaxShortField)) return "A skill entry is too long.";
|
||||
|
||||
if (Over(p.Contact.FullName, MaxShortField) || Over(p.Contact.Email, MaxShortField)
|
||||
|| Over(p.Contact.Headline, MaxShortField) || Over(p.Contact.Location, MaxShortField))
|
||||
|| Over(p.Contact.Headline, MaxShortField) || Over(p.Contact.Phone, MaxShortField)
|
||||
|| Over(p.Contact.Location, MaxShortField) || Over(p.Contact.Website, MaxShortField)
|
||||
|| Over(p.Contact.LinkedIn, MaxShortField))
|
||||
return "A contact field exceeds the allowed length.";
|
||||
|
||||
return null;
|
||||
|
||||
@@ -81,7 +81,7 @@ public sealed class CvProfileDiffService : ICvProfileDiffService
|
||||
|
||||
public StructuredCvProfile Merge(StructuredCvProfile current, StructuredCvProfile extracted, IReadOnlySet<string>? acceptedLowConfidenceIds = null)
|
||||
{
|
||||
var merged = StructuredCvProfileJson.Deserialize(StructuredCvProfileJson.Serialize(current ?? new StructuredCvProfile()));
|
||||
var merged = StructuredCvProfileJson.DeserializePersisted(StructuredCvProfileJson.SerializePersisted(current ?? new StructuredCvProfile()));
|
||||
extracted = FilterLowConfidence(extracted ?? new StructuredCvProfile(), acceptedLowConfidenceIds);
|
||||
|
||||
MergeContact(merged.Contact, extracted.Contact);
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace JobTrackerApi.Services
|
||||
|
||||
public static string BuildStructuredCvContext(ApplicationUser? user)
|
||||
{
|
||||
var structured = StructuredCvProfileJson.Deserialize(user?.ProfileCvStructureJson);
|
||||
var structured = StructuredCvProfileJson.DeserializePersisted(user?.ProfileCvStructureJson);
|
||||
var blocks = new List<string>();
|
||||
|
||||
var contactLines = new List<string>();
|
||||
@@ -102,7 +102,7 @@ namespace JobTrackerApi.Services
|
||||
|
||||
public static string BuildCvSearchCorpus(ApplicationUser? user)
|
||||
{
|
||||
var structured = StructuredCvProfileJson.Deserialize(user?.ProfileCvStructureJson);
|
||||
var structured = StructuredCvProfileJson.DeserializePersisted(user?.ProfileCvStructureJson);
|
||||
var parts = new List<string>();
|
||||
if (!string.IsNullOrWhiteSpace(user?.ProfileCvText)) parts.Add(user.ProfileCvText!);
|
||||
if (!string.IsNullOrWhiteSpace(structured.Contact.Headline)) parts.Add(structured.Contact.Headline!);
|
||||
|
||||
Reference in New Issue
Block a user