はじめに:在庫切れと過剰在庫の見逃しを減らしたい
ECを運営していると、在庫の問題はいつも「気づいたときには手遅れ」という形でやってきます。
人気商品が売り切れていて、本来なら売れたはずの注文を取りこぼしていた。逆に、勢いで多めに発注した商品がまったく動かず、倉庫の奥で在庫だけが膨らんでいた。どちらも、毎日きちんと数字を見ていれば防げたはずのことです。
とはいえ、何百という商品の在庫数と売れ行きを毎朝チェックし続けるのは、現実的ではありません。気合いで乗り切れるのは商品数が少ないうちだけで、扱う商品が増えるほど「見るのを忘れる日」が出てきます。そして問題は、たいていその忘れた日に起きます。
そこでこの記事では、在庫と売れ行きを定期的に監視し、しきい値を割った商品を見つけて発注量の目安を計算し、その結果をSlackに通知するところまでを、Claude Codeを使って組み立てる流れを紹介します。
ポイントは、すべてを機械任せにするのではなく、最後の判断は人間が握るという設計にすることです。自動で発注までやってしまう仕組みは便利そうに見えて、思わぬ事故のもとになります。その理由は記事の後半で詳しく触れます。
なお、在庫データの土台づくりについては、関連記事の在庫回転率をBigQueryで可視化する方法もあわせて読むと、全体像がつかみやすくなります。
何を「監視」するのかを先に決める
自動化の話に入る前に、そもそも何を見たいのかをはっきりさせておきます。ここが曖昧なまま仕組みだけ作ると、通知が鳴っても「で、どうすればいいの」となってしまいます。
在庫アラートで見るべき指標は、大きく分けて2つです。
- 在庫切れリスク:このままのペースで売れ続けたら、何日後に在庫がゼロになるか
- 過剰在庫リスク:在庫はあるのに売れ行きが鈍く、何日分も在庫が積み上がっていないか
どちらも、単純な「在庫数」だけでは判断できません。在庫が10個でも、1日1個ペースなら10日もちますが、1日5個売れる商品なら2日でなくなります。つまり、在庫数と売れ行きを掛け合わせて初めて意味のある数字になるわけです。
この「あと何日で在庫がなくなるか」を表す指標を、ここでは在庫日数と呼びます。計算式はシンプルです。
在庫日数 = 現在の在庫数 ÷ 1日あたりの平均販売数
たとえば在庫日数が3日を切ったら「もうすぐ切れる」、逆に60日を超えたら「過剰ぎみ」と判断する、といった具合です。このしきい値は商品や仕入れリードタイムによって変わるので、後から調整できるようにしておきます。
在庫×売れ行きを監視するロジックをBigQueryで書く
監視ロジックの本体は、SQLで書きます。在庫データと、直近の販売実績を商品ごとに突き合わせて、在庫日数を計算する形です。
ここでは、在庫マスタのテーブル(商品ごとの現在庫)と、受注明細のテーブル(いつ・何が・何個売れたか)がBigQuery上にある前提で書きます。受注データをBigQueryに集約する方法は別記事で触れているので、まだ基盤がない方はそちらを参考にしてください。
直近14日間の平均販売数をもとに、在庫日数を求めるクエリのイメージです。
WITH recent_sales AS (
SELECT
product_id,
SUM(quantity) / 14 AS avg_daily_qty
FROM `your_project.ec.order_items`
WHERE order_date >= DATE_SUB(CURRENT_DATE('Asia/Tokyo'), INTERVAL 14 DAY)
GROUP BY product_id
)
SELECT
inv.product_id,
inv.product_name,
inv.stock_qty,
COALESCE(rs.avg_daily_qty, 0) AS avg_daily_qty,
SAFE_DIVIDE(inv.stock_qty, rs.avg_daily_qty) AS days_of_stock
FROM `your_project.ec.inventory` AS inv
LEFT JOIN recent_sales AS rs
USING (product_id)
ORDER BY days_of_stock ASC
SAFE_DIVIDE を使っているのは、まったく売れていない商品(平均販売数がゼロ)で割り算がエラーにならないようにするためです。こういう商品は在庫日数が計算できないので、「動いていない在庫」として別枠で扱うのが現実的です。
平均を14日でとっているのは一例で、季節商品やセール直後など売れ行きが大きく変わる場面では、期間を短めにしたり、曜日の偏りをならしたりといった調整が要ります。まずは固定の期間で動かしてみて、現場感覚とズレるところを後から直すのがおすすめです。
なお、BigQueryやSlackなど外部サービスの細かい仕様は変わることがあるので、実装時には最新の公式ドキュメントで確認してから進めてください。
しきい値で発注量の目安を提案する
在庫日数が出せたら、次は「じゃあ何個発注すればいいのか」の目安を出します。
考え方はこうです。在庫が切れてから次の入荷までの間に売り逃しが出ないよう、仕入れリードタイム+安全在庫の日数分を確保できる量を補充の目安とします。
推奨発注量 = 1日あたり平均販売数 ×(リードタイム日数 + 安全在庫日数)− 現在の在庫数
たとえば1日平均5個売れる商品で、発注してから入荷まで7日かかり、念のため3日分の余裕を持たせたいとします。すると必要な在庫は 5 ×(7 + 3)= 50個。現在の在庫が10個なら、目安の発注量は40個、という計算になります。
この計算をSQLの結果に1列足す形で組み込んでおけば、「在庫日数が短い商品」と「そのおおよその発注量」がセットで出てきます。
ここで強調しておきたいのは、これはあくまで**目安(提案)**だということです。実際の発注は、ロット単位の制約があったり、仕入先の都合があったり、近くにセールを控えていたりと、数式だけでは決められない事情が絡みます。仕組みが出すのは「検討の出発点となる数字」であって、最終的な発注数ではありません。
しきい値は商品ごとに分けられるようにする
すべての商品を一律のしきい値で見ると、どうしても無理が出ます。回転の速い定番商品と、ゆっくり売れる季節商品では、適正な在庫日数がまったく違うからです。
最初は「全商品で在庫日数3日未満をアラート」のようなシンプルなルールで始めて構いません。運用しながら、商品カテゴリごとにしきい値の列を持たせていくと、通知の精度が上がっていきます。Claude Codeに「この商品カテゴリ別にしきい値テーブルを作って結合したい」と相談しながら育てていくと、SQLが苦手でも形にしやすいはずです。
Slack通知までの流れ
監視と発注提案ができたら、最後はその結果を毎朝Slackに届けます。
全体の流れは、次のような順番になります。
- スケジューラが定刻(たとえば毎朝8時)にスクリプトを起動する
- スクリプトがBigQueryの監視クエリを実行する
- しきい値を割った商品だけを抜き出して、メッセージを組み立てる
- SlackのIncoming Webhook宛てにメッセージを送る
Claude Codeには「この監視クエリを毎朝動かして、しきい値を割った商品だけSlackに通知するスクリプトを書いて」と頼めば、たたき台を一気に作ってくれます。あとは実際のデータで動かしながら、メッセージの文面や項目を調整していく形です。
Slackへ送る部分のイメージは、次のようなコードになります。
import os
import json
import urllib.request
# Webhook URLは環境変数などから読み込む(コードに直書きしない)
WEBHOOK_URL = os.environ["SLACK_WEBHOOK_URL"]
def notify_slack(alerts):
if not alerts:
return # アラート対象がなければ通知しない
lines = ["*本日の在庫アラート*"]
for a in alerts:
lines.append(
f"- {a['product_name']}:残{a['stock_qty']}個 "
f"(在庫日数{a['days_of_stock']:.1f}日 / 発注目安{a['suggested_qty']}個)"
)
lines.append("※ 発注量は目安です。最終判断のうえ手動で発注してください。")
payload = {"text": "\n".join(lines)}
req = urllib.request.Request(
WEBHOOK_URL,
data=json.dumps(payload).encode("utf-8"),
headers={"Content-Type": "application/json"},
)
urllib.request.urlopen(req)
ここで気をつけたいのは、Webhook URLのような秘密情報をコードに直接書かないことです。上の例のように環境変数から読み込み、実際の値は設定ファイルや環境変数として、コードとは別の場所で管理します。誤ってGitに秘密情報を上げてしまうと、取り返しのつかない事故になります。
通知の文面には、商品名・残り在庫・在庫日数・発注目安に加えて、「これは目安であって自動発注ではない」という一文を必ず添えておきます。通知を受け取る人が、それを見て次の行動を決められるようにするためです。
メッセージを毎日送る形にすると、対象がない日でも「異常なし」と送りたくなりますが、アラートがない日は通知しないほうが、いざ通知が来たときに見落としにくくなります。通知の頻度と中身は、運用しながら調整してください。
⚠️ 人の承認を挟む:自動発注はしない
最後に、この記事でいちばん伝えたいことを書きます。
在庫日数を計算し、発注量の目安まで出せると、つい「ここまでできるなら、そのまま発注APIを叩いて自動発注すればいいのでは」と考えたくなります。気持ちは分かりますが、そこには一線を引くことを強くおすすめします。
自動発注をやってはいけない理由は、はっきりしています。
- データの不備がそのまま発注事故になる:在庫データの取り込みが一度失敗しただけで、在庫ゼロと誤認して大量発注がかかる、といったことが起こりえます
- 異常値に弱い:たまたま1日だけ大量注文が入ると、平均販売数が跳ね上がり、必要のない在庫を抱え込むことになります
- 取り返しがつかない:通知の出し過ぎなら無視すれば済みますが、誤った発注は仕入れ費用という形で実際のお金が出ていきます
仕組みが得意なのは「気づくこと」と「下調べ」であって、「決めること」ではありません。在庫日数を計算し、候補を絞り、目安を出すところまでを任せて、発注ボタンを押すのは人間にする。この役割分担を守るだけで、自動化のメリットだけを安全に受け取れます。
「対外的にお金やモノが動く操作の手前で必ず止める」という線引きは、在庫発注にかぎらず、業務を自動化するときの基本だと考えておくとよいでしょう。
まとめ
在庫アラートの自動化は、難しい技術というより「見るのを忘れない仕組み」をつくる作業です。
- 在庫数だけでなく、**在庫日数(在庫 ÷ 平均販売数)**で在庫切れと過剰を見分ける
- しきい値を割った商品に対して、リードタイムと安全在庫から発注量の目安を出す
- 結果はSlackなど毎日見る場所に通知し、気づける状態をつくる
- 秘密情報はコードに直書きせず、環境変数などで管理する
- そして、発注の最終判断は必ず人間が握り、自動発注はしない
まずは1つのカテゴリ、シンプルなしきい値から始めてみてください。Claude Codeにクエリやスクリプトのたたき台を作ってもらいながら、自分の店の感覚に合わせて少しずつ育てていくのが、いちばん続けやすいやり方です。在庫の見逃しが減るだけで、取りこぼした売上と眠った仕入れ資金の両方を、静かに取り戻していけるはずです。