@futdevpro/fdp-agent-memory 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +345 -0
- package/build/package.json +96 -0
- package/build/src/_assets/mcp-client-config/README.md +29 -0
- package/build/src/_assets/mcp-client-config/claude_desktop_config.json +15 -0
- package/build/src/_assets/mcp-client-config/mcp.json +15 -0
- package/build/src/_collections/config-catalog.const.js +180 -0
- package/build/src/_collections/config-error-codes.const.js +30 -0
- package/build/src/_collections/config-presets.const.js +25 -0
- package/build/src/_collections/error-banners.const.js +100 -0
- package/build/src/_collections/error-codes.const.js +150 -0
- package/build/src/_collections/fam-db-models.const.js +37 -0
- package/build/src/_collections/fam-entry-bootstrap.util.js +80 -0
- package/build/src/_collections/fam-error-context.util.js +90 -0
- package/build/src/_collections/fam-error-factory.util.js +64 -0
- package/build/src/_enums/fam-config-level.type-enum.js +15 -0
- package/build/src/_enums/fam-table.type-enum.js +20 -0
- package/build/src/_integration-tests/_helpers/fam-integration-test-setup.util.js +105 -0
- package/build/src/_models/data-models/fam-codebase.data-model.js +51 -0
- package/build/src/_models/data-models/fam-coding-patterns.data-model.js +58 -0
- package/build/src/_models/data-models/fam-config.data-model.js +68 -0
- package/build/src/_models/data-models/fam-documents.data-model.js +53 -0
- package/build/src/_models/data-models/fam-entry-base-properties.const.js +43 -0
- package/build/src/_models/data-models/fam-entry.data-model.js +81 -0
- package/build/src/_models/data-models/fam-error.data-model.js +88 -0
- package/build/src/_models/data-models/fam-ingest-run.data-model.js +74 -0
- package/build/src/_models/data-models/fam-knowledge.data-model.js +48 -0
- package/build/src/_models/data-models/fam-memory.data-model.js +55 -0
- package/build/src/_models/data-models/fam-reference.data-model.js +67 -0
- package/build/src/_models/data-models/fam-rules.data-model.js +51 -0
- package/build/src/_models/data-models/fam-scope.data-model.js +52 -0
- package/build/src/_models/interfaces/fam-common.interface.js +23 -0
- package/build/src/_models/interfaces/fam-config.interface.js +2 -0
- package/build/src/_models/interfaces/fam-error.interface.js +2 -0
- package/build/src/_modules/embedding/_collections/fam-embedding-pricing.const.js +22 -0
- package/build/src/_modules/embedding/_collections/fam-store-registry.const.js +63 -0
- package/build/src/_modules/embedding/_models/interfaces/fam-embedding-cost.interface.js +10 -0
- package/build/src/_modules/embedding/_models/interfaces/fam-embedding-provider.interface.js +2 -0
- package/build/src/_modules/embedding/_models/interfaces/fam-resolved-provider.interface.js +2 -0
- package/build/src/_modules/embedding/_services/fam-embedding-bootstrap.control-service.js +52 -0
- package/build/src/_modules/embedding/_services/fam-embedding-cost.control-service.js +175 -0
- package/build/src/_modules/embedding/_services/fam-embedding-pipeline.control-service.js +202 -0
- package/build/src/_modules/embedding/_services/fam-embedding-preset.control-service.js +66 -0
- package/build/src/_modules/embedding/_services/fam-embedding.control-service.js +253 -0
- package/build/src/_modules/embedding/_services/fam-entry.data-service.js +64 -0
- package/build/src/_modules/embedding/_services/fam-lmstudio-embedding.provider.js +112 -0
- package/build/src/_modules/embedding/_services/fam-mock-embedding.provider.js +64 -0
- package/build/src/_modules/embedding/_services/fam-openai-embedding.provider.js +64 -0
- package/build/src/_modules/embedding/_services/fam-vector-search.control-service.js +244 -0
- package/build/src/_modules/embedding/index.js +40 -0
- package/build/src/_modules/ingest/_collections/fam-content-hash.util.js +35 -0
- package/build/src/_modules/ingest/_collections/fam-file-routing.util.js +95 -0
- package/build/src/_modules/ingest/_collections/fam-glob-match.util.js +84 -0
- package/build/src/_modules/ingest/_collections/fam-md-chunker.util.js +164 -0
- package/build/src/_modules/ingest/_collections/fam-scan-path.util.js +91 -0
- package/build/src/_modules/ingest/_collections/fam-secret-exclude.util.js +54 -0
- package/build/src/_modules/ingest/_collections/fam-sliding-chunker.util.js +76 -0
- package/build/src/_modules/ingest/_collections/fam-ts-chunker.util.js +316 -0
- package/build/src/_modules/ingest/_models/interfaces/fam-ingest.interface.js +2 -0
- package/build/src/_modules/ingest/_services/fam-chunker.control-service.js +114 -0
- package/build/src/_modules/ingest/_services/fam-delta-compare.util.js +74 -0
- package/build/src/_modules/ingest/_services/fam-ingest-run.data-service.js +85 -0
- package/build/src/_modules/ingest/_services/fam-ingest.control-service.js +384 -0
- package/build/src/_modules/ingest/_services/fam-scan.control-service.js +211 -0
- package/build/src/_modules/ingest/index.js +46 -0
- package/build/src/_modules/mcp/_collections/fam-core-tools.const.js +186 -0
- package/build/src/_modules/mcp/_models/interfaces/fam-mcp.interface.js +31 -0
- package/build/src/_modules/mcp/_services/fam-capabilities-tool.service.js +111 -0
- package/build/src/_modules/mcp/_services/fam-capability-registry.service.js +1180 -0
- package/build/src/_modules/mcp/_services/fam-mcp-adapter.service.js +123 -0
- package/build/src/_modules/mcp/_services/fam-mcp-server.service.js +69 -0
- package/build/src/_modules/mcp/_services/fam-read-tool.service.js +99 -0
- package/build/src/_modules/mcp/_services/fam-write-tool.service.js +460 -0
- package/build/src/_modules/mcp/index.js +35 -0
- package/build/src/_modules/migration/_collections/fam-claude-mem-normalize.util.js +166 -0
- package/build/src/_modules/migration/_collections/fam-import-content-hash.util.js +38 -0
- package/build/src/_modules/migration/_collections/fam-target-mapping.util.js +90 -0
- package/build/src/_modules/migration/_enums/fam-claude-mem-source.type-enum.js +20 -0
- package/build/src/_modules/migration/_models/interfaces/fam-claude-mem.interface.js +26 -0
- package/build/src/_modules/migration/_services/fam-claude-mem-export-reader.service.js +134 -0
- package/build/src/_modules/migration/_services/fam-claude-mem-import.control-service.js +533 -0
- package/build/src/_modules/migration/_services/fam-claude-mem-sqlite-reader.service.js +144 -0
- package/build/src/_modules/migration/_services/fam-claude-mem-worker-reader.service.js +115 -0
- package/build/src/_modules/migration/_services/fam-import-dedup.data-service.js +102 -0
- package/build/src/_modules/migration/index.js +38 -0
- package/build/src/_modules/retrieval/_models/interfaces/fam-retrieval.interface.js +2 -0
- package/build/src/_modules/retrieval/_services/fam-retrieval-candidate.data-service.js +67 -0
- package/build/src/_modules/retrieval/_services/fam-retrieval-suggestions.util.js +182 -0
- package/build/src/_modules/retrieval/_services/fam-retrieval.control-service.js +282 -0
- package/build/src/_modules/retrieval/index.js +22 -0
- package/build/src/_modules/scope-reference/_collections/fam-fuzzy-match.util.js +86 -0
- package/build/src/_modules/scope-reference/_collections/fam-scope-normalize.util.js +47 -0
- package/build/src/_modules/scope-reference/_models/interfaces/fam-reference-resolution.interface.js +2 -0
- package/build/src/_modules/scope-reference/_models/interfaces/fam-resolution-trace.interface.js +2 -0
- package/build/src/_modules/scope-reference/_services/fam-reference.data-service.js +179 -0
- package/build/src/_modules/scope-reference/_services/fam-scope-resolver.control-service.js +473 -0
- package/build/src/_modules/scope-reference/_services/fam-scope.data-service.js +215 -0
- package/build/src/_modules/scope-reference/index.js +26 -0
- package/build/src/_routes/server/api/api.controller.js +400 -0
- package/build/src/_routes/server/client-app/client-app.control-service.js +132 -0
- package/build/src/_routes/server/client-app/client-app.controller.js +35 -0
- package/build/src/_routes/server/config/config.control-service.js +476 -0
- package/build/src/_routes/server/config/config.data-service.js +49 -0
- package/build/src/_routes/server/errors/errors.control-service.js +123 -0
- package/build/src/_routes/server/errors/errors.controller.js +65 -0
- package/build/src/_routes/server/errors/errors.data-service.js +80 -0
- package/build/src/_routes/server/server-status/server-status.control-service.js +19 -0
- package/build/src/_routes/server/server-status/server-status.controller.js +39 -0
- package/build/src/app.server.js +122 -0
- package/build/src/environments/environment.js +20 -0
- package/build/src/index.js +18 -0
- package/client-dist/chunk-GHKRM4SM.js +1 -0
- package/client-dist/chunk-LMTL7GA3.js +575 -0
- package/client-dist/index.html +17 -0
- package/client-dist/main-2KWB3QYK.js +2 -0
- package/client-dist/polyfills-HGDOEU5L.js +2 -0
- package/client-dist/styles-3J7JD5YE.css +1 -0
- package/package.json +96 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FAM_Retrieval_ControlService = void 0;
|
|
4
|
+
const config_control_service_1 = require("../../../_routes/server/config/config.control-service");
|
|
5
|
+
const embedding_1 = require("../../embedding");
|
|
6
|
+
const scope_reference_1 = require("../../scope-reference");
|
|
7
|
+
const fam_retrieval_candidate_data_service_1 = require("./fam-retrieval-candidate.data-service");
|
|
8
|
+
const fam_retrieval_suggestions_util_1 = require("./fam-retrieval-suggestions.util");
|
|
9
|
+
/** A topK / lapozás abszolút plafonja (CCAP MAX_PAGE_SIZE=500; dsgn-005 §6). */
|
|
10
|
+
const MAX_PAGE_SIZE = 500;
|
|
11
|
+
/**
|
|
12
|
+
* `FAM_Retrieval_ControlService` (MP-5, dsgn-005) — a `read` **egylépcsős** retrieval-pipeline ENGINE-je.
|
|
13
|
+
* Singleton. Egy hívás több táblát + több query-t kezel; minden query ÖNÁLLÓ, kötött-sorrendű
|
|
14
|
+
* pipeline-on fut és ÖNÁLLÓ `results[]`-t termel. A `read` MCP-tool / REST (MP-6) ezt hívja, és a
|
|
15
|
+
* kimenetet a dsgn-003 §2.2 wire-shape-re map-eli (a dsgn-003 az AUTHORITATIV I/O — itt nem
|
|
16
|
+
* definiáljuk újra, csak kitöltjük).
|
|
17
|
+
*
|
|
18
|
+
* **Per-query pipeline (dsgn-005 §2, kötött sorrend):**
|
|
19
|
+
* ① scope-prefilter (MP-3 `expandForRead` → subtree scopeId-halmaz) →
|
|
20
|
+
* ② tag/kind prefilter + `_deleted`/`rules.isActive` guard (Mongo-szintű, a vektor-keresés ELŐTT) →
|
|
21
|
+
* ④ vektor-keresés a szűkített poolon (MP-2 `search({ candidateIds })`, koszinusz) →
|
|
22
|
+
* ⑤ weight-szorzó (`finalScore = score × weight`; weight≤0 → kizárt; coding_patterns weight 5) →
|
|
23
|
+
* ⑥ minScore-küszöb (a relevantSet alapja) →
|
|
24
|
+
* ⑦ rendezés (finalScore desc → recency → _id asc) →
|
|
25
|
+
* ⑧ topK-vágás + dense-detektálás (§4 dual-gate + signaling).
|
|
26
|
+
*
|
|
27
|
+
* **NINCS LLM chunk-selector (dsgn-005 §7, ADR-009):** a precíziót a vektor-score + weight + minScore
|
|
28
|
+
* + prefilter adja, nem nyelvi modell. A pipeline egyetlen LLM-hívást sem indít (az embedding NEM
|
|
29
|
+
* chat-completion — az a query-vektor előállítása). Az opcionális relevance-filter BACKLOG, NEM épül.
|
|
30
|
+
*
|
|
31
|
+
* **FIGYELEM (memory: dynts_dataservice_eager_resolve):** NEM tartunk élő DataService mezőt; minden
|
|
32
|
+
* DB-művelet előtt lazy `new FAM_RetrievalCandidate_DataService`.
|
|
33
|
+
*/
|
|
34
|
+
class FAM_Retrieval_ControlService {
|
|
35
|
+
static _instance;
|
|
36
|
+
/** Default issuer a retrieval-műveletekhez (a hibák / cost-event issuer-éhez; dsgn-008). */
|
|
37
|
+
issuer = 'FAM_Retrieval_ControlService';
|
|
38
|
+
static getInstance() {
|
|
39
|
+
if (!FAM_Retrieval_ControlService._instance) {
|
|
40
|
+
FAM_Retrieval_ControlService._instance = new FAM_Retrieval_ControlService();
|
|
41
|
+
}
|
|
42
|
+
return FAM_Retrieval_ControlService._instance;
|
|
43
|
+
}
|
|
44
|
+
// =========================================================================
|
|
45
|
+
// BELÉPŐ (SP-5.1) — egylépcsős, multi-tábla + multi-query
|
|
46
|
+
// =========================================================================
|
|
47
|
+
/**
|
|
48
|
+
* `read(request)` (SP-5.1, dsgn-005 §1) — a single-pass belépő. Minden `queries[]` elemre ÖNÁLLÓ
|
|
49
|
+
* pipeline (a query-k egymástól függetlenek, párhuzamosíthatók); a query-nkénti `results[]`-t a
|
|
50
|
+
* bemeneti sorrendben fűzi össze. Az `includeContent` default `true` (azonnal tartalom). A
|
|
51
|
+
* `globalScopeFilter` minden query-re közös scope-prefilter (a query saját `scopeFilter`-e
|
|
52
|
+
* felülírja, ha van).
|
|
53
|
+
*/
|
|
54
|
+
async read(request) {
|
|
55
|
+
const includeContent = request.includeContent !== false;
|
|
56
|
+
// Per-query párhuzamosítható pipeline-ok (egymástól függetlenek; dsgn-005 §2).
|
|
57
|
+
const results = await Promise.all(request.queries.map((query) => this.runQuery(query, includeContent, request.globalScopeFilter)));
|
|
58
|
+
return { results: results };
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Egy query teljes pipeline-ja (dsgn-005 §2). A `reference` tár SOHA nem szerepelhet a `tables`-ben
|
|
62
|
+
* (helper; dsgn-002/dsgn-005 §1) — a `read`-validáció (MP-6) elutasítja, de itt is kihagyjuk
|
|
63
|
+
* (defense-in-depth: a `FAM_StoreRegistry_Util` a reference-re undefined-et ad → üres jelölt-halmaz).
|
|
64
|
+
*/
|
|
65
|
+
async runQuery(query, includeContent, globalScopeFilter) {
|
|
66
|
+
// ① scope-prefilter (MP-3): a query saját scopeFilter-e VAGY a globalScopeFilter (a query nyer).
|
|
67
|
+
const rawScopeFilter = query.scopeFilter && query.scopeFilter.length ? query.scopeFilter : globalScopeFilter;
|
|
68
|
+
const scopeExpand = await scope_reference_1.FAM_ScopeResolver_ControlService.getInstance().expandForRead(rawScopeFilter);
|
|
69
|
+
// A config-feloldás kontextusa: az ELSŐ cél-tár (a multi-tábla query közös rangsorba kerül; a
|
|
70
|
+
// limit/dense-küszöbök táblánként eltérhetnek — az első tár konfigja az alap, dsgn-005 §6).
|
|
71
|
+
const primaryTable = query.tables[0];
|
|
72
|
+
const config = await this.resolveReadConfig(primaryTable, query);
|
|
73
|
+
// A query-vektort EGYSZER embeddeljük (a több tár ugyanazt a query-vektort használja).
|
|
74
|
+
const queryVector = await this.embedQuery(query.query, primaryTable);
|
|
75
|
+
// ②–④ per-tár: Mongo-prefilter → vektor-keresés a szűkített poolon → hit-row összefűzés.
|
|
76
|
+
const allHits = [];
|
|
77
|
+
for (const table of query.tables) {
|
|
78
|
+
const tableHits = await this.searchTable({ table, query, queryVector, scopeExpand, config, includeContent });
|
|
79
|
+
allHits.push(...tableHits);
|
|
80
|
+
}
|
|
81
|
+
// ⑤ weight-szorzó MÁR a searchTable-ben (finalScore). ⑥ minScore-küszöb → relevantSet.
|
|
82
|
+
const relevantSet = allHits.filter((hit) => hit.finalScore >= config.minScore);
|
|
83
|
+
// ⑦ rendezés (finalScore desc → recency → _id asc).
|
|
84
|
+
this.sortHits(relevantSet, config.scoreOrder);
|
|
85
|
+
// ⑧ topK-vágás + dense-detektálás + suggestions.
|
|
86
|
+
return this.buildResult({ query, relevantSet, config, scopeExpand });
|
|
87
|
+
}
|
|
88
|
+
// =========================================================================
|
|
89
|
+
// ②–④ PER-TÁR: prefilter → vektor-keresés → hit-row (SP-5.1/SP-5.2)
|
|
90
|
+
// =========================================================================
|
|
91
|
+
/**
|
|
92
|
+
* Egy tár találatai (dsgn-005 §2.1/§2.2/§2.4/§2.5). ① a scope subtree + ② tag/kind Mongo-prefilter
|
|
93
|
+
* a candidate-ID halmazt adja; ④ a vektor-keresés CSAK a candidate-ID-kon fut (prefilter→vektor,
|
|
94
|
+
* dsgn-006 §4.3); ⑤ a finalScore = score × weight a candidate-entry weight-jéből (coding_patterns 5).
|
|
95
|
+
* A vektor-keresés `topK`-ja a relevantSet teljes felvételéhez igazítva (NEM a végső topK — a
|
|
96
|
+
* dense-detektálás a topK ELŐTTI halmazon számol; dsgn-005 §4.1 / SP-5.2 feladat).
|
|
97
|
+
*/
|
|
98
|
+
async searchTable(set) {
|
|
99
|
+
// ①–② Mongo-prefilter → jelölt-entry-k (a soft-delete + rules.isActive guard beépített).
|
|
100
|
+
const candidates = await new fam_retrieval_candidate_data_service_1.FAM_RetrievalCandidate_DataService().loadCandidates({
|
|
101
|
+
table: set.table,
|
|
102
|
+
scopeIdSet: set.scopeExpand.scopeIdSet,
|
|
103
|
+
tagFilter: set.query.tagFilter,
|
|
104
|
+
kindFilter: set.query.kindFilter,
|
|
105
|
+
});
|
|
106
|
+
if (!candidates.length) {
|
|
107
|
+
return [];
|
|
108
|
+
}
|
|
109
|
+
// A candidate-entry-k id→entry indexe (a hit-row + weight + recency összefűzéséhez).
|
|
110
|
+
const byId = new Map();
|
|
111
|
+
const candidateIds = [];
|
|
112
|
+
for (const entry of candidates) {
|
|
113
|
+
if (entry._id) {
|
|
114
|
+
byId.set(entry._id, entry);
|
|
115
|
+
candidateIds.push(entry._id);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// ④ vektor-keresés a szűkített poolon (koszinusz; a vektor-réteg `limit`-je = a teljes
|
|
119
|
+
// jelölt-halmaz, hogy a teljes relevantSet meglegyen a dense-detektáláshoz — NEM a végső topK).
|
|
120
|
+
const vectorHits = await embedding_1.FAM_VectorSearch_ControlService.getInstance().search({
|
|
121
|
+
table: set.table,
|
|
122
|
+
queryVector: set.queryVector,
|
|
123
|
+
candidateIds: candidateIds,
|
|
124
|
+
topK: Math.min(candidateIds.length, MAX_PAGE_SIZE),
|
|
125
|
+
});
|
|
126
|
+
// ⑤ weight-szorzó: finalScore = score × (weight ?? 1); weight ≤ 0 → kizárt (nem kerül a hits közé).
|
|
127
|
+
const hits = [];
|
|
128
|
+
for (const vectorHit of vectorHits) {
|
|
129
|
+
const entry = byId.get(vectorHit.id);
|
|
130
|
+
if (!entry) {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
const weight = entry.weight ?? 1;
|
|
134
|
+
if (weight <= 0) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
hits.push(this.toHit({ entry, vectorHit, table: set.table, weight, includeContent: set.includeContent }));
|
|
138
|
+
}
|
|
139
|
+
return hits;
|
|
140
|
+
}
|
|
141
|
+
/** Egy candidate-entry + a vektor-hit → `FAM_RetrievalHit` (finalScore + denormalizált mezők). */
|
|
142
|
+
toHit(set) {
|
|
143
|
+
const lastModified = set.entry.__lastModified;
|
|
144
|
+
return {
|
|
145
|
+
id: set.vectorHit.id,
|
|
146
|
+
table: set.table,
|
|
147
|
+
score: set.vectorHit.score,
|
|
148
|
+
weight: set.weight,
|
|
149
|
+
finalScore: set.vectorHit.score * set.weight,
|
|
150
|
+
content: set.includeContent ? set.entry.content : undefined,
|
|
151
|
+
kind: set.entry.kind,
|
|
152
|
+
tags: set.entry.tags ?? [],
|
|
153
|
+
scopePath: (set.entry.scopePath ?? []),
|
|
154
|
+
source: set.entry.source,
|
|
155
|
+
sourceFilePath: set.entry.sourceFilePath ?? null,
|
|
156
|
+
lastModifiedMs: lastModified ? new Date(lastModified).getTime() : 0,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
// =========================================================================
|
|
160
|
+
// ⑦ RENDEZÉS (SP-5.2, dsgn-005 §5)
|
|
161
|
+
// =========================================================================
|
|
162
|
+
/**
|
|
163
|
+
* Kötött rendezés (dsgn-005 §5): finalScore DESC → `__lastModified` DESC (recency) → `_id` ASC
|
|
164
|
+
* (determinisztikus végső tiebreak — stabil, reprodukálható sorrend). A `scoreOrder` config a fő
|
|
165
|
+
* kulcsot váltja: `weighted` (default; finalScore), `raw` (nyers koszinusz score), `recency`
|
|
166
|
+
* (lastModified-elsődleges). A tiebreak-lánc mindkét score-rendnél a recency + `_id`.
|
|
167
|
+
*/
|
|
168
|
+
sortHits(hits, scoreOrder) {
|
|
169
|
+
hits.sort((a, b) => {
|
|
170
|
+
const primary = this.primaryComparator(a, b, scoreOrder);
|
|
171
|
+
if (primary !== 0) {
|
|
172
|
+
return primary;
|
|
173
|
+
}
|
|
174
|
+
// Recency tiebreak (frissebb előrébb azonos elsődleges kulcsnál).
|
|
175
|
+
if (b.lastModifiedMs !== a.lastModifiedMs) {
|
|
176
|
+
return b.lastModifiedMs - a.lastModifiedMs;
|
|
177
|
+
}
|
|
178
|
+
// Determinisztikus végső tiebreak (_id ASC) — stabil sorrend (teszt-stabilitás).
|
|
179
|
+
return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
/** Az elsődleges összehasonlító a `scoreOrder` szerint (DESC a score-okra, recency külön ágon). */
|
|
183
|
+
primaryComparator(a, b, scoreOrder) {
|
|
184
|
+
if (scoreOrder === 'raw') {
|
|
185
|
+
return b.score - a.score;
|
|
186
|
+
}
|
|
187
|
+
if (scoreOrder === 'recency') {
|
|
188
|
+
return b.lastModifiedMs - a.lastModifiedMs;
|
|
189
|
+
}
|
|
190
|
+
// 'weighted' (default).
|
|
191
|
+
return b.finalScore - a.finalScore;
|
|
192
|
+
}
|
|
193
|
+
// =========================================================================
|
|
194
|
+
// ⑧ topK-vágás + DENSE-DETEKTÁLÁS + signaling (SP-5.3, dsgn-005 §4)
|
|
195
|
+
// =========================================================================
|
|
196
|
+
/**
|
|
197
|
+
* A rendezett relevantSet → `FAM_RetrievalResult` (dsgn-005 §4). `totalRelevant = relevantSet.length`;
|
|
198
|
+
* `returnedHits` = az első topK; `truncated = totalRelevant > topK`. Dense dual-gate (§4.2):
|
|
199
|
+
* `(totalRelevant >= ratio × topK) || (totalRelevant >= absMin)`, DE csak `truncated:true` mellett
|
|
200
|
+
* (a kötés: vágás nélkül nincs elrejtett releváns tömeg). A suggestions a relevantSet valós
|
|
201
|
+
* statisztikáiból (§4.4). Az `uncertaintyNotes` a scope-prefilter jegyzetei (dsgn-002 §4).
|
|
202
|
+
*/
|
|
203
|
+
buildResult(set) {
|
|
204
|
+
const totalRelevant = set.relevantSet.length;
|
|
205
|
+
const effectiveTopK = set.config.topK;
|
|
206
|
+
const returnedHits = set.relevantSet.slice(0, effectiveTopK);
|
|
207
|
+
const truncated = totalRelevant > effectiveTopK;
|
|
208
|
+
// Dual-gate dense (§4.2) — csak `truncated:true` mellett lehet `true`.
|
|
209
|
+
const densenessRatio = totalRelevant / effectiveTopK;
|
|
210
|
+
const denseByRatio = densenessRatio >= set.config.denseResultRatio;
|
|
211
|
+
const denseByAbs = totalRelevant >= set.config.denseResultAbsMin;
|
|
212
|
+
const denseResults = truncated && (denseByRatio || denseByAbs);
|
|
213
|
+
// Suggestions (§4.4) — a relevantSet valós statisztikáiból + a query paramétereiből.
|
|
214
|
+
const suggestions = (denseResults || truncated)
|
|
215
|
+
? fam_retrieval_suggestions_util_1.FAM_RetrievalSuggestions_Util.generate({
|
|
216
|
+
relevantSet: set.relevantSet,
|
|
217
|
+
query: set.query,
|
|
218
|
+
effectiveTopK: effectiveTopK,
|
|
219
|
+
truncated: truncated,
|
|
220
|
+
maxPageSize: MAX_PAGE_SIZE,
|
|
221
|
+
})
|
|
222
|
+
: [];
|
|
223
|
+
return {
|
|
224
|
+
query: set.query.query,
|
|
225
|
+
tables: set.query.tables,
|
|
226
|
+
hits: returnedHits,
|
|
227
|
+
denseResults: denseResults,
|
|
228
|
+
totalRelevant: totalRelevant,
|
|
229
|
+
truncated: truncated,
|
|
230
|
+
suggestions: suggestions,
|
|
231
|
+
uncertaintyNotes: set.scopeExpand.uncertaintyNotes,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
// =========================================================================
|
|
235
|
+
// CONFIG-FELOLDÁS (SP-5.2, dsgn-005 §6 / dsgn-007 SSOT) + query-embedding (SP-5.2 ④)
|
|
236
|
+
// =========================================================================
|
|
237
|
+
/**
|
|
238
|
+
* A read-pipeline config-rétegének feloldása az ELSŐ cél-tárra (dsgn-005 §6; MP-7 precedencia
|
|
239
|
+
* global<table<scope). A `read.topK` az effektív `min(query.topK ?? config, MAX_PAGE_SIZE)` (a
|
|
240
|
+
* query-szintű override nyer a config-default felett); a `read.minScore` szintén query-override-os;
|
|
241
|
+
* a `read.denseResultRatio` / `read.denseResultAbsMin` / `read.scoreOrder` a config-rétegből
|
|
242
|
+
* (dsgn-007 birtokolja az értéket, MP-5 csak fogyasztja).
|
|
243
|
+
*/
|
|
244
|
+
async resolveReadConfig(table, query) {
|
|
245
|
+
const config = config_control_service_1.FAM_Config_ControlService.getInstance();
|
|
246
|
+
const ctx = { table: table };
|
|
247
|
+
// query.topK / query.minScore felülírja a config-default-ot (dsgn-003 §2.1 query-opció).
|
|
248
|
+
const topKDefault = query.topK ?? await this.resolveNumber(config, 'read.topK', ctx, 5);
|
|
249
|
+
const minScore = query.minScore ?? await this.resolveNumber(config, 'read.minScore', ctx, 0);
|
|
250
|
+
const denseResultRatio = await this.resolveNumber(config, 'read.denseResultRatio', ctx, 2.0);
|
|
251
|
+
const denseResultAbsMin = await this.resolveNumber(config, 'read.denseResultAbsMin', ctx, 25);
|
|
252
|
+
const scoreOrderValue = (await config.resolve('read.scoreOrder', ctx)).value;
|
|
253
|
+
const scoreOrder = typeof scoreOrderValue === 'string' ? scoreOrderValue : 'weighted';
|
|
254
|
+
return {
|
|
255
|
+
topK: Math.min(topKDefault, MAX_PAGE_SIZE),
|
|
256
|
+
minScore: minScore,
|
|
257
|
+
denseResultRatio: denseResultRatio,
|
|
258
|
+
denseResultAbsMin: denseResultAbsMin,
|
|
259
|
+
scoreOrder: scoreOrder,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
/** Egy szám-config feloldása a default-tal (a `resolve.value` lehet más típus → fallback). */
|
|
263
|
+
async resolveNumber(config, key, ctx, fallback) {
|
|
264
|
+
const value = (await config.resolve(key, ctx)).value;
|
|
265
|
+
return typeof value === 'number' ? value : fallback;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* A query-vektor előállítása (SP-5.2 ④; dsgn-005 §2.4). A provider-agnosztikus
|
|
269
|
+
* `FAM_Embedding_ControlService.embedOne` (a `table`-re feloldva — per-tár provider/modell
|
|
270
|
+
* override). Ez NEM LLM chat-completion (dsgn-005 §7) — csak embedding. A multi-tábla query
|
|
271
|
+
* ugyanazt a query-vektort használja minden tárra (egy embed-hívás).
|
|
272
|
+
*/
|
|
273
|
+
async embedQuery(query, table) {
|
|
274
|
+
return embedding_1.FAM_Embedding_ControlService.getInstance().embedOne({
|
|
275
|
+
text: query,
|
|
276
|
+
table: table,
|
|
277
|
+
callType: 'embed-query',
|
|
278
|
+
issuer: this.issuer,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
exports.FAM_Retrieval_ControlService = FAM_Retrieval_ControlService;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `retrieval` modul barrel (MP-5, dsgn-005) — a `read` egylépcsős retrieval-pipeline ENGINE publikus
|
|
4
|
+
* felülete. A `read` MCP-tool / REST (MP-6) innen importálja a `FAM_Retrieval_ControlService`-t és a
|
|
5
|
+
* pipeline I/O-vokabulárt; a kimenetet a dsgn-003 §2.2 wire-shape-re map-eli (a dsgn-003 az
|
|
6
|
+
* authoritatív MCP-kontraktus — itt nem definiáljuk újra).
|
|
7
|
+
*
|
|
8
|
+
* Boundary (dsgn-005 §7, ADR-009): MP-5 = a read-pipeline ENGINE. NINCS core LLM chunk-selector; a
|
|
9
|
+
* precíziót a vektor-score + weight + minScore + prefilter adja. A `read` tool / REST = MP-6; az
|
|
10
|
+
* opcionális relevance-filter = BACKLOG (NEM épül).
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.FAM_RetrievalSuggestions_Util = exports.FAM_RetrievalCandidate_DataService = exports.FAM_Retrieval_ControlService = void 0;
|
|
14
|
+
// SP-5.1/5.2/5.3 — a read-pipeline orchestrátor
|
|
15
|
+
var fam_retrieval_control_service_1 = require("./_services/fam-retrieval.control-service");
|
|
16
|
+
Object.defineProperty(exports, "FAM_Retrieval_ControlService", { enumerable: true, get: function () { return fam_retrieval_control_service_1.FAM_Retrieval_ControlService; } });
|
|
17
|
+
// SP-5.1 ① scope + ② tag/kind Mongo-prefilter (a vektor-keresés ELŐTT)
|
|
18
|
+
var fam_retrieval_candidate_data_service_1 = require("./_services/fam-retrieval-candidate.data-service");
|
|
19
|
+
Object.defineProperty(exports, "FAM_RetrievalCandidate_DataService", { enumerable: true, get: function () { return fam_retrieval_candidate_data_service_1.FAM_RetrievalCandidate_DataService; } });
|
|
20
|
+
// SP-5.3 dense-suggestion generátor (a relevantSet valós statisztikáiból)
|
|
21
|
+
var fam_retrieval_suggestions_util_1 = require("./_services/fam-retrieval-suggestions.util");
|
|
22
|
+
Object.defineProperty(exports, "FAM_RetrievalSuggestions_Util", { enumerable: true, get: function () { return fam_retrieval_suggestions_util_1.FAM_RetrievalSuggestions_Util; } });
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FAM_FuzzyMatch_Util = void 0;
|
|
4
|
+
const fam_scope_normalize_util_1 = require("./fam-scope-normalize.util");
|
|
5
|
+
/**
|
|
6
|
+
* `FAM_FuzzyMatch_Util` (SP-3.2, dsgn-002 §3.1 string-fuzzy ág) — **projekt-lokális workaround**:
|
|
7
|
+
* a Dynamo-szinten nincs dedikált string-fuzzy, ezért normalizált Levenshtein-távolságon alapuló
|
|
8
|
+
* typo-toleráns string-similarity. A fonetikus/STT-variánsok eleve az `aliasTerms`-ben élnek (a
|
|
9
|
+
* dsgn-002 §5 alias-csomagok); a string-fuzzy a KATALOGIZÁLATLAN elírásokat fogja meg
|
|
10
|
+
* (pl. `"Dyanmo FSM"` → `"Dynamo FSM"`).
|
|
11
|
+
*
|
|
12
|
+
* **Hossz-arányos szigorítás (SP-3.2 kockázat):** a similarity score `1 - distance/maxLen`,
|
|
13
|
+
* de a rövid token false-positive-jának elkerülésére a NYERS score-t adjuk vissza, a küszöbölést
|
|
14
|
+
* (`reference.fuzzyMinScore`) a hívó (resolver) végzi — a rövid tokennél eleve magasabb relatív
|
|
15
|
+
* távolság kell a match-hez.
|
|
16
|
+
*/
|
|
17
|
+
class FAM_FuzzyMatch_Util {
|
|
18
|
+
/**
|
|
19
|
+
* Normalizált string-similarity score 0..1 (1 = azonos normalizált forma). A két tokent a
|
|
20
|
+
* `FAM_ScopeNormalize_Util`-on át normalizálja (közös choke-pont), majd a Levenshtein-távolságot
|
|
21
|
+
* a hosszabb stringre vetíti: `1 - distance/maxLen`. Üres normalizált forma → 0 (nem match-el).
|
|
22
|
+
*/
|
|
23
|
+
static similarity(a, b) {
|
|
24
|
+
const normalizedA = fam_scope_normalize_util_1.FAM_ScopeNormalize_Util.normalize(a);
|
|
25
|
+
const normalizedB = fam_scope_normalize_util_1.FAM_ScopeNormalize_Util.normalize(b);
|
|
26
|
+
if (!normalizedA.length || !normalizedB.length) {
|
|
27
|
+
return 0;
|
|
28
|
+
}
|
|
29
|
+
if (normalizedA === normalizedB) {
|
|
30
|
+
return 1;
|
|
31
|
+
}
|
|
32
|
+
const maxLen = Math.max(normalizedA.length, normalizedB.length);
|
|
33
|
+
const distance = FAM_FuzzyMatch_Util.levenshtein(normalizedA, normalizedB);
|
|
34
|
+
const score = 1 - distance / maxLen;
|
|
35
|
+
return score < 0 ? 0 : score;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* A legjobb string-similarity score egy token és egy term-halmaz (canonicalTerm + aliasTerms)
|
|
39
|
+
* között — a halmaz BÁRMELY tagjával vett legmagasabb similarity. A fonetikus/typo variánsok
|
|
40
|
+
* elszórtan vannak az `aliasTerms`-ben, ezért a max-aggregálás a helyes (egyetlen alias-egyezés
|
|
41
|
+
* is felemeli a score-t).
|
|
42
|
+
*/
|
|
43
|
+
static bestSimilarity(token, terms) {
|
|
44
|
+
let best = 0;
|
|
45
|
+
for (const term of terms) {
|
|
46
|
+
const score = FAM_FuzzyMatch_Util.similarity(token, term);
|
|
47
|
+
if (score > best) {
|
|
48
|
+
best = score;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return best;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Levenshtein-edit-távolság (insert/delete/substitute) két string között — két-soros DP
|
|
55
|
+
* (memória O(min(n,m))). Determinisztikus, függőség-mentes (a teszt-determinizmushoz).
|
|
56
|
+
*/
|
|
57
|
+
static levenshtein(a, b) {
|
|
58
|
+
if (a === b) {
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
if (!a.length) {
|
|
62
|
+
return b.length;
|
|
63
|
+
}
|
|
64
|
+
if (!b.length) {
|
|
65
|
+
return a.length;
|
|
66
|
+
}
|
|
67
|
+
// Két-soros DP: a `previous` az előző sor, a `current` az aktuális.
|
|
68
|
+
let previous = [];
|
|
69
|
+
for (let j = 0; j <= b.length; j++) {
|
|
70
|
+
previous[j] = j;
|
|
71
|
+
}
|
|
72
|
+
for (let i = 1; i <= a.length; i++) {
|
|
73
|
+
const current = [i];
|
|
74
|
+
for (let j = 1; j <= b.length; j++) {
|
|
75
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
76
|
+
const deletion = previous[j] + 1;
|
|
77
|
+
const insertion = current[j - 1] + 1;
|
|
78
|
+
const substitution = previous[j - 1] + cost;
|
|
79
|
+
current[j] = Math.min(deletion, insertion, substitution);
|
|
80
|
+
}
|
|
81
|
+
previous = current;
|
|
82
|
+
}
|
|
83
|
+
return previous[b.length];
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
exports.FAM_FuzzyMatch_Util = FAM_FuzzyMatch_Util;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FAM_ScopeNormalize_Util = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* `FAM_ScopeNormalize_Util` (SP-3.1, dsgn-002 §3.1 fast-path) — statikus, **függőség-mentes**
|
|
6
|
+
* normalizáló util. EGYETLEN choke-pont a de-dup kulcshoz és az exact-match fast-path-hez:
|
|
7
|
+
* lowercase + ékezet-fold (Unicode NFD diakritika-strip) + whitespace-collapse + trim.
|
|
8
|
+
*
|
|
9
|
+
* A tárolt `canonicalName` / `canonicalTerm` az EREDETI (megjelenítéshez); a feloldási / de-dup
|
|
10
|
+
* KULCS a normalizált forma. Ha a normalizálás nem fedi az ékezet/STT/whitespace-variánsokat,
|
|
11
|
+
* ugyanaz az entitás két `scopeId`-t kap → a determinizmus-acceptance (dsgn-002 §7.6) bukik;
|
|
12
|
+
* ezért minden normalizálás ITT fut át (a scope-de-dup és a reference exact-match is).
|
|
13
|
+
*/
|
|
14
|
+
class FAM_ScopeNormalize_Util {
|
|
15
|
+
/**
|
|
16
|
+
* A Unicode kombináló-diakritika tartomány (U+0300..U+036F) — NFD-bontás után ezt strip-eljük.
|
|
17
|
+
* `new RegExp` explicit `\u`-escape-pel (a literál kombináló-karakter encoding-ambiguitás ellen).
|
|
18
|
+
*/
|
|
19
|
+
static DIACRITICS = new RegExp('[\\u0300-\\u036f]', 'g');
|
|
20
|
+
/**
|
|
21
|
+
* Egy nyers token normalizálása a de-dup / exact-match kulcshoz (dsgn-002 §3.1). A lépések:
|
|
22
|
+
* (1) lowercase (Unicode-aware), (2) ékezet-fold (NFD + diakritika-strip — `"Adventör"` →
|
|
23
|
+
* `"adventor"`), (3) belső whitespace-collapse egyetlen szóközre, (4) trim. Üres/nem-string
|
|
24
|
+
* bemenet → üres string (a hívó dönti el, hogy az üres kulcs match-elhet-e).
|
|
25
|
+
*/
|
|
26
|
+
static normalize(raw) {
|
|
27
|
+
if (!raw) {
|
|
28
|
+
return '';
|
|
29
|
+
}
|
|
30
|
+
return raw
|
|
31
|
+
.toLowerCase()
|
|
32
|
+
.normalize('NFD')
|
|
33
|
+
.replace(FAM_ScopeNormalize_Util.DIACRITICS, '')
|
|
34
|
+
.replace(/\s+/g, ' ')
|
|
35
|
+
.trim();
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Két nyers token normalizált-egyezése (a fast-path egyenlőség-tesztje). Üres normalizált
|
|
39
|
+
* forma SOHA nem match-el (egy üres token nem old fel semmire — dsgn-002 §4 nincs-match ág).
|
|
40
|
+
*/
|
|
41
|
+
static equals(a, b) {
|
|
42
|
+
const normalizedA = FAM_ScopeNormalize_Util.normalize(a);
|
|
43
|
+
const normalizedB = FAM_ScopeNormalize_Util.normalize(b);
|
|
44
|
+
return normalizedA.length > 0 && normalizedA === normalizedB;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
exports.FAM_ScopeNormalize_Util = FAM_ScopeNormalize_Util;
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FAM_Reference_DataService = void 0;
|
|
4
|
+
const nts_dynamo_1 = require("@futdevpro/nts-dynamo");
|
|
5
|
+
const fam_reference_data_model_1 = require("../../../_models/data-models/fam-reference.data-model");
|
|
6
|
+
const fam_fuzzy_match_util_1 = require("../_collections/fam-fuzzy-match.util");
|
|
7
|
+
const fam_scope_normalize_util_1 = require("../_collections/fam-scope-normalize.util");
|
|
8
|
+
/** A reference-alapú string/exact match küszöbök. */
|
|
9
|
+
const VECTOR_TOP_N = 5;
|
|
10
|
+
/**
|
|
11
|
+
* `FAM_Reference_DataService` (SP-3.2, dsgn-002 §3.1/§6) — a `fam_reference` collection CRUD + a
|
|
12
|
+
* **kétfázisú fuzzy match**: (a) exact fast-path (`canonicalTerm`/`aliasTerms` normalizált egyezés),
|
|
13
|
+
* (b) string-fuzzy (`FAM_FuzzyMatch_Util` Levenshtein) ÉS vektor-fuzzy (cosine a `contentVector`-on).
|
|
14
|
+
* Csak `isActive: true` bejegyzés expand-elődik (dsgn-002 §3.1).
|
|
15
|
+
*
|
|
16
|
+
* `extends DyNTS_DataService` (soft-delete = move-to-archive a `getArchiveDataService`-en át). A
|
|
17
|
+
* vektor-fuzzy az MP-2-re **lágy** dependens: a `vectorMatch` egy KÉSZ query-vektort + a
|
|
18
|
+
* reference-korpuszt kapja (a resolver embeddeli a tokent), így a string-fuzzy ág MP-2 NÉLKÜL is teljes értékű.
|
|
19
|
+
*
|
|
20
|
+
* **FIGYELEM (memory: dynts_dataservice_eager_resolve):** a base-ctor EAGER `getDBService`-t hív —
|
|
21
|
+
* a control-service-ek NEM tartanak élő példányt mezőként; minden művelet előtt lazy `new`.
|
|
22
|
+
*/
|
|
23
|
+
class FAM_Reference_DataService extends nts_dynamo_1.DyNTS_DataService {
|
|
24
|
+
constructor(set) {
|
|
25
|
+
super(set.data instanceof fam_reference_data_model_1.FAM_Reference_DataModel ? set.data : new fam_reference_data_model_1.FAM_Reference_DataModel(set.data), fam_reference_data_model_1.famReference_dataParams, set.issuer);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* A move-to-archive soft-delete (dsgn-001 §8) archív-collection service-e: a base `deleteData`
|
|
29
|
+
* (addArchive) ezt hívja, hogy a törölt reference-t a `fam_reference_archived` tárba mozgassa. A
|
|
30
|
+
* `DyNTS_DataService` base default-ja DOB (nincs ökoszisztéma-implementáció) — ezért az explicit
|
|
31
|
+
* override (a `DyNTS_ArchiveDataService` a `dataParams.addArchive`-ból oldja az archív-nevet).
|
|
32
|
+
*/
|
|
33
|
+
getArchiveDataService() {
|
|
34
|
+
return new nts_dynamo_1.DyNTS_ArchiveDataService(new fam_reference_data_model_1.FAM_Reference_DataModel(), this.dataParams, this.issuer);
|
|
35
|
+
}
|
|
36
|
+
// =========================================================================
|
|
37
|
+
// CRUD + active-only (dsgn-002 §3.1)
|
|
38
|
+
// =========================================================================
|
|
39
|
+
/** Egy reference `_id` alapján (vagy üres-objektum, ha nincs / törölt). */
|
|
40
|
+
async findReferenceById(referenceId) {
|
|
41
|
+
return this.findData({ _id: referenceId }, true);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* `findActive()` (dsgn-002 §3.1) — az AKTÍV (`isActive: true`, nem soft-delete-elt) reference-ek.
|
|
45
|
+
* Az inaktív bejegyzés NEM expand-elődik. A resolver-cache (SP-3.3) ezt snapshot-olja.
|
|
46
|
+
*/
|
|
47
|
+
async findActive() {
|
|
48
|
+
return this.findDataList({ isActive: true }, true);
|
|
49
|
+
}
|
|
50
|
+
// =========================================================================
|
|
51
|
+
// EXACT FAST-PATH (dsgn-002 §3.1)
|
|
52
|
+
// =========================================================================
|
|
53
|
+
/**
|
|
54
|
+
* `exactMatch(rawToken, references)` (SP-3.2) — normalizált exact egyezés a `canonicalTerm`
|
|
55
|
+
* VAGY bármely `aliasTerm` ellen (a `FAM_ScopeNormalize_Util` közös choke-pontján). A korpuszt
|
|
56
|
+
* a hívó adja (cache-snapshot) — nincs per-hívás DB-kör. Az első egyezés a győztes (a korpusz
|
|
57
|
+
* de-dup-olt). Üres token / nincs egyezés → `null`.
|
|
58
|
+
*/
|
|
59
|
+
exactMatch(rawToken, references) {
|
|
60
|
+
const targetKey = fam_scope_normalize_util_1.FAM_ScopeNormalize_Util.normalize(rawToken);
|
|
61
|
+
if (!targetKey.length) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
for (const reference of references) {
|
|
65
|
+
if (fam_scope_normalize_util_1.FAM_ScopeNormalize_Util.normalize(reference.canonicalTerm) === targetKey) {
|
|
66
|
+
return reference;
|
|
67
|
+
}
|
|
68
|
+
const aliases = reference.aliasTerms ?? [];
|
|
69
|
+
for (const alias of aliases) {
|
|
70
|
+
if (fam_scope_normalize_util_1.FAM_ScopeNormalize_Util.normalize(alias) === targetKey) {
|
|
71
|
+
return reference;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
// =========================================================================
|
|
78
|
+
// STRING-FUZZY (dsgn-002 §3.1, SP-3.2)
|
|
79
|
+
// =========================================================================
|
|
80
|
+
/**
|
|
81
|
+
* `stringFuzzyMatch(rawToken, references)` (SP-3.2) — Levenshtein-alapú string-similarity a
|
|
82
|
+
* `canonicalTerm` + minden `aliasTerm` ellen (a katalogizálatlan elírásokra, pl. `"Dyanmo FSM"`).
|
|
83
|
+
* Per-reference a LEGJOBB term-similarity a score; minden reference egy kandidátus. A küszöbölést
|
|
84
|
+
* (`reference.fuzzyMinScore`) a resolver (SP-3.4) végzi — ITT a NYERS score-os kandidátusok.
|
|
85
|
+
*/
|
|
86
|
+
stringFuzzyMatch(rawToken, references) {
|
|
87
|
+
const candidates = [];
|
|
88
|
+
for (const reference of references) {
|
|
89
|
+
const terms = [reference.canonicalTerm ?? '', ...(reference.aliasTerms ?? [])].filter((t) => t.length > 0);
|
|
90
|
+
if (!terms.length) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const score = fam_fuzzy_match_util_1.FAM_FuzzyMatch_Util.bestSimilarity(rawToken, terms);
|
|
94
|
+
if (score <= 0) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
candidates.push(this.toCandidate(reference, score, 'string-fuzzy'));
|
|
98
|
+
}
|
|
99
|
+
return candidates;
|
|
100
|
+
}
|
|
101
|
+
// =========================================================================
|
|
102
|
+
// VEKTOR-FUZZY (dsgn-002 §3.1, SP-3.2) — SOFT dep MP-2
|
|
103
|
+
// =========================================================================
|
|
104
|
+
/**
|
|
105
|
+
* `vectorMatch(queryVector, references)` (SP-3.2) — koszinusz-similarity a kész query-vektor és
|
|
106
|
+
* a reference-ek `contentVector`-ja között (a szemantikai közelséget fogja, amit a string-fuzzy
|
|
107
|
+
* NEM). A query-vektort a resolver embeddeli (MP-2 `FAM_Embedding_ControlService`); ÜRES vektor
|
|
108
|
+
* (embedding-hiány) → üres lista (graceful degrade — a resolver string-only fallbackra vált + warn).
|
|
109
|
+
* A dimenzió-eltérésű reference-vektort kihagyja (nem kever eltérő dimenziót). Top-N kandidátus.
|
|
110
|
+
*/
|
|
111
|
+
vectorMatch(queryVector, references) {
|
|
112
|
+
if (!queryVector.length) {
|
|
113
|
+
return [];
|
|
114
|
+
}
|
|
115
|
+
const candidates = [];
|
|
116
|
+
for (const reference of references) {
|
|
117
|
+
const vector = reference.contentVector ?? [];
|
|
118
|
+
if (vector.length !== queryVector.length) {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
const score = FAM_Reference_DataService.cosineSimilarity(queryVector, vector);
|
|
122
|
+
if (score <= 0) {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
candidates.push(this.toCandidate(reference, score, 'vector-fuzzy'));
|
|
126
|
+
}
|
|
127
|
+
candidates.sort((a, b) => b.score - a.score);
|
|
128
|
+
return candidates.slice(0, VECTOR_TOP_N);
|
|
129
|
+
}
|
|
130
|
+
// =========================================================================
|
|
131
|
+
// helpers
|
|
132
|
+
// =========================================================================
|
|
133
|
+
/** A `FAM_FuzzyResolveContext` szűrés alkalmazása egy korpuszra (csak a releváns layer/tár). */
|
|
134
|
+
filterByContext(references, context) {
|
|
135
|
+
if (!context || (!context.scopeLayer && !(context.ragScopes && context.ragScopes.length))) {
|
|
136
|
+
return references;
|
|
137
|
+
}
|
|
138
|
+
return references.filter((reference) => {
|
|
139
|
+
if (context.scopeLayer && reference.scopeLayer && reference.scopeLayer !== context.scopeLayer) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
if (context.ragScopes && context.ragScopes.length) {
|
|
143
|
+
const refScopes = (reference.ragScopes ?? []);
|
|
144
|
+
// Üres ragScopes a reference-en = minden tárra alkalmazható (dsgn-001 §5).
|
|
145
|
+
if (refScopes.length && !refScopes.some((scope) => context.ragScopes?.includes(scope))) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return true;
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
/** Egy reference → kandidátus map (a canonicalScopeRef / scopeLayer / referenceId átemelése). */
|
|
153
|
+
toCandidate(reference, score, matchSource) {
|
|
154
|
+
return {
|
|
155
|
+
canonicalTerm: reference.canonicalTerm ?? '',
|
|
156
|
+
score: score,
|
|
157
|
+
matchSource: matchSource,
|
|
158
|
+
canonicalScopeRef: reference.canonicalScopeRef,
|
|
159
|
+
scopeLayer: reference.scopeLayer,
|
|
160
|
+
referenceId: reference._id,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
/** Koszinusz-similarity két azonos-dimenziójú vektor között (0..1 l2-normalizálással). */
|
|
164
|
+
static cosineSimilarity(a, b) {
|
|
165
|
+
let dot = 0;
|
|
166
|
+
let normA = 0;
|
|
167
|
+
let normB = 0;
|
|
168
|
+
for (let i = 0; i < a.length; i++) {
|
|
169
|
+
dot += a[i] * b[i];
|
|
170
|
+
normA += a[i] * a[i];
|
|
171
|
+
normB += b[i] * b[i];
|
|
172
|
+
}
|
|
173
|
+
if (!normA || !normB) {
|
|
174
|
+
return 0;
|
|
175
|
+
}
|
|
176
|
+
return dot / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
exports.FAM_Reference_DataService = FAM_Reference_DataService;
|