@odda-ai/matching-core 1.0.0 → 1.0.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 +790 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/src/ai/AIProvider.d.ts +36 -0
- package/dist/src/ai/AIProvider.js +127 -0
- package/dist/src/ai/adapters/AnthropicAdapter.d.ts +12 -0
- package/dist/src/ai/adapters/AnthropicAdapter.js +38 -0
- package/dist/src/ai/adapters/OllamaAdapter.d.ts +17 -0
- package/dist/src/ai/adapters/OllamaAdapter.js +38 -0
- package/dist/src/ai/adapters/OpenAIAdapter.d.ts +12 -0
- package/dist/src/ai/adapters/OpenAIAdapter.js +47 -0
- package/{src/ai/adapters/index.ts → dist/src/ai/adapters/index.d.ts} +1 -1
- package/dist/src/ai/adapters/index.js +3 -0
- package/dist/src/ai/factory.d.ts +12 -0
- package/dist/src/ai/factory.js +37 -0
- package/{src/ai/index.ts → dist/src/ai/index.d.ts} +1 -1
- package/dist/src/ai/index.js +5 -0
- package/dist/src/ai/registry.d.ts +13 -0
- package/{src/ai/registry.ts → dist/src/ai/registry.js} +7 -8
- package/dist/src/ai/types.d.ts +54 -0
- package/dist/src/ai/types.js +1 -0
- package/dist/src/cv-parser/PDFParserService.d.ts +41 -0
- package/dist/src/cv-parser/PDFParserService.js +136 -0
- package/dist/src/cv-parser/index.js +1 -0
- package/dist/src/cv-parser/types.d.ts +51 -0
- package/dist/src/cv-parser/types.js +1 -0
- package/dist/src/features/ai-cv-resume.service.d.ts +14 -0
- package/dist/src/features/ai-cv-resume.service.js +78 -0
- package/dist/src/features/ai-talent.service.d.ts +18 -0
- package/dist/src/features/ai-talent.service.js +29 -0
- package/dist/src/features/cv-chunking.service.d.ts +140 -0
- package/dist/src/features/cv-chunking.service.js +334 -0
- package/dist/src/features/index.js +3 -0
- package/dist/src/features/job-matcher.service.d.ts +14 -0
- package/dist/src/features/job-matcher.service.js +19 -0
- package/dist/src/features/prompts.d.ts +8 -0
- package/{src/features/prompts.ts → dist/src/features/prompts.js} +6 -21
- package/dist/src/features/system-messages.d.ts +6 -0
- package/{src/features/system-messages.ts → dist/src/features/system-messages.js} +3 -4
- package/dist/src/features/types.d.ts +49 -0
- package/dist/src/features/types.js +15 -0
- package/package.json +8 -9
- package/src/ai/AIProvider.ts +0 -159
- package/src/ai/adapters/AnthropicAdapter.ts +0 -42
- package/src/ai/adapters/OllamaAdapter.ts +0 -42
- package/src/ai/adapters/OpenAIAdapter.ts +0 -53
- package/src/ai/factory.ts +0 -48
- package/src/ai/types.ts +0 -59
- package/src/cv-parser/PDFParserService.ts +0 -160
- package/src/cv-parser/types.ts +0 -58
- package/src/features/ai-cv-resume.service.ts +0 -104
- package/src/features/ai-talent.service.ts +0 -49
- package/src/features/cv-chunking.service.ts +0 -510
- package/src/features/job-matcher.service.ts +0 -41
- package/src/features/types.ts +0 -55
- /package/{src/cv-parser/index.ts → dist/src/cv-parser/index.d.ts} +0 -0
- /package/{src/features/index.ts → dist/src/features/index.d.ts} +0 -0
package/README.md
ADDED
|
@@ -0,0 +1,790 @@
|
|
|
1
|
+
# 🧠 @odda-ai/matching-core
|
|
2
|
+
|
|
3
|
+
Libreria TypeScript modulare per l'integrazione di provider AI multipli, parsing di CV in PDF e analisi intelligente di competenze e profili professionali.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 📋 Indice
|
|
12
|
+
|
|
13
|
+
- [Overview](#-overview)
|
|
14
|
+
- [Features](#-features)
|
|
15
|
+
- [Installazione](#-installazione)
|
|
16
|
+
- [Quick Start](#-quick-start)
|
|
17
|
+
- [Architettura](#-architettura)
|
|
18
|
+
- [AI Providers](#-ai-providers)
|
|
19
|
+
- [CV Parser](#-cv-parser)
|
|
20
|
+
- [Features & Services](#-features--services)
|
|
21
|
+
- [API Reference](#-api-reference)
|
|
22
|
+
- [Esempi Avanzati](#-esempi-avanzati)
|
|
23
|
+
- [Configurazione](#-configurazione)
|
|
24
|
+
- [TypeScript Support](#-typescript-support)
|
|
25
|
+
- [Testing](#-testing)
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 🌟 Overview
|
|
30
|
+
|
|
31
|
+
**@odda-ai/matching-core** è una libreria core che fornisce:
|
|
32
|
+
|
|
33
|
+
- 🤖 **Astrazione unificata** per provider AI multipli (OpenAI, Anthropic, Ollama)
|
|
34
|
+
- 📄 **PDF parsing** ottimizzato per CV e documenti strutturati
|
|
35
|
+
- 🎯 **Analisi CV intelligente** con estrazione automatica di skill, certificazioni, esperienza
|
|
36
|
+
- 📊 **Skill extraction** da job description con matching semantico
|
|
37
|
+
- 🔧 **Type-safe** con pieno supporto TypeScript
|
|
38
|
+
- 🧩 **Modulare** con architettura a plugin per provider AI
|
|
39
|
+
|
|
40
|
+
### Quando usarlo
|
|
41
|
+
|
|
42
|
+
- ✅ Costruire sistemi di recruitment e talent matching
|
|
43
|
+
- ✅ Automatizzare l'analisi di CV e profili professionali
|
|
44
|
+
- ✅ Estrarre competenze da job description
|
|
45
|
+
- ✅ Integrare AI in applicazioni esistenti con provider pluggable
|
|
46
|
+
- ✅ Processare documenti PDF strutturati
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## ✨ Features
|
|
51
|
+
|
|
52
|
+
### 🤖 AI Provider Abstraction
|
|
53
|
+
|
|
54
|
+
- **Multi-provider support**: OpenAI, Anthropic (Claude), Ollama
|
|
55
|
+
- **Factory pattern** per creazione provider
|
|
56
|
+
- **Registry centralizzato** per adapter management
|
|
57
|
+
- **Configurazione per provider** con modelli specifici
|
|
58
|
+
- **Error handling consistente** tra provider
|
|
59
|
+
|
|
60
|
+
### 📄 CV Parsing
|
|
61
|
+
|
|
62
|
+
- **PDF extraction** con pdf-parse
|
|
63
|
+
- **Text cleaning** e normalizzazione
|
|
64
|
+
- **Metadata extraction** automatica
|
|
65
|
+
- **Buffer-based API** per flessibilità
|
|
66
|
+
- **Error handling robusto**
|
|
67
|
+
|
|
68
|
+
### 🎯 CV Analysis
|
|
69
|
+
|
|
70
|
+
- **Structured extraction** di:
|
|
71
|
+
- Personal info (nome, email, phone, location, LinkedIn)
|
|
72
|
+
- Technical skills con proficiency level
|
|
73
|
+
- Soft skills con importanza
|
|
74
|
+
- Work experience con ruoli e durata
|
|
75
|
+
- Education con titoli e istituzioni
|
|
76
|
+
- Certifications con provider e validità
|
|
77
|
+
- Languages con livello di competenza
|
|
78
|
+
- **Seniority assessment** automatico (Junior, Mid, Senior, Lead, Principal)
|
|
79
|
+
- **Years of experience** calcolati
|
|
80
|
+
- **JSON-structured output** per facile integrazione
|
|
81
|
+
|
|
82
|
+
### 📊 Job Matching
|
|
83
|
+
|
|
84
|
+
- **Skill extraction** da job description
|
|
85
|
+
- **Semantic matching** con skill esistenti
|
|
86
|
+
- **Importance weighting** automatico
|
|
87
|
+
- **Categorization** (Technical/Soft, Required/Nice-to-have)
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## 📦 Installazione
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
npm install @odda-ai/matching-core
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Peer Dependencies
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
# Se usi OpenAI
|
|
101
|
+
npm install openai
|
|
102
|
+
|
|
103
|
+
# Se usi Anthropic Claude
|
|
104
|
+
npm install @anthropic-ai/sdk
|
|
105
|
+
|
|
106
|
+
# Se usi Ollama (nessuna dipendenza extra)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## 🚀 Quick Start
|
|
112
|
+
|
|
113
|
+
### 1. Setup Base con OpenAI
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
import { createAIProvider, AiTalentService } from '@odda-ai/matching-core';
|
|
117
|
+
|
|
118
|
+
// Crea provider AI
|
|
119
|
+
const aiProvider = createAIProvider({
|
|
120
|
+
provider: 'openai',
|
|
121
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
122
|
+
model: 'gpt-4-turbo-preview',
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Crea service principale
|
|
126
|
+
const aiTalent = new AiTalentService(aiProvider);
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### 2. Analizza un CV
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
import fs from 'fs';
|
|
133
|
+
|
|
134
|
+
// Leggi CV PDF
|
|
135
|
+
const cvBuffer = fs.readFileSync('./cv-mario-rossi.pdf');
|
|
136
|
+
|
|
137
|
+
// Analizza CV
|
|
138
|
+
const analysis = await aiTalent.cv.analyzeCvResume(cvBuffer);
|
|
139
|
+
|
|
140
|
+
console.log('Nome:', analysis.personalInfo.name);
|
|
141
|
+
console.log('Seniority:', analysis.overallSeniority);
|
|
142
|
+
console.log('Skills:', analysis.technicalSkills.map(s => s.name));
|
|
143
|
+
console.log('Certificazioni:', analysis.certifications.length);
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### 3. Estrai Skills da Job Description
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
const jobDescription = `
|
|
150
|
+
Cerchiamo un Senior Full-Stack Developer con esperienza in:
|
|
151
|
+
- React, TypeScript, Node.js
|
|
152
|
+
- AWS, Docker, Kubernetes
|
|
153
|
+
- PostgreSQL, MongoDB
|
|
154
|
+
`;
|
|
155
|
+
|
|
156
|
+
const existingSkills = [
|
|
157
|
+
'React', 'Vue', 'Angular', 'TypeScript', 'JavaScript',
|
|
158
|
+
'Node.js', 'Python', 'AWS', 'Azure', 'Docker'
|
|
159
|
+
];
|
|
160
|
+
|
|
161
|
+
const jobSkills = await aiTalent.jobs.getSkillsFromJobDescription(
|
|
162
|
+
jobDescription,
|
|
163
|
+
existingSkills
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
console.log('Skills richieste:', jobSkills);
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## 🏗️ Architettura
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
@odda-ai/matching-core/
|
|
175
|
+
├── ai/ # AI Provider Layer
|
|
176
|
+
│ ├── AIProvider.ts # Interface unificata
|
|
177
|
+
│ ├── factory.ts # Factory per creazione provider
|
|
178
|
+
│ ├── registry.ts # Registry adapter
|
|
179
|
+
│ ├── types.ts # Types condivisi
|
|
180
|
+
│ └── adapters/
|
|
181
|
+
│ ├── OpenAIAdapter.ts # Adapter OpenAI
|
|
182
|
+
│ ├── AnthropicAdapter.ts # Adapter Anthropic
|
|
183
|
+
│ └── OllamaAdapter.ts # Adapter Ollama
|
|
184
|
+
│
|
|
185
|
+
├── cv-parser/ # PDF Parsing Layer
|
|
186
|
+
│ ├── PDFParserService.ts # Service parsing PDF
|
|
187
|
+
│ └── types.ts # Types per parsing
|
|
188
|
+
│
|
|
189
|
+
└── features/ # Business Logic Layer
|
|
190
|
+
├── ai-talent.service.ts # Facade principale
|
|
191
|
+
├── ai-cv-resume.service.ts # CV analysis
|
|
192
|
+
├── job-matcher.service.ts # Job skill extraction
|
|
193
|
+
├── cv-chunking.service.ts # CV chunking per RAG
|
|
194
|
+
├── prompts.ts # AI prompts
|
|
195
|
+
├── system-messages.ts # System messages
|
|
196
|
+
└── types.ts # Response types
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Data Flow
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
|
|
203
|
+
│ PDF CV │──────▶│ PDFParser │──────▶│ Raw Text │
|
|
204
|
+
└─────────────┘ └──────────────┘ └─────────────┘
|
|
205
|
+
│
|
|
206
|
+
▼
|
|
207
|
+
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
|
|
208
|
+
│ Structured │◀──────│ AI Provider │◀──────│ Prompts │
|
|
209
|
+
│ Output │ │ (GPT/Claude) │ │ + Context │
|
|
210
|
+
└─────────────┘ └──────────────┘ └─────────────┘
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## 🤖 AI Providers
|
|
216
|
+
|
|
217
|
+
### Supported Providers
|
|
218
|
+
|
|
219
|
+
| Provider | Model Support | Streaming | Vision | Function Calling |
|
|
220
|
+
|----------|---------------|-----------|--------|------------------|
|
|
221
|
+
| **OpenAI** | GPT-3.5, GPT-4, GPT-4o | ✅ | ✅ | ✅ |
|
|
222
|
+
| **Anthropic** | Claude 3 (Opus, Sonnet, Haiku) | ✅ | ✅ | ✅ |
|
|
223
|
+
| **Ollama** | Llama 2, Mistral, CodeLlama, etc. | ✅ | ❌ | ⚠️ Limited |
|
|
224
|
+
|
|
225
|
+
### Factory Pattern
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
import { createAIProvider } from '@odda-ai/matching-core';
|
|
229
|
+
|
|
230
|
+
// OpenAI
|
|
231
|
+
const openai = createAIProvider({
|
|
232
|
+
provider: 'openai',
|
|
233
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
234
|
+
model: 'gpt-4-turbo-preview',
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// Anthropic Claude
|
|
238
|
+
const claude = createAIProvider({
|
|
239
|
+
provider: 'anthropic',
|
|
240
|
+
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
241
|
+
model: 'claude-3-opus-20240229',
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// Ollama (local)
|
|
245
|
+
const ollama = createAIProvider({
|
|
246
|
+
provider: 'ollama',
|
|
247
|
+
baseURL: 'http://localhost:11434',
|
|
248
|
+
model: 'llama2',
|
|
249
|
+
});
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Custom Provider Configuration
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
const provider = createAIProvider({
|
|
256
|
+
provider: 'openai',
|
|
257
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
258
|
+
model: 'gpt-4',
|
|
259
|
+
temperature: 0.3, // Più deterministico
|
|
260
|
+
maxTokens: 4000, // Max token response
|
|
261
|
+
responseFormat: 'json', // JSON mode
|
|
262
|
+
});
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Direct Provider Usage
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
import { AIProvider } from '@odda-ai/matching-core';
|
|
269
|
+
|
|
270
|
+
// Usa direttamente il provider
|
|
271
|
+
const response = await aiProvider.chat([
|
|
272
|
+
{
|
|
273
|
+
role: 'system',
|
|
274
|
+
content: 'You are a helpful assistant.',
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
role: 'user',
|
|
278
|
+
content: 'Explain TypeScript generics.',
|
|
279
|
+
},
|
|
280
|
+
]);
|
|
281
|
+
|
|
282
|
+
console.log(response.content);
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## 📄 CV Parser
|
|
288
|
+
|
|
289
|
+
### Basic Usage
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
import { PDFParserService } from '@odda-ai/matching-core';
|
|
293
|
+
|
|
294
|
+
const parser = new PDFParserService();
|
|
295
|
+
|
|
296
|
+
// Parse PDF
|
|
297
|
+
const result = await parser.parsePDF(pdfBuffer);
|
|
298
|
+
|
|
299
|
+
console.log('Text:', result.text);
|
|
300
|
+
console.log('Pages:', result.numPages);
|
|
301
|
+
console.log('Info:', result.info);
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### Advanced Options
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
const result = await parser.parsePDF(pdfBuffer, {
|
|
308
|
+
max: 50, // Max pages to parse
|
|
309
|
+
version: '1.10.100', // PDF.js version
|
|
310
|
+
verbosity: 0, // Logging level (0-5)
|
|
311
|
+
});
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Error Handling
|
|
315
|
+
|
|
316
|
+
```typescript
|
|
317
|
+
try {
|
|
318
|
+
const result = await parser.parsePDF(buffer);
|
|
319
|
+
} catch (error) {
|
|
320
|
+
if (error.message.includes('Invalid PDF')) {
|
|
321
|
+
console.error('File non è un PDF valido');
|
|
322
|
+
} else {
|
|
323
|
+
console.error('Errore parsing:', error.message);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
## 🎯 Features & Services
|
|
331
|
+
|
|
332
|
+
### AiTalentService
|
|
333
|
+
|
|
334
|
+
Facade principale che orchestra tutti i servizi.
|
|
335
|
+
|
|
336
|
+
```typescript
|
|
337
|
+
import { AiTalentService } from '@odda-ai/matching-core';
|
|
338
|
+
|
|
339
|
+
const service = new AiTalentService(aiProvider);
|
|
340
|
+
|
|
341
|
+
// CV Analysis
|
|
342
|
+
const cvAnalysis = await service.cv.analyzeCvResume(buffer);
|
|
343
|
+
|
|
344
|
+
// CV Chunking (per RAG/Vector DB)
|
|
345
|
+
const chunks = await service.cv.chunkCvAnalysis(cvAnalysis, {
|
|
346
|
+
chunkSize: 500,
|
|
347
|
+
overlap: 50,
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// Job Skills Extraction
|
|
351
|
+
const skills = await service.jobs.getSkillsFromJobDescription(
|
|
352
|
+
jobDescription,
|
|
353
|
+
existingSkills
|
|
354
|
+
);
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### CV Analysis Response Structure
|
|
358
|
+
|
|
359
|
+
```typescript
|
|
360
|
+
interface CvAnalysisResponse {
|
|
361
|
+
// Personal Information
|
|
362
|
+
personalInfo: {
|
|
363
|
+
name: string;
|
|
364
|
+
email?: string;
|
|
365
|
+
phone?: string;
|
|
366
|
+
location?: string;
|
|
367
|
+
linkedIn?: string;
|
|
368
|
+
portfolio?: string;
|
|
369
|
+
github?: string;
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
// Technical Skills
|
|
373
|
+
technicalSkills: Array<{
|
|
374
|
+
name: string;
|
|
375
|
+
proficiency: number; // 0-100
|
|
376
|
+
yearsOfExperience?: number;
|
|
377
|
+
lastUsed?: string;
|
|
378
|
+
}>;
|
|
379
|
+
|
|
380
|
+
// Soft Skills
|
|
381
|
+
softSkills: Array<{
|
|
382
|
+
name: string;
|
|
383
|
+
importance: 'HIGH' | 'MEDIUM' | 'LOW';
|
|
384
|
+
}>;
|
|
385
|
+
|
|
386
|
+
// Work Experience
|
|
387
|
+
workExperience: Array<{
|
|
388
|
+
company: string;
|
|
389
|
+
role: string;
|
|
390
|
+
startDate: string;
|
|
391
|
+
endDate?: string;
|
|
392
|
+
description?: string;
|
|
393
|
+
achievements?: string[];
|
|
394
|
+
}>;
|
|
395
|
+
|
|
396
|
+
// Education
|
|
397
|
+
education: Array<{
|
|
398
|
+
institution: string;
|
|
399
|
+
degree: string;
|
|
400
|
+
field?: string;
|
|
401
|
+
startDate?: string;
|
|
402
|
+
endDate?: string;
|
|
403
|
+
grade?: string;
|
|
404
|
+
}>;
|
|
405
|
+
|
|
406
|
+
// Certifications
|
|
407
|
+
certifications: Array<{
|
|
408
|
+
name: string;
|
|
409
|
+
provider: string;
|
|
410
|
+
obtainedDate?: string;
|
|
411
|
+
expiryDate?: string;
|
|
412
|
+
credentialId?: string;
|
|
413
|
+
}>;
|
|
414
|
+
|
|
415
|
+
// Languages
|
|
416
|
+
languages: Array<{
|
|
417
|
+
name: string;
|
|
418
|
+
proficiency: 'NATIVE' | 'FLUENT' | 'ADVANCED' | 'INTERMEDIATE' | 'BASIC';
|
|
419
|
+
}>;
|
|
420
|
+
|
|
421
|
+
// Summary
|
|
422
|
+
professionalSummary?: string;
|
|
423
|
+
overallSeniority: 'JUNIOR' | 'MID' | 'SENIOR' | 'LEAD' | 'PRINCIPAL';
|
|
424
|
+
yearsOfExperience: number;
|
|
425
|
+
strengths: string[];
|
|
426
|
+
areasForGrowth: string[];
|
|
427
|
+
keyAchievements: string[];
|
|
428
|
+
}
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### CV Chunking
|
|
432
|
+
|
|
433
|
+
Divide il CV analysis in chunk per vector databases o RAG systems.
|
|
434
|
+
|
|
435
|
+
```typescript
|
|
436
|
+
const chunks = await service.cv.chunkCvAnalysis(cvAnalysis, {
|
|
437
|
+
chunkSize: 500, // Caratteri per chunk
|
|
438
|
+
overlap: 50, // Overlap tra chunk
|
|
439
|
+
strategy: 'semantic' // 'semantic' | 'fixed'
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
// Ogni chunk contiene:
|
|
443
|
+
chunks.forEach(chunk => {
|
|
444
|
+
console.log('Text:', chunk.text);
|
|
445
|
+
console.log('Type:', chunk.type); // 'personal' | 'skills' | 'experience' | 'education'
|
|
446
|
+
console.log('Metadata:', chunk.metadata);
|
|
447
|
+
console.log('Embedding:', chunk.embedding); // Optional, se generato
|
|
448
|
+
});
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
### Job Skills Extraction
|
|
452
|
+
|
|
453
|
+
```typescript
|
|
454
|
+
const skills = await service.jobs.getSkillsFromJobDescription(
|
|
455
|
+
jobDescription,
|
|
456
|
+
existingSkillsList
|
|
457
|
+
);
|
|
458
|
+
|
|
459
|
+
// Response structure
|
|
460
|
+
interface JobSkill {
|
|
461
|
+
name: string;
|
|
462
|
+
importance: number; // 0-100
|
|
463
|
+
skillType: 'TECHNICAL' | 'SOFT';
|
|
464
|
+
category: 'REQUIRED' | 'NICE_TO_HAVE';
|
|
465
|
+
context?: string; // Contesto dalla job description
|
|
466
|
+
matchedFrom?: string; // Skill originale matchata
|
|
467
|
+
}
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
---
|
|
471
|
+
|
|
472
|
+
## 📚 API Reference
|
|
473
|
+
|
|
474
|
+
### createAIProvider()
|
|
475
|
+
|
|
476
|
+
```typescript
|
|
477
|
+
function createAIProvider(config: AIProviderConfig): AIProvider
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
**Parameters:**
|
|
481
|
+
- `provider`: `'openai' | 'anthropic' | 'ollama'`
|
|
482
|
+
- `apiKey`: string (required per openai/anthropic)
|
|
483
|
+
- `model`: string (es. 'gpt-4', 'claude-3-opus-20240229')
|
|
484
|
+
- `baseURL?`: string (per Ollama o proxy)
|
|
485
|
+
- `temperature?`: number (0-2)
|
|
486
|
+
- `maxTokens?`: number
|
|
487
|
+
- `responseFormat?`: `'text' | 'json'`
|
|
488
|
+
|
|
489
|
+
### AiTalentService
|
|
490
|
+
|
|
491
|
+
#### Constructor
|
|
492
|
+
|
|
493
|
+
```typescript
|
|
494
|
+
new AiTalentService(aiProvider: AIProvider)
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
#### Methods
|
|
498
|
+
|
|
499
|
+
**cv.analyzeCvResume()**
|
|
500
|
+
```typescript
|
|
501
|
+
async analyzeCvResume(
|
|
502
|
+
pdfBuffer: Buffer,
|
|
503
|
+
options?: AnalyzeResumeOptions
|
|
504
|
+
): Promise<CvAnalysisResponse>
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
**cv.chunkCvAnalysis()**
|
|
508
|
+
```typescript
|
|
509
|
+
async chunkCvAnalysis(
|
|
510
|
+
cvAnalysis: CvAnalysisResponse,
|
|
511
|
+
options?: ChunkingOptions
|
|
512
|
+
): Promise<CvChunk[]>
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
**jobs.getSkillsFromJobDescription()**
|
|
516
|
+
```typescript
|
|
517
|
+
async getSkillsFromJobDescription(
|
|
518
|
+
jobDescription: string,
|
|
519
|
+
existingSkills: string[]
|
|
520
|
+
): Promise<JobSkill[]>
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
### PDFParserService
|
|
524
|
+
|
|
525
|
+
```typescript
|
|
526
|
+
class PDFParserService {
|
|
527
|
+
async parsePDF(
|
|
528
|
+
buffer: Buffer,
|
|
529
|
+
options?: ParseOptions
|
|
530
|
+
): Promise<ParsedPDF>
|
|
531
|
+
}
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
---
|
|
535
|
+
|
|
536
|
+
## 💡 Esempi Avanzati
|
|
537
|
+
|
|
538
|
+
### Batch CV Analysis
|
|
539
|
+
|
|
540
|
+
```typescript
|
|
541
|
+
async function batchAnalyze(cvPaths: string[]) {
|
|
542
|
+
const results = [];
|
|
543
|
+
|
|
544
|
+
for (const path of cvPaths) {
|
|
545
|
+
try {
|
|
546
|
+
const buffer = fs.readFileSync(path);
|
|
547
|
+
const analysis = await aiTalent.cv.analyzeCvResume(buffer);
|
|
548
|
+
|
|
549
|
+
results.push({
|
|
550
|
+
path,
|
|
551
|
+
status: 'success',
|
|
552
|
+
data: analysis,
|
|
553
|
+
});
|
|
554
|
+
} catch (error) {
|
|
555
|
+
results.push({
|
|
556
|
+
path,
|
|
557
|
+
status: 'error',
|
|
558
|
+
error: error.message,
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
return results;
|
|
564
|
+
}
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
### Skill Matching con Tolleranza
|
|
568
|
+
|
|
569
|
+
```typescript
|
|
570
|
+
function fuzzyMatchSkills(
|
|
571
|
+
cvSkills: string[],
|
|
572
|
+
jobSkills: string[],
|
|
573
|
+
threshold: number = 0.8
|
|
574
|
+
) {
|
|
575
|
+
const matches = [];
|
|
576
|
+
|
|
577
|
+
for (const cvSkill of cvSkills) {
|
|
578
|
+
for (const jobSkill of jobSkills) {
|
|
579
|
+
const similarity = calculateSimilarity(cvSkill, jobSkill);
|
|
580
|
+
|
|
581
|
+
if (similarity >= threshold) {
|
|
582
|
+
matches.push({
|
|
583
|
+
cvSkill,
|
|
584
|
+
jobSkill,
|
|
585
|
+
similarity,
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
return matches;
|
|
592
|
+
}
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
### Vector Store Integration
|
|
596
|
+
|
|
597
|
+
```typescript
|
|
598
|
+
import { Pinecone } from '@pinecone-database/pinecone';
|
|
599
|
+
|
|
600
|
+
const pinecone = new Pinecone({ apiKey: process.env.PINECONE_API_KEY });
|
|
601
|
+
const index = pinecone.index('cv-embeddings');
|
|
602
|
+
|
|
603
|
+
// Chunka e genera embeddings
|
|
604
|
+
const chunks = await service.cv.chunkCvAnalysis(cvAnalysis);
|
|
605
|
+
|
|
606
|
+
// Upsert in Pinecone
|
|
607
|
+
const vectors = chunks.map((chunk, i) => ({
|
|
608
|
+
id: `cv-${cvAnalysis.personalInfo.name}-${i}`,
|
|
609
|
+
values: chunk.embedding || [], // Generate with OpenAI embeddings API
|
|
610
|
+
metadata: {
|
|
611
|
+
text: chunk.text,
|
|
612
|
+
type: chunk.type,
|
|
613
|
+
...chunk.metadata,
|
|
614
|
+
},
|
|
615
|
+
}));
|
|
616
|
+
|
|
617
|
+
await index.upsert(vectors);
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
### Switch Provider Dinamico
|
|
621
|
+
|
|
622
|
+
```typescript
|
|
623
|
+
class AIService {
|
|
624
|
+
private providers: Map<string, AIProvider>;
|
|
625
|
+
|
|
626
|
+
constructor() {
|
|
627
|
+
this.providers = new Map([
|
|
628
|
+
['fast', createAIProvider({ provider: 'openai', model: 'gpt-3.5-turbo' })],
|
|
629
|
+
['accurate', createAIProvider({ provider: 'openai', model: 'gpt-4' })],
|
|
630
|
+
['local', createAIProvider({ provider: 'ollama', model: 'llama2' })],
|
|
631
|
+
]);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
async analyze(cv: Buffer, mode: 'fast' | 'accurate' | 'local') {
|
|
635
|
+
const provider = this.providers.get(mode)!;
|
|
636
|
+
const service = new AiTalentService(provider);
|
|
637
|
+
return service.cv.analyzeCvResume(cv);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
---
|
|
643
|
+
|
|
644
|
+
## ⚙️ Configurazione
|
|
645
|
+
|
|
646
|
+
### Environment Variables
|
|
647
|
+
|
|
648
|
+
```bash
|
|
649
|
+
# OpenAI
|
|
650
|
+
OPENAI_API_KEY=sk-...
|
|
651
|
+
OPENAI_MODEL=gpt-4-turbo-preview
|
|
652
|
+
|
|
653
|
+
# Anthropic
|
|
654
|
+
ANTHROPIC_API_KEY=sk-ant-...
|
|
655
|
+
ANTHROPIC_MODEL=claude-3-opus-20240229
|
|
656
|
+
|
|
657
|
+
# Ollama
|
|
658
|
+
OLLAMA_BASE_URL=http://localhost:11434
|
|
659
|
+
OLLAMA_MODEL=llama2
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
### Configuration File
|
|
663
|
+
|
|
664
|
+
```typescript
|
|
665
|
+
// config/ai.config.ts
|
|
666
|
+
import { AIProviderConfig } from '@odda-ai/matching-core';
|
|
667
|
+
|
|
668
|
+
export const aiConfig: AIProviderConfig = {
|
|
669
|
+
provider: process.env.AI_PROVIDER as any || 'openai',
|
|
670
|
+
apiKey: process.env.AI_API_KEY || '',
|
|
671
|
+
model: process.env.AI_MODEL || 'gpt-4',
|
|
672
|
+
temperature: 0.3,
|
|
673
|
+
maxTokens: 4000,
|
|
674
|
+
responseFormat: 'json',
|
|
675
|
+
};
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
---
|
|
679
|
+
|
|
680
|
+
## 📘 TypeScript Support
|
|
681
|
+
|
|
682
|
+
Libreria fully-typed con completo supporto TypeScript.
|
|
683
|
+
|
|
684
|
+
### Type Imports
|
|
685
|
+
|
|
686
|
+
```typescript
|
|
687
|
+
import type {
|
|
688
|
+
// Provider types
|
|
689
|
+
AIProvider,
|
|
690
|
+
AIProviderConfig,
|
|
691
|
+
ChatMessage,
|
|
692
|
+
ChatResponse,
|
|
693
|
+
|
|
694
|
+
// CV Analysis types
|
|
695
|
+
CvAnalysisResponse,
|
|
696
|
+
PersonalInfo,
|
|
697
|
+
TechnicalSkill,
|
|
698
|
+
SoftSkill,
|
|
699
|
+
WorkExperience,
|
|
700
|
+
Education,
|
|
701
|
+
Certification,
|
|
702
|
+
Language,
|
|
703
|
+
|
|
704
|
+
// Chunking types
|
|
705
|
+
CvChunk,
|
|
706
|
+
ChunkingOptions,
|
|
707
|
+
ChunkingResult,
|
|
708
|
+
|
|
709
|
+
// Job Matching types
|
|
710
|
+
JobSkill,
|
|
711
|
+
|
|
712
|
+
// Parser types
|
|
713
|
+
ParsedPDF,
|
|
714
|
+
ParseOptions,
|
|
715
|
+
} from '@odda-ai/matching-core';
|
|
716
|
+
```
|
|
717
|
+
|
|
718
|
+
### Generic Support
|
|
719
|
+
|
|
720
|
+
```typescript
|
|
721
|
+
interface CustomAnalysis extends CvAnalysisResponse {
|
|
722
|
+
customField: string;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
const analysis: CustomAnalysis = {
|
|
726
|
+
...await service.cv.analyzeCvResume(buffer),
|
|
727
|
+
customField: 'custom value',
|
|
728
|
+
};
|
|
729
|
+
```
|
|
730
|
+
|
|
731
|
+
---
|
|
732
|
+
|
|
733
|
+
## 🧪 Testing
|
|
734
|
+
|
|
735
|
+
### Unit Tests
|
|
736
|
+
|
|
737
|
+
```typescript
|
|
738
|
+
import { describe, it, expect } from 'vitest';
|
|
739
|
+
import { createAIProvider } from '@odda-ai/matching-core';
|
|
740
|
+
|
|
741
|
+
describe('AI Provider', () => {
|
|
742
|
+
it('should create OpenAI provider', () => {
|
|
743
|
+
const provider = createAIProvider({
|
|
744
|
+
provider: 'openai',
|
|
745
|
+
apiKey: 'test-key',
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
expect(provider).toBeDefined();
|
|
749
|
+
});
|
|
750
|
+
});
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
### Integration Tests
|
|
754
|
+
|
|
755
|
+
```typescript
|
|
756
|
+
describe('CV Analysis', () => {
|
|
757
|
+
it('should analyze CV correctly', async () => {
|
|
758
|
+
const buffer = fs.readFileSync('./test/fixtures/sample-cv.pdf');
|
|
759
|
+
const analysis = await service.cv.analyzeCvResume(buffer);
|
|
760
|
+
|
|
761
|
+
expect(analysis.personalInfo.name).toBeDefined();
|
|
762
|
+
expect(analysis.technicalSkills.length).toBeGreaterThan(0);
|
|
763
|
+
expect(analysis.overallSeniority).toMatch(/JUNIOR|MID|SENIOR|LEAD|PRINCIPAL/);
|
|
764
|
+
});
|
|
765
|
+
});
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
---
|
|
769
|
+
|
|
770
|
+
## 📄 License
|
|
771
|
+
|
|
772
|
+
ISC
|
|
773
|
+
|
|
774
|
+
---
|
|
775
|
+
|
|
776
|
+
## 🤝 Contributing
|
|
777
|
+
|
|
778
|
+
Contributions are welcome! Please open an issue or submit a pull request.
|
|
779
|
+
|
|
780
|
+
---
|
|
781
|
+
|
|
782
|
+
## 🔗 Links
|
|
783
|
+
|
|
784
|
+
- **Main Project**: [Odda Matching AI](../README.md)
|
|
785
|
+
- **API Documentation**: [ai-talent-api/README.md](../ai-talent-api/README.md)
|
|
786
|
+
- **Dashboard**: [insights/README.md](../insights/README.md)
|
|
787
|
+
|
|
788
|
+
---
|
|
789
|
+
|
|
790
|
+
**Made with ❤️ by Odda Studio**
|