个人编程网站

进销存(JXC)软件开发技术积累与分享

进销存系统智能销售预测与需求预测算法

销售预测概述

准确的销售预测是进销存系统优化库存周转、降低运营成本的关键。通过对历史销售数据的分析和机器学习算法的应用,可以实现更准确的需求预测,为采购决策提供数据支撑。本文介绍进销存系统中智能销售预测的技术实现方案。

系统架构设计

销售预测系统采用数据驱动的架构设计:

层次 功能描述 技术选型
数据层 销售数据、库存数据、外部数据采集 MySQL、Python
特征层 特征工程、特征选择、数据预处理 Pandas、NumPy
模型层 时序预测、机器学习模型训练与推理 Prophet、XGBoost
应用层 预测展示、采购建议、预警提醒 Koa.js、Web

核心算法实现

1. 时间序列特征提取

从销售数据中提取有效的特征:

// 特征工程服务
class FeatureEngineering {
  constructor() {
    this.holidays = this.getHolidayList();
  }

  // 提取时间序列特征
  extractTimeFeatures(date) {
    const d = new Date(date);
    return {
      year: d.getFullYear(),
      month: d.getMonth() + 1,
      day: d.getDate(),
      dayOfWeek: d.getDay(),
      dayOfYear: this.getDayOfYear(d),
      weekOfYear: this.getWeekOfYear(d),
      quarter: Math.floor((d.getMonth() + 3) / 3),
      isWeekend: d.getDay() === 0 || d.getDay() === 6,
      isHoliday: this.isHoliday(d),
      isMonthStart: d.getDate() === 1,
      isMonthEnd: d.getDate() === this.getDaysInMonth(d)
    };
  }

  // 滞后特征
  createLagFeatures(data, lags = [1, 7, 14, 30]) {
    const result = data.map((item, index) => {
      const newItem = { ...item };
      for (const lag of lags) {
        if (index >= lag) {
          newItem[`lag_${lag}`] = data[index - lag].sales;
        }
      }
      return newItem;
    });
    return result;
  }

  // 滚动统计特征
  createRollingFeatures(data, windows = [7, 14, 30]) {
    return data.map((item, index) => {
      const newItem = { ...item };
      for (const window of windows) {
        if (index >= window) {
          const windowData = data.slice(index - window, index);
          const sales = windowData.map(d => d.sales);
          newItem[`rolling_mean_${window}`] = this.mean(sales);
          newItem[`rolling_std_${window}`] = this.std(sales);
          newItem[`rolling_max_${window}`] = Math.max(...sales);
          newItem[`rolling_min_${window}`] = Math.min(...sales);
        }
      }
      return newItem;
    });
  }

  // 同比环比特征
  createYoYFeatures(data, productId) {
    const productData = data.filter(d => d.productId === productId);
    return productData.map((item, index) => {
      const newItem = { ...item };
      // 去年同月
      const lastYear = productData.find(d =>
        d.date.getFullYear() === item.date.getFullYear() - 1 &&
        d.date.getMonth() === item.date.getMonth()
      );
      if (lastYear) {
        newItem.yoy_sales = lastYear.sales;
        newItem.yoy_growth = (item.sales - lastYear.sales) / lastYear.sales;
      }
      // 上月
      const lastMonth = productData.find(d =>
        d.date.getFullYear() === item.date.getFullYear() &&
        d.date.getMonth() === item.date.getMonth() - 1
      );
      if (lastMonth) {
        newItem.mom_sales = lastMonth.sales;
        newItem.mom_growth = (item.sales - lastMonth.sales) / lastMonth.sales;
      }
      return newItem;
    });
  }

  // 获取节假日的日期列表
  getHolidayList() {
    // 这里可以从配置文件或API获取
    return [
      '2025-01-01', // 元旦
      '2025-02-10', // 春节
      '2025-04-04', // 清明节
      '2025-05-01', // 劳动节
      '2025-06-10', // 端午节
    ];
  }

  isHoliday(date) {
    const dateStr = date.toISOString().split('T')[0];
    return this.holidays.includes(dateStr);
  }

  mean(arr) {
    return arr.reduce((a, b) => a + b, 0) / arr.length;
  }

  std(arr) {
    const m = this.mean(arr);
    return Math.sqrt(arr.reduce((sq, n) => sq + Math.pow(n - m, 2), 0) / arr.length);
  }

  getDayOfYear(date) {
    const start = new Date(date.getFullYear(), 0, 0);
    const diff = date - start;
    return Math.floor(diff / (1000 * 60 * 60 * 24));
  }

