@odda-ai/matching-core 1.0.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/package.json +34 -0
- package/src/ai/AIProvider.ts +159 -0
- package/src/ai/adapters/AnthropicAdapter.ts +42 -0
- package/src/ai/adapters/OllamaAdapter.ts +42 -0
- package/src/ai/adapters/OpenAIAdapter.ts +53 -0
- package/src/ai/adapters/index.ts +3 -0
- package/src/ai/factory.ts +48 -0
- package/src/ai/index.ts +5 -0
- package/src/ai/registry.ts +15 -0
- package/src/ai/types.ts +59 -0
- package/src/cv-parser/PDFParserService.ts +160 -0
- package/src/cv-parser/index.ts +2 -0
- package/src/cv-parser/types.ts +58 -0
- package/src/features/ai-cv-resume.service.ts +104 -0
- package/src/features/ai-talent.service.ts +49 -0
- package/src/features/cv-chunking.service.ts +510 -0
- package/src/features/index.ts +5 -0
- package/src/features/job-matcher.service.ts +41 -0
- package/src/features/prompts.ts +621 -0
- package/src/features/system-messages.ts +28 -0
- package/src/features/types.ts +55 -0
|
@@ -0,0 +1,621 @@
|
|
|
1
|
+
export const CV_ANALYSIS_PROMPT_V1 = (
|
|
2
|
+
cvText: string,
|
|
3
|
+
) => `
|
|
4
|
+
Sei un esperto analista HR specializzato nell’analisi di curriculum vitae.
|
|
5
|
+
Il tuo compito è analizzare il seguente CV ed estrarre informazioni strutturate in formato JSON.
|
|
6
|
+
|
|
7
|
+
**CV da Analizzare:**
|
|
8
|
+
${cvText}
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Istruzioni Ultra-Stabili:
|
|
13
|
+
|
|
14
|
+
### 0. Dati Anagrafici (Massima Priorità)
|
|
15
|
+
- Estrai tutti i dati personali disponibili:
|
|
16
|
+
- \`firstName\` (OBBLIGATORIO, se non trovato usare \`"N/A"\`)
|
|
17
|
+
- \`lastName\` (OBBLIGATORIO, se non trovato usare \`"N/A"\`)
|
|
18
|
+
- \`email\`, \`phone\`, \`address\`, \`dateOfBirth\` (YYYY-MM-DD), \`nationality\`, \`linkedIn\`, \`github\`, \`website\` (opzionali, usare \`null\` se mancanti)
|
|
19
|
+
- Cerca nell’intestazione e nel corpo del CV.
|
|
20
|
+
- Mantieni sempre lo stesso ordine dei campi JSON.
|
|
21
|
+
|
|
22
|
+
### 1. Profilo Professionale
|
|
23
|
+
- Breve descrizione (max 2-3 frasi) con:
|
|
24
|
+
- Ruolo principale
|
|
25
|
+
- Anni totali di esperienza
|
|
26
|
+
- Aree di specializzazione chiave
|
|
27
|
+
- Tono professionale e oggettivo.
|
|
28
|
+
|
|
29
|
+
### 2. Skills Tecniche (Massima Stabilità)
|
|
30
|
+
- Estrai **solo skills esplicitamente menzionate**.
|
|
31
|
+
- Per ciascuna skill:
|
|
32
|
+
- \`proficiency\`: 0-100, calcolato rigidamente:
|
|
33
|
+
- Hobby/progetti personali: 20
|
|
34
|
+
- Progetti professionali base: 50
|
|
35
|
+
- Progetti professionali avanzati / responsabilità senior: 80
|
|
36
|
+
- Expertise avanzata / certificazioni: 90
|
|
37
|
+
- Bonus certificazioni avanzate: +10 (non oltre 100)
|
|
38
|
+
- \`seniority\`: basato sugli anni di esperienza specifica:
|
|
39
|
+
- JUNIOR: 0-2 anni
|
|
40
|
+
- MID: 2-5 anni
|
|
41
|
+
- SENIOR: 5-10 anni
|
|
42
|
+
- LEAD: 10-15 anni
|
|
43
|
+
- PRINCIPAL: 15+ anni
|
|
44
|
+
- \`experience\`: mesi di esperienza se menzionati, altrimenti \`null\`
|
|
45
|
+
- \`isInferred\`: false
|
|
46
|
+
- Inferisci **solo skills strettamente correlate** (max 5):
|
|
47
|
+
- React → JavaScript, HTML, CSS
|
|
48
|
+
- NestJS → Node.js, TypeScript
|
|
49
|
+
- PostgreSQL → SQL
|
|
50
|
+
- DevOps → Linux, Docker, Git
|
|
51
|
+
- Attingi anche da descrizioni di progetti e responsabilità o da una statistica generale, non solo dalla sezione skills.
|
|
52
|
+
- Per skill inferite:
|
|
53
|
+
- \`proficiency\`: 30-50
|
|
54
|
+
- \`seniority\`: uguale o inferiore alla skill correlata
|
|
55
|
+
- \`isInferred\`: true
|
|
56
|
+
- Usa \`referenceSkills\` per matching e inferenza coerente.
|
|
57
|
+
- Ordina sempre le skills in ordine alfabetico per consistenza.
|
|
58
|
+
|
|
59
|
+
### 3. Esperienze Lavorative
|
|
60
|
+
- Seleziona 2-3 posizioni più rilevanti (cronologico inverso).
|
|
61
|
+
- Per ciascuna posizione:
|
|
62
|
+
- \`companyName\`
|
|
63
|
+
- \`role\`
|
|
64
|
+
- \`durationInYears\` (arrotondato a 0.5 anni)
|
|
65
|
+
- \`mainResponsibility\` (max 1 frase)
|
|
66
|
+
- Massimo 4-5 frasi totali.
|
|
67
|
+
|
|
68
|
+
### 4. Certificazioni
|
|
69
|
+
- Estrai tutte le certificazioni con:
|
|
70
|
+
- \`name\`
|
|
71
|
+
- \`issuer\` (se disponibile)
|
|
72
|
+
- \`year\` (se disponibile)
|
|
73
|
+
- Se assenti, restituire array vuoto.
|
|
74
|
+
|
|
75
|
+
### 5. Seniority Complessiva
|
|
76
|
+
- Basata su:
|
|
77
|
+
- Anni totali esperienza
|
|
78
|
+
- Livello dei ruoli ricoperti
|
|
79
|
+
- Numero di skills con \`proficiency ≥ 80\`
|
|
80
|
+
- Presenza di certificazioni avanzate
|
|
81
|
+
- Scala rigida:
|
|
82
|
+
- JUNIOR: 0-2 anni
|
|
83
|
+
- MID: 2-5 anni
|
|
84
|
+
- SENIOR: 5-10 anni
|
|
85
|
+
- LEAD: 10-15 anni
|
|
86
|
+
- PRINCIPAL: 15+ anni
|
|
87
|
+
|
|
88
|
+
### 6. Anni di Esperienza Totale
|
|
89
|
+
- Somma esperienze lavorative professionali, **escludendo stage e hobby projects**.
|
|
90
|
+
- Se non esplicitati, inferire dai ruoli principali.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Formato JSON Richiesto
|
|
95
|
+
Restituisci **solo un JSON valido**, senza markdown:
|
|
96
|
+
|
|
97
|
+
\`\`\`json
|
|
98
|
+
{
|
|
99
|
+
"personalInfo": {
|
|
100
|
+
"firstName": "Mario",
|
|
101
|
+
"lastName": "Rossi",
|
|
102
|
+
"email": "mario.rossi@example.com",
|
|
103
|
+
"phone": "+39 348 1234567",
|
|
104
|
+
"address": "Via Roma 123, Milano, Italia",
|
|
105
|
+
"dateOfBirth": "1990-05-15",
|
|
106
|
+
"nationality": "Italiana",
|
|
107
|
+
"linkedIn": "https://linkedin.com/in/mariorossi",
|
|
108
|
+
"github": "https://github.com/mariorossi",
|
|
109
|
+
"website": "https://mariorossi.dev"
|
|
110
|
+
},
|
|
111
|
+
"description": "Breve descrizione professionale",
|
|
112
|
+
"technicalSkills": [
|
|
113
|
+
{
|
|
114
|
+
"name": "NomeSkill",
|
|
115
|
+
"proficiency": 85,
|
|
116
|
+
"isInferred": false,
|
|
117
|
+
"seniority": "SENIOR",
|
|
118
|
+
"experience": 36
|
|
119
|
+
}
|
|
120
|
+
],
|
|
121
|
+
"workExperienceSummary": "Riassunto esperienze rilevanti",
|
|
122
|
+
"certifications": [
|
|
123
|
+
{
|
|
124
|
+
"name": "Nome Certificazione",
|
|
125
|
+
"issuer": "Ente Certificatore",
|
|
126
|
+
"year": 2023
|
|
127
|
+
}
|
|
128
|
+
],
|
|
129
|
+
"overallSeniority": "SENIOR",
|
|
130
|
+
"yearsOfExperience": 8
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
\`\`\`
|
|
134
|
+
|
|
135
|
+
Note Finali
|
|
136
|
+
- Rispondi SOLO con JSON valido.
|
|
137
|
+
- firstName e lastName obbligatori ("N/A" se mancanti).
|
|
138
|
+
- Tutti gli altri campi obbligatori (array vuoti se assenti).
|
|
139
|
+
- Mantieni coerenza e ripetibilità dei punteggi e inferenze tra CV simili.
|
|
140
|
+
`;
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
export const CV_ANALYSIS_PROMPT = (
|
|
144
|
+
cvText: string,
|
|
145
|
+
) => `# Prompt Analisi CV in JSON – Versione Ultra-Stabile
|
|
146
|
+
|
|
147
|
+
Sei un esperto analista HR specializzato nell’analisi di curriculum vitae.
|
|
148
|
+
Il tuo compito è analizzare il CV fornito e restituire un JSON strutturato con tutte le informazioni principali.
|
|
149
|
+
|
|
150
|
+
**CV da Analizzare:**
|
|
151
|
+
${cvText}
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Istruzioni Ultra-Stabili:
|
|
156
|
+
|
|
157
|
+
### 0. Dati Anagrafici (Massima Priorità)
|
|
158
|
+
- Estrai tutti i dati personali disponibili:
|
|
159
|
+
- \`firstName\` (OBBLIGATORIO, se non trovato usare \`"N/A"\`)
|
|
160
|
+
- \`lastName\` (OBBLIGATORIO, se non trovato usare \`"N/A"\`)
|
|
161
|
+
- \`email\`, \`phone\`, \`address\`, \`dateOfBirth\` (YYYY-MM-DD), \`nationality\`, \`linkedIn\`, \`github\`, \`website\` (opzionali, usare \`null\` se mancanti)
|
|
162
|
+
- Cerca informazioni nell’intestazione e nel corpo del CV.
|
|
163
|
+
- Se la data di nascita contiene solo anno/mese, completare con giorno \`01\`.
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
### 1. Profilo Professionale
|
|
168
|
+
- Breve descrizione (max 2-3 frasi) con:
|
|
169
|
+
- Ruolo principale
|
|
170
|
+
- Anni totali di esperienza
|
|
171
|
+
- Settore/industria
|
|
172
|
+
- Aree di specializzazione chiave
|
|
173
|
+
- Tono professionale e oggettivo.
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
### 2. Skills Tecniche (Massima Stabilità)
|
|
178
|
+
|
|
179
|
+
#### 2.1 Estrazione esplicita
|
|
180
|
+
- Considera **solo le skills menzionate testualmente** nelle sezioni “Competenze”, “Skills”, “Progetti”, “Esperienze” o “Certificazioni tecniche”.
|
|
181
|
+
- Per ciascuna skill:
|
|
182
|
+
- \`name\` → come scritto nel CV (normalizzato per coerenza)
|
|
183
|
+
- \`proficiency\` → calcolato rigidamente:
|
|
184
|
+
- Hobby/progetti personali: 20
|
|
185
|
+
- Progetti professionali base: 50
|
|
186
|
+
- Progetti professionali avanzati / responsabilità senior: 80
|
|
187
|
+
- Expertise avanzata / certificazioni: 90
|
|
188
|
+
- Bonus certificazioni avanzate: +10 (max 100)
|
|
189
|
+
- \`seniority\` → basato sugli anni di esperienza specifica:
|
|
190
|
+
- JUNIOR: 0-2 anni
|
|
191
|
+
- MID: 2-5 anni
|
|
192
|
+
- SENIOR: 5-10 anni
|
|
193
|
+
- LEAD: 10-15 anni
|
|
194
|
+
- PRINCIPAL: 15+ anni
|
|
195
|
+
- \`experience\` → mesi esplicitamente menzionati o \`null\`
|
|
196
|
+
- \`isInferred\` → false
|
|
197
|
+
|
|
198
|
+
#### 2.2 Inferenza di skill correlate
|
|
199
|
+
- Solo per skill tecniche note, massimo 5 inferenze per CV.
|
|
200
|
+
- Mappe di inferenza (referenceSkills):
|
|
201
|
+
- React → JavaScript, TypeScript, HTML, CSS, JSX, Redux, Hooks, Context API, Router, SSR, Testing, Recoil, Zustand, Framer Motion, GSAP, Storybook, Playwright, Web Vitals
|
|
202
|
+
- Angular → TypeScript, HTML, CSS, RxJS, NgRx, CLI, DI, Testing, PWA, Lazy Loading
|
|
203
|
+
- Vue → JavaScript, TypeScript, HTML, CSS, Vuex, Router, Composition API, SSR, Testing, Pinia
|
|
204
|
+
- Node.js → JavaScript, TypeScript, Async/Await, Express.js, NestJS, REST, GraphQL, WebSockets, Microservices, Testing, gRPC, Thrift, Kafka, Celery, BullMQ
|
|
205
|
+
- NestJS → Node.js, TypeScript, REST, GraphQL, DI, Modules, Middleware, Pipes, Guards, Interceptors, TypeORM/Prisma
|
|
206
|
+
- Python → OOP, Async, Flask, Django, FastAPI, Testing, Pandas, NumPy, ML, gRPC, Kafka, Airflow, Prefect, MLflow, Kubeflow, Triton
|
|
207
|
+
- Java → OOP, Spring Boot, REST, Microservices, Concurrency, Testing, gRPC, Kafka
|
|
208
|
+
- C# → OOP, .NET Core, ASP.NET, REST, Microservices, Testing, gRPC
|
|
209
|
+
- SQL → PostgreSQL, MySQL, SQL Server, Oracle, Indexing, Joins, Transactions, Views, Procedures, TimescaleDB
|
|
210
|
+
- NoSQL → MongoDB, Redis, Cassandra, Aggregation, Replication, Sharding, Caching, Neo4j, ArangoDB, Elasticsearch, Meilisearch
|
|
211
|
+
- Redis → Caching, Pub/Sub, Session Management, Performance
|
|
212
|
+
- Messaging → RabbitMQ, Kafka, Event-driven, Queues, Retry, Streaming
|
|
213
|
+
- Docker → Containers, Images, Volumes, Networking, CI/CD, Security
|
|
214
|
+
- Kubernetes → Pods, Deployments, Services, Ingress, ConfigMaps, Secrets, Helm, Scaling, Observability
|
|
215
|
+
- DevOps → Linux, Git, CI/CD, Docker, Kubernetes, Monitoring, Logging, Cloud, Security, Terraform, Pulumi, CloudFormation, Vault, SOPS, Serverless Framework, SST
|
|
216
|
+
- Git → Version Control, Branching, Merging, PR
|
|
217
|
+
- Linux → Shell, Networking, Permissions, Cron, Systemd
|
|
218
|
+
- Cloud → AWS (EC2,S3,Lambda,RDS,IAM), GCP (Compute,Storage,Functions,BigQuery), Azure (VM,App,Functions,SQL), Edge/CDN (Cloudflare Workers, Fastly)
|
|
219
|
+
- REST API → HTTP, JSON, CRUD, Auth, Rate Limiting, Testing
|
|
220
|
+
- GraphQL → Schema, Resolvers, Subscriptions, Apollo, Caching
|
|
221
|
+
- Testing → Unit, Integration, E2E, TDD, BDD, Jest, Mocha, Cypress, Selenium, Postman/Newman
|
|
222
|
+
- CI/CD → Jenkins, GitHub Actions, GitLab CI, Pipelines, Rollbacks, Blue/Green
|
|
223
|
+
- Security → Auth, JWT, OAuth2, HTTPS, CORS, Encryption, Input Validation, Secrets, SAST, DAST, PenTesting, Keycloak, Auth0, Container Security
|
|
224
|
+
- Monitoring → Prometheus, Grafana, ELK, CloudWatch, Metrics, Tracing, OpenTelemetry, Datadog
|
|
225
|
+
- Microservices → Service Discovery, API Gateway, Event-driven, Circuit Breaker, CQRS, Saga, Serverless
|
|
226
|
+
- Architecture → MVC, MVVM, Layered, Event-driven, Serverless, Monolith vs Microservices, DDD, Hexagonal, Clean Architecture, Multi-tenant, SaaS Patterns
|
|
227
|
+
- Performance → Caching, Load Balancing, Scaling, DB Optimization, Code Splitting, Lazy Loading, CDN
|
|
228
|
+
|
|
229
|
+
- Per skill inferite:
|
|
230
|
+
- \`proficiency\` : 30-70 (inferiore o uguale alla skill madre, assegna un valore realistico in base alla relazione)
|
|
231
|
+
- \`seniority\`: uguale o inferiore alla skill madre
|
|
232
|
+
- \`experience\`: null
|
|
233
|
+
- \`isInferred\`: true
|
|
234
|
+
- \`source\`: opzionale, “inferred from {skill madre}”
|
|
235
|
+
|
|
236
|
+
#### 2.3 Ordinamento
|
|
237
|
+
- Ordinare alfabeticamente tutte le skill, prima esplicite, poi inferite.
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
### 3. Esperienze Lavorative
|
|
242
|
+
- Seleziona 2-3 posizioni più rilevanti, cronologico inverso.
|
|
243
|
+
- Per ciascuna posizione:
|
|
244
|
+
- \`companyName\`
|
|
245
|
+
- \`role\`
|
|
246
|
+
- \`durationInYears\` (arrotondato a 0.5)
|
|
247
|
+
- \`mainResponsibility\` (max 1 frase)
|
|
248
|
+
- Escludere stage o hobby projects dai calcoli di esperienza.
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
### 4. Certificazioni
|
|
253
|
+
- Estrarre tutte le certificazioni con:
|
|
254
|
+
- \`name\` (OBBLIGATORIO)
|
|
255
|
+
- \`issuer\` (null se non disponibile)
|
|
256
|
+
- \`year\` (null se non disponibile)
|
|
257
|
+
- Se assenti, restituire array vuoto.
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
### 5. Seniority Complessiva
|
|
262
|
+
- Basata su:
|
|
263
|
+
- Anni totali esperienza
|
|
264
|
+
- Livello dei ruoli ricoperti
|
|
265
|
+
- Numero di skill con \`proficiency ≥ 80\`
|
|
266
|
+
- Presenza di certificazioni avanzate
|
|
267
|
+
- Scala:
|
|
268
|
+
- JUNIOR: 0-2 anni
|
|
269
|
+
- MID: 2-5 anni
|
|
270
|
+
- SENIOR: 5-10 anni
|
|
271
|
+
- LEAD: 10-15 anni
|
|
272
|
+
- PRINCIPAL: 15+ anni
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
### 6. Anni di Esperienza Totale
|
|
277
|
+
- Somma esperienze lavorative professionali, **escludendo stage e hobby projects**.
|
|
278
|
+
- Se non esplicitati, inferire dai ruoli principali.
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## Formato JSON Richiesto
|
|
283
|
+
Restituire **solo un JSON valido**, senza markdown.
|
|
284
|
+
|
|
285
|
+
\`\`\`json
|
|
286
|
+
{
|
|
287
|
+
"personalInfo": {
|
|
288
|
+
"firstName": "N/A",
|
|
289
|
+
"lastName": "N/A",
|
|
290
|
+
"email": null,
|
|
291
|
+
"phone": null,
|
|
292
|
+
"address": null,
|
|
293
|
+
"dateOfBirth": null,
|
|
294
|
+
"nationality": null,
|
|
295
|
+
"linkedIn": null,
|
|
296
|
+
"github": null,
|
|
297
|
+
"website": null
|
|
298
|
+
},
|
|
299
|
+
"description": "Breve descrizione professionale",
|
|
300
|
+
"technicalSkills": [
|
|
301
|
+
{
|
|
302
|
+
"name": "NomeSkill",
|
|
303
|
+
"proficiency": 85,
|
|
304
|
+
"isInferred": false,
|
|
305
|
+
"seniority": "SENIOR",
|
|
306
|
+
"experience": 36
|
|
307
|
+
}
|
|
308
|
+
],
|
|
309
|
+
"workExperienceSummary": "Riassunto esperienze rilevanti",
|
|
310
|
+
"certifications": [
|
|
311
|
+
{
|
|
312
|
+
"name": "Nome Certificazione",
|
|
313
|
+
"issuer": "Ente Certificatore",
|
|
314
|
+
"year": 2023
|
|
315
|
+
}
|
|
316
|
+
],
|
|
317
|
+
"overallSeniority": "SENIOR",
|
|
318
|
+
"yearsOfExperience": 8
|
|
319
|
+
}
|
|
320
|
+
`;
|
|
321
|
+
|
|
322
|
+
export const JOB_MATCHING_PROMPT_V1 = (
|
|
323
|
+
jobDescription: string,
|
|
324
|
+
skillsList: string[],
|
|
325
|
+
) => `Task: Perform end-to-end skill extraction, semantic matching, and scoring.
|
|
326
|
+
|
|
327
|
+
Input:
|
|
328
|
+
- Job Description: ${jobDescription}
|
|
329
|
+
- List of Reference Skills:
|
|
330
|
+
${skillsList.map((skill) => ` - ${skill}`).join("\n")}
|
|
331
|
+
|
|
332
|
+
Instructions:
|
|
333
|
+
|
|
334
|
+
1. Skill Extraction & Normalization
|
|
335
|
+
- Extract all relevant technical and soft skills from the Job Description
|
|
336
|
+
- Include explicit and clearly inferable implicit skills
|
|
337
|
+
- Normalize all skills to canonical names
|
|
338
|
+
- Map overly specific skills to their broader parent skills when appropriate
|
|
339
|
+
|
|
340
|
+
2. Skill Importance Classification
|
|
341
|
+
For each extracted skill, determine its importance for the role:
|
|
342
|
+
- core: essential to perform the job
|
|
343
|
+
- enabling: improves effectiveness
|
|
344
|
+
- nice_to_have: optional or contextual
|
|
345
|
+
|
|
346
|
+
3. Semantic Matching
|
|
347
|
+
For each Reference Skill:
|
|
348
|
+
- Evaluate semantic similarity with extracted skills
|
|
349
|
+
- Consider hierarchical and functional relationships (general ↔ specific)
|
|
350
|
+
- Recognize equivalent or alternative skills covering the same domain
|
|
351
|
+
- Do not penalize missing overly specific skills when a broader equivalent exists
|
|
352
|
+
|
|
353
|
+
4. Scoring
|
|
354
|
+
Assign a multiplier between 0.00 and 1.00 based on:
|
|
355
|
+
- Semantic similarity
|
|
356
|
+
- Importance of the matched extracted skill
|
|
357
|
+
- Functional coverage by alternative skills
|
|
358
|
+
- Realistic expectations in a recruiting context
|
|
359
|
+
|
|
360
|
+
Scoring guidelines:
|
|
361
|
+
- 0.80–1.00 → crucial and strongly matched
|
|
362
|
+
- 0.50–0.79 → relevant or partially matched
|
|
363
|
+
- 0.20–0.49 → weak or indirect relevance
|
|
364
|
+
- < 0.20 → exclude from output
|
|
365
|
+
|
|
366
|
+
5. Output
|
|
367
|
+
Return ONLY a valid JSON array.
|
|
368
|
+
Include only Reference Skills with multiplier > 0.
|
|
369
|
+
|
|
370
|
+
Output format:
|
|
371
|
+
[
|
|
372
|
+
{ "skill": "Exact Reference Skill Name", "multiplier": 0.00 }
|
|
373
|
+
]
|
|
374
|
+
|
|
375
|
+
Constraints:
|
|
376
|
+
- Do NOT include markdown
|
|
377
|
+
- Do NOT include explanations
|
|
378
|
+
- Do NOT include intermediate steps
|
|
379
|
+
- Return ONLY the final JSON array
|
|
380
|
+
|
|
381
|
+
Example:
|
|
382
|
+
|
|
383
|
+
Job Description:
|
|
384
|
+
"Looking for a Frontend Engineer with strong React experience.
|
|
385
|
+
You will collaborate with designers and backend engineers.
|
|
386
|
+
Experience with modern state management libraries is a plus."
|
|
387
|
+
|
|
388
|
+
Reference Skills:
|
|
389
|
+
- React
|
|
390
|
+
- Redux
|
|
391
|
+
- Zustand
|
|
392
|
+
- Communication
|
|
393
|
+
- AWS
|
|
394
|
+
|
|
395
|
+
Expected Output:
|
|
396
|
+
[
|
|
397
|
+
{ "skill": "React", "multiplier": 0.95 },
|
|
398
|
+
{ "skill": "Redux", "multiplier": 0.60 },
|
|
399
|
+
{ "skill": "Zustand", "multiplier": 0.55 },
|
|
400
|
+
{ "skill": "Communication", "multiplier": 0.40 }
|
|
401
|
+
]
|
|
402
|
+
`;
|
|
403
|
+
|
|
404
|
+
export const JOB_MATCHING_PROMPT = (
|
|
405
|
+
jobDescription: string,
|
|
406
|
+
skillsList: string[],
|
|
407
|
+
) => `# TASK
|
|
408
|
+
Perform two-pass deterministic skill extraction with validation, normalization, semantic alignment, classification, and scoring.
|
|
409
|
+
|
|
410
|
+
Precision target: ~90%.
|
|
411
|
+
Priority: avoid hallucinations and unjustified inferences.
|
|
412
|
+
|
|
413
|
+
Extraction must be text-grounded and validated before final output.
|
|
414
|
+
|
|
415
|
+
---
|
|
416
|
+
|
|
417
|
+
# INPUT
|
|
418
|
+
|
|
419
|
+
## Job Description
|
|
420
|
+
${jobDescription}
|
|
421
|
+
|
|
422
|
+
## Reference Skills
|
|
423
|
+
${skillsList.map((skill) => `- ${skill}`).join("\n")}
|
|
424
|
+
|
|
425
|
+
---
|
|
426
|
+
|
|
427
|
+
# GLOBAL RULES
|
|
428
|
+
|
|
429
|
+
1. Do NOT invent skills.
|
|
430
|
+
2. Do NOT use external knowledge.
|
|
431
|
+
3. Do NOT assume standard tech stacks.
|
|
432
|
+
4. Do NOT infer from job title alone.
|
|
433
|
+
5. Every skill must be grounded in explicit text or strictly necessary logic.
|
|
434
|
+
6. If uncertain → remove the skill.
|
|
435
|
+
7. Precision > Recall.
|
|
436
|
+
|
|
437
|
+
---
|
|
438
|
+
|
|
439
|
+
# PASS 1 — CONTROLLED EXTRACTION
|
|
440
|
+
|
|
441
|
+
Extract ALL candidate skills that satisfy one of the following:
|
|
442
|
+
|
|
443
|
+
### A) Explicit Mention
|
|
444
|
+
Skill is directly written in the Job Description.
|
|
445
|
+
|
|
446
|
+
### B) Deterministic Logical Necessity (STRICT)
|
|
447
|
+
Infer ONLY if:
|
|
448
|
+
- The responsibility cannot be performed without that skill.
|
|
449
|
+
- The inference is structurally required.
|
|
450
|
+
- The inference does not depend on market assumptions.
|
|
451
|
+
|
|
452
|
+
Allowed examples:
|
|
453
|
+
- “Build REST APIs” → REST
|
|
454
|
+
- “Write unit tests” → Unit Testing
|
|
455
|
+
- “Design relational database schema” → SQL
|
|
456
|
+
|
|
457
|
+
Forbidden examples:
|
|
458
|
+
- “Frontend Developer” → React
|
|
459
|
+
- “Cloud environment” → AWS
|
|
460
|
+
- “Agile team” → Scrum
|
|
461
|
+
- “Microservices” → Docker
|
|
462
|
+
|
|
463
|
+
For each candidate skill:
|
|
464
|
+
- Normalize to canonical name.
|
|
465
|
+
- Remove duplicates.
|
|
466
|
+
- Classify category:
|
|
467
|
+
- technical
|
|
468
|
+
- soft
|
|
469
|
+
|
|
470
|
+
---
|
|
471
|
+
|
|
472
|
+
# PASS 2 — VALIDATION & PRUNING (CRITICAL FOR 90% PRECISION)
|
|
473
|
+
|
|
474
|
+
For EACH extracted skill, validate:
|
|
475
|
+
|
|
476
|
+
1. Can I point to a direct phrase in the Job Description supporting this skill?
|
|
477
|
+
2. Is the inference strictly necessary (not assumed)?
|
|
478
|
+
3. Would a human reviewer agree this skill is clearly implied?
|
|
479
|
+
|
|
480
|
+
If ANY answer is uncertain → REMOVE the skill.
|
|
481
|
+
|
|
482
|
+
Do NOT keep borderline skills.
|
|
483
|
+
Do NOT keep ecosystem expansions.
|
|
484
|
+
Do NOT broaden vendor-specific tools unless explicitly stated.
|
|
485
|
+
|
|
486
|
+
After pruning:
|
|
487
|
+
The remaining list becomes the FINAL extracted skill set.
|
|
488
|
+
|
|
489
|
+
---
|
|
490
|
+
|
|
491
|
+
# IMPORTANCE CLASSIFICATION (TEXT-DRIVEN ONLY)
|
|
492
|
+
|
|
493
|
+
Use only explicit textual signals.
|
|
494
|
+
|
|
495
|
+
CORE:
|
|
496
|
+
- must
|
|
497
|
+
- required
|
|
498
|
+
- essential
|
|
499
|
+
- primary responsibility
|
|
500
|
+
- strongly emphasized
|
|
501
|
+
|
|
502
|
+
ENABLING:
|
|
503
|
+
- preferred
|
|
504
|
+
- experience with
|
|
505
|
+
- plus
|
|
506
|
+
- good to have
|
|
507
|
+
- improves execution
|
|
508
|
+
|
|
509
|
+
NICE_TO_HAVE:
|
|
510
|
+
- optional
|
|
511
|
+
- secondary section
|
|
512
|
+
- clearly non-blocking
|
|
513
|
+
|
|
514
|
+
If ambiguous → classify as ENABLING.
|
|
515
|
+
Never upgrade importance without textual evidence.
|
|
516
|
+
|
|
517
|
+
---
|
|
518
|
+
|
|
519
|
+
# REFERENCE SKILL MAPPING (AFTER VALIDATION ONLY)
|
|
520
|
+
|
|
521
|
+
For each validated extracted skill:
|
|
522
|
+
|
|
523
|
+
Step 1: Attempt exact match (case-insensitive).
|
|
524
|
+
Step 2: If no exact match → attempt strict semantic equivalence.
|
|
525
|
+
Step 3: If semantic confidence is not high → DO NOT MAP.
|
|
526
|
+
|
|
527
|
+
Mapping constraints:
|
|
528
|
+
- Only map when meaning is substantially identical.
|
|
529
|
+
- Do NOT map broader ↔ narrower unless coverage is complete.
|
|
530
|
+
- Do NOT force mapping.
|
|
531
|
+
|
|
532
|
+
If mapped:
|
|
533
|
+
- Use exact Reference Skill name.
|
|
534
|
+
- "isReferenceSkill": true
|
|
535
|
+
|
|
536
|
+
If not mapped:
|
|
537
|
+
- Keep normalized extracted name.
|
|
538
|
+
- "isReferenceSkill": false
|
|
539
|
+
|
|
540
|
+
Never remove unmatched skills.
|
|
541
|
+
|
|
542
|
+
---
|
|
543
|
+
|
|
544
|
+
# MULTIPLIER SCORING (DETERMINISTIC RANGE SYSTEM)
|
|
545
|
+
|
|
546
|
+
Scoring depends ONLY on:
|
|
547
|
+
- Importance
|
|
548
|
+
- Mapping status
|
|
549
|
+
|
|
550
|
+
No subjective creativity.
|
|
551
|
+
|
|
552
|
+
If isReferenceSkill = true:
|
|
553
|
+
|
|
554
|
+
CORE → 0.90–1.00
|
|
555
|
+
ENABLING → 0.65–0.80
|
|
556
|
+
NICE_TO_HAVE → 0.40–0.55
|
|
557
|
+
|
|
558
|
+
If isReferenceSkill = false:
|
|
559
|
+
|
|
560
|
+
CORE → 0.80–0.90
|
|
561
|
+
ENABLING → 0.55–0.70
|
|
562
|
+
NICE_TO_HAVE → 0.30–0.50
|
|
563
|
+
|
|
564
|
+
Rules:
|
|
565
|
+
- Default to mid-range value.
|
|
566
|
+
- Use upper bound only if strongly emphasized.
|
|
567
|
+
- Never assign 1.00 unless clearly critical.
|
|
568
|
+
- Exclude skills with multiplier < 0.30.
|
|
569
|
+
|
|
570
|
+
---
|
|
571
|
+
|
|
572
|
+
# JOB SKILL TYPE ASSIGNMENT
|
|
573
|
+
|
|
574
|
+
If category = technical:
|
|
575
|
+
- core OR enabling → technical_required
|
|
576
|
+
- nice_to_have → technical_nice_to_have
|
|
577
|
+
|
|
578
|
+
If category = soft:
|
|
579
|
+
- core OR enabling → soft_required
|
|
580
|
+
- nice_to_have → soft_nice_to_have
|
|
581
|
+
|
|
582
|
+
Allowed values only:
|
|
583
|
+
- technical_required
|
|
584
|
+
- technical_nice_to_have
|
|
585
|
+
- soft_required
|
|
586
|
+
- soft_nice_to_have
|
|
587
|
+
|
|
588
|
+
---
|
|
589
|
+
|
|
590
|
+
# OUTPUT RULES (STRICT)
|
|
591
|
+
|
|
592
|
+
Return ONLY a valid JSON array.
|
|
593
|
+
|
|
594
|
+
- No markdown
|
|
595
|
+
- No explanations
|
|
596
|
+
- No reasoning
|
|
597
|
+
- No comments
|
|
598
|
+
- No intermediate steps
|
|
599
|
+
- No extra fields
|
|
600
|
+
- Include only skills with multiplier ≥ 0.30
|
|
601
|
+
- Include both mapped and unmapped skills
|
|
602
|
+
- If no skills validated → return []
|
|
603
|
+
|
|
604
|
+
---
|
|
605
|
+
|
|
606
|
+
# OUTPUT FORMAT
|
|
607
|
+
|
|
608
|
+
[
|
|
609
|
+
{
|
|
610
|
+
"skill": "Final Skill Name",
|
|
611
|
+
"multiplier": 0.00,
|
|
612
|
+
"jobSkillType": "technical_required | technical_nice_to_have | soft_required | soft_nice_to_have",
|
|
613
|
+
"isReferenceSkill": true | false
|
|
614
|
+
}
|
|
615
|
+
]
|
|
616
|
+
\`\`\``;
|
|
617
|
+
|
|
618
|
+
export const prompts = {
|
|
619
|
+
cvAnalysis: CV_ANALYSIS_PROMPT,
|
|
620
|
+
jobMatching: JOB_MATCHING_PROMPT,
|
|
621
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export const ANALYZE_RESUME: () => string | undefined = () => undefined;
|
|
2
|
+
export const JOB_MATCHING = () => `
|
|
3
|
+
You are a senior HR Analytics and Skill Intelligence expert specialized in realistic AI-based recruiting systems.
|
|
4
|
+
|
|
5
|
+
You work with noisy job descriptions, implicit requirements, rare skills, and non-standardized taxonomies.
|
|
6
|
+
|
|
7
|
+
You MUST follow these principles:
|
|
8
|
+
- Skills are semantic concepts, not keywords
|
|
9
|
+
- Overly specific skills must never be treated as hard requirements
|
|
10
|
+
- Implicit skills may be inferred only when clearly supported by responsibilities
|
|
11
|
+
- Soft skills are supporting signals, never exclusion criteria
|
|
12
|
+
- Matching must be realistic, recruiter-aligned, and explainable
|
|
13
|
+
|
|
14
|
+
Operational constraints:
|
|
15
|
+
- Normalize and canonicalize skill names
|
|
16
|
+
- Evaluate skill importance (core / enabling / nice-to-have)
|
|
17
|
+
- Prefer realistic recall over overly strict precision
|
|
18
|
+
- Do not invent unjustified skills
|
|
19
|
+
|
|
20
|
+
Output discipline:
|
|
21
|
+
- When structured output is requested, return ONLY valid JSON
|
|
22
|
+
- Do not include explanations, markdown, or extra text
|
|
23
|
+
`;
|
|
24
|
+
|
|
25
|
+
export const systemMessages = {
|
|
26
|
+
analyzeResume: ANALYZE_RESUME,
|
|
27
|
+
jobMatching: JOB_MATCHING,
|
|
28
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export enum Seniority {
|
|
2
|
+
JUNIOR = 'JUNIOR',
|
|
3
|
+
MID = 'MID',
|
|
4
|
+
SENIOR = 'SENIOR',
|
|
5
|
+
LEAD = 'LEAD',
|
|
6
|
+
PRINCIPAL = 'PRINCIPAL',
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export enum JobSkillType {
|
|
10
|
+
TECHNICAL_REQUIRED = 'technical_required',
|
|
11
|
+
TECHNICAL_NICE_TO_HAVE = 'technical_nice_to_have',
|
|
12
|
+
SOFT_REQUIRED = 'soft_required',
|
|
13
|
+
SOFT_NICE_TO_HAVE = 'soft_nice_to_have',
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type TechnicalSkill = {
|
|
17
|
+
name: string;
|
|
18
|
+
proficiency: number;
|
|
19
|
+
isInferred: boolean;
|
|
20
|
+
seniority: Seniority;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type PersonalInfo = {
|
|
24
|
+
firstName?: string;
|
|
25
|
+
lastName?: string;
|
|
26
|
+
email?: string;
|
|
27
|
+
phone?: string;
|
|
28
|
+
address?: string;
|
|
29
|
+
dateOfBirth?: string;
|
|
30
|
+
nationality?: string;
|
|
31
|
+
linkedIn?: string;
|
|
32
|
+
github?: string;
|
|
33
|
+
website?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type Certification = {
|
|
37
|
+
name: string;
|
|
38
|
+
issuer?: string;
|
|
39
|
+
year?: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type CvAnalysisRequest = {
|
|
43
|
+
cvText: string;
|
|
44
|
+
referenceSkills?: string[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export type CvAnalysisResponse = {
|
|
48
|
+
personalInfo: PersonalInfo;
|
|
49
|
+
description: string;
|
|
50
|
+
technicalSkills: TechnicalSkill[];
|
|
51
|
+
workExperienceSummary: string;
|
|
52
|
+
certifications: Certification[];
|
|
53
|
+
overallSeniority: Seniority;
|
|
54
|
+
yearsOfExperience: number;
|
|
55
|
+
}
|