个人编程网站

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

进销存系统智能补货预测模型与库存优化策略

引言

库存管理是进销存系统的核心模块,直接影响企业资金占用和供货能力。传统的补货方式依赖人工经验,往往出现库存积压或缺货的问题。本文将介绍如何构建智能补货预测模型,实现基于数据驱动的自动补货策略。

预测模型架构

智能补货系统采用多模型融合的预测策略:

模型类型 适用场景 预测周期
时序预测模型 稳定销量的常规商品 7-30天
机器学习模型 受多因素影响的商品 7-90天
深度学习模型 复杂模式的商品 7-180天

核心算法实现

时序预测模型核心实现:

// 预测服务
class ForecastService {
  constructor(config) {
    this.models = new Map();
    this.modelConfig = config;
    this.metrics = new Map();
    this.initModels();
  }

  // 初始化预测模型
  initModels() {
    // ARIMA 模型 - 适用于稳定时序数据
    this.models.set('arima', {
      name: 'ARIMA',
      predict: this.arimaPredict.bind(this),
      train: this.arimaTrain.bind(this)
    });

    // XGBoost 模型 - 适用于多特征预测
    this.models.set('xgboost', {
      name: 'XGBoost',
      predict: this.xgboostPredict.bind(this),
      train: this.xgboostTrain.bind(this)
    });

    // Prophet 模型 - 适用于有周期性的数据
    this.models.set('prophet', {
      name: 'Prophet',
      predict: this.prophetPredict.bind(this),
      train: this.prophetTrain.bind(this)
    });

    // LSTM 深度学习模型
    this.models.set('lstm', {
      name: 'LSTM',
      predict: this.lstmPredict.bind(this),
      train: this.lstmTrain.bind(this)
    });
  }

  // 智能模型选择
  selectModel(productId, historicalData) {
    const stats = this.calculateStats(historicalData);

    // 销量波动系数
    const cv = stats.std / stats.mean;

    // 检查数据周期特征
    const hasSeasonality = this.detectSeasonality(historicalData);

    // 趋势检测
    const trend = this.detectTrend(historicalData);

    // 模型选择逻辑
    if (cv < 0.1 && !hasSeasonality && !trend) {
      // 销量稳定,使用简单模型
      return 'arima';
    } else if (cv < 0.3 && hasSeasonality) {
      // 有周期性,使用 Prophet
      return 'prophet';
    } else if (historicalData.length >= 200) {
      // 数据量充足,使用深度学习
      return 'lstm';
    } else {
      // 默认使用 XGBoost
      return 'xgboost';
    }
  }

  // 计算数据统计特征
  calculateStats(data) {
    const values = data.map(d => d.quantity);
    const mean = values.reduce((a, b) => a + b, 0) / values.length;
    const variance = values.reduce((sum, val) =>
      sum + Math.pow(val - mean, 2), 0) / values.length;
    const std = Math.sqrt(variance);

    // 计算季节性波动
    const seasonalPattern = this.extractSeasonalPattern(data);

    return { mean, variance, std, cv: std / mean, seasonalPattern };
  }

  // 检测周期性
  detectSeasonality(data) {
    if (data.length < 30) return false;

    // 使用自相关检测周期性
    const acf = this.calculateACF(data);

    // 检查是否存在显著峰值
    const peaks = this.findPeaks(acf);
    return peaks.some(p => p.correlation > 0.5);
  }

  // 计算自相关函数
  calculateACF(data, maxLag = 30) {
    const values = data.map(d => d.quantity);
    const n = values.length;
    const mean = values.reduce((a, b) => a + b, 0) / n;

    const acf = [];
    for (let lag = 0; lag <= maxLag; lag++) {
      let sum = 0;
      for (let i = 0; i < n - lag; i++) {
        sum += (values[i] - mean) * (values[i + lag] - mean);
      }
      acf.push({ lag, correlation: sum / (n * this.variance) });
    }

    return acf;
  }

