@memberjunction/ng-dashboards 5.25.0 → 5.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/dist/AI/components/analytics/agent-runs/agent-run-analysis.component.d.ts +96 -0
  2. package/dist/AI/components/analytics/agent-runs/agent-run-analysis.component.d.ts.map +1 -0
  3. package/dist/AI/components/analytics/agent-runs/agent-run-analysis.component.js +710 -0
  4. package/dist/AI/components/analytics/agent-runs/agent-run-analysis.component.js.map +1 -0
  5. package/dist/AI/components/analytics/ai-analytics-resource.component.d.ts +52 -0
  6. package/dist/AI/components/analytics/ai-analytics-resource.component.d.ts.map +1 -0
  7. package/dist/AI/components/analytics/ai-analytics-resource.component.js +356 -0
  8. package/dist/AI/components/analytics/ai-analytics-resource.component.js.map +1 -0
  9. package/dist/AI/components/analytics/analytics-filter-bar.component.d.ts +52 -0
  10. package/dist/AI/components/analytics/analytics-filter-bar.component.d.ts.map +1 -0
  11. package/dist/AI/components/analytics/analytics-filter-bar.component.js +306 -0
  12. package/dist/AI/components/analytics/analytics-filter-bar.component.js.map +1 -0
  13. package/dist/AI/components/analytics/cost-budget/cost-budget.component.d.ts +81 -0
  14. package/dist/AI/components/analytics/cost-budget/cost-budget.component.d.ts.map +1 -0
  15. package/dist/AI/components/analytics/cost-budget/cost-budget.component.js +744 -0
  16. package/dist/AI/components/analytics/cost-budget/cost-budget.component.js.map +1 -0
  17. package/dist/AI/components/analytics/error-analysis/error-analysis.component.d.ts +61 -0
  18. package/dist/AI/components/analytics/error-analysis/error-analysis.component.d.ts.map +1 -0
  19. package/dist/AI/components/analytics/error-analysis/error-analysis.component.js +490 -0
  20. package/dist/AI/components/analytics/error-analysis/error-analysis.component.js.map +1 -0
  21. package/dist/AI/components/analytics/executive-summary/executive-summary.component.d.ts +77 -0
  22. package/dist/AI/components/analytics/executive-summary/executive-summary.component.d.ts.map +1 -0
  23. package/dist/AI/components/analytics/executive-summary/executive-summary.component.js +673 -0
  24. package/dist/AI/components/analytics/executive-summary/executive-summary.component.js.map +1 -0
  25. package/dist/AI/components/analytics/model-performance/model-performance.component.d.ts +65 -0
  26. package/dist/AI/components/analytics/model-performance/model-performance.component.d.ts.map +1 -0
  27. package/dist/AI/components/analytics/model-performance/model-performance.component.js +537 -0
  28. package/dist/AI/components/analytics/model-performance/model-performance.component.js.map +1 -0
  29. package/dist/AI/components/analytics/prompt-runs/prompt-run-analysis.component.d.ts +131 -0
  30. package/dist/AI/components/analytics/prompt-runs/prompt-run-analysis.component.d.ts.map +1 -0
  31. package/dist/AI/components/analytics/prompt-runs/prompt-run-analysis.component.js +1030 -0
  32. package/dist/AI/components/analytics/prompt-runs/prompt-run-analysis.component.js.map +1 -0
  33. package/dist/AI/components/analytics/usage-patterns/usage-patterns.component.d.ts +78 -0
  34. package/dist/AI/components/analytics/usage-patterns/usage-patterns.component.d.ts.map +1 -0
  35. package/dist/AI/components/analytics/usage-patterns/usage-patterns.component.js +569 -0
  36. package/dist/AI/components/analytics/usage-patterns/usage-patterns.component.js.map +1 -0
  37. package/dist/AI/components/execution-monitoring.component.d.ts.map +1 -1
  38. package/dist/AI/components/execution-monitoring.component.js +4 -14
  39. package/dist/AI/components/execution-monitoring.component.js.map +1 -1
  40. package/dist/AI/components/overview/ai-overview-hub.component.d.ts +58 -0
  41. package/dist/AI/components/overview/ai-overview-hub.component.d.ts.map +1 -0
  42. package/dist/AI/components/overview/ai-overview-hub.component.js +315 -0
  43. package/dist/AI/components/overview/ai-overview-hub.component.js.map +1 -0
  44. package/dist/AI/components/prompts/prompt-management.component.js +1 -1
  45. package/dist/AI/components/prompts/prompt-management.component.js.map +1 -1
  46. package/dist/AI/index.d.ts +11 -0
  47. package/dist/AI/index.d.ts.map +1 -1
  48. package/dist/AI/index.js +13 -0
  49. package/dist/AI/index.js.map +1 -1
  50. package/dist/AI/interfaces/analytics-preferences.interface.d.ts +50 -0
  51. package/dist/AI/interfaces/analytics-preferences.interface.d.ts.map +1 -0
  52. package/dist/AI/interfaces/analytics-preferences.interface.js +9 -0
  53. package/dist/AI/interfaces/analytics-preferences.interface.js.map +1 -0
  54. package/dist/ComponentStudio/components/artifact-load-dialog.component.d.ts.map +1 -1
  55. package/dist/ComponentStudio/components/artifact-load-dialog.component.js +1 -1
  56. package/dist/ComponentStudio/components/artifact-load-dialog.component.js.map +1 -1
  57. package/dist/Home/home-dashboard.component.js +2 -2
  58. package/dist/MCP/index.d.ts +1 -0
  59. package/dist/MCP/index.d.ts.map +1 -1
  60. package/dist/MCP/index.js +2 -0
  61. package/dist/MCP/index.js.map +1 -1
  62. package/dist/MCP/mcp-dashboard.component.d.ts +1 -0
  63. package/dist/MCP/mcp-dashboard.component.d.ts.map +1 -1
  64. package/dist/MCP/mcp-dashboard.component.js +5 -4
  65. package/dist/MCP/mcp-dashboard.component.js.map +1 -1
  66. package/dist/MCP/mcp-resource.component.d.ts +6 -5
  67. package/dist/MCP/mcp-resource.component.d.ts.map +1 -1
  68. package/dist/MCP/mcp-resource.component.js +7 -8
  69. package/dist/MCP/mcp-resource.component.js.map +1 -1
  70. package/dist/ai-dashboards.module.d.ts +27 -17
  71. package/dist/ai-dashboards.module.d.ts.map +1 -1
  72. package/dist/ai-dashboards.module.js +66 -3
  73. package/dist/ai-dashboards.module.js.map +1 -1
  74. package/dist/public-api.d.ts +1 -1
  75. package/dist/public-api.d.ts.map +1 -1
  76. package/dist/public-api.js +1 -1
  77. package/dist/public-api.js.map +1 -1
  78. package/package.json +49 -48
  79. package/dist/__tests__/analytics-resource.test.d.ts +0 -2
  80. package/dist/__tests__/analytics-resource.test.d.ts.map +0 -1
  81. package/dist/__tests__/analytics-resource.test.js +0 -181
  82. package/dist/__tests__/analytics-resource.test.js.map +0 -1
  83. package/dist/__tests__/dashboards.test.d.ts +0 -2
  84. package/dist/__tests__/dashboards.test.d.ts.map +0 -1
  85. package/dist/__tests__/dashboards.test.js +0 -40
  86. package/dist/__tests__/dashboards.test.js.map +0 -1
  87. package/dist/__tests__/integration-data-service.test.d.ts +0 -2
  88. package/dist/__tests__/integration-data-service.test.d.ts.map +0 -1
  89. package/dist/__tests__/integration-data-service.test.js +0 -132
  90. package/dist/__tests__/integration-data-service.test.js.map +0 -1
  91. package/dist/__tests__/mapping-validation.test.d.ts +0 -2
  92. package/dist/__tests__/mapping-validation.test.d.ts.map +0 -1
  93. package/dist/__tests__/mapping-validation.test.js +0 -170
  94. package/dist/__tests__/mapping-validation.test.js.map +0 -1
  95. package/dist/__tests__/scheduling.test.d.ts +0 -2
  96. package/dist/__tests__/scheduling.test.d.ts.map +0 -1
  97. package/dist/__tests__/scheduling.test.js +0 -205
  98. package/dist/__tests__/scheduling.test.js.map +0 -1
