b52ef8ddff
The AI prompt now instructs the LLM to derive keywords entirely from the candidate's CV (seniority level, primary role title, core technologies they emphasize) rather than from the job description being matched. This ensures the job-board search keywords used by cv-search-job represent who the candidate actually is, not a mirror of the job they happened to match against. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
94 lines
8.0 KiB
C#
94 lines
8.0 KiB
C#
using CvMatcher.Data;
|
|
using Microsoft.EntityFrameworkCore.Migrations;
|
|
|
|
#nullable disable
|
|
|
|
namespace CvMatcher.Data.Migrations
|
|
{
|
|
/// <inheritdoc />
|
|
public partial class FixKeywordExtractionPrompt : Migration
|
|
{
|
|
// Full prompt values — only the 'keywords' instruction changes vs. the previous migration.
|
|
// Stored in full so Down() can restore the previous version exactly.
|
|
|
|
private const string EnNew =
|
|
"You are a strict CV-to-job matching engine. Return JSON only. Score realistically from 0 to 100. Penalize missing required skills. Do not invent experience. Use concise business language. All text fields in the JSON response must be in English.\n" +
|
|
"JSON shape: {\"score\":number,\"summary\":\"one-line summary in English\",\"strengths\":[\"strength 1 in English\"],\"gaps\":[\"gap 1 in English\"],\"recommendations\":[\"recommendation 1 in English\"],\"evidence\":[\"evidence 1 in English\"],\"keywords\":[\"Senior .NET Developer\",\"C#\",\"Azure\"],\"location\":\"City, Country\"}.\n" +
|
|
"For 'keywords': extract 2-4 job-board search terms that represent the candidate's professional identity as shown in their CV — their seniority level and primary role title (e.g. 'Software Architect', 'Engineering Manager', 'Senior .NET Developer') plus 1-2 core technologies they genuinely emphasize throughout the CV. Derive these entirely from the CV — do not use the job title or job technologies unless they independently match the candidate's actual positioning. Avoid generic terms like 'developer', 'engineer', 'cloud', or 'leadership'.\n" +
|
|
"For 'location': extract the candidate's city and country from the CV (e.g. 'Cluj-Napoca, Romania'). Use an empty string if not found.";
|
|
|
|
private const string EnPrev =
|
|
"You are a strict CV-to-job matching engine. Return JSON only. Score realistically from 0 to 100. Penalize missing required skills. Do not invent experience. Use concise business language. All text fields in the JSON response must be in English.\n" +
|
|
"JSON shape: {\"score\":number,\"summary\":\"one-line summary in English\",\"strengths\":[\"strength 1 in English\"],\"gaps\":[\"gap 1 in English\"],\"recommendations\":[\"recommendation 1 in English\"],\"evidence\":[\"evidence 1 in English\"],\"keywords\":[\"Senior .NET Developer\",\"C#\",\"Azure\"],\"location\":\"City, Country\"}.\n" +
|
|
"For 'keywords': extract 2-4 short, concrete terms a recruiter would search for on a job board — the candidate's primary role title and key technologies (e.g. 'Senior .NET Developer', 'C#', 'Azure'). Avoid abstract concepts like 'leadership', 'cloud', or 'microservices'.\n" +
|
|
"For 'location': extract the candidate's city and country from the CV (e.g. 'Cluj-Napoca, Romania'). Use an empty string if not found.";
|
|
|
|
private const string RoNew =
|
|
"Ești un motor strict de potrivire CV-job. Returnează doar JSON. Punctează realist între 0 și 100. Penalizează abilitățile lipsă necesare. Nu inventa experiență. Folosește limbaj profesional concis. Toate câmpurile text din răspunsul JSON trebuie să fie în limba română.\n" +
|
|
"JSON shape: {\"score\":number,\"summary\":\"rezumat pe o linie în română\",\"strengths\":[\"punct forte 1 în română\"],\"gaps\":[\"lipsă 1 în română\"],\"recommendations\":[\"recomandare 1 în română\"],\"evidence\":[\"dovadă 1 în română\"],\"keywords\":[\"Senior .NET Developer\",\"C#\",\"Azure\"],\"location\":\"Oraș, Țară\"}.\n" +
|
|
"Pentru 'keywords': extrage 2-4 termeni de căutare pe site-uri de joburi care reprezintă identitatea profesională a candidatului conform CV-ului — nivelul de senioritate și titlul principal de rol (ex. 'Software Architect', 'Engineering Manager', 'Senior .NET Developer') și 1-2 tehnologii de bază pe care candidatul le evidențiază cu adevărat în CV. Derivă aceștia exclusiv din CV — nu folosi titlul jobului sau tehnologiile din job dacă nu corespund poziționării reale a candidatului. Evită termeni generici precum 'developer', 'engineer', 'cloud' sau 'leadership'.\n" +
|
|
"Pentru 'location': extrage orașul și țara candidatului din CV (ex. 'Cluj-Napoca, România'). Folosește string gol dacă nu se găsește.";
|
|
|
|
private const string RoPrev =
|
|
"Ești un motor strict de potrivire CV-job. Returnează doar JSON. Punctează realist între 0 și 100. Penalizează abilitățile lipsă necesare. Nu inventa experiență. Folosește limbaj profesional concis. Toate câmpurile text din răspunsul JSON trebuie să fie în limba română.\n" +
|
|
"JSON shape: {\"score\":number,\"summary\":\"rezumat pe o linie în română\",\"strengths\":[\"punct forte 1 în română\"],\"gaps\":[\"lipsă 1 în română\"],\"recommendations\":[\"recomandare 1 în română\"],\"evidence\":[\"dovadă 1 în română\"],\"keywords\":[\"Senior .NET Developer\",\"C#\",\"Azure\"],\"location\":\"Oraș, Țară\"}.\n" +
|
|
"Pentru 'keywords': extrage 2-4 termeni scurți și concreți pe care un recrutor i-ar căuta pe un site de joburi — titlul principal al rolului și tehnologiile cheie (ex. 'Senior .NET Developer', 'C#', 'Azure'). Evită concepte abstracte precum 'leadership', 'cloud' sau 'microservicii'.\n" +
|
|
"Pentru 'location': extrage orașul și țara candidatului din CV (ex. 'Cluj-Napoca, România'). Folosește string gol dacă nu se găsește.";
|
|
|
|
/// <inheritdoc />
|
|
protected override void Up(MigrationBuilder migrationBuilder)
|
|
{
|
|
// Update English prompt: keywords must now be derived from the CV only,
|
|
// not influenced by the job description being matched against.
|
|
migrationBuilder.UpdateData(
|
|
schema: MigrationConstants.SchemaName,
|
|
table: "AiPrompts",
|
|
keyColumns: ["Key", "Language"],
|
|
keyValues: ["ai.cv-match.system-prompt", "en"],
|
|
columns: ["Value", "Description"],
|
|
values: [
|
|
EnNew,
|
|
"System prompt for CV-to-job matching in English. Keywords represent the candidate's CV identity (seniority + role + core tech), not the job being matched."
|
|
]);
|
|
|
|
// Update Romanian prompt: same improvement.
|
|
migrationBuilder.UpdateData(
|
|
schema: MigrationConstants.SchemaName,
|
|
table: "AiPrompts",
|
|
keyColumns: ["Key", "Language"],
|
|
keyValues: ["ai.cv-match.system-prompt", "ro"],
|
|
columns: ["Value", "Description"],
|
|
values: [
|
|
RoNew,
|
|
"System prompt pentru potrivire CV-job în română. Cuvintele cheie reprezintă identitatea CV-ului candidatului (senioritate + rol + tehnologii cheie), nu jobul cu care se face potrivirea."
|
|
]);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected override void Down(MigrationBuilder migrationBuilder)
|
|
{
|
|
migrationBuilder.UpdateData(
|
|
schema: MigrationConstants.SchemaName,
|
|
table: "AiPrompts",
|
|
keyColumns: ["Key", "Language"],
|
|
keyValues: ["ai.cv-match.system-prompt", "en"],
|
|
columns: ["Value", "Description"],
|
|
values: [
|
|
EnPrev,
|
|
"System prompt for CV-to-job matching in English. Extracts job-board-friendly keywords (role title + key tech) and candidate location."
|
|
]);
|
|
|
|
migrationBuilder.UpdateData(
|
|
schema: MigrationConstants.SchemaName,
|
|
table: "AiPrompts",
|
|
keyColumns: ["Key", "Language"],
|
|
keyValues: ["ai.cv-match.system-prompt", "ro"],
|
|
columns: ["Value", "Description"],
|
|
values: [
|
|
RoPrev,
|
|
"System prompt pentru potrivire CV-job în limba română. Extrage cuvinte cheie prietenoase pentru site-uri de joburi (titlu rol + tehnologii cheie) și locația candidatului."
|
|
]);
|
|
}
|
|
}
|
|
}
|