@sybilion/uilib 1.3.22 → 1.3.25

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 (53) hide show
  1. package/dist/esm/components/widgets/DriversComparisonChart/DriversComparisonChart.js +139 -0
  2. package/dist/esm/components/widgets/DriversComparisonChart/DriversComparisonChart.styl.js +7 -0
  3. package/dist/esm/components/widgets/DriversComparisonChart/driversComparisonChart.helpers.js +159 -0
  4. package/dist/esm/components/widgets/PerformanceChart/HorizonsSelector/HorizonsSelector.js +34 -0
  5. package/dist/esm/components/widgets/PerformanceChart/HorizonsSelector/HorizonsSelector.styl.js +7 -0
  6. package/dist/esm/components/widgets/PerformanceChart/PerformanceChart.constants.js +17 -0
  7. package/dist/esm/components/widgets/PerformanceChart/PerformanceChart.js +807 -0
  8. package/dist/esm/components/widgets/PerformanceChart/PerformanceChart.styl.js +7 -0
  9. package/dist/esm/components/widgets/PerformanceChart/PerformanceTable.js +130 -0
  10. package/dist/esm/components/widgets/PerformanceChart/PerformanceUnderChartLegend/PerformanceUnderChartLegend.js +20 -0
  11. package/dist/esm/components/widgets/PerformanceChart/PerformanceUnderChartLegend/PerformanceUnderChartLegend.styl.js +7 -0
  12. package/dist/esm/components/widgets/PerformanceChart/performanceChart.helpers.js +591 -0
  13. package/dist/esm/components/widgets/PerformanceChart/performanceChartUserSeries.js +109 -0
  14. package/dist/esm/index.js +6 -0
  15. package/dist/esm/types/src/components/widgets/DriversComparisonChart/DriversComparisonChart.d.ts +18 -0
  16. package/dist/esm/types/src/components/widgets/DriversComparisonChart/driversComparisonChart.helpers.d.ts +26 -0
  17. package/dist/esm/types/src/components/widgets/DriversComparisonChart/index.d.ts +2 -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/DriversComparisonChartPage.d.ts +1 -0
  27. package/dist/esm/types/src/docs/pages/PerformanceChartPage.d.ts +1 -0
  28. package/dist/esm/types/src/index.d.ts +2 -0
  29. package/dist/esm/utils/chartConnectionPoint.js +9 -1
  30. package/package.json +1 -1
  31. package/src/components/widgets/DriversComparisonChart/DriversComparisonChart.styl +145 -0
  32. package/src/components/widgets/DriversComparisonChart/DriversComparisonChart.styl.d.ts +29 -0
  33. package/src/components/widgets/DriversComparisonChart/DriversComparisonChart.tsx +325 -0
  34. package/src/components/widgets/DriversComparisonChart/driversComparisonChart.helpers.ts +206 -0
  35. package/src/components/widgets/DriversComparisonChart/index.ts +13 -0
  36. package/src/components/widgets/PerformanceChart/HorizonsSelector/HorizonsSelector.styl +25 -0
  37. package/src/components/widgets/PerformanceChart/HorizonsSelector/HorizonsSelector.styl.d.ts +11 -0
  38. package/src/components/widgets/PerformanceChart/HorizonsSelector/HorizonsSelector.tsx +67 -0
  39. package/src/components/widgets/PerformanceChart/PerformanceChart.constants.ts +17 -0
  40. package/src/components/widgets/PerformanceChart/PerformanceChart.styl +194 -0
  41. package/src/components/widgets/PerformanceChart/PerformanceChart.styl.d.ts +30 -0
  42. package/src/components/widgets/PerformanceChart/PerformanceChart.tsx +1251 -0
  43. package/src/components/widgets/PerformanceChart/PerformanceTable.tsx +381 -0
  44. package/src/components/widgets/PerformanceChart/PerformanceUnderChartLegend/PerformanceUnderChartLegend.styl +49 -0
  45. package/src/components/widgets/PerformanceChart/PerformanceUnderChartLegend/PerformanceUnderChartLegend.styl.d.ts +12 -0
  46. package/src/components/widgets/PerformanceChart/PerformanceUnderChartLegend/PerformanceUnderChartLegend.tsx +83 -0
  47. package/src/components/widgets/PerformanceChart/index.ts +28 -0
  48. package/src/components/widgets/PerformanceChart/performanceChart.helpers.ts +790 -0
  49. package/src/components/widgets/PerformanceChart/performanceChartUserSeries.ts +149 -0
  50. package/src/docs/pages/DriversComparisonChartPage.tsx +174 -0
  51. package/src/docs/pages/PerformanceChartPage.tsx +211 -0
  52. package/src/docs/registry.ts +12 -0
  53. package/src/index.ts +2 -0
package/dist/esm/index.js CHANGED
@@ -104,6 +104,12 @@ export { DriverMap } from './components/widgets/DriverMap/DriverMap.js';
104
104
  export { getCategoryIcon } from './components/widgets/DriverMap/driverCategoryIcon.js';
