driftdetect-dashboard 0.1.1 → 0.4.2
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/client/assets/GalaxyTab-DA9097-Z.js +4177 -0
- package/dist/client/assets/GalaxyTab-DA9097-Z.js.map +1 -0
- package/dist/client/assets/main-CqioacxX.css +2 -0
- package/dist/client/assets/main-D1M_eef4.js +59 -0
- package/dist/client/assets/main-D1M_eef4.js.map +1 -0
- package/dist/client/assets/sfxr-BUUZlhMn.js +2 -0
- package/dist/client/assets/sfxr-BUUZlhMn.js.map +1 -0
- package/dist/client/index.html +2 -2
- package/dist/server/api-routes.d.ts.map +1 -1
- package/dist/server/api-routes.js +156 -0
- package/dist/server/api-routes.js.map +1 -1
- package/dist/server/drift-data-reader.d.ts +111 -0
- package/dist/server/drift-data-reader.d.ts.map +1 -1
- package/dist/server/drift-data-reader.js +400 -1
- package/dist/server/drift-data-reader.js.map +1 -1
- package/dist/server/galaxy-data-transformer.d.ts +178 -0
- package/dist/server/galaxy-data-transformer.d.ts.map +1 -0
- package/dist/server/galaxy-data-transformer.js +562 -0
- package/dist/server/galaxy-data-transformer.js.map +1 -0
- package/package.json +8 -2
- package/dist/client/assets/main-DQAs4VF9.js +0 -59
- package/dist/client/assets/main-DQAs4VF9.js.map +0 -1
- package/dist/client/assets/main-Du5_09U3.css +0 -2
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
* Reads and parses data from the .drift/ folder structure.
|
|
5
5
|
* Provides methods for accessing patterns, violations, files, and configuration.
|
|
6
6
|
*
|
|
7
|
+
* OPTIMIZED: Uses DataLake for fast reads with pre-computed views.
|
|
8
|
+
* Falls back to direct file reading when lake data is unavailable.
|
|
9
|
+
*
|
|
7
10
|
* @requirements 1.6 - THE Dashboard_Server SHALL read pattern and violation data from the existing `.drift/` folder structure
|
|
8
11
|
* @requirements 8.1 - THE Dashboard_Server SHALL expose GET `/api/patterns` to list all patterns
|
|
9
12
|
* @requirements 8.2 - THE Dashboard_Server SHALL expose GET `/api/patterns/:id` to get pattern details with locations
|
|
@@ -235,20 +238,105 @@ export interface DashboardContractStats {
|
|
|
235
238
|
totalMismatches: number;
|
|
236
239
|
mismatchesByType: Record<string, number>;
|
|
237
240
|
}
|
|
241
|
+
/**
|
|
242
|
+
* A snapshot of a single pattern's state at a point in time
|
|
243
|
+
*/
|
|
244
|
+
export interface PatternSnapshot {
|
|
245
|
+
patternId: string;
|
|
246
|
+
patternName: string;
|
|
247
|
+
category: PatternCategory;
|
|
248
|
+
confidence: number;
|
|
249
|
+
locationCount: number;
|
|
250
|
+
outlierCount: number;
|
|
251
|
+
complianceRate: number;
|
|
252
|
+
status: PatternStatus;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Category summary in a snapshot
|
|
256
|
+
*/
|
|
257
|
+
export interface CategorySummary {
|
|
258
|
+
patternCount: number;
|
|
259
|
+
avgConfidence: number;
|
|
260
|
+
totalLocations: number;
|
|
261
|
+
totalOutliers: number;
|
|
262
|
+
complianceRate: number;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* A full snapshot of all patterns at a point in time
|
|
266
|
+
*/
|
|
267
|
+
export interface HistorySnapshot {
|
|
268
|
+
timestamp: string;
|
|
269
|
+
date: string;
|
|
270
|
+
patterns: PatternSnapshot[];
|
|
271
|
+
summary: {
|
|
272
|
+
totalPatterns: number;
|
|
273
|
+
avgConfidence: number;
|
|
274
|
+
totalLocations: number;
|
|
275
|
+
totalOutliers: number;
|
|
276
|
+
overallComplianceRate: number;
|
|
277
|
+
byCategory: Record<string, CategorySummary>;
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* A detected regression or improvement
|
|
282
|
+
*/
|
|
283
|
+
export interface PatternTrend {
|
|
284
|
+
patternId: string;
|
|
285
|
+
patternName: string;
|
|
286
|
+
category: PatternCategory;
|
|
287
|
+
type: 'regression' | 'improvement' | 'stable';
|
|
288
|
+
metric: 'confidence' | 'compliance' | 'outliers';
|
|
289
|
+
previousValue: number;
|
|
290
|
+
currentValue: number;
|
|
291
|
+
change: number;
|
|
292
|
+
changePercent: number;
|
|
293
|
+
severity: 'critical' | 'warning' | 'info';
|
|
294
|
+
firstSeen: string;
|
|
295
|
+
details: string;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Aggregated trends for the dashboard
|
|
299
|
+
*/
|
|
300
|
+
export interface TrendSummary {
|
|
301
|
+
period: '7d' | '30d' | '90d';
|
|
302
|
+
startDate: string;
|
|
303
|
+
endDate: string;
|
|
304
|
+
regressions: PatternTrend[];
|
|
305
|
+
improvements: PatternTrend[];
|
|
306
|
+
stable: number;
|
|
307
|
+
overallTrend: 'improving' | 'declining' | 'stable';
|
|
308
|
+
healthDelta: number;
|
|
309
|
+
categoryTrends: Record<string, {
|
|
310
|
+
trend: 'improving' | 'declining' | 'stable';
|
|
311
|
+
avgConfidenceChange: number;
|
|
312
|
+
complianceChange: number;
|
|
313
|
+
}>;
|
|
314
|
+
}
|
|
238
315
|
export declare class DriftDataReader {
|
|
239
316
|
private readonly driftDir;
|
|
240
317
|
private readonly patternsDir;
|
|
318
|
+
private readonly dataLake;
|
|
319
|
+
private lakeInitialized;
|
|
241
320
|
constructor(driftDir: string);
|
|
242
321
|
/**
|
|
243
322
|
* Get the drift directory path
|
|
244
323
|
*/
|
|
245
324
|
get directory(): string;
|
|
325
|
+
/**
|
|
326
|
+
* Initialize the data lake (lazy initialization)
|
|
327
|
+
*/
|
|
328
|
+
private initializeLake;
|
|
246
329
|
/**
|
|
247
330
|
* Get all patterns, optionally filtered
|
|
331
|
+
* OPTIMIZED: Uses DataLake pattern shards for fast category-based queries
|
|
248
332
|
*
|
|
249
333
|
* @requirements 8.1 - List all patterns
|
|
250
334
|
*/
|
|
251
335
|
getPatterns(query?: PatternQuery): Promise<DashboardPattern[]>;
|
|
336
|
+
/**
|
|
337
|
+
* Get patterns from DataLake (optimized path)
|
|
338
|
+
*/
|
|
339
|
+
private getPatternsFromLake;
|
|
252
340
|
/**
|
|
253
341
|
* Get a single pattern by ID with all locations
|
|
254
342
|
*
|
|
@@ -265,9 +353,14 @@ export declare class DriftDataReader {
|
|
|
265
353
|
getViolations(query?: ViolationQuery): Promise<DashboardViolation[]>;
|
|
266
354
|
/**
|
|
267
355
|
* Get dashboard statistics
|
|
356
|
+
* OPTIMIZED: Uses DataLake status view for instant response
|
|
268
357
|
* @requirements 8.9 - GET `/api/stats` to get overview statistics
|
|
269
358
|
*/
|
|
270
359
|
getStats(): Promise<DashboardStats>;
|
|
360
|
+
/**
|
|
361
|
+
* Convert StatusView from DataLake to DashboardStats
|
|
362
|
+
*/
|
|
363
|
+
private statusViewToStats;
|
|
271
364
|
/**
|
|
272
365
|
* Get the file tree structure
|
|
273
366
|
* @requirements 8.7 - GET `/api/files` to get the file tree
|
|
@@ -407,5 +500,23 @@ export declare class DriftDataReader {
|
|
|
407
500
|
* Filter contracts based on query
|
|
408
501
|
*/
|
|
409
502
|
private filterContracts;
|
|
503
|
+
/**
|
|
504
|
+
* Get trend summary for pattern regressions and improvements
|
|
505
|
+
* OPTIMIZED: Uses DataLake trends view for instant response
|
|
506
|
+
*/
|
|
507
|
+
getTrends(period?: '7d' | '30d' | '90d'): Promise<TrendSummary | null>;
|
|
508
|
+
/**
|
|
509
|
+
* Convert TrendsView from DataLake to TrendSummary
|
|
510
|
+
* TrendsView has: generatedAt, period, overallTrend, healthDelta, regressions, improvements, stableCount, categoryTrends
|
|
511
|
+
*/
|
|
512
|
+
private trendsViewToSummary;
|
|
513
|
+
/**
|
|
514
|
+
* Get historical snapshots for charting
|
|
515
|
+
*/
|
|
516
|
+
getSnapshots(limit?: number): Promise<HistorySnapshot[]>;
|
|
517
|
+
/**
|
|
518
|
+
* Calculate trend summary between two snapshots
|
|
519
|
+
*/
|
|
520
|
+
private calculateTrendSummary;
|
|
410
521
|
}
|
|
411
522
|
//# sourceMappingURL=drift-data-reader.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drift-data-reader.d.ts","sourceRoot":"","sources":["../../src/server/drift-data-reader.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"drift-data-reader.d.ts","sourceRoot":"","sources":["../../src/server/drift-data-reader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,OAAO,KAAK,EAEV,aAAa,EACb,eAAe,EAIf,QAAQ,EACT,MAAM,kBAAkB,CAAC;AAO1B,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,aAAa,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE;QACV,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;IACF,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE;QACR,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;KAC7B,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,6BAA8B,SAAQ,gBAAgB;IACrE,SAAS,EAAE,gBAAgB,EAAE,CAAC;IAC9B,QAAQ,EAAE,kBAAkB,EAAE,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE;QACL,KAAK,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,SAAS,EAAE,MAAM,CAAA;SAAE,CAAC;QAC3C,GAAG,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,SAAS,EAAE,MAAM,CAAA;SAAE,CAAC;KAC1C,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,kBAAmB,SAAQ,gBAAgB;IAC1D,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACrC;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE;QACL,KAAK,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,SAAS,EAAE,MAAM,CAAA;SAAE,CAAC;QAC3C,GAAG,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,SAAS,EAAE,MAAM,CAAA;SAAE,CAAC;KAC1C,CAAC;IACF,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,QAAQ,CAAC,EAAE,YAAY,EAAE,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,KAAK,CAAC;QACd,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,eAAe,CAAC;QAC1B,SAAS,EAAE,gBAAgB,EAAE,CAAC;KAC/B,CAAC,CAAC;IACH,UAAU,EAAE,kBAAkB,EAAE,CAAC;CAClC;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,mBAAmB,EAAE,CAAC;IACjC,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC5C,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,YAAY,CAAC,EAAE;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,CAAC,EAAE,eAAe,EAAE,CAAC;KAChC,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,eAAe,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE;QACR,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;QACxC,UAAU,EAAE,MAAM,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC;KAC7C,CAAC;IACF,UAAU,EAAE;QACV,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;KACtC,CAAC;IACF,KAAK,EAAE;QACL,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,SAAS,EAAE;QACT,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;IACF,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE;QACP,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;QAClB,cAAc,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,OAAO,CAAA;SAAE,CAAC,CAAC;KAC1E,CAAC;IACF,QAAQ,EAAE,KAAK,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,cAAc,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,OAAO,CAAA;SAAE,CAAC,CAAC;KAC1E,CAAC,CAAC;IACH,UAAU,EAAE,KAAK,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;QAClB,YAAY,EAAE,MAAM,CAAC;QACrB,WAAW,EAAE,MAAM,CAAC;QACpB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC,CAAC;IACH,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE;QACV,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;IACF,QAAQ,EAAE;QACR,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC1C;AAMD;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,eAAe,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,aAAa,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,OAAO,EAAE;QACP,aAAa,EAAE,MAAM,CAAC;QACtB,aAAa,EAAE,MAAM,CAAC;QACtB,cAAc,EAAE,MAAM,CAAC;QACvB,aAAa,EAAE,MAAM,CAAC;QACtB,qBAAqB,EAAE,MAAM,CAAC;QAC9B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;KAC7C,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,eAAe,CAAC;IAC1B,IAAI,EAAE,YAAY,GAAG,aAAa,GAAG,QAAQ,CAAC;IAC9C,MAAM,EAAE,YAAY,GAAG,YAAY,GAAG,UAAU,CAAC;IACjD,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,UAAU,GAAG,SAAS,GAAG,MAAM,CAAC;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,IAAI,GAAG,KAAK,GAAG,KAAK,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,YAAY,EAAE,CAAC;IAC5B,YAAY,EAAE,YAAY,EAAE,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,WAAW,GAAG,WAAW,GAAG,QAAQ,CAAC;IACnD,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE;QAC7B,KAAK,EAAE,WAAW,GAAG,WAAW,GAAG,QAAQ,CAAC;QAC5C,mBAAmB,EAAE,MAAM,CAAC;QAC5B,gBAAgB,EAAE,MAAM,CAAC;KAC1B,CAAC,CAAC;CACJ;AAkFD,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAW;IACpC,OAAO,CAAC,eAAe,CAAS;gBAEpB,QAAQ,EAAE,MAAM;IAU5B;;OAEG;IACH,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED;;OAEG;YACW,cAAc;IAW5B;;;;;OAKG;IACG,WAAW,CAAC,KAAK,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAsDpE;;OAEG;YACW,mBAAmB;IAmDjC;;;;OAIG;IACG,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,6BAA6B,GAAG,IAAI,CAAC;IAsC3E;;;;;;OAMG;IACG,aAAa,CAAC,KAAK,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC;IA2C1E;;;;OAIG;IACG,QAAQ,IAAI,OAAO,CAAC,cAAc,CAAC;IAmGzC;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAkCzB;;;OAGG;IACG,WAAW,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;IAqC5C;;;OAGG;IACG,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAyCnE;;;OAGG;IACG,SAAS,IAAI,OAAO,CAAC,WAAW,CAAC;IAiBvC;;;OAGG;IACG,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAmBhE;;;;OAIG;IACG,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI/C;;;;OAIG;IACG,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI9C;;;;OAIG;IACG,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA6B9C;;OAEG;IACH,OAAO,CAAC,wBAAwB;IA2BhC;;OAEG;IACH,OAAO,CAAC,qCAAqC;IAa7C;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAoB1B;;OAEG;IACH,OAAO,CAAC,cAAc;IAsCtB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAsCxB;;;;;;;;OAQG;IACH,OAAO,CAAC,oBAAoB;IAiC5B;;OAEG;IACH,OAAO,CAAC,aAAa;IAoHrB;;OAEG;IACH,OAAO,CAAC,YAAY;IAqBpB;;;OAGG;IACH,OAAO,CAAC,eAAe;IAUvB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAwC3B;;OAEG;YACW,mBAAmB;IAmEjC;;OAEG;YACW,mBAAmB;IA0CjC;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAcxB;;OAEG;IACG,cAAc,CAClB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,YAAY,GAAE,MAAU,GACvB,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IA+BzF;;OAEG;IACG,YAAY,CAAC,KAAK,CAAC,EAAE;QACzB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,aAAa,CAAC,EAAE,OAAO,CAAC;QACxB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC;IAsChC;;OAEG;IACG,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;IA+BhE;;OAEG;IACG,gBAAgB,IAAI,OAAO,CAAC,sBAAsB,CAAC;IA8CzD;;OAEG;IACG,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI/C;;OAEG;IACG,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI/C;;OAEG;YACW,oBAAoB;IA6ElC;;OAEG;IACH,OAAO,CAAC,eAAe;IAwBvB;;;OAGG;IACG,SAAS,CAAC,MAAM,GAAE,IAAI,GAAG,KAAK,GAAG,KAAY,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAyDlF;;;OAGG;IACH,OAAO,CAAC,mBAAmB;IAgE3B;;OAEG;IACG,YAAY,CAAC,KAAK,GAAE,MAAW,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAuBlE;;OAEG;IACH,OAAO,CAAC,qBAAqB;CAuI9B"}
|
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
* Reads and parses data from the .drift/ folder structure.
|
|
5
5
|
* Provides methods for accessing patterns, violations, files, and configuration.
|
|
6
6
|
*
|
|
7
|
+
* OPTIMIZED: Uses DataLake for fast reads with pre-computed views.
|
|
8
|
+
* Falls back to direct file reading when lake data is unavailable.
|
|
9
|
+
*
|
|
7
10
|
* @requirements 1.6 - THE Dashboard_Server SHALL read pattern and violation data from the existing `.drift/` folder structure
|
|
8
11
|
* @requirements 8.1 - THE Dashboard_Server SHALL expose GET `/api/patterns` to list all patterns
|
|
9
12
|
* @requirements 8.2 - THE Dashboard_Server SHALL expose GET `/api/patterns/:id` to get pattern details with locations
|
|
@@ -14,6 +17,7 @@
|
|
|
14
17
|
*/
|
|
15
18
|
import * as fs from 'node:fs/promises';
|
|
16
19
|
import * as path from 'node:path';
|
|
20
|
+
import { createDataLake } from 'driftdetect-core';
|
|
17
21
|
// ============================================================================
|
|
18
22
|
// Constants
|
|
19
23
|
// ============================================================================
|
|
@@ -89,9 +93,15 @@ function generateViolationId(patternId, outlier) {
|
|
|
89
93
|
export class DriftDataReader {
|
|
90
94
|
driftDir;
|
|
91
95
|
patternsDir;
|
|
96
|
+
dataLake;
|
|
97
|
+
lakeInitialized = false;
|
|
92
98
|
constructor(driftDir) {
|
|
93
99
|
this.driftDir = driftDir;
|
|
94
100
|
this.patternsDir = path.join(driftDir, PATTERNS_DIR);
|
|
101
|
+
// Initialize DataLake for optimized reads
|
|
102
|
+
// rootDir is the parent of .drift/
|
|
103
|
+
const rootDir = path.dirname(driftDir);
|
|
104
|
+
this.dataLake = createDataLake({ rootDir });
|
|
95
105
|
}
|
|
96
106
|
/**
|
|
97
107
|
* Get the drift directory path
|
|
@@ -99,14 +109,42 @@ export class DriftDataReader {
|
|
|
99
109
|
get directory() {
|
|
100
110
|
return this.driftDir;
|
|
101
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* Initialize the data lake (lazy initialization)
|
|
114
|
+
*/
|
|
115
|
+
async initializeLake() {
|
|
116
|
+
if (this.lakeInitialized)
|
|
117
|
+
return true;
|
|
118
|
+
try {
|
|
119
|
+
await this.dataLake.initialize();
|
|
120
|
+
this.lakeInitialized = true;
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
102
127
|
/**
|
|
103
128
|
* Get all patterns, optionally filtered
|
|
129
|
+
* OPTIMIZED: Uses DataLake pattern shards for fast category-based queries
|
|
104
130
|
*
|
|
105
131
|
* @requirements 8.1 - List all patterns
|
|
106
132
|
*/
|
|
107
133
|
async getPatterns(query) {
|
|
134
|
+
// OPTIMIZATION: Try DataLake first for fast reads
|
|
135
|
+
if (await this.initializeLake()) {
|
|
136
|
+
try {
|
|
137
|
+
const lakePatterns = await this.getPatternsFromLake(query);
|
|
138
|
+
if (lakePatterns && lakePatterns.length > 0) {
|
|
139
|
+
return lakePatterns;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
// Fall through to direct file reading
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// Fallback: Read patterns from all status directories
|
|
108
147
|
const patterns = [];
|
|
109
|
-
// Read patterns from all status directories
|
|
110
148
|
for (const status of STATUS_DIRS) {
|
|
111
149
|
const statusDir = path.join(this.patternsDir, status);
|
|
112
150
|
if (!(await fileExists(statusDir))) {
|
|
@@ -140,6 +178,56 @@ export class DriftDataReader {
|
|
|
140
178
|
// Apply filters if provided
|
|
141
179
|
return this.filterPatterns(patterns, query);
|
|
142
180
|
}
|
|
181
|
+
/**
|
|
182
|
+
* Get patterns from DataLake (optimized path)
|
|
183
|
+
*/
|
|
184
|
+
async getPatternsFromLake(query) {
|
|
185
|
+
try {
|
|
186
|
+
// Build query options for DataLake
|
|
187
|
+
const queryOptions = {
|
|
188
|
+
limit: 1000, // Get all patterns
|
|
189
|
+
};
|
|
190
|
+
if (query?.category) {
|
|
191
|
+
queryOptions.categories = [query.category];
|
|
192
|
+
}
|
|
193
|
+
if (query?.status && query.status !== 'all') {
|
|
194
|
+
queryOptions.status = query.status;
|
|
195
|
+
}
|
|
196
|
+
if (query?.minConfidence !== undefined) {
|
|
197
|
+
queryOptions.minConfidence = query.minConfidence;
|
|
198
|
+
}
|
|
199
|
+
if (query?.search) {
|
|
200
|
+
queryOptions.search = query.search;
|
|
201
|
+
}
|
|
202
|
+
const result = await this.dataLake.query.getPatterns(queryOptions);
|
|
203
|
+
if (result.items.length === 0 && result.total === 0) {
|
|
204
|
+
return null; // No data in lake, fall back to files
|
|
205
|
+
}
|
|
206
|
+
// Convert lake PatternSummary to DashboardPattern
|
|
207
|
+
return result.items.map(p => ({
|
|
208
|
+
id: p.id,
|
|
209
|
+
name: p.name,
|
|
210
|
+
category: p.category,
|
|
211
|
+
subcategory: p.subcategory,
|
|
212
|
+
status: p.status,
|
|
213
|
+
description: '', // PatternSummary doesn't include description
|
|
214
|
+
confidence: {
|
|
215
|
+
score: p.confidence,
|
|
216
|
+
level: p.confidenceLevel,
|
|
217
|
+
},
|
|
218
|
+
locationCount: p.locationCount,
|
|
219
|
+
outlierCount: p.outlierCount,
|
|
220
|
+
severity: p.severity || (p.outlierCount > 0 ? 'warning' : 'info'),
|
|
221
|
+
metadata: {
|
|
222
|
+
firstSeen: new Date().toISOString(),
|
|
223
|
+
lastSeen: new Date().toISOString(),
|
|
224
|
+
},
|
|
225
|
+
}));
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
143
231
|
/**
|
|
144
232
|
* Get a single pattern by ID with all locations
|
|
145
233
|
*
|
|
@@ -224,9 +312,23 @@ export class DriftDataReader {
|
|
|
224
312
|
}
|
|
225
313
|
/**
|
|
226
314
|
* Get dashboard statistics
|
|
315
|
+
* OPTIMIZED: Uses DataLake status view for instant response
|
|
227
316
|
* @requirements 8.9 - GET `/api/stats` to get overview statistics
|
|
228
317
|
*/
|
|
229
318
|
async getStats() {
|
|
319
|
+
// OPTIMIZATION: Try DataLake status view first (instant)
|
|
320
|
+
if (await this.initializeLake()) {
|
|
321
|
+
try {
|
|
322
|
+
const statusView = await this.dataLake.query.getStatus();
|
|
323
|
+
if (statusView) {
|
|
324
|
+
return this.statusViewToStats(statusView);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
catch {
|
|
328
|
+
// Fall through to direct file reading
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
// Fallback: Compute from raw pattern files
|
|
230
332
|
const patterns = await this.getPatterns();
|
|
231
333
|
const violations = await this.getViolations();
|
|
232
334
|
// Count patterns by status
|
|
@@ -304,6 +406,42 @@ export class DriftDataReader {
|
|
|
304
406
|
lastScan,
|
|
305
407
|
};
|
|
306
408
|
}
|
|
409
|
+
/**
|
|
410
|
+
* Convert StatusView from DataLake to DashboardStats
|
|
411
|
+
*/
|
|
412
|
+
statusViewToStats(view) {
|
|
413
|
+
return {
|
|
414
|
+
healthScore: view.health.score,
|
|
415
|
+
patterns: {
|
|
416
|
+
total: view.patterns.total,
|
|
417
|
+
byStatus: {
|
|
418
|
+
discovered: view.patterns.discovered,
|
|
419
|
+
approved: view.patterns.approved,
|
|
420
|
+
ignored: view.patterns.ignored,
|
|
421
|
+
},
|
|
422
|
+
byCategory: view.patterns.byCategory,
|
|
423
|
+
},
|
|
424
|
+
violations: {
|
|
425
|
+
total: view.issues.critical + view.issues.warnings,
|
|
426
|
+
bySeverity: {
|
|
427
|
+
error: view.issues.critical,
|
|
428
|
+
warning: view.issues.warnings,
|
|
429
|
+
info: 0,
|
|
430
|
+
hint: 0,
|
|
431
|
+
},
|
|
432
|
+
},
|
|
433
|
+
files: {
|
|
434
|
+
// StatusView doesn't track files, estimate from patterns
|
|
435
|
+
total: 0,
|
|
436
|
+
scanned: view.lastScan.filesScanned,
|
|
437
|
+
},
|
|
438
|
+
detectors: {
|
|
439
|
+
active: Object.keys(view.patterns.byCategory).length,
|
|
440
|
+
total: Object.keys(view.patterns.byCategory).length,
|
|
441
|
+
},
|
|
442
|
+
lastScan: view.lastScan.timestamp || null,
|
|
443
|
+
};
|
|
444
|
+
}
|
|
307
445
|
/**
|
|
308
446
|
* Get the file tree structure
|
|
309
447
|
* @requirements 8.7 - GET `/api/files` to get the file tree
|
|
@@ -1147,5 +1285,266 @@ export class DriftDataReader {
|
|
|
1147
1285
|
return true;
|
|
1148
1286
|
});
|
|
1149
1287
|
}
|
|
1288
|
+
// ==========================================================================
|
|
1289
|
+
// Trend / History Methods
|
|
1290
|
+
// ==========================================================================
|
|
1291
|
+
/**
|
|
1292
|
+
* Get trend summary for pattern regressions and improvements
|
|
1293
|
+
* OPTIMIZED: Uses DataLake trends view for instant response
|
|
1294
|
+
*/
|
|
1295
|
+
async getTrends(period = '7d') {
|
|
1296
|
+
// OPTIMIZATION: Try DataLake trends view first
|
|
1297
|
+
if (await this.initializeLake()) {
|
|
1298
|
+
try {
|
|
1299
|
+
const trendsView = await this.dataLake.views.getTrendsView();
|
|
1300
|
+
if (trendsView) {
|
|
1301
|
+
return this.trendsViewToSummary(trendsView, period);
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
catch {
|
|
1305
|
+
// Fall through to direct file reading
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
// Fallback: Read from history snapshots directly
|
|
1309
|
+
const historyDir = path.join(this.driftDir, 'history', 'snapshots');
|
|
1310
|
+
if (!(await fileExists(historyDir))) {
|
|
1311
|
+
return null;
|
|
1312
|
+
}
|
|
1313
|
+
const days = period === '7d' ? 7 : period === '30d' ? 30 : 90;
|
|
1314
|
+
const now = new Date();
|
|
1315
|
+
const startDate = new Date(now);
|
|
1316
|
+
startDate.setDate(startDate.getDate() - days);
|
|
1317
|
+
// Get snapshots
|
|
1318
|
+
const files = await fs.readdir(historyDir);
|
|
1319
|
+
const jsonFiles = files.filter(f => f.endsWith('.json')).sort();
|
|
1320
|
+
if (jsonFiles.length < 2) {
|
|
1321
|
+
return null; // Need at least 2 snapshots to calculate trends
|
|
1322
|
+
}
|
|
1323
|
+
// Get latest and comparison snapshot
|
|
1324
|
+
const latestFile = jsonFiles[jsonFiles.length - 1];
|
|
1325
|
+
const latestContent = await fs.readFile(path.join(historyDir, latestFile), 'utf-8');
|
|
1326
|
+
const latestSnapshot = JSON.parse(latestContent);
|
|
1327
|
+
// Find snapshot closest to start date
|
|
1328
|
+
const startDateStr = startDate.toISOString().split('T')[0];
|
|
1329
|
+
let comparisonFile = jsonFiles[0];
|
|
1330
|
+
for (const file of jsonFiles) {
|
|
1331
|
+
const fileDate = file.replace('.json', '');
|
|
1332
|
+
if (fileDate <= startDateStr) {
|
|
1333
|
+
comparisonFile = file;
|
|
1334
|
+
}
|
|
1335
|
+
else {
|
|
1336
|
+
break;
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
const comparisonContent = await fs.readFile(path.join(historyDir, comparisonFile), 'utf-8');
|
|
1340
|
+
const comparisonSnapshot = JSON.parse(comparisonContent);
|
|
1341
|
+
// Calculate trends
|
|
1342
|
+
return this.calculateTrendSummary(latestSnapshot, comparisonSnapshot, period);
|
|
1343
|
+
}
|
|
1344
|
+
/**
|
|
1345
|
+
* Convert TrendsView from DataLake to TrendSummary
|
|
1346
|
+
* TrendsView has: generatedAt, period, overallTrend, healthDelta, regressions, improvements, stableCount, categoryTrends
|
|
1347
|
+
*/
|
|
1348
|
+
trendsViewToSummary(view, period) {
|
|
1349
|
+
// Calculate date range from period
|
|
1350
|
+
const now = new Date();
|
|
1351
|
+
const days = period === '7d' ? 7 : period === '30d' ? 30 : 90;
|
|
1352
|
+
const startDate = new Date(now);
|
|
1353
|
+
startDate.setDate(startDate.getDate() - days);
|
|
1354
|
+
// Convert TrendItem[] to PatternTrend[]
|
|
1355
|
+
const regressions = (view.regressions || []).map(r => ({
|
|
1356
|
+
patternId: r.patternId,
|
|
1357
|
+
patternName: r.patternName,
|
|
1358
|
+
category: r.category,
|
|
1359
|
+
type: 'regression',
|
|
1360
|
+
metric: r.metric,
|
|
1361
|
+
previousValue: r.previousValue,
|
|
1362
|
+
currentValue: r.currentValue,
|
|
1363
|
+
change: r.change,
|
|
1364
|
+
changePercent: r.previousValue > 0 ? (r.change / r.previousValue) * 100 : 0,
|
|
1365
|
+
severity: r.severity,
|
|
1366
|
+
firstSeen: view.generatedAt,
|
|
1367
|
+
details: `${r.metric} changed from ${r.previousValue} to ${r.currentValue}`,
|
|
1368
|
+
}));
|
|
1369
|
+
const improvements = (view.improvements || []).map(i => ({
|
|
1370
|
+
patternId: i.patternId,
|
|
1371
|
+
patternName: i.patternName,
|
|
1372
|
+
category: i.category,
|
|
1373
|
+
type: 'improvement',
|
|
1374
|
+
metric: i.metric,
|
|
1375
|
+
previousValue: i.previousValue,
|
|
1376
|
+
currentValue: i.currentValue,
|
|
1377
|
+
change: i.change,
|
|
1378
|
+
changePercent: i.previousValue > 0 ? (i.change / i.previousValue) * 100 : 0,
|
|
1379
|
+
severity: i.severity,
|
|
1380
|
+
firstSeen: view.generatedAt,
|
|
1381
|
+
details: `${i.metric} improved from ${i.previousValue} to ${i.currentValue}`,
|
|
1382
|
+
}));
|
|
1383
|
+
// Convert CategoryTrend to TrendSummary format
|
|
1384
|
+
const categoryTrends = {};
|
|
1385
|
+
for (const [category, trend] of Object.entries(view.categoryTrends || {})) {
|
|
1386
|
+
categoryTrends[category] = {
|
|
1387
|
+
trend: trend.trend,
|
|
1388
|
+
avgConfidenceChange: trend.avgConfidenceChange,
|
|
1389
|
+
complianceChange: trend.complianceChange,
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1392
|
+
return {
|
|
1393
|
+
period,
|
|
1394
|
+
startDate: startDate.toISOString().split('T')[0],
|
|
1395
|
+
endDate: now.toISOString().split('T')[0],
|
|
1396
|
+
regressions,
|
|
1397
|
+
improvements,
|
|
1398
|
+
stable: view.stableCount || 0,
|
|
1399
|
+
overallTrend: view.overallTrend,
|
|
1400
|
+
healthDelta: view.healthDelta || 0,
|
|
1401
|
+
categoryTrends,
|
|
1402
|
+
};
|
|
1403
|
+
}
|
|
1404
|
+
/**
|
|
1405
|
+
* Get historical snapshots for charting
|
|
1406
|
+
*/
|
|
1407
|
+
async getSnapshots(limit = 30) {
|
|
1408
|
+
const historyDir = path.join(this.driftDir, 'history', 'snapshots');
|
|
1409
|
+
if (!(await fileExists(historyDir))) {
|
|
1410
|
+
return [];
|
|
1411
|
+
}
|
|
1412
|
+
const files = await fs.readdir(historyDir);
|
|
1413
|
+
const jsonFiles = files.filter(f => f.endsWith('.json')).sort().slice(-limit);
|
|
1414
|
+
const snapshots = [];
|
|
1415
|
+
for (const file of jsonFiles) {
|
|
1416
|
+
try {
|
|
1417
|
+
const content = await fs.readFile(path.join(historyDir, file), 'utf-8');
|
|
1418
|
+
snapshots.push(JSON.parse(content));
|
|
1419
|
+
}
|
|
1420
|
+
catch (error) {
|
|
1421
|
+
console.error(`Error reading snapshot ${file}:`, error);
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
return snapshots;
|
|
1425
|
+
}
|
|
1426
|
+
/**
|
|
1427
|
+
* Calculate trend summary between two snapshots
|
|
1428
|
+
*/
|
|
1429
|
+
calculateTrendSummary(current, previous, period) {
|
|
1430
|
+
const regressions = [];
|
|
1431
|
+
const improvements = [];
|
|
1432
|
+
const previousMap = new Map(previous.patterns.map(p => [p.patternId, p]));
|
|
1433
|
+
// Thresholds
|
|
1434
|
+
const CONFIDENCE_THRESHOLD = 0.05;
|
|
1435
|
+
const COMPLIANCE_THRESHOLD = 0.10;
|
|
1436
|
+
const OUTLIER_THRESHOLD = 3;
|
|
1437
|
+
for (const currentPattern of current.patterns) {
|
|
1438
|
+
const prevPattern = previousMap.get(currentPattern.patternId);
|
|
1439
|
+
if (!prevPattern)
|
|
1440
|
+
continue;
|
|
1441
|
+
// Check confidence change
|
|
1442
|
+
const confidenceChange = currentPattern.confidence - prevPattern.confidence;
|
|
1443
|
+
if (Math.abs(confidenceChange) >= CONFIDENCE_THRESHOLD) {
|
|
1444
|
+
const trend = {
|
|
1445
|
+
patternId: currentPattern.patternId,
|
|
1446
|
+
patternName: currentPattern.patternName,
|
|
1447
|
+
category: currentPattern.category,
|
|
1448
|
+
type: confidenceChange < 0 ? 'regression' : 'improvement',
|
|
1449
|
+
metric: 'confidence',
|
|
1450
|
+
previousValue: prevPattern.confidence,
|
|
1451
|
+
currentValue: currentPattern.confidence,
|
|
1452
|
+
change: confidenceChange,
|
|
1453
|
+
changePercent: (confidenceChange / prevPattern.confidence) * 100,
|
|
1454
|
+
severity: confidenceChange <= -0.15 ? 'critical' : confidenceChange < 0 ? 'warning' : 'info',
|
|
1455
|
+
firstSeen: previous.timestamp,
|
|
1456
|
+
details: `Confidence ${confidenceChange < 0 ? 'dropped' : 'improved'} from ${(prevPattern.confidence * 100).toFixed(0)}% to ${(currentPattern.confidence * 100).toFixed(0)}%`,
|
|
1457
|
+
};
|
|
1458
|
+
if (trend.type === 'regression') {
|
|
1459
|
+
regressions.push(trend);
|
|
1460
|
+
}
|
|
1461
|
+
else {
|
|
1462
|
+
improvements.push(trend);
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
// Check compliance change
|
|
1466
|
+
const complianceChange = currentPattern.complianceRate - prevPattern.complianceRate;
|
|
1467
|
+
if (Math.abs(complianceChange) >= COMPLIANCE_THRESHOLD) {
|
|
1468
|
+
const trend = {
|
|
1469
|
+
patternId: currentPattern.patternId,
|
|
1470
|
+
patternName: currentPattern.patternName,
|
|
1471
|
+
category: currentPattern.category,
|
|
1472
|
+
type: complianceChange < 0 ? 'regression' : 'improvement',
|
|
1473
|
+
metric: 'compliance',
|
|
1474
|
+
previousValue: prevPattern.complianceRate,
|
|
1475
|
+
currentValue: currentPattern.complianceRate,
|
|
1476
|
+
change: complianceChange,
|
|
1477
|
+
changePercent: prevPattern.complianceRate > 0 ? (complianceChange / prevPattern.complianceRate) * 100 : 0,
|
|
1478
|
+
severity: complianceChange <= -0.20 ? 'critical' : complianceChange < 0 ? 'warning' : 'info',
|
|
1479
|
+
firstSeen: previous.timestamp,
|
|
1480
|
+
details: `Compliance ${complianceChange < 0 ? 'dropped' : 'improved'} from ${(prevPattern.complianceRate * 100).toFixed(0)}% to ${(currentPattern.complianceRate * 100).toFixed(0)}%`,
|
|
1481
|
+
};
|
|
1482
|
+
if (trend.type === 'regression') {
|
|
1483
|
+
regressions.push(trend);
|
|
1484
|
+
}
|
|
1485
|
+
else {
|
|
1486
|
+
improvements.push(trend);
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
// Check outlier increase
|
|
1490
|
+
const outlierChange = currentPattern.outlierCount - prevPattern.outlierCount;
|
|
1491
|
+
if (outlierChange >= OUTLIER_THRESHOLD) {
|
|
1492
|
+
regressions.push({
|
|
1493
|
+
patternId: currentPattern.patternId,
|
|
1494
|
+
patternName: currentPattern.patternName,
|
|
1495
|
+
category: currentPattern.category,
|
|
1496
|
+
type: 'regression',
|
|
1497
|
+
metric: 'outliers',
|
|
1498
|
+
previousValue: prevPattern.outlierCount,
|
|
1499
|
+
currentValue: currentPattern.outlierCount,
|
|
1500
|
+
change: outlierChange,
|
|
1501
|
+
changePercent: prevPattern.outlierCount > 0 ? (outlierChange / prevPattern.outlierCount) * 100 : 100,
|
|
1502
|
+
severity: outlierChange >= 10 ? 'critical' : 'warning',
|
|
1503
|
+
firstSeen: previous.timestamp,
|
|
1504
|
+
details: `${outlierChange} new outliers (${prevPattern.outlierCount} → ${currentPattern.outlierCount})`,
|
|
1505
|
+
});
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
// Calculate category trends
|
|
1509
|
+
const categoryTrends = {};
|
|
1510
|
+
const categories = new Set([
|
|
1511
|
+
...Object.keys(current.summary.byCategory),
|
|
1512
|
+
...Object.keys(previous.summary.byCategory),
|
|
1513
|
+
]);
|
|
1514
|
+
for (const category of categories) {
|
|
1515
|
+
const currentCat = current.summary.byCategory[category];
|
|
1516
|
+
const prevCat = previous.summary.byCategory[category];
|
|
1517
|
+
if (currentCat && prevCat) {
|
|
1518
|
+
const avgConfidenceChange = currentCat.avgConfidence - prevCat.avgConfidence;
|
|
1519
|
+
const complianceChange = currentCat.complianceRate - prevCat.complianceRate;
|
|
1520
|
+
categoryTrends[category] = {
|
|
1521
|
+
trend: avgConfidenceChange > 0.02 ? 'improving'
|
|
1522
|
+
: avgConfidenceChange < -0.02 ? 'declining'
|
|
1523
|
+
: 'stable',
|
|
1524
|
+
avgConfidenceChange,
|
|
1525
|
+
complianceChange,
|
|
1526
|
+
};
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
// Calculate overall trend
|
|
1530
|
+
const healthDelta = current.summary.overallComplianceRate - previous.summary.overallComplianceRate;
|
|
1531
|
+
const overallTrend = healthDelta > 0.02 ? 'improving'
|
|
1532
|
+
: healthDelta < -0.02 ? 'declining'
|
|
1533
|
+
: 'stable';
|
|
1534
|
+
// Count stable patterns
|
|
1535
|
+
const changedPatternIds = new Set([...regressions, ...improvements].map(t => t.patternId));
|
|
1536
|
+
const stableCount = current.patterns.filter(p => !changedPatternIds.has(p.patternId)).length;
|
|
1537
|
+
return {
|
|
1538
|
+
period,
|
|
1539
|
+
startDate: previous.date,
|
|
1540
|
+
endDate: current.date,
|
|
1541
|
+
regressions,
|
|
1542
|
+
improvements,
|
|
1543
|
+
stable: stableCount,
|
|
1544
|
+
overallTrend,
|
|
1545
|
+
healthDelta,
|
|
1546
|
+
categoryTrends,
|
|
1547
|
+
};
|
|
1548
|
+
}
|
|
1150
1549
|
}
|
|
1151
1550
|
//# sourceMappingURL=drift-data-reader.js.map
|