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.
Files changed (56) hide show
  1. package/dist/domains/relation/tools/visualize-relations-tool.d.ts +2 -2
  2. package/dist/infrastructure/database/database/migration/migrations/009-quality-assurance-schema.d.ts +60 -0
  3. package/dist/infrastructure/database/database/migration/migrations/009-quality-assurance-schema.d.ts.map +1 -0
  4. package/dist/infrastructure/database/database/migration/migrations/009-quality-assurance-schema.js +276 -0
  5. package/dist/infrastructure/database/database/migration/migrations/009-quality-assurance-schema.js.map +1 -0
  6. package/dist/infrastructure/database/database/migration/migrations/009-quality-assurance-schema.sql +128 -0
  7. package/dist/infrastructure/scheduler/batch-scheduler.d.ts +17 -0
  8. package/dist/infrastructure/scheduler/batch-scheduler.d.ts.map +1 -1
  9. package/dist/infrastructure/scheduler/batch-scheduler.js +124 -0
  10. package/dist/infrastructure/scheduler/batch-scheduler.js.map +1 -1
  11. package/dist/infrastructure/scheduler/jobs/quality-measurement-batch-job.d.ts +108 -0
  12. package/dist/infrastructure/scheduler/jobs/quality-measurement-batch-job.d.ts.map +1 -0
  13. package/dist/infrastructure/scheduler/jobs/quality-measurement-batch-job.js +184 -0
  14. package/dist/infrastructure/scheduler/jobs/quality-measurement-batch-job.js.map +1 -0
  15. package/dist/server/http-server.d.ts.map +1 -1
  16. package/dist/server/http-server.js +3 -0
  17. package/dist/server/http-server.js.map +1 -1
  18. package/dist/server/routes/quality.routes.d.ts +14 -0
  19. package/dist/server/routes/quality.routes.d.ts.map +1 -0
  20. package/dist/server/routes/quality.routes.js +460 -0
  21. package/dist/server/routes/quality.routes.js.map +1 -0
  22. package/dist/services/quality-assurance/quality-assurance-service.d.ts +207 -0
  23. package/dist/services/quality-assurance/quality-assurance-service.d.ts.map +1 -0
  24. package/dist/services/quality-assurance/quality-assurance-service.js +247 -0
  25. package/dist/services/quality-assurance/quality-assurance-service.js.map +1 -0
  26. package/dist/services/quality-assurance/quality-evaluator.d.ts +163 -0
  27. package/dist/services/quality-assurance/quality-evaluator.d.ts.map +1 -0
  28. package/dist/services/quality-assurance/quality-evaluator.js +256 -0
  29. package/dist/services/quality-assurance/quality-evaluator.js.map +1 -0
  30. package/dist/services/quality-assurance/quality-metrics-collector.d.ts +219 -0
  31. package/dist/services/quality-assurance/quality-metrics-collector.d.ts.map +1 -0
  32. package/dist/services/quality-assurance/quality-metrics-collector.js +725 -0
  33. package/dist/services/quality-assurance/quality-metrics-collector.js.map +1 -0
  34. package/dist/services/quality-assurance/quality-recorder.d.ts +108 -0
  35. package/dist/services/quality-assurance/quality-recorder.d.ts.map +1 -0
  36. package/dist/services/quality-assurance/quality-recorder.js +281 -0
  37. package/dist/services/quality-assurance/quality-recorder.js.map +1 -0
  38. package/dist/services/quality-assurance/quality-reporter.d.ts +189 -0
  39. package/dist/services/quality-assurance/quality-reporter.d.ts.map +1 -0
  40. package/dist/services/quality-assurance/quality-reporter.js +558 -0
  41. package/dist/services/quality-assurance/quality-reporter.js.map +1 -0
  42. package/dist/services/quality-assurance/quality-threshold-manager.d.ts +102 -0
  43. package/dist/services/quality-assurance/quality-threshold-manager.d.ts.map +1 -0
  44. package/dist/services/quality-assurance/quality-threshold-manager.js +252 -0
  45. package/dist/services/quality-assurance/quality-threshold-manager.js.map +1 -0
  46. package/dist/test/helpers/search-quality-metrics.d.ts +96 -0
  47. package/dist/test/helpers/search-quality-metrics.d.ts.map +1 -0
  48. package/dist/test/helpers/search-quality-metrics.js +185 -0
  49. package/dist/test/helpers/search-quality-metrics.js.map +1 -0
  50. package/dist/test/helpers/vector-search-quality-metrics.d.ts +1287 -0
  51. package/dist/test/helpers/vector-search-quality-metrics.d.ts.map +1 -0
  52. package/dist/test/helpers/vector-search-quality-metrics.js +2214 -0
  53. package/dist/test/helpers/vector-search-quality-metrics.js.map +1 -0
  54. package/package.json +4 -1
  55. package/scripts/quality-report.ts +166 -0
  56. package/scripts/quality-thresholds.ts +279 -0
