#12│通販企業版パランティア―予測的補充という作戦計画


「弾薬が切れた軍は戦えない」

軍事ロジスティクスにおいて、弾薬・燃料・食料の補給は「作戦の前提条件」だ。
どれだけ優秀な将兵がいても、弾薬が切れた瞬間に戦闘能力はゼロになる。

パランティアがペンタゴン(米国防総省)に提供しているシステムの一つが「予測的メンテナンス・補給」だ。5年・8500万ドルの契約で「機械学習を使って防衛機器のメンテナンス問題を予測し、補給の問題点を解決する」システムを構築している。

戦車や航空機が「故障する前に」部品を交換し、「枯渇する前に」弾薬を補充する。

これはそのまま通販の在庫管理に翻訳できる。

「在庫が切れた通販は売れない」

機会損失は即座に競合へのシェア移転を意味する。


「反応的補充」から「予測的補充」へ

多くの通販企業が実践しているのは「反応的補充」だ。

在庫が安全在庫を下回ったら発注する。売り切れたら緊急発注する。「起きたことへの反応」だ。

しかし軍事ロジスティクスでは「反応的補充」は最悪手だ。
弾薬が切れてから補充を要請しても、戦闘が終わるまでに届かない。

「予測的補充」とは「2週間後に何が何個必要になるか」を今から計算し、発注リードタイムを考慮した上で「今日発注すべき商品と数量」を自動で算出することだ。

-- 予測的補充エンジン:今日発注すべき商品と数量を算出する

WITH sales_velocity AS (
  -- 過去30日の商品別日次平均販売数(販売速度)を計算
  SELECT
    product_id,
    product_name,
    SUM(quantity_sold)                           AS total_sold_30d,
    COUNT(DISTINCT order_date)                   AS selling_days,
    -- 1日あたりの平均販売数(0除算防止)
    ROUND(SUM(quantity_sold) / NULLIF(COUNT(DISTINCT order_date), 0), 2)
                                                 AS daily_avg_units,
    -- 直近7日の販売速度(トレンド検知)
    SUM(CASE WHEN order_date >= DATE_ADD('day', -7, CURRENT_DATE)
      THEN quantity_sold ELSE 0 END) / 7.0      AS daily_avg_last_7d
  FROM order_items oi
  JOIN orders o ON oi.order_id = o.order_id
  WHERE
    o.status = 'completed'
    AND o.order_date >= DATE_ADD('day', -30, CURRENT_DATE)
  GROUP BY product_id, product_name
),
inventory_status AS (
  -- 現在の在庫状況
  SELECT
    product_id,
    current_stock,
    reorder_lead_days,   -- 発注から入荷までのリードタイム(日)
    safety_stock_days    -- 安全在庫として確保したい日数
  FROM inventory_master
),
reorder_calculation AS (
  SELECT
    sv.product_id,
    sv.product_name,
    sv.daily_avg_units,
    sv.daily_avg_last_7d,
    inv.current_stock,
    inv.reorder_lead_days,
    inv.safety_stock_days,
    -- リードタイム中の予測消費量
    ROUND(sv.daily_avg_units * inv.reorder_lead_days, 0)
                                                 AS consumption_during_lead,
    -- 安全在庫量
    ROUND(sv.daily_avg_units * inv.safety_stock_days, 0)
                                                 AS safety_stock_units,
    -- 発注点(この在庫を下回ったら発注すべき水準)
    ROUND(sv.daily_avg_units * (inv.reorder_lead_days + inv.safety_stock_days), 0)
                                                 AS reorder_point,
    -- 現在の在庫で何日分あるか
    ROUND(inv.current_stock / NULLIF(sv.daily_avg_units, 0), 1)
                                                 AS days_of_stock_remaining,
    -- トレンド(直近7日が30日平均より増えているか)
    ROUND(sv.daily_avg_last_7d / NULLIF(sv.daily_avg_units, 0), 2)
                                                 AS trend_multiplier
  FROM sales_velocity sv
  JOIN inventory_status inv USING (product_id)
)
SELECT
  product_id,
  product_name,
  current_stock,
  days_of_stock_remaining,
  reorder_point,
  daily_avg_units,
  trend_multiplier,
  -- 今日発注すべきかどうかの判定
  CASE
    WHEN current_stock <= reorder_point THEN '🔴 要発注(在庫が発注点を下回っています)'
    WHEN current_stock <= reorder_point * 1.2 THEN '🟡 発注準備(まもなく発注点に到達)'
    ELSE '🟢 正常'
  END                                            AS reorder_status,
  -- 推奨発注量(30日分を目標に補充)
  CASE
    WHEN current_stock <= reorder_point
    THEN GREATEST(
      ROUND(daily_avg_units * 30 * trend_multiplier - current_stock, 0),
      0
    )
    ELSE 0
  END                                            AS recommended_order_qty
FROM reorder_calculation
ORDER BY
  CASE WHEN current_stock <= reorder_point THEN 0 ELSE 1 END,
  days_of_stock_remaining ASC

「トレンドの先読み」—季節性と外部シグナルの統合

軍事ロジスティクスのアナリストは「天候・地形・敵の動向」という外部要因を補給計画に織り込む。

