@pranavraut033/ats-checker 1.0.5 → 1.1.0

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