Gehan77 commited on
Commit
d82f88b
路
verified 路
1 Parent(s): 302e8a6

Improve browser CV field extraction

Browse files
Files changed (1) hide show
  1. app.js +22 -13
app.js CHANGED
@@ -38,10 +38,10 @@ function cleanList(value) {
38
  }
39
 
40
  const headerRules = [
41
- ["summary", /^(?:professional\s+)?summary$|^profile$|^about\s+me$|^objective$|^career\s+objective$/i],
42
- ["experience", /^(?:professional\s+)?(?:work\s+)?experience$|^employment\s+history$|^career\s+history$/i],
43
- ["education", /^education(?:al\s+background)?$|^academic\s+qualifications?$/i],
44
- ["skills", /^(?:technical\s+|core\s+|key\s+|professional\s+)?skills?$|^competenc(?:y|ies)$|^technologies$/i],
45
  ];
46
 
47
  function headerName(line) {
@@ -86,6 +86,12 @@ function sectionItems(section = [], limit = 1200) {
86
  return section.filter(Boolean).join("; ").slice(0, limit);
87
  }
88
 
 
 
 
 
 
 
89
  function extractCandidateFields(text) {
90
  const sections = parseSections(text);
91
  const allText = text.replace(/\s+/g, " ");
@@ -94,10 +100,9 @@ function extractCandidateFields(text) {
94
  let role = "";
95
  for (const line of (sections.other ?? []).slice(0, 8)) {
96
  if (line === firstLine) continue;
97
- if (roleKeywords.some((keyword) => line.toLowerCase().includes(keyword))) {
98
- role = line;
99
- break;
100
- }
101
  }
102
  role ||= firstLine;
103
 
@@ -105,18 +110,22 @@ function extractCandidateFields(text) {
105
  const seniority = seniorityMatch ? seniorityMatch[1].replace("-", " ").replace(/\b\w/g, (letter) => letter.toUpperCase()) : "";
106
  const yearsMatch = allText.match(/\b(\d+(?:\.\d+)?)\+?\s*(?:years?|yrs?)\b/i);
107
  const industryTerms = ["FinTech", "Banking", "Finance", "Software", "Information Technology", "Healthcare", "Education", "Marketing", "Tourism", "Retail", "Logistics", "Construction", "Manufacturing", "Telecommunications", "Insurance", "Hospitality", "Government", "E-commerce"];
108
- const industry = industryTerms.find((term) => new RegExp(term.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i").test(allText)) ?? "";
109
- const summary = sectionText(sections.summary) || sectionText((sections.other ?? []).slice(1, 5), 900);
 
 
 
 
110
 
111
  return {
112
  candidateRole: role,
113
  candidateSeniority: seniority,
114
  yearsExperience: yearsMatch?.[1] ?? "",
115
  candidateIndustry: industry,
116
- education: sectionItems(sections.education, 600),
117
- candidateSkills: sectionItems(sections.skills),
118
  summary,
119
- experienceBullets: sectionItems(sections.experience, 1400),
120
  };
121
  }
122
 
 
38
  }
39
 
40
  const headerRules = [
41
+ ["summary", /^(?:(?:professional\s+)?summary|profile|about\s+me|objective|career\s+objective)$/i],
42
+ ["experience", /^(?:(?:professional\s+)?(?:work\s+)?experience|employment\s+history|career\s+history)$/i],
43
+ ["education", /^(?:education(?:al\s+background)?|academic\s+qualifications?)$/i],
44
+ ["skills", /^(?:(?:technical\s+|core\s+|key\s+|professional\s+)?skills?|competenc(?:y|ies)|technologies)$/i],
45
  ];
46
 
47
  function headerName(line) {
 
86
  return section.filter(Boolean).join("; ").slice(0, limit);
87
  }
88
 
89
+ function inlineSection(text, labels, nextLabels, limit) {
90
+ const flat = text.replace(/\s+/g, " ");
91
+ const match = flat.match(new RegExp(`\\b(?:${labels})\\b\\s*[:\\-]\\s*(.*?)(?=\\s+\\b(?:${nextLabels})\\b\\s*[:\\-]\\s*|$)`, "i"));
92
+ return match ? match[1].trim().slice(0, limit) : "";
93
+ }
94
+
95
  function extractCandidateFields(text) {
96
  const sections = parseSections(text);
97
  const allText = text.replace(/\s+/g, " ");
 
100
  let role = "";
101
  for (const line of (sections.other ?? []).slice(0, 8)) {
102
  if (line === firstLine) continue;
103
+ const parts = line.split(/\s*(?:\||鈥路)\s*|\s{2,}/);
104
+ role = parts.find((part) => roleKeywords.some((keyword) => part.toLowerCase().includes(keyword)))?.trim() || "";
105
+ if (role) break;
 
106
  }
107
  role ||= firstLine;
108
 
 
110
  const seniority = seniorityMatch ? seniorityMatch[1].replace("-", " ").replace(/\b\w/g, (letter) => letter.toUpperCase()) : "";
111
  const yearsMatch = allText.match(/\b(\d+(?:\.\d+)?)\+?\s*(?:years?|yrs?)\b/i);
112
  const industryTerms = ["FinTech", "Banking", "Finance", "Software", "Information Technology", "Healthcare", "Education", "Marketing", "Tourism", "Retail", "Logistics", "Construction", "Manufacturing", "Telecommunications", "Insurance", "Hospitality", "Government", "E-commerce"];
113
+ let industry = industryTerms.find((term) => new RegExp(term.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i").test(allText)) ?? "";
114
+ industry ||= allText.match(/\bindustry\s*[:\-]\s*([^|]+)/i)?.[1]?.trim() || "";
115
+ const summary = sectionText(sections.summary) || inlineSection(text, "professional\\s+summary|summary|profile|objective", "experience|education|skills|certifications", 900) || sectionText((sections.other ?? []).slice(1, 5), 900);
116
+ const education = sectionItems(sections.education, 600) || inlineSection(text, "education|academic\\s+qualifications?", "experience|skills|summary|certifications", 600);
117
+ const skills = sectionItems(sections.skills) || inlineSection(text, "technical\\s+skills|core\\s+skills|skills|competenc(?:y|ies)|technologies", "experience|education|summary|certifications", 1200);
118
+ const experience = sectionItems(sections.experience, 1400) || inlineSection(text, "professional\\s+experience|work\\s+experience|experience|employment\\s+history", "education|skills|summary|certifications", 1400);
119
 
120
  return {
121
  candidateRole: role,
122
  candidateSeniority: seniority,
123
  yearsExperience: yearsMatch?.[1] ?? "",
124
  candidateIndustry: industry,
125
+ education,
126
+ candidateSkills: skills,
127
  summary,
128
+ experienceBullets: experience,
129
  };
130
  }
131