@@ -0,0 +1,558 @@
1
+ /**
2
+ * Quality Reporter
3
+ *
4
+ * 품질 리포트 생성 서비스
5
+ *
6
+ * 주요 기능:
7
+ * - Markdown, JSON, HTML 형식 리포트 생성
8
+ * - 네임스페이스, 컨텍스트, 시간 범위 필터링
9
+ * - 품질 지표 요약, 경고 정보, 이력 추이 포함
10
+ *
11
+ * PRD FR-1.1: Reporter 역할 - 회상용 리포트 생성
12
+ */
13
+ import Database from 'better-sqlite3';
14
+ import { promises as fsPromises } from 'fs';
15
+ import { existsSync, mkdirSync } from 'fs';
16
+ import path from 'path';
17
+ import { QualityRecorder } from './quality-recorder.js';
18
+ import { QualityThresholdManager } from './quality-threshold-manager.js';
19
+ import { DatabaseUtils } from '../../shared/utils/database.js';
20
+ import { logger } from '../../shared/utils/logger.js';
21
+ /**
22
+ * Quality Reporter
23
+ *
24
+ * PRD FR-1.1: Reporter 역할 - 회상용 리포트 생성
25
+ */
26
+ export class QualityReporter {
27
+ db;
28
+ recorder;
29
+ thresholdManager;
30
+ constructor(db) {
31
+ this.db = db;
32
+ if (!db) {
33
+ throw new Error('Database instance is required');
34
+ }
35
+ this.recorder = new QualityRecorder(db);
36
+ this.thresholdManager = new QualityThresholdManager(db);
37
+ }
38
+ /**
39
+ * 리포트 데이터 수집
40
+ *
41
+ * @param options - 리포트 옵션
42
+ * @returns 리포트 데이터
43
+ */
44
+ async collectReportData(options = {}) {
45
+ const { namespace, context = 'default', from, to, historyLimit = 100 } = options;
46
+ const now = new Date().toISOString();
47
+ // 1. 최신 품질 지표 조회
48
+ const latestMetrics = this.recorder.getLatestMetrics(namespace, context);
49
+ // 2. 경고 정보 수집 (status가 'fail'인 지표)
50
+ const warnings = [];
51
+ for (const metric of latestMetrics) {
52
+ if (metric.status === 'fail' && metric.threshold_value !== null) {
53
+ const threshold = this.thresholdManager.getThreshold(metric.metric_namespace, metric.metric_key, metric.context);
54
+ if (threshold) {
55
+ const difference = threshold.threshold_type === 'min'
56
+ ? metric.metric_value - threshold.threshold_value
57
+ : threshold.threshold_value - metric.metric_value;
58
+ warnings.push({
59
+ metric_namespace: metric.metric_namespace,
60
+ metric_key: metric.metric_key,
61
+ context: metric.context,
62
+ measured_value: metric.metric_value,
63
+ threshold_value: threshold.threshold_value,
64
+ threshold_type: threshold.threshold_type,
65
+ difference,
66
+ message: `${metric.metric_namespace}.${metric.metric_key}: ${metric.metric_value} ${threshold.threshold_type === 'min' ? '<' : '>'} ${threshold.threshold_value}`
67
+ });
68
+ }
69
+ }
70
+ }
71
+ // 3. 네임스페이스별 상태 집계
72
+ const namespaceStatusMap = new Map();
73
+ for (const metric of latestMetrics) {
74
+ const ns = metric.metric_namespace;
75
+ if (!namespaceStatusMap.has(ns)) {
76
+ namespaceStatusMap.set(ns, {
77
+ namespace: ns,
78
+ status: 'pass',
79
+ metrics_count: 0,
80
+ passed_count: 0,
81
+ failed_count: 0
82
+ });
83
+ }
84
+ const status = namespaceStatusMap.get(ns);
85
+ status.metrics_count++;
86
+ if (metric.status === 'pass') {
87
+ status.passed_count++;
88
+ }
89
+ else if (metric.status === 'fail') {
90
+ status.failed_count++;
91
+ status.status = 'fail';
92
+ }
93
+ else if (metric.status === 'warning' && status.status === 'pass') {
94
+ status.status = 'warning';
95
+ }
96
+ }
97
+ const namespaceStatus = Array.from(namespaceStatusMap.values());
98
+ // 4. 전체 상태 결정
99
+ let overallStatus = 'pass';
100
+ if (namespaceStatus.some(ns => ns.status === 'fail')) {
101
+ overallStatus = 'fail';
102
+ }
103
+ else if (namespaceStatus.some(ns => ns.status === 'warning')) {
104
+ overallStatus = 'warning';
105
+ }
106
+ // 5. 전체 통계 계산
107
+ const totalMetrics = latestMetrics.length;
108
+ const passedMetrics = latestMetrics.filter(m => m.status === 'pass').length;
109
+ const failedMetrics = latestMetrics.filter(m => m.status === 'fail').length;
110
+ const warningMetrics = latestMetrics.filter(m => m.status === 'warning').length;
111
+ // 6. 측정 이력 조회
112
+ const history = this.recorder.getMeasurementHistory(namespace, context, from, to, historyLimit);
113
+ // 7. 이력에 namespace와 context 정보 추가 (JSON 파싱)
114
+ const enrichedHistory = history.map(h => {
115
+ const fullRecord = DatabaseUtils.get(this.db, 'SELECT metrics FROM quality_measurement_history WHERE id = ?', [h.id]);
116
+ if (fullRecord && fullRecord.metrics) {
117
+ try {
118
+ const metricsJson = JSON.parse(fullRecord.metrics);
119
+ return {
120
+ ...h,
121
+ namespace: metricsJson.namespace,
122
+ context: metricsJson.context
123
+ };
124
+ }
125
+ catch (error) {
126
+ const errorObj = error instanceof Error
127
+ ? { message: error.message, stack: error.stack }
128
+ : typeof error === 'object' && error !== null
129
+ ? error
130
+ : { error: String(error) };
131
+ logger.warn(`Failed to parse metrics JSON for history ${h.id}:`, errorObj);
132
+ }
133
+ }
134
+ return h;
135
+ });
136
+ // 8. 최신 지표에 threshold_type 추가
137
+ const enrichedLatestMetrics = latestMetrics.map(metric => {
138
+ const threshold = this.thresholdManager.getThreshold(metric.metric_namespace, metric.metric_key, metric.context);
139
+ return {
140
+ ...metric,
141
+ status: metric.status,
142
+ threshold_type: threshold?.threshold_type || null
143
+ };
144
+ });
145
+ return {
146
+ generated_at: now,
147
+ options,
148
+ summary: {
149
+ overall_status: overallStatus,
150
+ namespace_status: namespaceStatus,
151
+ total_metrics: totalMetrics,
152
+ passed_metrics: passedMetrics,
153
+ failed_metrics: failedMetrics,
154
+ warning_metrics: warningMetrics
155
+ },
156
+ latest_metrics: enrichedLatestMetrics,
157
+ warnings,
158
+ history: enrichedHistory
159
+ };
160
+ }
161
+ /**
162
+ * Markdown 형식 리포트 생성
163
+ *
164
+ * @param reportData - 리포트 데이터
165
+ * @returns Markdown 형식 리포트 문자열
166
+ */
167
+ generateMarkdownReport(reportData) {
168
+ const { summary, latest_metrics, warnings, history } = reportData;
169
+ const statusEmoji = {
170
+ pass: '✅',
171
+ warning: '⚠️',
172
+ fail: '❌'
173
+ };
174
+ let markdown = `# Quality Assurance Report\n\n`;
175
+ markdown += `**생성 일시**: ${reportData.generated_at}\n`;
176
+ markdown += `**전체 상태**: ${statusEmoji[summary.overall_status]} ${summary.overall_status.toUpperCase()}\n\n`;
177
+ markdown += `---\n\n`;
178
+ // 요약
179
+ markdown += `## 요약\n\n`;
180
+ markdown += `| 항목 | 값 |\n`;
181
+ markdown += `|------|-----|\n`;
182
+ markdown += `| **전체 상태** | ${statusEmoji[summary.overall_status]} ${summary.overall_status.toUpperCase()} |\n`;
183
+ markdown += `| **총 지표 수** | ${summary.total_metrics} |\n`;
184
+ markdown += `| **통과 지표** | ${summary.passed_metrics} |\n`;
185
+ markdown += `| **실패 지표** | ${summary.failed_metrics} |\n`;
186
+ markdown += `| **경고 지표** | ${summary.warning_metrics} |\n\n`;
187
+ // 네임스페이스별 상태
188
+ if (summary.namespace_status.length > 0) {
189
+ markdown += `## 네임스페이스별 상태\n\n`;
190
+ markdown += `| 네임스페이스 | 상태 | 지표 수 | 통과 | 실패 |\n`;
191
+ markdown += `|-------------|------|---------|------|------|\n`;
192
+ for (const ns of summary.namespace_status) {
193
+ markdown += `| **${ns.namespace}** | ${statusEmoji[ns.status]} ${ns.status.toUpperCase()} | ${ns.metrics_count} | ${ns.passed_count} | ${ns.failed_count} |\n`;
194
+ }
195
+ markdown += `\n`;
196
+ }
197
+ // 경고 정보
198
+ if (warnings.length > 0) {
199
+ markdown += `## ⚠️ 경고\n\n`;
200
+ markdown += `| 네임스페이스 | 지표 | 측정값 | 임계값 | 차이 |\n`;
201
+ markdown += `|-------------|------|--------|--------|------|\n`;
202
+ for (const warning of warnings) {
203
+ markdown += `| ${warning.metric_namespace} | ${warning.metric_key} | ${warning.measured_value.toFixed(3)} | ${warning.threshold_value.toFixed(3)} (${warning.threshold_type}) | ${warning.difference > 0 ? '+' : ''}${warning.difference.toFixed(3)} |\n`;
204
+ }
205
+ markdown += `\n`;
206
+ }
207
+ // 최신 지표
208
+ if (latest_metrics.length > 0) {
209
+ markdown += `## 최신 품질 지표\n\n`;
210
+ markdown += `| 네임스페이스 | 지표 | 값 | 상태 | 임계값 | 측정 시간 |\n`;
211
+ markdown += `|-------------|------|-----|------|--------|----------|\n`;
212
+ for (const metric of latest_metrics) {
213
+ const statusIcon = statusEmoji[metric.status] || '❓';
214
+ const thresholdStr = metric.threshold_value !== null
215
+ ? `${metric.threshold_value.toFixed(3)} (${metric.threshold_type})`
216
+ : 'N/A';
217
+ markdown += `| ${metric.metric_namespace} | ${metric.metric_key} | ${metric.metric_value.toFixed(3)} | ${statusIcon} ${metric.status.toUpperCase()} | ${thresholdStr} | ${metric.measured_at} |\n`;
218
+ }
219
+ markdown += `\n`;
220
+ }
221
+ // 측정 이력
222
+ if (history.length > 0) {
223
+ markdown += `## 측정 이력 (최근 ${history.length}개)\n\n`;
224
+ markdown += `| ID | 타입 | 네임스페이스 | 컨텍스트 | 상태 | 측정 시간 |\n`;
225
+ markdown += `|----|------|-------------|----------|------|----------|\n`;
226
+ for (const h of history.slice(0, 20)) { // 최대 20개만 표시
227
+ const statusIcon = h.status === 'success' ? '✅' : h.status === 'warning' ? '⚠️' : '❌';
228
+ markdown += `| ${h.id.substring(0, 20)}... | ${h.measurement_type} | ${h.namespace || 'N/A'} | ${h.context || 'N/A'} | ${statusIcon} ${h.status} | ${h.measured_at} |\n`;
229
+ }
230
+ markdown += `\n`;
231
+ }
232
+ return markdown;
233
+ }
234
+ /**
235
+ * JSON 형식 리포트 생성
236
+ *
237
+ * @param reportData - 리포트 데이터
238
+ * @returns JSON 형식 리포트 문자열
239
+ */
240
+ generateJsonReport(reportData) {
241
+ return JSON.stringify(reportData, null, 2);
242
+ }
243
+ /**
244
+ * HTML 형식 리포트 생성
245
+ *
246
+ * @param reportData - 리포트 데이터
247
+ * @returns HTML 형식 리포트 문자열
248
+ */
249
+ generateHtmlReport(reportData) {
250
+ const { summary, latest_metrics, warnings, history } = reportData;
251
+ const statusClass = {
252
+ pass: 'status-pass',
253
+ warning: 'status-warning',
254
+ fail: 'status-fail'
255
+ };
256
+ const statusText = {
257
+ pass: 'PASS',
258
+ warning: 'WARNING',
259
+ fail: 'FAIL'
260
+ };
261
+ let html = `<!DOCTYPE html>
262
+ <html lang="ko">
263
+ <head>
264
+ <meta charset="UTF-8">
265
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
266
+ <title>Quality Assurance Report</title>
267
+ <style>
268
+ body {
269
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
270
+ line-height: 1.6;
271
+ color: #333;
272
+ max-width: 1200px;
273
+ margin: 0 auto;
274
+ padding: 20px;
275
+ background-color: #f5f5f5;
276
+ }
277
+ .container {
278
+ background: white;
279
+ padding: 30px;
280
+ border-radius: 8px;
281
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
282
+ }
283
+ h1 {
284
+ color: #2c3e50;
285
+ border-bottom: 3px solid #3498db;
286
+ padding-bottom: 10px;
287
+ }
288
+ h2 {
289
+ color: #34495e;
290
+ margin-top: 30px;
291
+ border-bottom: 2px solid #ecf0f1;
292
+ padding-bottom: 5px;
293
+ }
294
+ table {
295
+ width: 100%;
296
+ border-collapse: collapse;
297
+ margin: 20px 0;
298
+ }
299
+ th, td {
300
+ padding: 12px;
301
+ text-align: left;
302
+ border-bottom: 1px solid #ddd;
303
+ }
304
+ th {
305
+ background-color: #3498db;
306
+ color: white;
307
+ font-weight: 600;
308
+ }
309
+ tr:hover {
310
+ background-color: #f5f5f5;
311
+ }
312
+ .status-pass {
313
+ color: #27ae60;
314
+ font-weight: bold;
315
+ }
316
+ .status-warning {
317
+ color: #f39c12;
318
+ font-weight: bold;
319
+ }
320
+ .status-fail {
321
+ color: #e74c3c;
322
+ font-weight: bold;
323
+ }
324
+ .summary-box {
325
+ background: #ecf0f1;
326
+ padding: 20px;
327
+ border-radius: 5px;
328
+ margin: 20px 0;
329
+ }
330
+ .summary-box h3 {
331
+ margin-top: 0;
332
+ color: #2c3e50;
333
+ }
334
+ .warning-box {
335
+ background: #fff3cd;
336
+ border-left: 4px solid #ffc107;
337
+ padding: 15px;
338
+ margin: 20px 0;
339
+ }
340
+ </style>
341
+ </head>
342
+ <body>
343
+ <div class="container">
344
+ <h1>Quality Assurance Report</h1>
345
+ <p><strong>생성 일시:</strong> ${reportData.generated_at}</p>
346
+ <p><strong>전체 상태:</strong> <span class="${statusClass[summary.overall_status]}">${statusText[summary.overall_status]}</span></p>
347
+
348
+ <div class="summary-box">
349
+ <h3>요약</h3>
350
+ <table>
351
+ <tr>
352
+ <th>항목</th>
353
+ <th>값</th>
354
+ </tr>
355
+ <tr>
356
+ <td><strong>전체 상태</strong></td>
357
+ <td><span class="${statusClass[summary.overall_status]}">${statusText[summary.overall_status]}</span></td>
358
+ </tr>
359
+ <tr>
360
+ <td><strong>총 지표 수</strong></td>
361
+ <td>${summary.total_metrics}</td>
362
+ </tr>
363
+ <tr>
364
+ <td><strong>통과 지표</strong></td>
365
+ <td>${summary.passed_metrics}</td>
366
+ </tr>
367
+ <tr>
368
+ <td><strong>실패 지표</strong></td>
369
+ <td>${summary.failed_metrics}</td>
370
+ </tr>
371
+ <tr>
372
+ <td><strong>경고 지표</strong></td>
373
+ <td>${summary.warning_metrics}</td>
374
+ </tr>
375
+ </table>
376
+ </div>`;
377
+ // 네임스페이스별 상태
378
+ if (summary.namespace_status.length > 0) {
379
+ html += `
380
+ <h2>네임스페이스별 상태</h2>
381
+ <table>
382
+ <tr>
383
+ <th>네임스페이스</th>
384
+ <th>상태</th>
385
+ <th>지표 수</th>
386
+ <th>통과</th>
387
+ <th>실패</th>
388
+ </tr>`;
389
+ for (const ns of summary.namespace_status) {
390
+ html += `
391
+ <tr>
392
+ <td><strong>${ns.namespace}</strong></td>
393
+ <td><span class="${statusClass[ns.status]}">${statusText[ns.status]}</span></td>
394
+ <td>${ns.metrics_count}</td>
395
+ <td>${ns.passed_count}</td>
396
+ <td>${ns.failed_count}</td>
397
+ </tr>`;
398
+ }
399
+ html += `
400
+ </table>`;
401
+ }
402
+ // 경고 정보
403
+ if (warnings.length > 0) {
404
+ html += `
405
+ <div class="warning-box">
406
+ <h2>⚠️ 경고</h2>
407
+ <table>
408
+ <tr>
409
+ <th>네임스페이스</th>
410
+ <th>지표</th>
411
+ <th>측정값</th>
412
+ <th>임계값</th>
413
+ <th>차이</th>
414
+ </tr>`;
415
+ for (const warning of warnings) {
416
+ html += `
417
+ <tr>
418
+ <td>${warning.metric_namespace}</td>
419
+ <td>${warning.metric_key}</td>
420
+ <td>${warning.measured_value.toFixed(3)}</td>
421
+ <td>${warning.threshold_value.toFixed(3)} (${warning.threshold_type})</td>
422
+ <td>${warning.difference > 0 ? '+' : ''}${warning.difference.toFixed(3)}</td>
423
+ </tr>`;
424
+ }
425
+ html += `
426
+ </table>
427
+ </div>`;
428
+ }
429
+ // 최신 지표
430
+ if (latest_metrics.length > 0) {
431
+ html += `
432
+ <h2>최신 품질 지표</h2>
433
+ <table>
434
+ <tr>
435
+ <th>네임스페이스</th>
436
+ <th>지표</th>
437
+ <th>값</th>
438
+ <th>상태</th>
439
+ <th>임계값</th>
440
+ <th>측정 시간</th>
441
+ </tr>`;
442
+ for (const metric of latest_metrics) {
443
+ const thresholdStr = metric.threshold_value !== null
444
+ ? `${metric.threshold_value.toFixed(3)} (${metric.threshold_type})`
445
+ : 'N/A';
446
+ html += `
447
+ <tr>
448
+ <td>${metric.metric_namespace}</td>
449
+ <td>${metric.metric_key}</td>
450
+ <td>${metric.metric_value.toFixed(3)}</td>
451
+ <td><span class="${statusClass[metric.status]}">${statusText[metric.status]}</span></td>
452
+ <td>${thresholdStr}</td>
453
+ <td>${metric.measured_at}</td>
454
+ </tr>`;
455
+ }
456
+ html += `
457
+ </table>`;
458
+ }
459
+ // 측정 이력
460
+ if (history.length > 0) {
461
+ html += `
462
+ <h2>측정 이력 (최근 ${history.length}개)</h2>
463
+ <table>
464
+ <tr>
465
+ <th>ID</th>
466
+ <th>타입</th>
467
+ <th>네임스페이스</th>
468
+ <th>컨텍스트</th>
469
+ <th>상태</th>
470
+ <th>측정 시간</th>
471
+ </tr>`;
472
+ for (const h of history.slice(0, 20)) {
473
+ html += `
474
+ <tr>
475
+ <td>${h.id.substring(0, 20)}...</td>
476
+ <td>${h.measurement_type}</td>
477
+ <td>${h.namespace || 'N/A'}</td>
478
+ <td>${h.context || 'N/A'}</td>
479
+ <td><span class="${statusClass[h.status] || 'status-warning'}">${h.status.toUpperCase()}</span></td>
480
+ <td>${h.measured_at}</td>
481
+ </tr>`;
482
+ }
483
+ html += `
484
+ </table>`;
485
+ }
486
+ html += `
487
+ </div>
488
+ </body>
489
+ </html>`;
490
+ return html;
491
+ }
492
+ /**
493
+ * 리포트 생성
494
+ *
495
+ * PRD FR-5.5: 리포트 생성 시 JSON 형식 로그 파일 저장
496
+ * `logs/quality-report-{timestamp}.json` 형식으로 저장
497
+ *
498
+ * @param options - 리포트 옵션
499
+ * @returns 리포트 문자열
500
+ */
501
+ async generateReport(options = {}) {
502
+ const format = options.format || 'markdown';
503
+ const reportData = await this.collectReportData(options);
504
+ logger.info(`품질 리포트 생성: ${format} 형식`, {
505
+ format,
506
+ namespace: options.namespace,
507
+ context: options.context,
508
+ metrics_count: reportData.summary.total_metrics,
509
+ overall_status: reportData.summary.overall_status
510
+ });
511
+ // PRD FR-5.5: JSON 형식 로그 파일 저장
512
+ await this.saveReportToFile(reportData);
513
+ switch (format) {
514
+ case 'markdown':
515
+ return this.generateMarkdownReport(reportData);
516
+ case 'json':
517
+ return this.generateJsonReport(reportData);
518
+ case 'html':
519
+ return this.generateHtmlReport(reportData);
520
+ default:
521
+ throw new Error(`Unsupported report format: ${format}`);
522
+ }
523
+ }
524
+ /**
525
+ * 리포트를 JSON 형식으로 파일에 저장
526
+ *
527
+ * PRD FR-5.5: `logs/quality-report-{timestamp}.json` 형식으로 저장
528
+ *
529
+ * @param reportData - 리포트 데이터
530
+ */
531
+ async saveReportToFile(reportData) {
532
+ try {
533
+ const logDir = path.join(process.cwd(), 'logs');
534
+ // 로그 디렉토리 생성
535
+ if (!existsSync(logDir)) {
536
+ mkdirSync(logDir, { recursive: true });
537
+ }
538
+ // 타임스탬프 기반 파일명 생성 (ISO 8601 형식, 파일명에 사용 가능하도록 변환)
539
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-').replace('T', '_').split('.')[0];
540
+ const logFilePath = path.join(logDir, `quality-report-${timestamp}.json`);
541
+ // JSON 형식으로 리포트 데이터 저장
542
+ const jsonContent = JSON.stringify(reportData, null, 2);
543
+ await fsPromises.writeFile(logFilePath, jsonContent, 'utf-8');
544
+ logger.info('품질 리포트 파일 저장 완료', {
545
+ file_path: logFilePath,
546
+ metrics_count: reportData.summary.total_metrics,
547
+ overall_status: reportData.summary.overall_status
548
+ });
549
+ }
550
+ catch (error) {
551
+ // 파일 저장 실패는 콘솔 로거에 위임
552
+ logger.error('품질 리포트 파일 저장 실패', {
553
+ error: error instanceof Error ? error.message : String(error)
554
+ });
555
+ }
556
+ }
557
+ }
558
+ //# sourceMappingURL=quality-reporter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quality-reporter.js","sourceRoot":"","sources":["../../../src/services/quality-assurance/quality-reporter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Quality Threshold Manager
3
+ *
4
+ * 품질 임계값 관리 서비스
5
+ *
6
+ * 주요 기능:
7
+ * - 품질 임계값 CRUD (생성, 조회, 업데이트, 삭제)
8
+ * - 기본 임계값 초기화
9
+ * - 임계값 검증
10
+ */
11
+ import Database from 'better-sqlite3';
12
+ /**
13
+ * 품질 임계값 정보
14
+ */
15
+ export interface QualityThreshold {
16
+ metric_namespace: string;
17
+ metric_key: string;
18
+ context: string;
19
+ threshold_value: number;
20
+ threshold_type: 'min' | 'max';
21
+ description: string | null;
22
+ updated_at: string;
23
+ }
24
+ /**
25
+ * 임계값 생성/업데이트 옵션
26
+ */
27
+ export interface ThresholdOptions {
28
+ threshold_value: number;
29
+ threshold_type: 'min' | 'max';
30
+ description?: string;
31
+ }
32
+ /**
33
+ * Quality Threshold Manager
34
+ */
35
+ export declare class QualityThresholdManager {
36
+ private db;
37
+ constructor(db: Database.Database);
38
+ /**
39
+ * 임계값 조회
40
+ *
41
+ * @param namespace - 지표 네임스페이스 (예: 'search', 'relation')
42
+ * @param key - 지표 키 (예: 'precision_at_5')
43
+ * @param context - 컨텍스트 (기본값: 'default')
44
+ * @returns 임계값 정보 또는 null
45
+ */
46
+ getThreshold(namespace: string, key: string, context?: string): QualityThreshold | null;
47
+ /**
48
+ * 모든 임계값 조회
49
+ *
50
+ * @param namespace - 네임스페이스 필터 (선택적)
51
+ * @param context - 컨텍스트 필터 (선택적)
52
+ * @returns 임계값 목록
53
+ */
54
+ getAllThresholds(namespace?: string, context?: string): QualityThreshold[];
55
+ /**
56
+ * 임계값 설정 (생성 또는 업데이트)
57
+ *
58
+ * @param namespace - 지표 네임스페이스
59
+ * @param key - 지표 키
60
+ * @param options - 임계값 옵션
61
+ * @param context - 컨텍스트 (기본값: 'default')
62
+ * @returns 설정된 임계값 정보
63
+ */
64
+ setThreshold(namespace: string, key: string, options: ThresholdOptions, context?: string): QualityThreshold;
65
+ /**
66
+ * 임계값 삭제
67
+ *
68
+ * @param namespace - 지표 네임스페이스
69
+ * @param key - 지표 키
70
+ * @param context - 컨텍스트 (기본값: 'default')
71
+ * @returns 삭제 성공 여부
72
+ */
73
+ deleteThreshold(namespace: string, key: string, context?: string): boolean;
74
+ /**
75
+ * 기본 임계값 초기화
76
+ *
77
+ * PRD 4.1: 기본 품질 임계값 초기화 로직
78
+ * 기존 임계값이 있으면 건너뛰고, 없으면 기본값으로 설정
79
+ *
80
+ * @param context - 컨텍스트 (기본값: 'default')
81
+ * @param overwrite - 기존 임계값 덮어쓰기 여부 (기본값: false)
82
+ * @returns 초기화된 임계값 개수
83
+ */
84
+ initializeDefaultThresholds(context?: string, overwrite?: boolean): number;
85
+ /**
86
+ * 임계값 검증
87
+ *
88
+ * 측정값이 임계값을 만족하는지 검증
89
+ *
90
+ * @param namespace - 지표 네임스페이스
91
+ * @param key - 지표 키
92
+ * @param value - 측정값
93
+ * @param context - 컨텍스트 (기본값: 'default')
94
+ * @returns 검증 결과: { passed: boolean, threshold: QualityThreshold | null, message: string }
95
+ */
96
+ validateThreshold(namespace: string, key: string, value: number, context?: string): {
97
+ passed: boolean;
98
+ threshold: QualityThreshold | null;
99
+ message: string;
100
+ };
101
+ }
102
+ //# sourceMappingURL=quality-threshold-manager.d.ts.map