105
105
  export { getDriverImportance, getHighestImportanceDriver } from './components/widgets/DriverMap/driverMapSelection.js';
106
106
  export { geographicCoordinates, geographicToSVG, getContinentFromRegion, getPreciseCoordinates, getResponsiveCoordinates, svgToPercentage } from './components/widgets/DriverMap/driverMapGeography.js';
107
+ export { DriversComparisonChart } from './components/widgets/DriversComparisonChart/DriversComparisonChart.js';
108
+ export { DRIVER_COMPARISON_CHART_LEAD_MONTHS, DRIVER_FORECAST_ID_BASE, INITIAL_VISIBLE_SERIES_COUNT, buildDriversComparisonChartData, formatSeriesImportance, mergeBacktestsChartData, mergeDatasetHistoricalWithBacktestsChartData } from './components/widgets/DriversComparisonChart/driversComparisonChart.helpers.js';
109
+ export { PerformanceChart } from './components/widgets/PerformanceChart/PerformanceChart.js';
110
+ export { PerformanceTable } from './components/widgets/PerformanceChart/PerformanceTable.js';
111
+ export { SPAGHETTI_DRIFT_PER_HORIZON_ID_BASE, SPAGHETTI_MODEL_PER_HORIZON_ID_BASE, averageForecastErrorsVsHistoricalForMatrixColumn, buildDriftSpaghettiMatrixForCustomDialog, buildPerHorizonSpaghettiEntries, buildSpaghettiMergedChartData, calculateYRangeFromChartData, getForecastModelDisplayName, spaghettiGridFromHistoricalPreviousMonth } from './components/widgets/PerformanceChart/performanceChart.helpers.js';
112
+ export { SPAGHETTI_LOCAL_LS_USER_SERIES_ROW_ID, SPAGHETTI_TIME_SERIES_MATRIX_V, tryParseSpaghettiPerformanceMatrix } from './components/widgets/PerformanceChart/performanceChartUserSeries.js';
107
113
  export { SybilionAppHeader } from './components/widgets/SybilionAppHeader/SybilionAppHeader.js';
108
114
  export { SybilionAuthLayout } from './components/widgets/SybilionAuthLayout/SybilionAuthLayout.js';
109
115
  export { SybilionAuthHeadline } from './components/widgets/SybilionAuthLayout/SybilionAuthHeadline.js';
