feat(career): add factual evidence bank
This commit is contained in:
@@ -138,6 +138,7 @@ public sealed class AccountDataExportService(
|
||||
var careerProjects = await db.CareerProjects.IgnoreQueryFilters().AsNoTracking().Where(item => item.OwnerUserId == ownerUserId).OrderBy(item => item.SortOrder).ToListAsync(cancellationToken);
|
||||
var careerCertifications = await db.CareerCertifications.IgnoreQueryFilters().AsNoTracking().Where(item => item.OwnerUserId == ownerUserId).OrderBy(item => item.SortOrder).ToListAsync(cancellationToken);
|
||||
var careerLanguages = await db.CareerLanguages.IgnoreQueryFilters().AsNoTracking().Where(item => item.OwnerUserId == ownerUserId).OrderBy(item => item.SortOrder).ToListAsync(cancellationToken);
|
||||
var careerEvidence = await db.CareerEvidence.IgnoreQueryFilters().AsNoTracking().Where(item => item.OwnerUserId == ownerUserId).OrderBy(item => item.Id).ToListAsync(cancellationToken);
|
||||
await AddJsonAsync("data/career.json", new
|
||||
{
|
||||
Profile = careerProfile,
|
||||
@@ -148,7 +149,8 @@ public sealed class AccountDataExportService(
|
||||
Projects = careerProjects,
|
||||
Certifications = careerCertifications,
|
||||
Languages = careerLanguages,
|
||||
}, (careerProfileId is null ? 0 : 1) + careerVersions.Count + careerExperiences.Count + careerEducation.Count + careerSkills.Count + careerProjects.Count + careerCertifications.Count + careerLanguages.Count);
|
||||
Evidence = careerEvidence,
|
||||
}, (careerProfileId is null ? 0 : 1) + careerVersions.Count + careerExperiences.Count + careerEducation.Count + careerSkills.Count + careerProjects.Count + careerCertifications.Count + careerLanguages.Count + careerEvidence.Count);
|
||||
|
||||
var variants = await db.CvVariants.IgnoreQueryFilters().AsNoTracking().Where(item => item.OwnerUserId == ownerUserId).OrderBy(item => item.Id).ToListAsync(cancellationToken);
|
||||
var variantVersions = await db.CvVariantVersions.IgnoreQueryFilters().AsNoTracking().Where(item => item.OwnerUserId == ownerUserId).OrderBy(item => item.Id).ToListAsync(cancellationToken);
|
||||
|
||||
@@ -248,6 +248,7 @@ public sealed class AccountDeletionService(
|
||||
deleted += await db.CareerProjects.IgnoreQueryFilters().Where(item => item.OwnerUserId == owner).ExecuteDeleteAsync(cancellationToken);
|
||||
deleted += await db.CareerCertifications.IgnoreQueryFilters().Where(item => item.OwnerUserId == owner).ExecuteDeleteAsync(cancellationToken);
|
||||
deleted += await db.CareerLanguages.IgnoreQueryFilters().Where(item => item.OwnerUserId == owner).ExecuteDeleteAsync(cancellationToken);
|
||||
deleted += await db.CareerEvidence.IgnoreQueryFilters().Where(item => item.OwnerUserId == owner).ExecuteDeleteAsync(cancellationToken);
|
||||
deleted += await db.CareerProfileVersions.IgnoreQueryFilters().Where(item => item.OwnerUserId == owner).ExecuteDeleteAsync(cancellationToken);
|
||||
deleted += await db.CareerProfiles.IgnoreQueryFilters().Where(item => item.OwnerUserId == owner).ExecuteDeleteAsync(cancellationToken);
|
||||
deleted += await db.CvExtractionRuns.IgnoreQueryFilters().Where(item => item.OwnerUserId == owner).ExecuteDeleteAsync(cancellationToken);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using JobTrackerApi.Data;
|
||||
using JobTrackerApi.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace JobTrackerApi.Services;
|
||||
|
||||
@@ -176,6 +177,26 @@ public sealed class ApplicationAssetsService : IApplicationAssetsService
|
||||
match.RelevantProjects.Select(p => p.Subtitle is null ? p.Title : $"{p.Title} — {p.Subtitle}").ToList()));
|
||||
}
|
||||
|
||||
var evidenceTerms = match.MatchedSkills.Concat(analysis.Keywords)
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x)).Select(x => x.Trim()).Distinct(StringComparer.OrdinalIgnoreCase).ToList();
|
||||
var evidence = await _db.CareerEvidence.AsNoTracking()
|
||||
.Where(x => x.OwnerUserId == ownerUserId && x.IsVerified)
|
||||
.OrderByDescending(x => x.UpdatedAtUtc)
|
||||
.ToListAsync(ct);
|
||||
var relevantEvidence = evidence.Where(item =>
|
||||
{
|
||||
var searchable = $"{item.Title} {item.Statement} {string.Join(' ', EvidenceTags(item.TagsJson))}";
|
||||
return evidenceTerms.Any(term => searchable.Contains(term, StringComparison.OrdinalIgnoreCase));
|
||||
}).Take(6).Select(item => $"{item.Title} — {item.Statement}").ToList();
|
||||
if (relevantEvidence.Count > 0)
|
||||
{
|
||||
suggestions.Add(new TailoringSuggestionDto(
|
||||
"career-evidence",
|
||||
"Verified evidence to reuse",
|
||||
"Facts you previously approved. Adapt the wording, but keep the claim accurate.",
|
||||
relevantEvidence));
|
||||
}
|
||||
|
||||
if (analysis.Keywords.Count > 0)
|
||||
{
|
||||
suggestions.Add(new TailoringSuggestionDto(
|
||||
@@ -297,4 +318,10 @@ public sealed class ApplicationAssetsService : IApplicationAssetsService
|
||||
private Task<JobApplication?> LoadJobAsync(string ownerUserId, int jobApplicationId, CancellationToken ct) =>
|
||||
_db.JobApplications.AsNoTracking()
|
||||
.FirstOrDefaultAsync(j => j.Id == jobApplicationId && j.OwnerUserId == ownerUserId, ct);
|
||||
|
||||
private static IReadOnlyList<string> EvidenceTags(string? json)
|
||||
{
|
||||
try { return JsonSerializer.Deserialize<List<string>>(json ?? "[]") ?? []; }
|
||||
catch (JsonException) { return []; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ internal static class StartupSchemaOwnership
|
||||
"Attachments",
|
||||
"CareerCertifications",
|
||||
"CareerEducations",
|
||||
"CareerEvidence",
|
||||
"CareerExperiences",
|
||||
"CareerLanguages",
|
||||
"CareerProfiles",
|
||||
|
||||
Reference in New Issue
Block a user