Overview
SmartShelf’s demand forecasting system predicts future inventory requirements using historical consumption data and multiple forecasting algorithms. The system provides 7-day projections with trend indicators to help optimize purchasing decisions and prevent stockouts.
Forecasting requires at least 7 days of historical data for accurate predictions. New items will show “Insufficient historical data” until enough data is collected.
Forecasting Algorithms
The system employs three complementary forecasting methods:
1. Ensemble Model (Primary)
The main forecasting algorithm that combines multiple signals (analyticsController.js:24-42):
const daysSincePurchase = Math . ceil (
( new Date () - new Date ( product . purchaseDate )) / ( 1000 * 60 * 60 * 24 )
);
// Calculate daily consumption rate
const estimatedDailyConsumption = daysSincePurchase > 0
? ( product . quantity * 0.1 ) / daysSincePurchase
: product . quantity * 0.05 ;
// Generate 7-day forecast
for ( let i = 1 ; i <= 7 ; i ++ ) {
const projectedQty = Math . max ( 0 , product . quantity - ( estimatedDailyConsumption * i ));
const trend = i <= 3 ? 'stable' : projectedQty < 10 ? 'critical' : 'normal' ;
forecast . push ({
day: i ,
date: date . toISOString (). split ( 'T' )[ 0 ],
projectedQuantity: Math . round ( projectedQty ),
trend
});
}
2. Linear Regression
Trend-based forecasting using least squares method (ManagerDashboard.tsx:377-396):
const calcLinearRegression = ( y : number []) => {
const n = y . length ;
const x = Array . from ({ length: n }, ( _ , i ) => i + 1 );
const xMean = x . reduce (( a , b ) => a + b , 0 ) / n ;
const yMean = y . reduce (( a , b ) => a + b , 0 ) / n ;
let sxx = 0 , sxy = 0 , sst = 0 ;
for ( let i = 0 ; i < n ; i ++ ) {
const dx = x [ i ] - xMean ;
const dy = y [ i ] - yMean ;
sxx += dx * dx ;
sxy += dx * dy ;
sst += dy * dy ;
}
const slope = sxx === 0 ? 0 : sxy / sxx ;
const intercept = yMean - slope * xMean ;
const yhat = x . map ( xi => slope * xi + intercept );
const r2 = sst === 0 ? 1 : clamp ( 1 - sse / sst , 0 , 1 );
return { slope , intercept , yhat , r2 };
};
Key Metrics:
Slope : Rate of change in consumption
Intercept : Initial quantity estimate
R² : Model accuracy (0-1, higher is better)
3. Moving Average
Smoothing algorithm to reduce noise (ManagerDashboard.tsx:399-408):
const calcMovingAverage = ( y : number [], window = 3 ) => {
const n = y . length ;
const out : number [] = new Array ( n ). fill ( 0 );
for ( let i = 0 ; i < n ; i ++ ) {
const start = Math . max ( 0 , i - window + 1 );
const slice = y . slice ( start , i + 1 );
out [ i ] = slice . reduce (( a , b ) => a + b , 0 ) / slice . length ;
}
return out ;
};
Window Size : 3 days (configurable)
Daily Consumption Rate
The foundation of all forecasts is the consumption rate calculation (forecastController.js:23-52):
const daysSincePurchase = Math . ceil (
( new Date () - new Date ( product . purchaseDate )) / ( 1000 * 60 * 60 * 24 )
);
const estimatedInitialQty = product . quantity * 2 ; // Initial quantity estimate
const consumptionRate = daysSincePurchase > 0
? ( estimatedInitialQty - product . quantity ) / daysSincePurchase
: 0 ;
The system estimates initial quantity as 2× current quantity. For more accurate forecasting, implement inventory history tracking.
7-Day Projections
Forecast Structure
Each product gets a detailed 7-day forecast:
{
"product" : {
"id" : "673ab12c5f8e9a001234abcd" ,
"name" : "Fresh Milk" ,
"category" : "Dairy" ,
"sku" : "DA-001" ,
"currentQuantity" : 150
},
"forecast" : [
{
"day" : 1 ,
"date" : "2025-11-16" ,
"projectedQuantity" : 145 ,
"trend" : "stable"
},
{
"day" : 7 ,
"date" : "2025-11-22" ,
"projectedQuantity" : 115 ,
"trend" : "normal"
}
],
"estimatedDailyConsumption" : 5.2
}
Trend Indicators
Three trend classifications based on projected quantities:
Stable Days 1-3 Normal consumption pattern. No immediate action needed.
Normal Days 4-7, Qty ≥ 10 Regular depletion. Monitor for reorder needs.
Critical Any day, Qty < 10 Low stock projected. Immediate reorder recommended.
const trend = i <= 3
? 'stable'
: projectedQty < 10
? 'critical'
: 'normal' ;
Interactive Forecast Graphs
The Manager Dashboard displays interactive SVG charts (ManagerDashboard.tsx:573-638):
Product Selection Interface
Tab-based product selector:
< div className = "flex gap-3 mb-5 overflow-x-auto" >
{ forecastData . map (( item , index ) => (
< button
onClick = {() => setSelectedForecast ( index )}
className = { selectedForecast === index
? 'border-primary bg-primary/10 ring-2 ring-primary/30'
: 'border-border-light hover:bg-slate-100'
}
>
< div className = "flex items-center justify-between" >
< div >
< p className = "text-xs text-slate-500" > {item.product. category } </ p >
< p className = "font-semibold" > {item.product. name } </ p >
</ div >
< div className = "text-right" >
< p className = "text-xs text-slate-500" > Stock </ p >
< p className = "text-lg font-bold" > {item.product. currentQuantity } </ p >
</ div >
</ div >
</ button >
))}
</ div >
Forecast Metrics Display
Four key metrics shown above the chart:
Current Stock Real-time quantity level {sel.product.currentQuantity}
Daily Consumption Average consumption rate {sel.estimatedDailyConsumption}
Forecast Accuracy Ensemble confidence score {toPercent(confEnsemble)}
Reorder Days Days until reorder needed {reorderIdx === -1 ? '> 7 days' : `${reorderIdx + 1} day(s)`}
SVG Chart Visualization
Three overlaid forecast lines:
< svg viewBox = "0 0 700 260" className = "w-full" >
{ /* Ensemble - Solid primary line */ }
< polyline
points = { pts ( ensemble )}
fill = "none"
stroke = "currentColor"
className = "text-primary"
strokeWidth = { 2.5 }
/>
{ /* Linear Regression - Dotted gray line */ }
< polyline
points = { pts (reg.yhat)}
stroke = "currentColor"
className = "text-slate-500"
strokeWidth = { 2 }
strokeDasharray = "2,6"
/>
{ /* Moving Average - Dashed blue line */ }
< polyline
points = { pts ( ma )}
stroke = "currentColor"
className = "text-sky-500"
strokeWidth = { 2 }
strokeDasharray = "6,6"
/>
{ /* Data points with tooltips */ }
{ ensemble . map (( v , i ) => (
< circle cx = { xFor ( i )} cy = { yFor ( v )} r = { 3.5 } fill = "currentColor" >
< title > Day { days [ i ]} • Ensemble : { v }</ title >
</ circle >
))}
</ svg >
Trend Analysis Panel
Below the chart, detailed trend metrics:
< div className = "grid grid-cols-1 md:grid-cols-3 gap-4" >
{ /* Trend Badge */ }
< div className = { trendBadge } >
< div className = "text-sm font-semibold" > Trend </ div >
< div className = "mt-1 text-xl font-bold" >
< span >{ trend === 'increasing' ? '🟢' : trend === 'declining' ? '🔴' : '🟡' } </ span >
< span className = "capitalize" > { trend } </ span >
< span >{ trendArrow } </ span >
</ div >
</ div >
{ /* Trend Strength */ }
< div >
< div className = "text-sm" > Trend Strength </ div >
< div className = "text-xl font-bold" > { toPercent ( trendStrength )} </ div >
</ div >
{ /* Regression R² */ }
< div >
< div className = "text-sm" > Regression R ² </ div >
< div className = "text-xl font-bold" > { toPercent (reg.r2 * 100)} </ div >
</ div >
</ div >
Trend Determination:
const pctChange = ( ensemble [ ensemble . length - 1 ] - ensemble [ 0 ]) / Math . max ( 1 , ensemble [ 0 ]);
const trend = pctChange > 0.02 ? 'increasing' : pctChange < - 0.02 ? 'declining' : 'stable' ;
const trendStrength = clamp ( Math . abs ( pctChange ) * 100 , 0 , 100 );
Forecast Accuracy Scores
The system calculates confidence scores using NRMSE (Normalized Root Mean Square Error):
const rmse = ( a : number [], b : number []) => {
const n = a . length ;
let s = 0 ;
for ( let i = 0 ; i < n ; i ++ ) s += ( a [ i ] - b [ i ]) ** 2 ;
return Math . sqrt ( s / Math . max ( 1 , n ));
};
const nrmseReg = rmse ( ensemble , reg . yhat ) / ( Math . max ( ... ensemble ) - Math . min ( ... ensemble ));
const confRegression = clamp (( 1 - nrmseReg ) * 100 , 0 , 100 );
Model Comparison Table:
Model Score Visual Notes Ensemble 85% Solid line Primary forecast signal Linear Regression 82% Dotted line Trend fitting Moving Average 79% Dashed line Noise smoothing
Reorder Point Detection
Automatic reorder recommendations (forecastController.js:163-233):
const reorderPoint = calculateReorderPoint (
consumptionRate ,
leadTimeDays ,
Math . ceil ( consumptionRate * leadTimeDays * ( safetyStockMultiplier - 1 ))
);
if ( item . quantity <= reorderPoint ) {
const suggestedOrderQty = Math . ceil ( consumptionRate * 30 ); // 30 days supply
suggestions . push ({
product: item ,
currentQuantity: item . quantity ,
reorderPoint: Math . round ( reorderPoint ),
suggestedOrderQuantity: suggestedOrderQty ,
urgency: item . quantity === 0 ? 'CRITICAL'
: item . quantity < reorderPoint / 2 ? 'HIGH'
: 'MEDIUM' ,
estimatedStockoutDays: Math . ceil ( item . quantity / consumptionRate )
});
}
API Endpoints
Get Demand Forecast
GET / api / analytics / demand - forecast ? limit = 5
Query Parameters:
limit - Number of products to forecast (default: 5)
Get Product Forecast
GET / api / forecast / demand ? productId = 673 ab12c5f8e9a001234abcd & months = 3
Query Parameters:
productId (required) - Product ID
months - Forecast period in months (default: 3)
Get Reorder Suggestions
GET / api / forecast / reorder - suggestions ? leadTime = 7 & safetyStock = 1.5
Query Parameters:
leadTime - Supplier lead time in days (default: 7)
safetyStock - Safety stock multiplier (default: 1.5)
Use Cases
Forecast helps determine optimal order quantities
Reduces overstocking and understocking
Improves cash flow by optimizing inventory investment
Supports just-in-time inventory strategies
Identify consumption patterns and trends
Prepare for peak demand periods
Adjust safety stock levels seasonally
Plan promotional activities based on forecast
Share forecasts with suppliers for better planning
Negotiate better terms based on predicted volumes
Reduce lead times through advance notice
Build stronger supplier relationships
Estimate future inventory costs
Plan working capital requirements
Forecast storage space needs
Support financial planning and analysis
Limitations
Current Limitations:
Historical Data : Requires at least 7 days of data
Initial Quantity : Estimated rather than tracked
Seasonality : Does not account for seasonal patterns
External Factors : Cannot predict market disruptions
Promotions : Does not factor in promotional impacts
Best Practices
Regular Review
Review forecasts daily for critical items, weekly for normal stock
Adjust Safety Stock
Increase safety stock for items with high forecast uncertainty
Monitor Accuracy
Track actual vs. predicted consumption to improve models
Combine with FEFO
Use both forecasting and FEFO to optimize inventory rotation
Document Exceptions
Record events that deviate from forecasts (promotions, stockouts)
FEFO Ordering Prioritize by expiry date
Alert System Low stock notifications
Inventory Management Track consumption data
Analytics Dashboard Visualize forecast insights