@@ -0,0 +1,18 @@
1
+ import type { ChartDataPoint } from '#uilib/components/ui/ChartAreaInteractive/ChartAreaInteractive.types';
2
+ import type { BacktestsComponentPayload } from '@sybilion/platform-sdk';
3
+ export type DriversComparisonChartProps = {
4
+ payload: BacktestsComponentPayload | null;
5
+ datasetHistorical?: ChartDataPoint[];
6
+ loading?: boolean;
7
+ chartLoading?: boolean;
8
+ statusHint?: string | null;
9
+ statusTone?: 'destructive' | 'muted';
10
+ runAnalysisHint?: boolean;
11
+ timeRange?: string;
12
+ onTimeRangeChange?: (range: string) => void;
13
+ isDarkTheme?: boolean;
14
+ className?: string;
15
+ /** Resets visible series when this key changes (e.g. selected analysis id). */
16
+ seriesInitKey?: string;
17
+ };
18
+ export declare function DriversComparisonChart({ payload, datasetHistorical, loading, chartLoading, statusHint, statusTone, runAnalysisHint, timeRange: timeRangeProp, onTimeRangeChange, isDarkTheme, className, seriesInitKey, }: DriversComparisonChartProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,26 @@
1
+ import type { ChartDataPoint } from '#uilib/components/ui/ChartAreaInteractive/ChartAreaInteractive.types';
2
+ import type { BacktestsComponentPayload } from '@sybilion/platform-sdk';
3
+ export declare const DRIVER_FORECAST_ID_BASE = 8000000;
4
+ export declare const INITIAL_VISIBLE_SERIES_COUNT = 3;
5
+ /** Months of historical context before the earliest forecast month (inclusive). */
6
+ export declare const DRIVER_COMPARISON_CHART_LEAD_MONTHS = 6;
7
+ export declare function formatSeriesImportance(value: number | null): string;
8
+ export declare function mergeBacktestsChartData(payload: BacktestsComponentPayload | null): ChartDataPoint[];
9
+ /** While drivers comparison is not loaded: dataset historical only. After load: normalized historical from target.normalized_series (see mergeBacktestsChartData) replaces it. */
10
+ export declare function mergeDatasetHistoricalWithBacktestsChartData(datasetHistorical: ChartDataPoint[], backtestsMerged: ChartDataPoint[]): ChartDataPoint[];
11
+ /**
12
+ * For each driver, first month with materially non-zero value; anchor = median of those months.
13
+ * (Min is too early with outliers; max matched logs where one driver started 2017-12 while most started 2015-01.)
14
+ */
15
+ export declare function getZoomAnchorMonthFromDriverMaterialStarts(points: ChartDataPoint[], forecastIds: number[]): {
16
+ anchor: string | null;
17
+ perDriverFirstMaterial: string[];
18
+ };
19
+ export declare function subtractMonthsFromMonthStart(date: string, count: number): string;
20
+ /**
21
+ * Backtests payload often starts at the same month for target + drivers, so xMin falls
22
+ * in a range with no rows. Prepend historical-only months from raw dataset series
23
+ * (scaled to the first normalized point) so the lead-in shows the target line alone.
24
+ */
25
+ export declare function prependHistoricalLeadFromDataset(points: ChartDataPoint[], datasetHistorical: ChartDataPoint[], xMin: string): ChartDataPoint[];
26
+ export declare function buildDriversComparisonChartData(mergedWithHistorical: ChartDataPoint[], datasetHistorical: ChartDataPoint[], forecastIds: number[]): ChartDataPoint[];
@@ -0,0 +1,2 @@
1
+ export { DriversComparisonChart, type DriversComparisonChartProps, } from './DriversComparisonChart';
2
+ export { buildDriversComparisonChartData, DRIVER_COMPARISON_CHART_LEAD_MONTHS, DRIVER_FORECAST_ID_BASE, formatSeriesImportance, INITIAL_VISIBLE_SERIES_COUNT, mergeBacktestsChartData, mergeDatasetHistoricalWithBacktestsChartData, } from './driversComparisonChart.helpers';
@@ -0,0 +1,7 @@
1
+ interface HorizonsSelectorProps {
2
+ selectedHorizon: string;
3
+ onHorizonChange: (horizon: string) => void;
4
+ availableHorizons: string[];
5
+ }
6
+ export declare function HorizonsSelector({ selectedHorizon, onHorizonChange, availableHorizons, }: HorizonsSelectorProps): import("react/jsx-runtime").JSX.Element;
7
+ export {};
@@ -0,0 +1,3 @@
1
+ export declare const METRICS_KEY: "24m";
2
+ export declare const MONTHS_24 = 24;
3
+ export declare const MONTH_NAMES: string[];
@@ -0,0 +1,54 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { ChartDataPoint } from '#uilib/components/ui/ChartAreaInteractive/ChartAreaInteractive.types';
3
+ import type { ForecastData } from '#uilib/types/forecast-data';
4
+ import { type SpaghettiPerformanceMatrixPayload } from './performanceChartUserSeries';
5
+ export type PerformanceViewTab = 'perHorizon' | 'spaghetti';
6
+ export type PerformanceChartPayload = Record<string, {
7
+ forecasts?: Record<string, Record<string, number>>;
8
+ metrics_history?: Record<string, {
9
+ mae?: Record<string, number>;
10
+ mape?: Record<string, number>;
11
+ }>;
12
+ }>;
13
+ export type PerformanceChartProps = {
14
+ performanceData: PerformanceChartPayload | null;
15
+ historicalData?: ChartDataPoint[];
16
+ combinedData?: ChartDataPoint[];
17
+ forecastData?: Record<string, ForecastData>;
18
+ userSeries?: {
19
+ id: number;
20
+ name?: string;
21
+ }[];
22
+ spaghettiUserSeries?: {
23
+ id: number;
24
+ name?: string;
25
+ }[];
26
+ customPerformanceMatrix?: SpaghettiPerformanceMatrixPayload | null;
27
+ customPerformanceLabel?: string | null;
28
+ loading?: boolean;
29
+ chartLoading?: boolean;
30
+ performanceSectionPending?: boolean;
31
+ isEmpty?: boolean;
32
+ perfFetchSettled?: boolean;
33
+ performanceDataLoading?: boolean;
34
+ performanceAnalysisId?: number | null;
35
+ statusHint?: string | null;
36
+ statusTone?: 'destructive' | 'muted';
37
+ runAnalysisHint?: boolean;
38
+ timeRange?: string;
39
+ onTimeRangeChange?: (range: string, meta?: {
40
+ viewTab: PerformanceViewTab;
41
+ }) => void;
42
+ isDarkTheme?: boolean;
43
+ className?: string;
44
+ seriesInitKey?: string;
45
+ toolbarStart?: ReactNode;
46
+ onEditCustomPerformance?: () => void;
47
+ showAddEditCustomDataButton?: boolean;
48
+ addEditCustomDataDisabled?: boolean;
49
+ hiddenSeries?: Set<string>;
50
+ onToggleLegendSeries?: (key: string) => void;
51
+ onEnsureSeriesVisible?: (key: string) => void;
52
+ onHiddenSeriesChange?: (update: Set<string> | ((prev: Set<string>) => Set<string>)) => void;
53
+ };
54
+ export declare function PerformanceChart({ performanceData, historicalData, combinedData, forecastData, userSeries, spaghettiUserSeries, customPerformanceMatrix, customPerformanceLabel, loading, chartLoading, performanceSectionPending, isEmpty, perfFetchSettled, performanceDataLoading, performanceAnalysisId, statusHint, statusTone, runAnalysisHint, timeRange: timeRangeProp, onTimeRangeChange, isDarkTheme, className, seriesInitKey, toolbarStart, onEditCustomPerformance, showAddEditCustomDataButton, addEditCustomDataDisabled, hiddenSeries: hiddenSeriesProp, onToggleLegendSeries, onEnsureSeriesVisible, onHiddenSeriesChange, }: PerformanceChartProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,31 @@
1
+ export interface ForecastModelData {
2
+ key: string;
3
+ mae: number;
4
+ mape: number;
5
+ }
6
+ export interface AdjustParameters {
7
+ procurementVolume: number;
8
+ variableRawMaterialCostShare: number;
9
+ controllableCosts: number;
10
+ currentForecastAccuracy: number;
11
+ expectedImprovement: number;
12
+ analystHourlyRate: number;
13
+ }
14
+ interface PerformanceTableProps {
15
+ forecastModels: ForecastModelData[];
16
+ adjustParameters: AdjustParameters;
17
+ onAdjustParametersChange: (params: AdjustParameters) => void;
18
+ /** Shown when custom spaghetti matrix is saved (localStorage). Metrics vs historical for selected horizon. */
19
+ customPerformance?: {
20
+ label: string;
21
+ mae: number;
22
+ mape: number;
23
+ } | null;
24
+ /** Opens custom performance dialog; used by the custom row edit control. */
25
+ onEditCustomPerformance?: () => void;
26
+ /** Last row with Add custom data (hidden when custom row already present); per-horizon + spaghetti. */
27
+ showAddEditCustomDataButton?: boolean;
28
+ addEditCustomDataDisabled?: boolean;
29
+ }
30
+ export declare function PerformanceTable({ forecastModels, adjustParameters, onAdjustParametersChange, customPerformance, onEditCustomPerformance, showAddEditCustomDataButton, addEditCustomDataDisabled, }: PerformanceTableProps): import("react/jsx-runtime").JSX.Element;
31
+ export {};
@@ -0,0 +1,20 @@
1
+ export type PerformanceUnderChartLegendRowProps = {
2
+ label: React.ReactNode;
3
+ /** Line color (CSS color string). */
4
+ lineColor: string;
5
+ /** Historical uses solid bar; forecast lines use dashed preview. */
6
+ lineStyle?: 'solid' | 'dashed';
7
+ hidden: boolean;
8
+ onToggle?: () => void;
9
+ };
10
+ export type PerformanceUnderChartLegendItemConfig = PerformanceUnderChartLegendRowProps & {
11
+ id: string;
12
+ };
13
+ export declare function PerformanceUnderChartLegendRow({ label, lineColor, lineStyle, hidden, onToggle, }: PerformanceUnderChartLegendRowProps): import("react/jsx-runtime").JSX.Element;
14
+ /** Renders rows from tab-agnostic config (labels + toggles supplied by parent). */
15
+ export declare function PerformanceUnderChartLegendFromItems({ items, }: {
16
+ items: PerformanceUnderChartLegendItemConfig[];
17
+ }): import("react/jsx-runtime").JSX.Element;
18
+ export declare function PerformanceUnderChartLegend({ children, }: {
19
+ children: React.ReactNode;
20
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,4 @@
1
+ export { PerformanceChart, type PerformanceChartPayload, type PerformanceChartProps, type PerformanceViewTab, } from './PerformanceChart';
2
+ export { type AdjustParameters, type ForecastModelData, PerformanceTable, } from './PerformanceTable';
3
+ export { SPAGHETTI_DRIFT_PER_HORIZON_ID_BASE, SPAGHETTI_MODEL_PER_HORIZON_ID_BASE, averageForecastErrorsVsHistoricalForMatrixColumn, buildDriftSpaghettiMatrixForCustomDialog, buildPerHorizonSpaghettiEntries, buildSpaghettiMergedChartData, calculateYRangeFromChartData, getForecastModelDisplayName, spaghettiGridFromHistoricalPreviousMonth, } from './performanceChart.helpers';
4
+ export { SPAGHETTI_LOCAL_LS_USER_SERIES_ROW_ID, SPAGHETTI_TIME_SERIES_MATRIX_V, tryParseSpaghettiPerformanceMatrix, type SpaghettiPerformanceMatrixPayload, } from './performanceChartUserSeries';
@@ -0,0 +1,212 @@
1
+ /**
2
+ * Helper functions for Performance Tab calculations
3
+ * Note: ROI/calculatons.ts is only for reference/example
4
+ */
5
+ import type { ChartDataPoint } from '#uilib/components/ui/ChartAreaInteractive/ChartAreaInteractive.types';
6
+ import type { ForecastData } from '#uilib/types/forecast-data';
7
+ import type { SpaghettiPerformanceMatrixPayload } from './performanceChartUserSeries';
8
+ export type RealBacktestsEntry = {
9
+ forecast_start: string;
10
+ forecast_end: string;
11
+ forecast_series: Record<string, number>;
12
+ };
13
+ /** Legacy: API backtest spaghetti lines used `SPAGHETTI_FORECAST_ID_BASE + i` (spaghetti view now uses drift per-horizon instead). */
14
+ export declare const SPAGHETTI_FORECAST_ID_BASE = 6000;
15
+ /** Model PER_HORIZON_TO_SPAGHETTI lines use `SPAGHETTI_MODEL_PER_HORIZON_ID_BASE + i`. */
16
+ export declare const SPAGHETTI_MODEL_PER_HORIZON_ID_BASE = 7000;
17
+ /** Drift PER_HORIZON_TO_SPAGHETTI lines use `SPAGHETTI_DRIFT_PER_HORIZON_ID_BASE + i`. */
18
+ export declare const SPAGHETTI_DRIFT_PER_HORIZON_ID_BASE = 8000;
19
+ export declare function isSpaghettiModelPerHorizonLineId(id: number): boolean;
20
+ export declare function isSpaghettiDriftPerHorizonLineId(id: number): boolean;
21
+ /**
22
+ * Converts `performance.model` / `performance.drift` per-horizon forecasts into synthetic
23
+ * backtest-shaped entries: each line is [horizon_1[i], …, horizon_n[i]] as date→value points
24
+ * (aligned by sorted key index per horizon).
25
+ */
26
+ export declare function buildPerHorizonSpaghettiEntries(forecastRoot: {
27
+ forecasts?: Record<string, Record<string, number>>;
28
+ } | null | undefined, horizonKeys: string[]): RealBacktestsEntry[];
29
+ /**
30
+ * Same row alignment as {@link buildPerHorizonSpaghettiEntries}: row `i` uses each horizon's
31
+ * i-th sorted forecast month; `dates[i]` is horizon_1's month at that row (Date column).
32
+ * Use this for custom dialog seed + “copy statistical baseline (drift)” prefill.
33
+ */
34
+ export declare function buildDriftSpaghettiMatrixForCustomDialog(driftRoot: {
35
+ forecasts?: Record<string, Record<string, number>>;
36
+ } | null | undefined, horizonKeys: string[]): {
37
+ dates: string[];
38
+ grid: number[][];
39
+ perHorizonDates: string[][];
40
+ } | null;
41
+ /**
42
+ * Prefill for custom performance when copying drift layout: each spaghetti row is flat at the
43
+ * historical value for the month before the earliest forecast month in that row (same anchor as
44
+ * chart connection-point logic).
45
+ */
46
+ export declare function spaghettiGridFromHistoricalPreviousMonth(perHorizonDates: string[][], horizonCount: number, historicalByDate: Map<string, number>): number[][];
47
+ /** Mean absolute error / MAPE vs historical for one horizon column of a spaghetti matrix (aligned by month). */
48
+ export declare function averageForecastErrorsVsHistoricalForMatrixColumn(matrix: SpaghettiPerformanceMatrixPayload, horizonColumnIndex: number, historicalByDate: Map<string, number>): {
49
+ mae: number;
50
+ mape: number;
51
+ } | null;
52
+ /** @deprecated Use {@link buildPerHorizonSpaghettiEntries} (same implementation). */
53
+ export declare const buildModelPerHorizonSpaghettiEntries: typeof buildPerHorizonSpaghettiEntries;
54
+ export declare function mergeSpaghettiMergedBases(a: {
55
+ mergedData: ChartDataPoint[];
56
+ seriesMeta: {
57
+ id: number;
58
+ label: string;
59
+ }[];
60
+ }, b: {
61
+ mergedData: ChartDataPoint[];
62
+ seriesMeta: {
63
+ id: number;
64
+ label: string;
65
+ }[];
66
+ }): {
67
+ mergedData: ChartDataPoint[];
68
+ seriesMeta: {
69
+ id: number;
70
+ label: string;
71
+ }[];
72
+ };
73
+ export declare function buildSpaghettiMergedChartData(entries: RealBacktestsEntry[], historicalChartData: ChartDataPoint[], idBase?: number): {
74
+ mergedData: ChartDataPoint[];
75
+ seriesMeta: {
76
+ id: number;
77
+ label: string;
78
+ }[];
79
+ };
80
+ /** Appends forecast-backed user series (from dataset context) onto spaghetti backtest lines. */
81
+ export declare function mergeSpaghettiUserSeriesFromForecastData(base: {
82
+ mergedData: ChartDataPoint[];
83
+ seriesMeta: {
84
+ id: number;
85
+ label: string;
86
+ }[];
87
+ }, userSeries: {
88
+ id: number;
89
+ name?: string;
90
+ }[], forecastData: Record<string, ForecastData>): {
91
+ mergedData: ChartDataPoint[];
92
+ seriesMeta: {
93
+ id: number;
94
+ label: string;
95
+ }[];
96
+ };
97
+ export declare function filterChartDataLast24Months(data: ChartDataPoint[], months: number): ChartDataPoint[];
98
+ /**
99
+ * Spaghetti chart x-range: from two months before the earliest forecast_start
100
+ * (per-horizon synthetic entries + mergeable user forecast series), inclusive.
101
+ * Falls back to last N months when no forecast candidates exist.
102
+ *
103
+ * When `historicalWindowFloor` is set (e.g. first month of per-horizon `filtered24mData`),
104
+ * the range start is floored to that month so the historical line matches the per-horizon tab.
105
+ *
106
+ * When `historicalWindowCeiling` is set (e.g. last month of `filtered24mData`), drops points after
107
+ * that month so spaghetti X matches per-horizon (per-horizon chart trims forecasts past last historical).
108
+ */
109
+ export declare function filterSpaghettiDataFromEarliestForecastStart(mergedData: ChartDataPoint[], entries: RealBacktestsEntry[], userSeries: {
110
+ id: number;
111
+ name?: string;
112
+ }[], forecastData: Record<string, ForecastData>,
113
+ /** Series meta from the first merged batch (e.g. drift) — used to skip duplicate user-series ids. */
114
+ firstBatchSeriesMeta: {
115
+ id: number;
116
+ label: string;
117
+ }[], fallbackMonths: number, extraForecastCandidates?: RealBacktestsEntry[], historicalWindowFloor?: string | null, historicalWindowCeiling?: string | null): ChartDataPoint[];
118
+ /**
119
+ * Ensure each spaghetti row has `historical` from {@link historicalChartData} when that month
120
+ * falls in the filtered window — matches per-horizon chart (actuals line), and fills months that
121
+ * exist in history but were missing after merge/filter.
122
+ */
123
+ export declare function mergeHistoricalIntoSpaghettiChartData(filteredRows: ChartDataPoint[], historicalChartData: ChartDataPoint[]): ChartDataPoint[];
124
+ /**
125
+ * Calculate Accuracy: 100% - MAPE
126
+ * @param mape - MAPE value as decimal (e.g., 0.0436 for 4.36%)
127
+ * @returns Accuracy percentage
128
+ */
129
+ export declare function calculateAccuracy(mape: number): number;
130
+ /**
131
+ * Format Accuracy as percentage
132
+ * @param value - Accuracy value
133
+ * @returns Formatted accuracy string (e.g., "97%")
134
+ */
135
+ export declare function formatAccuracy(value: number): string;
136
+ /**
137
+ * Format Error as "$/Ton"
138
+ * @param mae - MAE value from metrics_summary.24m.mae
139
+ * @returns Formatted error string (e.g., "0.05$/Ton")
140
+ */
141
+ export declare function formatError(mae: number): string;
142
+ /**
143
+ * Calculate ROI
144
+ * Reference: ROI/calculatons.ts for example logic
145
+ * @param totalBenefit - Total annual benefit
146
+ * @param operatingCost - Annual operating cost
147
+ * @returns ROI percentage
148
+ */
149
+ export declare function calculateROI(totalBenefit: number, operatingCost: number): number;
150
+ /**
151
+ * Calculate ROI Multiple
152
+ * Formula: ROI Multiple = ROI / 100
153
+ * Where ROI = (AB - IC) / IC × 100
154
+ * @param totalBenefit - Total annual benefit (AB)
155
+ * @param operatingCost - Annual operating cost (IC)
156
+ * @returns ROI multiple (e.g., 1.0 for 100% ROI, 2.0 for 200% ROI)
157
+ */
158
+ export declare function calculateROIMultiple(totalBenefit: number, operatingCost: number): number;
159
+ /**
160
+ * Format ROI as multiplier (xN format)
161
+ * Formula: ROI Multiple = ROI / 100
162
+ * Examples: 100% ROI = x1, 200% ROI = x2, 0% ROI = x0
163
+ * Note: Negative ROI (when benefit < cost) is displayed as x1
164
+ * @param totalBenefit - Total annual benefit (AB)
165
+ * @param operatingCost - Annual operating cost (IC)
166
+ * @returns Formatted ROI string (e.g., "x1.5")
167
+ */
168
+ export declare function formatROI(totalBenefit: number, operatingCost: number): string;
169
+ /**
170
+ * Calculate Benefit p/a
171
+ * Formula: Annual Benefit = PV × V × C × PI
172
+ * Where:
173
+ * - PV = Procurement Volume
174
+ * - V = Variable Raw Material Share (as decimal)
175
+ * - C = Controllable Cost Share (as decimal)
176
+ * - PI = Price Improvement = |FA_new - FA_current| × 0.2 (correlation factor)
177
+ * @param mae - MAE value (not used in calculation, kept for backward compatibility)
178
+ * @param mape - MAPE value (used to calculate actual forecast accuracy)
179
+ * @param adjustParams - Adjust parameters from dialog
180
+ * @param baselineAccuracy - Optional baseline accuracy for comparison (if provided, uses actual improvement)
181
+ * @returns Benefit per annum
182
+ */
183
+ export declare function calculateBenefit(mae: number, mape: number, adjustParams: {
184
+ procurementVolume: number;
185
+ variableRawMaterialCostShare: number;
186
+ controllableCosts: number;
187
+ currentForecastAccuracy: number;
188
+ expectedImprovement: number;
189
+ analystHourlyRate: number;
190
+ }, baselineAccuracy?: number): number;
191
+ /**
192
+ * Format Benefit as euros
193
+ * @param value - Benefit value
194
+ * @returns Formatted benefit string (e.g., "€616.640")
195
+ */
196
+ export declare function formatBenefit(value: number): string;
197
+ /**
198
+ * Map forecast model key to display name
199
+ * @param key - Forecast model key (e.g., "mean", "drift", "seasonal")
200
+ * @returns Display name (e.g., "Mean", "Drift", "Seasonal")
201
+ */
202
+ export declare function getForecastModelDisplayName(key: string): string;
203
+ /**
204
+ * Calculate Y range (min/max) from chart data points
205
+ * Extracts all numeric values from chart data points and calculates min/max with padding
206
+ * @param chartData - Array of chart data points
207
+ * @returns Object with yMin and yMax values
208
+ */
209
+ export declare function calculateYRangeFromChartData(chartData: Array<Record<string, unknown>>): {
210
+ yMin: number;
211
+ yMax: number;
212
+ };
@@ -0,0 +1,20 @@
1
+ import type { ForecastData } from '#uilib/types/forecast-data';
2
+ export declare const SPAGHETTI_TIME_SERIES_MATRIX_V: 2;
3
+ export interface SpaghettiPerformanceMatrixPayload {
4
+ v: typeof SPAGHETTI_TIME_SERIES_MATRIX_V;
5
+ dates: string[];
6
+ horizonKeys: string[];
7
+ grid: number[][];
8
+ perHorizonDates?: string[][];
9
+ }
10
+ export declare const SPAGHETTI_MATRIX_SYNTHETIC_BASE = 9800000;
11
+ export declare const SPAGHETTI_MATRIX_MAX_COLS = 128;
12
+ export declare const SPAGHETTI_LOCAL_LS_USER_SERIES_ROW_ID = 9000001;
13
+ export declare function spaghettiMatrixSyntheticId(userSeriesRowId: number, horizonIndex: number): number;
14
+ export declare function isSpaghettiMatrixSyntheticLineId(id: number): boolean;
15
+ export declare function getCustomMatrixSeriesForHorizonTab(matrix: SpaghettiPerformanceMatrixPayload, horizonKey: string, rowId: number, forecastData: Record<string, ForecastData> | undefined): {
16
+ lineId: number;
17
+ dates: string[];
18
+ forecastValues: number[];
19
+ } | null;
20
+ export declare function tryParseSpaghettiPerformanceMatrix(parsed: unknown): SpaghettiPerformanceMatrixPayload | null;
@@ -0,0 +1 @@
1
+ export default function DriversComparisonChartPage(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1 @@
1
+ export default function PerformanceChartPage(): import("react/jsx-runtime").JSX.Element;
@@ -68,6 +68,8 @@ export * from './components/ui/WorkspaceAppSwitcher';
68
68
  export * from './components/widgets/SidebarDatasetsItemsGrouped';
69
69
  export * from './components/widgets/DriverCard';
70
70
  export * from './components/widgets/DriverMap';
71
+ export * from './components/widgets/DriversComparisonChart';
72
+ export * from './components/widgets/PerformanceChart';
71
73
  export * from './components/widgets/SybilionAppHeader';
72
74
  export * from './components/widgets/SybilionAuthLayout';
73
75
  export * from './components/widgets/SybilionSignInPanel';
@@ -101,5 +101,13 @@ function getPreviousMonth(date) {
101
101
  d.setMonth(d.getMonth() - 1);
102
102
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-01`;
103
103
  }
104
+ /**
105
+ * Get the next month date (first day of next month)
106
+ */
107
+ function getNextMonth(date) {
108
+ const d = new Date(date);
109
+ d.setMonth(d.getMonth() + 1);
110
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-01`;
111
+ }
104
112
 
105
- export { ensureChartForecastBridge, findLastValidHistoricalChartPoint, getPreviousMonth, normalizeToMonthStart };
113
+ export { ensureChartForecastBridge, findLastValidHistoricalChartPoint, getNextMonth, getPreviousMonth, normalizeToMonthStart };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sybilion/uilib",
3
- "version": "1.3.22",
3
+ "version": "1.3.25",
4
4
  "description": "Sybilion Design System — React UI components (Webpack + Stylus)",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -0,0 +1,145 @@
1
+ @import '../../../lib/theme.styl';
2
+
3
+ .root
4
+ display flex
5
+ flex-direction column
6
+ gap var(--p-4)
7
+ width 100%
8
+
9
+ .chartShell
10
+ position relative
11
+ width 100%
12
+
13
+ .chartShellLoading
14
+ .chartSlot
15
+ opacity 0.3
16
+ pointer-events none
17
+
18
+ :global(.recharts-line-dots)
19
+ display none
20
+
21
+ .chartSlot
22
+ width 100%
23
+
24
+ .loadingLayer
25
+ position absolute
26
+ inset 0
27
+ z-index 10
28
+ pointer-events none
29
+
30
+ .loadingMessage
31
+ position absolute
32
+ z-index 1
33
+ top calc(50% - 4em)
34
+ left 0
35
+ width 100%
36
+ display block
37
+ text-align center
38
+ color var(--foreground)
39
+
40
+ .loadingText
41
+ font-size 16px
42
+ font-weight 500
43
+ color var(--foreground)
44
+ border-radius var(--p-4)
45
+ padding 0 var(--p-3)
46
+ backdrop-filter blur(10px)
47
+ box-shadow 0 0 0 2px var(--page-color)
48
+
49
+ .chartWithOverlay
50
+ position relative
51
+ width 100%
52
+
53
+ .chartInteractiveLayer
54
+ width 100%
55
+ transition opacity 0.2s ease-out
56
+
57
+ .chartInteractiveDimmed
58
+ opacity 0.3
59
+ pointer-events none
60
+
61
+ :global(.recharts-line-dots)
62
+ display none
63
+
64
+ .chartEmptyOverlay
65
+ position absolute
66
+ inset 0
67
+ z-index 6
68
+ display flex
69
+ align-items center
70
+ justify-content center
71
+ padding var(--p-4)
72
+ pointer-events none
73
+
74
+ .chartEmptyBlurb
75
+ max-width 42rem
76
+ padding 0 var(--p-3)
77
+
78
+ .seriesEmptyWrap
79
+ display flex
80
+ justify-content center
81
+ padding var(--p-8) var(--p-4)
82
+
83
+ .seriesSection
84
+ width 100%
85
+
86
+ .seriesTableWrapper
87
+ position relative
88
+ margin 0 calc(var(--page-x-padding) * -1)
89
+
90
+ .seriesTableContainer
91
+ position relative
92
+ width 100%
93
+ overflow-x auto
94
+
95
+ .seriesScrollbar
96
+ bottom calc(var(--p-7) * -1 + 2px) !important
97
+
98
+ .seriesTable
99
+ table-layout auto
100
+ width 100%
101
+ max-width 100%
102
+
103
+ tr
104
+ position relative
105
+ cursor pointer
106
+
107
+ td
108
+ th
109
+ &:not(:first-child)
110
+ text-align right
111
+
112
+ .seriesColSeries
113
+ text-align left
114
+ vertical-align middle
115
+
116
+ .rowHidden
117
+ opacity .4
118
+
119
+ .colorSwatch
120
+ display inline-flex
121
+ flex-shrink 0
122
+ width 10px
123
+ height 10px
124
+ margin-right var(--p-2)
125
+ border-radius 2px
126
+
127
+ .seriesLabel
128
+ min-width 0
129
+ max-width 60vw
130
+ padding var(--p-2) 0
131
+ line-clamp(3)
132
+ white-space break-spaces
133
+
134
+ .chartContainer
135
+ margin-left calc(-1 * var(--page-x-padding) + 26px)
136
+ width calc(100% + 60px)
137
+ max-width @width
138
+ transition opacity 300ms ease-out
139
+
140
+ :global(.recharts-yAxis-tick-labels)
141
+ display none
142
+
143
+ @media (max-width: unit(MOBILE, 'px'))
144
+ margin-left calc(-1 * var(--page-x-padding) + 10px)
145
+ width calc(100% + 30px)
@@ -0,0 +1,29 @@
1
+ // This file is automatically generated.
2
+ // Please do not change this file!
3
+ interface CssExports {
4
+ 'chartContainer': string;
5
+ 'chartEmptyBlurb': string;
6
+ 'chartEmptyOverlay': string;
7
+ 'chartInteractiveDimmed': string;
8
+ 'chartInteractiveLayer': string;
9
+ 'chartShell': string;
10
+ 'chartShellLoading': string;
11
+ 'chartSlot': string;
12
+ 'chartWithOverlay': string;
13
+ 'colorSwatch': string;
14
+ 'loadingLayer': string;
15
+ 'loadingMessage': string;
16
+ 'loadingText': string;
17
+ 'root': string;
18
+ 'rowHidden': string;
19
+ 'seriesColSeries': string;
20
+ 'seriesEmptyWrap': string;
21
+ 'seriesLabel': string;
22
+ 'seriesScrollbar': string;
23
+ 'seriesSection': string;
24
+ 'seriesTable': string;
25
+ 'seriesTableContainer': string;
26
+ 'seriesTableWrapper': string;
27
+ }
28
+ export const cssExports: CssExports;
29
+ export default cssExports;