Skip to main content

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
  • : 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-3Normal consumption pattern. No immediate action needed.

Normal

Days 4-7, Qty ≥ 10Regular depletion. Monitor for reorder needs.

Critical

Any day, Qty < 10Low 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:
ModelScoreVisualNotes
Ensemble85%Solid linePrimary forecast signal
Linear Regression82%Dotted lineTrend fitting
Moving Average79%Dashed lineNoise 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=673ab12c5f8e9a001234abcd&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:
  1. Historical Data: Requires at least 7 days of data
  2. Initial Quantity: Estimated rather than tracked
  3. Seasonality: Does not account for seasonal patterns
  4. External Factors: Cannot predict market disruptions
  5. Promotions: Does not factor in promotional impacts

Best Practices

1

Regular Review

Review forecasts daily for critical items, weekly for normal stock
2

Adjust Safety Stock

Increase safety stock for items with high forecast uncertainty
3

Monitor Accuracy

Track actual vs. predicted consumption to improve models
4

Combine with FEFO

Use both forecasting and FEFO to optimize inventory rotation
5

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

Build docs developers (and LLMs) love