@@ -0,0 +1,673 @@
1
+ import { Component, Input, Output, EventEmitter, ChangeDetectorRef, inject } from '@angular/core';
2
+ import { Subject } from 'rxjs';
3
+ import { takeUntil } from 'rxjs/operators';
4
+ import { AIInstrumentationService } from '../../../services/ai-instrumentation.service';
5
+ import * as i0 from "@angular/core";
6
+ import * as i1 from "@memberjunction/ng-shared-generic";
7
+ import * as i2 from "../../charts/time-series-chart.component";
8
+ import * as i3 from "../analytics-filter-bar.component";
9
+ import * as i4 from "@angular/common";
10
+ const _forTrack0 = ($index, $item) => $item.Label;
11
+ const _forTrack1 = ($index, $item) => $item.Name;
12
+ const _forTrack2 = ($index, $item) => $item.Source;
13
+ function AnalyticsExecutiveSummaryComponent_Conditional_1_Template(rf, ctx) { if (rf & 1) {
14
+ i0.ɵɵelementStart(0, "div", 1);
15
+ i0.ɵɵelement(1, "mj-loading", 18);
16
+ i0.ɵɵelementEnd();
17
+ } }
18
+ function AnalyticsExecutiveSummaryComponent_For_4_For_7_Template(rf, ctx) { if (rf & 1) {
19
+ i0.ɵɵelement(0, "div", 25);
20
+ } if (rf & 2) {
21
+ const bar_r1 = ctx.$implicit;
22
+ const card_r2 = i0.ɵɵnextContext().$implicit;
23
+ i0.ɵɵstyleProp("height", bar_r1, "%")("background", card_r2.BorderColor);
24
+ } }
25
+ function AnalyticsExecutiveSummaryComponent_For_4_Conditional_8_Template(rf, ctx) { if (rf & 1) {
26
+ i0.ɵɵelementStart(0, "div", 26);
27
+ i0.ɵɵelement(1, "i");
28
+ i0.ɵɵtext(2);
29
+ i0.ɵɵpipe(3, "number");
30
+ i0.ɵɵelementStart(4, "span", 27);
31
+ i0.ɵɵtext(5);
32
+ i0.ɵɵelementEnd()();
33
+ } if (rf & 2) {
34
+ const card_r2 = i0.ɵɵnextContext().$implicit;
35
+ const ctx_r2 = i0.ɵɵnextContext();
36
+ i0.ɵɵclassProp("kpi-delta--good", card_r2.IsImprovement)("kpi-delta--bad", !card_r2.IsImprovement);
37
+ i0.ɵɵadvance();
38
+ i0.ɵɵclassMap(card_r2.DeltaDirection === "up" ? "fa-solid fa-arrow-up" : "fa-solid fa-arrow-down");
39
+ i0.ɵɵadvance();
40
+ i0.ɵɵtextInterpolate1(" ", i0.ɵɵpipeBind2(3, 8, card_r2.DeltaPercent, "1.1-1"), "% ");
41
+ i0.ɵɵadvance(3);
42
+ i0.ɵɵtextInterpolate1("vs prev ", ctx_r2.PeriodLabel);
43
+ } }
44
+ function AnalyticsExecutiveSummaryComponent_For_4_Template(rf, ctx) { if (rf & 1) {
45
+ i0.ɵɵelementStart(0, "div", 19)(1, "div", 20);
46
+ i0.ɵɵtext(2);
47
+ i0.ɵɵelementEnd();
48
+ i0.ɵɵelementStart(3, "div", 21);
49
+ i0.ɵɵtext(4);
50
+ i0.ɵɵelementEnd();
51
+ i0.ɵɵelementStart(5, "div", 22);
52
+ i0.ɵɵrepeaterCreate(6, AnalyticsExecutiveSummaryComponent_For_4_For_7_Template, 1, 4, "div", 23, i0.ɵɵrepeaterTrackByIndex);
53
+ i0.ɵɵelementEnd();
54
+ i0.ɵɵconditionalCreate(8, AnalyticsExecutiveSummaryComponent_For_4_Conditional_8_Template, 6, 11, "div", 24);
55
+ i0.ɵɵelementEnd();
56
+ } if (rf & 2) {
57
+ const card_r2 = ctx.$implicit;
58
+ const ctx_r2 = i0.ɵɵnextContext();
59
+ i0.ɵɵstyleProp("border-left-color", card_r2.BorderColor);
60
+ i0.ɵɵadvance(2);
61
+ i0.ɵɵtextInterpolate(card_r2.Label);
62
+ i0.ɵɵadvance(2);
63
+ i0.ɵɵtextInterpolate(card_r2.Value);
64
+ i0.ɵɵadvance(2);
65
+ i0.ɵɵrepeater(card_r2.SparklineData);
66
+ i0.ɵɵadvance(2);
67
+ i0.ɵɵconditional(ctx_r2.ComparisonEnabled && card_r2.DeltaDirection !== "stable" ? 8 : -1);
68
+ } }
69
+ function AnalyticsExecutiveSummaryComponent_Conditional_16_Template(rf, ctx) { if (rf & 1) {
70
+ i0.ɵɵelementStart(0, "div", 13);
71
+ i0.ɵɵtext(1, "No data for selected period");
72
+ i0.ɵɵelementEnd();
73
+ } }
74
+ function AnalyticsExecutiveSummaryComponent_For_18_Template(rf, ctx) { if (rf & 1) {
75
+ const _r4 = i0.ɵɵgetCurrentView();
76
+ i0.ɵɵelementStart(0, "div", 28);
77
+ i0.ɵɵlistener("click", function AnalyticsExecutiveSummaryComponent_For_18_Template_div_click_0_listener() { const item_r5 = i0.ɵɵrestoreView(_r4).$implicit; const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.OnConsumerClick(item_r5)); })("keydown.enter", function AnalyticsExecutiveSummaryComponent_For_18_Template_div_keydown_enter_0_listener() { const item_r5 = i0.ɵɵrestoreView(_r4).$implicit; const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.OnConsumerClick(item_r5)); });
78
+ i0.ɵɵelementStart(1, "div", 29);
79
+ i0.ɵɵtext(2);
80
+ i0.ɵɵelementEnd();
81
+ i0.ɵɵelementStart(3, "div", 30);
82
+ i0.ɵɵtext(4);
83
+ i0.ɵɵelementEnd();
84
+ i0.ɵɵelementStart(5, "div", 31);
85
+ i0.ɵɵtext(6);
86
+ i0.ɵɵelementEnd();
87
+ i0.ɵɵelementStart(7, "div", 32);
88
+ i0.ɵɵtext(8);
89
+ i0.ɵɵelementEnd();
90
+ i0.ɵɵelementStart(9, "div", 33);
91
+ i0.ɵɵelement(10, "div", 34);
92
+ i0.ɵɵelementEnd()();
93
+ } if (rf & 2) {
94
+ const item_r5 = ctx.$implicit;
95
+ const ctx_r2 = i0.ɵɵnextContext();
96
+ i0.ɵɵadvance();
97
+ i0.ɵɵclassProp("consumer-rank--top", item_r5.Rank <= 3);
98
+ i0.ɵɵadvance();
99
+ i0.ɵɵtextInterpolate(item_r5.Rank);
100
+ i0.ɵɵadvance();
101
+ i0.ɵɵclassProp("consumer-type-pill--agent", item_r5.Type === "agent");
102
+ i0.ɵɵadvance();
103
+ i0.ɵɵtextInterpolate(item_r5.Type);
104
+ i0.ɵɵadvance();
105
+ i0.ɵɵproperty("title", item_r5.Name);
106
+ i0.ɵɵadvance();
107
+ i0.ɵɵtextInterpolate(item_r5.Name);
108
+ i0.ɵɵadvance(2);
109
+ i0.ɵɵtextInterpolate1("$", ctx_r2.FormatCost(item_r5.Cost));
110
+ i0.ɵɵadvance(2);
111
+ i0.ɵɵstyleProp("width", item_r5.Proportion * 100, "%");
112
+ } }
113
+ function AnalyticsExecutiveSummaryComponent_Conditional_27_Template(rf, ctx) { if (rf & 1) {
114
+ i0.ɵɵelementStart(0, "div", 13);
115
+ i0.ɵɵtext(1, "No errors in selected period");
116
+ i0.ɵɵelementEnd();
117
+ } }
118
+ function AnalyticsExecutiveSummaryComponent_For_29_Template(rf, ctx) { if (rf & 1) {
119
+ i0.ɵɵelementStart(0, "div", 16)(1, "div", 35);
120
+ i0.ɵɵelement(2, "i", 36);
121
+ i0.ɵɵelementEnd();
122
+ i0.ɵɵelementStart(3, "div", 37)(4, "div", 38);
123
+ i0.ɵɵtext(5);
124
+ i0.ɵɵelementEnd();
125
+ i0.ɵɵelementStart(6, "div", 39);
126
+ i0.ɵɵtext(7);
127
+ i0.ɵɵelementEnd()();
128
+ i0.ɵɵelementStart(8, "div", 40);
129
+ i0.ɵɵtext(9);
130
+ i0.ɵɵelementEnd()();
131
+ } if (rf & 2) {
132
+ const item_r6 = ctx.$implicit;
133
+ i0.ɵɵadvance(5);
134
+ i0.ɵɵtextInterpolate(item_r6.Source);
135
+ i0.ɵɵadvance();
136
+ i0.ɵɵproperty("title", item_r6.ErrorMessage);
137
+ i0.ɵɵadvance();
138
+ i0.ɵɵtextInterpolate(item_r6.ErrorMessage);
139
+ i0.ɵɵadvance(2);
140
+ i0.ɵɵtextInterpolate(item_r6.Count);
141
+ } }
142
+ function AnalyticsExecutiveSummaryComponent_Conditional_30_Template(rf, ctx) { if (rf & 1) {
143
+ const _r7 = i0.ɵɵgetCurrentView();
144
+ i0.ɵɵelementStart(0, "button", 41);
145
+ i0.ɵɵlistener("click", function AnalyticsExecutiveSummaryComponent_Conditional_30_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r7); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.SectionNavigate.emit("error-analysis")); });
146
+ i0.ɵɵtext(1, " View All Errors ");
147
+ i0.ɵɵelement(2, "i", 42);
148
+ i0.ɵɵelementEnd();
149
+ } }
150
+ // ─── Component ─────────────────────────────────────────────────────
151
+ export class AnalyticsExecutiveSummaryComponent {
152
+ // ─── Inputs / Outputs ────────────────────────────────────────────
153
+ _timeRange = '24h';
154
+ set TimeRange(value) {
155
+ if (value !== this._timeRange) {
156
+ this._timeRange = value;
157
+ if (this.isInitialized) {
158
+ this.applyDateRange();
159
+ }
160
+ }
161
+ }
162
+ get TimeRange() { return this._timeRange; }
163
+ Filters = { Models: [], Agents: [], Prompts: [], Statuses: [] };
164
+ TimeRangeChange = new EventEmitter();
165
+ FiltersChange = new EventEmitter();
166
+ SectionNavigate = new EventEmitter();
167
+ // ─── DI ──────────────────────────────────────────────────────────
168
+ instrumentationService = inject(AIInstrumentationService);
169
+ cdr = inject(ChangeDetectorRef);
170
+ // ─── Public State ────────────────────────────────────────────────
171
+ KpiCards = [];
172
+ TrendsData = [];
173
+ TopConsumers = [];
174
+ ErrorHotspots = [];
175
+ IsLoading = false;
176
+ ComparisonEnabled = false;
177
+ PeriodLabel = 'period';
178
+ TimeSeriesConfig = {
179
+ showGrid: true,
180
+ showTooltip: true,
181
+ animationDuration: 300,
182
+ useDualAxis: true
183
+ };
184
+ // ─── Private State ───────────────────────────────────────────────
185
+ destroy$ = new Subject();
186
+ isInitialized = false;
187
+ previousKpis = null;
188
+ // ─── Lifecycle ───────────────────────────────────────────────────
189
+ ngOnInit() {
190
+ this.subscribeToStreams();
191
+ this.applyDateRange();
192
+ this.isInitialized = true;
193
+ }
194
+ ngOnDestroy() {
195
+ this.destroy$.next();
196
+ this.destroy$.complete();
197
+ }
198
+ // ─── Event Handlers ──────────────────────────────────────────────
199
+ OnTimeRangeChange(range) {
200
+ this.TimeRange = range;
201
+ this.TimeRangeChange.emit(range);
202
+ }
203
+ OnFiltersChange(filters) {
204
+ this.Filters = filters;
205
+ this.FiltersChange.emit(filters);
206
+ }
207
+ OnCompareToggled(active) {
208
+ this.ComparisonEnabled = active;
209
+ if (this.ComparisonEnabled) {
210
+ this.loadComparisonData();
211
+ }
212
+ else {
213
+ this.previousKpis = null;
214
+ this.rebuildKpiCards(this.latestKpis);
215
+ }
216
+ }
217
+ OnRefresh() {
218
+ this.instrumentationService.refresh();
219
+ if (this.ComparisonEnabled) {
220
+ this.loadComparisonData();
221
+ }
222
+ }
223
+ OnConsumerClick(item) {
224
+ this.SectionNavigate.emit(item.Type === 'agent' ? 'agent-runs' : 'prompt-runs');
225
+ }
226
+ // ─── Formatting Helpers ──────────────────────────────────────────
227
+ FormatCost(cost) {
228
+ if (cost >= 1000) {
229
+ return (cost / 1000).toFixed(1) + 'K';
230
+ }
231
+ if (cost >= 1) {
232
+ return cost.toFixed(2);
233
+ }
234
+ return cost.toFixed(4);
235
+ }
236
+ // ─── Private: Subscriptions ──────────────────────────────────────
237
+ latestKpis = null;
238
+ subscribeToStreams() {
239
+ this.instrumentationService.isLoading$
240
+ .pipe(takeUntil(this.destroy$))
241
+ .subscribe(loading => {
242
+ this.IsLoading = loading;
243
+ this.cdr.markForCheck();
244
+ });
245
+ this.instrumentationService.kpis$
246
+ .pipe(takeUntil(this.destroy$))
247
+ .subscribe(kpis => {
248
+ this.latestKpis = kpis;
249
+ this.rebuildKpiCards(kpis);
250
+ this.cdr.markForCheck();
251
+ });
252
+ this.instrumentationService.trends$
253
+ .pipe(takeUntil(this.destroy$))
254
+ .subscribe(trends => {
255
+ this.TrendsData = trends;
256
+ this.cdr.markForCheck();
257
+ });
258
+ this.instrumentationService.chartData$
259
+ .pipe(takeUntil(this.destroy$))
260
+ .subscribe(chartData => {
261
+ this.TopConsumers = this.buildTopConsumers(chartData);
262
+ this.ErrorHotspots = this.buildErrorHotspots(chartData);
263
+ this.cdr.markForCheck();
264
+ });
265
+ }
266
+ // ─── Private: Date Range ─────────────────────────────────────────
267
+ applyDateRange() {
268
+ const { start, end } = this.computeDateRange(this.TimeRange);
269
+ this.PeriodLabel = this.getPeriodLabel(this.TimeRange);
270
+ this.instrumentationService.setDateRange(start, end);
271
+ // Explicitly refresh to ensure data loads on first visit
272
+ this.instrumentationService.refresh();
273
+ }
274
+ computeDateRange(range) {
275
+ const end = new Date();
276
+ const start = new Date();
277
+ switch (range) {
278
+ case '1h':
279
+ start.setHours(start.getHours() - 1);
280
+ break;
281
+ case '24h':
282
+ start.setDate(start.getDate() - 1);
283
+ break;
284
+ case '7d':
285
+ start.setDate(start.getDate() - 7);
286
+ break;
287
+ case '30d':
288
+ start.setDate(start.getDate() - 30);
289
+ break;
290
+ case '90d':
291
+ start.setDate(start.getDate() - 90);
292
+ break;
293
+ default:
294
+ start.setDate(start.getDate() - 1);
295
+ }
296
+ return { start, end };
297
+ }
298
+ getPeriodLabel(range) {
299
+ switch (range) {
300
+ case '1h': return 'hour';
301
+ case '24h': return 'day';
302
+ case '7d': return 'week';
303
+ case '30d': return 'month';
304
+ case '90d': return 'quarter';
305
+ default: return 'period';
306
+ }
307
+ }
308
+ // ─── Private: Comparison Data ────────────────────────────────────
309
+ async loadComparisonData() {
310
+ const { start: currentStart, end: currentEnd } = this.computeDateRange(this.TimeRange);
311
+ const durationMs = currentEnd.getTime() - currentStart.getTime();
312
+ const prevEnd = new Date(currentStart.getTime());
313
+ const prevStart = new Date(prevEnd.getTime() - durationMs);
314
+ // Temporarily set date range to previous period, capture KPIs, then restore
315
+ const previousService = new AIInstrumentationService();
316
+ previousService.setDateRange(prevStart, prevEnd);
317
+ // We subscribe to the previousService's kpis$ once
318
+ const sub = previousService.kpis$
319
+ .pipe(takeUntil(this.destroy$))
320
+ .subscribe(kpis => {
321
+ this.previousKpis = kpis;
322
+ this.rebuildKpiCards(this.latestKpis);
323
+ this.cdr.markForCheck();
324
+ sub.unsubscribe();
325
+ });
326
+ }
327
+ // ─── Private: KPI Card Builder ───────────────────────────────────
328
+ rebuildKpiCards(kpis) {
329
+ if (!kpis) {
330
+ this.KpiCards = [];
331
+ return;
332
+ }
333
+ const trends = this.TrendsData;
334
+ this.KpiCards = [
335
+ this.buildKpiCard('Total Executions', this.formatNumber(kpis.totalExecutions), this.extractSparkline(trends, 'executions'), kpis.totalExecutions, this.previousKpis?.totalExecutions ?? null, 'up-is-neutral', 'var(--mj-brand-primary)'),
336
+ this.buildKpiCard('Total Cost', '$' + this.FormatCost(kpis.totalCost), this.extractSparkline(trends, 'cost'), kpis.totalCost, this.previousKpis?.totalCost ?? null, 'down-is-good', 'var(--mj-status-warning)'),
337
+ this.buildKpiCard('Success Rate', (kpis.successRate * 100).toFixed(1) + '%', this.extractSparkline(trends, 'executions'), kpis.successRate, this.previousKpis?.successRate ?? null, 'up-is-good', 'var(--mj-status-success)'),
338
+ this.buildKpiCard('Avg Latency', this.formatLatency(kpis.avgExecutionTime), this.extractSparkline(trends, 'avgTime'), kpis.avgExecutionTime, this.previousKpis?.avgExecutionTime ?? null, 'down-is-good', 'var(--mj-status-info)'),
339
+ this.buildKpiCard('Token Usage', this.formatNumber(kpis.totalTokens), this.extractSparkline(trends, 'tokens'), kpis.totalTokens, this.previousKpis?.totalTokens ?? null, 'up-is-neutral', 'var(--mj-brand-accent, var(--mj-brand-primary))'),
340
+ this.buildKpiCard('Errors', this.formatNumber(Math.round(kpis.errorRate * kpis.totalExecutions)), this.extractSparkline(trends, 'errors'), kpis.errorRate, this.previousKpis?.errorRate ?? null, 'down-is-good', 'var(--mj-status-error)')
341
+ ];
342
+ }
343
+ buildKpiCard(label, value, sparkline, current, previous, goodDirection, borderColor) {
344
+ const { percent, direction } = this.computeDelta(current, previous);
345
+ const isImprovement = this.isDirectionGood(direction, goodDirection);
346
+ return {
347
+ Label: label,
348
+ Value: value,
349
+ SparklineData: sparkline,
350
+ DeltaPercent: percent,
351
+ DeltaDirection: direction,
352
+ IsImprovement: isImprovement,
353
+ BorderColor: borderColor
354
+ };
355
+ }
356
+ computeDelta(current, previous) {
357
+ if (previous == null || previous === 0) {
358
+ return { percent: 0, direction: 'stable' };
359
+ }
360
+ const change = ((current - previous) / previous) * 100;
361
+ if (Math.abs(change) < 0.5) {
362
+ return { percent: 0, direction: 'stable' };
363
+ }
364
+ return {
365
+ percent: Math.abs(change),
366
+ direction: change > 0 ? 'up' : 'down'
367
+ };
368
+ }
369
+ isDirectionGood(direction, rule) {
370
+ if (direction === 'stable')
371
+ return true;
372
+ if (rule === 'up-is-good')
373
+ return direction === 'up';
374
+ if (rule === 'down-is-good')
375
+ return direction === 'down';
376
+ return true; // neutral
377
+ }
378
+ // ─── Private: Sparkline ──────────────────────────────────────────
379
+ extractSparkline(trends, metric) {
380
+ if (!trends || trends.length === 0) {
381
+ return [20, 40, 30, 60, 50, 70, 45]; // placeholder
382
+ }
383
+ // Sample up to 7 evenly-spaced points
384
+ const step = Math.max(1, Math.floor(trends.length / 7));
385
+ const sampled = [];
386
+ for (let i = 0; i < trends.length && sampled.length < 7; i += step) {
387
+ sampled.push(this.getMetricFromTrend(trends[i], metric));
388
+ }
389
+ // Normalize to 0-100 percentage
390
+ const maxVal = Math.max(...sampled, 1);
391
+ return sampled.map(v => Math.max(5, (v / maxVal) * 100));
392
+ }
393
+ getMetricFromTrend(trend, metric) {
394
+ switch (metric) {
395
+ case 'executions': return trend.executions;
396
+ case 'cost': return trend.cost;
397
+ case 'tokens': return trend.tokens;
398
+ case 'avgTime': return trend.avgTime;
399
+ case 'errors': return trend.errors;
400
+ }
401
+ }
402
+ // ─── Private: Top Consumers ──────────────────────────────────────
403
+ buildTopConsumers(chartData) {
404
+ const consumers = [];
405
+ const maxCost = Math.max(...chartData.costByModel.map(m => m.cost), ...chartData.performanceMatrix.map(p => 1), // agents don't have cost here directly
406
+ 1);
407
+ // Add model-based consumers (from prompt runs)
408
+ for (const model of chartData.costByModel.slice(0, 5)) {
409
+ consumers.push({
410
+ Rank: 0,
411
+ Type: 'prompt',
412
+ Name: model.model,
413
+ Cost: model.cost,
414
+ Proportion: 0
415
+ });
416
+ }
417
+ // Sort by cost descending, assign ranks
418
+ consumers.sort((a, b) => b.Cost - a.Cost);
419
+ const topCost = consumers.length > 0 ? consumers[0].Cost : 1;
420
+ return consumers.slice(0, 5).map((c, i) => ({
421
+ ...c,
422
+ Rank: i + 1,
423
+ Proportion: topCost > 0 ? c.Cost / topCost : 0
424
+ }));
425
+ }
426
+ // ─── Private: Error Hotspots ─────────────────────────────────────
427
+ // Error hotspots are computed reactively when KPIs change.
428
+ // Since rawData$ is private on the service, we compute from the
429
+ // kpis errorRate + totalExecutions, and rely on chartData for names.
430
+ // For a richer implementation, the service could expose an errors$ stream.
431
+ // For now, we derive from chartData.performanceMatrix entries with low successRate.
432
+ buildErrorHotspots(chartData) {
433
+ const hotspots = [];
434
+ for (const entry of chartData.performanceMatrix) {
435
+ if (entry.successRate < 1.0) {
436
+ const errorCount = Math.round((1 - entry.successRate) * 10); // approximate
437
+ hotspots.push({
438
+ Source: `${entry.agent} / ${entry.model}`,
439
+ ErrorMessage: `${((1 - entry.successRate) * 100).toFixed(0)}% failure rate (avg ${(entry.avgTime / 1000).toFixed(1)}s)`,
440
+ Count: errorCount
441
+ });
442
+ }
443
+ }
444
+ return hotspots
445
+ .sort((a, b) => b.Count - a.Count)
446
+ .slice(0, 4);
447
+ }
448
+ // ─── Private: Formatting ─────────────────────────────────────────
449
+ formatNumber(value) {
450
+ if (value >= 1_000_000)
451
+ return (value / 1_000_000).toFixed(1) + 'M';
452
+ if (value >= 1_000)
453
+ return (value / 1_000).toFixed(1) + 'K';
454
+ return value.toLocaleString();
455
+ }
456
+ formatLatency(ms) {
457
+ if (ms >= 60_000)
458
+ return (ms / 60_000).toFixed(1) + 'm';
459
+ if (ms >= 1_000)
460
+ return (ms / 1_000).toFixed(1) + 's';
461
+ return Math.round(ms) + 'ms';
462
+ }
463
+ static ɵfac = function AnalyticsExecutiveSummaryComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || AnalyticsExecutiveSummaryComponent)(); };
464
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: AnalyticsExecutiveSummaryComponent, selectors: [["app-analytics-executive-summary"]], inputs: { TimeRange: "TimeRange", Filters: "Filters" }, outputs: { TimeRangeChange: "TimeRangeChange", FiltersChange: "FiltersChange", SectionNavigate: "SectionNavigate" }, standalone: false, decls: 31, vars: 15, consts: [[3, "TimeRangeChange", "FiltersChange", "CompareToggled", "TimeRange", "Filters", "ShowCompareToggle", "ShowModelFilter", "ShowAgentFilter", "ShowPromptFilter", "ShowStatusFilter"], [1, "loading-container"], [1, "kpi-row"], [1, "kpi-card", 3, "border-left-color"], [1, "trends-panel"], ["title", "Execution Trends", 3, "data", "config"], [1, "panels-grid"], [1, "panel"], [1, "panel-header"], [1, "panel-header__title"], [1, "fa-solid", "fa-ranking-star", "panel-header__icon"], [1, "panel-header__subtitle"], [1, "panel-body"], [1, "panel-empty"], ["role", "button", "tabindex", "0", 1, "consumer-item"], [1, "fa-solid", "fa-triangle-exclamation", "panel-header__icon", "panel-header__icon--error"], [1, "error-item"], [1, "view-all-link"], ["text", "Loading executive summary...", "size", "medium"], [1, "kpi-card"], [1, "kpi-label"], [1, "kpi-value"], [1, "kpi-sparkline"], [1, "spark-bar", 3, "height", "background"], [1, "kpi-delta", 3, "kpi-delta--good", "kpi-delta--bad"], [1, "spark-bar"], [1, "kpi-delta"], [1, "kpi-delta__period"], ["role", "button", "tabindex", "0", 1, "consumer-item", 3, "click", "keydown.enter"], [1, "consumer-rank"], [1, "consumer-type-pill"], [1, "consumer-name", 3, "title"], [1, "consumer-cost"], [1, "consumer-bar-container"], [1, "consumer-bar"], [1, "error-icon"], [1, "fa-solid", "fa-circle-exclamation"], [1, "error-info"], [1, "error-source"], [1, "error-message", 3, "title"], [1, "error-count"], [1, "view-all-link", 3, "click"], [1, "fa-solid", "fa-arrow-right"]], template: function AnalyticsExecutiveSummaryComponent_Template(rf, ctx) { if (rf & 1) {
465
+ i0.ɵɵelementStart(0, "app-analytics-filter-bar", 0);
466
+ i0.ɵɵlistener("TimeRangeChange", function AnalyticsExecutiveSummaryComponent_Template_app_analytics_filter_bar_TimeRangeChange_0_listener($event) { return ctx.OnTimeRangeChange($event); })("FiltersChange", function AnalyticsExecutiveSummaryComponent_Template_app_analytics_filter_bar_FiltersChange_0_listener($event) { return ctx.OnFiltersChange($event); })("CompareToggled", function AnalyticsExecutiveSummaryComponent_Template_app_analytics_filter_bar_CompareToggled_0_listener($event) { return ctx.OnCompareToggled($event); });
467
+ i0.ɵɵelementEnd();
468
+ i0.ɵɵconditionalCreate(1, AnalyticsExecutiveSummaryComponent_Conditional_1_Template, 2, 0, "div", 1);
469
+ i0.ɵɵelementStart(2, "div", 2);
470
+ i0.ɵɵrepeaterCreate(3, AnalyticsExecutiveSummaryComponent_For_4_Template, 9, 5, "div", 3, _forTrack0);
471
+ i0.ɵɵelementEnd();
472
+ i0.ɵɵelementStart(5, "div", 4);
473
+ i0.ɵɵelement(6, "app-time-series-chart", 5);
474
+ i0.ɵɵelementEnd();
475
+ i0.ɵɵelementStart(7, "div", 6)(8, "div", 7)(9, "div", 8)(10, "div", 9);
476
+ i0.ɵɵelement(11, "i", 10);
477
+ i0.ɵɵtext(12, " Top Consumers ");
478
+ i0.ɵɵelementEnd();
479
+ i0.ɵɵelementStart(13, "span", 11);
480
+ i0.ɵɵtext(14, "by cost");
481
+ i0.ɵɵelementEnd()();
482
+ i0.ɵɵelementStart(15, "div", 12);
483
+ i0.ɵɵconditionalCreate(16, AnalyticsExecutiveSummaryComponent_Conditional_16_Template, 2, 0, "div", 13);
484
+ i0.ɵɵrepeaterCreate(17, AnalyticsExecutiveSummaryComponent_For_18_Template, 11, 11, "div", 14, _forTrack1);
485
+ i0.ɵɵelementEnd()();
486
+ i0.ɵɵelementStart(19, "div", 7)(20, "div", 8)(21, "div", 9);
487
+ i0.ɵɵelement(22, "i", 15);
488
+ i0.ɵɵtext(23, " Error Hotspots ");
489
+ i0.ɵɵelementEnd();
490
+ i0.ɵɵelementStart(24, "span", 11);
491
+ i0.ɵɵtext(25);
492
+ i0.ɵɵelementEnd()();
493
+ i0.ɵɵelementStart(26, "div", 12);
494
+ i0.ɵɵconditionalCreate(27, AnalyticsExecutiveSummaryComponent_Conditional_27_Template, 2, 0, "div", 13);
495
+ i0.ɵɵrepeaterCreate(28, AnalyticsExecutiveSummaryComponent_For_29_Template, 10, 4, "div", 16, _forTrack2);
496
+ i0.ɵɵconditionalCreate(30, AnalyticsExecutiveSummaryComponent_Conditional_30_Template, 3, 0, "button", 17);
497
+ i0.ɵɵelementEnd()()();
498
+ } if (rf & 2) {
499
+ i0.ɵɵproperty("TimeRange", ctx.TimeRange)("Filters", ctx.Filters)("ShowCompareToggle", true)("ShowModelFilter", false)("ShowAgentFilter", false)("ShowPromptFilter", false)("ShowStatusFilter", false);
500
+ i0.ɵɵadvance();
501
+ i0.ɵɵconditional(ctx.IsLoading && ctx.KpiCards.length === 0 ? 1 : -1);
502
+ i0.ɵɵadvance(2);
503
+ i0.ɵɵrepeater(ctx.KpiCards);
504
+ i0.ɵɵadvance(3);
505
+ i0.ɵɵproperty("data", ctx.TrendsData)("config", ctx.TimeSeriesConfig);
506
+ i0.ɵɵadvance(10);
507
+ i0.ɵɵconditional(ctx.TopConsumers.length === 0 ? 16 : -1);
508
+ i0.ɵɵadvance();
509
+ i0.ɵɵrepeater(ctx.TopConsumers);
510
+ i0.ɵɵadvance(8);
511
+ i0.ɵɵtextInterpolate2(" ", ctx.ErrorHotspots.length, " source", ctx.ErrorHotspots.length !== 1 ? "s" : "", " ");
512
+ i0.ɵɵadvance(2);
513
+ i0.ɵɵconditional(ctx.ErrorHotspots.length === 0 ? 27 : -1);
514
+ i0.ɵɵadvance();
515
+ i0.ɵɵrepeater(ctx.ErrorHotspots);
516
+ i0.ɵɵadvance(2);
517
+ i0.ɵɵconditional(ctx.ErrorHotspots.length > 0 ? 30 : -1);
518
+ } }, dependencies: [i1.LoadingComponent, i2.TimeSeriesChartComponent, i3.AnalyticsFilterBarComponent, i4.DecimalPipe], styles: ["\n\n [_nghost-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 20px;\n padding: 4px;\n }\n\n \n\n .loading-container[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 60px 20px;\n }\n\n \n\n .kpi-row[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));\n gap: 14px;\n }\n .kpi-card[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-left: 4px solid var(--mj-brand-primary);\n border-radius: 12px;\n padding: 16px 18px;\n display: flex;\n flex-direction: column;\n gap: 6px;\n transition: all 0.25s ease;\n }\n .kpi-card[_ngcontent-%COMP%]:hover {\n transform: translateY(-2px);\n box-shadow: 0 6px 20px color-mix(in srgb, var(--mj-text-primary) 8%, transparent);\n }\n .kpi-label[_ngcontent-%COMP%] {\n font-size: 10px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.6px;\n color: var(--mj-text-muted);\n }\n .kpi-value[_ngcontent-%COMP%] {\n font-size: 24px;\n font-weight: 700;\n color: var(--mj-text-primary);\n line-height: 1.1;\n letter-spacing: -0.02em;\n }\n\n \n\n .kpi-sparkline[_ngcontent-%COMP%] {\n display: flex;\n align-items: flex-end;\n gap: 2px;\n height: 24px;\n margin-top: 4px;\n }\n .spark-bar[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 4px;\n max-width: 8px;\n border-radius: 1px;\n opacity: 0.35;\n transition: opacity 0.15s ease;\n }\n .kpi-card[_ngcontent-%COMP%]:hover .spark-bar[_ngcontent-%COMP%] {\n opacity: 0.55;\n }\n\n \n\n .kpi-delta[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 4px;\n font-size: 11px;\n font-weight: 600;\n margin-top: 2px;\n }\n .kpi-delta--good[_ngcontent-%COMP%] {\n color: var(--mj-status-success);\n }\n .kpi-delta--bad[_ngcontent-%COMP%] {\n color: var(--mj-status-error);\n }\n .kpi-delta__period[_ngcontent-%COMP%] {\n font-weight: 400;\n color: var(--mj-text-disabled);\n }\n\n \n\n .trends-panel[_ngcontent-%COMP%] {\n height: 320px;\n border-radius: 12px;\n overflow: hidden;\n }\n\n \n\n .panels-grid[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 16px;\n }\n .panel[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n overflow: hidden;\n }\n .panel-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 14px 18px;\n border-bottom: 1px solid var(--mj-border-subtle);\n }\n .panel-header__title[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 14px;\n font-weight: 600;\n color: var(--mj-text-primary);\n }\n .panel-header__icon[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n font-size: 14px;\n }\n .panel-header__icon--error[_ngcontent-%COMP%] {\n color: var(--mj-status-warning);\n }\n .panel-header__subtitle[_ngcontent-%COMP%] {\n font-size: 12px;\n color: var(--mj-text-muted);\n }\n .panel-body[_ngcontent-%COMP%] {\n padding: 8px 0;\n }\n .panel-empty[_ngcontent-%COMP%] {\n padding: 24px 18px;\n text-align: center;\n font-size: 13px;\n color: var(--mj-text-disabled);\n }\n\n \n\n .consumer-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 10px 18px;\n cursor: pointer;\n transition: background 0.15s ease;\n }\n .consumer-item[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n }\n .consumer-rank[_ngcontent-%COMP%] {\n width: 22px;\n height: 22px;\n border-radius: 6px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 11px;\n font-weight: 700;\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-muted);\n flex-shrink: 0;\n }\n .consumer-rank--top[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-brand-primary) 15%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n }\n .consumer-type-pill[_ngcontent-%COMP%] {\n font-size: 10px;\n font-weight: 600;\n text-transform: uppercase;\n padding: 2px 8px;\n border-radius: 4px;\n background: color-mix(in srgb, var(--mj-status-info) 12%, var(--mj-bg-surface));\n color: var(--mj-status-info);\n flex-shrink: 0;\n }\n .consumer-type-pill--agent[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-status-success) 12%, var(--mj-bg-surface));\n color: var(--mj-status-success);\n }\n .consumer-name[_ngcontent-%COMP%] {\n flex: 1;\n font-size: 13px;\n font-weight: 500;\n color: var(--mj-text-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .consumer-cost[_ngcontent-%COMP%] {\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-text-secondary);\n flex-shrink: 0;\n }\n .consumer-bar-container[_ngcontent-%COMP%] {\n width: 60px;\n height: 6px;\n border-radius: 3px;\n background: var(--mj-bg-surface-sunken);\n overflow: hidden;\n flex-shrink: 0;\n }\n .consumer-bar[_ngcontent-%COMP%] {\n height: 100%;\n border-radius: 3px;\n background: var(--mj-brand-primary);\n transition: width 0.3s ease;\n }\n\n \n\n .error-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n padding: 10px 18px;\n }\n .error-icon[_ngcontent-%COMP%] {\n color: var(--mj-status-error);\n font-size: 14px;\n margin-top: 2px;\n flex-shrink: 0;\n }\n .error-info[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 0;\n }\n .error-source[_ngcontent-%COMP%] {\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin-bottom: 2px;\n }\n .error-message[_ngcontent-%COMP%] {\n font-size: 12px;\n color: var(--mj-text-muted);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .error-count[_ngcontent-%COMP%] {\n font-size: 13px;\n font-weight: 700;\n color: var(--mj-status-error);\n flex-shrink: 0;\n min-width: 28px;\n height: 28px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 8px;\n background: color-mix(in srgb, var(--mj-status-error) 10%, var(--mj-bg-surface));\n }\n .view-all-link[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n margin: 8px 18px 10px;\n padding: 0;\n border: none;\n background: none;\n font-size: 12px;\n font-weight: 600;\n color: var(--mj-brand-primary);\n cursor: pointer;\n transition: color 0.15s ease;\n }\n .view-all-link[_ngcontent-%COMP%]:hover {\n color: var(--mj-brand-primary-hover);\n }\n\n \n\n @media (max-width: 1200px) {\n .panels-grid[_ngcontent-%COMP%] {\n grid-template-columns: 1fr;\n }\n }\n @media (max-width: 768px) {\n .kpi-row[_ngcontent-%COMP%] {\n grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));\n }\n .trends-panel[_ngcontent-%COMP%] {\n height: 260px;\n }\n }"] });
519
+ }
520
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(AnalyticsExecutiveSummaryComponent, [{
521
+ type: Component,
522
+ args: [{ standalone: false, selector: 'app-analytics-executive-summary', template: `
523
+ <!-- Filter Bar -->
524
+ <app-analytics-filter-bar
525
+ [TimeRange]="TimeRange"
526
+ [Filters]="Filters"
527
+ [ShowCompareToggle]="true"
528
+ [ShowModelFilter]="false"
529
+ [ShowAgentFilter]="false"
530
+ [ShowPromptFilter]="false"
531
+ [ShowStatusFilter]="false"
532
+ (TimeRangeChange)="OnTimeRangeChange($event)"
533
+ (FiltersChange)="OnFiltersChange($event)"
534
+ (CompareToggled)="OnCompareToggled($event)"
535
+ ></app-analytics-filter-bar>
536
+
537
+ @if (IsLoading && KpiCards.length === 0) {
538
+ <div class="loading-container">
539
+ <mj-loading text="Loading executive summary..." size="medium"></mj-loading>
540
+ </div>
541
+ }
542
+
543
+ <!-- KPI Row -->
544
+ <div class="kpi-row">
545
+ @for (card of KpiCards; track card.Label) {
546
+ <div class="kpi-card" [style.border-left-color]="card.BorderColor">
547
+ <div class="kpi-label">{{ card.Label }}</div>
548
+ <div class="kpi-value">{{ card.Value }}</div>
549
+ <div class="kpi-sparkline">
550
+ @for (bar of card.SparklineData; track $index) {
551
+ <div
552
+ class="spark-bar"
553
+ [style.height.%]="bar"
554
+ [style.background]="card.BorderColor"
555
+ ></div>
556
+ }
557
+ </div>
558
+ @if (ComparisonEnabled && card.DeltaDirection !== 'stable') {
559
+ <div class="kpi-delta" [class.kpi-delta--good]="card.IsImprovement" [class.kpi-delta--bad]="!card.IsImprovement">
560
+ <i [class]="card.DeltaDirection === 'up' ? 'fa-solid fa-arrow-up' : 'fa-solid fa-arrow-down'"></i>
561
+ {{ card.DeltaPercent | number:'1.1-1' }}%
562
+ <span class="kpi-delta__period">vs prev {{ PeriodLabel }}</span>
563
+ </div>
564
+ }
565
+ </div>
566
+ }
567
+ </div>
568
+
569
+ <!-- Execution Trends Chart -->
570
+ <div class="trends-panel">
571
+ <app-time-series-chart
572
+ [data]="TrendsData"
573
+ title="Execution Trends"
574
+ [config]="TimeSeriesConfig"
575
+ ></app-time-series-chart>
576
+ </div>
577
+
578
+ <!-- Two-Column Panels -->
579
+ <div class="panels-grid">
580
+ <!-- Top Consumers -->
581
+ <div class="panel">
582
+ <div class="panel-header">
583
+ <div class="panel-header__title">
584
+ <i class="fa-solid fa-ranking-star panel-header__icon"></i>
585
+ Top Consumers
586
+ </div>
587
+ <span class="panel-header__subtitle">by cost</span>
588
+ </div>
589
+ <div class="panel-body">
590
+ @if (TopConsumers.length === 0) {
591
+ <div class="panel-empty">No data for selected period</div>
592
+ }
593
+ @for (item of TopConsumers; track item.Name) {
594
+ <div
595
+ class="consumer-item"
596
+ (click)="OnConsumerClick(item)"
597
+ role="button"
598
+ tabindex="0"
599
+ (keydown.enter)="OnConsumerClick(item)"
600
+ >
601
+ <div
602
+ class="consumer-rank"
603
+ [class.consumer-rank--top]="item.Rank <= 3"
604
+ >{{ item.Rank }}</div>
605
+ <div
606
+ class="consumer-type-pill"
607
+ [class.consumer-type-pill--agent]="item.Type === 'agent'"
608
+ >{{ item.Type }}</div>
609
+ <div class="consumer-name" [title]="item.Name">{{ item.Name }}</div>
610
+ <div class="consumer-cost">\${{ FormatCost(item.Cost) }}</div>
611
+ <div class="consumer-bar-container">
612
+ <div
613
+ class="consumer-bar"
614
+ [style.width.%]="item.Proportion * 100"
615
+ ></div>
616
+ </div>
617
+ </div>
618
+ }
619
+ </div>
620
+ </div>
621
+
622
+ <!-- Error Hotspots -->
623
+ <div class="panel">
624
+ <div class="panel-header">
625
+ <div class="panel-header__title">
626
+ <i class="fa-solid fa-triangle-exclamation panel-header__icon panel-header__icon--error"></i>
627
+ Error Hotspots
628
+ </div>
629
+ <span class="panel-header__subtitle">
630
+ {{ ErrorHotspots.length }} source{{ ErrorHotspots.length !== 1 ? 's' : '' }}
631
+ </span>
632
+ </div>
633
+ <div class="panel-body">
634
+ @if (ErrorHotspots.length === 0) {
635
+ <div class="panel-empty">No errors in selected period</div>
636
+ }
637
+ @for (item of ErrorHotspots; track item.Source) {
638
+ <div class="error-item">
639
+ <div class="error-icon">
640
+ <i class="fa-solid fa-circle-exclamation"></i>
641
+ </div>
642
+ <div class="error-info">
643
+ <div class="error-source">{{ item.Source }}</div>
644
+ <div class="error-message" [title]="item.ErrorMessage">{{ item.ErrorMessage }}</div>
645
+ </div>
646
+ <div class="error-count">{{ item.Count }}</div>
647
+ </div>
648
+ }
649
+ @if (ErrorHotspots.length > 0) {
650
+ <button class="view-all-link" (click)="SectionNavigate.emit('error-analysis')">
651
+ View All Errors <i class="fa-solid fa-arrow-right"></i>
652
+ </button>
653
+ }
654
+ </div>
655
+ </div>
656
+ </div>
657
+ `, styles: ["\n /* \u2500\u2500\u2500 Host \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n :host {\n display: flex;\n flex-direction: column;\n gap: 20px;\n padding: 4px;\n }\n\n /* \u2500\u2500\u2500 Loading \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .loading-container {\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 60px 20px;\n }\n\n /* \u2500\u2500\u2500 KPI Row \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .kpi-row {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));\n gap: 14px;\n }\n .kpi-card {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-left: 4px solid var(--mj-brand-primary);\n border-radius: 12px;\n padding: 16px 18px;\n display: flex;\n flex-direction: column;\n gap: 6px;\n transition: all 0.25s ease;\n }\n .kpi-card:hover {\n transform: translateY(-2px);\n box-shadow: 0 6px 20px color-mix(in srgb, var(--mj-text-primary) 8%, transparent);\n }\n .kpi-label {\n font-size: 10px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.6px;\n color: var(--mj-text-muted);\n }\n .kpi-value {\n font-size: 24px;\n font-weight: 700;\n color: var(--mj-text-primary);\n line-height: 1.1;\n letter-spacing: -0.02em;\n }\n\n /* \u2500\u2500\u2500 Sparkline \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .kpi-sparkline {\n display: flex;\n align-items: flex-end;\n gap: 2px;\n height: 24px;\n margin-top: 4px;\n }\n .spark-bar {\n flex: 1;\n min-width: 4px;\n max-width: 8px;\n border-radius: 1px;\n opacity: 0.35;\n transition: opacity 0.15s ease;\n }\n .kpi-card:hover .spark-bar {\n opacity: 0.55;\n }\n\n /* \u2500\u2500\u2500 Delta Badge \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .kpi-delta {\n display: flex;\n align-items: center;\n gap: 4px;\n font-size: 11px;\n font-weight: 600;\n margin-top: 2px;\n }\n .kpi-delta--good {\n color: var(--mj-status-success);\n }\n .kpi-delta--bad {\n color: var(--mj-status-error);\n }\n .kpi-delta__period {\n font-weight: 400;\n color: var(--mj-text-disabled);\n }\n\n /* \u2500\u2500\u2500 Trends Panel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .trends-panel {\n height: 320px;\n border-radius: 12px;\n overflow: hidden;\n }\n\n /* \u2500\u2500\u2500 Panels Grid \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .panels-grid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 16px;\n }\n .panel {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n overflow: hidden;\n }\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 14px 18px;\n border-bottom: 1px solid var(--mj-border-subtle);\n }\n .panel-header__title {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 14px;\n font-weight: 600;\n color: var(--mj-text-primary);\n }\n .panel-header__icon {\n color: var(--mj-brand-primary);\n font-size: 14px;\n }\n .panel-header__icon--error {\n color: var(--mj-status-warning);\n }\n .panel-header__subtitle {\n font-size: 12px;\n color: var(--mj-text-muted);\n }\n .panel-body {\n padding: 8px 0;\n }\n .panel-empty {\n padding: 24px 18px;\n text-align: center;\n font-size: 13px;\n color: var(--mj-text-disabled);\n }\n\n /* \u2500\u2500\u2500 Consumer Item \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .consumer-item {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 10px 18px;\n cursor: pointer;\n transition: background 0.15s ease;\n }\n .consumer-item:hover {\n background: var(--mj-bg-surface-hover);\n }\n .consumer-rank {\n width: 22px;\n height: 22px;\n border-radius: 6px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 11px;\n font-weight: 700;\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-muted);\n flex-shrink: 0;\n }\n .consumer-rank--top {\n background: color-mix(in srgb, var(--mj-brand-primary) 15%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n }\n .consumer-type-pill {\n font-size: 10px;\n font-weight: 600;\n text-transform: uppercase;\n padding: 2px 8px;\n border-radius: 4px;\n background: color-mix(in srgb, var(--mj-status-info) 12%, var(--mj-bg-surface));\n color: var(--mj-status-info);\n flex-shrink: 0;\n }\n .consumer-type-pill--agent {\n background: color-mix(in srgb, var(--mj-status-success) 12%, var(--mj-bg-surface));\n color: var(--mj-status-success);\n }\n .consumer-name {\n flex: 1;\n font-size: 13px;\n font-weight: 500;\n color: var(--mj-text-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .consumer-cost {\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-text-secondary);\n flex-shrink: 0;\n }\n .consumer-bar-container {\n width: 60px;\n height: 6px;\n border-radius: 3px;\n background: var(--mj-bg-surface-sunken);\n overflow: hidden;\n flex-shrink: 0;\n }\n .consumer-bar {\n height: 100%;\n border-radius: 3px;\n background: var(--mj-brand-primary);\n transition: width 0.3s ease;\n }\n\n /* \u2500\u2500\u2500 Error Item \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .error-item {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n padding: 10px 18px;\n }\n .error-icon {\n color: var(--mj-status-error);\n font-size: 14px;\n margin-top: 2px;\n flex-shrink: 0;\n }\n .error-info {\n flex: 1;\n min-width: 0;\n }\n .error-source {\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin-bottom: 2px;\n }\n .error-message {\n font-size: 12px;\n color: var(--mj-text-muted);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .error-count {\n font-size: 13px;\n font-weight: 700;\n color: var(--mj-status-error);\n flex-shrink: 0;\n min-width: 28px;\n height: 28px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 8px;\n background: color-mix(in srgb, var(--mj-status-error) 10%, var(--mj-bg-surface));\n }\n .view-all-link {\n display: flex;\n align-items: center;\n gap: 6px;\n margin: 8px 18px 10px;\n padding: 0;\n border: none;\n background: none;\n font-size: 12px;\n font-weight: 600;\n color: var(--mj-brand-primary);\n cursor: pointer;\n transition: color 0.15s ease;\n }\n .view-all-link:hover {\n color: var(--mj-brand-primary-hover);\n }\n\n /* \u2500\u2500\u2500 Responsive \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n @media (max-width: 1200px) {\n .panels-grid {\n grid-template-columns: 1fr;\n }\n }\n @media (max-width: 768px) {\n .kpi-row {\n grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));\n }\n .trends-panel {\n height: 260px;\n }\n }\n "] }]
658
+ }], null, { TimeRange: [{
659
+ type: Input
660
+ }], Filters: [{
661
+ type: Input
662
+ }], TimeRangeChange: [{
663
+ type: Output
664
+ }], FiltersChange: [{
665
+ type: Output
666
+ }], SectionNavigate: [{
667
+ type: Output
668
+ }] }); })();
669
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(AnalyticsExecutiveSummaryComponent, { className: "AnalyticsExecutiveSummaryComponent", filePath: "src/AI/components/analytics/executive-summary/executive-summary.component.ts", lineNumber: 481 }); })();
670
+ export function LoadAnalyticsExecutiveSummary() {
671
+ // Prevents tree-shaking
672
+ }
673
+ //# sourceMappingURL=executive-summary.component.js.map