File size: 4,823 Bytes
302e8a6 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | const $ = (id) => document.getElementById(id);
const cvFile = $("cvFile");
const dropzone = $("dropzone");
const fileLabel = $("fileLabel");
const extractButton = $("extractButton");
const scoreButton = $("scoreButton");
const status = $("status");
const preview = $("preview");
const result = $("result");
let selectedFile = null;
function setStatus(message, type = "") {
status.textContent = message;
status.className = `status ${type}`.trim();
}
function value(id) {
return $(id).value.trim();
}
function setSelectedFile(file) {
if (!file) return;
selectedFile = file;
fileLabel.textContent = file.name;
extractButton.disabled = false;
setStatus("File selected. Click Extract CV details to fill the form.");
}
function applyCandidate(candidate) {
const mapping = {
candidateRole: candidate.role,
candidateSeniority: candidate.seniority,
yearsExperience: candidate.years_experience,
candidateIndustry: candidate.industry,
education: candidate.education,
candidateSkills: candidate.skills,
summary: candidate.summary,
experienceBullets: candidate.experience_bullets,
};
for (const [id, fieldValue] of Object.entries(mapping)) $(id).value = fieldValue || "";
}
async function extractCv() {
if (!selectedFile) return;
extractButton.disabled = true;
setStatus("Uploading CV to the FastAPI service for extraction…");
try {
const body = new FormData();
body.append("file", selectedFile);
const response = await fetch("/api/extract-cv", { method: "POST", body });
const data = await response.json();
if (!response.ok) throw new Error(data.detail || "Could not read this CV.");
preview.value = data.preview || "";
applyCandidate(data.candidate || {});
const filled = Object.values(data.candidate || {}).filter(Boolean).length;
const warning = data.warnings?.length ? ` ${data.warnings.join(" ")}` : "";
setStatus(`Extracted ${filled} of 8 candidate fields. Please review them before scoring.${warning}`, data.warnings?.length ? "" : "success");
} catch (error) {
setStatus(error.message || "Could not read this CV.", "error");
} finally {
extractButton.disabled = false;
}
}
function buildPayload() {
return {
job: {
job_title: value("jobTitle"),
seniority: value("jobSeniority"),
industry: value("jobIndustry"),
must_have_skills: value("mustHaveSkills"),
nice_to_have_skills: value("niceToHaveSkills"),
description: value("jobDescription"),
responsibilities: value("responsibilities"),
requirements: value("requirements"),
},
candidate: {
role: value("candidateRole"),
seniority: value("candidateSeniority"),
years_experience: value("yearsExperience"),
industry: value("candidateIndustry"),
education: value("education"),
skills: value("candidateSkills"),
summary: value("summary"),
experience_bullets: value("experienceBullets"),
},
};
}
async function scoreMatch() {
scoreButton.disabled = true;
scoreButton.textContent = "Scoring…";
setStatus("FastAPI is scoring the job and candidate profile…");
result.hidden = true;
try {
const response = await fetch("/api/score", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(buildPayload()),
});
const data = await response.json();
if (!response.ok) throw new Error(data.detail || "Scoring failed.");
const percent = (data.probability * 100).toFixed(1);
result.innerHTML = `
<div class="result-top">
<div><h3>${data.verdict}</h3><p>Model-estimated relevance for this job and candidate profile.</p></div>
<div class="score-number">${percent}%</div>
</div>
<div class="score-bar"><span style="width:${Math.max(2, data.probability * 100)}%"></span></div>
<p>Decision threshold: ${(data.threshold * 100).toFixed(1)}%</p>
<p class="result-note">${data.disclaimer}</p>
`;
result.hidden = false;
setStatus("Match scored by FastAPI.", "success");
} catch (error) {
setStatus(error.message || "Scoring failed.", "error");
} finally {
scoreButton.disabled = false;
scoreButton.innerHTML = 'Score match <span>→</span>';
}
}
cvFile.addEventListener("change", (event) => setSelectedFile(event.target.files[0]));
extractButton.addEventListener("click", extractCv);
scoreButton.addEventListener("click", scoreMatch);
dropzone.addEventListener("dragover", (event) => { event.preventDefault(); dropzone.classList.add("dragging"); });
dropzone.addEventListener("dragleave", () => dropzone.classList.remove("dragging"));
dropzone.addEventListener("drop", (event) => {
event.preventDefault();
dropzone.classList.remove("dragging");
setSelectedFile(event.dataTransfer.files[0]);
});
|