@pranavraut033/ats-checker 1.0.4 → 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/README.md CHANGED
@@ -1,6 +1,33 @@
1
- # ats-checker
1
+ # @pranavraut033/ats-checker
2
2
 
3
- 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.
3
+ [![npm version](https://img.shields.io/npm/v/@pranavraut033/ats-checker.svg)](https://www.npmjs.com/package/@pranavraut033/ats-checker)
4
+ [![npm downloads](https://img.shields.io/npm/dm/@pranavraut033/ats-checker.svg)](https://www.npmjs.com/package/@pranavraut033/ats-checker)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
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
17
+
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
+ ---
4
31
 
5
32
  ## Installation
6
33
 
@@ -8,165 +35,188 @@ A zero-dependency TypeScript library for evaluating resume compatibility with Ap
8
35
  npm install @pranavraut033/ats-checker
9
36
  ```
10
37
 
38
+ ---
39
+
11
40
  ## Usage
12
41
 
13
42
  ```typescript
14
43
  import { analyzeResume } from "@pranavraut033/ats-checker";
15
44
 
16
45
  const result = analyzeResume({
17
- resumeText: `John Doe
18
- Software Engineer with 5 years of experience in JavaScript and React.`,
19
- 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
20
57
  });
21
58
 
22
- console.log(result.score); // 78
23
- console.log(result.breakdown.skills); // 85
24
- 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", ...]
25
64
  ```
26
65
 
27
- ### LLM (Async) Usage
28
-
29
- For AI-enhanced suggestions while keeping scores deterministic, use the async API:
66
+ ---
30
67
 
31
- ```typescript
32
- import { analyzeResumeAsync } from "@pranavraut033/ats-checker";
68
+ ## Output
33
69
 
34
- const myLLMClient = /* implement LLMClient (OpenAI/Anthropic/local) */;
70
+ `analyzeResume()` returns an `ATSAnalysisResult`:
35
71
 
36
- const result = await analyzeResumeAsync({
37
- resumeText: "...",
38
- jobDescription: "...",
39
- llm: {
40
- client: myLLMClient,
41
- models: { default: "gpt-4o-mini" },
42
- limits: { maxCalls: 3, maxTokensPerCall: 1000, maxTotalTokens: 5000 },
43
- enable: { suggestions: true }
44
- }
45
- });
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 |
46
86
 
47
- console.log(result.score); // unchanged by LLM
48
- console.log(result.suggestions); // enhanced wording/context
49
- ```
87
+ **Scoring formula:**
88
+ `score = skills×0.30 + experience×0.30 + keywords×0.25 + education×0.15` → clamped to 0–100 → rule penalties subtracted.
50
89
 
51
- Note: Passing `llm` to `analyzeResume` (sync) will add a warning and skip enhancement. Prefer `analyzeResumeAsync` for LLM features.
90
+ ---
52
91
 
53
92
  ## Configuration
54
93
 
55
- Adjust scoring priorities, define skill synonyms, and add custom rules:
94
+ All options are optional. Pass any subset; `resolveConfig()` fills in defaults.
56
95
 
57
96
  ```typescript
58
97
  const result = analyzeResume({
59
98
  resumeText: "...",
60
99
  jobDescription: "...",
61
100
  config: {
101
+ // Override scoring weights (auto-normalized to sum to 1)
62
102
  weights: { skills: 0.4, experience: 0.3, keywords: 0.2, education: 0.1 },
63
- skillAliases: { "javascript": ["js", "ecmascript"] },
103
+
104
+ // Additional skill synonyms merged over built-in defaults
105
+ skillAliases: { javascript: ["js", "ecmascript"] },
106
+
107
+ // Industry profile: sets mandatory/optional skills and minExperience
108
+ profile: {
109
+ mandatorySkills: ["javascript", "react"],
110
+ optionalSkills: ["graphql", "docker"],
111
+ minExperience: 3,
112
+ },
113
+
114
+ // Freeze "Present" end dates for reproducible experience scoring
115
+ referenceDate: "2026-01-01",
116
+
117
+ // Keyword density thresholds
118
+ keywordDensity: { min: 0.0025, max: 0.04, overusePenalty: 5 },
119
+
120
+ // Custom penalty rules
64
121
  rules: [
65
122
  {
66
- id: "min-years",
67
- penalty: 5,
68
- warning: "Less than 3 years experience",
69
- condition: (ctx) => (ctx.resume.experienceYears ?? 0) < 3
123
+ id: "no-tables",
124
+ penalty: 10,
125
+ warning: "Remove tables ATS parsers often mangle them",
126
+ condition: (ctx) => ctx.resume.detectedSections.length < 2,
70
127
  },
71
128
  {
72
- id: "require-contact",
73
- penalty: 2,
74
- warning: "Add phone/email to contact info",
75
- condition: (ctx) => !ctx.resume.contactInfo?.phone || !ctx.resume.contactInfo?.email
76
- }
77
- ]
78
- }
129
+ id: "experience-gap",
130
+ penalty: 5,
131
+ warning: "Resume has less than 3 years experience",
132
+ condition: (ctx) => ctx.resume.totalExperienceYears < 3,
133
+ },
134
+ ],
135
+ },
79
136
  });
80
137
  ```
81
138
 
82
- See [Configuration](docs/configuration.md) for complete options.
139
+ ### Defaults
140
+
141
+ | Setting | Default |
142
+ |---|---|
143
+ | `weights.skills` | `0.30` |
144
+ | `weights.experience` | `0.30` |
145
+ | `weights.keywords` | `0.25` |
146
+ | `weights.education` | `0.15` |
147
+ | `keywordDensity.min` | `0.0025` |
148
+ | `keywordDensity.max` | `0.04` |
149
+ | `keywordDensity.overusePenalty` | `5` |
150
+ | `allowPartialMatches` | `true` |
151
+ | `referenceDate` | Current date (use explicit ISO string for determinism) |
152
+
153
+ See [Configuration docs](https://pranavraut033.github.io/ats-checker/docs/configuration/) for all options.
83
154
 
84
- ### Configuration Defaults
155
+ ---
85
156
 
86
- - Weights: skills 0.3, experience 0.3, keywords 0.25, education 0.15 (normalized)
87
- - Keyword density: min 0.0025, max 0.04, overusePenalty 5
88
- - Section penalties: summary 4, experience 10, skills 8, education 6
89
- - Partial matches: `allowPartialMatches: true`
157
+ ## Built-in Skill Aliases
90
158
 
91
- All user config is merged via `resolveConfig()` and weights are normalized to sum to 1.0.
159
+ Common tech synonyms are pre-loaded so `js` matches `javascript`, `k8s` matches `kubernetes`, etc. Extend or override via `config.skillAliases`.
160
+
161
+ ```typescript
162
+ import { defaultSkillAliases } from "@pranavraut033/ats-checker";
163
+ // { javascript: ["js"], node: ["node.js", "nodejs"], typescript: ["ts"], ... }
164
+ ```
92
165
 
93
- ### Custom Rules
166
+ ---
94
167
 
95
- Add penalties/warnings via rule conditions:
168
+ ## Built-in Profiles
96
169
 
97
170
  ```typescript
171
+ import {
172
+ softwareEngineerProfile,
173
+ dataScientistProfile,
174
+ productManagerProfile,
175
+ } from "@pranavraut033/ats-checker";
176
+
98
177
  const result = analyzeResume({
99
178
  resumeText: "...",
100
179
  jobDescription: "...",
101
- config: {
102
- rules: [
103
- {
104
- id: "min-years",
105
- penalty: 5,
106
- warning: "Less than 3 years experience",
107
- condition: (ctx) => (ctx.resume.experienceYears ?? 0) < 3
108
- },
109
- {
110
- id: "require-contact",
111
- penalty: 2,
112
- warning: "Add phone/email to contact info",
113
- condition: (ctx) => !ctx.resume.contactInfo?.phone || !ctx.resume.contactInfo?.email
114
- }
115
- ]
116
- }
180
+ config: { profile: softwareEngineerProfile },
117
181
  });
118
182
  ```
119
- See [Rules Engine](docs/rules.md) for default rules and context fields.
120
-
121
- ## Features
122
183
 
123
- - Deterministic scoring based on skills, experience, keywords, and education
124
- - Detects common ATS issues like missing sections or keyword overuse
125
- - Customizable scoring weights and validation rules
126
- - Optional LLM integration for enhanced suggestions
127
- - Includes a web interface for testing (`npm run dev`)
128
- - [Live Demo](https://pranavraut033.github.io/ats-checker/)
184
+ ---
129
185
 
130
- ## API
186
+ ## LLM Integration (deprecated)
131
187
 
132
- ### `analyzeResume(input: AnalyzeResumeInput): ATSAnalysisResult`
188
+ `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.
133
189
 
134
- Analyzes a resume against a job description.
135
-
136
- **Input:**
137
- - `resumeText: string` - The full text of the resume
138
- - `jobDescription: string` - The job description text
139
- - `config?: ATSConfig` - Optional configuration overrides
140
-
141
- **Output:**
142
- - `score: number` - Overall ATS score (0-100)
143
- - `breakdown: ATSBreakdown` - Component scores
144
- - `matchedKeywords: string[]` - Keywords found in both
145
- - `missingKeywords: string[]` - Important keywords not in resume
146
- - `suggestions: string[]` - Improvement recommendations
147
- - `warnings: string[]` - Issues detected
190
+ ---
148
191
 
149
192
  ## Development
150
193
 
151
194
  ```bash
152
195
  npm install
153
- npm run build # Build to dist/
154
- npm test # Run tests
155
- npm run dev # Start web UI at http://localhost:3005
196
+ npm run build # tsup ESM + CJS in dist/
197
+ npm test # vitest (single pass)
198
+ npm run type-check # tsc --noEmit
199
+ npm run dev # static demo UI at http://localhost:3005
156
200
  ```
157
201
 
202
+ ---
203
+
158
204
  ## Documentation
159
205
 
160
- **Live Docs** (hosted on GitHub Pages):
161
- - https://Pranavraut033.github.io/ats-checker/docs/
206
+ Full docs at **[pranavraut033.github.io/ats-checker/docs/](https://pranavraut033.github.io/ats-checker/docs/)**
162
207
 
163
- **Local Docs** (in repository):
164
- - [Configuration Guide](docs/configuration.md)
165
- - [LLM Integration](docs/llm-integration.md)
166
- - [Web Interface](docs/ui.md)
167
208
  - [Architecture](docs/architecture.md)
209
+ - [Configuration](docs/configuration.md)
210
+ - [Rules Engine](docs/rules.md)
168
211
 
169
- ## License
212
+ ---
213
+
214
+ ## Contributing
215
+
216
+ See [CONTRIBUTING.md](CONTRIBUTING.md). PRs welcome.
170
217
 
171
- MIT
218
+ ---
219
+
220
+ ## License
172
221
 
222
+ MIT © [Pranav Raut](https://github.com/Pranavraut033)
package/dist/index.d.mts CHANGED
@@ -79,6 +79,13 @@ interface ATSConfig {
79
79
  keywordDensity?: KeywordDensityConfig;
80
80
  sectionPenalties?: SectionPenaltyConfig;
81
81
  allowPartialMatches?: boolean;
82
+ /**
83
+ * ISO date string (e.g. "2024-06-01") used as the "today" reference when
84
+ * computing duration for open-ended date ranges ("Present"/"Current"/"Now").
85
+ * Omit to use the actual current date (live/production behaviour).
86
+ * Set to a fixed value in tests or batch processing to guarantee determinism.
87
+ */
88
+ referenceDate?: string;
82
89
  }
83
90
  interface NormalizedWeights extends ATSWeights {
84
91
  /** Weights normalized so they sum to 1. */
@@ -92,6 +99,8 @@ interface ResolvedATSConfig {
92
99
  keywordDensity: KeywordDensityConfig;
93
100
  sectionPenalties: Required<SectionPenaltyConfig>;
94
101
  allowPartialMatches: boolean;
102
+ /** Resolved reference date for "Present" duration calculations. */
103
+ referenceDate?: Date;
95
104
  }
96
105
  interface RuleContext {
97
106
  resume: ParsedResume;
@@ -224,11 +233,21 @@ interface AnalyzeResumeInput {
224
233
  interface ATSAnalysisResult {
225
234
  score: number;
226
235
  breakdown: ATSBreakdown;
236
+ /** Skills found in the resume that satisfy JD + profile requirements. */
237
+ matchedSkills: string[];
238
+ /** Required skills absent from the resume. */
239
+ missingSkills: string[];
227
240
  matchedKeywords: string[];
228
241
  missingKeywords: string[];
229
242
  overusedKeywords: string[];
230
243
  suggestions: string[];
231
244
  warnings: string[];
245
+ /** Years below the JD's minimum experience requirement; 0 when the requirement is met. */
246
+ experienceGap: number;
247
+ /** Resume sections the parser successfully detected (e.g. "summary", "skills"). */
248
+ detectedSections: string[];
249
+ /** Total years of experience parsed from the resume's date ranges. */
250
+ parsedExperienceYears: number;
232
251
  }
233
252
 
234
253
  declare const defaultSkillAliases: SkillAliases;
@@ -277,10 +296,6 @@ declare class LLMManager {
277
296
  * Check if features are enabled
278
297
  */
279
298
  isFeatureEnabled(feature: keyof NonNullable<LLMConfig["enable"]>): boolean;
280
- /**
281
- * Create a timeout promise
282
- */
283
- private createTimeout;
284
299
  /**
285
300
  * Estimate tokens for a call (rough approximation)
286
301
  * 1 token ≈ 4 characters average
@@ -475,18 +490,12 @@ declare function safeExtractNumber(obj: unknown, key: string): number | undefine
475
490
  */
476
491
  declare function analyzeResume(input: AnalyzeResumeInput): ATSAnalysisResult;
477
492
  /**
478
- * Async version: Analyze a resume with full LLM support
479
- * This version properly handles async LLM calls
493
+ * @deprecated The LLM layer only rewrites suggestion text and adds non-determinism.
494
+ * Prefer `analyzeResume` (sync, deterministic) and call your own LLM on the result if needed.
495
+ * This function will be removed in a future major version.
480
496
  *
481
497
  * @param input Resume, job description, and optional LLM config
482
498
  * @returns Promise<ATSAnalysisResult>
483
- *
484
- * @example
485
- * const result = await analyzeResumeAsync({
486
- * resumeText,
487
- * jobDescription,
488
- * llm: { client, limits: {...}, enable: { suggestions: true } }
489
- * });
490
499
  */
491
500
  declare function analyzeResumeAsync(input: AnalyzeResumeInput): Promise<ATSAnalysisResult>;
492
501
 
package/dist/index.d.ts CHANGED
@@ -79,6 +79,13 @@ interface ATSConfig {
79
79
  keywordDensity?: KeywordDensityConfig;
80
80
  sectionPenalties?: SectionPenaltyConfig;
81
81
  allowPartialMatches?: boolean;
82
+ /**
83
+ * ISO date string (e.g. "2024-06-01") used as the "today" reference when
84
+ * computing duration for open-ended date ranges ("Present"/"Current"/"Now").
85
+ * Omit to use the actual current date (live/production behaviour).
86
+ * Set to a fixed value in tests or batch processing to guarantee determinism.
87
+ */
88
+ referenceDate?: string;
82
89
  }
83
90
  interface NormalizedWeights extends ATSWeights {
84
91
  /** Weights normalized so they sum to 1. */
@@ -92,6 +99,8 @@ interface ResolvedATSConfig {
92
99
  keywordDensity: KeywordDensityConfig;
93
100
  sectionPenalties: Required<SectionPenaltyConfig>;
94
101
  allowPartialMatches: boolean;
102
+ /** Resolved reference date for "Present" duration calculations. */
103
+ referenceDate?: Date;
95
104
  }
96
105
  interface RuleContext {
97
106
  resume: ParsedResume;
@@ -224,11 +233,21 @@ interface AnalyzeResumeInput {
224
233
  interface ATSAnalysisResult {
225
234
  score: number;
226
235
  breakdown: ATSBreakdown;
236
+ /** Skills found in the resume that satisfy JD + profile requirements. */
237
+ matchedSkills: string[];
238
+ /** Required skills absent from the resume. */
239
+ missingSkills: string[];
227
240
  matchedKeywords: string[];
228
241
  missingKeywords: string[];
229
242
  overusedKeywords: string[];
230
243
  suggestions: string[];
231
244
  warnings: string[];
245
+ /** Years below the JD's minimum experience requirement; 0 when the requirement is met. */
246
+ experienceGap: number;
247
+ /** Resume sections the parser successfully detected (e.g. "summary", "skills"). */
248
+ detectedSections: string[];
249
+ /** Total years of experience parsed from the resume's date ranges. */
250
+ parsedExperienceYears: number;
232
251
  }
233
252
 
234
253
  declare const defaultSkillAliases: SkillAliases;
@@ -277,10 +296,6 @@ declare class LLMManager {
277
296
  * Check if features are enabled
278
297
  */
279
298
  isFeatureEnabled(feature: keyof NonNullable<LLMConfig["enable"]>): boolean;
280
- /**
281
- * Create a timeout promise
282
- */
283
- private createTimeout;
284
299
  /**
285
300
  * Estimate tokens for a call (rough approximation)
286
301
  * 1 token ≈ 4 characters average
@@ -475,18 +490,12 @@ declare function safeExtractNumber(obj: unknown, key: string): number | undefine
475
490
  */
476
491
  declare function analyzeResume(input: AnalyzeResumeInput): ATSAnalysisResult;
477
492
  /**
478
- * Async version: Analyze a resume with full LLM support
479
- * This version properly handles async LLM calls
493
+ * @deprecated The LLM layer only rewrites suggestion text and adds non-determinism.
494
+ * Prefer `analyzeResume` (sync, deterministic) and call your own LLM on the result if needed.
495
+ * This function will be removed in a future major version.
480
496
  *
481
497
  * @param input Resume, job description, and optional LLM config
482
498
  * @returns Promise<ATSAnalysisResult>
483
- *
484
- * @example
485
- * const result = await analyzeResumeAsync({
486
- * resumeText,
487
- * jobDescription,
488
- * llm: { client, limits: {...}, enable: { suggestions: true } }
489
- * });
490
499
  */
491
500
  declare function analyzeResumeAsync(input: AnalyzeResumeInput): Promise<ATSAnalysisResult>;
492
501