@pranavraut033/ats-checker 1.1.1 → 1.3.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
@@ -17,8 +17,15 @@ 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
- - **Configurable** — adjust weights, add skill aliases, define custom penalty rules
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
28
+ - **PDF input** — optional `/pdf` subpath extracts resume text from a PDF buffer (requires `pdfjs-dist` peer dep)
22
29
  - **Built-in profiles** — software engineer, data scientist, product manager out of the box
23
30
 
24
31
  ---
@@ -78,6 +85,11 @@ console.log(result.suggestions); // ["Add GraphQL to your skills section",
78
85
  | `matchedKeywords` | `string[]` | JD keywords present in the resume (sorted) |
79
86
  | `missingKeywords` | `string[]` | JD keywords absent from the resume (sorted) |
80
87
  | `overusedKeywords` | `string[]` | Keywords exceeding density threshold (sorted) |
88
+ | `keywordsByCategory` | `Record<KeywordCategory, {matched, missing}>` | Matched/missing keywords grouped by category |
89
+ | `keywordWeights` | `KeywordWeight[]` | Per-keyword JD importance (`jdWeight`) and resume usage (`resumeWeight`) |
90
+ | `achievementStrength` | `{ strong: number; weak: number }` | Count of resume bullets classified as strong vs weak achievement statements |
91
+ | `matchedLanguages` | `ParsedLanguage[]` | JD-required languages the resume meets or exceeds in proficiency |
92
+ | `missingLanguages` | `ParsedLanguage[]` | JD-required languages absent or below the required proficiency |
81
93
  | `suggestions` | `string[]` | Deterministic improvement recommendations |
82
94
  | `warnings` | `string[]` | Parse warnings and section alerts |
83
95
  | `experienceGap` | `number` | Years below JD minimum; `0` when met |
@@ -88,6 +100,8 @@ console.log(result.suggestions); // ["Add GraphQL to your skills section",
88
100
  **Scoring formula:**
89
101
  `score = skills×0.30 + experience×0.30 + keywords×0.25 + education×0.15` → clamped to 0–100 → rule penalties subtracted.
90
102
 
103
+ 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.
104
+
91
105
  ---
92
106
 
93
107
  ## Configuration
