mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 14:45:24 +02:00
Implement Smart Seasonality blending for products with sparse history (fixes 0-forecast issue)
This commit is contained in:
+26
-19
@@ -1625,16 +1625,18 @@ export const calculateForecastViewData = (
|
|||||||
const data2026 = rawData.filter(r => r.year === 2026);
|
const data2026 = rawData.filter(r => r.year === 2026);
|
||||||
|
|
||||||
// Calculate Seasonality weights for 2025
|
// Calculate Seasonality weights for 2025
|
||||||
const getWeights = (records: SalesRecord[]): number[] | null => {
|
const getWeightsInfo = (records: SalesRecord[]): { weights: number[]; monthsCount: number } | null => {
|
||||||
const weights = new Array(12).fill(0);
|
const weights = new Array(12).fill(0);
|
||||||
let total = 0;
|
let total = 0;
|
||||||
|
const seenMonths = new Set<string>();
|
||||||
|
|
||||||
records.forEach(r => {
|
records.forEach(r => {
|
||||||
const m = r.month.split('-')[0];
|
const m = r.month.split('-')[0];
|
||||||
const idx = MONTH_ORDER.indexOf(m);
|
const idx = MONTH_ORDER.indexOf(m);
|
||||||
if (idx !== -1) {
|
if (idx !== -1 && r.units > 0) {
|
||||||
weights[idx] += r.units;
|
weights[idx] += r.units;
|
||||||
total += r.units;
|
total += r.units;
|
||||||
|
seenMonths.add(m);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1642,7 +1644,10 @@ export const calculateForecastViewData = (
|
|||||||
// Return null ONLY if there's no data at all for this ASIN in 2025.
|
// Return null ONLY if there's no data at all for this ASIN in 2025.
|
||||||
if (total === 0) return null;
|
if (total === 0) return null;
|
||||||
|
|
||||||
return weights.map(w => w / total);
|
return {
|
||||||
|
weights: weights.map(w => w / total),
|
||||||
|
monthsCount: seenMonths.size
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// 1. Determine Global/Default Weights
|
// 1. Determine Global/Default Weights
|
||||||
@@ -1734,25 +1739,27 @@ export const calculateForecastViewData = (
|
|||||||
|
|
||||||
if (productRecords2025.length > 0) {
|
if (productRecords2025.length > 0) {
|
||||||
if (isUkOnly) {
|
if (isUkOnly) {
|
||||||
// For UK Only, we try to be strict, but fallback to global UK weights if needed
|
|
||||||
const ukProd = productRecords2025.filter(r => r.customer === 'Amazon UK');
|
const ukProd = productRecords2025.filter(r => r.customer === 'Amazon UK');
|
||||||
// We use hybrid approach only if we have PanEU data for this product too
|
const info = getWeightsInfo(ukProd);
|
||||||
const peProd = productRecords2025.filter(r => PAN_EU_COUNTRIES.includes(r.customer));
|
if (info) {
|
||||||
|
if (info.monthsCount < 6) {
|
||||||
// If we have solid data for this product, use Hybrid or UK weights
|
// Smart Blending: 50% specific, 50% global
|
||||||
const specificWeights = getHybridWeights(peProd, ukProd);
|
productWeights = info.weights.map((w, i) => (w * 0.5) + (globalWeights[i] * 0.5));
|
||||||
// Wait, getHybridWeights calls getGlobalWeightsInner which never returns null.
|
} else {
|
||||||
// We need to check if specific product data is sparse.
|
productWeights = info.weights;
|
||||||
|
}
|
||||||
// Let's simplify: Check if we have enough UK history
|
|
||||||
const ukWeights = getWeights(ukProd);
|
|
||||||
if (ukWeights) {
|
|
||||||
productWeights = ukWeights;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const w = getWeights(productRecords2025);
|
const info = getWeightsInfo(productRecords2025);
|
||||||
if (w) productWeights = w;
|
if (info) {
|
||||||
// If w is null (sparse data), productWeights remains globalWeights (Default)
|
if (info.monthsCount < 6) {
|
||||||
|
// Smart Blending: 50% specific, 50% global
|
||||||
|
productWeights = info.weights.map((w, i) => (w * 0.5) + (globalWeights[i] * 0.5));
|
||||||
|
} else {
|
||||||
|
productWeights = info.weights;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If info is null (sparse data), productWeights remains globalWeights (Default)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user