@pranavraut033/ats-checker 1.2.0 → 1.3.2
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 +105 -11
- package/dist/chunk-ZJ5E4H7Z.mjs +446 -0
- package/dist/chunk-ZJ5E4H7Z.mjs.map +1 -0
- package/dist/{index.js → index.cjs} +594 -94
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.mts +4 -259
- package/dist/index.d.ts +4 -259
- package/dist/index.mjs +360 -301
- package/dist/index.mjs.map +1 -1
- package/dist/lang/de/index.cjs +70 -0
- package/dist/lang/de/index.cjs.map +1 -0
- package/dist/lang/de/index.d.mts +16 -0
- package/dist/lang/de/index.d.ts +16 -0
- package/dist/lang/de/index.mjs +65 -0
- package/dist/lang/de/index.mjs.map +1 -0
- package/dist/lang/en/index.cjs +212 -0
- package/dist/lang/en/index.cjs.map +1 -0
- package/dist/lang/en/index.d.mts +5 -0
- package/dist/lang/en/index.d.ts +5 -0
- package/dist/lang/en/index.mjs +9 -0
- package/dist/lang/en/index.mjs.map +1 -0
- package/dist/pdf/{index.js → index.cjs} +13 -4
- package/dist/pdf/index.cjs.map +1 -0
- package/dist/pdf/index.d.mts +15 -2
- package/dist/pdf/index.d.ts +15 -2
- package/dist/pdf/index.mjs +11 -2
- package/dist/pdf/index.mjs.map +1 -1
- package/dist/scoring-BCShrnki.d.mts +319 -0
- package/dist/scoring-BCShrnki.d.ts +319 -0
- package/package.json +13 -1
- package/dist/index.js.map +0 -1
- package/dist/pdf/index.js.map +0 -1
package/README.md
CHANGED
|
@@ -17,7 +17,13 @@ Zero-dependency TypeScript library that scores a resume against a job descriptio
|
|
|
17
17
|
|
|
18
18
|
- **Deterministic** — same input always produces the same score; pin it with `referenceDate` to freeze "Present" date math
|
|
19
19
|
- **Explainable** — breakdown by category (skills / experience / keywords / education) plus matched and missing skill/keyword lists
|
|
20
|
-
- **
|
|
20
|
+
- **Categorized keywords** — every keyword/alias belongs to a category (technical, tool, concept, soft, marketing, domain); results are grouped by category
|
|
21
|
+
- **Weighted keyword scoring** — JD keywords are weighted by where they appear (required > preferred > body) and how often, so a missing "required" keyword costs more than a missing body-only one
|
|
22
|
+
- **Alias-aware suggestions** — flags resume terms that should be reworded to match the JD's own wording (e.g. "js" → "JavaScript")
|
|
23
|
+
- **Achievement strength** — classifies resume experience bullets as strong/weak (verb + quantified impact) and suggests rewrites
|
|
24
|
+
- **Multi-language keyword packs** — `/en` and `/de` subpaths ship categorized keyword registries; install more by passing your own `keywordRegistry`
|
|
25
|
+
- **Language proficiency matching** — detects spoken-language requirements in the JD (CEFR `A1`–`C2` or words like "fluent"/"native") and flags resume gaps below the required level
|
|
26
|
+
- **Configurable** — adjust weights, add skill aliases or a custom keyword registry, define custom penalty rules
|
|
21
27
|
- **Zero dependencies** — core library has no runtime deps; ships ESM + CJS
|
|
22
28
|
- **PDF input** — optional `/pdf` subpath extracts resume text from a PDF buffer (requires `pdfjs-dist` peer dep)
|
|
23
29
|
- **Built-in profiles** — software engineer, data scientist, product manager out of the box
|
|
@@ -46,9 +52,12 @@ import { analyzeResume } from "@pranavraut033/ats-checker";
|
|
|
46
52
|
const result = analyzeResume({
|
|
47
53
|
resumeText: `
|
|
48
54
|
Software Engineer with 5 years of experience.
|
|
49
|
-
Skills
|
|
50
|
-
|
|
51
|
-
|
|
55
|
+
Skills
|
|
56
|
+
JavaScript, TypeScript, React, Node.js, SQL
|
|
57
|
+
Experience
|
|
58
|
+
Senior Engineer at ExampleCorp (Jan 2020 - Present)
|
|
59
|
+
Education
|
|
60
|
+
B.S. Computer Science
|
|
52
61
|
`,
|
|
53
62
|
jobDescription: `
|
|
54
63
|
Frontend engineer role. Must have React, TypeScript, accessibility best practices.
|
|
@@ -57,11 +66,11 @@ const result = analyzeResume({
|
|
|
57
66
|
config: { referenceDate: "2026-01-01" }, // freeze clock for reproducible scores
|
|
58
67
|
});
|
|
59
68
|
|
|
60
|
-
console.log(result.score); //
|
|
69
|
+
console.log(result.score); // 44.44
|
|
61
70
|
console.log(result.matchedSkills); // ["javascript", "node", "react", "typescript"]
|
|
62
|
-
console.log(result.missingSkills); // ["accessibility
|
|
71
|
+
console.log(result.missingSkills); // ["accessibility", "frontend", "graphql"]
|
|
63
72
|
console.log(result.experienceGap); // 0 (requirement met)
|
|
64
|
-
console.log(result.suggestions); // ["
|
|
73
|
+
console.log(result.suggestions); // ["Highlight these required skills: accessibility, frontend, graphql", ...]
|
|
65
74
|
```
|
|
66
75
|
|
|
67
76
|
---
|
|
@@ -79,6 +88,11 @@ console.log(result.suggestions); // ["Add GraphQL to your skills section",
|
|
|
79
88
|
| `matchedKeywords` | `string[]` | JD keywords present in the resume (sorted) |
|
|
80
89
|
| `missingKeywords` | `string[]` | JD keywords absent from the resume (sorted) |
|
|
81
90
|
| `overusedKeywords` | `string[]` | Keywords exceeding density threshold (sorted) |
|
|
91
|
+
| `keywordsByCategory` | `Record<KeywordCategory, {matched, missing}>` | Matched/missing keywords grouped by category |
|
|
92
|
+
| `keywordWeights` | `KeywordWeight[]` | Per-keyword JD importance (`jdWeight`) and resume usage (`resumeWeight`) |
|
|
93
|
+
| `achievementStrength` | `{ strong: number; weak: number }` | Count of resume bullets classified as strong vs weak achievement statements |
|
|
94
|
+
| `matchedLanguages` | `ParsedLanguage[]` | JD-required languages the resume meets or exceeds in proficiency |
|
|
95
|
+
| `missingLanguages` | `ParsedLanguage[]` | JD-required languages absent or below the required proficiency |
|
|
82
96
|
| `suggestions` | `string[]` | Deterministic improvement recommendations |
|
|
83
97
|
| `warnings` | `string[]` | Parse warnings and section alerts |
|
|
84
98
|
| `experienceGap` | `number` | Years below JD minimum; `0` when met |
|
|
@@ -89,6 +103,12 @@ console.log(result.suggestions); // ["Add GraphQL to your skills section",
|
|
|
89
103
|
**Scoring formula:**
|
|
90
104
|
`score = skills×0.30 + experience×0.30 + keywords×0.25 + education×0.15` → clamped to 0–100 → rule penalties subtracted.
|
|
91
105
|
|
|
106
|
+
The `keywords` sub-score is a **weighted** coverage ratio, not a flat count: each JD keyword gets a weight from its location (required > preferred > body text) and frequency, so missing a required keyword drops the score more than missing one mentioned once in the body.
|
|
107
|
+
|
|
108
|
+
> **Caveat — malformed/copy-pasted JD text:** required/preferred detection scans each line for literal trigger phrases (`required`, `must`, `nice to have`, `preferred`). Job postings copy-pasted from a wrapped/columned source sometimes split words across line breaks (e.g. `"Nice to\n\nhaveExperience..."`), which breaks these phrases across two lines and silently drops them into the unweighted body-keyword bucket instead of required/preferred. Skill keywords themselves (e.g. `react`, `python/fastapi`) are still picked up via the whole-text token scan and unaffected. If a JD looks oddly broken, paste it through a plain-text cleanup pass first, or expect required/preferred weighting to under-count.
|
|
109
|
+
|
|
110
|
+
The `education` sub-score normalizes degree abbreviations on both sides to a canonical level (`bachelor`, `master`, `phd`, `mba`, `associate`) before comparing — so a resume listing "B.S. Computer Science" satisfies a JD requiring "Bachelor's degree".
|
|
111
|
+
|
|
92
112
|
---
|
|
93
113
|
|
|
94
114
|
## Configuration
|
|
@@ -106,6 +126,9 @@ const result = analyzeResume({
|
|
|
106
126
|
// Additional skill synonyms merged over built-in defaults
|
|
107
127
|
skillAliases: { javascript: ["js", "ecmascript"] },
|
|
108
128
|
|
|
129
|
+
// Categorized keyword/alias entries; merges over the default registry by canonical term
|
|
130
|
+
keywordRegistry: [{ canonical: "rust", aliases: ["rustlang"], category: "technical" }],
|
|
131
|
+
|
|
109
132
|
// Industry profile: sets mandatory/optional skills and minExperience
|
|
110
133
|
profile: {
|
|
111
134
|
mandatorySkills: ["javascript", "react"],
|
|
@@ -156,15 +179,69 @@ See [Configuration docs](https://pranavraut033.github.io/ats-checker/docs/config
|
|
|
156
179
|
|
|
157
180
|
---
|
|
158
181
|
|
|
159
|
-
##
|
|
182
|
+
## Keyword Registry, Categories & Aliases
|
|
160
183
|
|
|
161
|
-
Common tech synonyms are pre-loaded so `js` matches `javascript`, `k8s` matches `kubernetes`, etc.
|
|
184
|
+
Every built-in keyword/skill belongs to a `KeywordRegistry` entry — a canonical term, its aliases, and a category (`technical` | `tool` | `concept` | `soft` | `marketing` | `domain`). Common tech synonyms are pre-loaded so `js` matches `javascript`, `k8s` matches `kubernetes`, etc.
|
|
162
185
|
|
|
163
186
|
```typescript
|
|
164
|
-
import { defaultSkillAliases } from "@pranavraut033/ats-checker";
|
|
165
|
-
// {
|
|
187
|
+
import { defaultKeywordRegistry, defaultSkillAliases } from "@pranavraut033/ats-checker";
|
|
188
|
+
// defaultKeywordRegistry: [{ canonical: "javascript", aliases: ["js"], category: "technical" }, ...]
|
|
189
|
+
// defaultSkillAliases: { javascript: ["js"], node: ["node.js", "nodejs"], ... } (derived, back-compat)
|
|
166
190
|
```
|
|
167
191
|
|
|
192
|
+
Extend or override the registry via `config.keywordRegistry` — entries merge over the defaults by canonical term:
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
const result = analyzeResume({
|
|
196
|
+
resumeText: "...",
|
|
197
|
+
jobDescription: "...",
|
|
198
|
+
config: {
|
|
199
|
+
keywordRegistry: [
|
|
200
|
+
{ canonical: "rust", aliases: ["rustlang"], category: "technical" },
|
|
201
|
+
{ canonical: "javascript", aliases: ["js", "ecmascript"], category: "technical" }, // overrides default
|
|
202
|
+
],
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
console.log(result.keywordsByCategory.technical); // { matched: [...], missing: [...] }
|
|
207
|
+
console.log(result.keywordWeights); // [{ term, category, jdWeight, resumeWeight, importance }, ...]
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
You can still pass `config.skillAliases` for a flat override — it merges on top of the registry-derived aliases.
|
|
211
|
+
|
|
212
|
+
## Multi-language Keyword Packs
|
|
213
|
+
|
|
214
|
+
Categorized keyword registries ship as installable subpaths, one per language. Canonical terms stay in English (so scoring/profiles keep working); the pack supplies localized aliases.
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
import de from "@pranavraut033/ats-checker/de";
|
|
218
|
+
import { analyzeResume } from "@pranavraut033/ats-checker";
|
|
219
|
+
|
|
220
|
+
const result = analyzeResume({
|
|
221
|
+
resumeText: "...", // e.g. a German-language resume
|
|
222
|
+
jobDescription: "...",
|
|
223
|
+
config: { keywordRegistry: de },
|
|
224
|
+
});
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Available packs: `/en` (the default registry) and `/de` (seed set — grows on demand). Each default-exports a `KeywordRegistry`.
|
|
228
|
+
|
|
229
|
+
## Language Requirements
|
|
230
|
+
|
|
231
|
+
The JD parser scans for spoken-language mentions — CEFR codes (`A1`–`C2`) or descriptive words (`basic`, `conversational`, `professional`, `fluent`, `native`) — and the resume parser does the same. Any language found in the JD is treated as required; the resume must mention it at an equal or higher level to count as matched.
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
const result = analyzeResume({
|
|
235
|
+
resumeText: "Languages: German (C1), English (native)",
|
|
236
|
+
jobDescription: "German (B2) required for this role.",
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
console.log(result.matchedLanguages); // [{ name: "german", level: "b2", levelRank: 4 }]
|
|
240
|
+
console.log(result.missingLanguages); // []
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
A missing or under-leveled language surfaces both in `result.missingLanguages` and as a suggestion (`"Mention your proficiency in: german (b2)"`). This is informational/suggestion-only — it does not change `score` or `breakdown`.
|
|
244
|
+
|
|
168
245
|
---
|
|
169
246
|
|
|
170
247
|
## Built-in Profiles
|
|
@@ -218,6 +295,23 @@ if (result.warnings.length) {
|
|
|
218
295
|
}
|
|
219
296
|
```
|
|
220
297
|
|
|
298
|
+
### OCR fallback for scanned PDFs
|
|
299
|
+
|
|
300
|
+
`extractTextFromPDF` accepts an optional `ocrFallback` that's only invoked when the text layer comes back too short (default threshold: 100 chars). The OCR engine and its dependency are entirely your choice — the core library never bundles one:
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
const resumeText = await extractTextFromPDF(bytes, {
|
|
304
|
+
ocrFallback: async (data) => {
|
|
305
|
+
// bring your own OCR engine, e.g. tesseract.js or a cloud OCR API
|
|
306
|
+
const { recognize } = await import("tesseract.js");
|
|
307
|
+
const { data: { text } } = await recognize(data, "eng");
|
|
308
|
+
return text;
|
|
309
|
+
},
|
|
310
|
+
});
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
If `ocrFallback` throws or returns text that isn't longer than the text-layer result, `extractTextFromPDF` silently keeps the original result — OCR failures never break the deterministic extraction path.
|
|
314
|
+
|
|
221
315
|
---
|
|
222
316
|
|
|
223
317
|
## LLM Integration (deprecated)
|
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
// src/utils/text.ts
|
|
2
|
+
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
3
|
+
// articles / prepositions / conjunctions
|
|
4
|
+
"the",
|
|
5
|
+
"and",
|
|
6
|
+
"or",
|
|
7
|
+
"a",
|
|
8
|
+
"an",
|
|
9
|
+
"of",
|
|
10
|
+
"for",
|
|
11
|
+
"to",
|
|
12
|
+
"with",
|
|
13
|
+
"in",
|
|
14
|
+
"on",
|
|
15
|
+
"at",
|
|
16
|
+
"by",
|
|
17
|
+
"from",
|
|
18
|
+
"as",
|
|
19
|
+
"into",
|
|
20
|
+
"onto",
|
|
21
|
+
"upon",
|
|
22
|
+
"via",
|
|
23
|
+
"per",
|
|
24
|
+
"plus",
|
|
25
|
+
// verbs / modals
|
|
26
|
+
"is",
|
|
27
|
+
"are",
|
|
28
|
+
"be",
|
|
29
|
+
"was",
|
|
30
|
+
"were",
|
|
31
|
+
"will",
|
|
32
|
+
"can",
|
|
33
|
+
"should",
|
|
34
|
+
"must",
|
|
35
|
+
"have",
|
|
36
|
+
"has",
|
|
37
|
+
"had",
|
|
38
|
+
"do",
|
|
39
|
+
"does",
|
|
40
|
+
"did",
|
|
41
|
+
"get",
|
|
42
|
+
"give",
|
|
43
|
+
"go",
|
|
44
|
+
"use",
|
|
45
|
+
"see",
|
|
46
|
+
"help",
|
|
47
|
+
"work",
|
|
48
|
+
"build",
|
|
49
|
+
"show",
|
|
50
|
+
"need",
|
|
51
|
+
"want",
|
|
52
|
+
"make",
|
|
53
|
+
"let",
|
|
54
|
+
// pronouns / determiners
|
|
55
|
+
"it",
|
|
56
|
+
"its",
|
|
57
|
+
"this",
|
|
58
|
+
"that",
|
|
59
|
+
"these",
|
|
60
|
+
"those",
|
|
61
|
+
"we",
|
|
62
|
+
"our",
|
|
63
|
+
"you",
|
|
64
|
+
"your",
|
|
65
|
+
"they",
|
|
66
|
+
"their",
|
|
67
|
+
"us",
|
|
68
|
+
"who",
|
|
69
|
+
"what",
|
|
70
|
+
"which",
|
|
71
|
+
"how",
|
|
72
|
+
// common English fillers that leak into JDs
|
|
73
|
+
"no",
|
|
74
|
+
"not",
|
|
75
|
+
"all",
|
|
76
|
+
"any",
|
|
77
|
+
"also",
|
|
78
|
+
"more",
|
|
79
|
+
"well",
|
|
80
|
+
"very",
|
|
81
|
+
"highly",
|
|
82
|
+
"across",
|
|
83
|
+
"over",
|
|
84
|
+
"under",
|
|
85
|
+
"within",
|
|
86
|
+
"about",
|
|
87
|
+
"out",
|
|
88
|
+
"up",
|
|
89
|
+
"down",
|
|
90
|
+
"new",
|
|
91
|
+
"if",
|
|
92
|
+
"so",
|
|
93
|
+
"such",
|
|
94
|
+
"both",
|
|
95
|
+
"each",
|
|
96
|
+
"one",
|
|
97
|
+
"many",
|
|
98
|
+
"only",
|
|
99
|
+
// JD/HR boilerplate — never skills
|
|
100
|
+
"years",
|
|
101
|
+
"year",
|
|
102
|
+
"experience",
|
|
103
|
+
"required",
|
|
104
|
+
"requirement",
|
|
105
|
+
"requirements",
|
|
106
|
+
"preferred",
|
|
107
|
+
"role",
|
|
108
|
+
"degree",
|
|
109
|
+
"practices",
|
|
110
|
+
"best",
|
|
111
|
+
"skills",
|
|
112
|
+
"team",
|
|
113
|
+
"field",
|
|
114
|
+
"related",
|
|
115
|
+
"relevant",
|
|
116
|
+
"desired",
|
|
117
|
+
"strong",
|
|
118
|
+
"solid",
|
|
119
|
+
"good",
|
|
120
|
+
"first",
|
|
121
|
+
"based",
|
|
122
|
+
"day",
|
|
123
|
+
"week",
|
|
124
|
+
"month",
|
|
125
|
+
"time",
|
|
126
|
+
"fast",
|
|
127
|
+
"open",
|
|
128
|
+
"dynamic"
|
|
129
|
+
]);
|
|
130
|
+
function normalizeWhitespace(text) {
|
|
131
|
+
return text.replace(/\r\n?/g, "\n").replace(/\s+/g, " ").trim();
|
|
132
|
+
}
|
|
133
|
+
function normalizeForComparison(text) {
|
|
134
|
+
return normalizeWhitespace(text).normalize("NFKC").toLowerCase();
|
|
135
|
+
}
|
|
136
|
+
function splitLines(text) {
|
|
137
|
+
return text.replace(/\r\n?/g, "\n").split("\n").map((line) => line.trim()).filter(Boolean);
|
|
138
|
+
}
|
|
139
|
+
var TECH_TOKEN_RE = /[a-z0-9][a-z0-9.#+\-/]*[a-z0-9#+]/g;
|
|
140
|
+
function tokenize(text) {
|
|
141
|
+
const normalized = normalizeForComparison(text);
|
|
142
|
+
return (normalized.match(TECH_TOKEN_RE) ?? []).filter(
|
|
143
|
+
(t) => /[a-z]/.test(t) && !STOP_WORDS.has(t)
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
function escapeRegExp(input) {
|
|
147
|
+
return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
148
|
+
}
|
|
149
|
+
function unique(values) {
|
|
150
|
+
const seen = /* @__PURE__ */ new Set();
|
|
151
|
+
const output = [];
|
|
152
|
+
for (const value of values) {
|
|
153
|
+
const lower = value.toLowerCase();
|
|
154
|
+
if (!seen.has(lower)) {
|
|
155
|
+
seen.add(lower);
|
|
156
|
+
output.push(value);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return output;
|
|
160
|
+
}
|
|
161
|
+
function clamp(value, min, max) {
|
|
162
|
+
return Math.min(Math.max(value, min), max);
|
|
163
|
+
}
|
|
164
|
+
function countFrequencies(values) {
|
|
165
|
+
const counts = {};
|
|
166
|
+
for (const value of values) {
|
|
167
|
+
counts[value] = (counts[value] ?? 0) + 1;
|
|
168
|
+
}
|
|
169
|
+
return counts;
|
|
170
|
+
}
|
|
171
|
+
function containsTableLikeStructure(text) {
|
|
172
|
+
const lines = splitLines(text);
|
|
173
|
+
let tableLines = 0;
|
|
174
|
+
for (const line of lines) {
|
|
175
|
+
const hasPipeColumns = line.includes("|") && line.split("|").length >= 3;
|
|
176
|
+
const hasTabColumns = /\t.+\t/.test(line);
|
|
177
|
+
const hasAlignedSpaces = /( {3,})(\S+)( {3,}\S+)/.test(line);
|
|
178
|
+
if (hasPipeColumns || hasTabColumns || hasAlignedSpaces) {
|
|
179
|
+
tableLines += 1;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return tableLines >= 2;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// src/utils/skills.ts
|
|
186
|
+
var aliasIndexCache = /* @__PURE__ */ new WeakMap();
|
|
187
|
+
function getAliasIndex(aliases) {
|
|
188
|
+
let index = aliasIndexCache.get(aliases);
|
|
189
|
+
if (!index) {
|
|
190
|
+
index = /* @__PURE__ */ new Map();
|
|
191
|
+
for (const [canonical, aliasList] of Object.entries(aliases)) {
|
|
192
|
+
const lower = canonical.toLowerCase();
|
|
193
|
+
index.set(lower, lower);
|
|
194
|
+
for (const alias of aliasList) {
|
|
195
|
+
index.set(alias.toLowerCase(), lower);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
aliasIndexCache.set(aliases, index);
|
|
199
|
+
}
|
|
200
|
+
return index;
|
|
201
|
+
}
|
|
202
|
+
function normalizeSkill(skill, aliases) {
|
|
203
|
+
const normalized = skill.trim().toLowerCase();
|
|
204
|
+
return getAliasIndex(aliases).get(normalized) ?? normalized;
|
|
205
|
+
}
|
|
206
|
+
function normalizeSkills(skills, aliases) {
|
|
207
|
+
return unique(skills.map((skill) => normalizeSkill(skill, aliases)));
|
|
208
|
+
}
|
|
209
|
+
function deriveSkillAliases(registry) {
|
|
210
|
+
const aliases = {};
|
|
211
|
+
for (const entry of registry) {
|
|
212
|
+
aliases[entry.canonical] = entry.aliases;
|
|
213
|
+
}
|
|
214
|
+
return aliases;
|
|
215
|
+
}
|
|
216
|
+
function buildCategoryIndex(registry) {
|
|
217
|
+
const index = /* @__PURE__ */ new Map();
|
|
218
|
+
for (const entry of registry) {
|
|
219
|
+
index.set(entry.canonical.toLowerCase(), entry.category);
|
|
220
|
+
}
|
|
221
|
+
return index;
|
|
222
|
+
}
|
|
223
|
+
function mergeKeywordRegistries(base, overrides) {
|
|
224
|
+
const byCanonical = /* @__PURE__ */ new Map();
|
|
225
|
+
for (const entry of base) byCanonical.set(entry.canonical.toLowerCase(), entry);
|
|
226
|
+
for (const entry of overrides) byCanonical.set(entry.canonical.toLowerCase(), entry);
|
|
227
|
+
return [...byCanonical.values()];
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// src/profiles/index.ts
|
|
231
|
+
var defaultKeywordRegistry = [
|
|
232
|
+
// languages / frameworks
|
|
233
|
+
// ponytail: "node" split from javascript — Node.js runtime !== JS language
|
|
234
|
+
{ canonical: "javascript", aliases: ["js"], category: "technical" },
|
|
235
|
+
{ canonical: "node", aliases: ["node.js", "nodejs"], category: "technical" },
|
|
236
|
+
{ canonical: "typescript", aliases: ["ts"], category: "technical" },
|
|
237
|
+
{ canonical: "react", aliases: ["reactjs", "react.js"], category: "technical" },
|
|
238
|
+
{ canonical: "angular", aliases: ["angularjs"], category: "technical" },
|
|
239
|
+
{ canonical: "vue", aliases: ["vue.js", "vuejs"], category: "technical" },
|
|
240
|
+
{ canonical: "svelte", aliases: [], category: "technical" },
|
|
241
|
+
{ canonical: "next.js", aliases: ["nextjs"], category: "technical" },
|
|
242
|
+
{ canonical: "c++", aliases: ["cpp"], category: "technical" },
|
|
243
|
+
{ canonical: "c#", aliases: ["csharp", ".net"], category: "technical" },
|
|
244
|
+
{ canonical: "java", aliases: [], category: "technical" },
|
|
245
|
+
{ canonical: "python", aliases: ["py"], category: "technical" },
|
|
246
|
+
{ canonical: "go", aliases: ["golang"], category: "technical" },
|
|
247
|
+
{ canonical: "rust", aliases: [], category: "technical" },
|
|
248
|
+
{ canonical: "ruby", aliases: ["ruby on rails", "rails"], category: "technical" },
|
|
249
|
+
{ canonical: "php", aliases: [], category: "technical" },
|
|
250
|
+
{ canonical: "swift", aliases: [], category: "technical" },
|
|
251
|
+
{ canonical: "kotlin", aliases: [], category: "technical" },
|
|
252
|
+
{ canonical: "scala", aliases: [], category: "technical" },
|
|
253
|
+
{ canonical: "html", aliases: ["html5"], category: "technical" },
|
|
254
|
+
{ canonical: "css", aliases: ["css3"], category: "technical" },
|
|
255
|
+
{ canonical: "ios development", aliases: ["ios"], category: "technical" },
|
|
256
|
+
{ canonical: "android development", aliases: ["android"], category: "technical" },
|
|
257
|
+
{ canonical: "react native", aliases: [], category: "technical" },
|
|
258
|
+
{ canonical: "flutter", aliases: [], category: "technical" },
|
|
259
|
+
{ canonical: "machine learning", aliases: ["ml"], category: "technical" },
|
|
260
|
+
{ canonical: "deep learning", aliases: [], category: "technical" },
|
|
261
|
+
{ canonical: "natural language processing", aliases: ["nlp"], category: "technical" },
|
|
262
|
+
// tools / platforms / infra
|
|
263
|
+
{ canonical: "sql", aliases: ["postgres", "mysql", "sqlite"], category: "tool" },
|
|
264
|
+
{ canonical: "graphql", aliases: ["gql"], category: "tool" },
|
|
265
|
+
{ canonical: "aws", aliases: ["amazon web services"], category: "tool" },
|
|
266
|
+
{ canonical: "azure", aliases: ["microsoft azure"], category: "tool" },
|
|
267
|
+
{ canonical: "gcp", aliases: ["google cloud", "google cloud platform"], category: "tool" },
|
|
268
|
+
{ canonical: "docker", aliases: ["containers"], category: "tool" },
|
|
269
|
+
{ canonical: "kubernetes", aliases: ["k8s"], category: "tool" },
|
|
270
|
+
{ canonical: "terraform", aliases: [], category: "tool" },
|
|
271
|
+
{ canonical: "ansible", aliases: [], category: "tool" },
|
|
272
|
+
{ canonical: "jenkins", aliases: [], category: "tool" },
|
|
273
|
+
{ canonical: "git", aliases: ["github", "gitlab"], category: "tool" },
|
|
274
|
+
{ canonical: "jira", aliases: [], category: "tool" },
|
|
275
|
+
{ canonical: "confluence", aliases: [], category: "tool" },
|
|
276
|
+
{ canonical: "pytorch", aliases: ["torch"], category: "tool" },
|
|
277
|
+
{ canonical: "tensorflow", aliases: ["tf"], category: "tool" },
|
|
278
|
+
{ canonical: "scikit-learn", aliases: ["sklearn"], category: "tool" },
|
|
279
|
+
{ canonical: "pandas", aliases: [], category: "tool" },
|
|
280
|
+
{ canonical: "numpy", aliases: [], category: "tool" },
|
|
281
|
+
{ canonical: "fastapi", aliases: [], category: "tool" },
|
|
282
|
+
{ canonical: "flask", aliases: [], category: "tool" },
|
|
283
|
+
{ canonical: "django", aliases: [], category: "tool" },
|
|
284
|
+
{ canonical: "kafka", aliases: [], category: "tool" },
|
|
285
|
+
{ canonical: "redis", aliases: [], category: "tool" },
|
|
286
|
+
{ canonical: "elasticsearch", aliases: ["elastic"], category: "tool" },
|
|
287
|
+
{ canonical: "spark", aliases: ["apache spark"], category: "tool" },
|
|
288
|
+
{ canonical: "tableau", aliases: [], category: "tool" },
|
|
289
|
+
{ canonical: "power bi", aliases: ["powerbi"], category: "tool" },
|
|
290
|
+
{ canonical: "excel", aliases: ["microsoft excel", "ms excel"], category: "tool" },
|
|
291
|
+
{ canonical: "salesforce", aliases: [], category: "tool" },
|
|
292
|
+
{ canonical: "hubspot", aliases: [], category: "tool" },
|
|
293
|
+
{ canonical: "sap", aliases: [], category: "tool" },
|
|
294
|
+
{ canonical: "quickbooks", aliases: [], category: "tool" },
|
|
295
|
+
{ canonical: "workday", aliases: [], category: "tool" },
|
|
296
|
+
{ canonical: "zendesk", aliases: [], category: "tool" },
|
|
297
|
+
{ canonical: "servicenow", aliases: [], category: "tool" },
|
|
298
|
+
{ canonical: "figma", aliases: [], category: "tool" },
|
|
299
|
+
{ canonical: "photoshop", aliases: ["adobe photoshop"], category: "tool" },
|
|
300
|
+
{ canonical: "illustrator", aliases: ["adobe illustrator"], category: "tool" },
|
|
301
|
+
{ canonical: "autocad", aliases: [], category: "tool" },
|
|
302
|
+
// engineering concepts
|
|
303
|
+
{ canonical: "accessibility", aliases: ["a11y"], category: "concept" },
|
|
304
|
+
{ canonical: "frontend", aliases: ["front-end"], category: "concept" },
|
|
305
|
+
{ canonical: "backend", aliases: ["back-end"], category: "concept" },
|
|
306
|
+
{ canonical: "security", aliases: ["cybersecurity"], category: "concept" },
|
|
307
|
+
{ canonical: "testing", aliases: ["unittest", "pytest"], category: "concept" },
|
|
308
|
+
{ canonical: "microservices", aliases: [], category: "concept" },
|
|
309
|
+
{ canonical: "agile", aliases: ["scrum"], category: "concept" },
|
|
310
|
+
{ canonical: "kanban", aliases: [], category: "concept" },
|
|
311
|
+
{ canonical: "blockchain", aliases: [], category: "concept" },
|
|
312
|
+
{ canonical: "devops", aliases: [], category: "concept" },
|
|
313
|
+
{ canonical: "ci/cd", aliases: ["continuous integration", "continuous deployment"], category: "concept" },
|
|
314
|
+
{ canonical: "rest api", aliases: ["restful api", "rest apis"], category: "concept" },
|
|
315
|
+
{ canonical: "design patterns", aliases: [], category: "concept" },
|
|
316
|
+
{ canonical: "data structures", aliases: [], category: "concept" },
|
|
317
|
+
{ canonical: "algorithms", aliases: [], category: "concept" },
|
|
318
|
+
{ canonical: "cloud computing", aliases: [], category: "concept" },
|
|
319
|
+
{ canonical: "system design", aliases: [], category: "concept" },
|
|
320
|
+
{ canonical: "tdd", aliases: ["test driven development", "test-driven development"], category: "concept" },
|
|
321
|
+
{ canonical: "ux design", aliases: ["user experience"], category: "concept" },
|
|
322
|
+
{ canonical: "ui design", aliases: ["user interface design"], category: "concept" },
|
|
323
|
+
{ canonical: "project management", aliases: [], category: "concept" },
|
|
324
|
+
{ canonical: "change management", aliases: [], category: "concept" },
|
|
325
|
+
{ canonical: "risk management", aliases: [], category: "concept" },
|
|
326
|
+
{ canonical: "quality assurance", aliases: ["qa"], category: "concept" },
|
|
327
|
+
// product / data domain
|
|
328
|
+
{ canonical: "roadmap", aliases: [], category: "domain" },
|
|
329
|
+
{ canonical: "stakeholder management", aliases: [], category: "domain" },
|
|
330
|
+
{ canonical: "prioritization", aliases: [], category: "domain" },
|
|
331
|
+
{ canonical: "a/b testing", aliases: ["ab testing"], category: "domain" },
|
|
332
|
+
{ canonical: "analytics", aliases: [], category: "domain" },
|
|
333
|
+
{ canonical: "statistics", aliases: ["stats"], category: "domain" },
|
|
334
|
+
{ canonical: "data visualization", aliases: [], category: "domain" },
|
|
335
|
+
// finance / accounting domain
|
|
336
|
+
{ canonical: "financial analysis", aliases: [], category: "domain" },
|
|
337
|
+
{ canonical: "budgeting", aliases: [], category: "domain" },
|
|
338
|
+
{ canonical: "forecasting", aliases: [], category: "domain" },
|
|
339
|
+
{ canonical: "bookkeeping", aliases: [], category: "domain" },
|
|
340
|
+
{ canonical: "accounts payable", aliases: ["ap"], category: "domain" },
|
|
341
|
+
{ canonical: "accounts receivable", aliases: ["ar"], category: "domain" },
|
|
342
|
+
{ canonical: "payroll", aliases: [], category: "domain" },
|
|
343
|
+
{ canonical: "auditing", aliases: ["audit"], category: "domain" },
|
|
344
|
+
{ canonical: "tax preparation", aliases: [], category: "domain" },
|
|
345
|
+
{ canonical: "gaap", aliases: [], category: "domain" },
|
|
346
|
+
// sales / account management domain
|
|
347
|
+
{ canonical: "lead generation", aliases: [], category: "domain" },
|
|
348
|
+
{ canonical: "account management", aliases: [], category: "domain" },
|
|
349
|
+
{ canonical: "crm", aliases: ["customer relationship management"], category: "domain" },
|
|
350
|
+
{ canonical: "sales pipeline", aliases: [], category: "domain" },
|
|
351
|
+
{ canonical: "cold calling", aliases: [], category: "domain" },
|
|
352
|
+
{ canonical: "upselling", aliases: ["cross-selling"], category: "domain" },
|
|
353
|
+
{ canonical: "customer retention", aliases: [], category: "domain" },
|
|
354
|
+
// human resources domain
|
|
355
|
+
{ canonical: "recruiting", aliases: ["talent acquisition"], category: "domain" },
|
|
356
|
+
{ canonical: "onboarding", aliases: [], category: "domain" },
|
|
357
|
+
{ canonical: "employee relations", aliases: [], category: "domain" },
|
|
358
|
+
{ canonical: "benefits administration", aliases: [], category: "domain" },
|
|
359
|
+
{ canonical: "performance management", aliases: [], category: "domain" },
|
|
360
|
+
// healthcare domain
|
|
361
|
+
{ canonical: "patient care", aliases: [], category: "domain" },
|
|
362
|
+
{ canonical: "clinical documentation", aliases: [], category: "domain" },
|
|
363
|
+
{ canonical: "hipaa", aliases: [], category: "domain" },
|
|
364
|
+
{ canonical: "electronic health records", aliases: ["ehr", "emr"], category: "domain" },
|
|
365
|
+
{ canonical: "medical billing", aliases: [], category: "domain" },
|
|
366
|
+
// legal domain
|
|
367
|
+
{ canonical: "contract review", aliases: [], category: "domain" },
|
|
368
|
+
{ canonical: "legal research", aliases: [], category: "domain" },
|
|
369
|
+
{ canonical: "litigation", aliases: [], category: "domain" },
|
|
370
|
+
{ canonical: "regulatory compliance", aliases: ["compliance"], category: "domain" },
|
|
371
|
+
{ canonical: "due diligence", aliases: [], category: "domain" },
|
|
372
|
+
// education domain
|
|
373
|
+
{ canonical: "curriculum development", aliases: [], category: "domain" },
|
|
374
|
+
{ canonical: "lesson planning", aliases: [], category: "domain" },
|
|
375
|
+
{ canonical: "classroom management", aliases: [], category: "domain" },
|
|
376
|
+
{ canonical: "instructional design", aliases: [], category: "domain" },
|
|
377
|
+
// operations / supply chain domain
|
|
378
|
+
{ canonical: "supply chain management", aliases: ["supply chain"], category: "domain" },
|
|
379
|
+
{ canonical: "inventory management", aliases: [], category: "domain" },
|
|
380
|
+
{ canonical: "procurement", aliases: [], category: "domain" },
|
|
381
|
+
{ canonical: "vendor management", aliases: [], category: "domain" },
|
|
382
|
+
{ canonical: "logistics", aliases: [], category: "domain" },
|
|
383
|
+
// customer service domain
|
|
384
|
+
{ canonical: "customer support", aliases: ["customer service"], category: "domain" },
|
|
385
|
+
{ canonical: "technical support", aliases: [], category: "domain" },
|
|
386
|
+
{ canonical: "conflict resolution", aliases: [], category: "domain" },
|
|
387
|
+
// soft skills
|
|
388
|
+
{ canonical: "communication", aliases: [], category: "soft" },
|
|
389
|
+
{ canonical: "leadership", aliases: [], category: "soft" },
|
|
390
|
+
{ canonical: "teamwork", aliases: ["collaboration"], category: "soft" },
|
|
391
|
+
{ canonical: "problem solving", aliases: ["problem-solving"], category: "soft" },
|
|
392
|
+
{ canonical: "adaptability", aliases: ["flexibility"], category: "soft" },
|
|
393
|
+
{ canonical: "time management", aliases: [], category: "soft" },
|
|
394
|
+
{ canonical: "critical thinking", aliases: [], category: "soft" },
|
|
395
|
+
{ canonical: "creativity", aliases: [], category: "soft" },
|
|
396
|
+
{ canonical: "attention to detail", aliases: [], category: "soft" },
|
|
397
|
+
{ canonical: "decision making", aliases: ["decision-making"], category: "soft" },
|
|
398
|
+
{ canonical: "emotional intelligence", aliases: [], category: "soft" },
|
|
399
|
+
{ canonical: "negotiation", aliases: [], category: "soft" },
|
|
400
|
+
{ canonical: "organization", aliases: ["organizational skills"], category: "soft" },
|
|
401
|
+
{ canonical: "public speaking", aliases: ["presentation skills"], category: "soft" },
|
|
402
|
+
{ canonical: "mentoring", aliases: ["coaching"], category: "soft" },
|
|
403
|
+
{ canonical: "interpersonal skills", aliases: [], category: "soft" },
|
|
404
|
+
{ canonical: "work ethic", aliases: [], category: "soft" },
|
|
405
|
+
// marketing
|
|
406
|
+
{ canonical: "seo", aliases: ["search engine optimization"], category: "marketing" },
|
|
407
|
+
{ canonical: "branding", aliases: ["brand strategy"], category: "marketing" },
|
|
408
|
+
{ canonical: "campaign management", aliases: [], category: "marketing" },
|
|
409
|
+
{ canonical: "content marketing", aliases: [], category: "marketing" },
|
|
410
|
+
{ canonical: "social media marketing", aliases: ["social media"], category: "marketing" },
|
|
411
|
+
{ canonical: "email marketing", aliases: [], category: "marketing" },
|
|
412
|
+
{ canonical: "digital marketing", aliases: [], category: "marketing" },
|
|
413
|
+
{ canonical: "copywriting", aliases: [], category: "marketing" },
|
|
414
|
+
{ canonical: "market research", aliases: [], category: "marketing" },
|
|
415
|
+
{ canonical: "ppc", aliases: ["pay-per-click", "google ads"], category: "marketing" },
|
|
416
|
+
{ canonical: "conversion rate optimization", aliases: ["cro"], category: "marketing" },
|
|
417
|
+
{ canonical: "public relations", aliases: ["pr"], category: "marketing" }
|
|
418
|
+
];
|
|
419
|
+
var defaultSkillAliases = deriveSkillAliases(defaultKeywordRegistry);
|
|
420
|
+
var softwareEngineerProfile = {
|
|
421
|
+
name: "software-engineer",
|
|
422
|
+
mandatorySkills: ["javascript", "typescript", "react", "node"],
|
|
423
|
+
optionalSkills: ["graphql", "sql", "docker"],
|
|
424
|
+
minExperience: 3
|
|
425
|
+
};
|
|
426
|
+
var dataScientistProfile = {
|
|
427
|
+
name: "data-scientist",
|
|
428
|
+
mandatorySkills: ["python", "sql", "statistics"],
|
|
429
|
+
optionalSkills: ["pandas", "numpy", "pytorch", "tensorflow"],
|
|
430
|
+
minExperience: 2
|
|
431
|
+
};
|
|
432
|
+
var productManagerProfile = {
|
|
433
|
+
name: "product-manager",
|
|
434
|
+
mandatorySkills: ["roadmap", "stakeholder management", "prioritization"],
|
|
435
|
+
optionalSkills: ["a/b testing", "analytics", "sql"],
|
|
436
|
+
minExperience: 3
|
|
437
|
+
};
|
|
438
|
+
var defaultProfiles = [
|
|
439
|
+
softwareEngineerProfile,
|
|
440
|
+
dataScientistProfile,
|
|
441
|
+
productManagerProfile
|
|
442
|
+
];
|
|
443
|
+
|
|
444
|
+
export { STOP_WORDS, buildCategoryIndex, clamp, containsTableLikeStructure, countFrequencies, defaultKeywordRegistry, defaultProfiles, defaultSkillAliases, deriveSkillAliases, escapeRegExp, mergeKeywordRegistries, normalizeForComparison, normalizeSkill, normalizeSkills, normalizeWhitespace, softwareEngineerProfile, splitLines, tokenize, unique };
|
|
445
|
+
//# sourceMappingURL=chunk-ZJ5E4H7Z.mjs.map
|
|
446
|
+
//# sourceMappingURL=chunk-ZJ5E4H7Z.mjs.map
|