Skip to content

Instantly share code, notes, and snippets.

@ytyng
Created February 25, 2026 08:20
Show Gist options
  • Select an option

  • Save ytyng/1713d0b0c3d11a23abd7e1dd016126d5 to your computer and use it in GitHub Desktop.

Select an option

Save ytyng/1713d0b0c3d11a23abd7e1dd016126d5 to your computer and use it in GitHub Desktop.
売上予測 Redash (query 2102) の予測値ずれ調査レポート - shortest_shipping_date と実績の乖離分析

売上予測 Redash (query 2102) の予測値ずれ調査レポート

概要

ホーリンラブブックス(HLB)の売上予測 Redash クエリ 2102 の予測値が実績と乖離している問題を調査した。 shortest_shipping_date(予測出荷日)ベースの集計と、MS(マンガサーバー)の実際の出荷実績(delivery_date)を突き合わせ、ずれの原因と改善策を分析した。

結論: Redash SQL自体に不具合はないが、shortest_shipping_date は「理論上の最短出荷日」であり、実際の出荷日とは構造的にずれる。+4日のオフセットを加算すると1月データでは最も実績に近い予測になる。


対象システム

項目 内容
リポジトリ torico-tokyo/torico-blog
Redash クエリ 2102: ホーリン期間予測売上
対象ショップ ホーリンラブブックス (site_id=18)
分析対象期間 2026年1月

突き合わせ検証結果

データ概要

データ 件数 金額
Redash 2102 相当 (shortest_shipping_date=1月) 4,489件 15,315,245円
MS 1月出荷実績 (delivery_date=1月) 3,453件 13,210,869円
差額 +1,036件 +2,104,376円 (Redash の方が多い)

ずれの要因分解

A. Redash予測にあり MS実績にない: 1,294件, 3,247,532円

ステータス 件数 金額 説明
SHIPPED 1,198件 2,995,888円 shortest_shipping_date=1月だが実際は2月出荷
UNSHIPPED 74件 103,921円 未出荷
SHIPPED_PENDING_PAYMENT 18件 132,493円 代引き配送中
UNPAID/ORDERED 4件 15,230円 未入金/注文完了

B. MS実績にあり Redash予測にない: 258件, 1,143,156円

shortest_shipping_date が12月以前だが、実際には1月に出荷された注文。

差額検算

A - B = 3,247,532 - 1,143,156 = 2,104,376円 → Redash予測 - MS実績 と一致。

共通データの検証

  • 共通の 3,195件は 全件金額が一致(torico-blog の o.total と MS 側の order_total_price が完全一致)
  • Redash 2102 の SQL 自体に二重計上の問題なし(orderitem JOIN なし、BUNDLED 除外、1 reserve = 1 order)

shortest_shipping_date のロジック

shortest_shipping_date(配送基準日)は「注文内の全商品のうち、最も遅い発売日/入荷予定日」を基準にした配送予定日。

1. 注文時(初期値)

ソース: reserveshop/cache.py L808-836

shortest_date = max(
    max(item.sell_start_date, item.expected_restock_date, 今日)
    for item in カート内商品 (同時購入特典は除外)
)
  • 各商品の sell_start_date(発売日)、expected_restock_date(入荷予定日)、当日の最大値を取る
  • 全商品の中から最大を取る(= 一番遅い商品に合わせる)
  • カットオフ時刻(深夜2時)考慮:当日の2時以降なら翌日にずらす

2. バッチ更新時

ソース: reserveshop/models.py L375-401 (_max_shortest_shipping_date)

管理コマンド: reserveshop/management/commands/t10_update_shortest_shipping_date.py

呼び出し元: reserveshop/update_reserve.py L222-309 (update_shortest_shipping_date)

def _max_shortest_shipping_date():
    dates = [order_date]  # 注文日
    for item in order.items:
        # 現在の商品マスタの sell_start_date
        # OrderItem.extra_data_json の sell_start_date(購入時の発売日)
        # OrderItem.extra_data_json の expected_restock_date(入荷予定日)
        dates.append(各日付)
    return max(dates).date()

商品マスタの最新の sell_start_date を参照して更新する。発売日が延期されたら shortest_shipping_date も後ろにずれる。

対象ステータス: UNPAID, PAID, ORDERED のみ(UNSHIPPED 以降は更新対象外)。

