@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 CHANGED
@@ -1,12 +1,33 @@
1
- # ats-checker
1
+ # @pranavraut033/ats-checker
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/@pranavraut033/ats-checker.svg)](https://www.npmjs.com/package/@pranavraut033/ats-checker)
4
4
  [![npm downloads](https://img.shields.io/npm/dm/@pranavraut033/ats-checker.svg)](https://www.npmjs.com/package/@pranavraut033/ats-checker)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
- [![Build Status](https://github.com/Pranavraut033/ats-checker/actions/workflows/deploy.yml/badge.svg)](https://github.com/Pranavraut033/ats-checker/actions/workflows/deploy.yml)
7
6
  [![Tests](https://github.com/Pranavraut033/ats-checker/actions/workflows/ci.yml/badge.svg)](https://github.com/Pranavraut033/ats-checker/actions/workflows/ci.yml)
7
+ [![Build Status](https://github.com/Pranavraut033/ats-checker/actions/workflows/deploy.yml/badge.svg)](https://github.com/Pranavraut033/ats-checker/actions/workflows/deploy.yml)
8
+
9
+ Zero-dependency TypeScript library that scores a resume against a job description and explains why — skills coverage, keyword overlap, experience match, and education — with no randomness, no LLM, and no external calls.
10
+
11
+ **[Live Demo →](https://pranavraut033.github.io/ats-checker/)**
12
+ **[Docs →](https://pranavraut033.github.io/ats-checker/docs/)**
13
+
14
+ ---
15
+
16
+ ## Features
8
17
 
9
- A zero-dependency TypeScript library for evaluating resume compatibility with Applicant Tracking Systems (ATS). It parses resumes and job descriptions, calculates a deterministic score from 0 to 100, and provides actionable feedback to improve match rates.
18
+ - **Deterministic** same input always produces the same score; pin it with `referenceDate` to freeze "Present" date math
19
+ - **Explainable** — breakdown by category (skills / experience / keywords / education) plus matched and missing skill/keyword lists
20
+ - **Configurable** — adjust weights, add skill aliases, define custom penalty rules
21
+ - **Zero dependencies** — core library has no runtime deps; ships ESM + CJS
22
+ - **Built-in profiles** — software engineer, data scientist, product manager out of the box
23
+
24
+ ---
25
+
26
+ ## Requirements
27
+
28
+ - Node.js ≥ 18
29
+
30
+ ---
10
31
 
11
32
  ## Installation
12
33
 
@@ -14,173 +35,189 @@ A zero-dependency TypeScript library for evaluating resume compatibility with Ap
14
35
  npm install @pranavraut033/ats-checker
15
36
  ```
16
37
 
38
+ ---
39
+
17
40
  ## Usage
18
41
 
19
42
  ```typescript
20
43
  import { analyzeResume } from "@pranavraut033/ats-checker";
21
44
 
22
45
  const result = analyzeResume({
23
- resumeText: `John Doe
24
- Software Engineer with 5 years of experience in JavaScript and React.`,
25
- jobDescription: `We are looking for a software engineer with JavaScript experience.`
46
+ resumeText: `
47
+ Software Engineer with 5 years of experience.
48
+ Skills: JavaScript, TypeScript, React, Node.js, SQL
49
+ Experience: Senior Engineer at ExampleCorp (Jan 2020 - Present)
50
+ Education: B.S. Computer Science
51
+ `,
52
+ jobDescription: `
53
+ Frontend engineer role. Must have React, TypeScript, accessibility best practices.
54
+ Preferred: GraphQL. 3+ years required. Bachelor's degree required.
55
+ `,
56
+ config: { referenceDate: "2026-01-01" }, // freeze clock for reproducible scores
26
57
  });
27
58
 
28
- console.log(result.score); // 78
29
- console.log(result.breakdown.skills); // 85
30
- console.log(result.suggestions); // ["Add more specific JavaScript frameworks", ...]
59
+ console.log(result.score); // e.g. 72.45
60
+ console.log(result.matchedSkills); // ["javascript", "node", "react", "typescript"]
61
+ console.log(result.missingSkills); // ["accessibility best practices", "graphql"]
62
+ console.log(result.experienceGap); // 0 (requirement met)
63
+ console.log(result.suggestions); // ["Add GraphQL to your skills section", ...]
31
64
  ```
32
65
 
33
- ### LLM (Async) Usage
66
+ ---
34
67
 
35
- Note: `expandAliases()` is deprecated — prefer `normalizeSkills()` or `skillMatched()` for normalizing and matching skill names.
68
+ ## Output
36
69
 
70
+ `analyzeResume()` returns an `ATSAnalysisResult`:
37
71
 
72
+ | Field | Type | Description |
73
+ |---|---|---|
74
+ | `score` | `number` | Overall ATS score 0–100 after rule penalties |
75
+ | `breakdown` | `ATSBreakdown` | Sub-scores: `skills`, `experience`, `keywords`, `education` |
76
+ | `matchedSkills` | `string[]` | Required skills found in the resume |
77
+ | `missingSkills` | `string[]` | Required skills absent from the resume |
78
+ | `matchedKeywords` | `string[]` | JD keywords present in the resume (sorted) |
79
+ | `missingKeywords` | `string[]` | JD keywords absent from the resume (sorted) |
80
+ | `overusedKeywords` | `string[]` | Keywords exceeding density threshold (sorted) |
81
+ | `suggestions` | `string[]` | Deterministic improvement recommendations |
82
+ | `warnings` | `string[]` | Parse warnings and section alerts |
83
+ | `experienceGap` | `number` | Years below JD minimum; `0` when met |
84
+ | `detectedSections` | `string[]` | Resume sections the parser found |
85
+ | `parsedExperienceYears` | `number` | Total years from resume date ranges (overlap-deduplicated) |
86
+ | `experienceEntries` | `ParsedExperienceEntry[]` | Parsed job entries: `title`, `company`, `dates` (with `start`/`end`/`durationInMonths`) |
38
87
 
39
- For AI-enhanced suggestions while keeping scores deterministic, use the async API:
88
+ **Scoring formula:**
89
+ `score = skills×0.30 + experience×0.30 + keywords×0.25 + education×0.15` → clamped to 0–100 → rule penalties subtracted.
40
90
 
41
- ```typescript
42
- import { analyzeResumeAsync } from "@pranavraut033/ats-checker";
43
-
44
- const myLLMClient = /* implement LLMClient (OpenAI/Anthropic/local) */;
45
-
46
- const result = await analyzeResumeAsync({
47
- resumeText: "...",
48
- jobDescription: "...",
49
- llm: {
50
- client: myLLMClient,
51
- models: { default: "gpt-4o-mini" },
52
- limits: { maxCalls: 3, maxTokensPerCall: 1000, maxTotalTokens: 5000 },
53
- enable: { suggestions: true }
54
- }
55
- });
56
-
57
- console.log(result.score); // unchanged by LLM
58
- console.log(result.suggestions); // enhanced wording/context
59
- ```
60
-
61
- Note: Passing `llm` to `analyzeResume` (sync) will add a warning and skip enhancement. Prefer `analyzeResumeAsync` for LLM features.
91
+ ---
62
92
 
63
93
  ## Configuration
64
94
 
65
- Adjust scoring priorities, define skill synonyms, and add custom rules:
95
+ All options are optional. Pass any subset; `resolveConfig()` fills in defaults.
66
96
 
67
97
  ```typescript
68
98
  const result = analyzeResume({
69
99
  resumeText: "...",
70
100
  jobDescription: "...",
71
101
  config: {
102
+ // Override scoring weights (auto-normalized to sum to 1)
72
103
  weights: { skills: 0.4, experience: 0.3, keywords: 0.2, education: 0.1 },
73
- skillAliases: { "javascript": ["js", "ecmascript"] },
104
+
105
+ // Additional skill synonyms merged over built-in defaults
106
+ skillAliases: { javascript: ["js", "ecmascript"] },
107
+
108
+ // Industry profile: sets mandatory/optional skills and minExperience
109
+ profile: {
110
+ mandatorySkills: ["javascript", "react"],
111
+ optionalSkills: ["graphql", "docker"],
112
+ minExperience: 3,
113
+ },
114
+
115
+ // Freeze "Present" end dates for reproducible experience scoring
116
+ referenceDate: "2026-01-01",
117
+
118
+ // Keyword density thresholds
119
+ keywordDensity: { min: 0.0025, max: 0.04, overusePenalty: 5 },
120
+
121
+ // Custom penalty rules
74
122
  rules: [
75
123
  {
76
- id: "min-years",
77
- penalty: 5,
78
- warning: "Less than 3 years experience",
79
- condition: (ctx) => (ctx.resume.experienceYears ?? 0) < 3
124
+ id: "no-tables",
125
+ penalty: 10,
126
+ warning: "Remove tables ATS parsers often mangle them",
127
+ condition: (ctx) => ctx.resume.detectedSections.length < 2,
80
128
  },
81
129
  {
82
- id: "require-contact",
83
- penalty: 2,
84
- warning: "Add phone/email to contact info",
85
- condition: (ctx) => !ctx.resume.contactInfo?.phone || !ctx.resume.contactInfo?.email
86
- }
87
- ]
88
- }
130
+ id: "experience-gap",
131
+ penalty: 5,
132
+ warning: "Resume has less than 3 years experience",
133
+ condition: (ctx) => ctx.resume.totalExperienceYears < 3,
134
+ },
135
+ ],
136
+ },
89
137
  });
90
138
  ```
91
139
 
92
- See [Configuration](docs/configuration.md) for complete options.
140
+ ### Defaults
141
+
142
+ | Setting | Default |
143
+ |---|---|
144
+ | `weights.skills` | `0.30` |
145
+ | `weights.experience` | `0.30` |
146
+ | `weights.keywords` | `0.25` |
147
+ | `weights.education` | `0.15` |
148
+ | `keywordDensity.min` | `0.0025` |
149
+ | `keywordDensity.max` | `0.04` |
150
+ | `keywordDensity.overusePenalty` | `5` |
151
+ | `allowPartialMatches` | `true` |
152
+ | `referenceDate` | Current date (use explicit ISO string for determinism) |
153
+
154
+ See [Configuration docs](https://pranavraut033.github.io/ats-checker/docs/configuration/) for all options.
93
155
 
94
- ### Configuration Defaults
156
+ ---
95
157
 
96
- - Weights: skills 0.3, experience 0.3, keywords 0.25, education 0.15 (normalized)
97
- - Keyword density: min 0.0025, max 0.04, overusePenalty 5
98
- - Section penalties: summary 4, experience 10, skills 8, education 6
99
- - Partial matches: `allowPartialMatches: true`
158
+ ## Built-in Skill Aliases
100
159
 
101
- All user config is merged via `resolveConfig()` and weights are normalized to sum to 1.0.
160
+ Common tech synonyms are pre-loaded so `js` matches `javascript`, `k8s` matches `kubernetes`, etc. Extend or override via `config.skillAliases`.
102
161
 
103
- ### Custom Rules
162
+ ```typescript
163
+ import { defaultSkillAliases } from "@pranavraut033/ats-checker";
164
+ // { javascript: ["js"], node: ["node.js", "nodejs"], typescript: ["ts"], ... }
165
+ ```
166
+
167
+ ---
104
168
 
105
- Add penalties/warnings via rule conditions:
169
+ ## Built-in Profiles
106
170
 
107
171
  ```typescript
172
+ import {
173
+ softwareEngineerProfile,
174
+ dataScientistProfile,
175
+ productManagerProfile,
176
+ } from "@pranavraut033/ats-checker";
177
+
108
178
  const result = analyzeResume({
109
179
  resumeText: "...",
110
180
  jobDescription: "...",
111
- config: {
112
- rules: [
113
- {
114
- id: "min-years",
115
- penalty: 5,
116
- warning: "Less than 3 years experience",
117
- condition: (ctx) => (ctx.resume.experienceYears ?? 0) < 3
118
- },
119
- {
120
- id: "require-contact",
121
- penalty: 2,
122
- warning: "Add phone/email to contact info",
123
- condition: (ctx) => !ctx.resume.contactInfo?.phone || !ctx.resume.contactInfo?.email
124
- }
125
- ]
126
- }
181
+ config: { profile: softwareEngineerProfile },
127
182
  });
128
183
  ```
129
- See [Rules Engine](docs/rules.md) for default rules and context fields.
130
-
131
- ## Features
132
184
 
133
- - Deterministic scoring based on skills, experience, keywords, and education
134
- - Detects common ATS issues like missing sections or keyword overuse
135
- - Customizable scoring weights and validation rules
136
- - Optional LLM integration for enhanced suggestions
137
- - Includes a web interface for testing (`npm run dev`)
138
- - [Live Demo](https://pranavraut033.github.io/ats-checker/)
185
+ ---
139
186
 
140
- ## API
187
+ ## LLM Integration (deprecated)
141
188
 
142
- ### `analyzeResume(input: AnalyzeResumeInput): ATSAnalysisResult`
189
+ `analyzeResumeAsync` accepts an optional `llm` config that rewrites suggestion text via a caller-supplied LLM client. **This path is deprecated** — scores and breakdowns are never touched by LLM. Prefer calling `analyzeResume` and running your own LLM pass on `result.suggestions` if you want AI-enhanced wording.
143
190
 
144
- Analyzes a resume against a job description.
145
-
146
- **Input:**
147
- - `resumeText: string` - The full text of the resume
148
- - `jobDescription: string` - The job description text
149
- - `config?: ATSConfig` - Optional configuration overrides
150
-
151
- **Output:**
152
- - `score: number` - Overall ATS score (0-100)
153
- - `breakdown: ATSBreakdown` - Component scores
154
- - `matchedKeywords: string[]` - Keywords found in both
155
- - `missingKeywords: string[]` - Important keywords not in resume
156
- - `suggestions: string[]` - Improvement recommendations
157
- - `warnings: string[]` - Issues detected
191
+ ---
158
192
 
159
193
  ## Development
160
194
 
161
195
  ```bash
162
196
  npm install
163
- npm run build # Build to dist/
164
- npm test # Run tests
165
- npm run dev # Start web UI at http://localhost:3005
197
+ npm run build # tsup ESM + CJS in dist/
198
+ npm test # vitest (single pass)
199
+ npm run type-check # tsc --noEmit
200
+ npm run dev # static demo UI at http://localhost:3005
166
201
  ```
167
202
 
203
+ ---
204
+
168
205
  ## Documentation
169
206
 
170
- **Live Docs** (hosted on GitHub Pages):
171
- - https://Pranavraut033.github.io/ats-checker/docs/
207
+ Full docs at **[pranavraut033.github.io/ats-checker/docs/](https://pranavraut033.github.io/ats-checker/docs/)**
172
208
 
173
- **Local Docs** (in repository):
174
- - [Configuration Guide](docs/configuration.md)
175
- - [LLM Integration](docs/llm-integration.md)
176
- - [Web Interface](docs/ui.md)
177
209
  - [Architecture](docs/architecture.md)
210
+ - [Configuration](docs/configuration.md)
211
+ - [Rules Engine](docs/rules.md)
212
+
213
+ ---
178
214
 
179
215
  ## Contributing
180
216
 
181
- Contributions are welcome! Please see the [Contributing Guide](https://github.com/Pranavraut033/ats-checker/blob/main/CONTRIBUTING.md) for details.
217
+ See [CONTRIBUTING.md](CONTRIBUTING.md). PRs welcome.
182
218
 
183
- ## License
219
+ ---
184
220
 
185
- MIT
221
+ ## License
186
222
 
223
+ MIT © [Pranav Raut](https://github.com/Pranavraut033)
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;
@@ -79,6 +84,13 @@ interface ATSConfig {
79
84
  keywordDensity?: KeywordDensityConfig;
80
85
  sectionPenalties?: SectionPenaltyConfig;
81
86
  allowPartialMatches?: boolean;
87
+ /**
88
+ * ISO date string (e.g. "2024-06-01") used as the "today" reference when
89
+ * computing duration for open-ended date ranges ("Present"/"Current"/"Now").
90
+ * Omit to use the actual current date (live/production behaviour).
91
+ * Set to a fixed value in tests or batch processing to guarantee determinism.
92
+ */
93
+ referenceDate?: string;
82
94
  }
83
95
  interface NormalizedWeights extends ATSWeights {
84
96
  /** Weights normalized so they sum to 1. */
@@ -92,6 +104,8 @@ interface ResolvedATSConfig {
92
104
  keywordDensity: KeywordDensityConfig;
93
105
  sectionPenalties: Required<SectionPenaltyConfig>;
94
106
  allowPartialMatches: boolean;
107
+ /** Resolved reference date for "Present" duration calculations. */
108
+ referenceDate?: Date;
95
109
  }
96
110
  interface RuleContext {
97
111
  resume: ParsedResume;
@@ -224,11 +238,23 @@ interface AnalyzeResumeInput {
224
238
  interface ATSAnalysisResult {
225
239
  score: number;
226
240
  breakdown: ATSBreakdown;
241
+ /** Skills found in the resume that satisfy JD + profile requirements. */
242
+ matchedSkills: string[];
243
+ /** Required skills absent from the resume. */
244
+ missingSkills: string[];
227
245
  matchedKeywords: string[];
228
246
  missingKeywords: string[];
229
247
  overusedKeywords: string[];
230
248
  suggestions: string[];
231
249
  warnings: string[];
250
+ /** Years below the JD's minimum experience requirement; 0 when the requirement is met. */
251
+ experienceGap: number;
252
+ /** Resume sections the parser successfully detected (e.g. "summary", "skills"). */
253
+ detectedSections: string[];
254
+ /** Total years of experience parsed from the resume's date ranges. */
255
+ parsedExperienceYears: number;
256
+ /** Parsed experience entries from the resume, with titles and date ranges. */
257
+ experienceEntries: ParsedExperienceEntry[];
232
258
  }
233
259
 
234
260
  declare const defaultSkillAliases: SkillAliases;
@@ -471,18 +497,12 @@ declare function safeExtractNumber(obj: unknown, key: string): number | undefine
471
497
  */
472
498
  declare function analyzeResume(input: AnalyzeResumeInput): ATSAnalysisResult;
473
499
  /**
474
- * Async version: Analyze a resume with full LLM support
475
- * This version properly handles async LLM calls
500
+ * @deprecated The LLM layer only rewrites suggestion text and adds non-determinism.
501
+ * Prefer `analyzeResume` (sync, deterministic) and call your own LLM on the result if needed.
502
+ * This function will be removed in a future major version.
476
503
  *
477
504
  * @param input Resume, job description, and optional LLM config
478
505
  * @returns Promise<ATSAnalysisResult>
479
- *
480
- * @example
481
- * const result = await analyzeResumeAsync({
482
- * resumeText,
483
- * jobDescription,
484
- * llm: { client, limits: {...}, enable: { suggestions: true } }
485
- * });
486
506
  */
487
507
  declare function analyzeResumeAsync(input: AnalyzeResumeInput): Promise<ATSAnalysisResult>;
488
508
 
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;
@@ -79,6 +84,13 @@ interface ATSConfig {
79
84
  keywordDensity?: KeywordDensityConfig;
80
85
  sectionPenalties?: SectionPenaltyConfig;
81
86
  allowPartialMatches?: boolean;
87
+ /**
88
+ * ISO date string (e.g. "2024-06-01") used as the "today" reference when
89
+ * computing duration for open-ended date ranges ("Present"/"Current"/"Now").
90
+ * Omit to use the actual current date (live/production behaviour).
91
+ * Set to a fixed value in tests or batch processing to guarantee determinism.
92
+ */
93
+ referenceDate?: string;
82
94
  }
83
95
  interface NormalizedWeights extends ATSWeights {
84
96
  /** Weights normalized so they sum to 1. */
@@ -92,6 +104,8 @@ interface ResolvedATSConfig {
92
104
  keywordDensity: KeywordDensityConfig;
93
105
  sectionPenalties: Required<SectionPenaltyConfig>;
94
106
  allowPartialMatches: boolean;
107
+ /** Resolved reference date for "Present" duration calculations. */
108
+ referenceDate?: Date;
95
109
  }
96
110
  interface RuleContext {
97
111
  resume: ParsedResume;
@@ -224,11 +238,23 @@ interface AnalyzeResumeInput {
224
238
  interface ATSAnalysisResult {
225
239
  score: number;
226
240
  breakdown: ATSBreakdown;
241
+ /** Skills found in the resume that satisfy JD + profile requirements. */
242
+ matchedSkills: string[];
243
+ /** Required skills absent from the resume. */
244
+ missingSkills: string[];
227
245
  matchedKeywords: string[];
228
246
  missingKeywords: string[];
229
247
  overusedKeywords: string[];
230
248
  suggestions: string[];
231
249
  warnings: string[];
250
+ /** Years below the JD's minimum experience requirement; 0 when the requirement is met. */
251
+ experienceGap: number;
252
+ /** Resume sections the parser successfully detected (e.g. "summary", "skills"). */
253
+ detectedSections: string[];
254
+ /** Total years of experience parsed from the resume's date ranges. */
255
+ parsedExperienceYears: number;
256
+ /** Parsed experience entries from the resume, with titles and date ranges. */
257
+ experienceEntries: ParsedExperienceEntry[];
232
258
  }
233
259
 
234
260
  declare const defaultSkillAliases: SkillAliases;
@@ -471,18 +497,12 @@ declare function safeExtractNumber(obj: unknown, key: string): number | undefine
471
497
  */
472
498
  declare function analyzeResume(input: AnalyzeResumeInput): ATSAnalysisResult;
473
499
  /**
474
- * Async version: Analyze a resume with full LLM support
475
- * This version properly handles async LLM calls
500
+ * @deprecated The LLM layer only rewrites suggestion text and adds non-determinism.
501
+ * Prefer `analyzeResume` (sync, deterministic) and call your own LLM on the result if needed.
502
+ * This function will be removed in a future major version.
476
503
  *
477
504
  * @param input Resume, job description, and optional LLM config
478
505
  * @returns Promise<ATSAnalysisResult>
479
- *
480
- * @example
481
- * const result = await analyzeResumeAsync({
482
- * resumeText,
483
- * jobDescription,
484
- * llm: { client, limits: {...}, enable: { suggestions: true } }
485
- * });
486
506
  */
487
507
  declare function analyzeResumeAsync(input: AnalyzeResumeInput): Promise<ATSAnalysisResult>;
488
508