@@ -105,6 +119,9 @@ const result = analyzeResume({
105
119
  // Additional skill synonyms merged over built-in defaults
106
120
  skillAliases: { javascript: ["js", "ecmascript"] },
107
121
 
122
+ // Categorized keyword/alias entries; merges over the default registry by canonical term
123
+ keywordRegistry: [{ canonical: "rust", aliases: ["rustlang"], category: "technical" }],
124
+
108
125
  // Industry profile: sets mandatory/optional skills and minExperience
109
126
  profile: {
110
127
  mandatorySkills: ["javascript", "react"],
@@ -155,15 +172,69 @@ See [Configuration docs](https://pranavraut033.github.io/ats-checker/docs/config
155
172
 
156
173
  ---
157
174
 
158
- ## Built-in Skill Aliases
175
+ ## Keyword Registry, Categories & Aliases
176
+
177
+ 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.
178
+
179
+ ```typescript
180
+ import { defaultKeywordRegistry, defaultSkillAliases } from "@pranavraut033/ats-checker";
181
+ // defaultKeywordRegistry: [{ canonical: "javascript", aliases: ["js"], category: "technical" }, ...]
182
+ // defaultSkillAliases: { javascript: ["js"], node: ["node.js", "nodejs"], ... } (derived, back-compat)
183
+ ```
184
+
185
+ Extend or override the registry via `config.keywordRegistry` — entries merge over the defaults by canonical term:
186
+
187
+ ```typescript
188
+ const result = analyzeResume({
189
+ resumeText: "...",
190
+ jobDescription: "...",
191
+ config: {
192
+ keywordRegistry: [
193
+ { canonical: "rust", aliases: ["rustlang"], category: "technical" },
194
+ { canonical: "javascript", aliases: ["js", "ecmascript"], category: "technical" }, // overrides default
195
+ ],
196
+ },
197
+ });
198
+
199
+ console.log(result.keywordsByCategory.technical); // { matched: [...], missing: [...] }
200
+ console.log(result.keywordWeights); // [{ term, category, jdWeight, resumeWeight, importance }, ...]
201
+ ```
202
+
203
+ You can still pass `config.skillAliases` for a flat override — it merges on top of the registry-derived aliases.
204
+
205
+ ## Multi-language Keyword Packs
206
+
207
+ 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.
208
+
209
+ ```typescript
210
+ import de from "@pranavraut033/ats-checker/de";
211
+ import { analyzeResume } from "@pranavraut033/ats-checker";
212
+
213
+ const result = analyzeResume({
214
+ resumeText: "...", // e.g. a German-language resume
215
+ jobDescription: "...",
216
+ config: { keywordRegistry: de },
217
+ });
218
+ ```
219
+
220
+ Available packs: `/en` (the default registry) and `/de` (seed set — grows on demand). Each default-exports a `KeywordRegistry`.
221
+
222
+ ## Language Requirements
159
223
 
160
- Common tech synonyms are pre-loaded so `js` matches `javascript`, `k8s` matches `kubernetes`, etc. Extend or override via `config.skillAliases`.
224
+ 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.
161
225
 
162
226
  ```typescript
163
- import { defaultSkillAliases } from "@pranavraut033/ats-checker";
164
- // { javascript: ["js"], node: ["node.js", "nodejs"], typescript: ["ts"], ... }
227
+ const result = analyzeResume({
228
+ resumeText: "Languages: German (C1), English (native)",
229
+ jobDescription: "German (B2) required for this role.",
230
+ });
231
+
232
+ console.log(result.matchedLanguages); // [{ name: "german", level: "b2", levelRank: 4 }]
233
+ console.log(result.missingLanguages); // []
165
234
  ```
166
235
 
236
+ 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`.
237
+
167
238
  ---
168
239
 
169
240
  ## Built-in Profiles
@@ -184,6 +255,41 @@ const result = analyzeResume({
184
255
 
185
256
  ---
186
257
 
258
+ ## PDF Input
259
+
260
+ Extract text from a PDF resume before passing it to `analyzeResume`. This uses `pdfjs-dist` as an optional peer dependency — the core library stays zero-dep.
261
+
262
+ ```bash
263
+ npm install pdfjs-dist
264
+ ```
265
+
266
+ ```typescript
267
+ import { extractTextFromPDF } from "@pranavraut033/ats-checker/pdf";
268
+ import { analyzeResume } from "@pranavraut033/ats-checker";
269
+ import { readFileSync } from "fs";
270
+
271
+ const bytes = readFileSync("resume.pdf");
272
+ const resumeText = await extractTextFromPDF(bytes);
273
+
274
+ const result = analyzeResume({ resumeText, jobDescription: "..." });
275
+ ```
276
+
277
+ `extractTextFromPDF` accepts a `Uint8Array` or `ArrayBuffer` and returns a plain `string`. Works in Node.js and the browser (text-layer PDFs only).
278
+
279
+ **Multi-column layouts are handled automatically.** The extractor uses glyph x/y coordinates to detect column boundaries and process each column independently, so a two-column resume parses cleanly without interleaved text.
280
+
281
+ For PDFs that can't be recovered — scanned/image resumes or exports with no text layer — `analyzeResume` surfaces an actionable message in `result.warnings`. Always check it after PDF input:
282
+
283
+ ```typescript
284
+ const result = analyzeResume({ resumeText, jobDescription: "..." });
285
+ if (result.warnings.length) {
286
+ console.warn("Parsing issues:", result.warnings);
287
+ // e.g. "Almost no text was extracted — the resume may be a scanned/image PDF."
288
+ }
289
+ ```
290
+
291
+ ---
292
+
187
293
  ## LLM Integration (deprecated)
188
294
 
189
295
  `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.
@@ -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