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