@pranavraut033/ats-checker 1.0.5 → 1.1.1
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 +145 -108
- package/dist/index.d.mts +29 -9
- package/dist/index.d.ts +29 -9
- package/dist/index.js +261 -53
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +261 -53
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/utils/text.ts
|
|
2
2
|
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
3
|
+
// articles / prepositions / conjunctions
|
|
3
4
|
"the",
|
|
4
5
|
"and",
|
|
5
6
|
"or",
|
|
@@ -15,12 +16,16 @@ var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
|
15
16
|
"by",
|
|
16
17
|
"from",
|
|
17
18
|
"as",
|
|
19
|
+
"into",
|
|
20
|
+
"onto",
|
|
21
|
+
"upon",
|
|
22
|
+
"via",
|
|
23
|
+
"per",
|
|
24
|
+
"plus",
|
|
25
|
+
// verbs / modals
|
|
18
26
|
"is",
|
|
19
27
|
"are",
|
|
20
28
|
"be",
|
|
21
|
-
"this",
|
|
22
|
-
"that",
|
|
23
|
-
"it",
|
|
24
29
|
"was",
|
|
25
30
|
"were",
|
|
26
31
|
"will",
|
|
@@ -29,19 +34,117 @@ var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
|
29
34
|
"must",
|
|
30
35
|
"have",
|
|
31
36
|
"has",
|
|
32
|
-
"had"
|
|
37
|
+
"had",
|
|
38
|
+
"do",
|
|
39
|
+
"does",
|
|
40
|
+
"did",
|
|
41
|
+
"get",
|
|
42
|
+
"give",
|
|
43
|
+
"go",
|
|
44
|
+
"use",
|
|
45
|
+
"see",
|
|
46
|
+
"help",
|
|
47
|
+
"work",
|
|
48
|
+
"build",
|
|
49
|
+
"show",
|
|
50
|
+
"need",
|
|
51
|
+
"want",
|
|
52
|
+
"make",
|
|
53
|
+
"let",
|
|
54
|
+
// pronouns / determiners
|
|
55
|
+
"it",
|
|
56
|
+
"its",
|
|
57
|
+
"this",
|
|
58
|
+
"that",
|
|
59
|
+
"these",
|
|
60
|
+
"those",
|
|
61
|
+
"we",
|
|
62
|
+
"our",
|
|
63
|
+
"you",
|
|
64
|
+
"your",
|
|
65
|
+
"they",
|
|
66
|
+
"their",
|
|
67
|
+
"us",
|
|
68
|
+
"who",
|
|
69
|
+
"what",
|
|
70
|
+
"which",
|
|
71
|
+
"how",
|
|
72
|
+
// common English fillers that leak into JDs
|
|
73
|
+
"no",
|
|
74
|
+
"not",
|
|
75
|
+
"all",
|
|
76
|
+
"any",
|
|
77
|
+
"also",
|
|
78
|
+
"more",
|
|
79
|
+
"well",
|
|
80
|
+
"very",
|
|
81
|
+
"highly",
|
|
82
|
+
"across",
|
|
83
|
+
"over",
|
|
84
|
+
"under",
|
|
85
|
+
"within",
|
|
86
|
+
"about",
|
|
87
|
+
"out",
|
|
88
|
+
"up",
|
|
89
|
+
"down",
|
|
90
|
+
"new",
|
|
91
|
+
"if",
|
|
92
|
+
"so",
|
|
93
|
+
"such",
|
|
94
|
+
"both",
|
|
95
|
+
"each",
|
|
96
|
+
"one",
|
|
97
|
+
"many",
|
|
98
|
+
"only",
|
|
99
|
+
// JD/HR boilerplate — never skills
|
|
100
|
+
"years",
|
|
101
|
+
"year",
|
|
102
|
+
"experience",
|
|
103
|
+
"required",
|
|
104
|
+
"requirement",
|
|
105
|
+
"requirements",
|
|
106
|
+
"preferred",
|
|
107
|
+
"role",
|
|
108
|
+
"degree",
|
|
109
|
+
"practices",
|
|
110
|
+
"best",
|
|
111
|
+
"skills",
|
|
112
|
+
"team",
|
|
113
|
+
"field",
|
|
114
|
+
"related",
|
|
115
|
+
"relevant",
|
|
116
|
+
"desired",
|
|
117
|
+
"strong",
|
|
118
|
+
"solid",
|
|
119
|
+
"good",
|
|
120
|
+
"first",
|
|
121
|
+
"based",
|
|
122
|
+
"day",
|
|
123
|
+
"week",
|
|
124
|
+
"month",
|
|
125
|
+
"time",
|
|
126
|
+
"fast",
|
|
127
|
+
"open",
|
|
128
|
+
"dynamic"
|
|
33
129
|
]);
|
|
34
130
|
function normalizeWhitespace(text) {
|
|
35
131
|
return text.replace(/\r\n?/g, "\n").replace(/\s+/g, " ").trim();
|
|
36
132
|
}
|
|
37
133
|
function normalizeForComparison(text) {
|
|
38
|
-
return normalizeWhitespace(text).toLowerCase();
|
|
134
|
+
return normalizeWhitespace(text).normalize("NFKC").toLowerCase();
|
|
39
135
|
}
|
|
40
136
|
function splitLines(text) {
|
|
41
137
|
return text.replace(/\r\n?/g, "\n").split("\n").map((line) => line.trim()).filter(Boolean);
|
|
42
138
|
}
|
|
139
|
+
var TECH_TOKEN_RE = /[a-z0-9][a-z0-9.#+\-/]*[a-z0-9#+]/g;
|
|
43
140
|
function tokenize(text) {
|
|
44
|
-
|
|
141
|
+
const normalized = normalizeForComparison(text);
|
|
142
|
+
return (normalized.match(TECH_TOKEN_RE) ?? []).filter(
|
|
143
|
+
(t) => /[a-z]/.test(t) && !STOP_WORDS.has(t)
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
function escapeRegExp(input) {
|
|
147
|
+
return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
45
148
|
}
|
|
46
149
|
function unique(values) {
|
|
47
150
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -97,37 +200,30 @@ function normalizeSkills(skills, aliases) {
|
|
|
97
200
|
}
|
|
98
201
|
|
|
99
202
|
// src/core/parser/jd.parser.ts
|
|
100
|
-
var
|
|
101
|
-
"bachelor",
|
|
102
|
-
|
|
103
|
-
"
|
|
104
|
-
"
|
|
105
|
-
"
|
|
106
|
-
"m.s",
|
|
107
|
-
"ms",
|
|
108
|
-
"msc",
|
|
109
|
-
"phd",
|
|
110
|
-
"doctorate",
|
|
111
|
-
"mba",
|
|
112
|
-
"associate"
|
|
203
|
+
var DEGREE_VARIANTS = [
|
|
204
|
+
[/\b(?:bachelor(?:'s)?|b\.s\.?|bs\.?|bsc\.?)\b/i, "bachelor"],
|
|
205
|
+
[/\b(?:master(?:'s)?|m\.s\.?|ms\.?|msc\.?)\b/i, "master"],
|
|
206
|
+
[/\b(?:phd|ph\.d\.?|doctorate)\b/i, "phd"],
|
|
207
|
+
[/\bmba\b/i, "mba"],
|
|
208
|
+
[/\bassociate(?:'s)?\b/i, "associate"]
|
|
113
209
|
];
|
|
114
210
|
function extractRequiredSkills(lines) {
|
|
115
211
|
const required = [];
|
|
116
212
|
for (const line of lines) {
|
|
117
213
|
if (/must|require|required|need/i.test(line)) {
|
|
118
|
-
required.push(...line
|
|
214
|
+
required.push(...tokenize(line));
|
|
119
215
|
}
|
|
120
216
|
}
|
|
121
|
-
return required
|
|
217
|
+
return required;
|
|
122
218
|
}
|
|
123
219
|
function extractPreferredSkills(lines) {
|
|
124
220
|
const preferred = [];
|
|
125
221
|
for (const line of lines) {
|
|
126
222
|
if (/preferred|nice to have|plus/i.test(line)) {
|
|
127
|
-
preferred.push(...line
|
|
223
|
+
preferred.push(...tokenize(line));
|
|
128
224
|
}
|
|
129
225
|
}
|
|
130
|
-
return preferred
|
|
226
|
+
return preferred;
|
|
131
227
|
}
|
|
132
228
|
function extractRoleKeywords(text) {
|
|
133
229
|
const roleMatch = text.match(/(engineer|developer|manager|scientist|analyst|designer|architect)/i);
|
|
@@ -142,23 +238,41 @@ function extractMinExperience(text) {
|
|
|
142
238
|
return void 0;
|
|
143
239
|
}
|
|
144
240
|
function extractEducationRequirements(text) {
|
|
145
|
-
const
|
|
146
|
-
|
|
241
|
+
const found = /* @__PURE__ */ new Set();
|
|
242
|
+
for (const [pattern, canonical] of DEGREE_VARIANTS) {
|
|
243
|
+
if (pattern.test(text)) found.add(canonical);
|
|
244
|
+
}
|
|
245
|
+
return [...found];
|
|
147
246
|
}
|
|
148
247
|
function parseJobDescription(jobDescription, config) {
|
|
149
248
|
const normalizedText = normalizeWhitespace(jobDescription);
|
|
150
249
|
const lines = splitLines(jobDescription);
|
|
151
|
-
const
|
|
152
|
-
const
|
|
250
|
+
const skillVocab = /* @__PURE__ */ new Set();
|
|
251
|
+
for (const [canonical, aliases] of Object.entries(config.skillAliases)) {
|
|
252
|
+
skillVocab.add(canonical.toLowerCase());
|
|
253
|
+
for (const alias of aliases) skillVocab.add(alias.toLowerCase());
|
|
254
|
+
}
|
|
255
|
+
for (const s of config.profile?.mandatorySkills ?? []) skillVocab.add(s.toLowerCase());
|
|
256
|
+
for (const s of config.profile?.optionalSkills ?? []) skillVocab.add(s.toLowerCase());
|
|
257
|
+
const isSkillLike = (t) => {
|
|
258
|
+
if (skillVocab.has(t)) return true;
|
|
259
|
+
if (/[.#+]/.test(t) && /[a-z]/.test(t)) return true;
|
|
260
|
+
if (t.includes("/")) return t.split("/").some((p) => p.length >= 2 && !STOP_WORDS.has(p));
|
|
261
|
+
return false;
|
|
262
|
+
};
|
|
263
|
+
const requiredSkillsRaw = extractRequiredSkills(lines).filter(isSkillLike);
|
|
264
|
+
const preferredSkillsRaw = extractPreferredSkills(lines).filter(isSkillLike);
|
|
153
265
|
const requiredSkills = normalizeSkills(requiredSkillsRaw, config.skillAliases);
|
|
154
266
|
const preferredSkills = normalizeSkills(preferredSkillsRaw, config.skillAliases);
|
|
155
|
-
const
|
|
267
|
+
const bodyTokens = tokenize(normalizedText).filter(isSkillLike);
|
|
268
|
+
const roleKeywords = extractRoleKeywords(jobDescription);
|
|
269
|
+
const keywords = unique([...requiredSkills, ...preferredSkills, ...roleKeywords, ...bodyTokens]);
|
|
156
270
|
return {
|
|
157
271
|
raw: jobDescription,
|
|
158
272
|
normalizedText,
|
|
159
273
|
requiredSkills,
|
|
160
274
|
preferredSkills,
|
|
161
|
-
roleKeywords
|
|
275
|
+
roleKeywords,
|
|
162
276
|
keywords,
|
|
163
277
|
minExperienceYears: extractMinExperience(jobDescription),
|
|
164
278
|
educationRequirements: extractEducationRequirements(jobDescription)
|
|
@@ -203,6 +317,14 @@ function parseDateToken(raw) {
|
|
|
203
317
|
return { year, month };
|
|
204
318
|
}
|
|
205
319
|
}
|
|
320
|
+
const slashMatch = cleaned.match(/^(\d{1,2})\/(\d{4})$/);
|
|
321
|
+
if (slashMatch) {
|
|
322
|
+
const month = Number.parseInt(slashMatch[1], 10);
|
|
323
|
+
const year = Number.parseInt(slashMatch[2], 10);
|
|
324
|
+
if (month >= 1 && month <= 12 && !Number.isNaN(year)) {
|
|
325
|
+
return { year, month };
|
|
326
|
+
}
|
|
327
|
+
}
|
|
206
328
|
const yearMatch = cleaned.match(/(20\d{2}|19\d{2})/);
|
|
207
329
|
if (yearMatch) {
|
|
208
330
|
const year = Number.parseInt(yearMatch[1], 10);
|
|
@@ -215,9 +337,11 @@ function monthsBetween(start, end) {
|
|
|
215
337
|
const endMonth = end.month ?? 12;
|
|
216
338
|
return (end.year - start.year) * 12 + (endMonth - startMonth + 1);
|
|
217
339
|
}
|
|
218
|
-
function parseDateRange(text) {
|
|
340
|
+
function parseDateRange(text, referenceDate) {
|
|
219
341
|
const normalized = text.trim();
|
|
220
|
-
const rangeMatch = normalized.match(
|
|
342
|
+
const rangeMatch = normalized.match(
|
|
343
|
+
/(\d{1,2}\/\d{4}|[A-Za-z]{3,9}\s+\d{4}|\d{4})\s*(?:-|to|–|—)\s*(Present|Current|Now|\d{1,2}\/\d{4}|[A-Za-z]{3,9}\s+\d{4}|\d{4})/i
|
|
344
|
+
);
|
|
221
345
|
if (!rangeMatch) {
|
|
222
346
|
return null;
|
|
223
347
|
}
|
|
@@ -228,20 +352,50 @@ function parseDateRange(text) {
|
|
|
228
352
|
if (!startToken) {
|
|
229
353
|
return null;
|
|
230
354
|
}
|
|
355
|
+
const ref = referenceDate ?? /* @__PURE__ */ new Date();
|
|
231
356
|
const endTokenResolved = endToken ?? {
|
|
232
|
-
year:
|
|
233
|
-
month:
|
|
357
|
+
year: ref.getFullYear(),
|
|
358
|
+
month: ref.getMonth() + 1
|
|
234
359
|
};
|
|
235
360
|
const durationInMonths = monthsBetween(startToken, endTokenResolved);
|
|
236
361
|
return {
|
|
237
362
|
raw: normalized,
|
|
238
363
|
start: rangeMatch[1],
|
|
239
364
|
end: isPresent ? "present" : rangeMatch[2],
|
|
240
|
-
durationInMonths: durationInMonths > 0 ? durationInMonths : void 0
|
|
365
|
+
durationInMonths: durationInMonths > 0 ? durationInMonths : void 0,
|
|
366
|
+
startYear: startToken.year,
|
|
367
|
+
startMonth: startToken.month,
|
|
368
|
+
endYear: endTokenResolved.year,
|
|
369
|
+
endMonth: endTokenResolved.month
|
|
241
370
|
};
|
|
242
371
|
}
|
|
243
372
|
function sumExperienceYears(ranges) {
|
|
244
|
-
const
|
|
373
|
+
const withBounds = ranges.filter(
|
|
374
|
+
(r) => r.startYear !== void 0 && r.endYear !== void 0
|
|
375
|
+
);
|
|
376
|
+
if (withBounds.length === ranges.length && ranges.length > 0) {
|
|
377
|
+
const toIndex = (year, month) => year * 12 + month;
|
|
378
|
+
const intervals = withBounds.map((r) => ({
|
|
379
|
+
s: toIndex(r.startYear, r.startMonth ?? 1),
|
|
380
|
+
e: toIndex(r.endYear, r.endMonth ?? 12)
|
|
381
|
+
})).sort((a, b) => a.s - b.s);
|
|
382
|
+
let totalMonths = 0;
|
|
383
|
+
let curStart = intervals[0].s;
|
|
384
|
+
let curEnd = intervals[0].e;
|
|
385
|
+
for (let i = 1; i < intervals.length; i++) {
|
|
386
|
+
const { s, e } = intervals[i];
|
|
387
|
+
if (s <= curEnd) {
|
|
388
|
+
curEnd = Math.max(curEnd, e);
|
|
389
|
+
} else {
|
|
390
|
+
totalMonths += curEnd - curStart + 1;
|
|
391
|
+
curStart = s;
|
|
392
|
+
curEnd = e;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
totalMonths += curEnd - curStart + 1;
|
|
396
|
+
return Number((totalMonths / 12).toFixed(2));
|
|
397
|
+
}
|
|
398
|
+
const months = ranges.reduce((total, r) => total + (r.durationInMonths ?? 0), 0);
|
|
245
399
|
return Number((months / 12).toFixed(2));
|
|
246
400
|
}
|
|
247
401
|
|
|
@@ -275,9 +429,6 @@ var ACTION_VERBS = [
|
|
|
275
429
|
"reduced",
|
|
276
430
|
"increased"
|
|
277
431
|
];
|
|
278
|
-
function escapeRegExp(input) {
|
|
279
|
-
return input.replace(/[.*+?^${}()|[\\]\\]/g, "\\$&");
|
|
280
|
-
}
|
|
281
432
|
function detectSection(line) {
|
|
282
433
|
const normalized = line.trim().toLowerCase();
|
|
283
434
|
for (const [section, aliases] of Object.entries(SECTION_ALIASES)) {
|
|
@@ -325,7 +476,7 @@ function parseActionVerbs(text) {
|
|
|
325
476
|
const words = tokenize(text);
|
|
326
477
|
return ACTION_VERBS.filter((verb) => words.includes(verb));
|
|
327
478
|
}
|
|
328
|
-
function parseExperience(sectionContent) {
|
|
479
|
+
function parseExperience(sectionContent, referenceDate) {
|
|
329
480
|
if (!sectionContent) {
|
|
330
481
|
return { entries: [], rangesInMonths: [], jobTitles: [] };
|
|
331
482
|
}
|
|
@@ -334,7 +485,7 @@ function parseExperience(sectionContent) {
|
|
|
334
485
|
const rangesInMonths = [];
|
|
335
486
|
const jobTitles = [];
|
|
336
487
|
for (const line of lines) {
|
|
337
|
-
const range = parseDateRange(line);
|
|
488
|
+
const range = parseDateRange(line, referenceDate);
|
|
338
489
|
if (range) {
|
|
339
490
|
const previous = entries[entries.length - 1];
|
|
340
491
|
if (previous && !previous.dates) {
|
|
@@ -374,11 +525,18 @@ function parseResume(resumeText, config) {
|
|
|
374
525
|
const { sections, detected } = extractSections(resumeText);
|
|
375
526
|
const skills = parseSkills(sections.skills, config.skillAliases);
|
|
376
527
|
const actionVerbs = parseActionVerbs(normalizedText);
|
|
377
|
-
const experienceData = parseExperience(sections.experience);
|
|
528
|
+
const experienceData = parseExperience(sections.experience, config.referenceDate);
|
|
378
529
|
const educationEntries = parseEducation(sections.education);
|
|
379
|
-
|
|
530
|
+
let totalExperienceYears = sumExperienceYears(
|
|
380
531
|
experienceData.entries.map((entry) => entry.dates).filter((range) => Boolean(range))
|
|
381
532
|
);
|
|
533
|
+
if (totalExperienceYears === 0) {
|
|
534
|
+
const textToScan = sections.summary ?? normalizedText;
|
|
535
|
+
const yearsMatch = textToScan.match(/(\d{1,2})\+?\s*years?/i);
|
|
536
|
+
if (yearsMatch) {
|
|
537
|
+
totalExperienceYears = Number.parseInt(yearsMatch[1], 10);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
382
540
|
const requiredSections = ["summary", "experience", "skills", "education"];
|
|
383
541
|
const warnings = [];
|
|
384
542
|
for (const section of requiredSections) {
|
|
@@ -498,8 +656,9 @@ function scoreSkills(resume, job, config) {
|
|
|
498
656
|
0,
|
|
499
657
|
100
|
|
500
658
|
);
|
|
501
|
-
const
|
|
502
|
-
|
|
659
|
+
const matched = [...required].filter((skill) => resumeSkills.has(skill)).sort();
|
|
660
|
+
const missing = [...required].filter((skill) => !resumeSkills.has(skill)).sort();
|
|
661
|
+
return { score, matched, missing };
|
|
503
662
|
}
|
|
504
663
|
function scoreExperience(resume, job, config) {
|
|
505
664
|
const requiredYears = job.minExperienceYears ?? config.profile?.minExperience ?? 0;
|
|
@@ -517,11 +676,15 @@ function scoreExperience(resume, job, config) {
|
|
|
517
676
|
return { score, missingYears: Number(missingYears.toFixed(2)) };
|
|
518
677
|
}
|
|
519
678
|
function scoreKeywords(resume, job, config) {
|
|
520
|
-
const jobKeywordSet = new Set(
|
|
679
|
+
const jobKeywordSet = new Set(
|
|
680
|
+
job.keywords.map((k) => normalizeSkill(k, config.skillAliases))
|
|
681
|
+
);
|
|
521
682
|
if (jobKeywordSet.size === 0) {
|
|
522
683
|
return { score: 100, matchedKeywords: [], missingKeywords: [], overusedKeywords: [] };
|
|
523
684
|
}
|
|
524
|
-
const resumeTokens = tokenize(resume.normalizedText)
|
|
685
|
+
const resumeTokens = tokenize(resume.normalizedText).map(
|
|
686
|
+
(t) => normalizeSkill(t, config.skillAliases)
|
|
687
|
+
);
|
|
525
688
|
const resumeTokenSet = new Set(resumeTokens);
|
|
526
689
|
const matchedKeywords = [...jobKeywordSet].filter((keyword) => resumeTokenSet.has(keyword));
|
|
527
690
|
const missingKeywords = [...jobKeywordSet].filter((keyword) => !resumeTokenSet.has(keyword));
|
|
@@ -535,9 +698,9 @@ function scoreKeywords(resume, job, config) {
|
|
|
535
698
|
});
|
|
536
699
|
return {
|
|
537
700
|
score,
|
|
538
|
-
matchedKeywords: unique(matchedKeywords),
|
|
539
|
-
missingKeywords: unique(missingKeywords),
|
|
540
|
-
overusedKeywords: unique(overusedKeywords)
|
|
701
|
+
matchedKeywords: unique(matchedKeywords).sort(),
|
|
702
|
+
missingKeywords: unique(missingKeywords).sort(),
|
|
703
|
+
overusedKeywords: unique(overusedKeywords).sort()
|
|
541
704
|
};
|
|
542
705
|
}
|
|
543
706
|
function scoreEducation(resume, job) {
|
|
@@ -569,12 +732,18 @@ function calculateScore(resume, job, config) {
|
|
|
569
732
|
return {
|
|
570
733
|
score: clamp(Number(weightedScore.toFixed(2)), 0, 100),
|
|
571
734
|
breakdown,
|
|
735
|
+
matchedSkills: skillsResult.matched,
|
|
736
|
+
missingSkills: skillsResult.missing,
|
|
572
737
|
matchedKeywords: keywordResult.matchedKeywords,
|
|
573
738
|
missingKeywords: keywordResult.missingKeywords,
|
|
574
739
|
overusedKeywords: keywordResult.overusedKeywords,
|
|
575
740
|
suggestions: [],
|
|
576
741
|
warnings: [],
|
|
577
|
-
|
|
742
|
+
// detectedSections / parsedExperienceYears / experienceGap / experienceEntries: filled by index.ts
|
|
743
|
+
experienceGap: experienceResult.missingYears,
|
|
744
|
+
detectedSections: [],
|
|
745
|
+
parsedExperienceYears: 0,
|
|
746
|
+
experienceEntries: [],
|
|
578
747
|
missingExperienceYears: experienceResult.missingYears,
|
|
579
748
|
educationScore
|
|
580
749
|
};
|
|
@@ -582,7 +751,9 @@ function calculateScore(resume, job, config) {
|
|
|
582
751
|
|
|
583
752
|
// src/profiles/index.ts
|
|
584
753
|
var defaultSkillAliases = {
|
|
585
|
-
|
|
754
|
+
// ponytail: "node" split from javascript — Node.js runtime !== JS language
|
|
755
|
+
javascript: ["js"],
|
|
756
|
+
node: ["node.js", "nodejs"],
|
|
586
757
|
typescript: ["ts"],
|
|
587
758
|
react: ["reactjs", "react.js"],
|
|
588
759
|
"c++": ["cpp"],
|
|
@@ -596,7 +767,31 @@ var defaultSkillAliases = {
|
|
|
596
767
|
docker: ["containers"],
|
|
597
768
|
kubernetes: ["k8s"],
|
|
598
769
|
html: ["html5"],
|
|
599
|
-
css: ["css3"]
|
|
770
|
+
css: ["css3"],
|
|
771
|
+
// ML / data science
|
|
772
|
+
pytorch: ["torch"],
|
|
773
|
+
tensorflow: ["tf"],
|
|
774
|
+
"scikit-learn": ["sklearn"],
|
|
775
|
+
pandas: [],
|
|
776
|
+
numpy: [],
|
|
777
|
+
fastapi: [],
|
|
778
|
+
flask: [],
|
|
779
|
+
django: [],
|
|
780
|
+
// data / infra
|
|
781
|
+
kafka: [],
|
|
782
|
+
redis: [],
|
|
783
|
+
elasticsearch: ["elastic"],
|
|
784
|
+
spark: ["apache spark"],
|
|
785
|
+
// common pure-letter tech skills (no symbol chars)
|
|
786
|
+
accessibility: ["a11y"],
|
|
787
|
+
frontend: ["front-end"],
|
|
788
|
+
backend: ["back-end"],
|
|
789
|
+
security: ["cybersecurity"],
|
|
790
|
+
testing: ["unittest", "pytest"],
|
|
791
|
+
microservices: [],
|
|
792
|
+
agile: ["scrum"],
|
|
793
|
+
blockchain: [],
|
|
794
|
+
devops: []
|
|
600
795
|
};
|
|
601
796
|
var softwareEngineerProfile = {
|
|
602
797
|
name: "software-engineer",
|
|
@@ -677,7 +872,8 @@ function resolveConfig(config = {}) {
|
|
|
677
872
|
...DEFAULT_SECTION_PENALTIES,
|
|
678
873
|
...config.sectionPenalties ?? {}
|
|
679
874
|
},
|
|
680
|
-
allowPartialMatches: config.allowPartialMatches ?? true
|
|
875
|
+
allowPartialMatches: config.allowPartialMatches ?? true,
|
|
876
|
+
referenceDate: config.referenceDate ? new Date(config.referenceDate) : void 0
|
|
681
877
|
};
|
|
682
878
|
return resolved;
|
|
683
879
|
}
|
|
@@ -1374,9 +1570,15 @@ function analyzeResume(input) {
|
|
|
1374
1570
|
return {
|
|
1375
1571
|
score: finalScore,
|
|
1376
1572
|
breakdown: scoring.breakdown,
|
|
1573
|
+
matchedSkills: scoring.matchedSkills,
|
|
1574
|
+
missingSkills: scoring.missingSkills,
|
|
1377
1575
|
matchedKeywords: scoring.matchedKeywords,
|
|
1378
1576
|
missingKeywords: scoring.missingKeywords,
|
|
1379
1577
|
overusedKeywords: scoring.overusedKeywords,
|
|
1578
|
+
experienceGap: scoring.experienceGap,
|
|
1579
|
+
detectedSections: parsedResume.detectedSections,
|
|
1580
|
+
parsedExperienceYears: parsedResume.totalExperienceYears,
|
|
1581
|
+
experienceEntries: parsedResume.experience,
|
|
1380
1582
|
suggestions,
|
|
1381
1583
|
warnings: [...suggestionResult.warnings, ...llmWarnings]
|
|
1382
1584
|
};
|
|
@@ -1433,9 +1635,15 @@ async function analyzeResumeAsync(input) {
|
|
|
1433
1635
|
return {
|
|
1434
1636
|
score: finalScore,
|
|
1435
1637
|
breakdown: scoring.breakdown,
|
|
1638
|
+
matchedSkills: scoring.matchedSkills,
|
|
1639
|
+
missingSkills: scoring.missingSkills,
|
|
1436
1640
|
matchedKeywords: scoring.matchedKeywords,
|
|
1437
1641
|
missingKeywords: scoring.missingKeywords,
|
|
1438
1642
|
overusedKeywords: scoring.overusedKeywords,
|
|
1643
|
+
experienceGap: scoring.experienceGap,
|
|
1644
|
+
detectedSections: parsedResume.detectedSections,
|
|
1645
|
+
parsedExperienceYears: parsedResume.totalExperienceYears,
|
|
1646
|
+
experienceEntries: parsedResume.experience,
|
|
1439
1647
|
suggestions,
|
|
1440
1648
|
warnings: [...suggestionResult.warnings, ...llmWarnings]
|
|
1441
1649
|
};
|