resuml 1.10.0 → 1.12.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/dist/index.cjs +407 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -0
- package/dist/mcp/server.cjs +407 -11
- package/dist/mcp/server.cjs.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -481,6 +481,7 @@ var init_en = __esm({
|
|
|
481
481
|
],
|
|
482
482
|
pronouns: ["i", "me", "my", "mine", "myself", "we", "our", "ours"],
|
|
483
483
|
stopWords: [
|
|
484
|
+
// Articles & determiners
|
|
484
485
|
"a",
|
|
485
486
|
"an",
|
|
486
487
|
"the",
|
|
@@ -551,7 +552,217 @@ var init_en = __esm({
|
|
|
551
552
|
"such",
|
|
552
553
|
"than",
|
|
553
554
|
"too",
|
|
554
|
-
"very"
|
|
555
|
+
"very",
|
|
556
|
+
// Pronouns & possessives (also checked by pronoun check, but filter from JD keywords)
|
|
557
|
+
"you",
|
|
558
|
+
"your",
|
|
559
|
+
"yours",
|
|
560
|
+
"yourself",
|
|
561
|
+
"we",
|
|
562
|
+
"our",
|
|
563
|
+
"ours",
|
|
564
|
+
"ourselves",
|
|
565
|
+
"they",
|
|
566
|
+
"them",
|
|
567
|
+
"their",
|
|
568
|
+
"theirs",
|
|
569
|
+
"he",
|
|
570
|
+
"she",
|
|
571
|
+
"his",
|
|
572
|
+
"her",
|
|
573
|
+
"hers",
|
|
574
|
+
"who",
|
|
575
|
+
"whom",
|
|
576
|
+
"whose",
|
|
577
|
+
"which",
|
|
578
|
+
"what",
|
|
579
|
+
"where",
|
|
580
|
+
"when",
|
|
581
|
+
"how",
|
|
582
|
+
"why",
|
|
583
|
+
// Common JD filler words (not meaningful for skill matching)
|
|
584
|
+
"able",
|
|
585
|
+
"also",
|
|
586
|
+
"across",
|
|
587
|
+
"already",
|
|
588
|
+
"always",
|
|
589
|
+
"among",
|
|
590
|
+
"any",
|
|
591
|
+
"apply",
|
|
592
|
+
"become",
|
|
593
|
+
"believe",
|
|
594
|
+
"best",
|
|
595
|
+
"bring",
|
|
596
|
+
"change",
|
|
597
|
+
"come",
|
|
598
|
+
"committed",
|
|
599
|
+
"company",
|
|
600
|
+
"comfortable",
|
|
601
|
+
"critical",
|
|
602
|
+
"current",
|
|
603
|
+
"day",
|
|
604
|
+
"desired",
|
|
605
|
+
"either",
|
|
606
|
+
"end",
|
|
607
|
+
"ensure",
|
|
608
|
+
"environment",
|
|
609
|
+
"equal",
|
|
610
|
+
"even",
|
|
611
|
+
"excellent",
|
|
612
|
+
"exciting",
|
|
613
|
+
"exceptional",
|
|
614
|
+
"expected",
|
|
615
|
+
"experience",
|
|
616
|
+
"fast",
|
|
617
|
+
"field",
|
|
618
|
+
"find",
|
|
619
|
+
"first",
|
|
620
|
+
"focused",
|
|
621
|
+
"follow",
|
|
622
|
+
"get",
|
|
623
|
+
"give",
|
|
624
|
+
"go",
|
|
625
|
+
"going",
|
|
626
|
+
"good",
|
|
627
|
+
"great",
|
|
628
|
+
"group",
|
|
629
|
+
"grow",
|
|
630
|
+
"growing",
|
|
631
|
+
"growth",
|
|
632
|
+
"help",
|
|
633
|
+
"here",
|
|
634
|
+
"high",
|
|
635
|
+
"highly",
|
|
636
|
+
"ideal",
|
|
637
|
+
"impact",
|
|
638
|
+
"important",
|
|
639
|
+
"include",
|
|
640
|
+
"includes",
|
|
641
|
+
"including",
|
|
642
|
+
"industry",
|
|
643
|
+
"interested",
|
|
644
|
+
"job",
|
|
645
|
+
"join",
|
|
646
|
+
"just",
|
|
647
|
+
"keep",
|
|
648
|
+
"key",
|
|
649
|
+
"know",
|
|
650
|
+
"large",
|
|
651
|
+
"latest",
|
|
652
|
+
"lead",
|
|
653
|
+
"level",
|
|
654
|
+
"like",
|
|
655
|
+
"location",
|
|
656
|
+
"long",
|
|
657
|
+
"look",
|
|
658
|
+
"looking",
|
|
659
|
+
"love",
|
|
660
|
+
"make",
|
|
661
|
+
"many",
|
|
662
|
+
"much",
|
|
663
|
+
"must",
|
|
664
|
+
"need",
|
|
665
|
+
"new",
|
|
666
|
+
"next",
|
|
667
|
+
"offer",
|
|
668
|
+
"one",
|
|
669
|
+
"only",
|
|
670
|
+
"open",
|
|
671
|
+
"opportunity",
|
|
672
|
+
"order",
|
|
673
|
+
"others",
|
|
674
|
+
"own",
|
|
675
|
+
"pace",
|
|
676
|
+
"part",
|
|
677
|
+
"partner",
|
|
678
|
+
"passionate",
|
|
679
|
+
"people",
|
|
680
|
+
"per",
|
|
681
|
+
"play",
|
|
682
|
+
"plus",
|
|
683
|
+
"position",
|
|
684
|
+
"preferred",
|
|
685
|
+
"provide",
|
|
686
|
+
"put",
|
|
687
|
+
"qualifications",
|
|
688
|
+
"quickly",
|
|
689
|
+
"range",
|
|
690
|
+
"related",
|
|
691
|
+
"required",
|
|
692
|
+
"requirements",
|
|
693
|
+
"requirement",
|
|
694
|
+
"responsible",
|
|
695
|
+
"responsibilities",
|
|
696
|
+
"responsibility",
|
|
697
|
+
"result",
|
|
698
|
+
"right",
|
|
699
|
+
"role",
|
|
700
|
+
"run",
|
|
701
|
+
"same",
|
|
702
|
+
"see",
|
|
703
|
+
"seek",
|
|
704
|
+
"seeking",
|
|
705
|
+
"set",
|
|
706
|
+
"several",
|
|
707
|
+
"since",
|
|
708
|
+
"skills",
|
|
709
|
+
"someone",
|
|
710
|
+
"start",
|
|
711
|
+
"state",
|
|
712
|
+
"still",
|
|
713
|
+
"strong",
|
|
714
|
+
"success",
|
|
715
|
+
"successful",
|
|
716
|
+
"support",
|
|
717
|
+
"sure",
|
|
718
|
+
"take",
|
|
719
|
+
"team",
|
|
720
|
+
"then",
|
|
721
|
+
"there",
|
|
722
|
+
"thing",
|
|
723
|
+
"think",
|
|
724
|
+
"through",
|
|
725
|
+
"time",
|
|
726
|
+
"together",
|
|
727
|
+
"top",
|
|
728
|
+
"truly",
|
|
729
|
+
"try",
|
|
730
|
+
"two",
|
|
731
|
+
"type",
|
|
732
|
+
"use",
|
|
733
|
+
"used",
|
|
734
|
+
"using",
|
|
735
|
+
"value",
|
|
736
|
+
"want",
|
|
737
|
+
"way",
|
|
738
|
+
"well",
|
|
739
|
+
"while",
|
|
740
|
+
"within",
|
|
741
|
+
"without",
|
|
742
|
+
"work",
|
|
743
|
+
"working",
|
|
744
|
+
"world",
|
|
745
|
+
"would",
|
|
746
|
+
"year",
|
|
747
|
+
"years",
|
|
748
|
+
// Section headers & structural words (not technical skills)
|
|
749
|
+
"description",
|
|
750
|
+
"overview",
|
|
751
|
+
"summary",
|
|
752
|
+
"duties",
|
|
753
|
+
"bachelor",
|
|
754
|
+
"bachelors",
|
|
755
|
+
"master",
|
|
756
|
+
"masters",
|
|
757
|
+
"degree",
|
|
758
|
+
"phd",
|
|
759
|
+
"minimum",
|
|
760
|
+
"preferred",
|
|
761
|
+
"implement",
|
|
762
|
+
"process",
|
|
763
|
+
"robust",
|
|
764
|
+
"consistent",
|
|
765
|
+
"operations"
|
|
555
766
|
]
|
|
556
767
|
};
|
|
557
768
|
en_default = en;
|
|
@@ -1123,7 +1334,7 @@ var init_genericChecks = __esm({
|
|
|
1123
1334
|
|
|
1124
1335
|
// src/ats/jdMatcher.ts
|
|
1125
1336
|
function tokenize(text, stopWords) {
|
|
1126
|
-
return text.toLowerCase().replace(/[^a-zA-Z0-9äöüßÄÖÜàáâãéèêëíìîïóòôõúùûüñç\s
|
|
1337
|
+
return text.toLowerCase().replace(/[^a-zA-Z0-9äöüßÄÖÜàáâãéèêëíìîïóòôõúùûüñç\s/+-]/g, " ").split(/\s+/).filter((word) => word.length > 2 && !stopWords.has(word));
|
|
1127
1338
|
}
|
|
1128
1339
|
function simpleStem(word, language) {
|
|
1129
1340
|
if (language === "de") {
|
|
@@ -1166,20 +1377,158 @@ function buildTfMap(tokens) {
|
|
|
1166
1377
|
}
|
|
1167
1378
|
return tf;
|
|
1168
1379
|
}
|
|
1380
|
+
function splitJdSections(text) {
|
|
1381
|
+
const lines = text.split("\n");
|
|
1382
|
+
const reqPatterns = /^(required|requirements?|minimum|preferred|qualifications?|must[\s-]have|nice[\s-]to[\s-]have|what you.?ll|what we.?re looking|skills|technical|you.?ll need|responsibilities)/i;
|
|
1383
|
+
let inReqSection = false;
|
|
1384
|
+
const reqLines = [];
|
|
1385
|
+
const otherLines = [];
|
|
1386
|
+
for (const line of lines) {
|
|
1387
|
+
const trimmed = line.trim();
|
|
1388
|
+
if (reqPatterns.test(trimmed.replace(/[:#*-]/g, "").trim())) {
|
|
1389
|
+
inReqSection = true;
|
|
1390
|
+
} else if (/^(about|summary|who we are|our (company|team|mission)|description|overview|benefits|perks|compensation|salary)/i.test(trimmed.replace(/[:#*-]/g, "").trim())) {
|
|
1391
|
+
inReqSection = false;
|
|
1392
|
+
}
|
|
1393
|
+
if (inReqSection) {
|
|
1394
|
+
reqLines.push(line);
|
|
1395
|
+
} else {
|
|
1396
|
+
otherLines.push(line);
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
return {
|
|
1400
|
+
requirementText: reqLines.join("\n"),
|
|
1401
|
+
otherText: otherLines.join("\n")
|
|
1402
|
+
};
|
|
1403
|
+
}
|
|
1404
|
+
function extractCompoundTerms(text) {
|
|
1405
|
+
const patterns = [
|
|
1406
|
+
/\b(machine\s+learning)\b/gi,
|
|
1407
|
+
/\b(deep\s+learning)\b/gi,
|
|
1408
|
+
/\b(computer\s+vision)\b/gi,
|
|
1409
|
+
/\b(natural\s+language\s+processing)\b/gi,
|
|
1410
|
+
/\b(data\s+pipelines?)\b/gi,
|
|
1411
|
+
/\b(data\s+models?)\b/gi,
|
|
1412
|
+
/\b(data\s+engineering)\b/gi,
|
|
1413
|
+
/\b(data\s+structures?)\b/gi,
|
|
1414
|
+
/\b(data\s+quality)\b/gi,
|
|
1415
|
+
/\b(data\s+flows?)\b/gi,
|
|
1416
|
+
/\b(data\s+orchestration)\b/gi,
|
|
1417
|
+
/\b(data\s+warehou\w+)\b/gi,
|
|
1418
|
+
/\b(synthetic\s+data)\b/gi,
|
|
1419
|
+
/\b(ci\s*\/?\s*cd)\b/gi,
|
|
1420
|
+
/\b(rest\s+api)\b/gi,
|
|
1421
|
+
/\b(open\s+source)\b/gi,
|
|
1422
|
+
/\b(human[\s-]+in[\s-]+the[\s-]+loop)\b/gi,
|
|
1423
|
+
/\b(self[\s-]+service)\b/gi,
|
|
1424
|
+
/\b(agentic\s+workflows?)\b/gi,
|
|
1425
|
+
/\b(distributed\s+systems?)\b/gi,
|
|
1426
|
+
/\b(cloud\s+infrastructure)\b/gi,
|
|
1427
|
+
/\b(micro\s*services?)\b/gi,
|
|
1428
|
+
/\b(full[\s-]+stack)\b/gi,
|
|
1429
|
+
/\b(front[\s-]*end)\b/gi,
|
|
1430
|
+
/\b(back[\s-]*end)\b/gi,
|
|
1431
|
+
/\b(sql\s*\/?\s*nosql)\b/gi
|
|
1432
|
+
];
|
|
1433
|
+
const found = [];
|
|
1434
|
+
for (const pattern of patterns) {
|
|
1435
|
+
const matches = text.matchAll(pattern);
|
|
1436
|
+
for (const m of matches) {
|
|
1437
|
+
const term = m[1]?.toLowerCase().replace(/\s+/g, " ").trim();
|
|
1438
|
+
if (term && !found.includes(term)) {
|
|
1439
|
+
found.push(term);
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
return found;
|
|
1444
|
+
}
|
|
1445
|
+
function extractBrandNames(text) {
|
|
1446
|
+
const brands = /* @__PURE__ */ new Set();
|
|
1447
|
+
const brandPatterns = [
|
|
1448
|
+
/\bat\s+([A-Z][a-zA-Z]+(?:\s+[A-Z][a-zA-Z]+)*)/g,
|
|
1449
|
+
/(?:^|\.\s+)([A-Z][a-zA-Z]+(?:\s+[A-Z][a-zA-Z]+)*)\s+(?:is|are|was|has|Inc|Corp|Ltd|GmbH)/g,
|
|
1450
|
+
/\b(?:join(?:ing)?|about)\s+([A-Z][a-zA-Z]+)/g
|
|
1451
|
+
];
|
|
1452
|
+
for (const pattern of brandPatterns) {
|
|
1453
|
+
const matches = text.matchAll(pattern);
|
|
1454
|
+
for (const m of matches) {
|
|
1455
|
+
const name = m[1]?.toLowerCase();
|
|
1456
|
+
if (name) {
|
|
1457
|
+
for (const word of name.split(/\s+/)) {
|
|
1458
|
+
if (word.length > 2) brands.add(word);
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
const knownBrands = [
|
|
1464
|
+
"apple",
|
|
1465
|
+
"google",
|
|
1466
|
+
"meta",
|
|
1467
|
+
"facebook",
|
|
1468
|
+
"amazon",
|
|
1469
|
+
"microsoft",
|
|
1470
|
+
"netflix",
|
|
1471
|
+
"uber",
|
|
1472
|
+
"airbnb",
|
|
1473
|
+
"twitter",
|
|
1474
|
+
"linkedin",
|
|
1475
|
+
"spotify",
|
|
1476
|
+
"stripe",
|
|
1477
|
+
"shopify",
|
|
1478
|
+
"iphone",
|
|
1479
|
+
"ipad",
|
|
1480
|
+
"mac",
|
|
1481
|
+
"macbook",
|
|
1482
|
+
"airpods",
|
|
1483
|
+
"android",
|
|
1484
|
+
"windows",
|
|
1485
|
+
"alexa",
|
|
1486
|
+
"siri",
|
|
1487
|
+
"cortana",
|
|
1488
|
+
"gmail",
|
|
1489
|
+
"chrome",
|
|
1490
|
+
"safari",
|
|
1491
|
+
"firefox"
|
|
1492
|
+
];
|
|
1493
|
+
for (const b of knownBrands) brands.add(b);
|
|
1494
|
+
return brands;
|
|
1495
|
+
}
|
|
1169
1496
|
function extractKeywords(text, language, maxKeywords = 30) {
|
|
1170
1497
|
const langData = getLanguageData(language);
|
|
1171
1498
|
const stopWords = new Set(langData.stopWords);
|
|
1172
|
-
const
|
|
1173
|
-
const
|
|
1499
|
+
const compoundTerms = extractCompoundTerms(text);
|
|
1500
|
+
const brandNames = extractBrandNames(text);
|
|
1501
|
+
const { requirementText, otherText } = splitJdSections(text);
|
|
1502
|
+
const reqTokens = tokenize(requirementText, stopWords).filter((t) => !brandNames.has(t));
|
|
1503
|
+
const otherTokens = tokenize(otherText, stopWords).filter((t) => !brandNames.has(t));
|
|
1504
|
+
const allTokens = [...reqTokens, ...reqTokens, ...reqTokens, ...otherTokens];
|
|
1505
|
+
const stemmed = allTokens.map((t) => simpleStem(t, language));
|
|
1174
1506
|
const tf = buildTfMap(stemmed);
|
|
1175
1507
|
const stemToOriginal = /* @__PURE__ */ new Map();
|
|
1176
|
-
for (let i = 0; i <
|
|
1508
|
+
for (let i = 0; i < allTokens.length; i++) {
|
|
1177
1509
|
const stem = stemmed[i] ?? "";
|
|
1178
1510
|
if (!stemToOriginal.has(stem)) {
|
|
1179
|
-
stemToOriginal.set(stem,
|
|
1511
|
+
stemToOriginal.set(stem, allTokens[i] ?? "");
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
const compoundsFlat = compoundTerms.join(" ").split(/\s+/);
|
|
1515
|
+
const compoundWordSet = new Set(compoundsFlat);
|
|
1516
|
+
const singleKeywords = [...tf.entries()].filter(([stem]) => stem.length > 2).filter(([stem]) => {
|
|
1517
|
+
const original = stemToOriginal.get(stem) || stem;
|
|
1518
|
+
if (compoundWordSet.has(original) && !reqTokens.includes(original)) {
|
|
1519
|
+
return false;
|
|
1520
|
+
}
|
|
1521
|
+
return true;
|
|
1522
|
+
}).sort((a, b) => b[1] - a[1]).slice(0, maxKeywords - compoundTerms.length).map(([stem]) => stemToOriginal.get(stem) || stem);
|
|
1523
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1524
|
+
const keywords = [];
|
|
1525
|
+
for (const term of [...compoundTerms, ...singleKeywords]) {
|
|
1526
|
+
if (!seen.has(term)) {
|
|
1527
|
+
seen.add(term);
|
|
1528
|
+
keywords.push(term);
|
|
1180
1529
|
}
|
|
1181
1530
|
}
|
|
1182
|
-
return
|
|
1531
|
+
return keywords.slice(0, maxKeywords);
|
|
1183
1532
|
}
|
|
1184
1533
|
function matchJobDescription(resume, jobDescription, language = "en") {
|
|
1185
1534
|
const langData = getLanguageData(language);
|
|
@@ -1192,11 +1541,24 @@ function matchJobDescription(resume, jobDescription, language = "en") {
|
|
|
1192
1541
|
const matched = [];
|
|
1193
1542
|
const missing = [];
|
|
1194
1543
|
for (const keyword of jdKeywords) {
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1544
|
+
if (keyword.includes(" ")) {
|
|
1545
|
+
const parts = keyword.split(/\s+/);
|
|
1546
|
+
const allPartsMatch = parts.every((part) => {
|
|
1547
|
+
const stem = simpleStem(part, language);
|
|
1548
|
+
return resumeStems.has(stem) || resumeTokenSet.has(part.toLowerCase());
|
|
1549
|
+
});
|
|
1550
|
+
if (allPartsMatch) {
|
|
1551
|
+
matched.push(keyword);
|
|
1552
|
+
} else {
|
|
1553
|
+
missing.push(keyword);
|
|
1554
|
+
}
|
|
1198
1555
|
} else {
|
|
1199
|
-
|
|
1556
|
+
const stem = simpleStem(keyword, language);
|
|
1557
|
+
if (resumeStems.has(stem) || resumeTokenSet.has(keyword.toLowerCase())) {
|
|
1558
|
+
matched.push(keyword);
|
|
1559
|
+
} else {
|
|
1560
|
+
missing.push(keyword);
|
|
1561
|
+
}
|
|
1200
1562
|
}
|
|
1201
1563
|
}
|
|
1202
1564
|
const matchPercentage = jdKeywords.length > 0 ? Math.round(matched.length / jdKeywords.length * 100) : 0;
|
|
@@ -1245,6 +1607,26 @@ function generateSummary(score, rating, hasJd) {
|
|
|
1245
1607
|
}
|
|
1246
1608
|
return `${base} \u2014 based on resume structure and content best practices.`;
|
|
1247
1609
|
}
|
|
1610
|
+
function assessFit(keywords) {
|
|
1611
|
+
const { matchPercentage, missing } = keywords;
|
|
1612
|
+
if (matchPercentage >= 70) {
|
|
1613
|
+
return {
|
|
1614
|
+
level: "strong",
|
|
1615
|
+
message: "Strong fit \u2014 your resume aligns well with this job description."
|
|
1616
|
+
};
|
|
1617
|
+
}
|
|
1618
|
+
const topMissing = missing.slice(0, 5).join(", ");
|
|
1619
|
+
if (matchPercentage >= 50) {
|
|
1620
|
+
return {
|
|
1621
|
+
level: "partial",
|
|
1622
|
+
message: `Partial fit \u2014 consider emphasizing transferable skills. Key gaps: ${topMissing}.`
|
|
1623
|
+
};
|
|
1624
|
+
}
|
|
1625
|
+
return {
|
|
1626
|
+
level: "weak",
|
|
1627
|
+
message: `Weak fit \u2014 this role requires skills not well represented in your resume. Major gaps: ${topMissing}.`
|
|
1628
|
+
};
|
|
1629
|
+
}
|
|
1248
1630
|
var weightMultiplier;
|
|
1249
1631
|
var init_scoring = __esm({
|
|
1250
1632
|
"src/ats/scoring.ts"() {
|
|
@@ -1264,8 +1646,10 @@ function analyzeAts(resume, options = {}) {
|
|
|
1264
1646
|
const checks = runGenericChecks(resume, language);
|
|
1265
1647
|
const genericScore = calculateScore(checks);
|
|
1266
1648
|
let keywords;
|
|
1649
|
+
let fitAssessment;
|
|
1267
1650
|
if (options.jobDescription) {
|
|
1268
1651
|
keywords = matchJobDescription(resume, options.jobDescription, language);
|
|
1652
|
+
fitAssessment = assessFit(keywords);
|
|
1269
1653
|
}
|
|
1270
1654
|
const finalScore = calculateCombinedScore(genericScore, keywords?.matchPercentage);
|
|
1271
1655
|
const rating = scoreToRating(finalScore);
|
|
@@ -1275,6 +1659,7 @@ function analyzeAts(resume, options = {}) {
|
|
|
1275
1659
|
rating,
|
|
1276
1660
|
checks,
|
|
1277
1661
|
keywords,
|
|
1662
|
+
fitAssessment,
|
|
1278
1663
|
summary
|
|
1279
1664
|
};
|
|
1280
1665
|
}
|
|
@@ -2144,6 +2529,17 @@ and matches them against the resume using stem matching. Results include:
|
|
|
2144
2529
|
- **missing**: Keywords not found (add these to improve score)
|
|
2145
2530
|
- **matchPercentage**: Percentage of JD keywords found in resume
|
|
2146
2531
|
|
|
2532
|
+
## Fit Assessment
|
|
2533
|
+
When a job description is provided, a \`fitAssessment\` field is included in the result:
|
|
2534
|
+
| Match % | Level | Meaning |
|
|
2535
|
+
|---------|-------|---------|
|
|
2536
|
+
| >= 70% | strong | Resume aligns well with the job description |
|
|
2537
|
+
| 50-69% | partial | Some alignment; emphasize transferable skills |
|
|
2538
|
+
| < 50% | weak | Significant skill gaps; role may not match profile |
|
|
2539
|
+
|
|
2540
|
+
The assessment includes the top 5 missing keywords as specific gaps to address.
|
|
2541
|
+
Use this to advise users whether to apply or focus effort elsewhere.
|
|
2542
|
+
|
|
2147
2543
|
## Tips for improving ATS score
|
|
2148
2544
|
1. Include all contact information (name, email, phone, city)
|
|
2149
2545
|
2. Add a LinkedIn profile URL
|