mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 15:45:24 +02:00
Fix Seasonality: Use Global Catalog curve as fallback for sparse ASIN history
This commit is contained in:
+45
-13
@@ -1625,7 +1625,7 @@ export const calculateForecastViewData = (
|
||||
const data2026 = rawData.filter(r => r.year === 2026);
|
||||
|
||||
// Calculate Seasonality weights for 2025
|
||||
const getWeights = (records: SalesRecord[]) => {
|
||||
const getWeights = (records: SalesRecord[]): number[] | null => {
|
||||
const weights = new Array(12).fill(0);
|
||||
let total = 0;
|
||||
const seenMonths = new Set<string>();
|
||||
@@ -1640,26 +1640,44 @@ export const calculateForecastViewData = (
|
||||
}
|
||||
});
|
||||
|
||||
// Safety Fallback: If we have sparse data (e.g., only Jan loaded),
|
||||
// using it as 100% seasonality skews the forecast entirely to that month.
|
||||
// We require at least 4 months of history to trust the curve; otherwise, we assume flat seasonality (1/12).
|
||||
if (total === 0 || seenMonths.size < 4) {
|
||||
return new Array(12).fill(1 / 12);
|
||||
}
|
||||
// 1. No data -> Fallback
|
||||
if (total === 0) return null;
|
||||
|
||||
// 2. Sparse Data Check (< 4 months)
|
||||
// If an ASIN has very little history (e.g. only Jan), using its own curve implies 100% seasonality in Jan.
|
||||
// The user requested to use the "General Catalog Seasonality" in these cases.
|
||||
if (seenMonths.size < 4) return null;
|
||||
|
||||
return weights.map(w => w / total);
|
||||
};
|
||||
|
||||
// 1. Determine Global/Default Weights
|
||||
const panEuData2025 = data2025.filter(r => PAN_EU_COUNTRIES.includes(r.customer));
|
||||
const panEuWeights = getWeights(panEuData2025);
|
||||
// For Global Weights, we do NOT return null on sparse data (we accept whatever we have for the whole catalog)
|
||||
// We recreate a simple version of getWeights that doesn't return null for the global set
|
||||
const getGlobalWeightsInner = (records: SalesRecord[]) => {
|
||||
const weights = new Array(12).fill(0);
|
||||
let total = 0;
|
||||
records.forEach(r => {
|
||||
const m = r.month.split('-')[0];
|
||||
const idx = MONTH_ORDER.indexOf(m);
|
||||
if (idx !== -1) {
|
||||
weights[idx] += r.units;
|
||||
total += r.units;
|
||||
}
|
||||
});
|
||||
return total === 0 ? new Array(12).fill(1 / 12) : weights.map(w => w / total);
|
||||
};
|
||||
|
||||
const panEuWeights = getGlobalWeightsInner(panEuData2025);
|
||||
|
||||
// Check if we are in UK-only mode
|
||||
const isUkOnly = filters?.customer?.includes('Amazon UK') && filters.customer.length === 1;
|
||||
|
||||
const getHybridWeights = (paEuRecords: SalesRecord[], ukRecords: SalesRecord[]) => {
|
||||
const peWeights = getWeights(paEuRecords);
|
||||
const ukWeights = getWeights(ukRecords);
|
||||
// Use Inner helper to ensure we always get weights for global subsets
|
||||
const peWeights = getGlobalWeightsInner(paEuRecords);
|
||||
const ukWeights = getGlobalWeightsInner(ukRecords);
|
||||
|
||||
// Blend: Jan-Aug from Pan-EU, Sep-Dec from UK
|
||||
const hybrid = new Array(12).fill(0);
|
||||
@@ -1711,11 +1729,25 @@ export const calculateForecastViewData = (
|
||||
|
||||
if (productRecords2025.length > 0) {
|
||||
if (isUkOnly) {
|
||||
const peProd = productRecords2025.filter(r => PAN_EU_COUNTRIES.includes(r.customer));
|
||||
// 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');
|
||||
productWeights = getHybridWeights(peProd.length > 0 ? peProd : panEuData2025, ukProd);
|
||||
// We use hybrid approach only if we have PanEU data for this product too
|
||||
const peProd = productRecords2025.filter(r => PAN_EU_COUNTRIES.includes(r.customer));
|
||||
|
||||
// If we have solid data for this product, use Hybrid or UK weights
|
||||
const specificWeights = getHybridWeights(peProd, ukProd);
|
||||
// Wait, getHybridWeights calls getGlobalWeightsInner which never returns null.
|
||||
// We need to check if specific product data is sparse.
|
||||
|
||||
// Let's simplify: Check if we have enough UK history
|
||||
const ukWeights = getWeights(ukProd);
|
||||
if (ukWeights) {
|
||||
productWeights = ukWeights;
|
||||
}
|
||||
} else {
|
||||
productWeights = getWeights(productRecords2025);
|
||||
const w = getWeights(productRecords2025);
|
||||
if (w) productWeights = w;
|
||||
// If w is null (sparse data), productWeights remains globalWeights (Default)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user