@dolusoft/hirebase-mcp 1.3.5 → 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/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 +20 -0
- 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/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/ba31dbc0-8fe2-44bc-8214-6b3d717566a0.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 +105 -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
|
@@ -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":"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"}
|
|
@@ -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
|
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { nanoid } from 'nanoid';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
2
4
|
import { detectFileType } from '../../shared/utils/text.js';
|
|
3
5
|
import { createChunks } from '../../shared/utils/chunking.js';
|
|
4
6
|
import { UnsupportedFileTypeError, DuplicateCandidateError, DocumentParseError } from '../../shared/errors/index.js';
|
|
7
|
+
import { SkillNormalizerService } from '../../shared/services/skill-normalizer.js';
|
|
5
8
|
import { storeOriginalFile, resolveFilePath } from '../../shared/utils/file-storage.js';
|
|
6
9
|
export class AddCvUseCase {
|
|
7
10
|
candidateRepo;
|
|
@@ -16,9 +19,17 @@ export class AddCvUseCase {
|
|
|
16
19
|
this.embeddingService = embeddingService;
|
|
17
20
|
this.extractor = extractor;
|
|
18
21
|
}
|
|
19
|
-
async execute(filePath, onProgress) {
|
|
22
|
+
async execute(filePath, options, onProgress) {
|
|
20
23
|
// Resolve Unicode NFC/NFD filename normalization differences
|
|
21
24
|
const resolvedPath = await resolveFilePath(filePath);
|
|
25
|
+
// Compute SHA-256 hash of the file for duplicate detection
|
|
26
|
+
const fileBuffer = await readFile(resolvedPath);
|
|
27
|
+
const fileHash = createHash('sha256').update(fileBuffer).digest('hex');
|
|
28
|
+
onProgress?.('checking_duplicates', 'Checking for duplicate file...');
|
|
29
|
+
const existingByHash = await this.candidateRepo.findByFileHash(fileHash);
|
|
30
|
+
if (existingByHash) {
|
|
31
|
+
throw new DuplicateCandidateError(`File already imported as candidate "${existingByHash.name}" (id: ${existingByHash.id})`);
|
|
32
|
+
}
|
|
22
33
|
const fileType = detectFileType(resolvedPath);
|
|
23
34
|
const parser = this.parsers.find((p) => p.supports(fileType));
|
|
24
35
|
if (!parser) {
|
|
@@ -31,7 +42,6 @@ export class AddCvUseCase {
|
|
|
31
42
|
}
|
|
32
43
|
onProgress?.('extracting', 'Extracting structured data with AI...');
|
|
33
44
|
const extracted = await this.extractor.extract(rawText);
|
|
34
|
-
onProgress?.('checking_duplicates', 'Checking for duplicate candidates...');
|
|
35
45
|
if (extracted.email) {
|
|
36
46
|
const existing = await this.candidateRepo.findByEmail(extracted.email);
|
|
37
47
|
if (existing) {
|
|
@@ -41,7 +51,13 @@ export class AddCvUseCase {
|
|
|
41
51
|
const candidateId = nanoid();
|
|
42
52
|
const now = new Date().toISOString();
|
|
43
53
|
onProgress?.('chunking', 'Creating text chunks...');
|
|
44
|
-
const
|
|
54
|
+
const normalizer = new SkillNormalizerService();
|
|
55
|
+
const chunkInputs = createChunks(extracted, rawText, {
|
|
56
|
+
name: extracted.name,
|
|
57
|
+
experienceYears: extracted.totalExperienceYears,
|
|
58
|
+
location: extracted.location,
|
|
59
|
+
topSkills: normalizer.normalizeAll(extracted.skills).slice(0, 5),
|
|
60
|
+
});
|
|
45
61
|
const chunkTexts = chunkInputs.map((c) => c.content);
|
|
46
62
|
onProgress?.('embedding', `Generating embeddings for ${chunkTexts.length} chunks...`);
|
|
47
63
|
const vectors = await this.embeddingService.embedBatch(chunkTexts);
|
|
@@ -68,12 +84,15 @@ export class AddCvUseCase {
|
|
|
68
84
|
startDate: e.startDate,
|
|
69
85
|
endDate: e.endDate,
|
|
70
86
|
})),
|
|
71
|
-
skills: extracted.skills,
|
|
87
|
+
skills: normalizer.normalizeAll(extracted.skills),
|
|
72
88
|
languages: extracted.languages,
|
|
73
89
|
certifications: extracted.certifications,
|
|
74
90
|
projects: extracted.projects,
|
|
75
91
|
links: extracted.links,
|
|
76
92
|
tags: [],
|
|
93
|
+
source: options?.source ?? 'direct',
|
|
94
|
+
pool: options?.pool,
|
|
95
|
+
fileHash,
|
|
77
96
|
experienceYears: extracted.totalExperienceYears,
|
|
78
97
|
avgTenureMonths: extracted.avgTenureMonths,
|
|
79
98
|
shortStintCount: extracted.shortStintCount,
|
|
@@ -107,6 +126,8 @@ export class AddCvUseCase {
|
|
|
107
126
|
const finalCandidate = { ...candidate, originalFilePath };
|
|
108
127
|
await this.candidateRepo.save(finalCandidate);
|
|
109
128
|
await this.chunkRepo.saveMany(chunks);
|
|
129
|
+
// Update FTS index with new chunks
|
|
130
|
+
await this.chunkRepo.optimizeIndex();
|
|
110
131
|
return finalCandidate;
|
|
111
132
|
}
|
|
112
133
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"add-cv.js","sourceRoot":"","sources":["../../../src/application/use-cases/add-cv.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"add-cv.js","sourceRoot":"","sources":["../../../src/application/use-cases/add-cv.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAQ5C,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAC9D,OAAO,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AACrH,OAAO,EAAE,sBAAsB,EAAE,MAAM,2CAA2C,CAAC;AACnF,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AASxF,MAAM,OAAO,YAAY;IAEb;IACA;IACA;IACA;IACA;IALV,YACU,aAAmC,EACnC,SAA2B,EAC3B,OAA0B,EAC1B,gBAAmC,EACnC,SAA+B;QAJ/B,kBAAa,GAAb,aAAa,CAAsB;QACnC,cAAS,GAAT,SAAS,CAAkB;QAC3B,YAAO,GAAP,OAAO,CAAmB;QAC1B,qBAAgB,GAAhB,gBAAgB,CAAmB;QACnC,cAAS,GAAT,SAAS,CAAsB;IACtC,CAAC;IAEJ,KAAK,CAAC,OAAO,CAAC,QAAgB,EAAE,OAAsB,EAAE,UAA6B;QACnF,6DAA6D;QAC7D,MAAM,YAAY,GAAG,MAAM,eAAe,CAAC,QAAQ,CAAC,CAAC;QAErD,2DAA2D;QAC3D,MAAM,UAAU,GAAG,MAAM,QAAQ,CAAC,YAAY,CAAC,CAAC;QAChD,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAEvE,UAAU,EAAE,CAAC,qBAAqB,EAAE,gCAAgC,CAAC,CAAC;QACtE,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;QACzE,IAAI,cAAc,EAAE,CAAC;YACnB,MAAM,IAAI,uBAAuB,CAC/B,uCAAuC,cAAc,CAAC,IAAI,UAAU,cAAc,CAAC,EAAE,GAAG,CACzF,CAAC;QACJ,CAAC;QAED,MAAM,QAAQ,GAAG,cAAc,CAAC,YAAY,CAAC,CAAC;QAE9C,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC9D,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,wBAAwB,CAAC,QAAQ,CAAC,CAAC;QAC/C,CAAC;QAED,UAAU,EAAE,CAAC,SAAS,EAAE,WAAW,QAAQ,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC;QACrE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QAE3D,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;YAC3C,MAAM,IAAI,kBAAkB,CAC1B,gCAAgC,YAAY,4DAA4D,CACzG,CAAC;QACJ,CAAC;QAED,UAAU,EAAE,CAAC,YAAY,EAAE,uCAAuC,CAAC,CAAC;QACpE,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAExD,IAAI,SAAS,CAAC,KAAK,EAAE,CAAC;YACpB,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,WAAW,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;YACvE,IAAI,QAAQ,EAAE,CAAC;gBACb,MAAM,IAAI,uBAAuB,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;YACrD,CAAC;QACH,CAAC;QAED,MAAM,WAAW,GAAG,MAAM,EAAE,CAAC;QAC7B,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAErC,UAAU,EAAE,CAAC,UAAU,EAAE,yBAAyB,CAAC,CAAC;QACpD,MAAM,UAAU,GAAG,IAAI,sBAAsB,EAAE,CAAC;QAChD,MAAM,WAAW,GAAG,YAAY,CAAC,SAAS,EAAE,OAAO,EAAE;YACnD,IAAI,EAAE,SAAS,CAAC,IAAI;YACpB,eAAe,EAAE,SAAS,CAAC,oBAAoB;YAC/C,QAAQ,EAAE,SAAS,CAAC,QAAQ;YAC5B,SAAS,EAAE,UAAU,CAAC,YAAY,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;SACjE,CAAC,CAAC;QACH,MAAM,UAAU,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QAErD,UAAU,EAAE,CAAC,WAAW,EAAE,6BAA6B,UAAU,CAAC,MAAM,YAAY,CAAC,CAAC;QACtF,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;QAEnE,MAAM,SAAS,GAAc;YAC3B,EAAE,EAAE,WAAW;YACf,IAAI,EAAE,SAAS,CAAC,IAAI;YACpB,OAAO,EAAE;gBACP,KAAK,EAAE,SAAS,CAAC,KAAK;gBACtB,KAAK,EAAE,SAAS,CAAC,KAAK;gBACtB,QAAQ,EAAE,SAAS,CAAC,QAAQ;aAC7B;YACD,OAAO,EAAE,SAAS,CAAC,OAAO;YAC1B,UAAU,EAAE,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBAC3C,OAAO,EAAE,CAAC,CAAC,OAAO;gBAClB,QAAQ,EAAE,CAAC,CAAC,QAAQ;gBACpB,SAAS,EAAE,CAAC,CAAC,SAAS;gBACtB,OAAO,EAAE,CAAC,CAAC,OAAO;gBAClB,WAAW,EAAE,CAAC,CAAC,WAAW;aAC3B,CAAC,CAAC;YACH,SAAS,EAAE,SAAS,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACzC,WAAW,EAAE,CAAC,CAAC,WAAW;gBAC1B,MAAM,EAAE,CAAC,CAAC,MAAM;gBAChB,KAAK,EAAE,CAAC,CAAC,KAAK;gBACd,SAAS,EAAE,CAAC,CAAC,SAAS;gBACtB,OAAO,EAAE,CAAC,CAAC,OAAO;aACnB,CAAC,CAAC;YACH,MAAM,EAAE,UAAU,CAAC,YAAY,CAAC,SAAS,CAAC,MAAM,CAAC;YACjD,SAAS,EAAE,SAAS,CAAC,SAAS;YAC9B,cAAc,EAAE,SAAS,CAAC,cAAc;YACxC,QAAQ,EAAE,SAAS,CAAC,QAAQ;YAC5B,KAAK,EAAE,SAAS,CAAC,KAAK;YACtB,IAAI,EAAE,EAAE;YACR,MAAM,EAAE,OAAO,EAAE,MAAM,IAAI,QAAQ;YACnC,IAAI,EAAE,OAAO,EAAE,IAAI;YACnB,QAAQ;YACR,eAAe,EAAE,SAAS,CAAC,oBAAoB;YAC/C,eAAe,EAAE,SAAS,CAAC,eAAe;YAC1C,eAAe,EAAE,SAAS,CAAC,eAAe;YAC1C,YAAY,EAAE,SAAS,CAAC,YAAY;YACpC,gBAAgB,EAAE,SAAS,EAAE,iBAAiB;YAC9C,UAAU,EAAE,QAAQ;YACpB,QAAQ;YACR,OAAO;YACP,SAAS,EAAE,GAAG;YACd,SAAS,EAAE,GAAG;YACd,OAAO,EAAE,CAAC;SACX,CAAC;QAEF,MAAM,MAAM,GAAc,WAAW,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;YACvD,EAAE,EAAE,MAAM,EAAE;YACZ,WAAW;YACX,WAAW,EAAE,KAAK,CAAC,WAAW;YAC9B,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;YAClB,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,SAAS,EAAE,GAAG;SACf,CAAC,CAAC,CAAC;QAEJ,UAAU,EAAE,CAAC,QAAQ,EAAE,uBAAuB,CAAC,CAAC;QAEhD,sBAAsB;QACtB,IAAI,gBAAoC,CAAC;QACzC,IAAI,CAAC;YACH,gBAAgB,GAAG,MAAM,iBAAiB,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC;QACxE,CAAC;QAAC,MAAM,CAAC;YACP,6DAA6D;QAC/D,CAAC;QAED,MAAM,cAAc,GAAc,EAAE,GAAG,SAAS,EAAE,gBAAgB,EAAE,CAAC;QACrE,MAAM,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAC9C,MAAM,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QACtC,mCAAmC;QACnC,MAAM,IAAI,CAAC,SAAS,CAAC,aAAa,EAAE,CAAC;QAErC,OAAO,cAAc,CAAC;IACxB,CAAC;CACF"}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import type { ICandidateRepository } from '../../domain/repositories/candidate-repository.js';
|
|
2
2
|
import type { IChunkRepository } from '../../domain/repositories/chunk-repository.js';
|
|
3
3
|
import type { IVersionRepository } from '../../domain/repositories/version-repository.js';
|
|
4
|
+
import type { IApplicationRepository } from '../../domain/repositories/application-repository.js';
|
|
5
|
+
import type { IApplicationEventRepository } from '../../domain/repositories/application-event-repository.js';
|
|
4
6
|
export declare class DeleteCvUseCase {
|
|
5
7
|
private candidateRepo;
|
|
6
8
|
private chunkRepo;
|
|
7
9
|
private versionRepo;
|
|
8
|
-
|
|
10
|
+
private applicationRepo?;
|
|
11
|
+
private eventRepo?;
|
|
12
|
+
constructor(candidateRepo: ICandidateRepository, chunkRepo: IChunkRepository, versionRepo: IVersionRepository, applicationRepo?: IApplicationRepository | undefined, eventRepo?: IApplicationEventRepository | undefined);
|
|
9
13
|
execute(candidateId: string): Promise<void>;
|
|
10
14
|
}
|