feat(ui): instant match-score panel on the Candidate Fit tab

Adds a MatchScoreCard at the top of the Candidate Fit tab that loads the
deterministic /match-score endpoint independently of the slow AI
narrative, so users see a reproducible score, matched/missing keyword
chips, and per-section coverage immediately.

- MatchScore types + cached, attachment-independent load effect
- graceful 'not enough signal' state
- EN/NB translations
- frontend panel test (matched/missing/section + degraded state)
- backend integration tests for GetMatchScore (happy path + missing CV)
- README endpoint reference

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
cesnimda
2026-07-03 03:24:53 +02:00
parent 3fad43a9e2
commit 209528c8b5
6 changed files with 306 additions and 1 deletions
+28
View File
@@ -867,6 +867,20 @@ export const translations = {
jobDetailsFollowUpSent: "Follow-up sent and logged.",
jobDetailsFollowUpSendFailed: "Failed to send follow-up.",
jobDetailsHowYouMatch: "How you match",
matchScoreTitle: "Match score",
matchScoreLoading: "Scoring your CV against this role…",
matchScoreBand_Strong: "Strong match",
matchScoreBand_Partial: "Partial match",
matchScoreBand_Low: "Low match",
matchScoreBand_Unknown: "Not enough signal",
matchScoreKeywordsCovered: "{matched}/{total} keywords",
matchScoreNoSignal: "Add more CV detail or a fuller job description to get a reliable score.",
matchScoreDeterministicHint: "Deterministic keyword coverage — no AI, so the score is stable and repeatable.",
matchScoreMatched: "Matched keywords",
matchScoreMissing: "Missing keywords",
matchScoreNoneYet: "No matches found yet.",
matchScoreAllCovered: "Every keyword is covered.",
matchScoreSectionCoverage: "Where your CV covers this role",
jobDetailsStrategySnapshot: "Strategy snapshot",
jobDetailsGenerateStrategySnapshot: "Generate strategy snapshot",
jobDetailsStrategySnapshotEmpty: "Generate a snapshot to see fit, positioning, and immediate priorities in one place.",
@@ -1784,6 +1798,20 @@ export const translations = {
jobDetailsFollowUpSent: "Oppfølging sendt og loggført.",
jobDetailsFollowUpSendFailed: "Kunne ikke sende oppfølging.",
jobDetailsHowYouMatch: "Slik matcher du",
matchScoreTitle: "Match-score",
matchScoreLoading: "Vurderer CV-en mot denne stillingen…",
matchScoreBand_Strong: "Sterk match",
matchScoreBand_Partial: "Delvis match",
matchScoreBand_Low: "Lav match",
matchScoreBand_Unknown: "For lite grunnlag",
matchScoreKeywordsCovered: "{matched}/{total} nøkkelord",
matchScoreNoSignal: "Legg til mer CV-innhold eller en fyldigere stillingstekst for en pålitelig score.",
matchScoreDeterministicHint: "Deterministisk nøkkelorddekning — ingen AI, så scoren er stabil og repeterbar.",
matchScoreMatched: "Treff på nøkkelord",
matchScoreMissing: "Manglende nøkkelord",
matchScoreNoneYet: "Ingen treff ennå.",
matchScoreAllCovered: "Alle nøkkelord er dekket.",
matchScoreSectionCoverage: "Hvor CV-en dekker denne rollen",
jobDetailsStrategySnapshot: "Strategioversikt",
jobDetailsGenerateStrategySnapshot: "Generer strategioversikt",
jobDetailsStrategySnapshotEmpty: "Generer en oversikt for å se match, posisjonering og viktigste prioriteringer på ett sted.",