3. 取寄商品の入荷完了時

ソース: reserveshop/update_reserve.py L425-617 (UpdateReserveForBackorder)

MS側で在庫引き当てが完了したら expected_restock_date をクリアし、post_sell_start_date_save で再計算。

まとめ

タイミング トリガー ロジック
注文時 チェックアウト cart.shortest_shipping_date — 全商品の発売日/入荷予定日の最大
発売日変更時 バッチ t10_update_shortest_shipping_date _max_shortest_shipping_date() — 商品マスタの最新発売日で再計算
取寄入荷時 バッチ UpdateReserveForBackorder 入荷予定日クリア後 post_sell_start_date_save で再計算

日付ずれの定量分析

共通 3,195件について delivery_date - shortest_shipping_date を計算した。

基本統計

指標
最小 -1日
最大 27日
平均 3.25日
中央値 0日
金額加重平均 2.36日

日付差分の分布

 -1日:    82件 (  2.6%) 金額:     333,937円
 +0日:  1777件 ( 55.6%) 金額:   6,803,602円  ← 過半数は当日出荷
 +1日:   374件 ( 11.7%) 金額:   1,562,033円
 +2日:   137件 (  4.3%) 金額:     579,080円
 +3日:    91件 (  2.8%) 金額:     454,391円
 +4日:    70件 (  2.2%) 金額:     334,369円
 +5日:    30件 (  0.9%) 金額:     195,587円
 +6日:    50件 (  1.6%) 金額:     285,976円
 +7日:    34件 (  1.1%) 金額:     202,861円
 +8日:    16件 (  0.5%) 金額:     171,478円
 +9日:    36件 (  1.1%) 金額:      85,152円
+10日:     6件 (  0.2%) 金額:      36,981円
+11日:    51件 (  1.6%) 金額:      77,917円
+12日:     7件 (  0.2%) 金額:      58,883円
+13日:    90件 (  2.8%) 金額:     182,057円
+14日:     6件 (  0.2%) 金額:      28,482円
+15日:     3件 (  0.1%) 金額:      49,811円
+16日:    54件 (  1.7%) 金額:      97,332円
+17日:    57件 (  1.8%) 金額:      91,361円
+18日:    71件 (  2.2%) 金額:     128,038円
+19日:    97件 (  3.0%) 金額:     175,336円
+20日:    38件 (  1.2%) 金額:      55,182円
+21日:     6件 (  0.2%) 金額:      19,351円
+22日:     2件 (  0.1%) 金額:       7,994円
+23日:     4件 (  0.1%) 金額:      24,383円
+25日:     4件 (  0.1%) 金額:      13,067円
+26日:     1件 (  0.0%) 金額:       3,194円
+27日:     1件 (  0.0%) 金額:       9,878円

55.6% は当日出荷(ずれなし)。残り44.4%は1日以上のずれがあり、分布はロングテール。

オフセット別シミュレーション

shortest_shipping_date + N日 を「予測出荷日」として1月に入る件数・金額を計算。 実績(3,453件 / 13,210,869円)にどれだけ近いかを比較。

  -5日:  4,203件 / 14,158,902円 (差額: +948,033円)
  -4日:  4,412件 / 14,941,784円 (差額: +1,730,915円)
  -3日:  4,430件 / 15,027,578円 (差額: +1,816,709円)
  -2日:  4,453件 / 15,144,221円 (差額: +1,933,352円)
  -1日:  4,463件 / 15,174,768円 (差額: +1,963,899円)
  +0日:  4,489件 / 15,315,245円 (差額: +2,104,376円)  ← 現状
  +1日:  4,387件 / 15,085,863円 (差額: +1,874,994円)
  +2日:  4,213件 / 13,930,761円 (差額: +719,892円)
  +3日:  4,102件 / 13,392,462円 (差額: +181,593円)
  +4日:  4,073件 / 13,293,948円 (差額: +83,079円)     ← 最小差額 ★
  +5日:  4,035件 / 13,119,482円 (差額: -91,387円)
  +6日:  3,913件 / 12,895,090円 (差額: -315,779円)
  +7日:  3,884件 / 12,782,015円 (差額: -428,854円)
  +8日:  3,852件 / 12,645,951円 (差額: -564,918円)

shortest_shipping_date + 4日 で金額差が最小(+83,079円、誤差0.6%)になる。


