@smartnet360/svelte-components 0.0.102 → 0.0.104
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/apps/antenna-pattern/index.d.ts +1 -0
- package/dist/apps/antenna-pattern/index.js +1 -0
- package/dist/apps/antenna-pattern/utils/load-static-antennas.d.ts +17 -0
- package/dist/apps/antenna-pattern/utils/load-static-antennas.js +83 -0
- package/dist/apps/site-check/SiteCheck.svelte +13 -81
- package/dist/apps/site-check/SiteCheckControls.svelte +0 -7
- package/dist/apps/site-check/helper.js +0 -33
- package/dist/apps/site-check/transforms.js +15 -65
- package/dist/core/CellTable/CellTable.svelte +456 -0
- package/dist/core/CellTable/CellTable.svelte.d.ts +27 -0
- package/dist/core/CellTable/CellTablePanel.svelte +211 -0
- package/dist/core/CellTable/CellTablePanel.svelte.d.ts +49 -0
- package/dist/core/CellTable/CellTableToolbar.svelte +218 -0
- package/dist/core/CellTable/CellTableToolbar.svelte.d.ts +32 -0
- package/dist/core/CellTable/column-config.d.ts +63 -0
- package/dist/core/CellTable/column-config.js +465 -0
- package/dist/core/CellTable/index.d.ts +10 -0
- package/dist/core/CellTable/index.js +11 -0
- package/dist/core/CellTable/types.d.ts +166 -0
- package/dist/core/CellTable/types.js +6 -0
- package/dist/core/Charts/ChartCard.svelte +118 -31
- package/dist/core/Charts/ChartCard.svelte.d.ts +2 -0
- package/dist/core/Charts/ChartComponent.svelte +8 -31
- package/dist/core/Charts/data-processor.js +1 -19
- package/dist/core/CoverageMap/ai/AITools.d.ts +117 -0
- package/dist/core/CoverageMap/ai/AITools.js +380 -0
- package/dist/core/CoverageMap/core/CoverageCalculator.d.ts +138 -0
- package/dist/core/CoverageMap/core/CoverageCalculator.js +375 -0
- package/dist/core/CoverageMap/core/GridCalculator.d.ts +115 -0
- package/dist/core/CoverageMap/core/GridCalculator.js +484 -0
- package/dist/core/CoverageMap/core/PathLossModels.d.ts +253 -0
- package/dist/core/CoverageMap/core/PathLossModels.js +380 -0
- package/dist/core/CoverageMap/core/SignalProcessor.d.ts +288 -0
- package/dist/core/CoverageMap/core/SignalProcessor.js +424 -0
- package/dist/core/CoverageMap/data/AntennaStore.d.ts +165 -0
- package/dist/core/CoverageMap/data/AntennaStore.js +327 -0
- package/dist/core/CoverageMap/data/SiteStore.d.ts +155 -0
- package/dist/core/CoverageMap/data/SiteStore.js +355 -0
- package/dist/core/CoverageMap/index.d.ts +74 -0
- package/dist/core/CoverageMap/index.js +103 -0
- package/dist/core/CoverageMap/types.d.ts +252 -0
- package/dist/core/CoverageMap/types.js +7 -0
- package/dist/core/CoverageMap/utils/geoUtils.d.ts +223 -0
- package/dist/core/CoverageMap/utils/geoUtils.js +374 -0
- package/dist/core/CoverageMap/utils/rfUtils.d.ts +329 -0
- package/dist/core/CoverageMap/utils/rfUtils.js +434 -0
- package/dist/core/CoverageMap/visualization/ColorSchemes.d.ts +149 -0
- package/dist/core/CoverageMap/visualization/ColorSchemes.js +377 -0
- package/dist/core/TreeView/index.d.ts +4 -4
- package/dist/core/TreeView/index.js +5 -5
- package/dist/core/TreeView/tree-utils.d.ts +12 -0
- package/dist/core/TreeView/tree-utils.js +115 -6
- package/dist/core/TreeView/tree.store.svelte.d.ts +94 -0
- package/dist/core/TreeView/tree.store.svelte.js +274 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.js +2 -0
- package/dist/map-v2/features/cells/controls/CellFilterControl.svelte +16 -27
- package/dist/map-v2/features/repeaters/controls/RepeaterFilterControl.svelte +33 -42
- package/dist/map-v2/features/sites/controls/SiteFilterControl.svelte +12 -19
- package/dist/map-v3/core/components/Map.svelte +4 -0
- package/dist/map-v3/core/stores/map.store.svelte.js +2 -0
- package/dist/map-v3/features/cells/components/CellFilterControl.svelte +24 -30
- package/dist/map-v3/features/coverage/index.d.ts +12 -0
- package/dist/map-v3/features/coverage/index.js +16 -0
- package/dist/map-v3/features/coverage/layers/CoverageLayer.svelte +198 -0
- package/dist/map-v3/features/coverage/layers/CoverageLayer.svelte.d.ts +10 -0
- package/dist/map-v3/features/coverage/logic/coloring.d.ts +28 -0
- package/dist/map-v3/features/coverage/logic/coloring.js +77 -0
- package/dist/map-v3/features/coverage/logic/geometry.d.ts +33 -0
- package/dist/map-v3/features/coverage/logic/geometry.js +112 -0
- package/dist/map-v3/features/coverage/stores/coverage.data.svelte.d.ts +46 -0
- package/dist/map-v3/features/coverage/stores/coverage.data.svelte.js +95 -0
- package/dist/map-v3/features/coverage/stores/coverage.display.svelte.d.ts +33 -0
- package/dist/map-v3/features/coverage/stores/coverage.display.svelte.js +90 -0
- package/dist/map-v3/features/coverage/types.d.ts +52 -0
- package/dist/map-v3/features/coverage/types.js +7 -0
- package/dist/map-v3/features/repeaters/components/RepeaterFilterControl.svelte +14 -20
- package/dist/map-v3/features/sites/components/SiteFilterControl.svelte +23 -33
- package/dist/map-v3/index.d.ts +4 -0
- package/dist/map-v3/index.js +5 -0
- package/package.json +4 -3
- package/dist/apps/site-check/transforms-old.d.ts +0 -56
- package/dist/apps/site-check/transforms-old.js +0 -273
- package/dist/core/TreeView/tree.store.d.ts +0 -10
- package/dist/core/TreeView/tree.store.js +0 -320
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
import { createTimeSeriesTraceWithMA, getYAxisTitle, createDefaultPlotlyLayout } from './data-utils.js';
|
|
8
8
|
import { adaptPlotlyLayout, addMarkersToLayout, type ContainerSize } from './adapt.js';
|
|
9
9
|
import { getKPIValues, type ProcessedChartData } from './data-processor.js';
|
|
10
|
-
import { log } from '../logger';
|
|
11
10
|
import { checkHealth, getMessage } from '../FeatureRegistry';
|
|
12
11
|
|
|
13
12
|
interface Props {
|
|
@@ -26,6 +25,8 @@
|
|
|
26
25
|
runtimeShowMarkers?: boolean; // Runtime control for showing markers (default: true)
|
|
27
26
|
runtimeShowLegend?: boolean; // Runtime control for showing legend (default: true)
|
|
28
27
|
runtimeHoverMode?: HoverMode; // Runtime override for hover mode from global controls
|
|
28
|
+
renderDelay?: number; // Delay in ms before rendering (for staggered loading)
|
|
29
|
+
lazyRender?: boolean; // Enable Intersection Observer-based lazy rendering
|
|
29
30
|
onchartcontextmenu?: (detail: {
|
|
30
31
|
chart: ChartModel;
|
|
31
32
|
sectionId?: string;
|
|
@@ -34,12 +35,13 @@
|
|
|
34
35
|
}) => void;
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
let { chart, processedData, markers, plotlyLayout, enableAdaptation = true, sectionId, sectionMovingAverage, layoutMovingAverage, layoutHoverMode, layoutColoredHover = true, runtimeMAOverride, runtimeShowOriginal, runtimeShowMarkers = true, runtimeShowLegend = true, runtimeHoverMode, onchartcontextmenu }: Props = $props();
|
|
38
|
+
let { chart, processedData, markers, plotlyLayout, enableAdaptation = true, sectionId, sectionMovingAverage, layoutMovingAverage, layoutHoverMode, layoutColoredHover = true, runtimeMAOverride, runtimeShowOriginal, runtimeShowMarkers = true, runtimeShowLegend = true, runtimeHoverMode, renderDelay = 0, lazyRender = false, onchartcontextmenu }: Props = $props();
|
|
38
39
|
|
|
39
40
|
// Chart container div and state
|
|
40
41
|
let chartDiv: HTMLElement;
|
|
41
42
|
let containerSize = $state<ContainerSize>({ width: 0, height: 0 });
|
|
42
43
|
let chartInitialized = $state(false); // Track if chart has been created
|
|
44
|
+
let isVisible = $state(!lazyRender); // For lazy rendering: start visible if not lazy
|
|
43
45
|
let isHealthy = $state(checkHealth('charts'));
|
|
44
46
|
|
|
45
47
|
function handleContextMenu(event: MouseEvent) {
|
|
@@ -241,22 +243,9 @@
|
|
|
241
243
|
// Use Plotly.react() for updates (preserves zoom/pan) or newPlot for initial render
|
|
242
244
|
if (chartInitialized) {
|
|
243
245
|
// Update existing chart - much faster, preserves user interactions
|
|
244
|
-
log('🔄 Updating chart with Plotly.react', {
|
|
245
|
-
chartTitle: chart.title,
|
|
246
|
-
hoverMode: effectiveHoverMode,
|
|
247
|
-
layoutHoverMode: finalLayout.hovermode
|
|
248
|
-
});
|
|
249
246
|
Plotly.react(chartDiv, traces, finalLayout, config);
|
|
250
247
|
} else {
|
|
251
248
|
// Initial chart creation
|
|
252
|
-
log('📊 Creating new chart with Plotly.newPlot', {
|
|
253
|
-
chartTitle: chart.title,
|
|
254
|
-
traces: traces.length,
|
|
255
|
-
leftKPIs: chart.yLeft.length,
|
|
256
|
-
rightKPIs: chart.yRight.length,
|
|
257
|
-
hoverMode: effectiveHoverMode,
|
|
258
|
-
layoutHoverMode: finalLayout.hovermode
|
|
259
|
-
});
|
|
260
249
|
Plotly.newPlot(chartDiv, traces, finalLayout, config);
|
|
261
250
|
chartInitialized = true;
|
|
262
251
|
}
|
|
@@ -270,29 +259,54 @@
|
|
|
270
259
|
}
|
|
271
260
|
|
|
272
261
|
onMount(() => {
|
|
273
|
-
|
|
274
|
-
log('📈 ChartCard mounted', {
|
|
275
|
-
chartTitle: chart.title,
|
|
276
|
-
leftKPIs: chart.yLeft.length,
|
|
277
|
-
rightKPIs: chart.yRight.length
|
|
278
|
-
});
|
|
279
|
-
|
|
280
262
|
// Initial container size measurement
|
|
281
263
|
if (chartDiv) {
|
|
282
264
|
const rect = chartDiv.getBoundingClientRect();
|
|
283
265
|
containerSize.width = rect.width;
|
|
284
266
|
containerSize.height = rect.height;
|
|
285
|
-
|
|
286
|
-
log('📐 Initial container size', {
|
|
287
|
-
chartTitle: chart.title,
|
|
288
|
-
width: rect.width,
|
|
289
|
-
height: rect.height
|
|
290
|
-
});
|
|
291
267
|
}
|
|
292
|
-
|
|
268
|
+
|
|
269
|
+
if (!isHealthy) {
|
|
293
270
|
return;
|
|
294
271
|
}
|
|
295
|
-
|
|
272
|
+
|
|
273
|
+
// Intersection Observer for lazy rendering
|
|
274
|
+
let intersectionObserver: IntersectionObserver | null = null;
|
|
275
|
+
|
|
276
|
+
const doRender = () => {
|
|
277
|
+
if (renderDelay > 0) {
|
|
278
|
+
// Staggered rendering: delay based on chart index
|
|
279
|
+
setTimeout(() => {
|
|
280
|
+
renderChart();
|
|
281
|
+
}, renderDelay);
|
|
282
|
+
} else {
|
|
283
|
+
renderChart();
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
if (lazyRender && chartDiv) {
|
|
288
|
+
// Lazy rendering: only render when chart enters viewport
|
|
289
|
+
intersectionObserver = new IntersectionObserver(
|
|
290
|
+
(entries) => {
|
|
291
|
+
const [entry] = entries;
|
|
292
|
+
if (entry.isIntersecting && !chartInitialized) {
|
|
293
|
+
isVisible = true;
|
|
294
|
+
doRender();
|
|
295
|
+
// Disconnect after first render - chart stays rendered
|
|
296
|
+
intersectionObserver?.disconnect();
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
// Render 200px before entering viewport for smooth experience
|
|
301
|
+
rootMargin: '200px',
|
|
302
|
+
threshold: 0
|
|
303
|
+
}
|
|
304
|
+
);
|
|
305
|
+
intersectionObserver.observe(chartDiv);
|
|
306
|
+
} else {
|
|
307
|
+
// Immediate or staggered rendering (no lazy)
|
|
308
|
+
doRender();
|
|
309
|
+
}
|
|
296
310
|
|
|
297
311
|
// Set up ResizeObserver with debouncing to prevent excessive re-renders
|
|
298
312
|
if (chartDiv && window.ResizeObserver) {
|
|
@@ -330,12 +344,13 @@
|
|
|
330
344
|
|
|
331
345
|
resizeObserver.observe(chartDiv);
|
|
332
346
|
|
|
333
|
-
// Clean up
|
|
347
|
+
// Clean up observers on component destroy
|
|
334
348
|
return () => {
|
|
335
349
|
if (resizeTimeout) {
|
|
336
350
|
clearTimeout(resizeTimeout);
|
|
337
351
|
}
|
|
338
352
|
resizeObserver.disconnect();
|
|
353
|
+
intersectionObserver?.disconnect();
|
|
339
354
|
|
|
340
355
|
// Clean up Plotly chart
|
|
341
356
|
if (chartDiv && chartInitialized) {
|
|
@@ -344,6 +359,15 @@
|
|
|
344
359
|
}
|
|
345
360
|
};
|
|
346
361
|
}
|
|
362
|
+
|
|
363
|
+
// If no ResizeObserver, still clean up intersection observer
|
|
364
|
+
return () => {
|
|
365
|
+
intersectionObserver?.disconnect();
|
|
366
|
+
if (chartDiv && chartInitialized) {
|
|
367
|
+
Plotly.purge(chartDiv);
|
|
368
|
+
chartInitialized = false;
|
|
369
|
+
}
|
|
370
|
+
};
|
|
347
371
|
});
|
|
348
372
|
|
|
349
373
|
// React to prop changes - debounce re-renders for better performance
|
|
@@ -368,9 +392,19 @@
|
|
|
368
392
|
</script>
|
|
369
393
|
|
|
370
394
|
<div class="chart-card" role="group" oncontextmenu={handleContextMenu}>
|
|
395
|
+
{#if !isVisible || !chartInitialized}
|
|
396
|
+
<!-- Loading placeholder for lazy/staggered rendering -->
|
|
397
|
+
<div class="chart-placeholder">
|
|
398
|
+
<div class="placeholder-content">
|
|
399
|
+
<div class="placeholder-spinner"></div>
|
|
400
|
+
<span class="placeholder-text">{chart.title}</span>
|
|
401
|
+
</div>
|
|
402
|
+
</div>
|
|
403
|
+
{/if}
|
|
371
404
|
<div
|
|
372
405
|
bind:this={chartDiv}
|
|
373
406
|
class="chart-container"
|
|
407
|
+
class:hidden={!isVisible}
|
|
374
408
|
></div>
|
|
375
409
|
</div>
|
|
376
410
|
|
|
@@ -387,6 +421,7 @@
|
|
|
387
421
|
overflow: hidden; /* Prevent content overflow */
|
|
388
422
|
box-sizing: border-box; /* Include padding in size calculations */
|
|
389
423
|
contain: layout style paint; /* Browser optimization: isolate rendering from rest of page */
|
|
424
|
+
position: relative; /* For placeholder positioning */
|
|
390
425
|
}
|
|
391
426
|
|
|
392
427
|
.chart-card:hover {
|
|
@@ -402,4 +437,56 @@
|
|
|
402
437
|
box-sizing: border-box; /* Include padding in size calculations */
|
|
403
438
|
overflow: hidden; /* Prevent Plotly overflow */
|
|
404
439
|
}
|
|
440
|
+
|
|
441
|
+
.chart-container.hidden {
|
|
442
|
+
visibility: hidden;
|
|
443
|
+
position: absolute;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/* Loading placeholder styles */
|
|
447
|
+
.chart-placeholder {
|
|
448
|
+
position: absolute;
|
|
449
|
+
top: 0;
|
|
450
|
+
left: 0;
|
|
451
|
+
right: 0;
|
|
452
|
+
bottom: 0;
|
|
453
|
+
display: flex;
|
|
454
|
+
align-items: center;
|
|
455
|
+
justify-content: center;
|
|
456
|
+
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
|
457
|
+
z-index: 1;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
.placeholder-content {
|
|
461
|
+
display: flex;
|
|
462
|
+
flex-direction: column;
|
|
463
|
+
align-items: center;
|
|
464
|
+
gap: 0.75rem;
|
|
465
|
+
color: #6c757d;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
.placeholder-spinner {
|
|
469
|
+
width: 24px;
|
|
470
|
+
height: 24px;
|
|
471
|
+
border: 2px solid #dee2e6;
|
|
472
|
+
border-top-color: #007bff;
|
|
473
|
+
border-radius: 50%;
|
|
474
|
+
animation: spin 0.8s linear infinite;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
.placeholder-text {
|
|
478
|
+
font-size: 0.75rem;
|
|
479
|
+
font-weight: 500;
|
|
480
|
+
text-align: center;
|
|
481
|
+
max-width: 80%;
|
|
482
|
+
overflow: hidden;
|
|
483
|
+
text-overflow: ellipsis;
|
|
484
|
+
white-space: nowrap;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
@keyframes spin {
|
|
488
|
+
to {
|
|
489
|
+
transform: rotate(360deg);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
405
492
|
</style>
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
import ChartCard from './ChartCard.svelte';
|
|
7
7
|
import GlobalControls from './GlobalControls.svelte';
|
|
8
8
|
import { getPreprocessedData, type ProcessedChartData } from './data-processor.js';
|
|
9
|
-
import { log } from '../logger';
|
|
10
9
|
import { checkHealth, getMessage } from '../FeatureRegistry';
|
|
11
10
|
|
|
12
11
|
interface Props {
|
|
@@ -58,18 +57,6 @@
|
|
|
58
57
|
|
|
59
58
|
let { layout, data, mode, markers, plotlyLayout, enableAdaptation = true, showGlobalControls = true, persistSettings = false }: Props = $props();
|
|
60
59
|
let isHealthy = $state(checkHealth('charts'));
|
|
61
|
-
// Log component initialization
|
|
62
|
-
$effect(() => {
|
|
63
|
-
log('📊 ChartComponent initialized', {
|
|
64
|
-
mode,
|
|
65
|
-
dataRows: data.length,
|
|
66
|
-
sections: layout.sections.length,
|
|
67
|
-
totalCharts: layout.sections.reduce((sum, s) => sum + s.charts.length, 0),
|
|
68
|
-
enableAdaptation,
|
|
69
|
-
showGlobalControls,
|
|
70
|
-
layoutHoverMode: layout.hoverMode
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
60
|
|
|
74
61
|
// Preprocess raw data once - automatically memoized by Svelte's $derived
|
|
75
62
|
// This extracts all KPI values and timestamps, cached until data or layout changes
|
|
@@ -98,13 +85,6 @@
|
|
|
98
85
|
|
|
99
86
|
// Handler for global controls updates
|
|
100
87
|
function handleControlsUpdate(updatedControls: GlobalChartControls) {
|
|
101
|
-
log('🎛️ Global controls updated', {
|
|
102
|
-
movingAverageEnabled: updatedControls.movingAverage?.enabled,
|
|
103
|
-
windowOverride: updatedControls.movingAverage?.windowOverride,
|
|
104
|
-
markersEnabled: updatedControls.markers?.enabled,
|
|
105
|
-
legendEnabled: updatedControls.legend?.enabled,
|
|
106
|
-
hoverMode: updatedControls.hoverMode?.mode
|
|
107
|
-
});
|
|
108
88
|
globalControls = updatedControls;
|
|
109
89
|
}
|
|
110
90
|
|
|
@@ -195,17 +175,12 @@
|
|
|
195
175
|
|
|
196
176
|
function zoomSelectedChart() {
|
|
197
177
|
if (contextMenu.chart && contextMenu.section) {
|
|
198
|
-
log('🔍 Zooming chart', {
|
|
199
|
-
chartTitle: contextMenu.chart.title,
|
|
200
|
-
sectionId: contextMenu.section.id
|
|
201
|
-
});
|
|
202
178
|
zoomedChart = { chart: contextMenu.chart, section: contextMenu.section };
|
|
203
179
|
}
|
|
204
180
|
closeContextMenu();
|
|
205
181
|
}
|
|
206
182
|
|
|
207
183
|
function exitZoom() {
|
|
208
|
-
log('🔍 Exiting zoom mode');
|
|
209
184
|
zoomedChart = null;
|
|
210
185
|
closeContextMenu();
|
|
211
186
|
}
|
|
@@ -319,7 +294,7 @@
|
|
|
319
294
|
</script>
|
|
320
295
|
|
|
321
296
|
<!-- Reusable chart grid snippet -->
|
|
322
|
-
{#snippet chartGrid(section: Section)}
|
|
297
|
+
{#snippet chartGrid(section: Section, enableLazyRender: boolean = false)}
|
|
323
298
|
{@const gridConfig = getGridConfig(section.grid)}
|
|
324
299
|
<div
|
|
325
300
|
class="chart-grid"
|
|
@@ -327,7 +302,7 @@
|
|
|
327
302
|
style:grid-template-columns={`repeat(${gridConfig.columns}, minmax(0, 1fr))`}
|
|
328
303
|
style:grid-template-rows={`repeat(${gridConfig.rows}, minmax(0, 1fr))`}
|
|
329
304
|
>
|
|
330
|
-
{#each section.charts as chart}
|
|
305
|
+
{#each section.charts as chart, chartIndex}
|
|
331
306
|
<div class="chart-slot">
|
|
332
307
|
<ChartCard
|
|
333
308
|
{chart}
|
|
@@ -344,6 +319,8 @@
|
|
|
344
319
|
runtimeShowMarkers={globalControls.markers?.enabled}
|
|
345
320
|
runtimeShowLegend={globalControls.legend?.enabled}
|
|
346
321
|
runtimeHoverMode={globalControls.hoverMode?.mode}
|
|
322
|
+
renderDelay={chartIndex * 50}
|
|
323
|
+
lazyRender={enableLazyRender}
|
|
347
324
|
onchartcontextmenu={(detail) => handleChartContextMenu(detail, section)}
|
|
348
325
|
/>
|
|
349
326
|
</div>
|
|
@@ -395,8 +372,8 @@
|
|
|
395
372
|
class="tab-section {section.id === activeTabId ? 'active' : 'hidden'}"
|
|
396
373
|
data-section-id="{section.id}"
|
|
397
374
|
>
|
|
398
|
-
<!-- Chart Grid -->
|
|
399
|
-
{@render chartGrid(section)}
|
|
375
|
+
<!-- Chart Grid - staggered rendering only, no lazy since tabs handle visibility -->
|
|
376
|
+
{@render chartGrid(section, false)}
|
|
400
377
|
</div>
|
|
401
378
|
{/each}
|
|
402
379
|
</div>
|
|
@@ -427,8 +404,8 @@
|
|
|
427
404
|
<div class="scrollspy-content" bind:this={scrollspyContent}>
|
|
428
405
|
{#each layout.sections as section}
|
|
429
406
|
<div class="section-content" id="{section.id}">
|
|
430
|
-
<!-- Chart Grid -->
|
|
431
|
-
{@render chartGrid(section)}
|
|
407
|
+
<!-- Chart Grid - lazy + staggered rendering for scrollspy -->
|
|
408
|
+
{@render chartGrid(section, true)}
|
|
432
409
|
</div>
|
|
433
410
|
{/each}
|
|
434
411
|
</div>
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { log } from '../logger';
|
|
2
1
|
/**
|
|
3
2
|
* Extract all unique KPI rawNames from a layout configuration
|
|
4
3
|
* This determines which columns we need to extract from raw data
|
|
@@ -17,10 +16,6 @@ export function extractKPINames(layout) {
|
|
|
17
16
|
}
|
|
18
17
|
}
|
|
19
18
|
}
|
|
20
|
-
log('📋 KPI names extracted', {
|
|
21
|
-
totalKPIs: kpiNames.size,
|
|
22
|
-
kpiNames: Array.from(kpiNames)
|
|
23
|
-
});
|
|
24
19
|
return kpiNames;
|
|
25
20
|
}
|
|
26
21
|
/**
|
|
@@ -33,11 +28,6 @@ export function extractKPINames(layout) {
|
|
|
33
28
|
* @returns Preprocessed data ready for chart rendering
|
|
34
29
|
*/
|
|
35
30
|
export function preprocessChartData(data, layout, timestampField = 'TIMESTAMP') {
|
|
36
|
-
log('🔄 Preprocessing chart data', {
|
|
37
|
-
rawDataRows: data.length,
|
|
38
|
-
sections: layout.sections.length,
|
|
39
|
-
timestampField
|
|
40
|
-
});
|
|
41
31
|
// Extract all unique KPI names we need to process
|
|
42
32
|
const kpiNames = extractKPINames(layout);
|
|
43
33
|
// Initialize the result map
|
|
@@ -53,17 +43,11 @@ export function preprocessChartData(data, layout, timestampField = 'TIMESTAMP')
|
|
|
53
43
|
.filter(val => !isNaN(val)); // Remove invalid values
|
|
54
44
|
kpiValues.set(kpiName, values);
|
|
55
45
|
if (values.length === 0) {
|
|
56
|
-
|
|
46
|
+
console.warn(`[Charts] No valid values found for KPI: ${kpiName}`);
|
|
57
47
|
}
|
|
58
48
|
}
|
|
59
49
|
// Extract timestamps once
|
|
60
50
|
const timestamps = data.map(row => row[timestampField]);
|
|
61
|
-
log('✅ Data preprocessing complete', {
|
|
62
|
-
processedKPIs: kpiValues.size,
|
|
63
|
-
timestampCount: timestamps.length,
|
|
64
|
-
sampleKPI: kpiValues.keys().next().value,
|
|
65
|
-
sampleValues: kpiValues.values().next().value?.slice(0, 3)
|
|
66
|
-
});
|
|
67
51
|
return {
|
|
68
52
|
kpiValues,
|
|
69
53
|
timestamps,
|
|
@@ -91,11 +75,9 @@ export function getPreprocessedData(data, layout, timestampField = 'TIMESTAMP')
|
|
|
91
75
|
if (cached) {
|
|
92
76
|
// Verify cache is still valid (data reference matches)
|
|
93
77
|
if (cached._rawDataRef === data) {
|
|
94
|
-
log('💾 Using cached preprocessed data');
|
|
95
78
|
return cached;
|
|
96
79
|
}
|
|
97
80
|
}
|
|
98
|
-
log('🔄 Cache miss - preprocessing data');
|
|
99
81
|
// Cache miss or invalid - compute and cache
|
|
100
82
|
const processed = preprocessChartData(data, layout, timestampField);
|
|
101
83
|
preprocessCache.set(data, processed);
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Integration Layer - Tool-Callable Functions
|
|
3
|
+
*
|
|
4
|
+
* This module provides functions designed to be called by AI assistants
|
|
5
|
+
* (e.g., ChatGPT) for coverage analysis and recommendations.
|
|
6
|
+
*
|
|
7
|
+
* Design principles:
|
|
8
|
+
* - Functions are stateless and pure where possible
|
|
9
|
+
* - Inputs and outputs are JSON-serializable
|
|
10
|
+
* - Each function has clear purpose and documentation
|
|
11
|
+
* - Error handling returns structured error objects
|
|
12
|
+
*
|
|
13
|
+
* Phase 2 Implementation Note:
|
|
14
|
+
* These functions will be exposed via a tool registry that ChatGPT
|
|
15
|
+
* can call through function calling / tool use APIs.
|
|
16
|
+
*/
|
|
17
|
+
import type { CoverageResult, AIRecommendation, AIToolFunction } from '../types';
|
|
18
|
+
/**
|
|
19
|
+
* Analyze coverage quality and generate recommendations
|
|
20
|
+
*
|
|
21
|
+
* This function takes coverage calculation results and generates
|
|
22
|
+
* structured analysis suitable for AI interpretation.
|
|
23
|
+
*
|
|
24
|
+
* In Phase 2, ChatGPT will call this with coverage data and receive
|
|
25
|
+
* structured metrics that it can analyze and explain in natural language.
|
|
26
|
+
*
|
|
27
|
+
* @param result - Coverage calculation result
|
|
28
|
+
* @returns Structured analysis data
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* // AI will call:
|
|
32
|
+
* const analysis = analyzeCoverageQuality(coverageResult);
|
|
33
|
+
*
|
|
34
|
+
* // AI receives:
|
|
35
|
+
* {
|
|
36
|
+
* overallQuality: "good",
|
|
37
|
+
* coveragePercent: 78.5,
|
|
38
|
+
* avgSignal: -82.3,
|
|
39
|
+
* issues: ["excessive_range", "weak_avg_signal"],
|
|
40
|
+
* metrics: { ... }
|
|
41
|
+
* }
|
|
42
|
+
*
|
|
43
|
+
* // AI interprets and responds:
|
|
44
|
+
* "Your coverage is good overall at 78.5%. However, I notice two issues:
|
|
45
|
+
* 1. Excessive range detected - consider adding downtilt to reduce interference
|
|
46
|
+
* 2. Average signal is weak at -82.3 dBm - most users will experience medium throughput"
|
|
47
|
+
*/
|
|
48
|
+
export declare function analyzeCoverageQuality(result: CoverageResult): {
|
|
49
|
+
overallQuality: 'excellent' | 'good' | 'fair' | 'poor';
|
|
50
|
+
coveragePercent: number;
|
|
51
|
+
avgSignal: number;
|
|
52
|
+
maxRange: number;
|
|
53
|
+
issues: string[];
|
|
54
|
+
metrics: Record<string, number>;
|
|
55
|
+
sectorBreakdown: any[];
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Generate optimization suggestions
|
|
59
|
+
*
|
|
60
|
+
* Based on coverage analysis, generates specific parameter adjustment
|
|
61
|
+
* recommendations that can be presented to the user.
|
|
62
|
+
*
|
|
63
|
+
* In Phase 2, AI will use this to generate specific, actionable recommendations.
|
|
64
|
+
*
|
|
65
|
+
* @param result - Coverage result
|
|
66
|
+
* @returns Array of recommendations
|
|
67
|
+
*/
|
|
68
|
+
export declare function generateOptimizationSuggestions(result: CoverageResult): AIRecommendation[];
|
|
69
|
+
/**
|
|
70
|
+
* Compare two coverage scenarios
|
|
71
|
+
*
|
|
72
|
+
* Useful for before/after analysis when user changes parameters.
|
|
73
|
+
*
|
|
74
|
+
* @param before - Coverage before changes
|
|
75
|
+
* @param after - Coverage after changes
|
|
76
|
+
* @returns Comparison analysis
|
|
77
|
+
*/
|
|
78
|
+
export declare function compareCoverageScenarios(before: CoverageResult, after: CoverageResult): {
|
|
79
|
+
coverageChange: number;
|
|
80
|
+
signalChange: number;
|
|
81
|
+
rangeChange: number;
|
|
82
|
+
improvements: string[];
|
|
83
|
+
degradations: string[];
|
|
84
|
+
summary: string;
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Extract key metrics for AI analysis
|
|
88
|
+
*
|
|
89
|
+
* Flattens coverage result into simple key-value pairs
|
|
90
|
+
* that are easy for AI to interpret.
|
|
91
|
+
*
|
|
92
|
+
* @param result - Coverage result
|
|
93
|
+
* @returns Flat metrics object
|
|
94
|
+
*/
|
|
95
|
+
export declare function extractAIMetrics(result: CoverageResult): Record<string, number | string>;
|
|
96
|
+
/**
|
|
97
|
+
* Registry of all AI-callable tools
|
|
98
|
+
*
|
|
99
|
+
* In Phase 2, this will be used to expose functions to ChatGPT.
|
|
100
|
+
* Each entry describes the function, its parameters, and its purpose.
|
|
101
|
+
*/
|
|
102
|
+
export declare const AI_TOOLS: AIToolFunction[];
|
|
103
|
+
/**
|
|
104
|
+
* Get AI tool by name
|
|
105
|
+
*
|
|
106
|
+
* @param name - Tool name
|
|
107
|
+
* @returns Tool function or undefined
|
|
108
|
+
*/
|
|
109
|
+
export declare function getAITool(name: string): AIToolFunction | undefined;
|
|
110
|
+
/**
|
|
111
|
+
* Execute AI tool by name
|
|
112
|
+
*
|
|
113
|
+
* @param name - Tool name
|
|
114
|
+
* @param params - Tool parameters
|
|
115
|
+
* @returns Tool result
|
|
116
|
+
*/
|
|
117
|
+
export declare function executeAITool(name: string, params: any): Promise<any>;
|