@pranavraut033/ats-checker 1.0.5 → 1.1.0
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 +144 -108
- package/dist/index.d.mts +22 -9
- package/dist/index.d.ts +22 -9
- package/dist/index.js +44 -18
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +44 -18
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -35,13 +35,15 @@ function normalizeWhitespace(text) {
|
|
|
35
35
|
return text.replace(/\r\n?/g, "\n").replace(/\s+/g, " ").trim();
|
|
36
36
|
}
|
|
37
37
|
function normalizeForComparison(text) {
|
|
38
|
-
return normalizeWhitespace(text).toLowerCase();
|
|
38
|
+
return normalizeWhitespace(text).normalize("NFKC").toLowerCase();
|
|
39
39
|
}
|
|
40
40
|
function splitLines(text) {
|
|
41
41
|
return text.replace(/\r\n?/g, "\n").split("\n").map((line) => line.trim()).filter(Boolean);
|
|
42
42
|
}
|
|
43
|
+
var TECH_TOKEN_RE = /[a-z0-9][a-z0-9.#+\-/]*[a-z0-9#+]/g;
|
|
43
44
|
function tokenize(text) {
|
|
44
|
-
|
|
45
|
+
const normalized = normalizeForComparison(text);
|
|
46
|
+
return (normalized.match(TECH_TOKEN_RE) ?? []).filter((t) => !STOP_WORDS.has(t));
|
|
45
47
|
}
|
|
46
48
|
function unique(values) {
|
|
47
49
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -215,7 +217,7 @@ function monthsBetween(start, end) {
|
|
|
215
217
|
const endMonth = end.month ?? 12;
|
|
216
218
|
return (end.year - start.year) * 12 + (endMonth - startMonth + 1);
|
|
217
219
|
}
|
|
218
|
-
function parseDateRange(text) {
|
|
220
|
+
function parseDateRange(text, referenceDate) {
|
|
219
221
|
const normalized = text.trim();
|
|
220
222
|
const rangeMatch = normalized.match(/([A-Za-z]{3,9}\s+\d{4}|\d{4})\s*(?:-|to|–|—)\s*(Present|Current|Now|[A-Za-z]{3,9}\s+\d{4}|\d{4})/i);
|
|
221
223
|
if (!rangeMatch) {
|
|
@@ -228,9 +230,10 @@ function parseDateRange(text) {
|
|
|
228
230
|
if (!startToken) {
|
|
229
231
|
return null;
|
|
230
232
|
}
|
|
233
|
+
const ref = referenceDate ?? /* @__PURE__ */ new Date();
|
|
231
234
|
const endTokenResolved = endToken ?? {
|
|
232
|
-
year:
|
|
233
|
-
month:
|
|
235
|
+
year: ref.getFullYear(),
|
|
236
|
+
month: ref.getMonth() + 1
|
|
234
237
|
};
|
|
235
238
|
const durationInMonths = monthsBetween(startToken, endTokenResolved);
|
|
236
239
|
return {
|
|
@@ -325,7 +328,7 @@ function parseActionVerbs(text) {
|
|
|
325
328
|
const words = tokenize(text);
|
|
326
329
|
return ACTION_VERBS.filter((verb) => words.includes(verb));
|
|
327
330
|
}
|
|
328
|
-
function parseExperience(sectionContent) {
|
|
331
|
+
function parseExperience(sectionContent, referenceDate) {
|
|
329
332
|
if (!sectionContent) {
|
|
330
333
|
return { entries: [], rangesInMonths: [], jobTitles: [] };
|
|
331
334
|
}
|
|
@@ -334,7 +337,7 @@ function parseExperience(sectionContent) {
|
|
|
334
337
|
const rangesInMonths = [];
|
|
335
338
|
const jobTitles = [];
|
|
336
339
|
for (const line of lines) {
|
|
337
|
-
const range = parseDateRange(line);
|
|
340
|
+
const range = parseDateRange(line, referenceDate);
|
|
338
341
|
if (range) {
|
|
339
342
|
const previous = entries[entries.length - 1];
|
|
340
343
|
if (previous && !previous.dates) {
|
|
@@ -374,7 +377,7 @@ function parseResume(resumeText, config) {
|
|
|
374
377
|
const { sections, detected } = extractSections(resumeText);
|
|
375
378
|
const skills = parseSkills(sections.skills, config.skillAliases);
|
|
376
379
|
const actionVerbs = parseActionVerbs(normalizedText);
|
|
377
|
-
const experienceData = parseExperience(sections.experience);
|
|
380
|
+
const experienceData = parseExperience(sections.experience, config.referenceDate);
|
|
378
381
|
const educationEntries = parseEducation(sections.education);
|
|
379
382
|
const totalExperienceYears = sumExperienceYears(
|
|
380
383
|
experienceData.entries.map((entry) => entry.dates).filter((range) => Boolean(range))
|
|
@@ -498,8 +501,9 @@ function scoreSkills(resume, job, config) {
|
|
|
498
501
|
0,
|
|
499
502
|
100
|
|
500
503
|
);
|
|
501
|
-
const
|
|
502
|
-
|
|
504
|
+
const matched = [...required].filter((skill) => resumeSkills.has(skill)).sort();
|
|
505
|
+
const missing = [...required].filter((skill) => !resumeSkills.has(skill)).sort();
|
|
506
|
+
return { score, matched, missing };
|
|
503
507
|
}
|
|
504
508
|
function scoreExperience(resume, job, config) {
|
|
505
509
|
const requiredYears = job.minExperienceYears ?? config.profile?.minExperience ?? 0;
|
|
@@ -517,11 +521,15 @@ function scoreExperience(resume, job, config) {
|
|
|
517
521
|
return { score, missingYears: Number(missingYears.toFixed(2)) };
|
|
518
522
|
}
|
|
519
523
|
function scoreKeywords(resume, job, config) {
|
|
520
|
-
const jobKeywordSet = new Set(
|
|
524
|
+
const jobKeywordSet = new Set(
|
|
525
|
+
job.keywords.map((k) => normalizeSkill(k, config.skillAliases))
|
|
526
|
+
);
|
|
521
527
|
if (jobKeywordSet.size === 0) {
|
|
522
528
|
return { score: 100, matchedKeywords: [], missingKeywords: [], overusedKeywords: [] };
|
|
523
529
|
}
|
|
524
|
-
const resumeTokens = tokenize(resume.normalizedText)
|
|
530
|
+
const resumeTokens = tokenize(resume.normalizedText).map(
|
|
531
|
+
(t) => normalizeSkill(t, config.skillAliases)
|
|
532
|
+
);
|
|
525
533
|
const resumeTokenSet = new Set(resumeTokens);
|
|
526
534
|
const matchedKeywords = [...jobKeywordSet].filter((keyword) => resumeTokenSet.has(keyword));
|
|
527
535
|
const missingKeywords = [...jobKeywordSet].filter((keyword) => !resumeTokenSet.has(keyword));
|
|
@@ -535,9 +543,9 @@ function scoreKeywords(resume, job, config) {
|
|
|
535
543
|
});
|
|
536
544
|
return {
|
|
537
545
|
score,
|
|
538
|
-
matchedKeywords: unique(matchedKeywords),
|
|
539
|
-
missingKeywords: unique(missingKeywords),
|
|
540
|
-
overusedKeywords: unique(overusedKeywords)
|
|
546
|
+
matchedKeywords: unique(matchedKeywords).sort(),
|
|
547
|
+
missingKeywords: unique(missingKeywords).sort(),
|
|
548
|
+
overusedKeywords: unique(overusedKeywords).sort()
|
|
541
549
|
};
|
|
542
550
|
}
|
|
543
551
|
function scoreEducation(resume, job) {
|
|
@@ -569,12 +577,17 @@ function calculateScore(resume, job, config) {
|
|
|
569
577
|
return {
|
|
570
578
|
score: clamp(Number(weightedScore.toFixed(2)), 0, 100),
|
|
571
579
|
breakdown,
|
|
580
|
+
matchedSkills: skillsResult.matched,
|
|
581
|
+
missingSkills: skillsResult.missing,
|
|
572
582
|
matchedKeywords: keywordResult.matchedKeywords,
|
|
573
583
|
missingKeywords: keywordResult.missingKeywords,
|
|
574
584
|
overusedKeywords: keywordResult.overusedKeywords,
|
|
575
585
|
suggestions: [],
|
|
576
586
|
warnings: [],
|
|
577
|
-
|
|
587
|
+
// detectedSections / parsedExperienceYears / experienceGap: filled by index.ts
|
|
588
|
+
experienceGap: experienceResult.missingYears,
|
|
589
|
+
detectedSections: [],
|
|
590
|
+
parsedExperienceYears: 0,
|
|
578
591
|
missingExperienceYears: experienceResult.missingYears,
|
|
579
592
|
educationScore
|
|
580
593
|
};
|
|
@@ -582,7 +595,9 @@ function calculateScore(resume, job, config) {
|
|
|
582
595
|
|
|
583
596
|
// src/profiles/index.ts
|
|
584
597
|
var defaultSkillAliases = {
|
|
585
|
-
|
|
598
|
+
// ponytail: "node" split from javascript — Node.js runtime !== JS language
|
|
599
|
+
javascript: ["js"],
|
|
600
|
+
node: ["node.js", "nodejs"],
|
|
586
601
|
typescript: ["ts"],
|
|
587
602
|
react: ["reactjs", "react.js"],
|
|
588
603
|
"c++": ["cpp"],
|
|
@@ -677,7 +692,8 @@ function resolveConfig(config = {}) {
|
|
|
677
692
|
...DEFAULT_SECTION_PENALTIES,
|
|
678
693
|
...config.sectionPenalties ?? {}
|
|
679
694
|
},
|
|
680
|
-
allowPartialMatches: config.allowPartialMatches ?? true
|
|
695
|
+
allowPartialMatches: config.allowPartialMatches ?? true,
|
|
696
|
+
referenceDate: config.referenceDate ? new Date(config.referenceDate) : void 0
|
|
681
697
|
};
|
|
682
698
|
return resolved;
|
|
683
699
|
}
|
|
@@ -1374,9 +1390,14 @@ function analyzeResume(input) {
|
|
|
1374
1390
|
return {
|
|
1375
1391
|
score: finalScore,
|
|
1376
1392
|
breakdown: scoring.breakdown,
|
|
1393
|
+
matchedSkills: scoring.matchedSkills,
|
|
1394
|
+
missingSkills: scoring.missingSkills,
|
|
1377
1395
|
matchedKeywords: scoring.matchedKeywords,
|
|
1378
1396
|
missingKeywords: scoring.missingKeywords,
|
|
1379
1397
|
overusedKeywords: scoring.overusedKeywords,
|
|
1398
|
+
experienceGap: scoring.experienceGap,
|
|
1399
|
+
detectedSections: parsedResume.detectedSections,
|
|
1400
|
+
parsedExperienceYears: parsedResume.totalExperienceYears,
|
|
1380
1401
|
suggestions,
|
|
1381
1402
|
warnings: [...suggestionResult.warnings, ...llmWarnings]
|
|
1382
1403
|
};
|
|
@@ -1433,9 +1454,14 @@ async function analyzeResumeAsync(input) {
|
|
|
1433
1454
|
return {
|
|
1434
1455
|
score: finalScore,
|
|
1435
1456
|
breakdown: scoring.breakdown,
|
|
1457
|
+
matchedSkills: scoring.matchedSkills,
|
|
1458
|
+
missingSkills: scoring.missingSkills,
|
|
1436
1459
|
matchedKeywords: scoring.matchedKeywords,
|
|
1437
1460
|
missingKeywords: scoring.missingKeywords,
|
|
1438
1461
|
overusedKeywords: scoring.overusedKeywords,
|
|
1462
|
+
experienceGap: scoring.experienceGap,
|
|
1463
|
+
detectedSections: parsedResume.detectedSections,
|
|
1464
|
+
parsedExperienceYears: parsedResume.totalExperienceYears,
|
|
1439
1465
|
suggestions,
|
|
1440
1466
|
warnings: [...suggestionResult.warnings, ...llmWarnings]
|
|
1441
1467
|
};
|