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]);
});