@pranavraut033/ats-checker 1.1.0 → 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 +2 -1
- package/dist/index.d.mts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +219 -37
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +219 -37
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -82,7 +82,8 @@ console.log(result.suggestions); // ["Add GraphQL to your skills section",
|
|
|
82
82
|
| `warnings` | `string[]` | Parse warnings and section alerts |
|
|
83
83
|
| `experienceGap` | `number` | Years below JD minimum; `0` when met |
|
|
84
84
|
| `detectedSections` | `string[]` | Resume sections the parser found |
|
|
85
|
-
| `parsedExperienceYears` | `number` | Total years from resume date ranges |
|
|
85
|
+
| `parsedExperienceYears` | `number` | Total years from resume date ranges (overlap-deduplicated) |
|
|
86
|
+
| `experienceEntries` | `ParsedExperienceEntry[]` | Parsed job entries: `title`, `company`, `dates` (with `start`/`end`/`durationInMonths`) |
|
|
86
87
|
|
|
87
88
|
**Scoring formula:**
|
|
88
89
|
`score = skills×0.30 + experience×0.30 + keywords×0.25 + education×0.15` → clamped to 0–100 → rule penalties subtracted.
|
package/dist/index.d.mts
CHANGED
|
@@ -4,6 +4,11 @@ interface ParsedDateRange {
|
|
|
4
4
|
start?: string;
|
|
5
5
|
end?: string;
|
|
6
6
|
durationInMonths?: number;
|
|
7
|
+
/** Numeric year/month of the start and end, for overlap-aware summing. */
|
|
8
|
+
startYear?: number;
|
|
9
|
+
startMonth?: number;
|
|
10
|
+
endYear?: number;
|
|
11
|
+
endMonth?: number;
|
|
7
12
|
}
|
|
8
13
|
interface ParsedExperienceEntry {
|
|
9
14
|
title?: string;
|
|
@@ -248,6 +253,8 @@ interface ATSAnalysisResult {
|
|
|
248
253
|
detectedSections: string[];
|
|
249
254
|
/** Total years of experience parsed from the resume's date ranges. */
|
|
250
255
|
parsedExperienceYears: number;
|
|
256
|
+
/** Parsed experience entries from the resume, with titles and date ranges. */
|
|
257
|
+
experienceEntries: ParsedExperienceEntry[];
|
|
251
258
|
}
|
|
252
259
|
|
|
253
260
|
declare const defaultSkillAliases: SkillAliases;
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,11 @@ interface ParsedDateRange {
|
|
|
4
4
|
start?: string;
|
|
5
5
|
end?: string;
|
|
6
6
|
durationInMonths?: number;
|
|
7
|
+
/** Numeric year/month of the start and end, for overlap-aware summing. */
|
|
8
|
+
startYear?: number;
|
|
9
|
+
startMonth?: number;
|
|
10
|
+
endYear?: number;
|
|
11
|
+
endMonth?: number;
|
|
7
12
|
}
|
|
8
13
|
interface ParsedExperienceEntry {
|
|
9
14
|
title?: string;
|
|
@@ -248,6 +253,8 @@ interface ATSAnalysisResult {
|
|
|
248
253
|
detectedSections: string[];
|
|
249
254
|
/** Total years of experience parsed from the resume's date ranges. */
|
|
250
255
|
parsedExperienceYears: number;
|
|
256
|
+
/** Parsed experience entries from the resume, with titles and date ranges. */
|
|
257
|
+
experienceEntries: ParsedExperienceEntry[];
|
|
251
258
|
}
|
|
252
259
|
|
|
253
260
|
declare const defaultSkillAliases: SkillAliases;
|
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,7 +36,98 @@ 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();
|
|
@@ -45,7 +141,12 @@ function splitLines(text) {
|
|
|
45
141
|
var TECH_TOKEN_RE = /[a-z0-9][a-z0-9.#+\-/]*[a-z0-9#+]/g;
|
|
46
142
|
function tokenize(text) {
|
|
47
143
|
const normalized = normalizeForComparison(text);
|
|
48
|
-
return (normalized.match(TECH_TOKEN_RE) ?? []).filter(
|
|
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, "\\$&");
|
|
49
150
|
}
|
|
50
151
|
function unique(values) {
|
|
51
152
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -101,37 +202,30 @@ function normalizeSkills(skills, aliases) {
|
|
|
101
202
|
}
|
|
102
203
|
|
|
103
204
|
// src/core/parser/jd.parser.ts
|
|
104
|
-
var
|
|
105
|
-
"bachelor",
|
|
106
|
-
|
|
107
|
-
"
|
|
108
|
-
"
|
|
109
|
-
"
|
|
110
|
-
"m.s",
|
|
111
|
-
"ms",
|
|
112
|
-
"msc",
|
|
113
|
-
"phd",
|
|
114
|
-
"doctorate",
|
|
115
|
-
"mba",
|
|
116
|
-
"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"]
|
|
117
211
|
];
|
|
118
212
|
function extractRequiredSkills(lines) {
|
|
119
213
|
const required = [];
|
|
120
214
|
for (const line of lines) {
|
|
121
215
|
if (/must|require|required|need/i.test(line)) {
|
|
122
|
-
required.push(...line
|
|
216
|
+
required.push(...tokenize(line));
|
|
123
217
|
}
|
|
124
218
|
}
|
|
125
|
-
return required
|
|
219
|
+
return required;
|
|
126
220
|
}
|
|
127
221
|
function extractPreferredSkills(lines) {
|
|
128
222
|
const preferred = [];
|
|
129
223
|
for (const line of lines) {
|
|
130
224
|
if (/preferred|nice to have|plus/i.test(line)) {
|
|
131
|
-
preferred.push(...line
|
|
225
|
+
preferred.push(...tokenize(line));
|
|
132
226
|
}
|
|
133
227
|
}
|
|
134
|
-
return preferred
|
|
228
|
+
return preferred;
|
|
135
229
|
}
|
|
136
230
|
function extractRoleKeywords(text) {
|
|
137
231
|
const roleMatch = text.match(/(engineer|developer|manager|scientist|analyst|designer|architect)/i);
|
|
@@ -146,23 +240,41 @@ function extractMinExperience(text) {
|
|
|
146
240
|
return void 0;
|
|
147
241
|
}
|
|
148
242
|
function extractEducationRequirements(text) {
|
|
149
|
-
const
|
|
150
|
-
|
|
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];
|
|
151
248
|
}
|
|
152
249
|
function parseJobDescription(jobDescription, config) {
|
|
153
250
|
const normalizedText = normalizeWhitespace(jobDescription);
|
|
154
251
|
const lines = splitLines(jobDescription);
|
|
155
|
-
const
|
|
156
|
-
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);
|
|
157
267
|
const requiredSkills = normalizeSkills(requiredSkillsRaw, config.skillAliases);
|
|
158
268
|
const preferredSkills = normalizeSkills(preferredSkillsRaw, config.skillAliases);
|
|
159
|
-
const
|
|
269
|
+
const bodyTokens = tokenize(normalizedText).filter(isSkillLike);
|
|
270
|
+
const roleKeywords = extractRoleKeywords(jobDescription);
|
|
271
|
+
const keywords = unique([...requiredSkills, ...preferredSkills, ...roleKeywords, ...bodyTokens]);
|
|
160
272
|
return {
|
|
161
273
|
raw: jobDescription,
|
|
162
274
|
normalizedText,
|
|
163
275
|
requiredSkills,
|
|
164
276
|
preferredSkills,
|
|
165
|
-
roleKeywords
|
|
277
|
+
roleKeywords,
|
|
166
278
|
keywords,
|
|
167
279
|
minExperienceYears: extractMinExperience(jobDescription),
|
|
168
280
|
educationRequirements: extractEducationRequirements(jobDescription)
|
|
@@ -207,6 +319,14 @@ function parseDateToken(raw) {
|
|
|
207
319
|
return { year, month };
|
|
208
320
|
}
|
|
209
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
|
+
}
|
|
210
330
|
const yearMatch = cleaned.match(/(20\d{2}|19\d{2})/);
|
|
211
331
|
if (yearMatch) {
|
|
212
332
|
const year = Number.parseInt(yearMatch[1], 10);
|
|
@@ -221,7 +341,9 @@ function monthsBetween(start, end) {
|
|
|
221
341
|
}
|
|
222
342
|
function parseDateRange(text, referenceDate) {
|
|
223
343
|
const normalized = text.trim();
|
|
224
|
-
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
|
+
);
|
|
225
347
|
if (!rangeMatch) {
|
|
226
348
|
return null;
|
|
227
349
|
}
|
|
@@ -242,11 +364,40 @@ function parseDateRange(text, referenceDate) {
|
|
|
242
364
|
raw: normalized,
|
|
243
365
|
start: rangeMatch[1],
|
|
244
366
|
end: isPresent ? "present" : rangeMatch[2],
|
|
245
|
-
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
|
|
246
372
|
};
|
|
247
373
|
}
|
|
248
374
|
function sumExperienceYears(ranges) {
|
|
249
|
-
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);
|
|
250
401
|
return Number((months / 12).toFixed(2));
|
|
251
402
|
}
|
|
252
403
|
|
|
@@ -280,9 +431,6 @@ var ACTION_VERBS = [
|
|
|
280
431
|
"reduced",
|
|
281
432
|
"increased"
|
|
282
433
|
];
|
|
283
|
-
function escapeRegExp(input) {
|
|
284
|
-
return input.replace(/[.*+?^${}()|[\\]\\]/g, "\\$&");
|
|
285
|
-
}
|
|
286
434
|
function detectSection(line) {
|
|
287
435
|
const normalized = line.trim().toLowerCase();
|
|
288
436
|
for (const [section, aliases] of Object.entries(SECTION_ALIASES)) {
|
|
@@ -381,9 +529,16 @@ function parseResume(resumeText, config) {
|
|
|
381
529
|
const actionVerbs = parseActionVerbs(normalizedText);
|
|
382
530
|
const experienceData = parseExperience(sections.experience, config.referenceDate);
|
|
383
531
|
const educationEntries = parseEducation(sections.education);
|
|
384
|
-
|
|
532
|
+
let totalExperienceYears = sumExperienceYears(
|
|
385
533
|
experienceData.entries.map((entry) => entry.dates).filter((range) => Boolean(range))
|
|
386
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
|
+
}
|
|
387
542
|
const requiredSections = ["summary", "experience", "skills", "education"];
|
|
388
543
|
const warnings = [];
|
|
389
544
|
for (const section of requiredSections) {
|
|
@@ -586,10 +741,11 @@ function calculateScore(resume, job, config) {
|
|
|
586
741
|
overusedKeywords: keywordResult.overusedKeywords,
|
|
587
742
|
suggestions: [],
|
|
588
743
|
warnings: [],
|
|
589
|
-
// detectedSections / parsedExperienceYears / experienceGap: filled by index.ts
|
|
744
|
+
// detectedSections / parsedExperienceYears / experienceGap / experienceEntries: filled by index.ts
|
|
590
745
|
experienceGap: experienceResult.missingYears,
|
|
591
746
|
detectedSections: [],
|
|
592
747
|
parsedExperienceYears: 0,
|
|
748
|
+
experienceEntries: [],
|
|
593
749
|
missingExperienceYears: experienceResult.missingYears,
|
|
594
750
|
educationScore
|
|
595
751
|
};
|
|
@@ -613,7 +769,31 @@ var defaultSkillAliases = {
|
|
|
613
769
|
docker: ["containers"],
|
|
614
770
|
kubernetes: ["k8s"],
|
|
615
771
|
html: ["html5"],
|
|
616
|
-
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: []
|
|
617
797
|
};
|
|
618
798
|
var softwareEngineerProfile = {
|
|
619
799
|
name: "software-engineer",
|
|
@@ -1400,6 +1580,7 @@ function analyzeResume(input) {
|
|
|
1400
1580
|
experienceGap: scoring.experienceGap,
|
|
1401
1581
|
detectedSections: parsedResume.detectedSections,
|
|
1402
1582
|
parsedExperienceYears: parsedResume.totalExperienceYears,
|
|
1583
|
+
experienceEntries: parsedResume.experience,
|
|
1403
1584
|
suggestions,
|
|
1404
1585
|
warnings: [...suggestionResult.warnings, ...llmWarnings]
|
|
1405
1586
|
};
|
|
@@ -1464,6 +1645,7 @@ async function analyzeResumeAsync(input) {
|
|
|
1464
1645
|
experienceGap: scoring.experienceGap,
|
|
1465
1646
|
detectedSections: parsedResume.detectedSections,
|
|
1466
1647
|
parsedExperienceYears: parsedResume.totalExperienceYears,
|
|
1648
|
+
experienceEntries: parsedResume.experience,
|
|
1467
1649
|
suggestions,
|
|
1468
1650
|
warnings: [...suggestionResult.warnings, ...llmWarnings]
|
|
1469
1651
|
};
|