memento-mcp-server 1.15.0 → 1.16.0-a
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/domains/relation/tools/visualize-relations-tool.d.ts +2 -2
- package/dist/infrastructure/database/database/migration/migrations/009-quality-assurance-schema.d.ts +60 -0
- package/dist/infrastructure/database/database/migration/migrations/009-quality-assurance-schema.d.ts.map +1 -0
- package/dist/infrastructure/database/database/migration/migrations/009-quality-assurance-schema.js +276 -0
- package/dist/infrastructure/database/database/migration/migrations/009-quality-assurance-schema.js.map +1 -0
- package/dist/infrastructure/database/database/migration/migrations/009-quality-assurance-schema.sql +128 -0
- package/dist/infrastructure/scheduler/batch-scheduler.d.ts +17 -0
- package/dist/infrastructure/scheduler/batch-scheduler.d.ts.map +1 -1
- package/dist/infrastructure/scheduler/batch-scheduler.js +124 -0
- package/dist/infrastructure/scheduler/batch-scheduler.js.map +1 -1
- package/dist/infrastructure/scheduler/jobs/quality-measurement-batch-job.d.ts +108 -0
- package/dist/infrastructure/scheduler/jobs/quality-measurement-batch-job.d.ts.map +1 -0
- package/dist/infrastructure/scheduler/jobs/quality-measurement-batch-job.js +184 -0
- package/dist/infrastructure/scheduler/jobs/quality-measurement-batch-job.js.map +1 -0
- package/dist/server/http-server.d.ts.map +1 -1
- package/dist/server/http-server.js +3 -0
- package/dist/server/http-server.js.map +1 -1
- package/dist/server/routes/quality.routes.d.ts +14 -0
- package/dist/server/routes/quality.routes.d.ts.map +1 -0
- package/dist/server/routes/quality.routes.js +460 -0
- package/dist/server/routes/quality.routes.js.map +1 -0
- package/dist/services/quality-assurance/quality-assurance-service.d.ts +207 -0
- package/dist/services/quality-assurance/quality-assurance-service.d.ts.map +1 -0
- package/dist/services/quality-assurance/quality-assurance-service.js +247 -0
- package/dist/services/quality-assurance/quality-assurance-service.js.map +1 -0
- package/dist/services/quality-assurance/quality-evaluator.d.ts +163 -0
- package/dist/services/quality-assurance/quality-evaluator.d.ts.map +1 -0
- package/dist/services/quality-assurance/quality-evaluator.js +256 -0
- package/dist/services/quality-assurance/quality-evaluator.js.map +1 -0
- package/dist/services/quality-assurance/quality-metrics-collector.d.ts +219 -0
- package/dist/services/quality-assurance/quality-metrics-collector.d.ts.map +1 -0
- package/dist/services/quality-assurance/quality-metrics-collector.js +725 -0
- package/dist/services/quality-assurance/quality-metrics-collector.js.map +1 -0
- package/dist/services/quality-assurance/quality-recorder.d.ts +108 -0
- package/dist/services/quality-assurance/quality-recorder.d.ts.map +1 -0
- package/dist/services/quality-assurance/quality-recorder.js +281 -0
- package/dist/services/quality-assurance/quality-recorder.js.map +1 -0
- package/dist/services/quality-assurance/quality-reporter.d.ts +189 -0
- package/dist/services/quality-assurance/quality-reporter.d.ts.map +1 -0
- package/dist/services/quality-assurance/quality-reporter.js +558 -0
- package/dist/services/quality-assurance/quality-reporter.js.map +1 -0
- package/dist/services/quality-assurance/quality-threshold-manager.d.ts +102 -0
- package/dist/services/quality-assurance/quality-threshold-manager.d.ts.map +1 -0
- package/dist/services/quality-assurance/quality-threshold-manager.js +252 -0
- package/dist/services/quality-assurance/quality-threshold-manager.js.map +1 -0
- package/dist/test/helpers/search-quality-metrics.d.ts +96 -0
- package/dist/test/helpers/search-quality-metrics.d.ts.map +1 -0
- package/dist/test/helpers/search-quality-metrics.js +185 -0
- package/dist/test/helpers/search-quality-metrics.js.map +1 -0
- package/dist/test/helpers/vector-search-quality-metrics.d.ts +1287 -0
- package/dist/test/helpers/vector-search-quality-metrics.d.ts.map +1 -0
- package/dist/test/helpers/vector-search-quality-metrics.js +2214 -0
- package/dist/test/helpers/vector-search-quality-metrics.js.map +1 -0
- package/package.json +4 -1
- package/scripts/quality-report.ts +166 -0
- package/scripts/quality-thresholds.ts +279 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quality-threshold-manager.d.ts","sourceRoot":"","sources":["../../../src/services/quality-assurance/quality-threshold-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAItC;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,KAAK,GAAG,KAAK,CAAC;IAC9B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,KAAK,GAAG,KAAK,CAAC;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAwCD;;GAEG;AACH,qBAAa,uBAAuB;IACtB,OAAO,CAAC,EAAE;gBAAF,EAAE,EAAE,QAAQ,CAAC,QAAQ;IAMzC;;;;;;;OAOG;IACH,YAAY,CACV,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,MAAkB,GAC1B,gBAAgB,GAAG,IAAI;IAkB1B;;;;;;OAMG;IACH,gBAAgB,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,gBAAgB,EAAE;IAmC1E;;;;;;;;OAQG;IACH,YAAY,CACV,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,gBAAgB,EACzB,OAAO,GAAE,MAAkB,GAC1B,gBAAgB;IAiDnB;;;;;;;OAOG;IACH,eAAe,CACb,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,MAAkB,GAC1B,OAAO;IAeV;;;;;;;;;OASG;IACH,2BAA2B,CAAC,OAAO,GAAE,MAAkB,EAAE,SAAS,GAAE,OAAe,GAAG,MAAM;IA6B5F;;;;;;;;;;OAUG;IACH,iBAAiB,CACf,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,MAAkB,GAC1B;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,gBAAgB,GAAG,IAAI,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE;CAgC5E"}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quality Threshold Manager
|
|
3
|
+
*
|
|
4
|
+
* 품질 임계값 관리 서비스
|
|
5
|
+
*
|
|
6
|
+
* 주요 기능:
|
|
7
|
+
* - 품질 임계값 CRUD (생성, 조회, 업데이트, 삭제)
|
|
8
|
+
* - 기본 임계값 초기화
|
|
9
|
+
* - 임계값 검증
|
|
10
|
+
*/
|
|
11
|
+
import Database from 'better-sqlite3';
|
|
12
|
+
import { DatabaseUtils } from '../../shared/utils/database.js';
|
|
13
|
+
import { logger } from '../../shared/utils/logger.js';
|
|
14
|
+
/**
|
|
15
|
+
* 기본 임계값 정의
|
|
16
|
+
*
|
|
17
|
+
* PRD 4.1: 기본 품질 임계값 초기화 로직
|
|
18
|
+
* 보수적 초기값 설정 원칙 적용
|
|
19
|
+
*/
|
|
20
|
+
const DEFAULT_THRESHOLDS = [
|
|
21
|
+
// 검색 품질 지표
|
|
22
|
+
{ namespace: 'search', key: 'precision_at_5', value: 0.7, type: 'min', description: 'Precision@5 최소값 (0.7 이상)' },
|
|
23
|
+
{ namespace: 'search', key: 'precision_at_10', value: 0.65, type: 'min', description: 'Precision@10 최소값 (0.65 이상)' },
|
|
24
|
+
{ namespace: 'search', key: 'recall_at_5', value: 0.6, type: 'min', description: 'Recall@5 최소값 (0.6 이상)' },
|
|
25
|
+
{ namespace: 'search', key: 'recall_at_10', value: 0.7, type: 'min', description: 'Recall@10 최소값 (0.7 이상)' },
|
|
26
|
+
{ namespace: 'search', key: 'ndcg_at_5', value: 0.65, type: 'min', description: 'NDCG@5 최소값 (0.65 이상)' },
|
|
27
|
+
{ namespace: 'search', key: 'ndcg_at_10', value: 0.7, type: 'min', description: 'NDCG@10 최소값 (0.7 이상)' },
|
|
28
|
+
{ namespace: 'search', key: 'mrr', value: 0.6, type: 'min', description: 'MRR 최소값 (0.6 이상)' },
|
|
29
|
+
{ namespace: 'search', key: 'kendalls_tau', value: 0.5, type: 'min', description: "Kendall's Tau 최소값 (0.5 이상)" },
|
|
30
|
+
// 관계 추출 품질 지표
|
|
31
|
+
{ namespace: 'relation', key: 'precision', value: 0.6, type: 'min', description: '관계 추출 Precision 최소값 (0.6 이상)' },
|
|
32
|
+
{ namespace: 'relation', key: 'recall', value: 0.5, type: 'min', description: '관계 추출 Recall 최소값 (0.5 이상)' },
|
|
33
|
+
{ namespace: 'relation', key: 'f1_score', value: 0.6, type: 'min', description: '관계 추출 F1-Score 최소값 (0.6 이상)' },
|
|
34
|
+
// Consolidation 점수 품질 지표
|
|
35
|
+
{ namespace: 'consolidation', key: 'score_stability', value: 0.7, type: 'min', description: 'Consolidation 점수 안정성 최소값 (0.7 이상)' },
|
|
36
|
+
{ namespace: 'consolidation', key: 'order_preservation', value: 0.8, type: 'min', description: '순서 보존율 최소값 (0.8 이상)' },
|
|
37
|
+
// 저장 품질 지표
|
|
38
|
+
{ namespace: 'storage', key: 'duplication_rate', value: 0.05, type: 'max', description: '중복 비율 최대값 (5% 이하)' },
|
|
39
|
+
{ namespace: 'storage', key: 'data_integrity', value: 0.95, type: 'min', description: '데이터 무결성 최소값 (95% 이상)' },
|
|
40
|
+
{ namespace: 'storage', key: 'schema_compliance', value: 0.98, type: 'min', description: '스키마 준수율 최소값 (98% 이상)' }
|
|
41
|
+
];
|
|
42
|
+
/**
|
|
43
|
+
* Quality Threshold Manager
|
|
44
|
+
*/
|
|
45
|
+
export class QualityThresholdManager {
|
|
46
|
+
db;
|
|
47
|
+
constructor(db) {
|
|
48
|
+
this.db = db;
|
|
49
|
+
if (!db) {
|
|
50
|
+
throw new Error('Database instance is required');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* 임계값 조회
|
|
55
|
+
*
|
|
56
|
+
* @param namespace - 지표 네임스페이스 (예: 'search', 'relation')
|
|
57
|
+
* @param key - 지표 키 (예: 'precision_at_5')
|
|
58
|
+
* @param context - 컨텍스트 (기본값: 'default')
|
|
59
|
+
* @returns 임계값 정보 또는 null
|
|
60
|
+
*/
|
|
61
|
+
getThreshold(namespace, key, context = 'default') {
|
|
62
|
+
const sql = `
|
|
63
|
+
SELECT
|
|
64
|
+
metric_namespace,
|
|
65
|
+
metric_key,
|
|
66
|
+
context,
|
|
67
|
+
threshold_value,
|
|
68
|
+
threshold_type,
|
|
69
|
+
description,
|
|
70
|
+
updated_at
|
|
71
|
+
FROM quality_thresholds
|
|
72
|
+
WHERE metric_namespace = ? AND metric_key = ? AND context = ?
|
|
73
|
+
`;
|
|
74
|
+
const result = DatabaseUtils.get(this.db, sql, [namespace, key, context]);
|
|
75
|
+
return result || null;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* 모든 임계값 조회
|
|
79
|
+
*
|
|
80
|
+
* @param namespace - 네임스페이스 필터 (선택적)
|
|
81
|
+
* @param context - 컨텍스트 필터 (선택적)
|
|
82
|
+
* @returns 임계값 목록
|
|
83
|
+
*/
|
|
84
|
+
getAllThresholds(namespace, context) {
|
|
85
|
+
let sql = `
|
|
86
|
+
SELECT
|
|
87
|
+
metric_namespace,
|
|
88
|
+
metric_key,
|
|
89
|
+
context,
|
|
90
|
+
threshold_value,
|
|
91
|
+
threshold_type,
|
|
92
|
+
description,
|
|
93
|
+
updated_at
|
|
94
|
+
FROM quality_thresholds
|
|
95
|
+
`;
|
|
96
|
+
const params = [];
|
|
97
|
+
const conditions = [];
|
|
98
|
+
if (namespace) {
|
|
99
|
+
conditions.push('metric_namespace = ?');
|
|
100
|
+
params.push(namespace);
|
|
101
|
+
}
|
|
102
|
+
if (context) {
|
|
103
|
+
conditions.push('context = ?');
|
|
104
|
+
params.push(context);
|
|
105
|
+
}
|
|
106
|
+
if (conditions.length > 0) {
|
|
107
|
+
sql += ' WHERE ' + conditions.join(' AND ');
|
|
108
|
+
}
|
|
109
|
+
sql += ' ORDER BY metric_namespace, metric_key, context';
|
|
110
|
+
return DatabaseUtils.all(this.db, sql, params);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* 임계값 설정 (생성 또는 업데이트)
|
|
114
|
+
*
|
|
115
|
+
* @param namespace - 지표 네임스페이스
|
|
116
|
+
* @param key - 지표 키
|
|
117
|
+
* @param options - 임계값 옵션
|
|
118
|
+
* @param context - 컨텍스트 (기본값: 'default')
|
|
119
|
+
* @returns 설정된 임계값 정보
|
|
120
|
+
*/
|
|
121
|
+
setThreshold(namespace, key, options, context = 'default') {
|
|
122
|
+
const { threshold_value, threshold_type, description } = options;
|
|
123
|
+
// 임계값 검증
|
|
124
|
+
if (threshold_value < 0 || threshold_value > 1) {
|
|
125
|
+
throw new Error(`임계값은 0과 1 사이의 값이어야 합니다: ${threshold_value}`);
|
|
126
|
+
}
|
|
127
|
+
const now = new Date().toISOString();
|
|
128
|
+
const sql = `
|
|
129
|
+
INSERT INTO quality_thresholds (
|
|
130
|
+
metric_namespace,
|
|
131
|
+
metric_key,
|
|
132
|
+
context,
|
|
133
|
+
threshold_value,
|
|
134
|
+
threshold_type,
|
|
135
|
+
description,
|
|
136
|
+
updated_at
|
|
137
|
+
)
|
|
138
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
139
|
+
ON CONFLICT(metric_namespace, metric_key, context)
|
|
140
|
+
DO UPDATE SET
|
|
141
|
+
threshold_value = excluded.threshold_value,
|
|
142
|
+
threshold_type = excluded.threshold_type,
|
|
143
|
+
description = excluded.description,
|
|
144
|
+
updated_at = excluded.updated_at
|
|
145
|
+
`;
|
|
146
|
+
DatabaseUtils.run(this.db, sql, [
|
|
147
|
+
namespace,
|
|
148
|
+
key,
|
|
149
|
+
context,
|
|
150
|
+
threshold_value,
|
|
151
|
+
threshold_type,
|
|
152
|
+
description || null,
|
|
153
|
+
now
|
|
154
|
+
]);
|
|
155
|
+
logger.info(`품질 임계값 설정: ${namespace}.${key} (${context}) = ${threshold_value} (${threshold_type})`);
|
|
156
|
+
// 설정된 임계값 반환
|
|
157
|
+
const result = this.getThreshold(namespace, key, context);
|
|
158
|
+
if (!result) {
|
|
159
|
+
throw new Error('임계값 설정 후 조회 실패');
|
|
160
|
+
}
|
|
161
|
+
return result;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* 임계값 삭제
|
|
165
|
+
*
|
|
166
|
+
* @param namespace - 지표 네임스페이스
|
|
167
|
+
* @param key - 지표 키
|
|
168
|
+
* @param context - 컨텍스트 (기본값: 'default')
|
|
169
|
+
* @returns 삭제 성공 여부
|
|
170
|
+
*/
|
|
171
|
+
deleteThreshold(namespace, key, context = 'default') {
|
|
172
|
+
const sql = `
|
|
173
|
+
DELETE FROM quality_thresholds
|
|
174
|
+
WHERE metric_namespace = ? AND metric_key = ? AND context = ?
|
|
175
|
+
`;
|
|
176
|
+
const result = DatabaseUtils.run(this.db, sql, [namespace, key, context]);
|
|
177
|
+
if (result.changes > 0) {
|
|
178
|
+
logger.info(`품질 임계값 삭제: ${namespace}.${key} (${context})`);
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* 기본 임계값 초기화
|
|
185
|
+
*
|
|
186
|
+
* PRD 4.1: 기본 품질 임계값 초기화 로직
|
|
187
|
+
* 기존 임계값이 있으면 건너뛰고, 없으면 기본값으로 설정
|
|
188
|
+
*
|
|
189
|
+
* @param context - 컨텍스트 (기본값: 'default')
|
|
190
|
+
* @param overwrite - 기존 임계값 덮어쓰기 여부 (기본값: false)
|
|
191
|
+
* @returns 초기화된 임계값 개수
|
|
192
|
+
*/
|
|
193
|
+
initializeDefaultThresholds(context = 'default', overwrite = false) {
|
|
194
|
+
let initializedCount = 0;
|
|
195
|
+
for (const threshold of DEFAULT_THRESHOLDS) {
|
|
196
|
+
const existing = this.getThreshold(threshold.namespace, threshold.key, context);
|
|
197
|
+
if (existing && !overwrite) {
|
|
198
|
+
// 기존 임계값이 있고 덮어쓰지 않는 경우 건너뛰기
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
this.setThreshold(threshold.namespace, threshold.key, {
|
|
202
|
+
threshold_value: threshold.value,
|
|
203
|
+
threshold_type: threshold.type,
|
|
204
|
+
description: threshold.description
|
|
205
|
+
}, context);
|
|
206
|
+
initializedCount++;
|
|
207
|
+
}
|
|
208
|
+
logger.info(`기본 품질 임계값 초기화 완료: ${initializedCount}개 (context: ${context})`);
|
|
209
|
+
return initializedCount;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* 임계값 검증
|
|
213
|
+
*
|
|
214
|
+
* 측정값이 임계값을 만족하는지 검증
|
|
215
|
+
*
|
|
216
|
+
* @param namespace - 지표 네임스페이스
|
|
217
|
+
* @param key - 지표 키
|
|
218
|
+
* @param value - 측정값
|
|
219
|
+
* @param context - 컨텍스트 (기본값: 'default')
|
|
220
|
+
* @returns 검증 결과: { passed: boolean, threshold: QualityThreshold | null, message: string }
|
|
221
|
+
*/
|
|
222
|
+
validateThreshold(namespace, key, value, context = 'default') {
|
|
223
|
+
const threshold = this.getThreshold(namespace, key, context);
|
|
224
|
+
if (!threshold) {
|
|
225
|
+
return {
|
|
226
|
+
passed: true, // 임계값이 없으면 통과로 간주
|
|
227
|
+
threshold: null,
|
|
228
|
+
message: `임계값이 설정되지 않음: ${namespace}.${key} (${context})`
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
let passed = false;
|
|
232
|
+
let message = '';
|
|
233
|
+
if (threshold.threshold_type === 'min') {
|
|
234
|
+
passed = value >= threshold.threshold_value;
|
|
235
|
+
message = passed
|
|
236
|
+
? `통과: ${value} >= ${threshold.threshold_value} (최소값)`
|
|
237
|
+
: `실패: ${value} < ${threshold.threshold_value} (최소값)`;
|
|
238
|
+
}
|
|
239
|
+
else if (threshold.threshold_type === 'max') {
|
|
240
|
+
passed = value <= threshold.threshold_value;
|
|
241
|
+
message = passed
|
|
242
|
+
? `통과: ${value} <= ${threshold.threshold_value} (최대값)`
|
|
243
|
+
: `실패: ${value} > ${threshold.threshold_value} (최대값)`;
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
passed,
|
|
247
|
+
threshold,
|
|
248
|
+
message
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
//# sourceMappingURL=quality-threshold-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quality-threshold-manager.js","sourceRoot":"","sources":["../../../src/services/quality-assurance/quality-threshold-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 검색 품질 지표 계산 공통 헬퍼
|
|
3
|
+
* Precision@K, Recall@K, NDCG@K 계산 로직 공유
|
|
4
|
+
*/
|
|
5
|
+
export interface SearchResult {
|
|
6
|
+
id: string;
|
|
7
|
+
score?: number;
|
|
8
|
+
finalScore?: number;
|
|
9
|
+
relevance?: number;
|
|
10
|
+
}
|
|
11
|
+
export interface GroundTruth {
|
|
12
|
+
queryId: string;
|
|
13
|
+
relevantIds: string[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Precision@K 계산
|
|
17
|
+
* 상위 K개 결과 중 관련 결과 비율
|
|
18
|
+
*
|
|
19
|
+
* @param results 검색 결과 (점수 순으로 정렬된 상태)
|
|
20
|
+
* @param relevantIds 관련 결과 ID 목록
|
|
21
|
+
* @param k 상위 K개
|
|
22
|
+
* @returns Precision@K (0-1)
|
|
23
|
+
*/
|
|
24
|
+
export declare function calculatePrecisionAtK(results: SearchResult[], relevantIds: string[], k: number): number;
|
|
25
|
+
/**
|
|
26
|
+
* Recall@K 계산
|
|
27
|
+
* 관련 결과 중 상위 K개에 포함된 비율
|
|
28
|
+
*
|
|
29
|
+
* @param results 검색 결과 (점수 순으로 정렬된 상태)
|
|
30
|
+
* @param relevantIds 관련 결과 ID 목록
|
|
31
|
+
* @param k 상위 K개
|
|
32
|
+
* @returns Recall@K (0-1)
|
|
33
|
+
*/
|
|
34
|
+
export declare function calculateRecallAtK(results: SearchResult[], relevantIds: string[], k: number): number;
|
|
35
|
+
/**
|
|
36
|
+
* NDCG@K (Normalized Discounted Cumulative Gain) 계산
|
|
37
|
+
* 정규화된 할인 누적 이득
|
|
38
|
+
*
|
|
39
|
+
* @param results 검색 결과 (점수 순으로 정렬된 상태)
|
|
40
|
+
* @param relevantIds 관련 결과 ID 목록
|
|
41
|
+
* @param k 상위 K개
|
|
42
|
+
* @returns NDCG@K (0-1)
|
|
43
|
+
*/
|
|
44
|
+
export declare function calculateNDCGAtK(results: SearchResult[], relevantIds: string[], k: number): number;
|
|
45
|
+
/**
|
|
46
|
+
* 평균 Precision@K 계산 (여러 쿼리에 대해)
|
|
47
|
+
*
|
|
48
|
+
* @param queryResults 쿼리별 검색 결과
|
|
49
|
+
* @param groundTruths 쿼리별 Ground Truth
|
|
50
|
+
* @param k 상위 K개
|
|
51
|
+
* @returns 평균 Precision@K
|
|
52
|
+
*/
|
|
53
|
+
export declare function calculateMeanPrecisionAtK(queryResults: Map<string, SearchResult[]>, groundTruths: GroundTruth[], k: number): number;
|
|
54
|
+
/**
|
|
55
|
+
* 평균 Recall@K 계산 (여러 쿼리에 대해)
|
|
56
|
+
*
|
|
57
|
+
* @param queryResults 쿼리별 검색 결과
|
|
58
|
+
* @param groundTruths 쿼리별 Ground Truth
|
|
59
|
+
* @param k 상위 K개
|
|
60
|
+
* @returns 평균 Recall@K
|
|
61
|
+
*/
|
|
62
|
+
export declare function calculateMeanRecallAtK(queryResults: Map<string, SearchResult[]>, groundTruths: GroundTruth[], k: number): number;
|
|
63
|
+
/**
|
|
64
|
+
* 평균 NDCG@K 계산 (여러 쿼리에 대해)
|
|
65
|
+
*
|
|
66
|
+
* @param queryResults 쿼리별 검색 결과
|
|
67
|
+
* @param groundTruths 쿼리별 Ground Truth
|
|
68
|
+
* @param k 상위 K개
|
|
69
|
+
* @returns 평균 NDCG@K
|
|
70
|
+
*/
|
|
71
|
+
export declare function calculateMeanNDCGAtK(queryResults: Map<string, SearchResult[]>, groundTruths: GroundTruth[], k: number): number;
|
|
72
|
+
/**
|
|
73
|
+
* 랭킹 정확도 계산
|
|
74
|
+
* 기대 순서와 실제 순서의 일치도
|
|
75
|
+
*
|
|
76
|
+
* @param results 검색 결과 (점수 순으로 정렬된 상태)
|
|
77
|
+
* @param expectedOrder 기대 순서 (ID 배열)
|
|
78
|
+
* @returns 랭킹 정확도 (0-1)
|
|
79
|
+
*/
|
|
80
|
+
export declare function calculateRankingAccuracy(results: SearchResult[], expectedOrder: string[]): number;
|
|
81
|
+
/**
|
|
82
|
+
* 품질 지표 리포트 생성
|
|
83
|
+
*
|
|
84
|
+
* @param results 검색 결과
|
|
85
|
+
* @param groundTruths Ground Truth 목록
|
|
86
|
+
* @param kValues K 값 배열 (예: [1, 5, 10])
|
|
87
|
+
* @returns 품질 지표 리포트
|
|
88
|
+
*/
|
|
89
|
+
export interface QualityMetricsReport {
|
|
90
|
+
precision: Record<number, number>;
|
|
91
|
+
recall: Record<number, number>;
|
|
92
|
+
ndcg: Record<number, number>;
|
|
93
|
+
rankingAccuracy?: number;
|
|
94
|
+
}
|
|
95
|
+
export declare function generateQualityReport(queryResults: Map<string, SearchResult[]>, groundTruths: GroundTruth[], kValues?: number[]): QualityMetricsReport;
|
|
96
|
+
//# sourceMappingURL=search-quality-metrics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-quality-metrics.d.ts","sourceRoot":"","sources":["../../../src/test/helpers/search-quality-metrics.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,YAAY,EAAE,EACvB,WAAW,EAAE,MAAM,EAAE,EACrB,CAAC,EAAE,MAAM,GACR,MAAM,CAQR;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,YAAY,EAAE,EACvB,WAAW,EAAE,MAAM,EAAE,EACrB,CAAC,EAAE,MAAM,GACR,MAAM,CAQR;AAkDD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,YAAY,EAAE,EACvB,WAAW,EAAE,MAAM,EAAE,EACrB,CAAC,EAAE,MAAM,GACR,MAAM,CASR;AAED;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CACvC,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,CAAC,EACzC,YAAY,EAAE,WAAW,EAAE,EAC3B,CAAC,EAAE,MAAM,GACR,MAAM,CAYR;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,CAAC,EACzC,YAAY,EAAE,WAAW,EAAE,EAC3B,CAAC,EAAE,MAAM,GACR,MAAM,CAYR;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,CAAC,EACzC,YAAY,EAAE,WAAW,EAAE,EAC3B,CAAC,EAAE,MAAM,GACR,MAAM,CAYR;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,YAAY,EAAE,EACvB,aAAa,EAAE,MAAM,EAAE,GACtB,MAAM,CAcR;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAgB,qBAAqB,CACnC,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,CAAC,EACzC,YAAY,EAAE,WAAW,EAAE,EAC3B,OAAO,GAAE,MAAM,EAAe,GAC7B,oBAAoB,CActB"}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 검색 품질 지표 계산 공통 헬퍼
|
|
3
|
+
* Precision@K, Recall@K, NDCG@K 계산 로직 공유
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Precision@K 계산
|
|
7
|
+
* 상위 K개 결과 중 관련 결과 비율
|
|
8
|
+
*
|
|
9
|
+
* @param results 검색 결과 (점수 순으로 정렬된 상태)
|
|
10
|
+
* @param relevantIds 관련 결과 ID 목록
|
|
11
|
+
* @param k 상위 K개
|
|
12
|
+
* @returns Precision@K (0-1)
|
|
13
|
+
*/
|
|
14
|
+
export function calculatePrecisionAtK(results, relevantIds, k) {
|
|
15
|
+
const topK = results.slice(0, k);
|
|
16
|
+
if (topK.length === 0)
|
|
17
|
+
return 0;
|
|
18
|
+
const relevantSet = new Set(relevantIds);
|
|
19
|
+
const relevantCount = topK.filter(r => relevantSet.has(r.id)).length;
|
|
20
|
+
return relevantCount / topK.length;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Recall@K 계산
|
|
24
|
+
* 관련 결과 중 상위 K개에 포함된 비율
|
|
25
|
+
*
|
|
26
|
+
* @param results 검색 결과 (점수 순으로 정렬된 상태)
|
|
27
|
+
* @param relevantIds 관련 결과 ID 목록
|
|
28
|
+
* @param k 상위 K개
|
|
29
|
+
* @returns Recall@K (0-1)
|
|
30
|
+
*/
|
|
31
|
+
export function calculateRecallAtK(results, relevantIds, k) {
|
|
32
|
+
if (relevantIds.length === 0)
|
|
33
|
+
return 0;
|
|
34
|
+
const topK = results.slice(0, k);
|
|
35
|
+
const relevantSet = new Set(relevantIds);
|
|
36
|
+
const relevantInTopK = topK.filter(r => relevantSet.has(r.id)).length;
|
|
37
|
+
return relevantInTopK / relevantIds.length;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* DCG (Discounted Cumulative Gain) 계산
|
|
41
|
+
*
|
|
42
|
+
* @param results 검색 결과
|
|
43
|
+
* @param relevantIds 관련 결과 ID 목록
|
|
44
|
+
* @param k 상위 K개
|
|
45
|
+
* @returns DCG@K
|
|
46
|
+
*/
|
|
47
|
+
function calculateDCG(results, relevantIds, k) {
|
|
48
|
+
const topK = results.slice(0, k);
|
|
49
|
+
const relevantSet = new Set(relevantIds);
|
|
50
|
+
let dcg = 0;
|
|
51
|
+
topK.forEach((result, index) => {
|
|
52
|
+
const position = index + 1;
|
|
53
|
+
const relevance = relevantSet.has(result.id) ? 1 : 0;
|
|
54
|
+
// DCG 공식: relevance / log2(position + 1)
|
|
55
|
+
dcg += relevance / Math.log2(position + 1);
|
|
56
|
+
});
|
|
57
|
+
return dcg;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* IDCG (Ideal DCG) 계산
|
|
61
|
+
* 이상적인 순서로 정렬된 경우의 DCG
|
|
62
|
+
*
|
|
63
|
+
* @param relevantIds 관련 결과 ID 목록
|
|
64
|
+
* @param k 상위 K개
|
|
65
|
+
* @returns IDCG@K
|
|
66
|
+
*/
|
|
67
|
+
function calculateIDCG(relevantIds, k) {
|
|
68
|
+
const idealRelevance = Array(Math.min(relevantIds.length, k)).fill(1);
|
|
69
|
+
let idcg = 0;
|
|
70
|
+
idealRelevance.forEach((relevance, index) => {
|
|
71
|
+
const position = index + 1;
|
|
72
|
+
idcg += relevance / Math.log2(position + 1);
|
|
73
|
+
});
|
|
74
|
+
return idcg;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* NDCG@K (Normalized Discounted Cumulative Gain) 계산
|
|
78
|
+
* 정규화된 할인 누적 이득
|
|
79
|
+
*
|
|
80
|
+
* @param results 검색 결과 (점수 순으로 정렬된 상태)
|
|
81
|
+
* @param relevantIds 관련 결과 ID 목록
|
|
82
|
+
* @param k 상위 K개
|
|
83
|
+
* @returns NDCG@K (0-1)
|
|
84
|
+
*/
|
|
85
|
+
export function calculateNDCGAtK(results, relevantIds, k) {
|
|
86
|
+
if (relevantIds.length === 0)
|
|
87
|
+
return 0;
|
|
88
|
+
const dcg = calculateDCG(results, relevantIds, k);
|
|
89
|
+
const idcg = calculateIDCG(relevantIds, k);
|
|
90
|
+
if (idcg === 0)
|
|
91
|
+
return 0;
|
|
92
|
+
return dcg / idcg;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* 평균 Precision@K 계산 (여러 쿼리에 대해)
|
|
96
|
+
*
|
|
97
|
+
* @param queryResults 쿼리별 검색 결과
|
|
98
|
+
* @param groundTruths 쿼리별 Ground Truth
|
|
99
|
+
* @param k 상위 K개
|
|
100
|
+
* @returns 평균 Precision@K
|
|
101
|
+
*/
|
|
102
|
+
export function calculateMeanPrecisionAtK(queryResults, groundTruths, k) {
|
|
103
|
+
if (groundTruths.length === 0)
|
|
104
|
+
return 0;
|
|
105
|
+
let totalPrecision = 0;
|
|
106
|
+
groundTruths.forEach(truth => {
|
|
107
|
+
const results = queryResults.get(truth.queryId) || [];
|
|
108
|
+
const precision = calculatePrecisionAtK(results, truth.relevantIds, k);
|
|
109
|
+
totalPrecision += precision;
|
|
110
|
+
});
|
|
111
|
+
return totalPrecision / groundTruths.length;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* 평균 Recall@K 계산 (여러 쿼리에 대해)
|
|
115
|
+
*
|
|
116
|
+
* @param queryResults 쿼리별 검색 결과
|
|
117
|
+
* @param groundTruths 쿼리별 Ground Truth
|
|
118
|
+
* @param k 상위 K개
|
|
119
|
+
* @returns 평균 Recall@K
|
|
120
|
+
*/
|
|
121
|
+
export function calculateMeanRecallAtK(queryResults, groundTruths, k) {
|
|
122
|
+
if (groundTruths.length === 0)
|
|
123
|
+
return 0;
|
|
124
|
+
let totalRecall = 0;
|
|
125
|
+
groundTruths.forEach(truth => {
|
|
126
|
+
const results = queryResults.get(truth.queryId) || [];
|
|
127
|
+
const recall = calculateRecallAtK(results, truth.relevantIds, k);
|
|
128
|
+
totalRecall += recall;
|
|
129
|
+
});
|
|
130
|
+
return totalRecall / groundTruths.length;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* 평균 NDCG@K 계산 (여러 쿼리에 대해)
|
|
134
|
+
*
|
|
135
|
+
* @param queryResults 쿼리별 검색 결과
|
|
136
|
+
* @param groundTruths 쿼리별 Ground Truth
|
|
137
|
+
* @param k 상위 K개
|
|
138
|
+
* @returns 평균 NDCG@K
|
|
139
|
+
*/
|
|
140
|
+
export function calculateMeanNDCGAtK(queryResults, groundTruths, k) {
|
|
141
|
+
if (groundTruths.length === 0)
|
|
142
|
+
return 0;
|
|
143
|
+
let totalNDCG = 0;
|
|
144
|
+
groundTruths.forEach(truth => {
|
|
145
|
+
const results = queryResults.get(truth.queryId) || [];
|
|
146
|
+
const ndcg = calculateNDCGAtK(results, truth.relevantIds, k);
|
|
147
|
+
totalNDCG += ndcg;
|
|
148
|
+
});
|
|
149
|
+
return totalNDCG / groundTruths.length;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* 랭킹 정확도 계산
|
|
153
|
+
* 기대 순서와 실제 순서의 일치도
|
|
154
|
+
*
|
|
155
|
+
* @param results 검색 결과 (점수 순으로 정렬된 상태)
|
|
156
|
+
* @param expectedOrder 기대 순서 (ID 배열)
|
|
157
|
+
* @returns 랭킹 정확도 (0-1)
|
|
158
|
+
*/
|
|
159
|
+
export function calculateRankingAccuracy(results, expectedOrder) {
|
|
160
|
+
if (expectedOrder.length === 0)
|
|
161
|
+
return 0;
|
|
162
|
+
let correctPositions = 0;
|
|
163
|
+
const minLength = Math.min(results.length, expectedOrder.length);
|
|
164
|
+
for (let i = 0; i < minLength; i++) {
|
|
165
|
+
const result = results[i];
|
|
166
|
+
if (result && result.id === expectedOrder[i]) {
|
|
167
|
+
correctPositions++;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return correctPositions / expectedOrder.length;
|
|
171
|
+
}
|
|
172
|
+
export function generateQualityReport(queryResults, groundTruths, kValues = [1, 5, 10]) {
|
|
173
|
+
const report = {
|
|
174
|
+
precision: {},
|
|
175
|
+
recall: {},
|
|
176
|
+
ndcg: {}
|
|
177
|
+
};
|
|
178
|
+
kValues.forEach(k => {
|
|
179
|
+
report.precision[k] = calculateMeanPrecisionAtK(queryResults, groundTruths, k);
|
|
180
|
+
report.recall[k] = calculateMeanRecallAtK(queryResults, groundTruths, k);
|
|
181
|
+
report.ndcg[k] = calculateMeanNDCGAtK(queryResults, groundTruths, k);
|
|
182
|
+
});
|
|
183
|
+
return report;
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=search-quality-metrics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-quality-metrics.js","sourceRoot":"","sources":["../../../src/test/helpers/search-quality-metrics.ts"],"names":[],"mappings":"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"}
|