  getWeekOfYear(date) {
    const start = new Date(date.getFullYear(), 0, 1);
    const diff = date - start;
    return Math.ceil((diff + start.getDay() + 1) / (7 * 24 * 3600 * 1000));
  }

  getDaysInMonth(date) {
    return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();
  }
}

2. Prophet 模型预测

使用 Facebook Prophet 进行销售预测:

// Prophet 预测服务
class SalesForecastService {
  constructor() {
    this.model = null;
    this.productModels = new Map();
  }

  // 准备训练数据
  prepareTrainingData(salesData) {
    return salesData.map(item => ({
      ds: new Date(item.date),
      y: item.sales,
      product_id: item.productId,
      category: item.category,
      price: item.price,
      promotion: item.promotion || 0
    }));
  }

  // 训练单个产品模型
  async trainProductModel(productId, salesData) {
    const data = this.prepareTrainingData(salesData);

    // 配置 Prophet 参数
    const config = {
      growth: 'linear',
      yearly_seasonality: true,
      weekly_seasonality: true,
      daily_seasonality: false,
      holidays: this.getHolidayFeatures(),
      changepoint_prior_scale: 0.05,
      seasonality_prior_scale: 10,
      seasonality_mode: 'multiplicative'
    };

    // 训练模型
    const model = new Prophet(config);
    model.add regressor('promotion');

    // 添加外部 regressor
    model.fit(data.map(d => ({
      ds: d.ds,
      y: d.y,
      promotion: d.promotion
    })));

    this.productModels.set(productId, model);
    return model;
  }

  // 预测未来销售
  async forecast(productId, days = 30) {
    const model = this.productModels.get(productId);
    if (!model) {
      throw new Error(`Model for product ${productId} not found`);
    }

    // 生成预测日期
    const future = [];
    const today = new Date();
    for (let i = 0; i < days; i++) {
      const date = new Date(today);
      date.setDate(date.getDate() + i);
      future.push({ ds: date, promotion: 0 });
    }

    // 进行预测
    const forecast = model.predict(future);

    return forecast.map(item => ({
      date: item.ds,
      predicted: item.yhat,
      lower: item.yhat_lower,
      upper: item.yhat_upper,
      trend: item.trend,
      weekly: item.weekly || 0,
      yearly: item.yearly || 0
    }));
  }

  // 批量预测
  async batchForecast(productIds, days = 30) {
    const results = {};
    for (const productId of productIds) {
      try {
        results[productId] = await this.forecast(productId, days);
      } catch (error) {
        console.error(`Failed to forecast product ${productId}:`, error);
        results[productId] = null;
      }
    }
    return results;
  }

  // 模型评估
  evaluateModel(productId, testData) {
    const model = this.productModels.get(productId);
    if (!model) return null;

    const predictions = model.predict(testData.map(d => ({ ds: d.date })));
    const actual = testData.map(d => d.sales);
    const predicted = predictions.map(p => p.yhat);

    return {
      mae: this.calculateMAE(actual, predicted),
      mape: this.calculateMAPE(actual, predicted),
      rmse: this.calculateRMSE(actual, predicted)
    };
  }

  calculateMAE(actual, predicted) {
    const n = actual.length;
    const sum = actual.reduce((acc, val, i) => acc + Math.abs(val - predicted[i]), 0);
    return sum / n;
  }

  calculateMAPE(actual, predicted) {
    const n = actual.length;
    const sum = actual.reduce((acc, val, i) => acc + Math.abs((val - predicted[i]) / val), 0);
    return sum / n * 100;
  }

  calculateRMSE(actual, predicted) {
    const n = actual.length;
    const sum = actual.reduce((acc, val, i) => acc + Math.pow(val - predicted[i], 2), 0);
    return Math.sqrt(sum / n);
  }
}

3. XGBoost 机器学习预测

使用 XGBoost 进行多因素销售预测:

// XGBoost 预测模型
class XGBoostForecastModel {
  constructor() {
    this.model = null;
    this.featureColumns = [];
  }

  // 准备训练数据
  prepareData(salesData) {
    const featureEngineer = new FeatureEngineering();

    // 提取特征
    const features = salesData.map(item => {
      const timeFeatures = featureEngineer.extractTimeFeatures(item.date);
      return {
        ...timeFeatures,
        price: item.price,
        promotion: item.promotion || 0,
        inventory: item.inventory || 0,
        competitor_price: item.competitorPrice || 0,
        temperature: item.temperature || 20,
        category_encoded: this.encodeCategory(item.category)
      };
    });

    this.featureColumns = Object.keys(features[0]);

    const X = features.map(f => Object.values(f));
    const y = salesData.map(d => d.sales);

    return { X, y };
  }

