配送低消方案分析報告
本報告根據各路線實際配送成本 與品項毛利% ,分析現行低消是否足以覆蓋配送成本, 並提出三種低消調整方案。
分析期間: 2025/10 ~ 2025/12 出發地: 屏東縣長治鄉(3.5噸貨車配送) 配送範圍: 僅高屏台南(台中以北為寄貨,不含在內) 目標淨利: 每客戶配送後至少淨利 100 元 低消級距: 以 500 元為一個門檻
資料載入
Code
import os
import sys
import math
import csv
import polars as pl
from great_tables import GT, style, loc
# CSV 檔案路徑(相對於 lighthouse 目錄)
CSV_DELIVERY_COST = "data/delivery-cost-2512-2601.csv"
CSV_ITEM_MARGIN = "data/item-sales-gm-202510-202512.csv"
一、常數與參數設定
Code
# 目標淨利(每客戶配送後至少賺取的金額)
TARGET_NET_PROFIT = 100
# 低消級距(以 500 元為一個門檻)
MIN_ORDER_STEP = 500
# 各路線現行低消設定(元)
CURRENT_MIN_ORDER = {
"A-PH" : 1000 ,
"A-PN" : 1000 ,
"A-PT" : 1000 ,
"K1" : 1500 ,
"K2-岡山以南" : 1000 ,
"K2-岡山以北(含岡山)" : 2000 ,
"K2-台南" : 3000 ,
}
# K2 子路線成本分界點
K2_THRESHOLD_LOW = 100 # ≤100 歸為「岡山以南」
K2_THRESHOLD_MID = 200 # 101~200 歸為「岡山以北」
# 低毛利品項判定門檻
LOW_GM_THRESHOLD = 12 # 毛利% ≤ 12% 視為低毛利品項
LOW_GM_MIN_SALES = 100 # 銷售次數 ≥ 100 才納入統計
# 競爭策略覆寫(不適用公式計算,直接指定低消)
COMPETITIVE_OVERRIDE = {
"A-PT" : (1000 , "一級戰區(同業低消3000),維持1000搶市佔" ),
"K2-台南" : (3000 , "防止台南同業搶客,維持不漲" ),
}
# 路線顯示順序
ROUTE_ORDER = [
"A-PH" , "A-PN" , "A-PT" , "K1" ,
"K2-岡山以南" , "K2-岡山以北(含岡山)" , "K2-台南" ,
]
二、讀取配送成本
配送成本採用 CSV 實際營運數據 (非理論估算),已包含油資、車損、人工, 為一趟多站路線優化後的「每客戶分攤」成本。
Code
# 讀取配送成本 CSV,K2 依成本自動拆分為三條子路線
routeCosts = {}
with open (CSV_DELIVERY_COST, encoding= "utf-8" ) as f:
reader = csv.DictReader(f)
for row in reader:
route = row["路線" ].strip()
costStr = row["每客戶平均成本" ].strip()
if not costStr or costStr.startswith("#" ):
continue
costVal = float (costStr.replace("$" , "" ).replace("," , "" ))
if route not in ["A-PH" , "A-PN" , "A-PT" , "K1" , "K2" ]:
continue
if route not in routeCosts:
routeCosts[route] = []
routeCosts[route].append(costVal)
# 計算各路線平均成本
avgCosts = {}
for route in ["A-PH" , "A-PN" , "A-PT" , "K1" ]:
costs = routeCosts.get(route, [])
if costs:
avgCosts[route] = round (sum (costs) / len (costs), 1 )
# K2 拆分
k2All = routeCosts.get("K2" , [])
k2South = [c for c in k2All if c <= K2_THRESHOLD_LOW]
k2North = [c for c in k2All if K2_THRESHOLD_LOW < c <= K2_THRESHOLD_MID]
k2Tainan = [c for c in k2All if c > K2_THRESHOLD_MID]
if k2South:
avgCosts["K2-岡山以南" ] = round (sum (k2South) / len (k2South), 1 )
if k2North:
avgCosts["K2-岡山以北(含岡山)" ] = round (sum (k2North) / len (k2North), 1 )
if k2Tainan:
avgCosts["K2-台南" ] = round (sum (k2Tainan) / len (k2Tainan), 1 )
# 顯示結果
dfCost = pl.DataFrame({
"路線" : ROUTE_ORDER,
"平均配送成本" : [avgCosts.get(r, 0 ) for r in ROUTE_ORDER],
"現行低消" : [CURRENT_MIN_ORDER.get(r, 0 ) for r in ROUTE_ORDER],
"資料筆數" : [
len (routeCosts.get(r, []))
if r in routeCosts
else len ({"K2-岡山以南" : k2South, "K2-岡山以北(含岡山)" : k2North, "K2-台南" : k2Tainan}.get(r, []))
for r in ROUTE_ORDER
],
})
(
GT(dfCost)
.tab_header(
title= "各路線配送成本" ,
subtitle= "CSV 實際營運數據(每客戶平均,已含路線分攤)" ,
)
.fmt_number(columns= ["平均配送成本" ], decimals= 1 )
.fmt_integer(columns= ["現行低消" , "資料筆數" ])
)
CSV 實際營運數據(每客戶平均,已含路線分攤)
A-PH
182.3
1,000
34
A-PN
107.1
1,000
36
A-PT
100.7
1,000
43
K1
130.6
1,500
37
K2-岡山以南
85.8
1,000
21
K2-岡山以北(含岡山)
153.7
2,000
6
K2-台南
262.2
3,000
11
K2 路線拆分說明
K2 路線成本標準差高達 83.76,因此依成本金額拆分為三條子路線:
Code
dfK2 = pl.DataFrame({
"K2子路線" : ["K2-岡山以南(含週二週五)" , "K2-岡山以北(含岡山)" , "K2-台南" ],
"涵蓋區域" : [
"萬丹/新園/崁頂/林邊/南州/大樹/大社" ,
"阿蓮/橋頭/茄萣" ,
"歸仁/仁德/佳里" ,
],
"筆數" : [len (k2South), len (k2North), len (k2Tainan)],
"平均成本" : [
round (sum (k2South)/ len (k2South), 1 ) if k2South else 0 ,
round (sum (k2North)/ len (k2North), 1 ) if k2North else 0 ,
round (sum (k2Tainan)/ len (k2Tainan), 1 ) if k2Tainan else 0 ,
],
"成本範圍" : [
f" { min (k2South):.0f} ~ { max (k2South):.0f} " if k2South else "" ,
f" { min (k2North):.0f} ~ { max (k2North):.0f} " if k2North else "" ,
f" { min (k2Tainan):.0f} ~ { max (k2Tainan):.0f} " if k2Tainan else "" ,
],
})
GT(dfK2).tab_header(title= "K2 路線拆分明細" )
K2子路線
涵蓋區域
筆數
平均成本
成本範圍
K2-岡山以南(含週二週五)
萬丹/新園/崁頂/林邊/南州/大樹/大社
21
85.8
61~100
K2-岡山以北(含岡山)
阿蓮/橋頭/茄萣
6
153.7
104~193
K2-台南
歸仁/仁德/佳里
11
262.2
202~355
三、品項毛利分析
Code
items = []
with open (CSV_ITEM_MARGIN, encoding= "utf-8" ) as f:
reader = csv.DictReader(f)
for row in reader:
try :
totalSales = int (row["合計銷售次數" ])
gmStr = row["三個月平均毛利%" ].strip()
if gmStr == "" or totalSales == 0 :
continue
items.append({
"品號" : row["品號" ],
"品名" : row["品名" ],
"銷售次數" : totalSales,
"毛利%" : float (gmStr),
})
except (ValueError , KeyError ):
continue
items.sort(key= lambda x: x["銷售次數" ], reverse= True )
totalAllSales = sum (i["銷售次數" ] for i in items)
def weightedGm(group):
"""銷售次數加權平均毛利%"""
s = sum (i["銷售次數" ] for i in group)
return sum (i["毛利%" ] * i["銷售次數" ] for i in group) / s if s else 0
三層級加權毛利
依銷售次數累計佔比分為高頻(50%)、中頻( 30%)、低頻(~20%)三層級:
Code
cumSales = 0
tier1Threshold = tier2Threshold = 0
for item in items:
cumSales += item["銷售次數" ]
ratio = cumSales / totalAllSales
if tier1Threshold == 0 and ratio >= 0.50 :
tier1Threshold = item["銷售次數" ]
if tier2Threshold == 0 and ratio >= 0.80 :
tier2Threshold = item["銷售次數" ]
break
tier1 = [i for i in items if i["銷售次數" ] >= tier1Threshold]
tier2 = [i for i in items if tier2Threshold <= i["銷售次數" ] < tier1Threshold]
tier3 = [i for i in items if i["銷售次數" ] < tier2Threshold]
overallGm = round (weightedGm(items), 2 )
dfTier = pl.DataFrame({
"層級" : ["第一層級(高頻)" , "第二層級(中頻)" , "第三層級(低頻)" , "整體加權" ],
"品項數" : [len (tier1), len (tier2), len (tier3), len (items)],
"佔總銷售%" : [
f" { sum (i['銷售次數' ] for i in tier1)/ totalAllSales* 100 :.1f} %" ,
f" { sum (i['銷售次數' ] for i in tier2)/ totalAllSales* 100 :.1f} %" ,
f" { sum (i['銷售次數' ] for i in tier3)/ totalAllSales* 100 :.1f} %" ,
"100%" ,
],
"加權平均毛利%" : [
f" { weightedGm(tier1):.2f} %" ,
f" { weightedGm(tier2):.2f} %" ,
f" { weightedGm(tier3):.2f} %" ,
f" { overallGm} %" ,
],
})
GT(dfTier).tab_header(
title= "三層級毛利權重分析" ,
subtitle= f"共 { len (items)} 項品項,整體加權毛利 { overallGm} %" ,
)
共 1126 項品項,整體加權毛利 19.32%
第一層級(高頻)
75
50.1%
17.86%
第二層級(中頻)
169
30.1%
19.36%
第三層級(低頻)
882
19.8%
22.96%
整體加權
1126
100%
19.32%
高頻品項毛利分佈(問題核心)
Code
gmBands = [
("極低毛利(≤8%)" , 0 , 8 ),
("低毛利(8~12%)" , 8 , 12 ),
("中低毛利(12~16%)" , 12 , 16 ),
("中毛利(16~20%)" , 16 , 20 ),
("中高毛利(20~25%)" , 20 , 25 ),
("高毛利(>25%)" , 25 , 100 ),
]
bandRows = []
for label, lo, hi in gmBands:
band = [i for i in tier1 if i["毛利%" ] <= hi] if lo == 0 else [i for i in tier1 if lo < i["毛利%" ] <= hi]
if not band:
continue
bandSales = sum (i["銷售次數" ] for i in band)
topItems = sorted (band, key= lambda x: x["銷售次數" ], reverse= True )[:3 ]
topStr = "、" .join(f" { i['品名' ][:12 ]}{ i['毛利%' ]:.1f} %" for i in topItems)
bandRows.append({
"毛利區間" : label,
"品項數" : len (band),
"銷售次數" : bandSales,
"加權毛利%" : f" { weightedGm(band):.1f} %" ,
"代表品項" : topStr,
})
dfBand = pl.DataFrame(bandRows)
GT(dfBand).tab_header(title= "高頻品項(Tier1)毛利分佈" , subtitle= "銷售量最大的品項,決定客戶實際毛利" )
銷售量最大的品項,決定客戶實際毛利
極低毛利(≤8%)
8
5604
5.6%
亞柏奶精 1公升5.6%、紅龍卡啦雞腿堡(原味) 4.8%、紅龍卡啦雞腿堡(辣味) 4.8%
低毛利(8~12%)
12
8348
10.1%
安美熱狗50條.8.1%、安美燻腸 1000g.10.1%、紅龍雞塊 1K.10.8%
中低毛利(12~16%)
13
6943
13.6%
正點卡啦雞腿堡(原味)112.9%、正點卡啦雞腿堡(辣)1013.4%、富統小熱狗50支13.9%
中毛利(16~20%)
14
7874
18.6%
香雞城小肉豆1K.18.9%、HISUN起司片84片.16.7%、香香雞米花1K.19.0%
中高毛利(20~25%)
11
6407
22.2%
伊植麥玉米粒(易開)3421.7%、香酥雞塊(HISUN) 22.1%、祥哥冷凍蘿蔔糕12片.(22.7%
高毛利(>25%)
17
9015
32.3%
可頌燒餅(原味) 10片28.1%、抓餅(禾)10片.31.3%、馬鈴薯條(HISUN) 26.9%
低毛利品項警示
毛利 ≤ 12% 且銷售 ≥ 100 次的品項,是造成虧損的主因:
Code
lowGmItems = [i for i in items if i["毛利%" ] <= LOW_GM_THRESHOLD and i["銷售次數" ] >= LOW_GM_MIN_SALES]
lowGmItems.sort(key= lambda x: x["銷售次數" ], reverse= True )
lowGmAvg = round (weightedGm(lowGmItems), 2 ) if lowGmItems else 0
lowGmRows = []
for item in lowGmItems[:15 ]:
gmAmt = round (1000 * item["毛利%" ] / 100 , 0 )
lowGmRows.append({
"品名" : item["品名" ],
"銷售次數" : item["銷售次數" ],
"毛利%" : item["毛利%" ],
"買1000元毛利額" : int (gmAmt),
})
dfLowGm = pl.DataFrame(lowGmRows)
(
GT(dfLowGm)
.tab_header(
title= f"低毛利熱銷品項 TOP15(毛利≤ { LOW_GM_THRESHOLD} %)" ,
subtitle= f"共 { len (lowGmItems)} 項,加權平均毛利 { lowGmAvg} %,買1000元毛利額僅 { int (1000 * lowGmAvg/ 100 )} 元" ,
)
.fmt_integer(columns= ["銷售次數" , "買1000元毛利額" ])
.fmt_number(columns= ["毛利%" ], decimals= 1 )
)
共 46 項,加權平均毛利 8.37%,買1000元毛利額僅 83 元
安美熱狗50條.
1,662
8.1
81
亞柏奶精 1公升
1,270
5.6
56
安美燻腸 1000g.
1,225
10.1
101
紅龍雞塊 1K.
1,106
10.8
108
奇津阿在伯手工蔥抓餅10入
1,055
11.5
115
紅龍卡啦雞腿堡(原味) 10片.
997
4.8
48
紅龍卡啦雞腿堡(辣味) 10片.
884
4.8
48
梨山花生醬2.8K
670
6.6
66
安美三明治火腿1.8K
527
7.1
71
梨山草莓醬3.2k
523
8.3
83
安佳84片乳酪990g(紅)
503
2.7
27
福汎巧克力3K
486
9.3
93
台畜培根A1k
472
11.9
119
富統培根 1K.
407
10.1
101
強匠卡拉雞腿堡(辣味)10片.
396
10.5
105
四、現行低消虧損診斷
若客戶只買低毛利品項(加權毛利 8.37%),現行低消能否覆蓋配送成本?
Code
diagRows = []
for route in ROUTE_ORDER:
cost = avgCosts.get(route, 0 )
curMin = CURRENT_MIN_ORDER.get(route, 0 )
gmAmt = round (curMin * lowGmAvg / 100 , 0 )
net = round (gmAmt - cost, 0 )
if net < 0 :
status = "虧損"
elif net < TARGET_NET_PROFIT:
status = f"不足 { TARGET_NET_PROFIT} 元"
else :
status = "達標"
diagRows.append({
"路線" : route,
"配送成本" : cost,
"現行低消" : curMin,
f"毛利額( { lowGmAvg} %)" : int (gmAmt),
"淨利" : int (net),
"診斷" : status,
})
dfDiag = pl.DataFrame(diagRows)
(
GT(dfDiag)
.tab_header(
title= "現行低消虧損診斷" ,
subtitle= f"假設客戶全買低毛利品項(毛利 { lowGmAvg} %)的最差情境" ,
)
.fmt_number(columns= ["配送成本" ], decimals= 1 )
.fmt_integer(columns= ["現行低消" , f"毛利額( { lowGmAvg} %)" , "淨利" ])
)
假設客戶全買低毛利品項(毛利8.37%)的最差情境
A-PH
182.3
1,000
84
−98
虧損
A-PN
107.1
1,000
84
−23
虧損
A-PT
100.7
1,000
84
−17
虧損
K1
130.6
1,500
126
−5
虧損
K2-岡山以南
85.8
1,000
84
−2
虧損
K2-岡山以北(含岡山)
153.7
2,000
167
13
不足100元
K2-台南
262.2
3,000
251
−11
虧損
若客戶僅購買低毛利品項(≤12%,加權毛利約 8.37%), 買 1,000 元只產生約 84 元毛利,不足以覆蓋任何路線的配送成本 。
五、競爭策略覆寫
以下路線因市場競爭因素,不適用公式計算 ,直接指定低消金額:
Code
overrideRows = []
for route, (overrideMin, reason) in COMPETITIVE_OVERRIDE.items():
cost = avgCosts.get(route, 0 )
curMin = CURRENT_MIN_ORDER.get(route, 0 )
netAvg = round (overrideMin * overallGm / 100 - cost, 0 )
netLow = round (overrideMin * lowGmAvg / 100 - cost, 0 )
overrideRows.append({
"路線" : route,
"現行低消" : curMin,
"指定低消" : overrideMin,
"原因" : reason,
"平均毛利淨利" : int (netAvg),
"低毛利淨利" : int (netLow),
})
dfOverride = pl.DataFrame(overrideRows)
(
GT(dfOverride)
.tab_header(title= "競爭策略覆寫路線" )
.fmt_integer(columns= ["現行低消" , "指定低消" , "平均毛利淨利" , "低毛利淨利" ])
)
路線
現行低消
指定低消
原因
平均毛利淨利
低毛利淨利
A-PT
1,000
1,000
一級戰區(同業低消3000),維持1000搶市佔
92
−17
K2-台南
3,000
3,000
防止台南同業搶客,維持不漲
317
−11
同業低消 3,000 元 ,我方 1,000 元為 3 倍競爭優勢
配送成本僅 100.7 元(全線最低),平均毛利淨利 92 元可接受
屏東是主場根據地,維持低門檻搶市佔率
公司在屏東,台南為遠距市場(配送成本 262 元最高)
現行 3,000 × 平均毛利 = 淨利 317 元,已充足
維持不漲避免被台南本地同業搶走客戶
六、三方案對照總覽
Code
def r500(x):
"""向上取整至500元級距"""
return int (math.ceil(x / MIN_ORDER_STEP) * MIN_ORDER_STEP)
# 三方案的基準毛利%
midGmPct = round ((lowGmAvg + overallGm) / 2 , 2 )
planDefs = {
"A" : ("方案A:安全方案" , lowGmAvg),
"B" : ("方案B:折衷方案" , midGmPct),
"C" : ("方案C:務實方案(建議)" , 15.0 ),
}
# 計算各方案各路線的低消
planResults = {}
for planKey, (planName, gmPct) in planDefs.items():
planResults[planKey] = {}
for route in ROUTE_ORDER:
cost = avgCosts.get(route, 0 )
curMin = CURRENT_MIN_ORDER.get(route, 0 )
# 公式計算
rawMin = (cost + TARGET_NET_PROFIT) / (gmPct / 100 )
calcMin = r500(rawMin)
# 競爭策略覆寫
if route in COMPETITIVE_OVERRIDE:
newMin = COMPETITIVE_OVERRIDE[route][0 ]
else :
newMin = max (calcMin, curMin) # 不得低於現行
planResults[planKey][route] = newMin
Code
compRows = []
for route in ROUTE_ORDER:
cost = avgCosts.get(route, 0 )
curMin = CURRENT_MIN_ORDER.get(route, 0 )
pA = planResults["A" ][route]
pB = planResults["B" ][route]
pC = planResults["C" ][route]
diffC = pC - curMin
compRows.append({
"路線" : route,
"配送成本" : cost,
"現行低消" : curMin,
f"方案A( { lowGmAvg} %)" : pA,
f"方案B( { midGmPct} %)" : pB,
"方案C(15%)" : pC,
"方案C變動" : f"+ { diffC} " if diffC > 0 else ("維持" if diffC == 0 else str (diffC)),
"方案C淨利(平均)" : int (round (pC * overallGm / 100 - cost, 0 )),
})
dfComp = pl.DataFrame(compRows)
(
GT(dfComp)
.tab_header(
title= "三方案對照總覽" ,
subtitle= f"目標淨利 { TARGET_NET_PROFIT} 元 | 級距 { MIN_ORDER_STEP} 元 | 不低於現行 | 含競爭策略覆寫" ,
)
.fmt_number(columns= ["配送成本" ], decimals= 1 )
.fmt_integer(columns= [
"現行低消" ,
f"方案A( { lowGmAvg} %)" ,
f"方案B( { midGmPct} %)" ,
"方案C(15%)" ,
"方案C淨利(平均)" ,
])
)
七、方案C 詳細說明(建議採用)
Code
planCRows = []
for route in ROUTE_ORDER:
cost = avgCosts.get(route, 0 )
curMin = CURRENT_MIN_ORDER.get(route, 0 )
newMin = planResults["C" ][route]
diff = newMin - curMin
netLow = round (newMin * lowGmAvg / 100 - cost, 0 )
net15 = round (newMin * 15.0 / 100 - cost, 0 )
netAvg = round (newMin * overallGm / 100 - cost, 0 )
isOverride = route in COMPETITIVE_OVERRIDE
note = "★競爭策略維持" if isOverride else ""
planCRows.append({
"路線" : route,
"配送成本" : cost,
"現行低消" : curMin,
"建議低消" : newMin,
"變動" : f"+ { diff} " if diff > 0 else ("維持" if diff == 0 else str (diff)),
f"淨利(低毛利 { lowGmAvg} %)" : int (netLow),
"淨利(15%)" : int (net15),
f"淨利(平均 { overallGm} %)" : int (netAvg),
"備註" : note,
})
dfPlanC = pl.DataFrame(planCRows)
(
GT(dfPlanC)
.tab_header(
title= "方案C:務實方案(建議採用)" ,
subtitle= "基準毛利 15% | 結合競爭策略覆寫" ,
)
.fmt_number(columns= ["配送成本" ], decimals= 1 )
.fmt_integer(columns= [
"現行低消" , "建議低消" ,
f"淨利(低毛利 { lowGmAvg} %)" , "淨利(15%)" , f"淨利(平均 { overallGm} %)" ,
])
)
基準毛利 15% | 結合競爭策略覆寫
A-PH
182.3
1,000
2,000
+1000
−15
118
204
A-PN
107.1
1,000
1,500
+500
18
118
183
A-PT
100.7
1,000
1,000
維持
−17
49
92
★競爭策略維持
K1
130.6
1,500
2,000
+500
37
169
256
K2-岡山以南
85.8
1,000
1,500
+500
40
139
204
K2-岡山以北(含岡山)
153.7
2,000
2,000
維持
14
146
233
K2-台南
262.2
3,000
3,000
維持
−11
188
317
★競爭策略維持
調漲路線(4條):
A-PH +1,000 (1000→2000):配送成本最高(182元),現行嚴重不足
A-PN +500 (1000→1500):成本適中,小幅調漲
K1 +500 (1500→2000):高雄市區,小幅調漲
K2-岡山以南 +500 (1000→1500):成本最低,小幅調漲
維持路線(3條):
A-PT 維持 1,000 ★ 一級戰區,同業3,000,維持3倍競爭優勢
K2-岡山以北 維持 2,000 :公式計算值未超過現行
K2-台南 維持 3,000 ★ 防止台南同業搶客
八、建議搭配措施
單純調整低消無法完全解決低毛利客戶虧損問題,建議搭配:
低毛利品項限制 :低毛利品項(≤12%)佔訂單金額 70% 以上者,低消提高至 1.5 倍
最低毛利額門檻 :毛利額需 ≥ 配送成本 + 100 元(從訂單系統端控管)
業務端引導 :引導低毛利客戶搭配中高毛利品項(如自有品牌 HISUN 系列)
定期檢視 :每季檢視各路線虧損客戶名單,針對性調整
九、配送成本說明
本分析使用的配送成本為 CSV 實際營運數據 (非理論估算):
資料來源
CSV 實際營運數據(2025/12 ~ 2026/01)
成本含義
一趟多站路線優化後的「每客戶分攤」成本(含油資+車損+人工)
出發地
屏東縣長治鄉
配送範圍
僅高屏台南配送路線(台中以北為寄貨,不含在內)
車輛規格
3.5 噸貨車
油資參考
柴油 31 元/升,油耗 7 km/升
每公里參考成本
約 9.7 元/公里(油資 4.4 + 折舊 2.5 + 保養 1.5 + 輪胎 0.8 + 保險 0.5)
理論估算(底線成本模型)假設「獨立出車」,算出的每站成本遠高於實際。 CSV 數據已反映路線優化(一趟跑 6~15 站)後的真實分攤成本,更貼近營運現實。