  // 趋势检测
  detectTrend(data) {
    const n = data.length;
    const x = [...Array(n).keys()];
    const y = data.map(d => d.quantity);

    const xMean = x.reduce((a, b) => a + b, 0) / n;
    const yMean = y.reduce((a, b) => a + b, 0) / n;

    let numerator = 0;
    let denominator = 0;

    for (let i = 0; i < n; i++) {
      numerator += (x[i] - xMean) * (y[i] - yMean);
      denominator += Math.pow(x[i] - xMean, 2);
    }

    const slope = numerator / denominator;

    // 判断趋势显著性
    return Math.abs(slope) > 0.01;
  }

  // ARIMA 预测
  async arimaPredict(data, periods) {
    // 差分处理
    const diffData = this.differencing(data, 1);

    // 计算自回归系数
    const arCoeffs = this.calculateARCoeffs(diffData, 3);

    // 计算移动平均系数
    const maCoeffs = this.calculateMACoeffs(diffData, 2);

    // 预测
    const predictions = [];
    let lastValues = diffData.slice(-3);

    for (let i = 0; i < periods; i++) {
      let prediction = 0;

      // AR 部分
      for (let j = 0; j < arCoeffs.length; j++) {
        prediction += arCoeffs[j] * lastValues[lastValues.length - 1 - j];
      }

      // MA 部分
      for (let j = 0; j < maCoeffs.length; j++) {
        prediction += maCoeffs[j] * (Math.random() - 0.5);
      }

      lastValues.push(prediction);
      predictions.push(prediction);
    }

    // 逆差分
    return this.inverseDifferencing(predictions, data);
  }

  // XGBoost 多特征预测
  async xgboostPredict(productId, features, periods) {
    const model = this.models.get('xgboost');
    const predictions = [];

    // 准备特征
    const baseFeatures = {
      productId,
      dayOfWeek: new Date().getDay(),
      month: new Date().getMonth() + 1,
      quarter: Math.floor((new Date().getMonth() + 1) / 3) + 1,
      isHoliday: this.checkHoliday(new Date()),
      ...features
    };

    for (let i = 0; i < periods; i++) {
      const currentFeatures = {
        ...baseFeatures,
        dayOfWeek: (new Date().getDay() + i) % 7,
      };

      // 添加滞后特征
      for (let lag = 1; lag <= 7; lag++) {
        currentFeatures[`lag_${lag}`] = await this.getHistoricalData(
          productId, lag + i
        );
      }

      // 添加移动平均特征
      currentFeatures['ma_7'] = await this.calculateMA(productId, 7, i);
      currentFeatures['ma_14'] = await this.calculateMA(productId, 14, i);
      currentFeatures['ma_30'] = await this.calculateMA(productId, 30, i);

      const prediction = await model.predict(currentFeatures);
      predictions.push(prediction);
    }

    return predictions;
  }

  // Prophet 模型预测
  async prophetPredict(data, periods) {
    const { trend, seasonality, holiday } = this.decomposeTimeSeries(data);

    const predictions = [];
    const startDate = new Date();

    for (let i = 0; i < periods; i++) {
      const date = new Date(startDate);
      date.setDate(date.getDate() + i);

      // 趋势预测
      let prediction = trend * (1 + this.getTrendGrowthRate(date));

      // 周期性影响
      prediction *= (1 + seasonality.getDayOfWeek(date.getDay()));
      prediction *= (1 + seasonality.getMonth(date.getMonth()));

      // 节假日影响
      const holidayEffect = holiday.getEffect(date);
      prediction *= (1 + holidayEffect);

      predictions.push(Math.max(0, Math.round(prediction)));
    }

    return predictions;
  }

