@smartnet360/svelte-components 0.0.47 → 0.0.49

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.
@@ -19,7 +19,52 @@ export async function loadCellTrafficData() {
19
19
  throw new Error(`Failed to load CSV: ${response.statusText}`);
20
20
  }
21
21
  const text = await response.text();
22
- return parseCsvData(text);
22
+ const records = parseCsvData(text);
23
+ // Log sample of converted dates for debugging
24
+ if (records.length > 0) {
25
+ console.log('📅 Sample date conversions:', {
26
+ first: records[0].date,
27
+ last: records[records.length - 1].date,
28
+ totalRecords: records.length
29
+ });
30
+ }
31
+ return records;
32
+ }
33
+ /**
34
+ * Parse DD-MMM-YY format to ISO date string
35
+ * @param dateStr - Date string in DD-MMM-YY format (e.g., "01-JUL-24" or "01-Jan-24")
36
+ * @returns ISO date string (e.g., "2024-07-01")
37
+ */
38
+ function parseDateString(dateStr) {
39
+ try {
40
+ // Parse DD-MMM-YY format
41
+ const parts = dateStr.split('-');
42
+ if (parts.length !== 3)
43
+ return dateStr; // Return original if invalid format
44
+ const day = parts[0].padStart(2, '0');
45
+ const monthStr = parts[1].toUpperCase(); // Handle both uppercase and lowercase
46
+ const year = parts[2];
47
+ // Convert month abbreviation to number (uppercase keys)
48
+ const monthMap = {
49
+ 'JAN': '01', 'FEB': '02', 'MAR': '03', 'APR': '04',
50
+ 'MAY': '05', 'JUN': '06', 'JUL': '07', 'AUG': '08',
51
+ 'SEP': '09', 'OCT': '10', 'NOV': '11', 'DEC': '12'
52
+ };
53
+ const month = monthMap[monthStr];
54
+ if (!month) {
55
+ console.warn('Invalid month abbreviation:', monthStr, 'in date:', dateStr);
56
+ return dateStr; // Return original if invalid month
57
+ }
58
+ // Assume 20xx for two-digit years
59
+ const fullYear = year.length === 2 ? `20${year}` : year;
60
+ // Return ISO format: YYYY-MM-DD
61
+ const isoDate = `${fullYear}-${month}-${day}`;
62
+ return isoDate;
63
+ }
64
+ catch (error) {
65
+ console.warn('Failed to parse date:', dateStr, error);
66
+ return dateStr; // Return original on error
67
+ }
23
68
  }
24
69
  /**
25
70
  * Parse CSV text into structured records
@@ -38,7 +83,7 @@ function parseCsvData(csv) {
38
83
  return null;
39
84
  }
40
85
  // Fixed columns (metadata) - based on known CSV structure
41
- const date = parts[0].trim();
86
+ const date = parseDateString(parts[0].trim()); // Convert to ISO format
42
87
  const cellName = parts[1].trim();
43
88
  const siteName = parts[4].trim();
44
89
  const sector = parseInt(parts[5]);
@@ -1,5 +1,6 @@
1
1
  import type { Layout, CellStylingConfig } from '../../core/Charts';
2
2
  import type { CellTrafficRecord } from './';
3
+ import { type StackGroupMode } from './transforms.js';
3
4
  /**
4
5
  * Expand base layout configuration with dynamic KPIs based on selected cells
5
6
  * Takes a base layout (with one KPI per metric) and expands it to include one KPI per cell
@@ -7,9 +8,10 @@ import type { CellTrafficRecord } from './';
7
8
  * @param baseLayout - The base layout configuration from JSON
8
9
  * @param data - Filtered cell traffic records for selected cells
9
10
  * @param stylingConfig - Optional cell styling configuration (band colors, sector line styles)
11
+ * @param stackGroupMode - Optional stackgroup strategy for stacked charts (default: 'none' = single stack)
10
12
  * @returns Expanded layout with cell-specific KPIs
11
13
  */
12
- export declare function expandLayoutForCells(baseLayout: Layout, data: CellTrafficRecord[], stylingConfig?: CellStylingConfig): Layout;
14
+ export declare function expandLayoutForCells(baseLayout: Layout, data: CellTrafficRecord[], stylingConfig?: CellStylingConfig, stackGroupMode?: StackGroupMode): Layout;
13
15
  /**
14
16
  * Extract base metric names from a layout configuration
15
17
  * Returns unique metric rawNames that need to be pivoted
@@ -1,4 +1,4 @@
1
- import { createStyledKPI, sortCellsByBandFrequency } from './transforms.js';
1
+ import { createStyledKPI, sortCellsByBandFrequency, assignStackGroups } from './transforms.js';
2
2
  /**
3
3
  * Expand base layout configuration with dynamic KPIs based on selected cells
4
4
  * Takes a base layout (with one KPI per metric) and expands it to include one KPI per cell
@@ -6,9 +6,10 @@ import { createStyledKPI, sortCellsByBandFrequency } from './transforms.js';
6
6
  * @param baseLayout - The base layout configuration from JSON
7
7
  * @param data - Filtered cell traffic records for selected cells
8
8
  * @param stylingConfig - Optional cell styling configuration (band colors, sector line styles)
9
+ * @param stackGroupMode - Optional stackgroup strategy for stacked charts (default: 'none' = single stack)
9
10
  * @returns Expanded layout with cell-specific KPIs
10
11
  */
11
- export function expandLayoutForCells(baseLayout, data, stylingConfig) {
12
+ export function expandLayoutForCells(baseLayout, data, stylingConfig, stackGroupMode = 'none') {
12
13
  // Get unique cells and their metadata, sorted by band frequency
13
14
  const cellMap = new Map();
14
15
  data.forEach((record) => {
@@ -36,8 +37,8 @@ export function expandLayoutForCells(baseLayout, data, stylingConfig) {
36
37
  ...section,
37
38
  charts: section.charts.map((chart) => ({
38
39
  ...chart,
39
- yLeft: expandKPIs(chart.yLeft, cells, effectiveStyling),
40
- yRight: expandKPIs(chart.yRight, cells, effectiveStyling)
40
+ yLeft: expandKPIs(chart.yLeft, cells, effectiveStyling, stackGroupMode),
41
+ yRight: expandKPIs(chart.yRight, cells, effectiveStyling, stackGroupMode)
41
42
  }))
42
43
  };
43
44
  })
@@ -52,10 +53,11 @@ export function expandLayoutForCells(baseLayout, data, stylingConfig) {
52
53
  * @param baseKPIs - Array of base KPIs from layout
53
54
  * @param cells - Array of [cellName, record] tuples
54
55
  * @param stylingConfig - Optional cell styling configuration
55
- * @returns Expanded array of KPIs (styled or default)
56
+ * @param stackGroupMode - Stackgroup strategy for this set of KPIs
57
+ * @returns Expanded array of KPIs (styled or default, with stackgroups assigned)
56
58
  */
57
- function expandKPIs(baseKPIs, cells, stylingConfig) {
58
- const expandedKPIs = [];
59
+ function expandKPIs(baseKPIs, cells, stylingConfig, stackGroupMode = 'none') {
60
+ let expandedKPIs = [];
59
61
  baseKPIs.forEach((baseKPI) => {
60
62
  cells.forEach(([cellName, record]) => {
61
63
  if (stylingConfig) {
@@ -73,6 +75,8 @@ function expandKPIs(baseKPIs, cells, stylingConfig) {
73
75
  }
74
76
  });
75
77
  });
78
+ // Apply stackgroups to all expanded KPIs
79
+ expandedKPIs = assignStackGroups(expandedKPIs, cells, stackGroupMode);
76
80
  return expandedKPIs;
77
81
  }
78
82
  /**
@@ -4,6 +4,6 @@
4
4
  */
5
5
  export { default as SiteCheck } from './SiteCheck.svelte';
6
6
  export { loadCellTrafficData, getUniqueSites, getUniqueCells, groupDataByCell, defaultTreeGrouping, type CellTrafficRecord, type TreeGroupingConfig, type TreeGroupField } from './data-loader.js';
7
- export { buildTreeNodes, filterChartData, transformChartData, createStyledKPI, extractBandFromCell, getBandFrequency, sortCellsByBandFrequency } from './transforms.js';
7
+ export { buildTreeNodes, filterChartData, transformChartData, createStyledKPI, extractBandFromCell, getBandFrequency, sortCellsByBandFrequency, assignStackGroups, createStackGroupId, type StackGroupMode } from './transforms.js';
8
8
  export { expandLayoutForCells, extractBaseMetrics } from './helper.js';
9
9
  export { defaultCellStyling } from './default-cell-styling.js';
@@ -7,7 +7,7 @@ export { default as SiteCheck } from './SiteCheck.svelte';
7
7
  // Data loading
8
8
  export { loadCellTrafficData, getUniqueSites, getUniqueCells, groupDataByCell, defaultTreeGrouping } from './data-loader.js';
9
9
  // Data transforms
10
- export { buildTreeNodes, filterChartData, transformChartData, createStyledKPI, extractBandFromCell, getBandFrequency, sortCellsByBandFrequency } from './transforms.js';
10
+ export { buildTreeNodes, filterChartData, transformChartData, createStyledKPI, extractBandFromCell, getBandFrequency, sortCellsByBandFrequency, assignStackGroups, createStackGroupId } from './transforms.js';
11
11
  // Helper functions
12
12
  export { expandLayoutForCells, extractBaseMetrics } from './helper.js';
13
13
  // Default cell styling configuration
@@ -5,6 +5,32 @@
5
5
  import type { TreeNode } from '../../core/TreeView';
6
6
  import type { KPI, CellStylingConfig } from '../../core/Charts';
7
7
  import type { CellTrafficRecord, TreeGroupingConfig } from './data-loader';
8
+ /**
9
+ * Stackgroup mode types for dynamic stacking strategies
10
+ */
11
+ export type StackGroupMode = 'band' | 'site' | 'azimuth' | 'sector' | 'cell' | 'none';
12
+ /**
13
+ * Generate a deterministic, production-safe stackgroup ID
14
+ * This function ensures stackgroups are:
15
+ * - Consistent across dev and prod builds
16
+ * - Never undefined, null, or empty
17
+ * - Properly stringified and sanitized
18
+ *
19
+ * @param value - The grouping value (band, site, etc.)
20
+ * @param mode - The grouping mode/strategy
21
+ * @returns Normalized stackgroup string
22
+ */
23
+ export declare function createStackGroupId(value: string | number | null | undefined, mode: StackGroupMode): string;
24
+ /**
25
+ * Assign stackgroups to KPIs based on cell metadata and grouping mode
26
+ * This is production-safe and works identically in dev and build modes
27
+ *
28
+ * @param kpis - Array of KPIs to assign stackgroups to
29
+ * @param cells - Array of [cellName, record] tuples with metadata
30
+ * @param mode - Grouping strategy (band, site, azimuth, etc.)
31
+ * @returns KPIs with stackGroup field populated
32
+ */
33
+ export declare function assignStackGroups(kpis: KPI[], cells: [string, CellTrafficRecord][], mode: StackGroupMode): KPI[];
8
34
  /**
9
35
  * Extract band from cell name using regex pattern matching
10
36
  * @param cellName - Cell name like "LTE700_1", "NR3500_2", etc.
@@ -3,6 +3,93 @@
3
3
  * Converts raw CSV data to TreeView nodes and Chart configurations
4
4
  */
5
5
  import { log } from '../../core/logger';
6
+ /**
7
+ * Generate a deterministic, production-safe stackgroup ID
8
+ * This function ensures stackgroups are:
9
+ * - Consistent across dev and prod builds
10
+ * - Never undefined, null, or empty
11
+ * - Properly stringified and sanitized
12
+ *
13
+ * @param value - The grouping value (band, site, etc.)
14
+ * @param mode - The grouping mode/strategy
15
+ * @returns Normalized stackgroup string
16
+ */
17
+ export function createStackGroupId(value, mode) {
18
+ // Sanitize the value - convert to string and handle nullish values
19
+ const sanitizedValue = value != null ? String(value).trim() : 'default';
20
+ // If mode is 'none', return a single stackgroup for all traces
21
+ if (mode === 'none') {
22
+ return 'stack_all';
23
+ }
24
+ // Build deterministic ID: stack_{mode}_{value}
25
+ // This ensures all traces with same mode+value get identical stackgroup
26
+ return `stack_${mode}_${sanitizedValue}`;
27
+ }
28
+ /**
29
+ * Assign stackgroups to KPIs based on cell metadata and grouping mode
30
+ * This is production-safe and works identically in dev and build modes
31
+ *
32
+ * @param kpis - Array of KPIs to assign stackgroups to
33
+ * @param cells - Array of [cellName, record] tuples with metadata
34
+ * @param mode - Grouping strategy (band, site, azimuth, etc.)
35
+ * @returns KPIs with stackGroup field populated
36
+ */
37
+ export function assignStackGroups(kpis, cells, mode) {
38
+ // Create a mapping of cellName → metadata for quick lookup
39
+ const cellMetadata = new Map(cells);
40
+ return kpis.map(kpi => {
41
+ // Extract cellName from the rawName (format: "METRIC_CELLNAME")
42
+ const cellName = kpi.rawName.split('_').slice(1).join('_');
43
+ const record = cellMetadata.get(cellName);
44
+ if (!record) {
45
+ // Fallback if cell not found - use default stackgroup
46
+ log('⚠️ Cell not found for KPI, using default stackgroup', {
47
+ kpiRawName: kpi.rawName,
48
+ cellName
49
+ });
50
+ return {
51
+ ...kpi,
52
+ stackGroup: createStackGroupId(null, mode)
53
+ };
54
+ }
55
+ // Get the grouping value based on mode
56
+ let groupValue;
57
+ switch (mode) {
58
+ case 'band':
59
+ groupValue = record.band;
60
+ break;
61
+ case 'site':
62
+ groupValue = record.siteName;
63
+ break;
64
+ case 'azimuth':
65
+ groupValue = record.azimuth;
66
+ break;
67
+ case 'sector':
68
+ groupValue = record.sector;
69
+ break;
70
+ case 'cell':
71
+ groupValue = record.cellName;
72
+ break;
73
+ case 'none':
74
+ default:
75
+ groupValue = null;
76
+ break;
77
+ }
78
+ // Create deterministic stackgroup ID
79
+ const stackGroup = createStackGroupId(groupValue, mode);
80
+ log('📊 Assigned stackgroup', {
81
+ kpiName: kpi.name,
82
+ cellName,
83
+ mode,
84
+ groupValue,
85
+ stackGroup
86
+ });
87
+ return {
88
+ ...kpi,
89
+ stackGroup
90
+ };
91
+ });
92
+ }
6
93
  /**
7
94
  * Band frequency mapping for consistent ordering
8
95
  * Maps band strings to their actual frequencies in MHz
@@ -14,6 +14,7 @@ export interface KPI {
14
14
  color?: string;
15
15
  lineStyle?: LineStyle;
16
16
  movingAverage?: MovingAverageConfig;
17
+ stackGroup?: string;
17
18
  }
18
19
  export type ChartPosition = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
19
20
  export type ChartGrid = "2x2" | "3x3" | "4x4" | "1x2" | "1x4" | "1x8";
@@ -96,6 +96,9 @@ export function calculateMovingAverage(values, window) {
96
96
  export function createTimeSeriesTrace(values, timestamps, kpi, yaxis = 'y1', colorIndex = 0, chartType = 'line', stackGroup, coloredHover = true) {
97
97
  // Use KPI color if provided, otherwise cycle through modern colors
98
98
  const traceColor = kpi.color || modernColors[colorIndex % modernColors.length];
99
+ // Use KPI-level stackgroup if available, otherwise fall back to chart-level stackgroup
100
+ // This allows per-KPI stackgroup assignment (production-safe for dynamic grouping)
101
+ const effectiveStackGroup = kpi.stackGroup || stackGroup;
99
102
  // Base trace configuration
100
103
  const baseTrace = {
101
104
  x: timestamps,
@@ -126,7 +129,7 @@ export function createTimeSeriesTrace(values, timestamps, kpi, yaxis = 'y1', col
126
129
  type: 'scatter',
127
130
  mode: 'lines',
128
131
  fill: 'tonexty',
129
- stackgroup: stackGroup || 'one',
132
+ stackgroup: effectiveStackGroup || 'one',
130
133
  line: {
131
134
  width: 1.5, // Visible border width
132
135
  color: darkenColor(traceColor, 0.25) // 25% darker border for better separation
@@ -139,7 +142,7 @@ export function createTimeSeriesTrace(values, timestamps, kpi, yaxis = 'y1', col
139
142
  type: 'scatter',
140
143
  mode: 'lines',
141
144
  fill: 'tonexty',
142
- stackgroup: stackGroup || 'one',
145
+ stackgroup: effectiveStackGroup || 'one',
143
146
  groupnorm: 'percent',
144
147
  line: {
145
148
  width: 1.5, // Visible border width
@@ -287,6 +290,7 @@ export function createDefaultPlotlyLayout(title, hoverMode, coloredHover = true)
287
290
  borderwidth: 1
288
291
  },
289
292
  xaxis: {
293
+ type: "date", // 👈 force real time-based axis
290
294
  showgrid: true,
291
295
  gridcolor: '#ecf0f1',
292
296
  linecolor: '#bdc3c7',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartnet360/svelte-components",
3
- "version": "0.0.47",
3
+ "version": "0.0.49",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && npm run prepack",