@dolusoft/hirebase-mcp 1.3.4 → 1.4.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 +28 -2
- package/dist/__tests__/integration/hybrid-search.test.d.ts +1 -0
- package/dist/__tests__/integration/hybrid-search.test.js +70 -0
- package/dist/__tests__/integration/hybrid-search.test.js.map +1 -0
- package/dist/__tests__/unit/chunking.test.d.ts +1 -0
- package/dist/__tests__/unit/chunking.test.js +70 -0
- package/dist/__tests__/unit/chunking.test.js.map +1 -0
- package/dist/__tests__/unit/composite-score.test.d.ts +1 -0
- package/dist/__tests__/unit/composite-score.test.js +28 -0
- package/dist/__tests__/unit/composite-score.test.js.map +1 -0
- package/dist/__tests__/unit/find-similar-candidates.test.d.ts +1 -0
- package/dist/__tests__/unit/find-similar-candidates.test.js +33 -0
- package/dist/__tests__/unit/find-similar-candidates.test.js.map +1 -0
- package/dist/__tests__/unit/query-enhancer.test.d.ts +1 -0
- package/dist/__tests__/unit/query-enhancer.test.js +20 -0
- package/dist/__tests__/unit/query-enhancer.test.js.map +1 -0
- package/dist/__tests__/unit/skill-graph.test.d.ts +1 -0
- package/dist/__tests__/unit/skill-graph.test.js +38 -0
- package/dist/__tests__/unit/skill-graph.test.js.map +1 -0
- package/dist/__tests__/unit/skill-normalizer.test.d.ts +1 -0
- package/dist/__tests__/unit/skill-normalizer.test.js +41 -0
- package/dist/__tests__/unit/skill-normalizer.test.js.map +1 -0
- package/dist/__tests__/unit/smoke.test.d.ts +1 -0
- package/dist/__tests__/unit/smoke.test.js +7 -0
- package/dist/__tests__/unit/smoke.test.js.map +1 -0
- package/dist/application/dto/candidate-summary.d.ts +2 -0
- package/dist/application/dto/filter-input.d.ts +2 -0
- package/dist/application/dto/search-results.d.ts +3 -0
- package/dist/application/dto/stats.d.ts +6 -0
- package/dist/application/services/query-enhancer.d.ts +10 -0
- package/dist/application/services/query-enhancer.js +34 -0
- package/dist/application/services/query-enhancer.js.map +1 -0
- package/dist/application/use-cases/add-cv.d.ts +5 -1
- package/dist/application/use-cases/add-cv.js +25 -4
- package/dist/application/use-cases/add-cv.js.map +1 -1
- package/dist/application/use-cases/delete-cv.d.ts +5 -1
- package/dist/application/use-cases/delete-cv.js +13 -1
- package/dist/application/use-cases/delete-cv.js.map +1 -1
- package/dist/application/use-cases/delete-job-posting.d.ts +10 -0
- package/dist/application/use-cases/delete-job-posting.js +23 -0
- package/dist/application/use-cases/delete-job-posting.js.map +1 -0
- package/dist/application/use-cases/filter-candidates.js +12 -1
- package/dist/application/use-cases/filter-candidates.js.map +1 -1
- package/dist/application/use-cases/find-similar-candidates.d.ts +21 -0
- package/dist/application/use-cases/find-similar-candidates.js +85 -0
- package/dist/application/use-cases/find-similar-candidates.js.map +1 -0
- package/dist/application/use-cases/get-stats.js +2 -0
- package/dist/application/use-cases/get-stats.js.map +1 -1
- package/dist/application/use-cases/list-cvs.d.ts +2 -1
- package/dist/application/use-cases/list-cvs.js +18 -2
- package/dist/application/use-cases/list-cvs.js.map +1 -1
- package/dist/application/use-cases/match-candidates.d.ts +5 -1
- package/dist/application/use-cases/match-candidates.js +61 -19
- package/dist/application/use-cases/match-candidates.js.map +1 -1
- package/dist/application/use-cases/reindex-all.d.ts +22 -0
- package/dist/application/use-cases/reindex-all.js +124 -0
- package/dist/application/use-cases/reindex-all.js.map +1 -0
- package/dist/application/use-cases/semantic-search.d.ts +3 -1
- package/dist/application/use-cases/semantic-search.js +13 -3
- package/dist/application/use-cases/semantic-search.js.map +1 -1
- package/dist/application/use-cases/update-cv.d.ts +2 -1
- package/dist/application/use-cases/update-cv.js +18 -3
- package/dist/application/use-cases/update-cv.js.map +1 -1
- package/dist/domain/entities/candidate.d.ts +3 -0
- package/dist/domain/repositories/candidate-repository.d.ts +1 -0
- package/dist/domain/repositories/chunk-repository.d.ts +8 -0
- package/dist/infrastructure/config/app-config.d.ts +5 -0
- package/dist/infrastructure/config/app-config.js +4 -0
- package/dist/infrastructure/config/app-config.js.map +1 -1
- package/dist/infrastructure/persistence/database.d.ts +1 -0
- package/dist/infrastructure/persistence/database.js +35 -1
- package/dist/infrastructure/persistence/database.js.map +1 -1
- package/dist/infrastructure/persistence/lancedb-candidate-repository.d.ts +1 -0
- package/dist/infrastructure/persistence/lancedb-candidate-repository.js +20 -0
- package/dist/infrastructure/persistence/lancedb-candidate-repository.js.map +1 -1
- package/dist/infrastructure/persistence/lancedb-chunk-repository.d.ts +4 -1
- package/dist/infrastructure/persistence/lancedb-chunk-repository.js +64 -2
- package/dist/infrastructure/persistence/lancedb-chunk-repository.js.map +1 -1
- package/dist/interface/cli/index.js +19 -2
- package/dist/interface/cli/index.js.map +1 -1
- package/dist/interface/dashboard/public/200.html +1 -1
- package/dist/interface/dashboard/public/404.html +1 -1
- package/dist/interface/dashboard/public/_nuxt/builds/latest.json +1 -1
- package/dist/interface/dashboard/public/_nuxt/builds/meta/05a92851-5bed-4eaf-bd75-e0aff9ec5876.json +1 -0
- package/dist/interface/dashboard/public/_nuxt/builds/meta/1d36013b-8fb8-4cea-a448-d5f224b731ee.json +1 -0
- package/dist/interface/dashboard/public/_nuxt/builds/meta/2bb695c5-c739-46a0-a3e6-4b76aa321c29.json +1 -0
- package/dist/interface/dashboard/public/_nuxt/builds/meta/3ee0a1ee-8e79-4443-a6e7-b0b6412b0a38.json +1 -0
- package/dist/interface/dashboard/public/_nuxt/builds/meta/a4cd5ce8-79f3-42cd-99a6-ddc9cd1696d0.json +1 -0
- package/dist/interface/dashboard/public/_nuxt/builds/meta/ba31dbc0-8fe2-44bc-8214-6b3d717566a0.json +1 -0
- package/dist/interface/dashboard/public/_nuxt/builds/meta/c24c5825-f907-4b91-805a-a6f061aae9ee.json +1 -0
- package/dist/interface/dashboard/public/_nuxt/builds/meta/c344ef44-e491-4e51-ae81-c21dfe2317bb.json +1 -0
- package/dist/interface/dashboard/public/_nuxt/builds/meta/fc914bdb-64a6-4798-a08f-64eb8ea9a307.json +1 -0
- package/dist/interface/dashboard/public/index.html +1 -1
- package/dist/interface/mcp/server.js +119 -42
- package/dist/interface/mcp/server.js.map +1 -1
- package/dist/interface/mcp/tools/add-cv.d.ts +2 -2
- package/dist/interface/mcp/tools/add-cv.js +31 -3
- package/dist/interface/mcp/tools/add-cv.js.map +1 -1
- package/dist/interface/mcp/tools/add-job-posting.d.ts +2 -2
- package/dist/interface/mcp/tools/add-job-posting.js +20 -12
- package/dist/interface/mcp/tools/add-job-posting.js.map +1 -1
- package/dist/interface/mcp/tools/add-pipeline-note.d.ts +2 -2
- package/dist/interface/mcp/tools/add-pipeline-note.js +10 -6
- package/dist/interface/mcp/tools/add-pipeline-note.js.map +1 -1
- package/dist/interface/mcp/tools/add-reference-cv.d.ts +2 -2
- package/dist/interface/mcp/tools/add-reference-cv.js +9 -5
- package/dist/interface/mcp/tools/add-reference-cv.js.map +1 -1
- package/dist/interface/mcp/tools/add-to-pipeline.d.ts +2 -2
- package/dist/interface/mcp/tools/add-to-pipeline.js +9 -5
- package/dist/interface/mcp/tools/add-to-pipeline.js.map +1 -1
- package/dist/interface/mcp/tools/check-duplicate-candidate.d.ts +2 -2
- package/dist/interface/mcp/tools/check-duplicate-candidate.js +8 -4
- package/dist/interface/mcp/tools/check-duplicate-candidate.js.map +1 -1
- package/dist/interface/mcp/tools/compare-candidates.d.ts +2 -2
- package/dist/interface/mcp/tools/compare-candidates.js +8 -4
- package/dist/interface/mcp/tools/compare-candidates.js.map +1 -1
- package/dist/interface/mcp/tools/create-call-batch.d.ts +2 -2
- package/dist/interface/mcp/tools/create-call-batch.js +10 -6
- package/dist/interface/mcp/tools/create-call-batch.js.map +1 -1
- package/dist/interface/mcp/tools/delete-cv.d.ts +2 -2
- package/dist/interface/mcp/tools/delete-cv.js +29 -2
- package/dist/interface/mcp/tools/delete-cv.js.map +1 -1
- package/dist/interface/mcp/tools/delete-job-posting.d.ts +2 -2
- package/dist/interface/mcp/tools/delete-job-posting.js +32 -3
- package/dist/interface/mcp/tools/delete-job-posting.js.map +1 -1
- package/dist/interface/mcp/tools/export-results.d.ts +2 -2
- package/dist/interface/mcp/tools/export-results.js +7 -3
- package/dist/interface/mcp/tools/export-results.js.map +1 -1
- package/dist/interface/mcp/tools/filter-candidates.d.ts +2 -2
- package/dist/interface/mcp/tools/filter-candidates.js +17 -9
- package/dist/interface/mcp/tools/filter-candidates.js.map +1 -1
- package/dist/interface/mcp/tools/find-similar-candidates.d.ts +3 -0
- package/dist/interface/mcp/tools/find-similar-candidates.js +39 -0
- package/dist/interface/mcp/tools/find-similar-candidates.js.map +1 -0
- package/dist/interface/mcp/tools/generate-screening-report.d.ts +2 -2
- package/dist/interface/mcp/tools/generate-screening-report.js +8 -4
- package/dist/interface/mcp/tools/generate-screening-report.js.map +1 -1
- package/dist/interface/mcp/tools/get-call-batch-status.d.ts +2 -2
- package/dist/interface/mcp/tools/get-call-batch-status.js +7 -3
- package/dist/interface/mcp/tools/get-call-batch-status.js.map +1 -1
- package/dist/interface/mcp/tools/get-candidate-history.d.ts +2 -2
- package/dist/interface/mcp/tools/get-candidate-history.js +7 -3
- package/dist/interface/mcp/tools/get-candidate-history.js.map +1 -1
- package/dist/interface/mcp/tools/get-cv-chunks.d.ts +2 -2
- package/dist/interface/mcp/tools/get-cv-chunks.js +13 -9
- package/dist/interface/mcp/tools/get-cv-chunks.js.map +1 -1
- package/dist/interface/mcp/tools/get-cv-detail.d.ts +2 -2
- package/dist/interface/mcp/tools/get-cv-detail.js +6 -2
- package/dist/interface/mcp/tools/get-cv-detail.js.map +1 -1
- package/dist/interface/mcp/tools/get-cv-versions.d.ts +2 -2
- package/dist/interface/mcp/tools/get-cv-versions.js +6 -2
- package/dist/interface/mcp/tools/get-cv-versions.js.map +1 -1
- package/dist/interface/mcp/tools/get-job-posting.d.ts +2 -2
- package/dist/interface/mcp/tools/get-job-posting.js +7 -3
- package/dist/interface/mcp/tools/get-job-posting.js.map +1 -1
- package/dist/interface/mcp/tools/get-pending-actions.d.ts +2 -2
- package/dist/interface/mcp/tools/get-pending-actions.js +6 -2
- package/dist/interface/mcp/tools/get-pending-actions.js.map +1 -1
- package/dist/interface/mcp/tools/get-pipeline-candidates.d.ts +2 -2
- package/dist/interface/mcp/tools/get-pipeline-candidates.js +7 -3
- package/dist/interface/mcp/tools/get-pipeline-candidates.js.map +1 -1
- package/dist/interface/mcp/tools/get-pipeline.d.ts +2 -2
- package/dist/interface/mcp/tools/get-pipeline.js +7 -3
- package/dist/interface/mcp/tools/get-pipeline.js.map +1 -1
- package/dist/interface/mcp/tools/get-screening-history.d.ts +2 -2
- package/dist/interface/mcp/tools/get-screening-history.js +7 -3
- package/dist/interface/mcp/tools/get-screening-history.js.map +1 -1
- package/dist/interface/mcp/tools/get-server-status.d.ts +2 -2
- package/dist/interface/mcp/tools/get-server-status.js +6 -2
- package/dist/interface/mcp/tools/get-server-status.js.map +1 -1
- package/dist/interface/mcp/tools/get-stats.d.ts +2 -2
- package/dist/interface/mcp/tools/get-stats.js +6 -2
- package/dist/interface/mcp/tools/get-stats.js.map +1 -1
- package/dist/interface/mcp/tools/list-cvs.d.ts +2 -2
- package/dist/interface/mcp/tools/list-cvs.js +32 -11
- package/dist/interface/mcp/tools/list-cvs.js.map +1 -1
- package/dist/interface/mcp/tools/list-job-postings.d.ts +2 -2
- package/dist/interface/mcp/tools/list-job-postings.js +6 -2
- package/dist/interface/mcp/tools/list-job-postings.js.map +1 -1
- package/dist/interface/mcp/tools/list-reference-cvs.d.ts +2 -2
- package/dist/interface/mcp/tools/list-reference-cvs.js +7 -3
- package/dist/interface/mcp/tools/list-reference-cvs.js.map +1 -1
- package/dist/interface/mcp/tools/manage-tags.d.ts +2 -2
- package/dist/interface/mcp/tools/manage-tags.js +8 -4
- package/dist/interface/mcp/tools/manage-tags.js.map +1 -1
- package/dist/interface/mcp/tools/match-candidates.d.ts +2 -2
- package/dist/interface/mcp/tools/match-candidates.js +26 -12
- package/dist/interface/mcp/tools/match-candidates.js.map +1 -1
- package/dist/interface/mcp/tools/reindex-all.d.ts +3 -0
- package/dist/interface/mcp/tools/reindex-all.js +31 -0
- package/dist/interface/mcp/tools/reindex-all.js.map +1 -0
- package/dist/interface/mcp/tools/remove-reference-cv.d.ts +2 -2
- package/dist/interface/mcp/tools/remove-reference-cv.js +29 -2
- package/dist/interface/mcp/tools/remove-reference-cv.js.map +1 -1
- package/dist/interface/mcp/tools/reset-database.d.ts +2 -2
- package/dist/interface/mcp/tools/reset-database.js +29 -2
- package/dist/interface/mcp/tools/reset-database.js.map +1 -1
- package/dist/interface/mcp/tools/semantic-search.d.ts +2 -2
- package/dist/interface/mcp/tools/semantic-search.js +24 -11
- package/dist/interface/mcp/tools/semantic-search.js.map +1 -1
- package/dist/interface/mcp/tools/set-pending-action.d.ts +2 -2
- package/dist/interface/mcp/tools/set-pending-action.js +16 -12
- package/dist/interface/mcp/tools/set-pending-action.js.map +1 -1
- package/dist/interface/mcp/tools/update-call-outcome.d.ts +2 -2
- package/dist/interface/mcp/tools/update-call-outcome.js +10 -6
- package/dist/interface/mcp/tools/update-call-outcome.js.map +1 -1
- package/dist/interface/mcp/tools/update-cv.d.ts +2 -2
- package/dist/interface/mcp/tools/update-cv.js +16 -6
- package/dist/interface/mcp/tools/update-cv.js.map +1 -1
- package/dist/interface/mcp/tools/update-job-posting.d.ts +2 -2
- package/dist/interface/mcp/tools/update-job-posting.js +16 -12
- package/dist/interface/mcp/tools/update-job-posting.js.map +1 -1
- package/dist/interface/mcp/tools/update-pipeline-status.d.ts +2 -2
- package/dist/interface/mcp/tools/update-pipeline-status.js +9 -5
- package/dist/interface/mcp/tools/update-pipeline-status.js.map +1 -1
- package/dist/interface/mcp/types.d.ts +2 -0
- package/dist/shared/data/skill-graph.json +115 -0
- package/dist/shared/data/skill-map.json +104 -0
- package/dist/shared/mcp/logger.d.ts +7 -0
- package/dist/shared/mcp/logger.js +20 -0
- package/dist/shared/mcp/logger.js.map +1 -0
- package/dist/shared/mcp/progress.d.ts +6 -0
- package/dist/shared/mcp/progress.js +33 -0
- package/dist/shared/mcp/progress.js.map +1 -0
- package/dist/shared/services/skill-graph.d.ts +11 -0
- package/dist/shared/services/skill-graph.js +84 -0
- package/dist/shared/services/skill-graph.js.map +1 -0
- package/dist/shared/services/skill-normalizer.d.ts +6 -0
- package/dist/shared/services/skill-normalizer.js +32 -0
- package/dist/shared/services/skill-normalizer.js.map +1 -0
- package/dist/shared/types/index.d.ts +3 -1
- package/dist/shared/utils/chunking.d.ts +9 -1
- package/dist/shared/utils/chunking.js +46 -36
- package/dist/shared/utils/chunking.js.map +1 -1
- package/dist/shared/utils/skill-migration.d.ts +6 -0
- package/dist/shared/utils/skill-migration.js +35 -0
- package/dist/shared/utils/skill-migration.js.map +1 -0
- package/package.json +12 -6
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@ src/
|
|
|
31
31
|
| mammoth | DOCX text extraction |
|
|
32
32
|
| Tesseract.js | OCR for image-based PDFs |
|
|
33
33
|
|
|
34
|
-
## MCP Tools (
|
|
34
|
+
## MCP Tools (38)
|
|
35
35
|
|
|
36
36
|
### CV Management
|
|
37
37
|
|
|
@@ -45,6 +45,7 @@ src/
|
|
|
45
45
|
| `get_cv_versions` | Version history |
|
|
46
46
|
| `list_cvs` | Paginated candidate list with sorting |
|
|
47
47
|
| `manage_tags` | Add/remove/list tags |
|
|
48
|
+
| `check_duplicate_candidate` | Check if a candidate already exists by name or email before importing |
|
|
48
49
|
|
|
49
50
|
### Search & Matching
|
|
50
51
|
|
|
@@ -53,6 +54,7 @@ src/
|
|
|
53
54
|
| `semantic_search` | Vector similarity search with optional section filter |
|
|
54
55
|
| `filter_candidates` | Structured filtering (skills, location, experience, languages, tags) |
|
|
55
56
|
| `match_candidates` | Composite scoring: semantic similarity + skill overlap + loyalty factor |
|
|
57
|
+
| `compare_candidates` | Compare 2–5 candidates side by side, optionally with job skill match scores |
|
|
56
58
|
| `export_results` | Export as JSON or CSV |
|
|
57
59
|
|
|
58
60
|
### Job Posting Management
|
|
@@ -65,6 +67,14 @@ src/
|
|
|
65
67
|
| `update_job_posting` | Update existing job posting |
|
|
66
68
|
| `delete_job_posting` | Delete a job posting |
|
|
67
69
|
|
|
70
|
+
### Reference CVs
|
|
71
|
+
|
|
72
|
+
| Tool | Description |
|
|
73
|
+
|---|---|
|
|
74
|
+
| `add_reference_cv` | Mark a candidate as a reference/benchmark CV for a job posting |
|
|
75
|
+
| `list_reference_cvs` | List all reference CVs for a job posting |
|
|
76
|
+
| `remove_reference_cv` | Remove a reference CV from a job posting |
|
|
77
|
+
|
|
68
78
|
### Recruitment Pipeline
|
|
69
79
|
|
|
70
80
|
| Tool | Description |
|
|
@@ -74,17 +84,33 @@ src/
|
|
|
74
84
|
| `add_pipeline_note` | Add notes, call attempts, or interview records |
|
|
75
85
|
| `set_pending_action` | Track pending actions with due dates and owner (us/candidate) |
|
|
76
86
|
| `get_pipeline` | Full pipeline view for a job posting |
|
|
87
|
+
| `get_pipeline_candidates` | Lightweight list of candidates in a pipeline (ID, name, status only) |
|
|
77
88
|
| `get_candidate_history` | Candidate's recruitment history across all postings |
|
|
78
89
|
| `get_pending_actions` | All overdue and upcoming pending actions |
|
|
79
90
|
|
|
80
91
|
**Pipeline statuses:** `new` → `contacted` → `unreachable` · `not_interested` · `interview_scheduled` → `no_show` · `interviewed` → `rejected` · `offer_sent` → `hired`
|
|
81
92
|
|
|
93
|
+
### Screening
|
|
94
|
+
|
|
95
|
+
| Tool | Description |
|
|
96
|
+
|---|---|
|
|
97
|
+
| `get_screening_history` | Shortlisted, screened out, and in-pipeline candidates for a job posting |
|
|
98
|
+
| `generate_screening_report` | Summary report with statistics, shortlisted, screened out, and pending candidates |
|
|
99
|
+
|
|
100
|
+
### Call Batches
|
|
101
|
+
|
|
102
|
+
| Tool | Description |
|
|
103
|
+
|---|---|
|
|
104
|
+
| `create_call_batch` | Create a call batch: assign candidates to a person for calling |
|
|
105
|
+
| `get_call_batch_status` | Status of a call batch: candidates, phone numbers, and call outcomes |
|
|
106
|
+
| `update_call_outcome` | Update call outcome: `pending`, `reached`, or `unreachable` |
|
|
107
|
+
|
|
82
108
|
### System
|
|
83
109
|
|
|
84
110
|
| Tool | Description |
|
|
85
111
|
|---|---|
|
|
86
112
|
| `get_stats` | Database statistics (candidates, skills, locations, etc.) |
|
|
87
|
-
| `get_server_status` | Server
|
|
113
|
+
| `get_server_status` | Server version, dashboard URL, connected clients, config |
|
|
88
114
|
| `reset_database` | Drop and recreate all tables |
|
|
89
115
|
|
|
90
116
|
## How It Works
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
2
|
+
import * as lancedb from '@lancedb/lancedb';
|
|
3
|
+
import { LanceDbChunkRepository } from '../../infrastructure/persistence/lancedb-chunk-repository.js';
|
|
4
|
+
import { getOrCreateTable, getChunkSeedRow, TABLE_NAMES } from '../../infrastructure/persistence/database.js';
|
|
5
|
+
import { nanoid } from 'nanoid';
|
|
6
|
+
const TEST_DB_PATH = './data/test-lancedb-' + nanoid(6);
|
|
7
|
+
describe('hybridSearch', () => {
|
|
8
|
+
let db;
|
|
9
|
+
let repo;
|
|
10
|
+
const mockChunks = [
|
|
11
|
+
{
|
|
12
|
+
id: 'chunk-1',
|
|
13
|
+
candidateId: 'cand-1',
|
|
14
|
+
sectionType: 'experience',
|
|
15
|
+
content: 'Senior React Developer at Acme Corp with 5 years TypeScript experience',
|
|
16
|
+
vector: new Array(1536).fill(0).map(() => Math.random()),
|
|
17
|
+
metadata: {},
|
|
18
|
+
createdAt: new Date().toISOString(),
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: 'chunk-2',
|
|
22
|
+
candidateId: 'cand-2',
|
|
23
|
+
sectionType: 'experience',
|
|
24
|
+
content: 'Java Backend Engineer at BigBank with Spring Boot and Kubernetes',
|
|
25
|
+
vector: new Array(1536).fill(0).map(() => Math.random()),
|
|
26
|
+
metadata: {},
|
|
27
|
+
createdAt: new Date().toISOString(),
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: 'chunk-3',
|
|
31
|
+
candidateId: 'cand-3',
|
|
32
|
+
sectionType: 'skills',
|
|
33
|
+
content: 'React, TypeScript, Node.js, Next.js, GraphQL, Docker',
|
|
34
|
+
vector: new Array(1536).fill(0).map(() => Math.random()),
|
|
35
|
+
metadata: {},
|
|
36
|
+
createdAt: new Date().toISOString(),
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
beforeAll(async () => {
|
|
40
|
+
db = await lancedb.connect(TEST_DB_PATH);
|
|
41
|
+
await getOrCreateTable(db, TABLE_NAMES.chunks, getChunkSeedRow());
|
|
42
|
+
repo = new LanceDbChunkRepository(db);
|
|
43
|
+
await repo.saveMany(mockChunks);
|
|
44
|
+
await repo.optimizeIndex();
|
|
45
|
+
});
|
|
46
|
+
afterAll(async () => {
|
|
47
|
+
const { rm } = await import('node:fs/promises');
|
|
48
|
+
await rm(TEST_DB_PATH, { recursive: true, force: true });
|
|
49
|
+
});
|
|
50
|
+
it('returns results for FTS text query', async () => {
|
|
51
|
+
const dummyVector = new Array(1536).fill(0);
|
|
52
|
+
const results = await repo.hybridSearch(dummyVector, 'React TypeScript', 10);
|
|
53
|
+
expect(results.length).toBeGreaterThan(0);
|
|
54
|
+
const contents = results.map(r => r.chunk.content);
|
|
55
|
+
expect(contents.some(c => c.includes('React'))).toBe(true);
|
|
56
|
+
});
|
|
57
|
+
it('falls back to pure vector search when textQuery is empty', async () => {
|
|
58
|
+
const dummyVector = mockChunks[0].vector;
|
|
59
|
+
const results = await repo.hybridSearch(dummyVector, '', 10);
|
|
60
|
+
expect(results.length).toBeGreaterThan(0);
|
|
61
|
+
});
|
|
62
|
+
it('filters with WHERE clause', async () => {
|
|
63
|
+
const dummyVector = new Array(1536).fill(0);
|
|
64
|
+
const results = await repo.hybridSearch(dummyVector, 'React', 10, "section_type = 'skills'");
|
|
65
|
+
for (const r of results) {
|
|
66
|
+
expect(r.chunk.sectionType).toBe('skills');
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
//# sourceMappingURL=hybrid-search.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hybrid-search.test.js","sourceRoot":"","sources":["../../../src/__tests__/integration/hybrid-search.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AACnE,OAAO,KAAK,OAAO,MAAM,kBAAkB,CAAC;AAE5C,OAAO,EAAE,sBAAsB,EAAE,MAAM,8DAA8D,CAAC;AACtG,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,8CAA8C,CAAC;AAE9G,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,MAAM,YAAY,GAAG,sBAAsB,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;AAExD,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,IAAI,EAAc,CAAC;IACnB,IAAI,IAA4B,CAAC;IAEjC,MAAM,UAAU,GAAc;QAC5B;YACE,EAAE,EAAE,SAAS;YACb,WAAW,EAAE,QAAQ;YACrB,WAAW,EAAE,YAAY;YACzB,OAAO,EAAE,wEAAwE;YACjF,MAAM,EAAE,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACxD,QAAQ,EAAE,EAAE;YACZ,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACpC;QACD;YACE,EAAE,EAAE,SAAS;YACb,WAAW,EAAE,QAAQ;YACrB,WAAW,EAAE,YAAY;YACzB,OAAO,EAAE,kEAAkE;YAC3E,MAAM,EAAE,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACxD,QAAQ,EAAE,EAAE;YACZ,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACpC;QACD;YACE,EAAE,EAAE,SAAS;YACb,WAAW,EAAE,QAAQ;YACrB,WAAW,EAAE,QAAQ;YACrB,OAAO,EAAE,sDAAsD;YAC/D,MAAM,EAAE,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACxD,QAAQ,EAAE,EAAE;YACZ,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACpC;KACF,CAAC;IAEF,SAAS,CAAC,KAAK,IAAI,EAAE;QACnB,EAAE,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;QACzC,MAAM,gBAAgB,CAAC,EAAE,EAAE,WAAW,CAAC,MAAM,EAAE,eAAe,EAAE,CAAC,CAAC;QAClE,IAAI,GAAG,IAAI,sBAAsB,CAAC,EAAE,CAAC,CAAC;QACtC,MAAM,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;QAChC,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;IAC7B,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,KAAK,IAAI,EAAE;QAClB,MAAM,EAAE,EAAE,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAC;QAChD,MAAM,EAAE,CAAC,YAAY,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oCAAoC,EAAE,KAAK,IAAI,EAAE;QAClD,MAAM,WAAW,GAAG,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC5C,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,WAAW,EAAE,kBAAkB,EAAE,EAAE,CAAC,CAAC;QAC7E,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QAC1C,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACnD,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0DAA0D,EAAE,KAAK,IAAI,EAAE;QACxE,MAAM,WAAW,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;QACzC,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,WAAW,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;QAC7D,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2BAA2B,EAAE,KAAK,IAAI,EAAE;QACzC,MAAM,WAAW,GAAG,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC5C,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,WAAW,EAAE,OAAO,EAAE,EAAE,EAAE,yBAAyB,CAAC,CAAC;QAC7F,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACxB,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC7C,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { createChunks, buildContextHeader, splitByTokens } from '../../shared/utils/chunking.js';
|
|
3
|
+
describe('buildContextHeader', () => {
|
|
4
|
+
it('builds header from candidate info', () => {
|
|
5
|
+
const header = buildContextHeader({
|
|
6
|
+
name: 'Ali Yılmaz',
|
|
7
|
+
experienceYears: 8,
|
|
8
|
+
location: 'Istanbul',
|
|
9
|
+
topSkills: ['React', 'TypeScript', 'Node.js'],
|
|
10
|
+
});
|
|
11
|
+
expect(header).toContain('Ali Yılmaz');
|
|
12
|
+
expect(header).toContain('8');
|
|
13
|
+
expect(header).toContain('Istanbul');
|
|
14
|
+
expect(header).toContain('React');
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
describe('splitByTokens', () => {
|
|
18
|
+
it('does not split short text', () => {
|
|
19
|
+
const parts = splitByTokens('Hello world', 512, 64);
|
|
20
|
+
expect(parts).toHaveLength(1);
|
|
21
|
+
expect(parts[0]).toBe('Hello world');
|
|
22
|
+
});
|
|
23
|
+
it('splits long text with overlap', () => {
|
|
24
|
+
const longText = 'word '.repeat(600);
|
|
25
|
+
const parts = splitByTokens(longText, 512, 64);
|
|
26
|
+
expect(parts.length).toBeGreaterThan(1);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
describe('createChunks', () => {
|
|
30
|
+
it('does NOT produce a full chunk', () => {
|
|
31
|
+
const extracted = {
|
|
32
|
+
name: 'Test User',
|
|
33
|
+
experience: [],
|
|
34
|
+
education: [],
|
|
35
|
+
skills: ['React'],
|
|
36
|
+
languages: [],
|
|
37
|
+
certifications: [],
|
|
38
|
+
projects: [],
|
|
39
|
+
};
|
|
40
|
+
const chunks = createChunks(extracted, 'some raw text', {
|
|
41
|
+
name: 'Test User',
|
|
42
|
+
experienceYears: 1,
|
|
43
|
+
location: '',
|
|
44
|
+
topSkills: ['React'],
|
|
45
|
+
});
|
|
46
|
+
const sectionTypes = chunks.map(c => c.sectionType);
|
|
47
|
+
expect(sectionTypes).not.toContain('full');
|
|
48
|
+
});
|
|
49
|
+
it('prepends contextual header to each chunk', () => {
|
|
50
|
+
const extracted = {
|
|
51
|
+
name: 'Ali Yılmaz',
|
|
52
|
+
experience: [{ company: 'Acme', position: 'Dev', description: 'Built stuff' }],
|
|
53
|
+
education: [],
|
|
54
|
+
skills: ['React'],
|
|
55
|
+
languages: [],
|
|
56
|
+
certifications: [],
|
|
57
|
+
projects: [],
|
|
58
|
+
};
|
|
59
|
+
const chunks = createChunks(extracted, 'raw', {
|
|
60
|
+
name: 'Ali Yılmaz',
|
|
61
|
+
experienceYears: 5,
|
|
62
|
+
location: 'Istanbul',
|
|
63
|
+
topSkills: ['React'],
|
|
64
|
+
});
|
|
65
|
+
for (const chunk of chunks) {
|
|
66
|
+
expect(chunk.content).toContain('[Ali Yılmaz');
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
//# sourceMappingURL=chunking.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunking.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/chunking.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
describe('composite score calculation', () => {
|
|
3
|
+
it('without references: 50% vector + 30% skill + 20% loyalty', () => {
|
|
4
|
+
const vectorScore = 0.8;
|
|
5
|
+
const skillOverlap = 0.6;
|
|
6
|
+
const loyaltyFactor = 1.0;
|
|
7
|
+
const expected = 0.5 * vectorScore + 0.3 * skillOverlap + 0.2 * loyaltyFactor;
|
|
8
|
+
expect(expected).toBeCloseTo(0.78);
|
|
9
|
+
});
|
|
10
|
+
it('with references: 40% vector + 25% skill + 15% loyalty + 20% reference', () => {
|
|
11
|
+
const vectorScore = 0.8;
|
|
12
|
+
const skillOverlap = 0.6;
|
|
13
|
+
const loyaltyFactor = 1.0;
|
|
14
|
+
const referenceBoost = 0.7;
|
|
15
|
+
const expected = 0.4 * vectorScore + 0.25 * skillOverlap + 0.15 * loyaltyFactor + 0.2 * referenceBoost;
|
|
16
|
+
expect(expected).toBeCloseTo(0.76);
|
|
17
|
+
});
|
|
18
|
+
it('skill overlap defaults to 1.0 when no required skills', () => {
|
|
19
|
+
// When requiredSkills is empty, computeSkillOverlap returns { skillOverlap: 1 }
|
|
20
|
+
// This means the skill component contributes its full weight to the score
|
|
21
|
+
const vectorScore = 0.8;
|
|
22
|
+
const skillOverlap = 1.0; // default when no requirements
|
|
23
|
+
const loyaltyFactor = 1.0;
|
|
24
|
+
const expected = 0.5 * vectorScore + 0.3 * skillOverlap + 0.2 * loyaltyFactor;
|
|
25
|
+
expect(expected).toBeCloseTo(0.9);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
//# sourceMappingURL=composite-score.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"composite-score.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/composite-score.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAE9C,QAAQ,CAAC,6BAA6B,EAAE,GAAG,EAAE;IAC3C,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;QAClE,MAAM,WAAW,GAAG,GAAG,CAAC;QACxB,MAAM,YAAY,GAAG,GAAG,CAAC;QACzB,MAAM,aAAa,GAAG,GAAG,CAAC;QAC1B,MAAM,QAAQ,GAAG,GAAG,GAAG,WAAW,GAAG,GAAG,GAAG,YAAY,GAAG,GAAG,GAAG,aAAa,CAAC;QAC9E,MAAM,CAAC,QAAQ,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uEAAuE,EAAE,GAAG,EAAE;QAC/E,MAAM,WAAW,GAAG,GAAG,CAAC;QACxB,MAAM,YAAY,GAAG,GAAG,CAAC;QACzB,MAAM,aAAa,GAAG,GAAG,CAAC;QAC1B,MAAM,cAAc,GAAG,GAAG,CAAC;QAC3B,MAAM,QAAQ,GAAG,GAAG,GAAG,WAAW,GAAG,IAAI,GAAG,YAAY,GAAG,IAAI,GAAG,aAAa,GAAG,GAAG,GAAG,cAAc,CAAC;QACvG,MAAM,CAAC,QAAQ,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;QAC/D,gFAAgF;QAChF,0EAA0E;QAC1E,MAAM,WAAW,GAAG,GAAG,CAAC;QACxB,MAAM,YAAY,GAAG,GAAG,CAAC,CAAC,+BAA+B;QACzD,MAAM,aAAa,GAAG,GAAG,CAAC;QAC1B,MAAM,QAAQ,GAAG,GAAG,GAAG,WAAW,GAAG,GAAG,GAAG,YAAY,GAAG,GAAG,GAAG,aAAa,CAAC;QAC9E,MAAM,CAAC,QAAQ,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { computeCentroidVector, findCommonSkills } from '../../application/use-cases/find-similar-candidates.js';
|
|
3
|
+
describe('computeCentroidVector', () => {
|
|
4
|
+
it('computes average of vectors', () => {
|
|
5
|
+
const vectors = [
|
|
6
|
+
[1, 0, 0],
|
|
7
|
+
[0, 1, 0],
|
|
8
|
+
[0, 0, 1],
|
|
9
|
+
];
|
|
10
|
+
const centroid = computeCentroidVector(vectors);
|
|
11
|
+
expect(centroid[0]).toBeCloseTo(1 / 3);
|
|
12
|
+
expect(centroid[1]).toBeCloseTo(1 / 3);
|
|
13
|
+
expect(centroid[2]).toBeCloseTo(1 / 3);
|
|
14
|
+
});
|
|
15
|
+
it('returns zero vector for empty input', () => {
|
|
16
|
+
const centroid = computeCentroidVector([]);
|
|
17
|
+
expect(centroid).toEqual([]);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
describe('findCommonSkills', () => {
|
|
21
|
+
it('finds intersection of skill arrays', () => {
|
|
22
|
+
const common = findCommonSkills(['react', 'typescript', 'nodejs'], ['typescript', 'python', 'nodejs']);
|
|
23
|
+
expect(common).toEqual(['typescript', 'nodejs']);
|
|
24
|
+
});
|
|
25
|
+
it('includes graph-based partial matches', () => {
|
|
26
|
+
// nextjs is a child of react in the skill graph
|
|
27
|
+
// findCommonSkills checks: for each skill in a, if bSkill is a child of skill → partial match
|
|
28
|
+
const common = findCommonSkills(['react'], ['nextjs']);
|
|
29
|
+
// getRelationship('nextjs', 'react') → 'child' because nextjs is in react's children
|
|
30
|
+
expect(common).toEqual(['react (via nextjs)']);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
//# sourceMappingURL=find-similar-candidates.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"find-similar-candidates.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/find-similar-candidates.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,wDAAwD,CAAC;AAEjH,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACrC,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;QACrC,MAAM,OAAO,GAAG;YACd,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;YACT,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;YACT,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;SACV,CAAC;QACF,MAAM,QAAQ,GAAG,qBAAqB,CAAC,OAAO,CAAC,CAAC;QAChD,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACvC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACvC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,MAAM,QAAQ,GAAG,qBAAqB,CAAC,EAAE,CAAC,CAAC;QAC3C,MAAM,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC/B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,MAAM,MAAM,GAAG,gBAAgB,CAAC,CAAC,OAAO,EAAE,YAAY,EAAE,QAAQ,CAAC,EAAE,CAAC,YAAY,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;QACvG,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,gDAAgD;QAChD,8FAA8F;QAC9F,MAAM,MAAM,GAAG,gBAAgB,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;QACvD,qFAAqF;QACrF,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { QueryEnhancerService } from '../../application/services/query-enhancer.js';
|
|
3
|
+
describe('QueryEnhancerService', () => {
|
|
4
|
+
it('returns hypotheticalCV and keywords', async () => {
|
|
5
|
+
const mockOpenAI = {
|
|
6
|
+
responses: {
|
|
7
|
+
create: vi.fn().mockResolvedValue({
|
|
8
|
+
output_text: 'Senior React developer with 5 years experience in TypeScript and Node.js. Built scalable web applications.\n\nKEYWORDS:\nReact, TypeScript, Node.js, frontend, senior developer',
|
|
9
|
+
}),
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
const service = new QueryEnhancerService(mockOpenAI);
|
|
13
|
+
const result = await service.enhanceQuery('React frontend developer Istanbul');
|
|
14
|
+
expect(result.hypotheticalCV).toBeTruthy();
|
|
15
|
+
expect(result.hypotheticalCV.length).toBeGreaterThan(10);
|
|
16
|
+
expect(result.keywords).toBeInstanceOf(Array);
|
|
17
|
+
expect(result.keywords.length).toBeGreaterThan(0);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
//# sourceMappingURL=query-enhancer.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-enhancer.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/query-enhancer.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAClD,OAAO,EAAE,oBAAoB,EAAE,MAAM,8CAA8C,CAAC;AAEpF,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;IACpC,EAAE,CAAC,qCAAqC,EAAE,KAAK,IAAI,EAAE;QACnD,MAAM,UAAU,GAAG;YACjB,SAAS,EAAE;gBACT,MAAM,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC;oBAChC,WAAW,EAAE,iLAAiL;iBAC/L,CAAC;aACH;SACF,CAAC;QAEF,MAAM,OAAO,GAAG,IAAI,oBAAoB,CAAC,UAAiB,CAAC,CAAC;QAC5D,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,mCAAmC,CAAC,CAAC;QAE/E,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,UAAU,EAAE,CAAC;QAC3C,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC;QACzD,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;QAC9C,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { SkillGraphService } from '../../shared/services/skill-graph.js';
|
|
3
|
+
describe('SkillGraphService', () => {
|
|
4
|
+
const service = new SkillGraphService();
|
|
5
|
+
describe('expandSkill', () => {
|
|
6
|
+
it('returns skill + children at depth 1', () => {
|
|
7
|
+
const expanded = service.expandSkill('react', 1);
|
|
8
|
+
expect(expanded).toContain('react');
|
|
9
|
+
expect(expanded).toContain('nextjs');
|
|
10
|
+
});
|
|
11
|
+
it('returns just the skill for unknown skills', () => {
|
|
12
|
+
expect(service.expandSkill('obscure-skill')).toEqual(['obscure-skill']);
|
|
13
|
+
});
|
|
14
|
+
it('does not expand beyond specified depth', () => {
|
|
15
|
+
const depth1 = service.expandSkill('javascript', 1);
|
|
16
|
+
expect(depth1).toContain('react');
|
|
17
|
+
expect(depth1).not.toContain('nextjs'); // nextjs is child of react (depth 2)
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
describe('getAncestors', () => {
|
|
21
|
+
it('returns ancestor chain', () => {
|
|
22
|
+
const ancestors = service.getAncestors('nextjs');
|
|
23
|
+
expect(ancestors).toEqual(['react', 'javascript']);
|
|
24
|
+
});
|
|
25
|
+
it('returns empty for root skills', () => {
|
|
26
|
+
expect(service.getAncestors('javascript')).toEqual([]);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
describe('findGaps', () => {
|
|
30
|
+
it('identifies missing and partial matches', () => {
|
|
31
|
+
const result = service.findGaps(['nextjs', 'typescript', 'docker'], ['react', 'nodejs', 'kubernetes']);
|
|
32
|
+
expect(result.partialMatch).toContain('react');
|
|
33
|
+
expect(result.partialMatch).toContain('kubernetes');
|
|
34
|
+
expect(result.missing).toContain('nodejs');
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
//# sourceMappingURL=skill-graph.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-graph.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/skill-graph.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAC;AAEzE,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IACjC,MAAM,OAAO,GAAG,IAAI,iBAAiB,EAAE,CAAC;IAExC,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;QAC3B,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;YAC7C,MAAM,QAAQ,GAAG,OAAO,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;YACjD,MAAM,CAAC,QAAQ,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;YACpC,MAAM,CAAC,QAAQ,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QACvC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;YACnD,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,eAAe,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC;QAC1E,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;YAChD,MAAM,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC;YACpD,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;YAClC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,qCAAqC;QAC/E,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;QAC5B,EAAE,CAAC,wBAAwB,EAAE,GAAG,EAAE;YAChC,MAAM,SAAS,GAAG,OAAO,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;YACjD,MAAM,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC;QACrD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;YACvC,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACzD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,UAAU,EAAE,GAAG,EAAE;QACxB,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;YAChD,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,CAC7B,CAAC,QAAQ,EAAE,YAAY,EAAE,QAAQ,CAAC,EAClC,CAAC,OAAO,EAAE,QAAQ,EAAE,YAAY,CAAC,CAClC,CAAC;YACF,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;YAC/C,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;YACpD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QAC7C,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { SkillNormalizerService } from '../../shared/services/skill-normalizer.js';
|
|
3
|
+
describe('SkillNormalizerService', () => {
|
|
4
|
+
const service = new SkillNormalizerService();
|
|
5
|
+
describe('normalize', () => {
|
|
6
|
+
it('normalizes known alias to canonical name', () => {
|
|
7
|
+
expect(service.normalize('Node.js')).toBe('nodejs');
|
|
8
|
+
expect(service.normalize('node')).toBe('nodejs');
|
|
9
|
+
expect(service.normalize('node js')).toBe('nodejs');
|
|
10
|
+
});
|
|
11
|
+
it('normalizes canonical name to itself', () => {
|
|
12
|
+
expect(service.normalize('react')).toBe('react');
|
|
13
|
+
expect(service.normalize('React')).toBe('react');
|
|
14
|
+
});
|
|
15
|
+
it('returns lowercase original for unknown skills', () => {
|
|
16
|
+
expect(service.normalize('SomeObscureFramework')).toBe('someobscureframework');
|
|
17
|
+
});
|
|
18
|
+
it('handles C# edge case', () => {
|
|
19
|
+
expect(service.normalize('C#')).toBe('csharp');
|
|
20
|
+
expect(service.normalize('c sharp')).toBe('csharp');
|
|
21
|
+
});
|
|
22
|
+
it('handles .NET edge case', () => {
|
|
23
|
+
expect(service.normalize('.NET')).toBe('dotnet');
|
|
24
|
+
expect(service.normalize('ASP.NET')).toBe('dotnet');
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
describe('normalizeAll', () => {
|
|
28
|
+
it('normalizes and deduplicates', () => {
|
|
29
|
+
const result = service.normalizeAll(['Node.js', 'node', 'React', 'react.js', 'Python']);
|
|
30
|
+
expect(result).toEqual(['nodejs', 'react', 'python']);
|
|
31
|
+
});
|
|
32
|
+
it('preserves order of first occurrence', () => {
|
|
33
|
+
const result = service.normalizeAll(['React', 'Node.js', 'react.js']);
|
|
34
|
+
expect(result).toEqual(['react', 'nodejs']);
|
|
35
|
+
});
|
|
36
|
+
it('returns empty for empty input', () => {
|
|
37
|
+
expect(service.normalizeAll([])).toEqual([]);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
//# sourceMappingURL=skill-normalizer.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-normalizer.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/skill-normalizer.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"smoke.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/smoke.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAE9C,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;IAC1B,EAAE,CAAC,gCAAgC,EAAE,GAAG,EAAE;QACxC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACxB,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
export interface FtsIndexStatusDto {
|
|
2
|
+
exists: boolean;
|
|
3
|
+
numIndexedRows?: number;
|
|
4
|
+
numUnindexedRows?: number;
|
|
5
|
+
}
|
|
1
6
|
export interface StatsDto {
|
|
2
7
|
totalCandidates: number;
|
|
3
8
|
totalChunks: number;
|
|
@@ -7,4 +12,5 @@ export interface StatsDto {
|
|
|
7
12
|
tagCounts: Record<string, number>;
|
|
8
13
|
totalApplications: number;
|
|
9
14
|
totalApplicationEvents: number;
|
|
15
|
+
ftsIndex: FtsIndexStatusDto;
|
|
10
16
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type OpenAI from 'openai';
|
|
2
|
+
export interface EnhancedQuery {
|
|
3
|
+
hypotheticalCV: string;
|
|
4
|
+
keywords: string[];
|
|
5
|
+
}
|
|
6
|
+
export declare class QueryEnhancerService {
|
|
7
|
+
private openai;
|
|
8
|
+
constructor(openai: OpenAI);
|
|
9
|
+
enhanceQuery(originalQuery: string, location?: string): Promise<EnhancedQuery>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export class QueryEnhancerService {
|
|
2
|
+
openai;
|
|
3
|
+
constructor(openai) {
|
|
4
|
+
this.openai = openai;
|
|
5
|
+
}
|
|
6
|
+
async enhanceQuery(originalQuery, location) {
|
|
7
|
+
const queryWithLocation = location
|
|
8
|
+
? `${originalQuery}\nPreferred location: ${location}`
|
|
9
|
+
: originalQuery;
|
|
10
|
+
const response = await this.openai.responses.create({
|
|
11
|
+
model: 'gpt-5-mini',
|
|
12
|
+
input: [
|
|
13
|
+
{
|
|
14
|
+
role: 'system',
|
|
15
|
+
content: 'You are a recruitment assistant. Given a job search query, write a hypothetical ideal CV summary that would perfectly match this query. If a location is specified, prominently include it in the CV summary (e.g., "based in {location}"). Max 150 words. Also extract 5-10 key search keywords from the query. Respond in this exact format:\n\nCV_SUMMARY:\n<summary>\n\nKEYWORDS:\n<comma-separated keywords>',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
role: 'user',
|
|
19
|
+
content: queryWithLocation,
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
});
|
|
23
|
+
const text = response.output_text ?? '';
|
|
24
|
+
const cvMatch = text.match(/CV_SUMMARY:\s*\n([\s\S]*?)(?=\nKEYWORDS:)/);
|
|
25
|
+
const kwMatch = text.match(/KEYWORDS:\s*\n(.+)/);
|
|
26
|
+
const hypotheticalCV = cvMatch?.[1]?.trim() ?? originalQuery;
|
|
27
|
+
const keywords = kwMatch?.[1]
|
|
28
|
+
?.split(',')
|
|
29
|
+
.map((k) => k.trim())
|
|
30
|
+
.filter(Boolean) ?? originalQuery.split(/\s+/);
|
|
31
|
+
return { hypotheticalCV, keywords };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=query-enhancer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-enhancer.js","sourceRoot":"","sources":["../../../src/application/services/query-enhancer.ts"],"names":[],"mappings":"AAOA,MAAM,OAAO,oBAAoB;IACX;IAApB,YAAoB,MAAc;QAAd,WAAM,GAAN,MAAM,CAAQ;IAAG,CAAC;IAEtC,KAAK,CAAC,YAAY,CAAC,aAAqB,EAAE,QAAiB;QACzD,MAAM,iBAAiB,GAAG,QAAQ;YAChC,CAAC,CAAC,GAAG,aAAa,yBAAyB,QAAQ,EAAE;YACrD,CAAC,CAAC,aAAa,CAAC;QAElB,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC;YAClD,KAAK,EAAE,YAAY;YACnB,KAAK,EAAE;gBACL;oBACE,IAAI,EAAE,QAAQ;oBACd,OAAO,EACL,mZAAmZ;iBACtZ;gBACD;oBACE,IAAI,EAAE,MAAM;oBACZ,OAAO,EAAE,iBAAiB;iBAC3B;aACF;SACF,CAAC,CAAC;QAEH,MAAM,IAAI,GAAG,QAAQ,CAAC,WAAW,IAAI,EAAE,CAAC;QAExC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,2CAA2C,CAAC,CAAC;QACxE,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,oBAAoB,CAAC,CAAC;QAEjD,MAAM,cAAc,GAAG,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,aAAa,CAAC;QAC7D,MAAM,QAAQ,GAAG,OAAO,EAAE,CAAC,CAAC,CAAC;YAC3B,EAAE,KAAK,CAAC,GAAG,CAAC;aACX,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;aACpB,MAAM,CAAC,OAAO,CAAC,IAAI,aAAa,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAEjD,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC;IACtC,CAAC;CACF"}
|
|
@@ -5,6 +5,10 @@ import type { IDocumentParser } from '../ports/document-parser.js';
|
|
|
5
5
|
import type { IEmbeddingService } from '../ports/embedding-service.js';
|
|
6
6
|
import type { IStructuredExtractor } from '../ports/structured-extractor.js';
|
|
7
7
|
export type ProgressCallback = (step: string, message: string) => void;
|
|
8
|
+
export interface AddCvOptions {
|
|
9
|
+
source?: string;
|
|
10
|
+
pool?: string;
|
|
11
|
+
}
|
|
8
12
|
export declare class AddCvUseCase {
|
|
9
13
|
private candidateRepo;
|
|
10
14
|
private chunkRepo;
|
|
@@ -12,5 +16,5 @@ export declare class AddCvUseCase {
|
|
|
12
16
|
private embeddingService;
|
|
13
17
|
private extractor;
|
|
14
18
|
constructor(candidateRepo: ICandidateRepository, chunkRepo: IChunkRepository, parsers: IDocumentParser[], embeddingService: IEmbeddingService, extractor: IStructuredExtractor);
|
|
15
|
-
execute(filePath: string, onProgress?: ProgressCallback): Promise<Candidate>;
|
|
19
|
+
execute(filePath: string, options?: AddCvOptions, onProgress?: ProgressCallback): Promise<Candidate>;
|
|
16
20
|
}
|