  // 模型集成预测
  async ensemblePredict(productId, historicalData, periods, weights = {}) {
    const predictions = [];

    // 获取各模型预测
    for (const [modelName, _] of this.models) {
      try {
        const pred = await this.predictWithModel(
          modelName, historicalData, periods
        );
        predictions.push({ model: modelName, pred });
      } catch (error) {
        console.error(`Model ${modelName} failed:`, error);
      }
    }

    // 计算各模型权重
    const modelWeights = weights[productId] || this.calculateModelWeights(predictions);

    // 加权平均
    let weightedSum = 0;
    let totalWeight = 0;

    for (const { model, pred } of predictions) {
      const weight = modelWeights[model] || 1;
      weightedSum += pred.reduce((a, b) => a + b, 0) / pred.length * weight;
      totalWeight += weight;
    }

    return weightedSum / totalWeight;
  }

  // 计算模型权重
  calculateModelWeights(predictions) {
    if (predictions.length === 0) return {};

    // 简单平均
    const weights = {};
    predictions.forEach(({ model }) => {
      weights[model] = 1 / predictions.length;
    });

    return weights;
  }

  // 差分处理
  differencing(data, order) {
    if (order === 0) return data;

    const diff = [];
    for (let i = order; i < data.length; i++) {
      diff.push(data[i] - data[i - order]);
    }

    return this.differencing(diff, order - 1);
  }

  // 逆差分
  inverseDifferencing(predictions, originalData) {
    const lastValue = originalData[originalData.length - 1];
    return predictions.map(p => lastValue + p);
  }
}

// 安全库存计算
class SafetyStockCalculator {
  constructor(config) {
    this.serviceLevel = config.serviceLevel || 0.95;
    this.zScore = this.getZScore(this.serviceLevel);
  }

  getZScore(serviceLevel) {
    const zScores = {
      0.90: 1.28,
      0.95: 1.65,
      0.99: 2.33
    };
    return zScores[serviceLevel] || 1.65;
  }

  // 计算安全库存
  calculate(historicalDemand, leadTime) {
    const avgDemand = this.calculateAvgDemand(historicalDemand);
    const stdDemand = this.calculateStdDemand(historicalDemand);
    const avgLeadTime = leadTime.average;
    const stdLeadTime = leadTime.std || 0;

    // 安全库存公式
    const ss = this.zScore * Math.sqrt(
      Math.pow(avgLeadTime * stdDemand, 2) +
      Math.pow(avgDemand * stdLeadTime, 2)
    );

    return Math.ceil(ss);
  }

  calculateAvgDemand(data) {
    return data.reduce((a, b) => a + b.quantity, 0) / data.length;
  }

  calculateStdDemand(data) {
    const mean = this.calculateAvgDemand(data);
    const squaredDiffs = data.map(d => Math.pow(d.quantity - mean, 2));
    return Math.sqrt(squaredDiffs.reduce((a, b) => a + b, 0) / data.length);
  }

  // 计算再订货点
  calculateROP(avgDemand, leadTime, safetyStock) {
    return avgDemand * leadTime + safetyStock;
  }

  // 最优订货量 (EOQ)
  calculateEOQ(annualDemand, orderingCost, holdingCost) {
    return Math.sqrt((2 * annualDemand * orderingCost) / holdingCost);
  }
}

库存优化策略

基于预测的智能补货策略:

// 智能补货引擎
class ReplenishmentEngine {
  constructor(forecastService, safetyStockCalc) {
    this.forecastService = forecastService;
    this.safetyStockCalc = safetyStockCalc;
    this.replenishmentRules = new Map();
    this.initRules();
  }

  // 初始化补货规则
  initRules() {
    // 规则1:基于预测的智能补货
    this.replenishmentRules.set('forecast', {
      enabled: true,
      execute: this.forecastBasedReplenish.bind(this)
    });

    // 规则2:安全库存触发的补货
    this.replenishmentRules.set('safety-stock', {
      enabled: true,
      execute: this.safetyStockTriggeredReplenish.bind(this)
    });

    // 规则3:季节性补货
    this.replenishmentRules.set('seasonal', {
      enabled: true,
      execute: this.seasonalReplenish.bind(this)
    });

    // 规则4:促销活动补货
    this.replenishmentRules.set('promotion', {
      enabled: true,
      execute: this.promotionReplenish.bind(this)
    });
  }

