@pranavraut033/ats-checker 1.3.0 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -6
- package/dist/index.cjs +105 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +105 -35
- package/dist/index.mjs.map +1 -1
- package/dist/pdf/index.cjs +11 -2
- package/dist/pdf/index.cjs.map +1 -1
- package/dist/pdf/index.d.mts +15 -2
- package/dist/pdf/index.d.ts +15 -2
- package/dist/pdf/index.mjs +11 -2
- package/dist/pdf/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -54,15 +54,38 @@ var LEVEL_RANK = {
|
|
|
54
54
|
fluent: 5,
|
|
55
55
|
native: 6,
|
|
56
56
|
"native speaker": 6,
|
|
57
|
-
bilingual: 6
|
|
57
|
+
bilingual: 6,
|
|
58
|
+
// German
|
|
59
|
+
grundkenntnisse: 1,
|
|
60
|
+
gering: 2,
|
|
61
|
+
gut: 3,
|
|
62
|
+
fortgeschritten: 4,
|
|
63
|
+
flie\u00DFend: 5,
|
|
64
|
+
muttersprache: 6,
|
|
65
|
+
muttersprachler: 6,
|
|
66
|
+
// French
|
|
67
|
+
"d\xE9butant": 1,
|
|
68
|
+
"\xE9l\xE9mentaire": 1,
|
|
69
|
+
"limit\xE9": 2,
|
|
70
|
+
"interm\xE9diaire": 3,
|
|
71
|
+
"avanc\xE9": 4,
|
|
72
|
+
courant: 5,
|
|
73
|
+
natif: 6,
|
|
74
|
+
"langue maternelle": 6,
|
|
75
|
+
bilingue: 6
|
|
58
76
|
};
|
|
59
77
|
var LANGUAGE_GROUP = KNOWN_LANGUAGES.join("|");
|
|
60
78
|
var LEVEL_GROUP = Object.keys(LEVEL_RANK).sort((a, b) => b.length - a.length).map((l) => l.replace(/\s+/g, "\\s+")).join("|");
|
|
79
|
+
var BOUNDARY_START = "(?:^|(?<=[^a-z\xE0-\xFF]))";
|
|
80
|
+
var BOUNDARY_END = "(?:$|(?=[^a-z\xE0-\xFF]))";
|
|
61
81
|
var LANGUAGE_LEVEL_RE = new RegExp(
|
|
62
|
-
`\\b(${LANGUAGE_GROUP})\\b(?:\\s*[\\(:\\-]?\\s*(${LEVEL_GROUP}|[abc][12]))?`,
|
|
82
|
+
`\\b(${LANGUAGE_GROUP})\\b(?:\\s*[\\(:\\-]?\\s*(${BOUNDARY_START}(?:${LEVEL_GROUP})${BOUNDARY_END}|[abc][12]))?`,
|
|
83
|
+
"gi"
|
|
84
|
+
);
|
|
85
|
+
var LEVEL_BEFORE_LANGUAGE_RE = new RegExp(
|
|
86
|
+
`${BOUNDARY_START}(${LEVEL_GROUP})${BOUNDARY_END}\\s+(?:in\\s+)?(${LANGUAGE_GROUP})\\b`,
|
|
63
87
|
"gi"
|
|
64
88
|
);
|
|
65
|
-
var LEVEL_BEFORE_LANGUAGE_RE = new RegExp(`\\b(${LEVEL_GROUP})\\s+(?:in\\s+)?(${LANGUAGE_GROUP})\\b`, "gi");
|
|
66
89
|
function canonicalLanguage(name) {
|
|
67
90
|
const lower = name.toLowerCase();
|
|
68
91
|
return LANGUAGE_ALIASES[lower] ?? lower;
|
|
@@ -112,9 +135,9 @@ function diffLanguages(resumeLanguages, requiredLanguages) {
|
|
|
112
135
|
|
|
113
136
|
// src/core/parser/jd.parser.ts
|
|
114
137
|
var DEGREE_VARIANTS = [
|
|
115
|
-
[/\b(?:bachelor(?:'s)?|b\.s\.?|bs\.?|bsc
|
|
116
|
-
[/\b(?:master(?:'s)?|m\.s\.?|ms\.?|msc
|
|
117
|
-
[/\b(?:phd|ph\.d\.?|doctorate)\b/i, "phd"],
|
|
138
|
+
[/\b(?:bachelor(?:'s)?|b\.s\.?|bs\.?|bsc\.?|licence)\b/i, "bachelor"],
|
|
139
|
+
[/\b(?:master(?:'s)?|m\.s\.?|ms\.?|msc\.?|diplom)\b/i, "master"],
|
|
140
|
+
[/\b(?:phd|ph\.d\.?|doctorate|doktor|doctorat)\b/i, "phd"],
|
|
118
141
|
[/\bmba\b/i, "mba"],
|
|
119
142
|
[/\bassociate(?:'s)?\b/i, "associate"]
|
|
120
143
|
];
|
|
@@ -137,16 +160,15 @@ function extractPreferredSkills(lines) {
|
|
|
137
160
|
return preferred;
|
|
138
161
|
}
|
|
139
162
|
function extractRoleKeywords(text) {
|
|
140
|
-
const
|
|
141
|
-
const
|
|
142
|
-
return unique(tokenize(
|
|
163
|
+
const roleMatches = text.match(/(engineer|developer|manager|scientist|analyst|designer|architect|director|consultant|lead|vp)/gi) ?? [];
|
|
164
|
+
const fallback = roleMatches.length === 0 ? [text.split(/\n/)[0] ?? ""] : [];
|
|
165
|
+
return unique(tokenize([...roleMatches, ...fallback].join(" ")));
|
|
143
166
|
}
|
|
144
167
|
function extractMinExperience(text) {
|
|
145
|
-
const match = text.match(/(\d{1,2})\+?\s
|
|
146
|
-
if (match)
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
return void 0;
|
|
168
|
+
const match = text.match(/(\d{1,2})\+?\s*(?:years?|yrs\.?|jahre?|ans?|années?)/i);
|
|
169
|
+
if (!match) return void 0;
|
|
170
|
+
const parsed = Number.parseInt(match[1], 10);
|
|
171
|
+
return parsed <= 60 ? parsed : void 0;
|
|
150
172
|
}
|
|
151
173
|
var SURFACE_TOKEN_RE = /[a-z0-9][a-z0-9.#+\-/]*[a-z0-9#+]/gi;
|
|
152
174
|
function collectKeywordSurfaceForms(rawText, aliases) {
|
|
@@ -160,7 +182,16 @@ function collectKeywordSurfaceForms(rawText, aliases) {
|
|
|
160
182
|
}
|
|
161
183
|
return surfaceForms;
|
|
162
184
|
}
|
|
163
|
-
|
|
185
|
+
var LANG_SECTION_RE = /^\s*(?:languages?|sprache|langue)s?\s*[:\-–—]?\s*/i;
|
|
186
|
+
var LANG_REQUIREMENT_HINT_RE = /\b(fluent|required|must|need|speak|proficient|native|conversational|intermediate|advanced|professional|[abc][12])\b/i;
|
|
187
|
+
function isLanguageRequired(lang, jobDescription) {
|
|
188
|
+
return splitLines(jobDescription).some((line) => {
|
|
189
|
+
const lower = line.toLowerCase();
|
|
190
|
+
if (!lower.includes(lang.name)) return false;
|
|
191
|
+
return LANG_SECTION_RE.test(line) || LANG_REQUIREMENT_HINT_RE.test(line);
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
function extractDegreeLevels(text) {
|
|
164
195
|
const found = /* @__PURE__ */ new Set();
|
|
165
196
|
for (const [pattern, canonical] of DEGREE_VARIANTS) {
|
|
166
197
|
if (pattern.test(text)) found.add(canonical);
|
|
@@ -198,11 +229,13 @@ function parseJobDescription(jobDescription, config) {
|
|
|
198
229
|
roleKeywords,
|
|
199
230
|
keywords,
|
|
200
231
|
minExperienceYears: extractMinExperience(jobDescription),
|
|
201
|
-
educationRequirements:
|
|
232
|
+
educationRequirements: extractDegreeLevels(jobDescription),
|
|
202
233
|
keywordSurfaceForms: collectKeywordSurfaceForms(jobDescription, config.skillAliases),
|
|
203
|
-
//
|
|
204
|
-
//
|
|
205
|
-
requiredLanguages: parseLanguageMentions(jobDescription)
|
|
234
|
+
// A language only counts as required if its mention carries a requirement/level cue
|
|
235
|
+
// or sits in a "Languages:" line — plain references ("our Berlin office") don't count.
|
|
236
|
+
requiredLanguages: parseLanguageMentions(jobDescription).filter(
|
|
237
|
+
(lang) => isLanguageRequired(lang, jobDescription)
|
|
238
|
+
)
|
|
206
239
|
};
|
|
207
240
|
}
|
|
208
241
|
|
|
@@ -231,11 +264,37 @@ var MONTHS = {
|
|
|
231
264
|
nov: 11,
|
|
232
265
|
november: 11,
|
|
233
266
|
dec: 12,
|
|
234
|
-
december: 12
|
|
267
|
+
december: 12,
|
|
268
|
+
// German
|
|
269
|
+
januar: 1,
|
|
270
|
+
j\u00E4nner: 1,
|
|
271
|
+
februar: 2,
|
|
272
|
+
m\u00E4rz: 3,
|
|
273
|
+
maerz: 3,
|
|
274
|
+
mai: 5,
|
|
275
|
+
juni: 6,
|
|
276
|
+
juli: 7,
|
|
277
|
+
oktober: 10,
|
|
278
|
+
dezember: 12,
|
|
279
|
+
// French
|
|
280
|
+
janvier: 1,
|
|
281
|
+
f\u00E9vrier: 2,
|
|
282
|
+
fevrier: 2,
|
|
283
|
+
mars: 3,
|
|
284
|
+
avril: 4,
|
|
285
|
+
juin: 6,
|
|
286
|
+
juillet: 7,
|
|
287
|
+
ao\u00FBt: 8,
|
|
288
|
+
aout: 8,
|
|
289
|
+
septembre: 9,
|
|
290
|
+
octobre: 10,
|
|
291
|
+
novembre: 11,
|
|
292
|
+
d\u00E9cembre: 12,
|
|
293
|
+
decembre: 12
|
|
235
294
|
};
|
|
236
295
|
function parseDateToken(raw) {
|
|
237
296
|
const cleaned = raw.trim().toLowerCase();
|
|
238
|
-
const monthMatch = cleaned.match(/([a-z]{3,9})\s*(\d{4})/i);
|
|
297
|
+
const monthMatch = cleaned.match(/([a-zà-ÿ]{3,9})\s*(\d{4})/i);
|
|
239
298
|
if (monthMatch) {
|
|
240
299
|
const monthName = monthMatch[1].toLowerCase();
|
|
241
300
|
const year = Number.parseInt(monthMatch[2], 10);
|
|
@@ -267,14 +326,14 @@ function monthsBetween(start, end) {
|
|
|
267
326
|
function parseDateRange(text, referenceDate) {
|
|
268
327
|
const normalized = text.trim();
|
|
269
328
|
const rangeMatch = normalized.match(
|
|
270
|
-
/(\d{1,2}\/\d{4}|[A-Za-z]{3,9}\s+\d{4}|\d{4})\s*(?:-|to
|
|
329
|
+
/(\d{1,2}\/\d{4}|[A-Za-zà-ÿ]{3,9}\s+\d{4}|\d{4})\s*(?:-|to|through|until|bis|jusqu'à|à|–|—)\s*(Present|Current|Now|Aktuell|Heute|Actuellement|Présent|\d{1,2}\/\d{4}|[A-Za-zà-ÿ]{3,9}\s+\d{4}|\d{4})/i
|
|
271
330
|
);
|
|
272
331
|
if (!rangeMatch) {
|
|
273
332
|
return null;
|
|
274
333
|
}
|
|
275
334
|
const startToken = parseDateToken(rangeMatch[1]);
|
|
276
335
|
const endRaw = rangeMatch[2];
|
|
277
|
-
const isPresent = /present|current|now/i.test(endRaw);
|
|
336
|
+
const isPresent = /present|current|now|aktuell|heute|actuellement|présent|actuel/i.test(endRaw);
|
|
278
337
|
const endToken = isPresent ? void 0 : parseDateToken(endRaw);
|
|
279
338
|
if (!startToken) {
|
|
280
339
|
return null;
|
|
@@ -328,12 +387,21 @@ function sumExperienceYears(ranges) {
|
|
|
328
387
|
|
|
329
388
|
// src/core/parser/resume.parser.ts
|
|
330
389
|
var SECTION_ALIASES = {
|
|
331
|
-
summary: ["summary", "profile", "about"],
|
|
332
|
-
experience: [
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
390
|
+
summary: ["summary", "profile", "about", "zusammenfassung", "profil", "r\xE9sum\xE9", "\xE0 propos"],
|
|
391
|
+
experience: [
|
|
392
|
+
"experience",
|
|
393
|
+
"work experience",
|
|
394
|
+
"professional experience",
|
|
395
|
+
"employment",
|
|
396
|
+
"erfahrung",
|
|
397
|
+
"berufserfahrung",
|
|
398
|
+
"exp\xE9rience",
|
|
399
|
+
"exp\xE9rience professionnelle"
|
|
400
|
+
],
|
|
401
|
+
skills: ["skills", "technical skills", "technologies", "f\xE4higkeiten", "kenntnisse", "comp\xE9tences"],
|
|
402
|
+
education: ["education", "academics", "academic background", "ausbildung", "formation", "\xE9tudes"],
|
|
403
|
+
projects: ["projects", "portfolio", "projekte", "projets"],
|
|
404
|
+
certifications: ["certifications", "licenses", "zertifizierungen", "certifications professionnelles"]
|
|
337
405
|
};
|
|
338
406
|
var STRONG_VERBS = [
|
|
339
407
|
"led",
|
|
@@ -411,7 +479,9 @@ function extractSections(text) {
|
|
|
411
479
|
}
|
|
412
480
|
function parseSkills(sectionContent, aliases) {
|
|
413
481
|
if (!sectionContent) return [];
|
|
414
|
-
const
|
|
482
|
+
const hasBullets = /[•·‣▪○●◦]/.test(sectionContent);
|
|
483
|
+
const normalized = hasBullets ? sectionContent.replace(/\n/g, " ") : sectionContent;
|
|
484
|
+
const raw = normalized.split(/[,;\n]|[•·‣▪○●◦]/).map((skill) => skill.trim().replace(/^[-•·‣▪○●◦\s]+|[-•·‣▪○●◦\s]+$/g, "").trim()).filter(Boolean);
|
|
415
485
|
return normalizeSkills(raw, aliases);
|
|
416
486
|
}
|
|
417
487
|
function parseActionVerbs(text) {
|
|
@@ -444,7 +514,7 @@ function parseExperience(sectionContent, referenceDate) {
|
|
|
444
514
|
}
|
|
445
515
|
continue;
|
|
446
516
|
}
|
|
447
|
-
const titleMatch = line.match(/^(Senior|Lead|Principal|Staff|Software|Full\s*Stack|Frontend|Backend|Engineer|Developer|Manager|Analyst)[^,-]*/i);
|
|
517
|
+
const titleMatch = line.match(/^(Senior|Lead|Principal|Staff|VP|Director|Consultant|Architect|Software|Full\s*Stack|Frontend|Backend|Engineer|Developer|Manager|Analyst)[^,-]*/i);
|
|
448
518
|
if (titleMatch) {
|
|
449
519
|
const title = titleMatch[0].trim();
|
|
450
520
|
jobTitles.push(title.toLowerCase());
|
|
@@ -482,7 +552,8 @@ function parseResume(resumeText, config) {
|
|
|
482
552
|
const textToScan = sections.summary ?? normalizedText;
|
|
483
553
|
const yearsMatch = textToScan.match(/(\d{1,2})\+?\s*years?/i);
|
|
484
554
|
if (yearsMatch) {
|
|
485
|
-
|
|
555
|
+
const parsed = Number.parseInt(yearsMatch[1], 10);
|
|
556
|
+
totalExperienceYears = parsed <= 60 ? parsed : 0;
|
|
486
557
|
}
|
|
487
558
|
}
|
|
488
559
|
const requiredSections = ["summary", "experience", "skills", "education"];
|
|
@@ -718,10 +789,9 @@ function scoreEducation(resume, job) {
|
|
|
718
789
|
if (job.educationRequirements.length === 0) {
|
|
719
790
|
return 100;
|
|
720
791
|
}
|
|
721
|
-
const
|
|
722
|
-
const normalizedEducation = resumeEducationText.toLowerCase();
|
|
792
|
+
const resumeDegreeLevels = extractDegreeLevels(resume.educationEntries.join(" "));
|
|
723
793
|
const matched = job.educationRequirements.filter(
|
|
724
|
-
(requirement) =>
|
|
794
|
+
(requirement) => resumeDegreeLevels.includes(requirement)
|
|
725
795
|
);
|
|
726
796
|
if (matched.length === 0) {
|
|
727
797
|
return 0;
|