@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.
Files changed (232) hide show
  1. package/dist/__tests__/integration/hybrid-search.test.d.ts +1 -0
  2. package/dist/__tests__/integration/hybrid-search.test.js +70 -0
  3. package/dist/__tests__/integration/hybrid-search.test.js.map +1 -0
  4. package/dist/__tests__/unit/chunking.test.d.ts +1 -0
  5. package/dist/__tests__/unit/chunking.test.js +70 -0
  6. package/dist/__tests__/unit/chunking.test.js.map +1 -0
  7. package/dist/__tests__/unit/composite-score.test.d.ts +1 -0
  8. package/dist/__tests__/unit/composite-score.test.js +28 -0
  9. package/dist/__tests__/unit/composite-score.test.js.map +1 -0
  10. package/dist/__tests__/unit/find-similar-candidates.test.d.ts +1 -0
  11. package/dist/__tests__/unit/find-similar-candidates.test.js +33 -0
  12. package/dist/__tests__/unit/find-similar-candidates.test.js.map +1 -0
  13. package/dist/__tests__/unit/query-enhancer.test.d.ts +1 -0
  14. package/dist/__tests__/unit/query-enhancer.test.js +20 -0
  15. package/dist/__tests__/unit/query-enhancer.test.js.map +1 -0
  16. package/dist/__tests__/unit/skill-graph.test.d.ts +1 -0
  17. package/dist/__tests__/unit/skill-graph.test.js +38 -0
  18. package/dist/__tests__/unit/skill-graph.test.js.map +1 -0
  19. package/dist/__tests__/unit/skill-normalizer.test.d.ts +1 -0
  20. package/dist/__tests__/unit/skill-normalizer.test.js +41 -0
  21. package/dist/__tests__/unit/skill-normalizer.test.js.map +1 -0
  22. package/dist/__tests__/unit/smoke.test.d.ts +1 -0
  23. package/dist/__tests__/unit/smoke.test.js +7 -0
  24. package/dist/__tests__/unit/smoke.test.js.map +1 -0
  25. package/dist/application/dto/candidate-summary.d.ts +2 -0
  26. package/dist/application/dto/filter-input.d.ts +2 -0
  27. package/dist/application/dto/search-results.d.ts +3 -0
  28. package/dist/application/dto/stats.d.ts +6 -0
  29. package/dist/application/services/query-enhancer.d.ts +10 -0
  30. package/dist/application/services/query-enhancer.js +34 -0
  31. package/dist/application/services/query-enhancer.js.map +1 -0
  32. package/dist/application/use-cases/add-cv.d.ts +5 -1
  33. package/dist/application/use-cases/add-cv.js +25 -4
  34. package/dist/application/use-cases/add-cv.js.map +1 -1
  35. package/dist/application/use-cases/delete-cv.d.ts +5 -1
  36. package/dist/application/use-cases/delete-cv.js +13 -1
  37. package/dist/application/use-cases/delete-cv.js.map +1 -1
  38. package/dist/application/use-cases/delete-job-posting.d.ts +10 -0
  39. package/dist/application/use-cases/delete-job-posting.js +23 -0
  40. package/dist/application/use-cases/delete-job-posting.js.map +1 -0
  41. package/dist/application/use-cases/filter-candidates.js +12 -1
  42. package/dist/application/use-cases/filter-candidates.js.map +1 -1
  43. package/dist/application/use-cases/find-similar-candidates.d.ts +21 -0
  44. package/dist/application/use-cases/find-similar-candidates.js +85 -0
  45. package/dist/application/use-cases/find-similar-candidates.js.map +1 -0
  46. package/dist/application/use-cases/get-stats.js +2 -0
  47. package/dist/application/use-cases/get-stats.js.map +1 -1
  48. package/dist/application/use-cases/list-cvs.d.ts +2 -1
  49. package/dist/application/use-cases/list-cvs.js +18 -2
  50. package/dist/application/use-cases/list-cvs.js.map +1 -1
  51. package/dist/application/use-cases/match-candidates.d.ts +5 -1
  52. package/dist/application/use-cases/match-candidates.js +61 -19
  53. package/dist/application/use-cases/match-candidates.js.map +1 -1
  54. package/dist/application/use-cases/reindex-all.d.ts +22 -0
  55. package/dist/application/use-cases/reindex-all.js +124 -0
  56. package/dist/application/use-cases/reindex-all.js.map +1 -0
  57. package/dist/application/use-cases/semantic-search.d.ts +3 -1
  58. package/dist/application/use-cases/semantic-search.js +13 -3
  59. package/dist/application/use-cases/semantic-search.js.map +1 -1
  60. package/dist/application/use-cases/update-cv.d.ts +2 -1
  61. package/dist/application/use-cases/update-cv.js +18 -3
  62. package/dist/application/use-cases/update-cv.js.map +1 -1
  63. package/dist/domain/entities/candidate.d.ts +3 -0
  64. package/dist/domain/repositories/candidate-repository.d.ts +1 -0
  65. package/dist/domain/repositories/chunk-repository.d.ts +8 -0
  66. package/dist/infrastructure/config/app-config.d.ts +5 -0
  67. package/dist/infrastructure/config/app-config.js +4 -0
  68. package/dist/infrastructure/config/app-config.js.map +1 -1
  69. package/dist/infrastructure/persistence/database.d.ts +1 -0
  70. package/dist/infrastructure/persistence/database.js +20 -0
  71. package/dist/infrastructure/persistence/database.js.map +1 -1
  72. package/dist/infrastructure/persistence/lancedb-candidate-repository.d.ts +1 -0
  73. package/dist/infrastructure/persistence/lancedb-candidate-repository.js +20 -0
  74. package/dist/infrastructure/persistence/lancedb-candidate-repository.js.map +1 -1
  75. package/dist/infrastructure/persistence/lancedb-chunk-repository.d.ts +4 -1
  76. package/dist/infrastructure/persistence/lancedb-chunk-repository.js +64 -2
  77. package/dist/infrastructure/persistence/lancedb-chunk-repository.js.map +1 -1
  78. package/dist/interface/dashboard/public/200.html +1 -1
  79. package/dist/interface/dashboard/public/404.html +1 -1
  80. package/dist/interface/dashboard/public/_nuxt/builds/latest.json +1 -1
  81. package/dist/interface/dashboard/public/_nuxt/builds/meta/05a92851-5bed-4eaf-bd75-e0aff9ec5876.json +1 -0
  82. package/dist/interface/dashboard/public/_nuxt/builds/meta/1d36013b-8fb8-4cea-a448-d5f224b731ee.json +1 -0
  83. package/dist/interface/dashboard/public/_nuxt/builds/meta/2bb695c5-c739-46a0-a3e6-4b76aa321c29.json +1 -0
  84. package/dist/interface/dashboard/public/_nuxt/builds/meta/3ee0a1ee-8e79-4443-a6e7-b0b6412b0a38.json +1 -0
  85. package/dist/interface/dashboard/public/_nuxt/builds/meta/ba31dbc0-8fe2-44bc-8214-6b3d717566a0.json +1 -0
  86. package/dist/interface/dashboard/public/_nuxt/builds/meta/fc914bdb-64a6-4798-a08f-64eb8ea9a307.json +1 -0
  87. package/dist/interface/dashboard/public/index.html +1 -1
  88. package/dist/interface/mcp/server.js +105 -42
  89. package/dist/interface/mcp/server.js.map +1 -1
  90. package/dist/interface/mcp/tools/add-cv.d.ts +2 -2
  91. package/dist/interface/mcp/tools/add-cv.js +31 -3
  92. package/dist/interface/mcp/tools/add-cv.js.map +1 -1
  93. package/dist/interface/mcp/tools/add-job-posting.d.ts +2 -2
  94. package/dist/interface/mcp/tools/add-job-posting.js +20 -12
  95. package/dist/interface/mcp/tools/add-job-posting.js.map +1 -1
  96. package/dist/interface/mcp/tools/add-pipeline-note.d.ts +2 -2
  97. package/dist/interface/mcp/tools/add-pipeline-note.js +10 -6
  98. package/dist/interface/mcp/tools/add-pipeline-note.js.map +1 -1
  99. package/dist/interface/mcp/tools/add-reference-cv.d.ts +2 -2
  100. package/dist/interface/mcp/tools/add-reference-cv.js +9 -5
  101. package/dist/interface/mcp/tools/add-reference-cv.js.map +1 -1
  102. package/dist/interface/mcp/tools/add-to-pipeline.d.ts +2 -2
  103. package/dist/interface/mcp/tools/add-to-pipeline.js +9 -5
  104. package/dist/interface/mcp/tools/add-to-pipeline.js.map +1 -1
  105. package/dist/interface/mcp/tools/check-duplicate-candidate.d.ts +2 -2
  106. package/dist/interface/mcp/tools/check-duplicate-candidate.js +8 -4
  107. package/dist/interface/mcp/tools/check-duplicate-candidate.js.map +1 -1
  108. package/dist/interface/mcp/tools/compare-candidates.d.ts +2 -2
  109. package/dist/interface/mcp/tools/compare-candidates.js +8 -4
  110. package/dist/interface/mcp/tools/compare-candidates.js.map +1 -1
  111. package/dist/interface/mcp/tools/create-call-batch.d.ts +2 -2
  112. package/dist/interface/mcp/tools/create-call-batch.js +10 -6
  113. package/dist/interface/mcp/tools/create-call-batch.js.map +1 -1
  114. package/dist/interface/mcp/tools/delete-cv.d.ts +2 -2
  115. package/dist/interface/mcp/tools/delete-cv.js +29 -2
  116. package/dist/interface/mcp/tools/delete-cv.js.map +1 -1
  117. package/dist/interface/mcp/tools/delete-job-posting.d.ts +2 -2
  118. package/dist/interface/mcp/tools/delete-job-posting.js +32 -3
  119. package/dist/interface/mcp/tools/delete-job-posting.js.map +1 -1
  120. package/dist/interface/mcp/tools/export-results.d.ts +2 -2
  121. package/dist/interface/mcp/tools/export-results.js +7 -3
  122. package/dist/interface/mcp/tools/export-results.js.map +1 -1
  123. package/dist/interface/mcp/tools/filter-candidates.d.ts +2 -2
  124. package/dist/interface/mcp/tools/filter-candidates.js +17 -9
  125. package/dist/interface/mcp/tools/filter-candidates.js.map +1 -1
  126. package/dist/interface/mcp/tools/find-similar-candidates.d.ts +3 -0
  127. package/dist/interface/mcp/tools/find-similar-candidates.js +39 -0
  128. package/dist/interface/mcp/tools/find-similar-candidates.js.map +1 -0
  129. package/dist/interface/mcp/tools/generate-screening-report.d.ts +2 -2
  130. package/dist/interface/mcp/tools/generate-screening-report.js +8 -4
  131. package/dist/interface/mcp/tools/generate-screening-report.js.map +1 -1
  132. package/dist/interface/mcp/tools/get-call-batch-status.d.ts +2 -2
  133. package/dist/interface/mcp/tools/get-call-batch-status.js +7 -3
  134. package/dist/interface/mcp/tools/get-call-batch-status.js.map +1 -1
  135. package/dist/interface/mcp/tools/get-candidate-history.d.ts +2 -2
  136. package/dist/interface/mcp/tools/get-candidate-history.js +7 -3
  137. package/dist/interface/mcp/tools/get-candidate-history.js.map +1 -1
  138. package/dist/interface/mcp/tools/get-cv-chunks.d.ts +2 -2
  139. package/dist/interface/mcp/tools/get-cv-chunks.js +13 -9
  140. package/dist/interface/mcp/tools/get-cv-chunks.js.map +1 -1
  141. package/dist/interface/mcp/tools/get-cv-detail.d.ts +2 -2
  142. package/dist/interface/mcp/tools/get-cv-detail.js +6 -2
  143. package/dist/interface/mcp/tools/get-cv-detail.js.map +1 -1
  144. package/dist/interface/mcp/tools/get-cv-versions.d.ts +2 -2
  145. package/dist/interface/mcp/tools/get-cv-versions.js +6 -2
  146. package/dist/interface/mcp/tools/get-cv-versions.js.map +1 -1
  147. package/dist/interface/mcp/tools/get-job-posting.d.ts +2 -2
  148. package/dist/interface/mcp/tools/get-job-posting.js +7 -3
  149. package/dist/interface/mcp/tools/get-job-posting.js.map +1 -1
  150. package/dist/interface/mcp/tools/get-pending-actions.d.ts +2 -2
  151. package/dist/interface/mcp/tools/get-pending-actions.js +6 -2
  152. package/dist/interface/mcp/tools/get-pending-actions.js.map +1 -1
  153. package/dist/interface/mcp/tools/get-pipeline-candidates.d.ts +2 -2
  154. package/dist/interface/mcp/tools/get-pipeline-candidates.js +7 -3
  155. package/dist/interface/mcp/tools/get-pipeline-candidates.js.map +1 -1
  156. package/dist/interface/mcp/tools/get-pipeline.d.ts +2 -2
  157. package/dist/interface/mcp/tools/get-pipeline.js +7 -3
  158. package/dist/interface/mcp/tools/get-pipeline.js.map +1 -1
  159. package/dist/interface/mcp/tools/get-screening-history.d.ts +2 -2
  160. package/dist/interface/mcp/tools/get-screening-history.js +7 -3
  161. package/dist/interface/mcp/tools/get-screening-history.js.map +1 -1
  162. package/dist/interface/mcp/tools/get-server-status.d.ts +2 -2
  163. package/dist/interface/mcp/tools/get-server-status.js +6 -2
  164. package/dist/interface/mcp/tools/get-server-status.js.map +1 -1
  165. package/dist/interface/mcp/tools/get-stats.d.ts +2 -2
  166. package/dist/interface/mcp/tools/get-stats.js +6 -2
  167. package/dist/interface/mcp/tools/get-stats.js.map +1 -1
  168. package/dist/interface/mcp/tools/list-cvs.d.ts +2 -2
  169. package/dist/interface/mcp/tools/list-cvs.js +32 -11
  170. package/dist/interface/mcp/tools/list-cvs.js.map +1 -1
  171. package/dist/interface/mcp/tools/list-job-postings.d.ts +2 -2
  172. package/dist/interface/mcp/tools/list-job-postings.js +6 -2
  173. package/dist/interface/mcp/tools/list-job-postings.js.map +1 -1
  174. package/dist/interface/mcp/tools/list-reference-cvs.d.ts +2 -2
  175. package/dist/interface/mcp/tools/list-reference-cvs.js +7 -3
  176. package/dist/interface/mcp/tools/list-reference-cvs.js.map +1 -1
  177. package/dist/interface/mcp/tools/manage-tags.d.ts +2 -2
  178. package/dist/interface/mcp/tools/manage-tags.js +8 -4
  179. package/dist/interface/mcp/tools/manage-tags.js.map +1 -1
  180. package/dist/interface/mcp/tools/match-candidates.d.ts +2 -2
  181. package/dist/interface/mcp/tools/match-candidates.js +26 -12
  182. package/dist/interface/mcp/tools/match-candidates.js.map +1 -1
  183. package/dist/interface/mcp/tools/reindex-all.d.ts +3 -0
  184. package/dist/interface/mcp/tools/reindex-all.js +31 -0
  185. package/dist/interface/mcp/tools/reindex-all.js.map +1 -0
  186. package/dist/interface/mcp/tools/remove-reference-cv.d.ts +2 -2
  187. package/dist/interface/mcp/tools/remove-reference-cv.js +29 -2
  188. package/dist/interface/mcp/tools/remove-reference-cv.js.map +1 -1
  189. package/dist/interface/mcp/tools/reset-database.d.ts +2 -2
  190. package/dist/interface/mcp/tools/reset-database.js +29 -2
  191. package/dist/interface/mcp/tools/reset-database.js.map +1 -1
  192. package/dist/interface/mcp/tools/semantic-search.d.ts +2 -2
  193. package/dist/interface/mcp/tools/semantic-search.js +24 -11
  194. package/dist/interface/mcp/tools/semantic-search.js.map +1 -1
  195. package/dist/interface/mcp/tools/set-pending-action.d.ts +2 -2
  196. package/dist/interface/mcp/tools/set-pending-action.js +16 -12
  197. package/dist/interface/mcp/tools/set-pending-action.js.map +1 -1
  198. package/dist/interface/mcp/tools/update-call-outcome.d.ts +2 -2
  199. package/dist/interface/mcp/tools/update-call-outcome.js +10 -6
  200. package/dist/interface/mcp/tools/update-call-outcome.js.map +1 -1
  201. package/dist/interface/mcp/tools/update-cv.d.ts +2 -2
  202. package/dist/interface/mcp/tools/update-cv.js +16 -6
  203. package/dist/interface/mcp/tools/update-cv.js.map +1 -1
  204. package/dist/interface/mcp/tools/update-job-posting.d.ts +2 -2
  205. package/dist/interface/mcp/tools/update-job-posting.js +16 -12
  206. package/dist/interface/mcp/tools/update-job-posting.js.map +1 -1
  207. package/dist/interface/mcp/tools/update-pipeline-status.d.ts +2 -2
  208. package/dist/interface/mcp/tools/update-pipeline-status.js +9 -5
  209. package/dist/interface/mcp/tools/update-pipeline-status.js.map +1 -1
  210. package/dist/interface/mcp/types.d.ts +2 -0
  211. package/dist/shared/data/skill-graph.json +115 -0
  212. package/dist/shared/data/skill-map.json +104 -0
  213. package/dist/shared/mcp/logger.d.ts +7 -0
  214. package/dist/shared/mcp/logger.js +20 -0
  215. package/dist/shared/mcp/logger.js.map +1 -0
  216. package/dist/shared/mcp/progress.d.ts +6 -0
  217. package/dist/shared/mcp/progress.js +33 -0
  218. package/dist/shared/mcp/progress.js.map +1 -0
  219. package/dist/shared/services/skill-graph.d.ts +11 -0
  220. package/dist/shared/services/skill-graph.js +84 -0
  221. package/dist/shared/services/skill-graph.js.map +1 -0
  222. package/dist/shared/services/skill-normalizer.d.ts +6 -0
  223. package/dist/shared/services/skill-normalizer.js +32 -0
  224. package/dist/shared/services/skill-normalizer.js.map +1 -0
  225. package/dist/shared/types/index.d.ts +3 -1
  226. package/dist/shared/utils/chunking.d.ts +9 -1
  227. package/dist/shared/utils/chunking.js +46 -36
  228. package/dist/shared/utils/chunking.js.map +1 -1
  229. package/dist/shared/utils/skill-migration.d.ts +6 -0
  230. package/dist/shared/utils/skill-migration.js +35 -0
  231. package/dist/shared/utils/skill-migration.js.map +1 -0
  232. 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,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,7 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ describe('smoke test', () => {
3
+ it('vitest is configured correctly', () => {
4
+ expect(1 + 1).toBe(2);
5
+ });
6
+ });
7
+ //# sourceMappingURL=smoke.test.js.map
@@ -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"}
@@ -7,4 +7,6 @@ export interface CandidateSummaryDto {
7
7
  experienceYears?: number;
8
8
  tags: string[];
9
9
  createdAt: string;
10
+ source: string;
11
+ pool?: string;
10
12
  }
@@ -5,4 +5,6 @@ export interface FilterInput {
5
5
  minExperienceYears?: number;
6
6
  languages?: string[];
7
7
  tags?: string[];
8
+ source?: string;
9
+ pool?: string;
8
10
  }
@@ -8,6 +8,9 @@ export interface SearchResultDto {
8
8
  }
9
9
  export interface SearchInput {
10
10
  query: string;
11
+ textQuery?: string;
12
+ enhanceQuery?: boolean;
11
13
  limit?: number;
12
14
  sectionType?: string;
15
+ location?: string;
13
16
  }
@@ -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 chunkInputs = createChunks(extracted, rawText);
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":"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"}
1
+ {"version":3,"file":"add-cv.js","sourceRoot":"","sources":["../../../src/application/use-cases/add-cv.ts"],"names":[],"mappings":"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"}
@@ -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
- constructor(candidateRepo: ICandidateRepository, chunkRepo: IChunkRepository, versionRepo: IVersionRepository);
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
  }