定例MTGの「Redashが少ない」との矛盾

今回の1月分析では Redash の方が 多い(+2,104,376円)。

推定される理由:

  1. 「Redashが少ない」は 2月予測に対する指摘 — 12月・1月の shortest_shipping_date の注文が2月に実際出荷された分は、Redash の2月予測には含まれないため、2月は逆に Redash が少なくなる
  2. 比較対象の期間・条件が異なる可能性

Redash SQL の調査メモ

query 2102 のSQL構造

  • reserveshop_reservetorico_shop_order, charge_charge と LEFT JOIN
  • orderitem は JOIN していない → 二重計上の問題なし
  • BUNDLED ステータスは除外していない(ステータスフィルタに BUNDLED が含まれていない = 実質除外)
  • 1 reserve = 1 order の関係

調査SQL の注意点

調査用に torico_shop_orderitem を JOIN した SQL は、1注文に複数商品がある場合に行が重複する。 正しい合計(reserve 単位): 15,315,245円 vs 全行合計(orderitem 重複含む): 62,785,415円。 集計時は必ず order_id で重複排除が必要。

同梱 (BUNDLED) の扱い

  • 同梱元 (source) の reserve → status が BUNDLED になる
  • 同梱先 (destination) として新しい Reserve/Order/Charge が作成される(checkout_type = 'reserveshop-bundle'
  • Redash 2102 では BUNDLED (同梱元) が対象外、同梱先の合算注文が集計に入る → 正しい

manga-server 側の実績データ

  • shipping_ec_analysis テーブルに出荷日ベースの EC サイト別売上集計がある
  • 集計元: order テーブルと delivery テーブル (delivery_status = 5: 配送済み確認)
  • total_due_sum = balance_due + delivery_charge + commission
  • EC サイト ID: 74 (HLB旧), 111 (HLB新)

改善提案

案1: 固定オフセット(+4日)

Redash 2102 の WHERE 条件を変更:

-- 現状
AND r.shortest_shipping_date >= '{{ delivery_date_start }}'
AND r.shortest_shipping_date <= '{{ delivery_date_end }}'

-- 改善案
AND DATE_ADD(r.shortest_shipping_date, INTERVAL 4 DAY) >= '{{ delivery_date_start }}'
AND DATE_ADD(r.shortest_shipping_date, INTERVAL 4 DAY) <= '{{ delivery_date_end }}'

1月データでは誤差0.6%に改善される。ただし、この値が他の月でも安定するかは未検証。

案2: ステータスフィルタの改善

SHIPPED で shortest_shipping_date が期間内だが実際は翌月出荷のケース(1,198件)が最大の要因。 SHIPPED を除外し「まだ出荷されていない注文」のみ集計する案もあるが、予測の趣旨が変わる。

案3: 実績ベースの集計に切り替え

予測ではなく MS の delivery_date ベースで実績集計する場合は、shipping_ec_analysis テーブルを直接参照する方が正確。


調査に使用した SQL

torico-blog-production (Redash 2102 相当)

SELECT
  COUNT(*) AS cnt,
  SUM(o.total) AS total_amount
FROM reserveshop_reserve r
LEFT JOIN torico_shop_order o ON o.id = r.order_id
WHERE r.site_id = 18
  AND r.shortest_shipping_date >= '2026-01-01'
  AND r.shortest_shipping_date <= '2026-01-31'
  AND r.status IN ('UNPAID','PAID','ORDERED','UNSHIPPED','SHIPPED_PENDING_PAYMENT','SHIPPED');

manga-server-production (MS 出荷実績)

SELECT
  o.order_id,
  o.m_ec_id,
  d.delivery_date,
  IFNULL(o.balance_due, 0) + IFNULL(o.delivery_charge, 0) + IFNULL(o.commission, 0) AS total_due,
  o.order_date,
  o.order_status,
  o.money_status
FROM `order` o
INNER JOIN delivery d ON d.order_id = o.order_id
WHERE o.m_ec_id IN (74, 111)
  AND d.delivery_status = 5
  AND d.delivery_date >= '2026-01-01'
  AND d.delivery_date < '2026-02-01'
ORDER BY d.delivery_date, o.order_id;

調査日: 2026-02-25 リポジトリ: torico-tokyo/torico-blog (commit: f6221f7)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment