@sybilion/uilib 1.3.23 → 1.3.26

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 (52) hide show
  1. package/dist/esm/components/ui/TextWithDeferTooltip/TextWithDeferTooltip.js +1 -25
  2. package/dist/esm/components/ui/Tooltip/Tooltip.js +92 -7
  3. package/dist/esm/components/ui/Tooltip/Tooltip.styl.js +2 -2
  4. package/dist/esm/components/widgets/DriversComparisonChart/DriversComparisonChart.js +1 -2
  5. package/dist/esm/components/widgets/PerformanceChart/HorizonsSelector/HorizonsSelector.js +34 -0
  6. package/dist/esm/components/widgets/PerformanceChart/HorizonsSelector/HorizonsSelector.styl.js +7 -0
  7. package/dist/esm/components/widgets/PerformanceChart/PerformanceChart.constants.js +17 -0
  8. package/dist/esm/components/widgets/PerformanceChart/PerformanceChart.js +807 -0
  9. package/dist/esm/components/widgets/PerformanceChart/PerformanceChart.styl.js +7 -0
  10. package/dist/esm/components/widgets/PerformanceChart/PerformanceTable.js +130 -0
  11. package/dist/esm/components/widgets/PerformanceChart/PerformanceUnderChartLegend/PerformanceUnderChartLegend.js +20 -0
  12. package/dist/esm/components/widgets/PerformanceChart/PerformanceUnderChartLegend/PerformanceUnderChartLegend.styl.js +7 -0
  13. package/dist/esm/components/widgets/PerformanceChart/performanceChart.helpers.js +591 -0
  14. package/dist/esm/components/widgets/PerformanceChart/performanceChartUserSeries.js +109 -0
  15. package/dist/esm/index.js +4 -0
  16. package/dist/esm/types/src/components/ui/Tooltip/Tooltip.d.ts +3 -3
  17. package/dist/esm/types/src/components/ui/Tooltip/Tooltip.types.d.ts +1 -0
  18. package/dist/esm/types/src/components/widgets/PerformanceChart/HorizonsSelector/HorizonsSelector.d.ts +7 -0
  19. package/dist/esm/types/src/components/widgets/PerformanceChart/PerformanceChart.constants.d.ts +3 -0
  20. package/dist/esm/types/src/components/widgets/PerformanceChart/PerformanceChart.d.ts +54 -0
  21. package/dist/esm/types/src/components/widgets/PerformanceChart/PerformanceTable.d.ts +31 -0
  22. package/dist/esm/types/src/components/widgets/PerformanceChart/PerformanceUnderChartLegend/PerformanceUnderChartLegend.d.ts +20 -0
  23. package/dist/esm/types/src/components/widgets/PerformanceChart/index.d.ts +4 -0
  24. package/dist/esm/types/src/components/widgets/PerformanceChart/performanceChart.helpers.d.ts +212 -0
  25. package/dist/esm/types/src/components/widgets/PerformanceChart/performanceChartUserSeries.d.ts +20 -0
  26. package/dist/esm/types/src/docs/pages/PerformanceChartPage.d.ts +1 -0
  27. package/dist/esm/types/src/index.d.ts +1 -0
  28. package/package.json +1 -1
  29. package/src/components/ui/TextWithDeferTooltip/TextWithDeferTooltip.tsx +5 -37
  30. package/src/components/ui/Tooltip/Tooltip.styl +12 -0
  31. package/src/components/ui/Tooltip/Tooltip.styl.d.ts +1 -0
  32. package/src/components/ui/Tooltip/Tooltip.tsx +156 -8
  33. package/src/components/ui/Tooltip/Tooltip.types.ts +1 -0
  34. package/src/components/widgets/PerformanceChart/HorizonsSelector/HorizonsSelector.styl +25 -0
  35. package/src/components/widgets/PerformanceChart/HorizonsSelector/HorizonsSelector.styl.d.ts +11 -0
  36. package/src/components/widgets/PerformanceChart/HorizonsSelector/HorizonsSelector.tsx +67 -0
  37. package/src/components/widgets/PerformanceChart/PerformanceChart.constants.ts +17 -0
  38. package/src/components/widgets/PerformanceChart/PerformanceChart.styl +194 -0
  39. package/src/components/widgets/PerformanceChart/PerformanceChart.styl.d.ts +30 -0
  40. package/src/components/widgets/PerformanceChart/PerformanceChart.tsx +1251 -0
  41. package/src/components/widgets/PerformanceChart/PerformanceTable.tsx +381 -0
  42. package/src/components/widgets/PerformanceChart/PerformanceUnderChartLegend/PerformanceUnderChartLegend.styl +49 -0
  43. package/src/components/widgets/PerformanceChart/PerformanceUnderChartLegend/PerformanceUnderChartLegend.styl.d.ts +12 -0
  44. package/src/components/widgets/PerformanceChart/PerformanceUnderChartLegend/PerformanceUnderChartLegend.tsx +83 -0
  45. package/src/components/widgets/PerformanceChart/index.ts +28 -0
  46. package/src/components/widgets/PerformanceChart/performanceChart.helpers.ts +790 -0
  47. package/src/components/widgets/PerformanceChart/performanceChartUserSeries.ts +149 -0
  48. package/src/docs/pages/PerformanceChartPage.tsx +211 -0
  49. package/src/docs/pages/TextWithDeferTooltipPage.tsx +26 -10
  50. package/src/docs/pages/TooltipPage.tsx +30 -0
  51. package/src/docs/registry.ts +6 -0
  52. package/src/index.ts +1 -0
