@pranavraut033/ats-checker 1.1.1 → 1.3.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 +111 -5
- package/dist/chunk-ZJ5E4H7Z.mjs +446 -0
- package/dist/chunk-ZJ5E4H7Z.mjs.map +1 -0
- package/dist/{index.js → index.cjs} +512 -67
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.mts +4 -259
- package/dist/index.d.ts +4 -259
- package/dist/index.mjs +278 -274
- package/dist/index.mjs.map +1 -1
- package/dist/lang/de/index.cjs +70 -0
- package/dist/lang/de/index.cjs.map +1 -0
- package/dist/lang/de/index.d.mts +16 -0
- package/dist/lang/de/index.d.ts +16 -0
- package/dist/lang/de/index.mjs +65 -0
- package/dist/lang/de/index.mjs.map +1 -0
- package/dist/lang/en/index.cjs +212 -0
- package/dist/lang/en/index.cjs.map +1 -0
- package/dist/lang/en/index.d.mts +5 -0
- package/dist/lang/en/index.d.ts +5 -0
- package/dist/lang/en/index.mjs +9 -0
- package/dist/lang/en/index.mjs.map +1 -0
- package/dist/pdf/index.cjs +81 -0
- package/dist/pdf/index.cjs.map +1 -0
- package/dist/pdf/index.d.mts +12 -0
- package/dist/pdf/index.d.ts +12 -0
- package/dist/pdf/index.mjs +79 -0
- package/dist/pdf/index.mjs.map +1 -0
- package/dist/scoring-BCShrnki.d.mts +319 -0
- package/dist/scoring-BCShrnki.d.ts +319 -0
- package/package.json +30 -3
- package/dist/index.js.map +0 -1
|
@@ -185,20 +185,157 @@ function containsTableLikeStructure(text) {
|
|
|
185
185
|
}
|
|
186
186
|
|
|
187
187
|
// src/utils/skills.ts
|
|
188
|
+
var aliasIndexCache = /* @__PURE__ */ new WeakMap();
|
|
189
|
+
function getAliasIndex(aliases) {
|
|
190
|
+
let index = aliasIndexCache.get(aliases);
|
|
191
|
+
if (!index) {
|
|
192
|
+
index = /* @__PURE__ */ new Map();
|
|
193
|
+
for (const [canonical, aliasList] of Object.entries(aliases)) {
|
|
194
|
+
const lower = canonical.toLowerCase();
|
|
195
|
+
index.set(lower, lower);
|
|
196
|
+
for (const alias of aliasList) {
|
|
197
|
+
index.set(alias.toLowerCase(), lower);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
aliasIndexCache.set(aliases, index);
|
|
201
|
+
}
|
|
202
|
+
return index;
|
|
203
|
+
}
|
|
188
204
|
function normalizeSkill(skill, aliases) {
|
|
189
205
|
const normalized = skill.trim().toLowerCase();
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
206
|
+
return getAliasIndex(aliases).get(normalized) ?? normalized;
|
|
207
|
+
}
|
|
208
|
+
function normalizeSkills(skills, aliases) {
|
|
209
|
+
return unique(skills.map((skill) => normalizeSkill(skill, aliases)));
|
|
210
|
+
}
|
|
211
|
+
function deriveSkillAliases(registry) {
|
|
212
|
+
const aliases = {};
|
|
213
|
+
for (const entry of registry) {
|
|
214
|
+
aliases[entry.canonical] = entry.aliases;
|
|
215
|
+
}
|
|
216
|
+
return aliases;
|
|
217
|
+
}
|
|
218
|
+
function buildCategoryIndex(registry) {
|
|
219
|
+
const index = /* @__PURE__ */ new Map();
|
|
220
|
+
for (const entry of registry) {
|
|
221
|
+
index.set(entry.canonical.toLowerCase(), entry.category);
|
|
222
|
+
}
|
|
223
|
+
return index;
|
|
224
|
+
}
|
|
225
|
+
function mergeKeywordRegistries(base, overrides) {
|
|
226
|
+
const byCanonical = /* @__PURE__ */ new Map();
|
|
227
|
+
for (const entry of base) byCanonical.set(entry.canonical.toLowerCase(), entry);
|
|
228
|
+
for (const entry of overrides) byCanonical.set(entry.canonical.toLowerCase(), entry);
|
|
229
|
+
return [...byCanonical.values()];
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// src/utils/languages.ts
|
|
233
|
+
var KNOWN_LANGUAGES = [
|
|
234
|
+
"english",
|
|
235
|
+
"spanish",
|
|
236
|
+
"french",
|
|
237
|
+
"german",
|
|
238
|
+
"italian",
|
|
239
|
+
"portuguese",
|
|
240
|
+
"dutch",
|
|
241
|
+
"russian",
|
|
242
|
+
"mandarin",
|
|
243
|
+
"chinese",
|
|
244
|
+
"cantonese",
|
|
245
|
+
"japanese",
|
|
246
|
+
"korean",
|
|
247
|
+
"arabic",
|
|
248
|
+
"hindi",
|
|
249
|
+
"polish",
|
|
250
|
+
"turkish",
|
|
251
|
+
"vietnamese",
|
|
252
|
+
"swedish",
|
|
253
|
+
"norwegian",
|
|
254
|
+
"danish",
|
|
255
|
+
"finnish",
|
|
256
|
+
"greek",
|
|
257
|
+
"hebrew",
|
|
258
|
+
"thai",
|
|
259
|
+
"indonesian",
|
|
260
|
+
"ukrainian",
|
|
261
|
+
"czech"
|
|
262
|
+
];
|
|
263
|
+
var LANGUAGE_ALIASES = {
|
|
264
|
+
mandarin: "chinese",
|
|
265
|
+
cantonese: "chinese"
|
|
266
|
+
};
|
|
267
|
+
var LEVEL_RANK = {
|
|
268
|
+
a1: 1,
|
|
269
|
+
a2: 2,
|
|
270
|
+
b1: 3,
|
|
271
|
+
b2: 4,
|
|
272
|
+
c1: 5,
|
|
273
|
+
c2: 6,
|
|
274
|
+
basic: 1,
|
|
275
|
+
elementary: 1,
|
|
276
|
+
limited: 2,
|
|
277
|
+
conversational: 3,
|
|
278
|
+
intermediate: 3,
|
|
279
|
+
professional: 4,
|
|
280
|
+
"upper intermediate": 4,
|
|
281
|
+
advanced: 4,
|
|
282
|
+
fluent: 5,
|
|
283
|
+
native: 6,
|
|
284
|
+
"native speaker": 6,
|
|
285
|
+
bilingual: 6
|
|
286
|
+
};
|
|
287
|
+
var LANGUAGE_GROUP = KNOWN_LANGUAGES.join("|");
|
|
288
|
+
var LEVEL_GROUP = Object.keys(LEVEL_RANK).sort((a, b) => b.length - a.length).map((l) => l.replace(/\s+/g, "\\s+")).join("|");
|
|
289
|
+
var LANGUAGE_LEVEL_RE = new RegExp(
|
|
290
|
+
`\\b(${LANGUAGE_GROUP})\\b(?:\\s*[\\(:\\-]?\\s*(${LEVEL_GROUP}|[abc][12]))?`,
|
|
291
|
+
"gi"
|
|
292
|
+
);
|
|
293
|
+
var LEVEL_BEFORE_LANGUAGE_RE = new RegExp(`\\b(${LEVEL_GROUP})\\s+(?:in\\s+)?(${LANGUAGE_GROUP})\\b`, "gi");
|
|
294
|
+
function canonicalLanguage(name) {
|
|
295
|
+
const lower = name.toLowerCase();
|
|
296
|
+
return LANGUAGE_ALIASES[lower] ?? lower;
|
|
297
|
+
}
|
|
298
|
+
function toParsedLanguage(name, level) {
|
|
299
|
+
const normalizedLevel = level?.toLowerCase().replace(/\s+/g, " ");
|
|
300
|
+
return {
|
|
301
|
+
name: canonicalLanguage(name),
|
|
302
|
+
level: normalizedLevel,
|
|
303
|
+
levelRank: normalizedLevel ? LEVEL_RANK[normalizedLevel] : void 0
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
function parseLanguageMentions(text) {
|
|
307
|
+
const found = /* @__PURE__ */ new Map();
|
|
308
|
+
for (const match of text.matchAll(LANGUAGE_LEVEL_RE)) {
|
|
309
|
+
const parsed = toParsedLanguage(match[1], match[2]);
|
|
310
|
+
const existing = found.get(parsed.name);
|
|
311
|
+
if (!existing || (parsed.levelRank ?? 0) > (existing.levelRank ?? 0)) {
|
|
312
|
+
found.set(parsed.name, parsed);
|
|
193
313
|
}
|
|
194
|
-
|
|
195
|
-
|
|
314
|
+
}
|
|
315
|
+
for (const match of text.matchAll(LEVEL_BEFORE_LANGUAGE_RE)) {
|
|
316
|
+
const parsed = toParsedLanguage(match[2], match[1]);
|
|
317
|
+
const existing = found.get(parsed.name);
|
|
318
|
+
if (!existing || (parsed.levelRank ?? 0) > (existing.levelRank ?? 0)) {
|
|
319
|
+
found.set(parsed.name, parsed);
|
|
196
320
|
}
|
|
197
321
|
}
|
|
198
|
-
return
|
|
322
|
+
return [...found.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
199
323
|
}
|
|
200
|
-
function
|
|
201
|
-
|
|
324
|
+
function diffLanguages(resumeLanguages, requiredLanguages) {
|
|
325
|
+
const byName = new Map(resumeLanguages.map((l) => [l.name, l]));
|
|
326
|
+
const matched = [];
|
|
327
|
+
const missing = [];
|
|
328
|
+
for (const required of requiredLanguages) {
|
|
329
|
+
const have = byName.get(required.name);
|
|
330
|
+
const requiredRank = required.levelRank ?? 0;
|
|
331
|
+
const haveRank = have?.levelRank ?? 0;
|
|
332
|
+
if (have && haveRank >= requiredRank) {
|
|
333
|
+
matched.push(required);
|
|
334
|
+
} else {
|
|
335
|
+
missing.push(required);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return { matched, missing };
|
|
202
339
|
}
|
|
203
340
|
|
|
204
341
|
// src/core/parser/jd.parser.ts
|
|
@@ -239,6 +376,18 @@ function extractMinExperience(text) {
|
|
|
239
376
|
}
|
|
240
377
|
return void 0;
|
|
241
378
|
}
|
|
379
|
+
var SURFACE_TOKEN_RE = /[a-z0-9][a-z0-9.#+\-/]*[a-z0-9#+]/gi;
|
|
380
|
+
function collectKeywordSurfaceForms(rawText, aliases) {
|
|
381
|
+
const matches = rawText.match(SURFACE_TOKEN_RE) ?? [];
|
|
382
|
+
const surfaceForms = {};
|
|
383
|
+
for (const match of matches) {
|
|
384
|
+
const canonical = normalizeSkill(match, aliases);
|
|
385
|
+
if (!(canonical in surfaceForms)) {
|
|
386
|
+
surfaceForms[canonical] = match;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return surfaceForms;
|
|
390
|
+
}
|
|
242
391
|
function extractEducationRequirements(text) {
|
|
243
392
|
const found = /* @__PURE__ */ new Set();
|
|
244
393
|
for (const [pattern, canonical] of DEGREE_VARIANTS) {
|
|
@@ -277,7 +426,11 @@ function parseJobDescription(jobDescription, config) {
|
|
|
277
426
|
roleKeywords,
|
|
278
427
|
keywords,
|
|
279
428
|
minExperienceYears: extractMinExperience(jobDescription),
|
|
280
|
-
educationRequirements: extractEducationRequirements(jobDescription)
|
|
429
|
+
educationRequirements: extractEducationRequirements(jobDescription),
|
|
430
|
+
keywordSurfaceForms: collectKeywordSurfaceForms(jobDescription, config.skillAliases),
|
|
431
|
+
// ponytail: any language mention in the JD is treated as a requirement — good enough until
|
|
432
|
+
// JDs that merely *reference* a language (not require it) show up as false positives.
|
|
433
|
+
requiredLanguages: parseLanguageMentions(jobDescription)
|
|
281
434
|
};
|
|
282
435
|
}
|
|
283
436
|
|
|
@@ -410,7 +563,7 @@ var SECTION_ALIASES = {
|
|
|
410
563
|
projects: ["projects", "portfolio"],
|
|
411
564
|
certifications: ["certifications", "licenses"]
|
|
412
565
|
};
|
|
413
|
-
var
|
|
566
|
+
var STRONG_VERBS = [
|
|
414
567
|
"led",
|
|
415
568
|
"managed",
|
|
416
569
|
"built",
|
|
@@ -431,6 +584,21 @@ var ACTION_VERBS = [
|
|
|
431
584
|
"reduced",
|
|
432
585
|
"increased"
|
|
433
586
|
];
|
|
587
|
+
var WEAK_VERBS = ["worked", "helped", "performed", "responsible", "assisted", "participated", "involved"];
|
|
588
|
+
var METRIC_RE = /\d|%|\$|\bk\+|\bm\+/i;
|
|
589
|
+
function classifyAchievement(line) {
|
|
590
|
+
const lower = line.toLowerCase();
|
|
591
|
+
const hasMetric = METRIC_RE.test(line);
|
|
592
|
+
const hasStrongVerb = STRONG_VERBS.some((verb) => new RegExp(`\\b${verb}\\b`).test(lower));
|
|
593
|
+
const hasWeakVerb = WEAK_VERBS.some((verb) => new RegExp(`\\b${verb}\\b`).test(lower));
|
|
594
|
+
if (hasStrongVerb && hasMetric) {
|
|
595
|
+
return { text: line, strength: "strong", reason: "strong verb + quantified impact" };
|
|
596
|
+
}
|
|
597
|
+
if (hasWeakVerb) {
|
|
598
|
+
return { text: line, strength: "weak", reason: "weak verb" };
|
|
599
|
+
}
|
|
600
|
+
return { text: line, strength: "weak", reason: "no quantified impact" };
|
|
601
|
+
}
|
|
434
602
|
function detectSection(line) {
|
|
435
603
|
const normalized = line.trim().toLowerCase();
|
|
436
604
|
for (const [section, aliases] of Object.entries(SECTION_ALIASES)) {
|
|
@@ -476,16 +644,20 @@ function parseSkills(sectionContent, aliases) {
|
|
|
476
644
|
}
|
|
477
645
|
function parseActionVerbs(text) {
|
|
478
646
|
const words = tokenize(text);
|
|
479
|
-
return
|
|
647
|
+
return {
|
|
648
|
+
strong: STRONG_VERBS.filter((verb) => words.includes(verb)),
|
|
649
|
+
weak: WEAK_VERBS.filter((verb) => words.includes(verb))
|
|
650
|
+
};
|
|
480
651
|
}
|
|
481
652
|
function parseExperience(sectionContent, referenceDate) {
|
|
482
653
|
if (!sectionContent) {
|
|
483
|
-
return { entries: [], rangesInMonths: [], jobTitles: [] };
|
|
654
|
+
return { entries: [], rangesInMonths: [], jobTitles: [], achievements: [] };
|
|
484
655
|
}
|
|
485
656
|
const lines = splitLines(sectionContent);
|
|
486
657
|
const entries = [];
|
|
487
658
|
const rangesInMonths = [];
|
|
488
659
|
const jobTitles = [];
|
|
660
|
+
const achievements = [];
|
|
489
661
|
for (const line of lines) {
|
|
490
662
|
const range = parseDateRange(line, referenceDate);
|
|
491
663
|
if (range) {
|
|
@@ -506,14 +678,16 @@ function parseExperience(sectionContent, referenceDate) {
|
|
|
506
678
|
jobTitles.push(title.toLowerCase());
|
|
507
679
|
const entry = { title, description: line };
|
|
508
680
|
entries.push(entry);
|
|
681
|
+
achievements.push(classifyAchievement(line));
|
|
509
682
|
continue;
|
|
510
683
|
}
|
|
511
684
|
if (entries.length > 0) {
|
|
512
685
|
const current = entries[entries.length - 1];
|
|
513
686
|
current.description = [current.description, line].filter(Boolean).join(" ").trim();
|
|
514
687
|
}
|
|
688
|
+
achievements.push(classifyAchievement(line));
|
|
515
689
|
}
|
|
516
|
-
return { entries, rangesInMonths, jobTitles: unique(jobTitles) };
|
|
690
|
+
return { entries, rangesInMonths, jobTitles: unique(jobTitles), achievements };
|
|
517
691
|
}
|
|
518
692
|
function parseEducation(sectionContent) {
|
|
519
693
|
if (!sectionContent) return [];
|
|
@@ -541,6 +715,16 @@ function parseResume(resumeText, config) {
|
|
|
541
715
|
}
|
|
542
716
|
const requiredSections = ["summary", "experience", "skills", "education"];
|
|
543
717
|
const warnings = [];
|
|
718
|
+
const lineCount = splitLines(resumeText).length;
|
|
719
|
+
if (resumeText.trim().length < 100) {
|
|
720
|
+
warnings.push(
|
|
721
|
+
"Almost no text was extracted \u2014 the resume may be a scanned/image PDF. Upload a text-based PDF or paste the text directly."
|
|
722
|
+
);
|
|
723
|
+
} else if (lineCount <= 2) {
|
|
724
|
+
warnings.push(
|
|
725
|
+
"Resume text has no line breaks \u2014 the PDF layout likely didn't export cleanly (common with multi-column designs). Export as a single-column PDF or paste plain text for accurate parsing."
|
|
726
|
+
);
|
|
727
|
+
}
|
|
544
728
|
for (const section of requiredSections) {
|
|
545
729
|
if (!detected.includes(section)) {
|
|
546
730
|
warnings.push(`${section} section not detected`);
|
|
@@ -553,11 +737,14 @@ function parseResume(resumeText, config) {
|
|
|
553
737
|
sectionContent: sections,
|
|
554
738
|
skills,
|
|
555
739
|
jobTitles: experienceData.jobTitles,
|
|
556
|
-
actionVerbs,
|
|
740
|
+
actionVerbs: actionVerbs.strong,
|
|
741
|
+
weakVerbs: actionVerbs.weak,
|
|
742
|
+
achievements: experienceData.achievements,
|
|
557
743
|
educationEntries,
|
|
558
744
|
experience: experienceData.entries,
|
|
559
745
|
totalExperienceYears,
|
|
560
746
|
keywords: collectKeywords(normalizedText),
|
|
747
|
+
languages: parseLanguageMentions(resumeText),
|
|
561
748
|
warnings
|
|
562
749
|
};
|
|
563
750
|
}
|
|
@@ -639,6 +826,14 @@ var REQUIRED_SKILL_WEIGHT = 0.7;
|
|
|
639
826
|
var OPTIONAL_SKILL_WEIGHT = 0.3;
|
|
640
827
|
var EXPERIENCE_YEARS_WEIGHT = 0.75;
|
|
641
828
|
var EXPERIENCE_ROLE_WEIGHT = 0.25;
|
|
829
|
+
var ALL_CATEGORIES = ["technical", "tool", "concept", "soft", "marketing", "domain"];
|
|
830
|
+
function emptyCategoryBuckets() {
|
|
831
|
+
const buckets = {};
|
|
832
|
+
for (const category of ALL_CATEGORIES) {
|
|
833
|
+
buckets[category] = { matched: [], missing: [] };
|
|
834
|
+
}
|
|
835
|
+
return buckets;
|
|
836
|
+
}
|
|
642
837
|
function scoreSkills(resume, job, config) {
|
|
643
838
|
const profileRequired = config.profile?.mandatorySkills ?? [];
|
|
644
839
|
const profileOptional = config.profile?.optionalSkills ?? [];
|
|
@@ -677,32 +872,74 @@ function scoreExperience(resume, job, config) {
|
|
|
677
872
|
const missingYears = Math.max(requiredYears - resume.totalExperienceYears, 0);
|
|
678
873
|
return { score, missingYears: Number(missingYears.toFixed(2)) };
|
|
679
874
|
}
|
|
875
|
+
function keywordWeightOf(keyword, requiredSet, preferredSet, jdFrequencies) {
|
|
876
|
+
const base = requiredSet.has(keyword) ? 3 : preferredSet.has(keyword) ? 2 : 1;
|
|
877
|
+
const freqBonus = Math.min((jdFrequencies[keyword] ?? 1) - 1, 3) * 0.25;
|
|
878
|
+
return base + freqBonus;
|
|
879
|
+
}
|
|
680
880
|
function scoreKeywords(resume, job, config) {
|
|
681
881
|
const jobKeywordSet = new Set(
|
|
682
882
|
job.keywords.map((k) => normalizeSkill(k, config.skillAliases))
|
|
683
883
|
);
|
|
684
884
|
if (jobKeywordSet.size === 0) {
|
|
685
|
-
return {
|
|
885
|
+
return {
|
|
886
|
+
score: 100,
|
|
887
|
+
matchedKeywords: [],
|
|
888
|
+
missingKeywords: [],
|
|
889
|
+
overusedKeywords: [],
|
|
890
|
+
keywordsByCategory: emptyCategoryBuckets(),
|
|
891
|
+
keywordWeights: []
|
|
892
|
+
};
|
|
686
893
|
}
|
|
687
894
|
const resumeTokens = tokenize(resume.normalizedText).map(
|
|
688
895
|
(t) => normalizeSkill(t, config.skillAliases)
|
|
689
896
|
);
|
|
690
897
|
const resumeTokenSet = new Set(resumeTokens);
|
|
898
|
+
const resumeFrequencies = countFrequencies(resumeTokens);
|
|
899
|
+
const requiredSet = new Set(job.requiredSkills);
|
|
900
|
+
const preferredSet = new Set(job.preferredSkills);
|
|
901
|
+
const jdFrequencies = countFrequencies(
|
|
902
|
+
tokenize(job.normalizedText).map((t) => normalizeSkill(t, config.skillAliases))
|
|
903
|
+
);
|
|
904
|
+
const weightOf = (keyword) => keywordWeightOf(keyword, requiredSet, preferredSet, jdFrequencies);
|
|
691
905
|
const matchedKeywords = [...jobKeywordSet].filter((keyword) => resumeTokenSet.has(keyword));
|
|
692
906
|
const missingKeywords = [...jobKeywordSet].filter((keyword) => !resumeTokenSet.has(keyword));
|
|
693
|
-
const
|
|
694
|
-
const
|
|
695
|
-
const
|
|
907
|
+
const totalWeight = [...jobKeywordSet].reduce((sum, keyword) => sum + weightOf(keyword), 0);
|
|
908
|
+
const matchedWeight = matchedKeywords.reduce((sum, keyword) => sum + weightOf(keyword), 0);
|
|
909
|
+
const score = clamp(matchedWeight / totalWeight * 100, 0, 100);
|
|
696
910
|
const totalTokens = resumeTokens.length || 1;
|
|
697
911
|
const overusedKeywords = matchedKeywords.filter((keyword) => {
|
|
698
|
-
const density = (
|
|
912
|
+
const density = (resumeFrequencies[keyword] ?? 0) / totalTokens;
|
|
699
913
|
return density > config.keywordDensity.max;
|
|
700
914
|
});
|
|
915
|
+
const keywordsByCategory = emptyCategoryBuckets();
|
|
916
|
+
for (const keyword of matchedKeywords) {
|
|
917
|
+
keywordsByCategory[config.categoryIndex.get(keyword) ?? "technical"].matched.push(keyword);
|
|
918
|
+
}
|
|
919
|
+
for (const keyword of missingKeywords) {
|
|
920
|
+
keywordsByCategory[config.categoryIndex.get(keyword) ?? "technical"].missing.push(keyword);
|
|
921
|
+
}
|
|
922
|
+
for (const bucket of Object.values(keywordsByCategory)) {
|
|
923
|
+
bucket.matched.sort();
|
|
924
|
+
bucket.missing.sort();
|
|
925
|
+
}
|
|
926
|
+
const keywordWeights = [...jobKeywordSet].map((term) => {
|
|
927
|
+
const weight = Number(weightOf(term).toFixed(2));
|
|
928
|
+
return {
|
|
929
|
+
term,
|
|
930
|
+
category: config.categoryIndex.get(term) ?? "technical",
|
|
931
|
+
jdWeight: weight,
|
|
932
|
+
resumeWeight: resumeFrequencies[term] ?? 0,
|
|
933
|
+
importance: weight
|
|
934
|
+
};
|
|
935
|
+
}).sort((a, b) => a.term.localeCompare(b.term));
|
|
701
936
|
return {
|
|
702
937
|
score,
|
|
703
938
|
matchedKeywords: unique(matchedKeywords).sort(),
|
|
704
939
|
missingKeywords: unique(missingKeywords).sort(),
|
|
705
|
-
overusedKeywords: unique(overusedKeywords).sort()
|
|
940
|
+
overusedKeywords: unique(overusedKeywords).sort(),
|
|
941
|
+
keywordsByCategory,
|
|
942
|
+
keywordWeights
|
|
706
943
|
};
|
|
707
944
|
}
|
|
708
945
|
function scoreEducation(resume, job) {
|
|
@@ -731,6 +968,14 @@ function calculateScore(resume, job, config) {
|
|
|
731
968
|
education: educationScore
|
|
732
969
|
};
|
|
733
970
|
const weightedScore = breakdown.skills * config.weights.skills + breakdown.experience * config.weights.experience + breakdown.keywords * config.weights.keywords + breakdown.education * config.weights.education;
|
|
971
|
+
const achievementStrength = {
|
|
972
|
+
strong: resume.achievements.filter((a) => a.strength === "strong").length,
|
|
973
|
+
weak: resume.achievements.filter((a) => a.strength === "weak").length
|
|
974
|
+
};
|
|
975
|
+
const { matched: matchedLanguages, missing: missingLanguages } = diffLanguages(
|
|
976
|
+
resume.languages,
|
|
977
|
+
job.requiredLanguages
|
|
978
|
+
);
|
|
734
979
|
return {
|
|
735
980
|
score: clamp(Number(weightedScore.toFixed(2)), 0, 100),
|
|
736
981
|
breakdown,
|
|
@@ -739,6 +984,11 @@ function calculateScore(resume, job, config) {
|
|
|
739
984
|
matchedKeywords: keywordResult.matchedKeywords,
|
|
740
985
|
missingKeywords: keywordResult.missingKeywords,
|
|
741
986
|
overusedKeywords: keywordResult.overusedKeywords,
|
|
987
|
+
keywordsByCategory: keywordResult.keywordsByCategory,
|
|
988
|
+
keywordWeights: keywordResult.keywordWeights,
|
|
989
|
+
achievementStrength,
|
|
990
|
+
matchedLanguages,
|
|
991
|
+
missingLanguages,
|
|
742
992
|
suggestions: [],
|
|
743
993
|
warnings: [],
|
|
744
994
|
// detectedSections / parsedExperienceYears / experienceGap / experienceEntries: filled by index.ts
|
|
@@ -752,49 +1002,195 @@ function calculateScore(resume, job, config) {
|
|
|
752
1002
|
}
|
|
753
1003
|
|
|
754
1004
|
// src/profiles/index.ts
|
|
755
|
-
var
|
|
1005
|
+
var defaultKeywordRegistry = [
|
|
1006
|
+
// languages / frameworks
|
|
756
1007
|
// ponytail: "node" split from javascript — Node.js runtime !== JS language
|
|
757
|
-
javascript: ["js"],
|
|
758
|
-
node: ["node.js", "nodejs"],
|
|
759
|
-
typescript: ["ts"],
|
|
760
|
-
react: ["reactjs", "react.js"],
|
|
761
|
-
"
|
|
762
|
-
"
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
"
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
}
|
|
1008
|
+
{ canonical: "javascript", aliases: ["js"], category: "technical" },
|
|
1009
|
+
{ canonical: "node", aliases: ["node.js", "nodejs"], category: "technical" },
|
|
1010
|
+
{ canonical: "typescript", aliases: ["ts"], category: "technical" },
|
|
1011
|
+
{ canonical: "react", aliases: ["reactjs", "react.js"], category: "technical" },
|
|
1012
|
+
{ canonical: "angular", aliases: ["angularjs"], category: "technical" },
|
|
1013
|
+
{ canonical: "vue", aliases: ["vue.js", "vuejs"], category: "technical" },
|
|
1014
|
+
{ canonical: "svelte", aliases: [], category: "technical" },
|
|
1015
|
+
{ canonical: "next.js", aliases: ["nextjs"], category: "technical" },
|
|
1016
|
+
{ canonical: "c++", aliases: ["cpp"], category: "technical" },
|
|
1017
|
+
{ canonical: "c#", aliases: ["csharp", ".net"], category: "technical" },
|
|
1018
|
+
{ canonical: "java", aliases: [], category: "technical" },
|
|
1019
|
+
{ canonical: "python", aliases: ["py"], category: "technical" },
|
|
1020
|
+
{ canonical: "go", aliases: ["golang"], category: "technical" },
|
|
1021
|
+
{ canonical: "rust", aliases: [], category: "technical" },
|
|
1022
|
+
{ canonical: "ruby", aliases: ["ruby on rails", "rails"], category: "technical" },
|
|
1023
|
+
{ canonical: "php", aliases: [], category: "technical" },
|
|
1024
|
+
{ canonical: "swift", aliases: [], category: "technical" },
|
|
1025
|
+
{ canonical: "kotlin", aliases: [], category: "technical" },
|
|
1026
|
+
{ canonical: "scala", aliases: [], category: "technical" },
|
|
1027
|
+
{ canonical: "html", aliases: ["html5"], category: "technical" },
|
|
1028
|
+
{ canonical: "css", aliases: ["css3"], category: "technical" },
|
|
1029
|
+
{ canonical: "ios development", aliases: ["ios"], category: "technical" },
|
|
1030
|
+
{ canonical: "android development", aliases: ["android"], category: "technical" },
|
|
1031
|
+
{ canonical: "react native", aliases: [], category: "technical" },
|
|
1032
|
+
{ canonical: "flutter", aliases: [], category: "technical" },
|
|
1033
|
+
{ canonical: "machine learning", aliases: ["ml"], category: "technical" },
|
|
1034
|
+
{ canonical: "deep learning", aliases: [], category: "technical" },
|
|
1035
|
+
{ canonical: "natural language processing", aliases: ["nlp"], category: "technical" },
|
|
1036
|
+
// tools / platforms / infra
|
|
1037
|
+
{ canonical: "sql", aliases: ["postgres", "mysql", "sqlite"], category: "tool" },
|
|
1038
|
+
{ canonical: "graphql", aliases: ["gql"], category: "tool" },
|
|
1039
|
+
{ canonical: "aws", aliases: ["amazon web services"], category: "tool" },
|
|
1040
|
+
{ canonical: "azure", aliases: ["microsoft azure"], category: "tool" },
|
|
1041
|
+
{ canonical: "gcp", aliases: ["google cloud", "google cloud platform"], category: "tool" },
|
|
1042
|
+
{ canonical: "docker", aliases: ["containers"], category: "tool" },
|
|
1043
|
+
{ canonical: "kubernetes", aliases: ["k8s"], category: "tool" },
|
|
1044
|
+
{ canonical: "terraform", aliases: [], category: "tool" },
|
|
1045
|
+
{ canonical: "ansible", aliases: [], category: "tool" },
|
|
1046
|
+
{ canonical: "jenkins", aliases: [], category: "tool" },
|
|
1047
|
+
{ canonical: "git", aliases: ["github", "gitlab"], category: "tool" },
|
|
1048
|
+
{ canonical: "jira", aliases: [], category: "tool" },
|
|
1049
|
+
{ canonical: "confluence", aliases: [], category: "tool" },
|
|
1050
|
+
{ canonical: "pytorch", aliases: ["torch"], category: "tool" },
|
|
1051
|
+
{ canonical: "tensorflow", aliases: ["tf"], category: "tool" },
|
|
1052
|
+
{ canonical: "scikit-learn", aliases: ["sklearn"], category: "tool" },
|
|
1053
|
+
{ canonical: "pandas", aliases: [], category: "tool" },
|
|
1054
|
+
{ canonical: "numpy", aliases: [], category: "tool" },
|
|
1055
|
+
{ canonical: "fastapi", aliases: [], category: "tool" },
|
|
1056
|
+
{ canonical: "flask", aliases: [], category: "tool" },
|
|
1057
|
+
{ canonical: "django", aliases: [], category: "tool" },
|
|
1058
|
+
{ canonical: "kafka", aliases: [], category: "tool" },
|
|
1059
|
+
{ canonical: "redis", aliases: [], category: "tool" },
|
|
1060
|
+
{ canonical: "elasticsearch", aliases: ["elastic"], category: "tool" },
|
|
1061
|
+
{ canonical: "spark", aliases: ["apache spark"], category: "tool" },
|
|
1062
|
+
{ canonical: "tableau", aliases: [], category: "tool" },
|
|
1063
|
+
{ canonical: "power bi", aliases: ["powerbi"], category: "tool" },
|
|
1064
|
+
{ canonical: "excel", aliases: ["microsoft excel", "ms excel"], category: "tool" },
|
|
1065
|
+
{ canonical: "salesforce", aliases: [], category: "tool" },
|
|
1066
|
+
{ canonical: "hubspot", aliases: [], category: "tool" },
|
|
1067
|
+
{ canonical: "sap", aliases: [], category: "tool" },
|
|
1068
|
+
{ canonical: "quickbooks", aliases: [], category: "tool" },
|
|
1069
|
+
{ canonical: "workday", aliases: [], category: "tool" },
|
|
1070
|
+
{ canonical: "zendesk", aliases: [], category: "tool" },
|
|
1071
|
+
{ canonical: "servicenow", aliases: [], category: "tool" },
|
|
1072
|
+
{ canonical: "figma", aliases: [], category: "tool" },
|
|
1073
|
+
{ canonical: "photoshop", aliases: ["adobe photoshop"], category: "tool" },
|
|
1074
|
+
{ canonical: "illustrator", aliases: ["adobe illustrator"], category: "tool" },
|
|
1075
|
+
{ canonical: "autocad", aliases: [], category: "tool" },
|
|
1076
|
+
// engineering concepts
|
|
1077
|
+
{ canonical: "accessibility", aliases: ["a11y"], category: "concept" },
|
|
1078
|
+
{ canonical: "frontend", aliases: ["front-end"], category: "concept" },
|
|
1079
|
+
{ canonical: "backend", aliases: ["back-end"], category: "concept" },
|
|
1080
|
+
{ canonical: "security", aliases: ["cybersecurity"], category: "concept" },
|
|
1081
|
+
{ canonical: "testing", aliases: ["unittest", "pytest"], category: "concept" },
|
|
1082
|
+
{ canonical: "microservices", aliases: [], category: "concept" },
|
|
1083
|
+
{ canonical: "agile", aliases: ["scrum"], category: "concept" },
|
|
1084
|
+
{ canonical: "kanban", aliases: [], category: "concept" },
|
|
1085
|
+
{ canonical: "blockchain", aliases: [], category: "concept" },
|
|
1086
|
+
{ canonical: "devops", aliases: [], category: "concept" },
|
|
1087
|
+
{ canonical: "ci/cd", aliases: ["continuous integration", "continuous deployment"], category: "concept" },
|
|
1088
|
+
{ canonical: "rest api", aliases: ["restful api", "rest apis"], category: "concept" },
|
|
1089
|
+
{ canonical: "design patterns", aliases: [], category: "concept" },
|
|
1090
|
+
{ canonical: "data structures", aliases: [], category: "concept" },
|
|
1091
|
+
{ canonical: "algorithms", aliases: [], category: "concept" },
|
|
1092
|
+
{ canonical: "cloud computing", aliases: [], category: "concept" },
|
|
1093
|
+
{ canonical: "system design", aliases: [], category: "concept" },
|
|
1094
|
+
{ canonical: "tdd", aliases: ["test driven development", "test-driven development"], category: "concept" },
|
|
1095
|
+
{ canonical: "ux design", aliases: ["user experience"], category: "concept" },
|
|
1096
|
+
{ canonical: "ui design", aliases: ["user interface design"], category: "concept" },
|
|
1097
|
+
{ canonical: "project management", aliases: [], category: "concept" },
|
|
1098
|
+
{ canonical: "change management", aliases: [], category: "concept" },
|
|
1099
|
+
{ canonical: "risk management", aliases: [], category: "concept" },
|
|
1100
|
+
{ canonical: "quality assurance", aliases: ["qa"], category: "concept" },
|
|
1101
|
+
// product / data domain
|
|
1102
|
+
{ canonical: "roadmap", aliases: [], category: "domain" },
|
|
1103
|
+
{ canonical: "stakeholder management", aliases: [], category: "domain" },
|
|
1104
|
+
{ canonical: "prioritization", aliases: [], category: "domain" },
|
|
1105
|
+
{ canonical: "a/b testing", aliases: ["ab testing"], category: "domain" },
|
|
1106
|
+
{ canonical: "analytics", aliases: [], category: "domain" },
|
|
1107
|
+
{ canonical: "statistics", aliases: ["stats"], category: "domain" },
|
|
1108
|
+
{ canonical: "data visualization", aliases: [], category: "domain" },
|
|
1109
|
+
// finance / accounting domain
|
|
1110
|
+
{ canonical: "financial analysis", aliases: [], category: "domain" },
|
|
1111
|
+
{ canonical: "budgeting", aliases: [], category: "domain" },
|
|
1112
|
+
{ canonical: "forecasting", aliases: [], category: "domain" },
|
|
1113
|
+
{ canonical: "bookkeeping", aliases: [], category: "domain" },
|
|
1114
|
+
{ canonical: "accounts payable", aliases: ["ap"], category: "domain" },
|
|
1115
|
+
{ canonical: "accounts receivable", aliases: ["ar"], category: "domain" },
|
|
1116
|
+
{ canonical: "payroll", aliases: [], category: "domain" },
|
|
1117
|
+
{ canonical: "auditing", aliases: ["audit"], category: "domain" },
|
|
1118
|
+
{ canonical: "tax preparation", aliases: [], category: "domain" },
|
|
1119
|
+
{ canonical: "gaap", aliases: [], category: "domain" },
|
|
1120
|
+
// sales / account management domain
|
|
1121
|
+
{ canonical: "lead generation", aliases: [], category: "domain" },
|
|
1122
|
+
{ canonical: "account management", aliases: [], category: "domain" },
|
|
1123
|
+
{ canonical: "crm", aliases: ["customer relationship management"], category: "domain" },
|
|
1124
|
+
{ canonical: "sales pipeline", aliases: [], category: "domain" },
|
|
1125
|
+
{ canonical: "cold calling", aliases: [], category: "domain" },
|
|
1126
|
+
{ canonical: "upselling", aliases: ["cross-selling"], category: "domain" },
|
|
1127
|
+
{ canonical: "customer retention", aliases: [], category: "domain" },
|
|
1128
|
+
// human resources domain
|
|
1129
|
+
{ canonical: "recruiting", aliases: ["talent acquisition"], category: "domain" },
|
|
1130
|
+
{ canonical: "onboarding", aliases: [], category: "domain" },
|
|
1131
|
+
{ canonical: "employee relations", aliases: [], category: "domain" },
|
|
1132
|
+
{ canonical: "benefits administration", aliases: [], category: "domain" },
|
|
1133
|
+
{ canonical: "performance management", aliases: [], category: "domain" },
|
|
1134
|
+
// healthcare domain
|
|
1135
|
+
{ canonical: "patient care", aliases: [], category: "domain" },
|
|
1136
|
+
{ canonical: "clinical documentation", aliases: [], category: "domain" },
|
|
1137
|
+
{ canonical: "hipaa", aliases: [], category: "domain" },
|
|
1138
|
+
{ canonical: "electronic health records", aliases: ["ehr", "emr"], category: "domain" },
|
|
1139
|
+
{ canonical: "medical billing", aliases: [], category: "domain" },
|
|
1140
|
+
// legal domain
|
|
1141
|
+
{ canonical: "contract review", aliases: [], category: "domain" },
|
|
1142
|
+
{ canonical: "legal research", aliases: [], category: "domain" },
|
|
1143
|
+
{ canonical: "litigation", aliases: [], category: "domain" },
|
|
1144
|
+
{ canonical: "regulatory compliance", aliases: ["compliance"], category: "domain" },
|
|
1145
|
+
{ canonical: "due diligence", aliases: [], category: "domain" },
|
|
1146
|
+
// education domain
|
|
1147
|
+
{ canonical: "curriculum development", aliases: [], category: "domain" },
|
|
1148
|
+
{ canonical: "lesson planning", aliases: [], category: "domain" },
|
|
1149
|
+
{ canonical: "classroom management", aliases: [], category: "domain" },
|
|
1150
|
+
{ canonical: "instructional design", aliases: [], category: "domain" },
|
|
1151
|
+
// operations / supply chain domain
|
|
1152
|
+
{ canonical: "supply chain management", aliases: ["supply chain"], category: "domain" },
|
|
1153
|
+
{ canonical: "inventory management", aliases: [], category: "domain" },
|
|
1154
|
+
{ canonical: "procurement", aliases: [], category: "domain" },
|
|
1155
|
+
{ canonical: "vendor management", aliases: [], category: "domain" },
|
|
1156
|
+
{ canonical: "logistics", aliases: [], category: "domain" },
|
|
1157
|
+
// customer service domain
|
|
1158
|
+
{ canonical: "customer support", aliases: ["customer service"], category: "domain" },
|
|
1159
|
+
{ canonical: "technical support", aliases: [], category: "domain" },
|
|
1160
|
+
{ canonical: "conflict resolution", aliases: [], category: "domain" },
|
|
1161
|
+
// soft skills
|
|
1162
|
+
{ canonical: "communication", aliases: [], category: "soft" },
|
|
1163
|
+
{ canonical: "leadership", aliases: [], category: "soft" },
|
|
1164
|
+
{ canonical: "teamwork", aliases: ["collaboration"], category: "soft" },
|
|
1165
|
+
{ canonical: "problem solving", aliases: ["problem-solving"], category: "soft" },
|
|
1166
|
+
{ canonical: "adaptability", aliases: ["flexibility"], category: "soft" },
|
|
1167
|
+
{ canonical: "time management", aliases: [], category: "soft" },
|
|
1168
|
+
{ canonical: "critical thinking", aliases: [], category: "soft" },
|
|
1169
|
+
{ canonical: "creativity", aliases: [], category: "soft" },
|
|
1170
|
+
{ canonical: "attention to detail", aliases: [], category: "soft" },
|
|
1171
|
+
{ canonical: "decision making", aliases: ["decision-making"], category: "soft" },
|
|
1172
|
+
{ canonical: "emotional intelligence", aliases: [], category: "soft" },
|
|
1173
|
+
{ canonical: "negotiation", aliases: [], category: "soft" },
|
|
1174
|
+
{ canonical: "organization", aliases: ["organizational skills"], category: "soft" },
|
|
1175
|
+
{ canonical: "public speaking", aliases: ["presentation skills"], category: "soft" },
|
|
1176
|
+
{ canonical: "mentoring", aliases: ["coaching"], category: "soft" },
|
|
1177
|
+
{ canonical: "interpersonal skills", aliases: [], category: "soft" },
|
|
1178
|
+
{ canonical: "work ethic", aliases: [], category: "soft" },
|
|
1179
|
+
// marketing
|
|
1180
|
+
{ canonical: "seo", aliases: ["search engine optimization"], category: "marketing" },
|
|
1181
|
+
{ canonical: "branding", aliases: ["brand strategy"], category: "marketing" },
|
|
1182
|
+
{ canonical: "campaign management", aliases: [], category: "marketing" },
|
|
1183
|
+
{ canonical: "content marketing", aliases: [], category: "marketing" },
|
|
1184
|
+
{ canonical: "social media marketing", aliases: ["social media"], category: "marketing" },
|
|
1185
|
+
{ canonical: "email marketing", aliases: [], category: "marketing" },
|
|
1186
|
+
{ canonical: "digital marketing", aliases: [], category: "marketing" },
|
|
1187
|
+
{ canonical: "copywriting", aliases: [], category: "marketing" },
|
|
1188
|
+
{ canonical: "market research", aliases: [], category: "marketing" },
|
|
1189
|
+
{ canonical: "ppc", aliases: ["pay-per-click", "google ads"], category: "marketing" },
|
|
1190
|
+
{ canonical: "conversion rate optimization", aliases: ["cro"], category: "marketing" },
|
|
1191
|
+
{ canonical: "public relations", aliases: ["pr"], category: "marketing" }
|
|
1192
|
+
];
|
|
1193
|
+
var defaultSkillAliases = deriveSkillAliases(defaultKeywordRegistry);
|
|
798
1194
|
var softwareEngineerProfile = {
|
|
799
1195
|
name: "software-engineer",
|
|
800
1196
|
mandatorySkills: ["javascript", "typescript", "react", "node"],
|
|
@@ -864,9 +1260,12 @@ function resolveConfig(config = {}) {
|
|
|
864
1260
|
keywords: config.weights?.keywords ?? DEFAULT_WEIGHTS.keywords,
|
|
865
1261
|
education: config.weights?.education ?? DEFAULT_WEIGHTS.education
|
|
866
1262
|
};
|
|
1263
|
+
const keywordRegistry = mergeKeywordRegistries(defaultKeywordRegistry, config.keywordRegistry ?? []);
|
|
867
1264
|
const resolved = {
|
|
868
1265
|
weights: normalizeWeights(weights),
|
|
869
|
-
skillAliases: { ...
|
|
1266
|
+
skillAliases: { ...deriveSkillAliases(keywordRegistry), ...config.skillAliases ?? {} },
|
|
1267
|
+
keywordRegistry,
|
|
1268
|
+
categoryIndex: buildCategoryIndex(keywordRegistry),
|
|
870
1269
|
profile: config.profile ?? softwareEngineerProfile,
|
|
871
1270
|
rules: config.rules ?? [],
|
|
872
1271
|
keywordDensity: config.keywordDensity ?? DEFAULT_KEYWORD_DENSITY,
|
|
@@ -886,6 +1285,18 @@ function formatList(values, max = 6) {
|
|
|
886
1285
|
const trimmed = uniqueValues.slice(0, max);
|
|
887
1286
|
return trimmed.join(", ") + (uniqueValues.length > max ? "..." : "");
|
|
888
1287
|
}
|
|
1288
|
+
function buildAliasReplacementSuggestions(resume, job, config) {
|
|
1289
|
+
const jobKeywordSet = new Set(job.keywords.map((k) => normalizeSkill(k, config.skillAliases)));
|
|
1290
|
+
const replacements = [];
|
|
1291
|
+
for (const token of unique(tokenize(resume.normalizedText))) {
|
|
1292
|
+
const canonical = normalizeSkill(token, config.skillAliases);
|
|
1293
|
+
const jdSurface = job.keywordSurfaceForms[canonical];
|
|
1294
|
+
if (jdSurface && jobKeywordSet.has(canonical) && jdSurface.toLowerCase() !== token.toLowerCase()) {
|
|
1295
|
+
replacements.push(`Replace "${token}" with "${jdSurface}" to match the job description's wording.`);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
return unique(replacements).slice(0, 5);
|
|
1299
|
+
}
|
|
889
1300
|
var SuggestionEngine = class {
|
|
890
1301
|
generate(input) {
|
|
891
1302
|
const suggestions = [];
|
|
@@ -900,6 +1311,7 @@ var SuggestionEngine = class {
|
|
|
900
1311
|
`Incorporate job-specific keywords: ${formatList(input.score.missingKeywords)}`
|
|
901
1312
|
);
|
|
902
1313
|
}
|
|
1314
|
+
suggestions.push(...buildAliasReplacementSuggestions(input.resume, input.job, input.config));
|
|
903
1315
|
if (input.score.overusedKeywords.length > 0) {
|
|
904
1316
|
suggestions.push(
|
|
905
1317
|
`Avoid keyword stuffing for: ${formatList(input.score.overusedKeywords)}`
|
|
@@ -920,6 +1332,26 @@ var SuggestionEngine = class {
|
|
|
920
1332
|
"Strengthen bullet points with impact verbs (led, built, improved, delivered)."
|
|
921
1333
|
);
|
|
922
1334
|
}
|
|
1335
|
+
if (input.resume.weakVerbs.length > 0) {
|
|
1336
|
+
suggestions.push(
|
|
1337
|
+
`Replace weak verbs (${formatList(input.resume.weakVerbs)}) with stronger ones (e.g. led, built, optimized).`
|
|
1338
|
+
);
|
|
1339
|
+
}
|
|
1340
|
+
if (input.score.missingLanguages.length > 0) {
|
|
1341
|
+
const formatted = input.score.missingLanguages.map((l) => l.level ? `${l.name} (${l.level})` : l.name).join(", ");
|
|
1342
|
+
suggestions.push(`Mention your proficiency in: ${formatted}`);
|
|
1343
|
+
}
|
|
1344
|
+
const weakAchievement = input.resume.achievements.find((a) => a.strength === "weak");
|
|
1345
|
+
if (weakAchievement) {
|
|
1346
|
+
suggestions.push(
|
|
1347
|
+
`Strengthen "${weakAchievement.text}" \u2014 add scope/metrics, e.g. "Built and maintained scalable services handling 500k+ requests/day."`
|
|
1348
|
+
);
|
|
1349
|
+
}
|
|
1350
|
+
if (input.resume.detectedSections.length < 2 && input.resume.raw.trim().length > 300) {
|
|
1351
|
+
suggestions.push(
|
|
1352
|
+
"Your resume may use a multi-column layout. Export as a single-column PDF or paste plain text \u2014 most ATS systems and this parser work best with a linear layout."
|
|
1353
|
+
);
|
|
1354
|
+
}
|
|
923
1355
|
return { suggestions, warnings };
|
|
924
1356
|
}
|
|
925
1357
|
};
|
|
@@ -1557,7 +1989,8 @@ function analyzeResume(input) {
|
|
|
1557
1989
|
resume: parsedResume,
|
|
1558
1990
|
job: parsedJob,
|
|
1559
1991
|
score: scoring,
|
|
1560
|
-
ruleWarnings: ruleResult.warnings
|
|
1992
|
+
ruleWarnings: ruleResult.warnings,
|
|
1993
|
+
config: resolvedConfig
|
|
1561
1994
|
});
|
|
1562
1995
|
let suggestions = suggestionResult.suggestions;
|
|
1563
1996
|
const llmWarnings = [];
|
|
@@ -1577,6 +2010,11 @@ function analyzeResume(input) {
|
|
|
1577
2010
|
matchedKeywords: scoring.matchedKeywords,
|
|
1578
2011
|
missingKeywords: scoring.missingKeywords,
|
|
1579
2012
|
overusedKeywords: scoring.overusedKeywords,
|
|
2013
|
+
keywordsByCategory: scoring.keywordsByCategory,
|
|
2014
|
+
keywordWeights: scoring.keywordWeights,
|
|
2015
|
+
achievementStrength: scoring.achievementStrength,
|
|
2016
|
+
matchedLanguages: scoring.matchedLanguages,
|
|
2017
|
+
missingLanguages: scoring.missingLanguages,
|
|
1580
2018
|
experienceGap: scoring.experienceGap,
|
|
1581
2019
|
detectedSections: parsedResume.detectedSections,
|
|
1582
2020
|
parsedExperienceYears: parsedResume.totalExperienceYears,
|
|
@@ -1619,7 +2057,8 @@ async function analyzeResumeAsync(input) {
|
|
|
1619
2057
|
resume: parsedResume,
|
|
1620
2058
|
job: parsedJob,
|
|
1621
2059
|
score: scoring,
|
|
1622
|
-
ruleWarnings: ruleResult.warnings
|
|
2060
|
+
ruleWarnings: ruleResult.warnings,
|
|
2061
|
+
config: resolvedConfig
|
|
1623
2062
|
});
|
|
1624
2063
|
let suggestions = suggestionResult.suggestions;
|
|
1625
2064
|
const llmWarnings = [];
|
|
@@ -1642,6 +2081,11 @@ async function analyzeResumeAsync(input) {
|
|
|
1642
2081
|
matchedKeywords: scoring.matchedKeywords,
|
|
1643
2082
|
missingKeywords: scoring.missingKeywords,
|
|
1644
2083
|
overusedKeywords: scoring.overusedKeywords,
|
|
2084
|
+
keywordsByCategory: scoring.keywordsByCategory,
|
|
2085
|
+
keywordWeights: scoring.keywordWeights,
|
|
2086
|
+
achievementStrength: scoring.achievementStrength,
|
|
2087
|
+
matchedLanguages: scoring.matchedLanguages,
|
|
2088
|
+
missingLanguages: scoring.missingLanguages,
|
|
1645
2089
|
experienceGap: scoring.experienceGap,
|
|
1646
2090
|
detectedSections: parsedResume.detectedSections,
|
|
1647
2091
|
parsedExperienceYears: parsedResume.totalExperienceYears,
|
|
@@ -1697,10 +2141,11 @@ exports.adaptSuggestionEnhancementResponse = adaptSuggestionEnhancementResponse;
|
|
|
1697
2141
|
exports.analyzeResume = analyzeResume;
|
|
1698
2142
|
exports.analyzeResumeAsync = analyzeResumeAsync;
|
|
1699
2143
|
exports.createPrompt = createPrompt;
|
|
2144
|
+
exports.defaultKeywordRegistry = defaultKeywordRegistry;
|
|
1700
2145
|
exports.defaultProfiles = defaultProfiles;
|
|
1701
2146
|
exports.defaultSkillAliases = defaultSkillAliases;
|
|
1702
2147
|
exports.safeExtractArray = safeExtractArray;
|
|
1703
2148
|
exports.safeExtractNumber = safeExtractNumber;
|
|
1704
2149
|
exports.safeExtractString = safeExtractString;
|
|
1705
|
-
//# sourceMappingURL=index.
|
|
1706
|
-
//# sourceMappingURL=index.
|
|
2150
|
+
//# sourceMappingURL=index.cjs.map
|
|
2151
|
+
//# sourceMappingURL=index.cjs.map
|