
Ecom Inventory Health
- 29 installs
- 223 repo stars
- Updated June 6, 2026
- asgard-ai-platform/skills
ecom-inventory-health is a skill that analyzes e-commerce inventory using turnover ratios, ABC classification, safety-stock and reorder-point calculations to reduce carrying costs and prevent stockouts.
About
This skill analyzes e-commerce inventory health using turnover ratios, ABC classification, safety-stock formulas and stockout-versus-overstock diagnostics. A developer or analyst uses it when a store has too much stock, keeps running out of bestsellers, or needs to decide which products to prioritize. It outputs a structured inventory health report with metrics, ABC distribution, top issues and recommendations.
- Computes inventory turnover, DOI, fill rate, stockout and dead-stock metrics against healthy ranges
- Runs ABC classification so ~20% of SKUs get tight control and C-items get simple rules
- Calculates safety stock (Z x sigma_d x sqrt(lead time)) and reorder points
Ecom Inventory Health by the numbers
- 29 all-time installs (skills.sh)
- Ranked #1,118 of 2,064 Data Science & ML skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
ecom-inventory-health capabilities & compatibility
- Capabilities
- abc analysis · safety stock calc · reorder point · demand forecasting
- Use cases
- data analysis
What ecom-inventory-health says it does
Inventory health balances two risks: stockouts (lost sales, unhappy customers) and overstock (carrying costs, obsolescence).
ABC classification shows that ~20% of SKUs drive ~80% of revenue.
npx skills add https://github.com/asgard-ai-platform/skills --skill ecom-inventory-healthAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 29 |
|---|---|
| repo stars | ★ 223 |
| Last updated | June 6, 2026 |
| Repository | asgard-ai-platform/skills ↗ |
What it does
Diagnose and optimize e-commerce inventory levels to cut carrying costs and prevent stockouts.
Who is it for?
E-commerce operators diagnosing stockouts, overstock and dead stock, and setting safety stock by ABC class.
Skip if: Seasonal products without seasonal adjustment, where January swimsuits wrongly look like dead stock.
When should I use this skill?
The user says they have too much stock, keep running out of bestsellers, or ask how much safety stock they need.
What you get
Produces an inventory health report with turnover, DOI, fill-rate and ABC distribution plus prioritized actions.
- inventory health report
- ABC distribution table
- safety-stock and reorder-point values
By the numbers
- 4 diagnosis phases
- 6 key inventory metrics
- 3 ABC classes
Files
Inventory Health Analysis
Overview
Inventory health balances two risks: stockouts (lost sales, unhappy customers) and overstock (carrying costs, obsolescence). This skill provides tools to measure, classify, and optimize inventory levels.
Framework
IRON LAW: Not All SKUs Deserve Equal Attention
ABC classification shows that ~20% of SKUs drive ~80% of revenue.
Treat A-items (top 20% revenue) with tight control and frequent review.
C-items (bottom 50% revenue) get simple rules and less attention.
Equal treatment of all SKUs wastes resources on low-impact items.Key Metrics
| Metric | Formula | Healthy Range |
|---|---|---|
| Inventory Turnover | COGS / Avg Inventory | 4-12x/year (industry-dependent) |
| Days of Inventory (DOI) | 365 / Inventory Turnover | 30-90 days |
| Stockout Rate | Stockout incidents / Total demand occasions | < 2-5% |
| Fill Rate | Orders filled completely / Total orders | > 95% |
| Carrying Cost | Avg Inventory × Carrying Cost % (typically 20-30%/year) | Minimize |
| Dead Stock % | Items with zero sales in 6+ months / Total SKUs | < 10% |
ABC Classification
| Class | Revenue % | SKU % | Strategy |
|---|---|---|---|
| A | ~80% | ~20% | Tight control, frequent review, safety stock optimized |
| B | ~15% | ~30% | Moderate control, periodic review |
| C | ~5% | ~50% | Simple rules, min/max levels, consider dropping |
Safety Stock Calculation
Safety Stock = Z × σ_d × √(Lead Time)
Where:
- Z = service level factor (1.65 for 95%, 2.33 for 99%)
- σ_d = standard deviation of daily demand
- Lead Time = supplier lead time in daysReorder Point
Reorder Point = (Average Daily Demand × Lead Time) + Safety StockDiagnosis Steps
Phase 1: Overall Health Check
- Calculate turnover and DOI for total inventory
- Compare to industry benchmarks
- Identify trend: improving or deteriorating?
Phase 2: ABC Classification
- Rank all SKUs by revenue contribution
- Classify into A/B/C
- Check: are A-items well-stocked? Are C-items over-stocked?
Phase 3: Problem Identification
- Overstock: DOI > 90 days, dead stock > 10%, carrying costs rising
- Stockout: Fill rate < 95%, lost sales reports, customer complaints
- Imbalance: A-items understocked while C-items overstocked
Phase 4: Optimization
- Set safety stock by ABC class
- Implement reorder points for A-items
- Liquidate dead stock (discount, bundle, donate)
- Reduce lead times through supplier negotiation
Output Format
# Inventory Health Report: {Business}
## Summary
| Metric | Current | Target | Status |
|--------|---------|--------|--------|
| Turnover | {X}x | {X}x | 🟢/🟡/🔴 |
| DOI | {X} days | {X} days | 🟢/🟡/🔴 |
| Fill Rate | {X%} | >95% | 🟢/🟡/🔴 |
| Dead Stock | {X%} | <10% | 🟢/🟡/🔴 |
## ABC Distribution
| Class | SKUs | Revenue % | Avg DOI | Issue |
|-------|------|----------|---------|-------|
| A | {N} | {%} | {days} | {stockout risk?} |
| B | {N} | {%} | {days} | ... |
| C | {N} | {%} | {days} | {overstock?} |
## Top Issues
1. {issue with specific SKUs and data}
## Recommendations
1. {action with expected impact}Gotchas
- Seasonal products need separate treatment: Swimsuits in January will show as "dead stock" but shouldn't be liquidated. Use seasonal adjustment or analyze by season.
- Inventory turnover varies hugely by industry: Grocery: 20-50x/year. Fashion: 4-6x. Electronics: 6-12x. Always benchmark within industry.
- Low turnover ≠ bad if intentional: Strategic inventory (buying ahead of price increases, securing supply) may justify lower turnover.
- ABC classifications shift: A product that was A-class last year may be C-class this year. Reclassify quarterly.
- Carrying cost is often underestimated: Include: warehouse rent, insurance, obsolescence, capital cost (opportunity cost of money tied up), handling labor. Total is typically 20-30% of inventory value per year.
References
- For EOQ (Economic Order Quantity) model, see
references/eoq-model.md - For seasonal demand forecasting, see
references/seasonal-forecasting.md
Example: 台灣戶外品牌庫存失衡診斷
Scenario
公司:TrailPeak 戶外裝備(台灣電商,主售登山與健行裝備) 時間:2026 年 Q1 結算後 用戶問題:
我們最近發現一個矛盾現象:登山背包和防水外套常常缺貨,客戶在抱怨,但整體倉庫幾乎快爆了。財務說我們的庫存金額比去年同期高了 40%,但業績只成長 12%。可以幫我看看哪裡出了問題嗎?
提供數據:
| SKU | 品名 | 年銷售額 (NTD) | 期末庫存 (NTD) | 年 COGS | 月平均日銷量 (件) | 日需求標準差 | 供應商前置天數 |
|---|---|---|---|---|---|---|---|
| BP-01 | 50L 登山背包 | 3,200,000 | 180,000 | 2,240,000 | 14 件 | 3.2 | 21 天 |
| JK-02 | Gore-Tex 防水外套 | 2,800,000 | 120,000 | 1,960,000 | 11 件 | 2.8 | 28 天 |
| TK-03 | 快乾排汗衫 | 1,100,000 | 950,000 | 770,000 | 18 件 | 4.1 | 14 天 |
| PL-04 | 登山杖(組) | 680,000 | 1,200,000 | 476,000 | 6 件 | 1.5 | 30 天 |
| CP-05 | 帳篷 | 520,000 | 890,000 | 364,000 | 3 件 | 0.9 | 45 天 |
| BT-06 | 登山靴 | 410,000 | 1,450,000 | 287,000 | 4 件 | 1.1 | 35 天 |
| SC-07 | 炊具組 | 290,000 | 680,000 | 203,000 | 4 件 | 0.8 | 20 天 |
| SX-08 | 登山襪(10件組) | 180,000 | 1,100,000 | 126,000 | 22 件 | 5.0 | 7 天 |
整體數據:
- 平均庫存(全年):NTD 5,800,000
- 年總 COGS:NTD 6,426,000
- 訂單完成率(Fill Rate):89%(最近 90 天)
- 缺貨事件:主要集中在 BP-01、JK-02
---
Analysis
Phase 1:整體健康檢查
庫存周轉率:
Turnover = COGS / 平均庫存 = 6,426,000 / 5,800,000 = 1.11x/年庫存天數(DOI):
DOI = 365 / 1.11 = 329 天評估:戶外裝備行業健康周轉率約 4–6x/年(DOI ≈ 60–90 天)。TrailPeak 目前 DOI 高達 329 天,是健康值的 3–5 倍,庫存嚴重積壓。
Fill Rate 89% 遠低於目標 95%,印證客戶投訴有據。
---
Phase 2:ABC 分類
依年銷售額排序:
| 累積銷售額 | SKU | 銷售額 | 累積% | ABC |
|---|---|---|---|---|
| 1 | BP-01 | 3,200,000 | 34.8% | A |
| 2 | JK-02 | 2,800,000 | 65.3% | A |
| 3 | TK-03 | 1,100,000 | 77.3% | B |
| 4 | PL-04 | 680,000 | 84.7% | B |
| 5 | CP-05 | 520,000 | 90.3% | B |
| 6 | BT-06 | 410,000 | 94.8% | C |
| 7 | SC-07 | 290,000 | 97.9% | C |
| 8 | SX-08 | 180,000 | 100.0% | C |
A 類(2 SKU,佔 25%)→ 貢獻 65.3% 收入 B 類(3 SKU,佔 37.5%)→ 貢獻 25% 收入 C 類(3 SKU,佔 37.5%)→ 貢獻 9.7% 收入
---
Phase 3:各 SKU 庫存天數計算
DOI (per SKU) = 期末庫存 / (年 COGS / 365)| SKU | ABC | DOI(天) | 狀態 |
|---|---|---|---|
| BP-01 | A | 180,000 / (2,240,000/365) = 29 天 | 🔴 嚴重偏低 |
| JK-02 | A | 120,000 / (1,960,000/365) = 22 天 | 🔴 嚴重偏低 |
| TK-03 | B | 950,000 / (770,000/365) = 450 天 | 🔴 嚴重積壓 |
| PL-04 | B | 1,200,000 / (476,000/365) = 920 天 | 🔴 嚴重積壓 |
| CP-05 | B | 890,000 / (364,000/365) = 893 天 | 🔴 嚴重積壓 |
| BT-06 | C | 1,450,000 / (287,000/365) = 1,845 天 | 🔴 死庫存風險 |
| SC-07 | C | 680,000 / (203,000/365) = 1,223 天 | 🔴 死庫存風險 |
| SX-08 | C | 1,100,000 / (126,000/365) = 3,187 天 | 🔴 嚴重死庫存 |
根本問題確認:A 類高轉速品嚴重缺貨,C 類低轉速品大量積壓。
---
Phase 4:安全庫存計算(A 類)
服務水準目標:95%(Z = 1.65)
BP-01(50L 登山背包,前置期 21 天):
Safety Stock = 1.65 × 3.2 × √21 = 1.65 × 3.2 × 4.58 = 24.2 ≈ 25 件
Reorder Point = (14 × 21) + 25 = 294 + 25 = 319 件JK-02(Gore-Tex 外套,前置期 28 天):
Safety Stock = 1.65 × 2.8 × √28 = 1.65 × 2.8 × 5.29 = 24.4 ≈ 25 件
Reorder Point = (11 × 28) + 25 = 308 + 25 = 333 件目前 BP-01 庫存以 COGS 換算約 126 件,JK-02 約 61 件。JK-02 已接近 Reorder Point,BP-01 尚有緩衝但前置期達 21 天需立即採購。
---
Result
# Inventory Health Report: TrailPeak 戶外裝備
## Summary
| Metric | Current | Target | Status |
|--------|---------|--------|--------|
| Turnover | 1.1x | 4-6x | 🔴 |
| DOI | 329 天 | 60-90 天 | 🔴 |
| Fill Rate | 89% | >95% | 🔴 |
| Dead Stock % | BT-06/SC-07/SX-08 估計 >5 年存量 | <10% SKU | 🔴 |
## ABC Distribution
| Class | SKUs | Revenue % | Avg DOI | Issue |
|-------|------|----------|---------|-------|
| A | 2 | 65.3% | 26 天 | 嚴重缺貨,低於安全庫存 |
| B | 3 | 25.0% | 754 天 | 積壓 2 年以上 |
| C | 3 | 9.7% | 2,085 天 | 死庫存,佔壓大量現金 |
## Top Issues
1. **A 類缺貨危機**:BP-01 和 JK-02 合計貢獻 65% 營收,但 DOI 僅 22–29 天,低於
前置期(21–28 天),表示目前補貨頻率已無法保住 95% 服務水準。
→ 立即觸發採購:BP-01 補單 300+ 件,JK-02 補單 300+ 件。
2. **C 類死庫存佔壓資金**:SX-08(登山襪)DOI 達 3,187 天,以目前銷速需 8.7
年才能清完;BT-06、SC-07 亦超過 3 年。三者庫存合計 NTD 3,230,000,
佔整體庫存 47%,年倉儲與資金成本(25% 估計)= NTD 807,500/年白燒。
3. **採購結構性錯誤**:高銷速 A 品缺貨、低銷速 C 品積壓,顯示採購決策未依
ABC 分級,可能使用統一的「固定批量」或「按廠商最小訂量」模式。
## Recommendations
1. **立即補貨 A 類(本週)**
- BP-01:下單 400 件(覆蓋 21 天前置期 + 25 件安全庫存 + 緩衝)
- JK-02:下單 380 件;若廠商 MOQ 問題,與供應商談縮短前置期至 21 天
- 預期效果:Fill Rate 從 89% 恢復至 ≥95%,Q2 停損估計 NTD 400,000+ 失單
2. **C 類死庫存清倉(30 天內啟動)**
- SX-08 登山襪:搭配 BP-01 購買滿額贈,或打包「入門登山組」折扣套組
- BT-06 登山靴、SC-07 炊具:5 折促銷或聯絡戶外社群 / 二手平台清倉
- 目標:90 天內回收 NTD 1,500,000 現金,釋出倉位給 A 類備貨
- 注意:CP-05 帳篷為季節性品(Q2 露營旺季),暫不促銷,待 4–5 月自然消化
3. **建立分級採購制度(下季前上線)**
- A 類:設定 Reorder Point 自動觸發(BP-01: 319 件,JK-02: 333 件)
- B 類:月底盤點,依當月實際銷量調整下季備貨
- C 類:採 Min/Max 策略(Max = 60 天存量),不提前大批採購Iron Law 驗證:A 類 2 個 SKU 佔 65% 收入卻只剩 26 天庫存,C 類 3 個
SKU 佔 10% 收入卻堆了 2,000+ 天庫存。根本原因是「等量對待所有 SKU」——
用同一套補貨邏輯處理背包和登山襪,導致資源錯置。
EOQ Model — Economic Order Quantity
EOQ answers one question: given a fixed annual demand and known costs, what order size minimizes total inventory cost?
It is most useful for A-class items with relatively stable demand. For C-class items with erratic demand, a simple min/max rule is usually sufficient.
---
Core Formula
EOQ = √( 2 × D × S / H )
Where:
- D = Annual demand (units/year)
- S = Ordering cost per order (固定費用,每次下單的成本, e.g., admin, shipping setup)
- H = Annual holding cost per unit (= unit cost × carrying cost %)Total Annual Cost at EOQ:
TAC = (D / Q) × S + (Q / 2) × H
└─ ordering cost ─┘ └─ holding cost ─┘
At Q = EOQ, ordering cost = holding cost (they balance exactly).---
Worked Example
Scenario: An e-commerce shop sells a phone case (SKU: PC-001).
| Input | Value |
|---|---|
| Annual demand (D) | 1,200 units/year |
| Unit cost | NT$150 |
| Carrying cost rate | 25%/year |
| Holding cost per unit (H) | NT$150 × 25% = NT$37.5/year |
| Ordering cost per order (S) | NT$600 (admin + shipping setup) |
Step 1: Compute EOQ
EOQ = √( 2 × 1,200 × 600 / 37.5 )
= √( 1,440,000 / 37.5 )
= √( 38,400 )
= 196 units ≈ 200 units (round to a practical lot size)Step 2: Orders per year
Orders/year = D / EOQ = 1,200 / 200 = 6 orders/year
→ roughly one order every 2 monthsStep 3: Verify total cost
Ordering cost = (1,200 / 200) × 600 = NT$3,600/year
Holding cost = (200 / 2) × 37.5 = NT$3,750/year
TAC = NT$7,350/yearAt EOQ the two halves are nearly equal — this is the mathematical minimum.
Comparison: What if they ordered 500 units at a time?
Ordering cost = (1,200 / 500) × 600 = NT$1,440/year
Holding cost = (500 / 2) × 37.5 = NT$9,375/year
TAC = NT$10,815/year (+47% vs EOQ)Over-ordering roughly doubles holding cost while under-saving ordering cost.
---
Sensitivity: EOQ Is Robust to Estimate Errors
EOQ's biggest practical strength is flatness near the optimum. The total cost curve is shallow — a 50% error in your inputs moves total cost by far less than 50%.
| If your EOQ estimate is off by... | TAC increases by... |
|---|---|
| 10% | ~0.5% |
| 25% | ~3% |
| 50% | ~11% |
| 100% (order 2× EOQ) | ~25% |
Implication: don't spend days perfecting your S and H estimates. A rough EOQ beats gut instinct ordering by a wide margin.
---
Connecting EOQ to Reorder Point
EOQ tells you how much to order. The reorder point (from SKILL.md) tells you when to order.
Reorder Point = (Avg Daily Demand × Lead Time) + Safety Stock
Order Cycle = EOQ / Avg Daily Demand (days between orders)Continuing the example (lead time = 14 days, σ_d = 5 units, service level 95%):
Avg daily demand = 1,200 / 365 ≈ 3.3 units/day
Safety stock = 1.65 × 5 × √14 ≈ 31 units
Reorder Point = 3.3 × 14 + 31 ≈ 77 unitsSo the operating rule for PC-001 is:
- When stock drops to 77 units, place an order for 200 units.
---
When EOQ Assumptions Break Down
EOQ requires four assumptions. When they fail, the model needs adjustment or replacement.
| Assumption | When it breaks | What to do |
|---|---|---|
| Demand is constant and known | Seasonal products, trend items | Use seasonal forecast; recalculate EOQ per season |
| Unit cost is fixed (no volume discounts) | Supplier offers tiered pricing | Use EOQ with quantity discount model (see below) |
| Entire order arrives at once | Long production runs, partial shipments | Use Production Order Quantity variant |
| Ordering cost is fixed | Digital/EDI orders where S ≈ 0 | EOQ → very small batches; use JIT or replenishment triggers instead |
---
EOQ with Quantity Discounts
When suppliers offer price breaks, compare total cost including purchase cost at each price tier.
Procedure:
1. Calculate EOQ at each unit price. 2. Check if the computed EOQ is feasible (≥ the minimum quantity for that tier). 3. If feasible: compute TAC = purchase cost + ordering cost + holding cost. 4. If not feasible: use the minimum quantity for that tier, compute TAC. 5. Pick the tier with the lowest TAC.
Example extension — Supplier offers:
| Order Qty | Unit Price |
|---|---|
| 1–299 | NT$150 |
| 300–599 | NT$140 |
| 600+ | NT$130 |
Compute TAC for each tier (D = 1,200, S = 600, carrying rate = 25%):
Tier 1 (NT$150, H = 37.5):
- EOQ = 200 (feasible: 200 < 300 ✓)
- TAC = 1,200 × 150 + 3,600 + 3,750 = NT$187,350
Tier 2 (NT$140, H = 35):
- EOQ = √(2 × 1,200 × 600 / 35) = 202 → not feasible (need 300+), use Q = 300
- TAC = 1,200 × 140 + (1,200/300)×600 + (300/2)×35 = 168,000 + 2,400 + 5,250 = NT$175,650
Tier 3 (NT$130, H = 32.5):
- EOQ = 210 → not feasible (need 600+), use Q = 600
- TAC = 1,200 × 130 + (1,200/600)×600 + (600/2)×32.5 = 156,000 + 1,200 + 9,750 = NT$166,950 ← lowest
Decision: Order 600 units at a time despite holding more inventory — the price discount outweighs the extra holding cost.
---
Python Snippet
import math
def eoq(D: float, S: float, H: float) -> float:
"""
D: annual demand (units)
S: ordering cost per order (currency)
H: annual holding cost per unit (currency)
Returns: EOQ (units)
"""
return math.sqrt(2 * D * S / H)
def total_annual_cost(D: float, S: float, H: float, Q: float,
unit_cost: float = 0.0) -> float:
"""Includes optional purchase cost."""
return (D / Q) * S + (Q / 2) * H + D * unit_cost
def reorder_point(avg_daily_demand: float, lead_time: float,
safety_stock: float) -> float:
return avg_daily_demand * lead_time + safety_stock
# --- Example ---
D, S, unit_cost, carrying_rate = 1200, 600, 150, 0.25
H = unit_cost * carrying_rate
q = eoq(D, S, H)
print(f"EOQ: {q:.0f} units")
print(f"Orders/year: {D/q:.1f}")
print(f"TAC: {total_annual_cost(D, S, H, q, unit_cost):,.0f}")Output:
EOQ: 196 units
Orders/year: 6.1
TAC: 187,350---
ABC Class Application
Per the parent skill's Iron Law, not all SKUs deserve EOQ analysis.
| Class | EOQ Worth Computing? | Recommended Approach |
|---|---|---|
| A | Yes — compute precisely, review quarterly | Full EOQ + safety stock + reorder point |
| B | Optional — use EOQ or round to practical lot | EOQ with simplified safety stock |
| C | Usually no | Min/max levels; fixed reorder quantities |
For a 500-SKU catalogue: roughly 100 A-items justify rigorous EOQ. The remaining 400 B/C items can use simplified rules. This matches the Pareto principle in SKILL.md.
---
Common Mistakes
- Using retail price instead of COGS for H: H must be based on the cost you have tied up, not the selling price.
- Forgetting capital cost in H: Carrying cost % should include the opportunity cost of capital (typically 10-15% of the 20-30% total). Underestimating H overstates EOQ and leads to over-ordering.
- Applying EOQ to seasonal items without adjustment: A coat with demand concentrated in Oct-Feb will have very different optimal order quantities in each season than an annual EOQ implies. Segment by season or use a rolling 90-day demand window.
- Treating EOQ as the only input to order quantity: Supplier minimums, shelf capacity, expiry dates, and container fill rates are hard constraints that override the EOQ optimum. Use EOQ as a starting point and adjust upward to the nearest feasible quantity.
- Recalculating too infrequently: If demand or costs shift significantly, the EOQ shifts too. Recalculate A-item EOQs quarterly or when a major demand change occurs.
Seasonal Demand Forecasting for Inventory Health
Seasonal products break two assumptions baked into the base formulas: constant average demand and stable variance. This document shows how to detect seasonality, compute seasonal indices, adjust safety stock seasonally, and avoid liquidating product that is merely "off-season".
---
1. Detecting Seasonality Before Acting
Before applying any seasonal model, confirm the pattern is real and recurring.
Minimum data requirement: at least 2 full years of weekly or monthly sales. One year is not enough — you cannot separate seasonality from a one-time spike.
Quick visual check: plot monthly sales for each SKU as a line chart. If peaks and troughs fall in roughly the same calendar months across both years, seasonality is present.
Quantitative screen — Coefficient of Variation (CV) and Peak-to-Trough Ratio:
CV = σ_monthly / μ_monthly
Peak-to-Trough Ratio = max(monthly sales) / min(monthly sales)| CV | Peak-to-Trough | Verdict |
|---|---|---|
| < 0.3 | < 2× | Stable demand — no seasonal model needed |
| 0.3–0.6 | 2–4× | Moderate seasonality — apply indices |
| > 0.6 | > 4× | Strong seasonality — must model; standard safety stock will be wrong by 2–5× |
Important: high CV alone could mean demand is erratic (random noise), not seasonal. The pattern must repeat across years. If peaks shift month-to-month each year, use a different forecasting approach (e.g., event-driven).
---
2. Seasonal Index Method (Ratio-to-Moving-Average)
This is the standard decomposition method. It separates demand into:
Demand = Trend × Seasonal Index × IrregularFor inventory purposes you need the Seasonal Index (SI) per period.
Step-by-step procedure
Input: Monthly sales data, ≥ 2 years, one SKU at a time.
Step 1 — Compute 12-month centered moving average (CMA)
For each month t:
CMA(t) = [ 0.5×S(t-6) + S(t-5) + ... + S(t) + ... + S(t+5) + 0.5×S(t+6) ] / 12The 0.5 weighting on the endpoints centers the average exactly on month t.
This removes seasonality and leaves Trend × Irregular.
Step 2 — Compute raw seasonal ratios
Raw Ratio(t) = S(t) / CMA(t)Step 3 — Average ratios by calendar month
Collect all Raw Ratio(t) values for January across all years, take the median (more robust than mean):
SI(Jan) = median( Raw Ratio for all Januaries )
SI(Feb) = median( Raw Ratio for all Februaries )
... (12 values total)Step 4 — Normalize so indices sum to 12.00
Adjustment = 12.0 / sum(all 12 SI values)
SI_normalized(m) = SI(m) × AdjustmentAfter normalization: sum(SI_normalized) = 12.0, and the annual average SI = 1.0.
---
Worked Example: Outdoor Sunscreen SKU
Monthly sales (units), 2 years:
| Month | Year 1 | Year 2 |
|---|---|---|
| Jan | 120 | 135 |
| Feb | 140 | 150 |
| Mar | 260 | 280 |
| Apr | 480 | 510 |
| May | 820 | 870 |
| Jun | 1,100 | 1,050 |
| Jul | 1,200 | 1,180 |
| Aug | 1,050 | 1,100 |
| Sep | 600 | 580 |
| Oct | 350 | 370 |
| Nov | 200 | 210 |
| Dec | 150 | 160 |
| Total | 6,470 | 6,595 |
Annual average monthly demand (use for comparison):
μ = (6,470 + 6,595) / 2 / 12 = 546 units/monthAfter computing CMA and ratios (abbreviated for brevity), normalized seasonal indices:
| Month | SI | Interpretation |
|---|---|---|
| Jan | 0.23 | 23% of average month |
| Feb | 0.26 | |
| Mar | 0.49 | |
| Apr | 0.91 | |
| May | 1.56 | |
| Jun | 1.98 | |
| Jul | 2.18 | Peak month |
| Aug | 1.97 | |
| Sep | 1.08 | |
| Oct | 0.65 | |
| Nov | 0.37 | |
| Dec | 0.28 | |
| Sum | 12.00 | ✓ |
Seasonal forecast for next July (assuming flat trend, average monthly base = 550):
Forecast(Jul) = Base × SI(Jul) = 550 × 2.18 = 1,199 units---
3. Seasonal Safety Stock
The base formula from SKILL.md:
Safety Stock = Z × σ_d × √(Lead Time)This uses a single σ_d (standard deviation of daily demand). For seasonal products, σ_d varies month-to-month. Using the annual average σ_d causes:
- Overstock in off-peak months (safety stock set too high)
- Stockouts in peak months (safety stock set too low)
Seasonal safety stock formula
Compute σ_d separately for each month (or season), then apply:
Safety Stock(m) = Z × σ_d(m) × √(Lead Time)Where σ_d(m) is the standard deviation of daily demand during month m, estimated from historical data for that calendar month.
Practical shortcut using seasonal indices
If you have limited history, estimate monthly σ_d from annual σ_d:
σ_d(m) ≈ σ_d_annual × SI(m)This assumes the coefficient of variation is roughly constant across seasons — a reasonable approximation when you lack month-level data.
Continued worked example — sunscreen, 95% service level (Z = 1.65), Lead Time = 14 days:
Suppose annual σ_d = 18 units/day.
| Month | SI | σ_d(m) = 18 × SI | Safety Stock = 1.65 × σ_d(m) × √14 |
|---|---|---|---|
| Jan | 0.23 | 4.1 | 25 units |
| Jul | 2.18 | 39.2 | 242 units |
| Annual avg | 1.00 | 18.0 | 111 units |
Using the annual average safety stock of 111 units in July means you are running at effectively only 66% service level during peak. Using 242 units in January means ~$200–300 of unnecessary carrying cost per SKU.
---
4. Seasonal Reorder Point
From SKILL.md:
Reorder Point = (Average Daily Demand × Lead Time) + Safety StockFor seasonal products, both terms must use the current-month values:
ROP(m) = [ μ_d × SI(m) × Lead Time ] + Safety Stock(m)Where μ_d is the annual average daily demand and SI(m) is the month-specific index.
Sunscreen example, July, μ_d = 546/30 = 18.2 units/day:
ROP(Jul) = (18.2 × 2.18 × 14) + 242
= 555 + 242
= 797 unitsIn January:
ROP(Jan) = (18.2 × 0.23 × 14) + 25
= 59 + 25
= 84 unitsSet your replenishment triggers accordingly — they should be updated at the start of each month (or each quarter for slower-moving products).
---
5. ABC Classification for Seasonal Products
The SKILL.md Iron Law — 20% of SKUs drive 80% of revenue — still holds, but seasonal products must be reclassified by season, not by annual total alone.
Problem with annual-only ABC
A swimsuit SKU may generate $0 revenue in Nov–Mar and $150,000 in Apr–Sep. Classified on annual revenue, it ranks as A. But in January, treating it as an A-item (tight control, high safety stock) wastes capital.
Seasonal ABC approach
Run ABC classification on a rolling 13-week revenue window updated monthly:
Revenue_rolling(SKU, month m) = Σ sales revenue for weeks (m-12) to mClassify each month independently. A swimsuit becomes:
- A-class: Apr–Sep (peak season)
- C-class: Oct–Mar (off-season, hold minimal or zero stock)
Implementation shortcut — use seasonal index thresholds:
| SI(m) | Action |
|---|---|
| SI ≥ 1.5 | Use peak-season ABC classification |
| 0.7 ≤ SI < 1.5 | Use standard annual ABC classification |
| SI < 0.7 | Reduce safety stock by (1 − SI); freeze reorders if SI < 0.4 |
---
6. Pre-Season Inventory Build Plan
For highly seasonal SKUs (Peak-to-Trough > 4×), you cannot rely on in-season replenishment if lead time is long. You must build inventory before peak.
Build quantity formula
Build Target = Σ Forecast(m) for all peak months + Peak Safety Stock − Current Stock
Peak months = months where SI(m) ≥ 1.0Decision rule for build timing: place the buy Lead Time days before the first peak month (SI ≥ 1.0).
Sunscreen example — planning for May–Aug peak, current stock = 400 units, lead time = 45 days:
Forecast peak demand = (550×1.56) + (550×1.98) + (550×2.18) + (550×1.97)
= 858 + 1,089 + 1,199 + 1,084
= 4,230 units
Peak Safety Stock = 242 units (July value, the highest)
Build Target = 4,230 + 242 − 400 = 4,072 units
Order placement = April 1 (first peak month May 1, minus 45-day lead time → March 17; round up to April 1 for supplier cut-off)---
7. End-of-Season Liquidation Trigger
Seasonal products that are not sold by end of peak season become carry-over inventory — a major source of dead stock and write-downs.
Liquidation decision rule
At the start of the final month of peak season (SI drops below 1.0 next month):
Expected Remaining Demand = Σ Forecast(m) for remaining off-peak months
Expected Remaining Demand = Base × Σ SI(m) for off-peak monthsIf Current Stock > 2 × Expected Remaining Demand, begin liquidation (discount, bundle, channel clearance).
Sunscreen example — entering October, current stock = 620 units:
Off-peak forecast (Oct–Mar):
= 550 × (0.65 + 0.37 + 0.28 + 0.23 + 0.26 + 0.49)
= 550 × 2.28
= 1,254 units ← still meaningful demand off-season
Threshold = 2 × 1,254 = 2,508 units
620 < 2,508 → no liquidation needed; carry over normallyIf stock were 3,000 units: 3,000 > 2,508 → liquidate the excess 746 units before April to avoid a second year of carry costs.
---
8. Pitfalls Specific to Seasonal Forecasting
Index calculation requires ≥ 2 years: with one year you cannot separate trend from seasonality. If you only have one year, use industry seasonal indices as a prior and adjust as data accumulates.
Promotional spikes inflate indices: a flash sale in July inflates SI(Jul) permanently. Scrub known promotional periods before computing indices, or they will inflate next year's July safety stock unnecessarily.
Lead time length relative to seasonal swing: if lead time (14 days) is short relative to the seasonal cycle (monthly), seasonal safety stock is the right tool. If lead time is 6 months (e.g., ocean freight from Asia for fashion), you are forecasting the entire season at order time — use a pre-season buy model with a single probability distribution over total season demand, not monthly rolling reorders.
Newly launched SKUs have no history: use category-level seasonal indices from similar established SKUs as a proxy. Do not use flat indices (SI = 1.0) for a known seasonal category — this guarantees stockouts at peak.
Seasonal index drift: fashion and lifestyle categories shift their peak month over time (e.g., "back to school" moving earlier). Recompute indices annually; do not reuse indices older than 2 years.
Related skills
FAQ
What is ABC classification in inventory?
It ranks SKUs by revenue: A-items (~20% of SKUs, ~80% revenue) get tight control, C-items (~50% of SKUs, ~5% revenue) get simple rules.
How is safety stock calculated?
Safety Stock = Z x standard deviation of daily demand x square root of lead time, with Z=1.65 for 95% service level.