@@ -0,0 +1,381 @@
1
+ import cn from 'classnames';
2
+ import React, { useMemo, useRef } from 'react';
3
+
4
+ import { Button } from '#uilib/components/ui/Button';
5
+ import { FORECAST_LINE_COLORS } from '#uilib/components/ui/ChartAreaInteractive/ChartLines';
6
+ import { PageXScroll } from '#uilib/components/ui/Page';
7
+ import {
8
+ Table,
9
+ TableBody,
10
+ TableCell,
11
+ TableCellValue,
12
+ TableHead,
13
+ TableHeader,
14
+ TableRow,
15
+ } from '#uilib/components/ui/Table';
16
+ import {
17
+ Tooltip,
18
+ TooltipContent,
19
+ TooltipTrigger,
20
+ } from '#uilib/components/ui/Tooltip';
21
+ import { InfoIcon, Pencil, TableIcon } from 'lucide-react';
22
+
23
+ import S from './PerformanceChart.styl';
24
+ import {
25
+ calculateAccuracy,
26
+ calculateBenefit,
27
+ formatAccuracy,
28
+ formatBenefit,
29
+ formatError,
30
+ formatROI,
31
+ getForecastModelDisplayName,
32
+ } from './performanceChart.helpers';
33
+
34
+ const DEFAULT_INVESTMENT_COST = 35000; // Investment Cost (IC) - annual cost of solution
35
+
36
+ const CUSTOM_PERFORMANCE_ROW_COLOR = 'var(--sb-purple-400)';
37
+
38
+ // Component wrapper for table rows with long tap support
39
+ function ModelTableRow({
40
+ modelKey,
41
+ row,
42
+ }: {
43
+ modelKey: string;
44
+ row: React.ReactElement;
45
+ onToggleSeries?: (modelKey: string) => void;
46
+ isHidden?: boolean;
47
+ }) {
48
+ const rowRef = useRef<HTMLTableRowElement>(null);
49
+
50
+ return React.cloneElement(row, {
51
+ ref: (el: HTMLTableRowElement | null) => {
52
+ rowRef.current = el;
53
+ // Call original ref if it exists
54
+ const originalRef = (row as any).ref;
55
+ if (typeof originalRef === 'function') {
56
+ originalRef(el);
57
+ } else if (originalRef) {
58
+ originalRef.current = el;
59
+ }
60
+ },
61
+ className: (row.props as any).className,
62
+ } as any);
63
+ }
64
+
65
+ export interface ForecastModelData {
66
+ key: string;
67
+ mae: number;
68
+ mape: number;
69
+ }
70
+
71
+ export interface AdjustParameters {
72
+ procurementVolume: number;
73
+ variableRawMaterialCostShare: number;
74
+ controllableCosts: number;
75
+ currentForecastAccuracy: number;
76
+ expectedImprovement: number;
77
+ analystHourlyRate: number;
78
+ }
79
+
80
+ interface PerformanceTableProps {
81
+ forecastModels: ForecastModelData[];
82
+ adjustParameters: AdjustParameters;
83
+ onAdjustParametersChange: (params: AdjustParameters) => void;
84
+ /** Shown when custom spaghetti matrix is saved (localStorage). Metrics vs historical for selected horizon. */
85
+ customPerformance?: { label: string; mae: number; mape: number } | null;
86
+ /** Opens custom performance dialog; used by the custom row edit control. */
87
+ onEditCustomPerformance?: () => void;
88
+ /** Last row with Add custom data (hidden when custom row already present); per-horizon + spaghetti. */
89
+ showAddEditCustomDataButton?: boolean;
90
+ addEditCustomDataDisabled?: boolean;
91
+ }
92
+
93
+ // Calculate color for a model key
94
+ const getModelColor = (key: string) => {
95
+ if (key === 'model') return 'var(--sb-cyan-400)';
96
+ return FORECAST_LINE_COLORS[0];
97
+ };
98
+
99
+ export function PerformanceTable({
100
+ forecastModels,
101
+ adjustParameters,
102
+ onAdjustParametersChange,
103
+ customPerformance = null,
104
+ onEditCustomPerformance,
105
+ showAddEditCustomDataButton = false,
106
+ addEditCustomDataDisabled = false,
107
+ }: PerformanceTableProps) {
108
+ // Separate Sybilion AI model from drift model
109
+ const { sybilionModel, driftModel } = useMemo(() => {
110
+ const sybilion = forecastModels.find(model => model.key === 'model');
111
+ const drift = forecastModels.find(model => model.key === 'drift');
112
+ return {
113
+ sybilionModel: sybilion,
114
+ driftModel: drift,
115
+ };
116
+ }, [forecastModels]);
117
+
118
+ // Calculate table rows for Sybilion AI model
119
+ const sybilionRow = useMemo(() => {
120
+ if (!sybilionModel) return null;
121
+
122
+ const accuracy = calculateAccuracy(sybilionModel.mape);
123
+ const error = formatError(sybilionModel.mae);
124
+ // Calculate benefit using actual accuracy improvement over baseline (drift)
125
+ const baselineAccuracy = driftModel
126
+ ? calculateAccuracy(driftModel.mape)
127
+ : undefined;
128
+ const benefit = calculateBenefit(
129
+ sybilionModel.mae,
130
+ sybilionModel.mape,
131
+ adjustParameters,
132
+ baselineAccuracy,
133
+ );
134
+ const roi = formatROI(benefit, DEFAULT_INVESTMENT_COST);
135
+
136
+ return {
137
+ key: sybilionModel.key,
138
+ forecast: getForecastModelDisplayName(sybilionModel.key),
139
+ color: getModelColor(sybilionModel.key),
140
+ accuracy: formatAccuracy(accuracy),
141
+ error,
142
+ roi,
143
+ benefit: formatBenefit(benefit),
144
+ };
145
+ }, [sybilionModel, driftModel, adjustParameters, getModelColor]);
146
+
147
+ // Calculate table row for drift model
148
+ const driftRow = useMemo(() => {
149
+ if (!driftModel) return null;
150
+
151
+ const accuracy = calculateAccuracy(driftModel.mape);
152
+ const error = formatError(driftModel.mae);
153
+ // Baseline model has no improvement, so benefit is 0
154
+ const benefit = 0;
155
+ const roi = formatROI(benefit, DEFAULT_INVESTMENT_COST);
156
+
157
+ return {
158
+ key: driftModel.key,
159
+ forecast: getForecastModelDisplayName(driftModel.key),
160
+ color: getModelColor(driftModel.key),
161
+ accuracy: formatAccuracy(accuracy),
162
+ error,
163
+ roi,
164
+ benefit: formatBenefit(benefit),
165
+ };
166
+ }, [driftModel, adjustParameters, getModelColor]);
167
+
168
+ const customRow = useMemo(() => {
169
+ if (!customPerformance) return null;
170
+ const accuracy = calculateAccuracy(customPerformance.mape);
171
+ const error = formatError(customPerformance.mae);
172
+ const baselineAccuracy = driftModel
173
+ ? calculateAccuracy(driftModel.mape)
174
+ : undefined;
175
+ const benefit = calculateBenefit(
176
+ customPerformance.mae,
177
+ customPerformance.mape,
178
+ adjustParameters,
179
+ baselineAccuracy,
180
+ );
181
+ const roi = formatROI(benefit, DEFAULT_INVESTMENT_COST);
182
+
183
+ return {
184
+ key: 'custom_performance',
185
+ forecast: customPerformance.label,
186
+ color: CUSTOM_PERFORMANCE_ROW_COLOR,
187
+ accuracy: formatAccuracy(accuracy),
188
+ error,
189
+ roi,
190
+ benefit: formatBenefit(benefit),
191
+ };
192
+ }, [customPerformance, driftModel, adjustParameters]);
193
+
194
+ const showCustomActionsColumn = Boolean(customRow);
195
+
196
+ return (
197
+ <div className={S.tableSection}>
198
+ <div className={S.tableWrapper}>
199
+ <PageXScroll
200
+ size="md"
201
+ fullWidth
202
+ innerClassName={S.tableContainer}
203
+ scrollbarClassName={S.scrollbar}
204
+ >
205
+ <Table withPaddings className={S.table}>
206
+ <TableHeader>
207
+ <TableRow>
208
+ <TableHead>
209
+ <TableCellValue>
210
+ Forecast
211
+ <Tooltip>
212
+ <TooltipTrigger asChild>
213
+ <span>
214
+ <InfoIcon size={16} style={{ cursor: 'help' }} />
215
+ </span>
216
+ </TooltipTrigger>
217
+ <TooltipContent side="top" maxWidth={300}>
218
+ Sybilion AI forecast vs other forecasting methods: MEAN,
219
+ Seasonal, ARMA, ARIMA, Linear, Theta
220
+ </TooltipContent>
221
+ </Tooltip>
222
+ </TableCellValue>
223
+ </TableHead>
224
+ <TableHead>Accuracy</TableHead>
225
+ <TableHead>Error</TableHead>
226
+ <TableHead>
227
+ <TableCellValue>
228
+ ROI
229
+ <Tooltip>
230
+ <TooltipTrigger asChild>
231
+ <span>
232
+ <InfoIcon size={16} style={{ cursor: 'help' }} />
233
+ </span>
234
+ </TooltipTrigger>
235
+ <TooltipContent side="top" maxWidth={300}>
236
+ ROI is calculated using default contract annual price.
237
+ </TooltipContent>
238
+ </Tooltip>
239
+ </TableCellValue>
240
+ </TableHead>
241
+ <TableHead>
242
+ <TableCellValue>
243
+ Benefit p/a
244
+ <Tooltip>
245
+ <TooltipTrigger asChild>
246
+ <span>
247
+ <InfoIcon size={16} style={{ cursor: 'help' }} />
248
+ </span>
249
+ </TooltipTrigger>
250
+ <TooltipContent side="top" maxWidth={300}>
251
+ Annual Benefit of using Sybilion AI forecast compared to
252
+ alternative forecasting methods. Calculated using
253
+ default assumptions: Procurement Volume €100,000,000 ·
254
+ Variable Share 50% · Controllable Share 60%
255
+ </TooltipContent>
256
+ </Tooltip>
257
+ </TableCellValue>
258
+ </TableHead>
259
+ {showCustomActionsColumn && <TableHead />}
260
+ </TableRow>
261
+ </TableHeader>
262
+ <TableBody>
263
+ {/* Sybilion AI row */}
264
+ {sybilionRow && (
265
+ <ModelTableRow
266
+ modelKey={sybilionRow.key}
267
+ row={
268
+ <TableRow key={sybilionRow.key}>
269
+ <TableCell>
270
+ <div className={S.forecastCell}>
271
+ <span
272
+ className={S.colorCircle}
273
+ style={{ backgroundColor: sybilionRow.color }}
274
+ />
275
+ {sybilionRow.forecast}
276
+ </div>
277
+ </TableCell>
278
+ <TableCell>{sybilionRow.accuracy}</TableCell>
279
+ <TableCell>{sybilionRow.error}</TableCell>
280
+ <TableCell>{sybilionRow.roi}</TableCell>
281
+ <TableCell>{sybilionRow.benefit}</TableCell>
282
+ {showCustomActionsColumn && <TableCell />}
283
+ </TableRow>
284
+ }
285
+ />
286
+ )}
287
+
288
+ {/* Drift model row */}
289
+ {driftRow && (
290
+ <ModelTableRow
291
+ modelKey={driftRow.key}
292
+ row={
293
+ <TableRow key={driftRow.key}>
294
+ <TableCell>
295
+ <div className={S.forecastCell}>
296
+ <span
297
+ className={S.colorCircle}
298
+ style={{ backgroundColor: driftRow.color }}
299
+ />
300
+ {driftRow.forecast}
301
+ </div>
302
+ </TableCell>
303
+ <TableCell>{driftRow.accuracy}</TableCell>
304
+ <TableCell>{driftRow.error}</TableCell>
305
+ <TableCell>{driftRow.roi}</TableCell>
306
+ <TableCell>{driftRow.benefit}</TableCell>
307
+ {showCustomActionsColumn && <TableCell />}
308
+ </TableRow>
309
+ }
310
+ />
311
+ )}
312
+
313
+ {customRow && (
314
+ <ModelTableRow
315
+ modelKey={customRow.key}
316
+ row={
317
+ <TableRow key={customRow.key}>
318
+ <TableCell>
319
+ <div className={S.forecastCell}>
320
+ <span
321
+ className={S.colorCircle}
322
+ style={{ backgroundColor: customRow.color }}
323
+ />
324
+ {customRow.forecast}
325
+ </div>
326
+ </TableCell>
327
+ <TableCell>{customRow.accuracy}</TableCell>
328
+ <TableCell>{customRow.error}</TableCell>
329
+ <TableCell>{customRow.roi}</TableCell>
330
+ <TableCell>{customRow.benefit}</TableCell>
331
+ {showCustomActionsColumn && (
332
+ <TableCell>
333
+ <Tooltip>
334
+ <TooltipTrigger asChild>
335
+ <Button
336
+ type="button"
337
+ variant="ghost"
338
+ icon
339
+ aria-label="Edit custom performance data"
340
+ onClick={() => onEditCustomPerformance?.()}
341
+ >
342
+ <Pencil size={16} />
343
+ </Button>
344
+ </TooltipTrigger>
345
+ <TooltipContent side="top">
346
+ Edit custom performance data
347
+ </TooltipContent>
348
+ </Tooltip>
349
+ </TableCell>
350
+ )}
351
+ </TableRow>
352
+ }
353
+ />
354
+ )}
355
+
356
+ {showAddEditCustomDataButton && !customPerformance && (
357
+ <TableRow className={S.tableCustomDataFooterRow}>
358
+ <TableCell
359
+ colSpan={5 + (showCustomActionsColumn ? 1 : 0)}
360
+ className={S.tableCustomDataFooterCell}
361
+ >
362
+ <Button
363
+ type="button"
364
+ variant="ghost"
365
+ size="sm"
366
+ disabled={addEditCustomDataDisabled}
367
+ onClick={() => onEditCustomPerformance?.()}
368
+ >
369
+ <TableIcon size={16} />
370
+ Add custom data
371
+ </Button>
372
+ </TableCell>
373
+ </TableRow>
374
+ )}
375
+ </TableBody>
376
+ </Table>
377
+ </PageXScroll>
378
+ </div>
379
+ </div>
380
+ );
381
+ }
@@ -0,0 +1,49 @@
1
+ @import '../../../../lib/theme.styl'
2
+
3
+ .root
4
+ display flex
5
+ align-items center
6
+ flex-wrap wrap
7
+ gap var(--p-4)
8
+
9
+ .row
10
+ display inline-flex
11
+ align-items center
12
+ gap var(--p-2)
13
+ padding var(--p-1) var(--p-2)
14
+ border none
15
+ background transparent
16
+ cursor pointer
17
+ font inherit
18
+ color var(--foreground)
19
+ border-radius var(--radius-sm)
20
+ transition opacity 0.2s ease
21
+
22
+ &:hover
23
+ opacity 0.85
24
+
25
+ .swatch
26
+ width 16px
27
+ height 3px
28
+ border-radius 1px
29
+ flex-shrink 0
30
+
31
+ .swatchDashed
32
+ background transparent !important
33
+ box-sizing border-box
34
+ width 16px
35
+ height 2px
36
+ border none
37
+ border-bottom 2px dashed currentColor
38
+ border-radius 0
39
+
40
+ .label
41
+ font-size var(--text-xs)
42
+ line-height var(--line-height-base)
43
+ white-space nowrap
44
+
45
+ .eye
46
+ width 14px
47
+ height 14px
48
+ flex-shrink 0
49
+ opacity 0.8
@@ -0,0 +1,12 @@
1
+ // This file is automatically generated.
2
+ // Please do not change this file!
3
+ interface CssExports {
4
+ 'eye': string;
5
+ 'label': string;
6
+ 'root': string;
7
+ 'row': string;
8
+ 'swatch': string;
9
+ 'swatchDashed': string;
10
+ }
11
+ export const cssExports: CssExports;
12
+ export default cssExports;
@@ -0,0 +1,83 @@
1
+ import cn from 'classnames';
2
+
3
+ import { EyeClosedIcon, EyeIcon } from '@phosphor-icons/react';
4
+
5
+ import S from './PerformanceUnderChartLegend.styl';
6
+
7
+ export type PerformanceUnderChartLegendRowProps = {
8
+ label: React.ReactNode;
9
+ /** Line color (CSS color string). */
10
+ lineColor: string;
11
+ /** Historical uses solid bar; forecast lines use dashed preview. */
12
+ lineStyle?: 'solid' | 'dashed';
13
+ hidden: boolean;
14
+ onToggle?: () => void;
15
+ };
16
+
17
+ export type PerformanceUnderChartLegendItemConfig =
18
+ PerformanceUnderChartLegendRowProps & { id: string };
19
+
20
+ export function PerformanceUnderChartLegendRow({
21
+ label,
22
+ lineColor,
23
+ lineStyle = 'solid',
24
+ hidden,
25
+ onToggle,
26
+ }: PerformanceUnderChartLegendRowProps) {
27
+ return (
28
+ <button type="button" className={S.row} onClick={onToggle}>
29
+ {lineStyle === 'solid' ? (
30
+ <span
31
+ className={S.swatch}
32
+ style={{ background: lineColor }}
33
+ aria-hidden
34
+ />
35
+ ) : (
36
+ <span
37
+ className={cn(S.swatch, S.swatchDashed)}
38
+ style={{ color: lineColor }}
39
+ aria-hidden
40
+ />
41
+ )}
42
+ <span className={S.label}>{label}</span>
43
+ {onToggle &&
44
+ (hidden ? (
45
+ <EyeClosedIcon className={S.eye} />
46
+ ) : (
47
+ <EyeIcon className={S.eye} />
48
+ ))}
49
+ </button>
50
+ );
51
+ }
52
+
53
+ /** Renders rows from tab-agnostic config (labels + toggles supplied by parent). */
54
+ export function PerformanceUnderChartLegendFromItems({
55
+ items,
56
+ }: {
57
+ items: PerformanceUnderChartLegendItemConfig[];
58
+ }) {
59
+ if (items?.length === 0) return null;
60
+
61
+ return (
62
+ <PerformanceUnderChartLegend>
63
+ {items.map(item => (
64
+ <PerformanceUnderChartLegendRow
65
+ key={item.id}
66
+ label={item.label}
67
+ lineColor={item.lineColor}
68
+ lineStyle={item.lineStyle ?? 'solid'}
69
+ hidden={item.hidden}
70
+ onToggle={item.onToggle}
71
+ />
72
+ ))}
73
+ </PerformanceUnderChartLegend>
74
+ );
75
+ }
76
+
77
+ export function PerformanceUnderChartLegend({
78
+ children,
79
+ }: {
80
+ children: React.ReactNode;
81
+ }) {
82
+ return <div className={S.root}>{children}</div>;
83
+ }
@@ -0,0 +1,28 @@
1
+ export {
2
+ PerformanceChart,
3
+ type PerformanceChartPayload,
4
+ type PerformanceChartProps,
5
+ type PerformanceViewTab,
6
+ } from './PerformanceChart';
7
+ export {
8
+ type AdjustParameters,
9
+ type ForecastModelData,
10
+ PerformanceTable,
11
+ } from './PerformanceTable';
12
+ export {
13
+ SPAGHETTI_DRIFT_PER_HORIZON_ID_BASE,
14
+ SPAGHETTI_MODEL_PER_HORIZON_ID_BASE,
15
+ averageForecastErrorsVsHistoricalForMatrixColumn,
16
+ buildDriftSpaghettiMatrixForCustomDialog,
17
+ buildPerHorizonSpaghettiEntries,
18
+ buildSpaghettiMergedChartData,
19
+ calculateYRangeFromChartData,
20
+ getForecastModelDisplayName,
21
+ spaghettiGridFromHistoricalPreviousMonth,
22
+ } from './performanceChart.helpers';
23
+ export {
24
+ SPAGHETTI_LOCAL_LS_USER_SERIES_ROW_ID,
25
+ SPAGHETTI_TIME_SERIES_MATRIX_V,
26
+ tryParseSpaghettiPerformanceMatrix,
27
+ type SpaghettiPerformanceMatrixPayload,
28
+ } from './performanceChartUserSeries';