通販の補給計画にも「外部シグナル」が必要だ。

-- 季節性補正を加えた需要予測
-- 前年同期の販売データで季節性インデックスを計算する

WITH yoy_comparison AS (
  SELECT
    product_id,
    -- 今年の過去30日の日次平均
    SUM(CASE
      WHEN order_date BETWEEN DATE_ADD('day', -30, CURRENT_DATE)
                          AND CURRENT_DATE
      THEN quantity_sold ELSE 0 END
    ) / 30.0                                      AS current_daily_avg,
    -- 前年同期の日次平均(±15日の範囲)
    SUM(CASE
      WHEN order_date BETWEEN DATE_ADD('day', -30, DATE_ADD('year', -1, CURRENT_DATE))
                          AND DATE_ADD('year', -1, CURRENT_DATE)
      THEN quantity_sold ELSE 0 END
    ) / 30.0                                      AS last_year_daily_avg,
    -- 前年の今後30日(来月相当)の日次平均
    SUM(CASE
      WHEN order_date BETWEEN DATE_ADD('year', -1, CURRENT_DATE)
                          AND DATE_ADD('day', 30, DATE_ADD('year', -1, CURRENT_DATE))
      THEN quantity_sold ELSE 0 END
    ) / 30.0                                      AS last_year_next_month_avg
  FROM order_items oi
  JOIN orders o ON oi.order_id = o.order_id
  WHERE
    o.status = 'completed'
    AND o.order_date >= DATE_ADD('year', -1, DATE_ADD('day', -30, CURRENT_DATE))
  GROUP BY product_id
)
SELECT
  product_id,
  ROUND(current_daily_avg, 2)                    AS current_daily_avg,
  ROUND(last_year_next_month_avg, 2)             AS last_year_next_month_avg,
  -- 来月の需要予測(前年の来月実績 × 今年の成長率)
  ROUND(
    last_year_next_month_avg
    * (current_daily_avg / NULLIF(last_year_daily_avg, 0))
  , 2)                                           AS forecast_next_month_daily,
  -- 30日後の予測在庫(現在の計画発注を含まない素の推定)
  -- ※ inventory_master.current_stock と結合して使う
  ROUND(
    last_year_next_month_avg
    * (current_daily_avg / NULLIF(last_year_daily_avg, 0))
    * 30
  , 0)                                           AS forecast_30day_consumption
FROM yoy_comparison
WHERE last_year_daily_avg > 0
ORDER BY forecast_30day_consumption DESC

「機会損失」を可視化する—欠品コストの算出

軍事では「撃てない弾薬を持っていることのコスト(重量・輸送費)」と「弾薬が足りないことのコスト(作戦の失敗)」を常に天秤にかける。

過剰在庫も過少在庫も、どちらもコストだ。

通販でも同じだ。

-- 機会損失の推定:欠品していた期間の潜在的な売上損失を計算する

WITH stockout_periods AS (
  -- 在庫ゼロだった期間を特定(在庫履歴テーブルから)
  SELECT
    product_id,
    stockout_start_date,
    stockout_end_date,
    DATE_DIFF('day', stockout_start_date, stockout_end_date) AS stockout_days
  FROM inventory_history
  WHERE stock_quantity = 0
    AND stockout_start_date >= DATE_ADD('day', -90, CURRENT_DATE)
),
avg_daily_revenue AS (
  -- 欠品がなかった期間の1日あたり平均売上
  SELECT
    oi.product_id,
    ROUND(SUM(oi.quantity_sold * oi.unit_price)
      / COUNT(DISTINCT o.order_date), 0)          AS avg_daily_revenue
  FROM order_items oi
  JOIN orders o ON oi.order_id = o.order_id
  WHERE o.status = 'completed'
    AND o.order_date >= DATE_ADD('day', -90, CURRENT_DATE)
  GROUP BY oi.product_id
)
SELECT
  sp.product_id,
  sp.stockout_start_date,
  sp.stockout_end_date,
  sp.stockout_days,
  adr.avg_daily_revenue,
  -- 機会損失の推定(欠品日数 × 通常日次売上)
  ROUND(sp.stockout_days * adr.avg_daily_revenue, 0)
                                                   AS estimated_lost_revenue,
  -- 年換算の機会損失
  ROUND(
    sp.stockout_days * adr.avg_daily_revenue * (365.0 / 90)
  , 0)                                             AS annualized_lost_revenue
FROM stockout_periods sp
JOIN avg_daily_revenue adr USING (product_id)
ORDER BY estimated_lost_revenue DESC

このSQLが示す数字が「予測的補充システムへの投資対効果」の根拠になる。

「去年の欠品による機会損失が年間2,000万円だった」という数字があれば、補充システムへの投資予算を正当化できる。


次回予告

「オントロジーという地図—データをつなぐ『意味の設計』

パランティアの技術的核心「オントロジー」を、Treasure Dataで実装するとはどういうことか。バラバラなデータに「意味」を与え、行動可能なインテリジェンスに変換する設計哲学を解説する。

MarTech Farmをもっと見る

今すぐ購読し、続きを読んで、すべてのアーカイブにアクセスしましょう。

続きを読む