  // 生成补货建议
  async generateReplenishment(productId) {
    const product = await this.getProduct(productId);
    const currentStock = await this.getCurrentStock(productId);
    const historicalSales = await this.getHistoricalSales(productId);

    // 计算预测需求量
    const forecast = await this.forecastService.ensemblePredict(
      productId, historicalSales, product.leadTime + 7
    );

    const totalForecast = forecast.reduce((a, b) => a + b, 0);

    // 计算安全库存
    const safetyStock = this.safetyStockCalc.calculate(
      historicalSales, { average: product.leadTime, std: product.leadTimeStd }
    );

    // 计算再订货点
    const rop = this.safetyStockCalc.calculateROP(
      totalForecast / (product.leadTime + 7),
      product.leadTime,
      safetyStock
    );

    // 判断是否需要补货
    const suggestions = [];

    // 基于预测的建议
    if (currentStock < totalForecast + safetyStock) {
      const optimalQty = this.calculateOptimalOrderQty(
        totalForecast + safetyStock - currentStock,
        product
      );

      suggestions.push({
        type: 'forecast',
        quantity: optimalQty,
        reason: `预测未来${product.leadTime + 7}天需求${Math.round(totalForecast)},当前库存不足`,
        priority: 'high'
      });
    }

    // 基于安全库存的建议
    if (currentStock < safetyStock) {
      suggestions.push({
        type: 'safety-stock',
        quantity: product.eoq || safetyStock,
        reason: `库存低于安全库存${safetyStock},需要补货`,
        priority: 'urgent'
      });
    }

    return {
      productId,
      currentStock,
      forecast: Math.round(totalForecast),
      safetyStock,
      rop: Math.round(rop),
      suggestions: this.prioritizeSuggestions(suggestions)
    };
  }

  // 计算最优订货量
  calculateOptimalOrderQty(suggestedQty, product) {
    const minOrderQty = product.minOrderQty || 1;
    const eoq = product.eoq || suggestedQty;
    const maxOrderQty = product.maxOrderQty || Infinity;

    // 取最大值但不超过最大订货量
    return Math.min(Math.max(suggestedQty, minOrderQty), maxOrderQty);
  }

  // 优先级排序
  prioritizeSuggestions(suggestions) {
    const priorityOrder = { urgent: 0, high: 1, medium: 2, low: 3 };

    return suggestions.sort((a, b) =>
      priorityOrder[a.priority] - priorityOrder[b.priority]
    );
  }

  // 批量生成所有商品补货建议
  async generateBatchReplenishment() {
    const products = await this.getAllProducts();
    const results = [];

    for (const product of products) {
      const suggestion = await this.generateReplenishment(product.id);
      if (suggestion.suggestions.length > 0) {
        results.push(suggestion);
      }
    }

    // 按优先级排序
    return results.sort((a, b) => {
      const priorityMap = { urgent: 0, high: 1, medium: 2, low: 3 };
      return priorityMap[a.suggestions[0].priority] -
             priorityMap[b.suggestions[0].priority];
    });
  }

  // 自动触发补货流程
  async autoReplenish(dryRun = true) {
    const suggestions = await this.generateBatchReplenishment();

    const results = {
      created: [],
      skipped: [],
      errors: []
    };

    for (const suggestion of suggestions) {
      try {
        if (!dryRun) {
          await this.createPurchaseOrder(suggestion);
        }
        results.created.push(suggestion);
      } catch (error) {
        results.errors.push({ suggestion, error: error.message });
      }
    }

    return results;
  }
}

最佳实践建议

总结

智能补货系统能够显著提升进销存管理效率:

实际落地时需要根据企业规模和业务特点选择合适的模型和策略。

← 下一篇:进销存系统智能推荐引擎设计与实现