  // 训练模型
  async train(salesData) {
    const { X, y } = this.prepareData(salesData);

    this.model = xgboost.train({
      objective: 'reg:squarederror',
      max_depth: 6,
      learning_rate: 0.1,
      n_estimators: 100,
      min_child_weight: 1,
      subsample: 0.8,
      colsample_bytree: 0.8
    }, X, y);

    return this.model;
  }

  // 预测
  predict(inputData) {
    if (!this.model) {
      throw new Error('Model not trained');
    }

    const featureEngineer = new FeatureEngineering();
    const features = {
      ...featureEngineer.extractTimeFeatures(inputData.date),
      price: inputData.price,
      promotion: inputData.promotion || 0,
      inventory: inputData.inventory || 0,
      competitor_price: inputData.competitorPrice || 0,
      temperature: inputData.temperature || 20,
      category_encoded: this.encodeCategory(inputData.category)
    };

    const X = [this.featureColumns.map(col => features[col])];
    return this.model.predict(X)[0];
  }

  // 特征重要性
  getFeatureImportance() {
    const importance = this.model.getScore(importanceType: 'gain');
    return Object.entries(importance)
      .map(([feature, score]) => ({ feature, score }))
      .sort((a, b) => b.score - a.score);
  }

  // 模型序列化
  saveModel(path) {
    this.model.saveModel(path);
  }

  // 模型加载
  loadModel(path) {
    this.model = xgboost.Booster({ nthread: 4 });
    this.model.loadModel(path);
  }

  encodeCategory(category) {
    const categoryMap = {
      '电子产品': 1,
      '服装': 2,
      '食品': 3,
      '家居': 4,
      '图书': 5
    };
    return categoryMap[category] || 0;
  }
}

预测结果应用

将预测结果应用到采购决策:

// 采购建议生成
class PurchaseRecommendation {
  constructor(forecastService, inventoryService) {
    this.forecastService = forecastService;
    this.inventoryService = inventoryService;
  }

  // 生成采购建议
  async generateRecommendations(productIds, forecastDays = 30) {
    const recommendations = [];

    for (const productId of productIds) {
      // 获取销售预测
      const forecast = await this.forecastService.forecast(productId, forecastDays);
      const totalPredicted = forecast.reduce((sum, f) => sum + f.predicted, 0);

      // 获取当前库存
      const currentInventory = await this.inventoryService.getInventory(productId);

      // 获取安全库存
      const safetyStock = await this.calculateSafetyStock(productId);

      // 计算建议采购量
      const recommendedQty = Math.max(0,
        Math.ceil(totalPredicted) - currentInventory + safetyStock
      );

      // 计算建议采购日期
      const runOutDate = await this.estimateRunOutDate(productId, currentInventory);
      const purchaseDate = new Date(runOutDate);
      purchaseDate.setDate(purchaseDate.getDate() - 7); // 提前7天采购

      recommendations.push({
        productId,
        predictedSales: Math.ceil(totalPredicted),
        currentInventory,
        safetyStock,
        recommendedQty,
        runOutDate,
        recommendedPurchaseDate: purchaseDate,
        urgency: this.calculateUrgency(currentInventory, safetyStock)
      });
    }

    return recommendations.sort((a, b) => b.urgency - a.urgency);
  }

  // 计算安全库存
  async calculateSafetyStock(productId) {
    const sales = await this.getHistoricalSales(productId, 90);
    const avgDaily = sales.reduce((a, b) => a + b, 0) / 90;
    const stdDev = this.calculateStdDev(sales);

    // 安全库存 = Z值 * 标准差 * sqrt(补货周期)
    const zValue = 1.65; // 95%服务水平
    const leadTime = 7; // 7天补货周期

    return Math.ceil(zValue * stdDev * Math.sqrt(leadTime));
  }

  calculateStdDev(arr) {
    const mean = arr.reduce((a, b) => a + b, 0) / arr.length;
    const squareDiffs = arr.map(val => Math.pow(val - mean, 2));
    return Math.sqrt(squareDiffs.reduce((a, b) => a + b, 0) / arr.length);
  }
}

模型选择策略

不同场景下的模型选择建议:

场景 推荐模型 原因
季节性商品 Prophet 内置季节性分解
多因素影响 XGBoost 支持多特征输入
新品预测 相似品参考 无历史数据
促销活动 ARIMA + 事件 处理异常波动

总结

智能销售预测是进销存系统智能化的重要组成部分,通过合理选择预测算法和特征工程,可以显著提升预测准确率。在实际应用中,需要根据业务特点选择合适的模型,并持续优化和迭代,以适应市场变化。

← 下一篇:进销存系统库存优化与成本控制策略