SOP-INV-02: Demand Forecasting & Inventory Planning¶
Version: 1.0 PRODUCTION_READY
Effective Date: 2025-12-10
Owner: Operations Team
Review Cycle: Quarterly
Dependency: SOP-INV-01 (Inventory Control)
1. PURPOSE & SCOPE¶
1.1 Purpose¶
This SOP establishes the systematic process for: - Forecasting weekly demand based on active subscriptions + new customer allowance - Calculating stock days coverage and reorder points - Triggering co-packer production orders at the right time - Preventing stockouts while minimizing frozen storage costs
1.2 Scope¶
In Scope: - Subscription-based demand calculation - New customer demand allowance - Stock coverage monitoring - Reorder point calculation and alerting - Weekly inventory planning summary
Out of Scope: - Physical inventory control (covered by SOP-INV-01) - Batch receipt and QA hold management (covered by SOP-01) - Pack day operations (covered by SOP-PACK-01)
1.3 Key Principles¶
- Subscription demand is predictable — active subscriptions define baseline demand
- Buffer for growth — new customer allowance absorbs acquisition variance
- Lead time drives timing — reorder when stock = (demand × lead time) + safety stock
- Conservative in Phase A — over-stock slightly rather than risk stockout during credibility-building
2. THE DEMAND FORMULA¶
2.1 Weekly Demand Calculation¶
Total Weekly Demand (kg) = Subscription Base + New Customer Allowance
Where:
├── Subscription Base = SUM(box_kg / frequency_weeks) for all active subscriptions
│ Example: 300 customers × avg 2kg/week = 600 kg/week
│
└── New Customer Allowance = Target New Customers × Avg First Box
Example: 20 new customers/week × 8kg = 160 kg/week
Total = 600 + 160 = 760 kg/week
2.2 Stock Days Coverage¶
Stock Days Coverage = Available Inventory (kg) ÷ Daily Demand (kg)
Where:
├── Available Inventory = SUM of kg_available for RELEASED batches only
└── Daily Demand = Total Weekly Demand ÷ 7
Example:
├── Available: 3,000 kg
├── Weekly Demand: 760 kg
├── Daily Demand: 760 ÷ 7 = 108.6 kg
└── Coverage: 3,000 ÷ 108.6 = 27.6 days
2.3 Reorder Point Calculation¶
Reorder Point (kg) = (Weekly Demand × Lead Time Weeks) + Safety Stock
Where:
├── Lead Time Weeks = Production + Lab Testing + Delivery (default: 4 weeks)
└── Safety Stock = Weekly Demand × Safety Stock Weeks (default: 2 weeks)
Example:
├── Weekly Demand: 760 kg
├── Lead Time: 4 weeks
├── Safety Stock: 2 weeks × 760 kg = 1,520 kg
└── Reorder Point: (760 × 4) + 1,520 = 4,560 kg
→ When available inventory drops below 4,560 kg, trigger production order
2.4 Suggested Order Quantity¶
Order Quantity = Target Stock Level - Current Stock - QA Hold Stock
Where:
├── Target Stock Level = Weekly Demand × (Lead Time + Safety + Buffer Weeks)
│ Buffer = 2 weeks (gets you to comfortable coverage post-delivery)
├── Current Stock = Available inventory (RELEASED batches)
└── QA Hold Stock = Inventory awaiting lab results (will become available)
Example:
├── Target: 760 × (4 + 2 + 2) = 6,080 kg
├── Current: 3,000 kg
├── QA Hold: 500 kg (batch in testing)
└── Order: 6,080 - 3,000 - 500 = 2,580 kg
3. CONFIGURABLE PARAMETERS¶
All planning parameters are stored in raw_ops.inventory_planning_config and can be adjusted without modifying SQL:
| Parameter | Default | Description |
|---|---|---|
lead_time_weeks |
4 | Co-packer production + lab + delivery |
safety_stock_weeks |
2 | Buffer to absorb variance |
target_new_customers_per_week |
20 | Expected weekly acquisitions |
avg_first_box_kg |
8 | Average first order size |
reorder_alert_threshold_days |
28 | Trigger "REORDER" status |
critical_stock_threshold_days |
14 | Trigger "CRITICAL" status |
To update a parameter:
UPDATE raw_ops.inventory_planning_config
SET config_value = 30,
updated_at = NOW(),
updated_by = 'anton'
WHERE config_key = 'target_new_customers_per_week';
4. DATABASE VIEWS¶
4.1 Master Dashboard View¶
View: analytics.v_inventory_planning_dashboard
Single-row view containing all planning metrics. Use in Metabase dashboard header.
| Column | Description |
|---|---|
report_date |
Current date |
available_kg |
RELEASED inventory available for allocation |
qa_hold_kg |
Inventory pending lab results |
active_subscription_count |
Active subscription count |
subscription_weekly_kg |
Weekly kg from subscriptions |
new_customer_weekly_kg |
Weekly kg allowance for new customers |
total_weekly_demand_kg |
Combined weekly demand |
stock_days_coverage |
Days until stockout at current rate |
stock_weeks_coverage |
Weeks until stockout |
reorder_point_kg |
Stock level triggering reorder |
stock_status |
OK / REORDER / CRITICAL / STOCKOUT |
reorder_required |
TRUE if below reorder point |
suggested_order_kg |
Recommended production order |
4.2 Weekly Forecast View¶
View: analytics.v_demand_forecast_weekly
6-week rolling forecast for trend visualization.
| Column | Description |
|---|---|
week_starting |
Start of forecast week |
subscription_kg |
Base subscription demand |
new_customer_allowance_kg |
Growth buffer |
total_forecast_kg |
Combined weekly forecast |
cumulative_kg |
Running total (for stock drawdown chart) |
4.3 Upcoming Billing View¶
View: analytics.v_upcoming_billing_demand
Actual orders expected based on next_billing_date.
| Column | Description |
|---|---|
billing_date |
Date orders will be charged |
orders_expected |
Number of subscription renewals |
kg_required |
Total kg for that day's orders |
boxes_8kg / boxes_12kg / boxes_16kg |
Breakdown by box size |
5. MAKE.COM SCENARIOS¶
Scenario 1: Daily Inventory Planning Summary (08:00 UTC)¶
Name: [SOP-INV-02](SOP-INV-02_Demand_Forecasting_v1_0_PRODUCTION_READY.md) Daily Inventory Planning
Trigger: Schedule - Every day at 08:00 UTC
Modules:
- Schedule (Trigger)
-
Every day at 08:00 UTC
-
HTTP - Make a Request
- URL:
https://[project].supabase.co/rest/v1/rpc/get_inventory_reorder_status - Method: POST
- Headers:
- apikey:
[service_role_key] - Content-Type:
application/json
- apikey:
-
Parse response: Yes
-
Router (based on stock_status)
Route A: Status = CRITICAL or STOCKOUT
4a. Slack - Create a Message
- Channel: #ops-urgent
- Text:
🚨 *CRITICAL STOCK ALERT*
Stock Status: {{2.stock_status}}
Days Coverage: {{2.stock_days_coverage}} days
Available: {{2.available_kg}} kg
⚠ï¸ *ACTION REQUIRED*
Reorder Point: {{2.reorder_point_kg}} kg
Suggested Order: {{2.suggested_order_kg}} kg
Weekly Demand: {{2.weekly_demand_kg}} kg
Active Subs: {{2.subscription_count}}
→ Contact co-packer immediately
Route B: Status = REORDER
4b. Slack - Create a Message
- Channel: #ops-daily
- Text:
📦 *REORDER REMINDER*
Stock Status: {{2.stock_status}}
Days Coverage: {{2.stock_days_coverage}} days
Available: {{2.available_kg}} kg
Reorder Point: {{2.reorder_point_kg}} kg
Suggested Order: {{2.suggested_order_kg}} kg
→ Plan co-packer order this week
Route C: Status = OK
4c. Slack - Create a Message
- Channel: #ops-daily
- Text:
📊 *Daily Inventory Status* - {{formatDate(now; "DD MMM YYYY")}}
✅ Stock Status: OK
📦 Available: {{2.available_kg}} kg
â±ï¸ Coverage: {{2.stock_days_coverage}} days
📈 Weekly Demand: {{2.weekly_demand_kg}} kg
👥 Active Subs: {{2.subscription_count}}
🔬 QA Hold: {{2.qa_hold_kg}} kg
- Error Handler (on HTTP module)
- Slack →
#ops-alerts - Text:
⚠ï¸ [SOP-INV-02](SOP-INV-02_Demand_Forecasting_v1_0_PRODUCTION_READY.md) Scenario 1 Error: {{error.message}}
Scenario 2: Weekly Planning Report (Monday 09:00 UTC)¶
Name: [SOP-INV-02](SOP-INV-02_Demand_Forecasting_v1_0_PRODUCTION_READY.md) Weekly Planning Report
Trigger: Schedule - Every Monday at 09:00 UTC
Modules:
- Schedule (Trigger)
-
Every Monday at 09:00 UTC
-
HTTP - Supabase Query (Inventory Dashboard)
- URL:
https://[project].supabase.co/rest/v1/v_inventory_planning_dashboard?select=* - Method: GET
-
Headers: apikey, Accept: application/json
-
HTTP - Supabase Query (Weekly Forecast)
- URL:
https://[project].supabase.co/rest/v1/v_demand_forecast_weekly?select=* - Method: GET
-
Headers: apikey, Accept: application/json
-
HTTP - Supabase Query (Upcoming Billing - Next 7 Days)
- URL:
https://[project].supabase.co/rest/v1/v_upcoming_billing_demand?billing_date=gte.{{formatDate(now; "YYYY-MM-DD")}}&billing_date=lt.{{formatDate(addDays(now; 7); "YYYY-MM-DD")}} -
Method: GET
-
Slack - Create a Message
- Channel:
#ops-weekly - Text:
📋 *WEEKLY INVENTORY PLANNING REPORT* Week of {{formatDate(now; "DD MMM YYYY")}} â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â” *CURRENT STATUS* Stock: {{2.available_kg}} kg ({{2.stock_days_coverage}} days coverage) Status: {{2.stock_status}} QA Hold: {{2.qa_hold_kg}} kg *DEMAND FORECAST* Subscription Base: {{2.subscription_weekly_kg}} kg/week New Customer Buffer: {{2.new_customer_weekly_kg}} kg/week Total Weekly: {{2.total_weekly_demand_kg}} kg/week *NEXT 7 DAYS BILLING* {{#each 4.data}} • {{billing_date}}: {{orders_expected}} orders ({{kg_required}} kg) {{/each}} *REORDER STATUS* Reorder Point: {{2.reorder_point_kg}} kg Reorder Required: {{2.reorder_required}} Suggested Order: {{2.suggested_order_kg}} kg â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â” Lead Time: {{2.lead_time_weeks}} weeks Safety Stock: {{2.safety_stock_weeks}} weeks
6. METABASE DASHBOARD¶
Dashboard: "Inventory Planning"¶
Row 1: Status Cards (4 across) - Stock Status (OK/REORDER/CRITICAL with color) - Days Coverage - Available Stock (kg) - Weekly Demand (kg)
Row 2: Demand Forecast Chart
- Line chart from v_demand_forecast_weekly
- X-axis: week_starting
- Y-axis: total_forecast_kg
- Overlay: cumulative_kg as area
Row 3: Upcoming Billing Calendar
- Table from v_upcoming_billing_demand
- Show next 6 weeks
- Highlight high-volume days
Row 4: Reorder Decision - Reorder Point vs Current Stock (gauge chart) - Suggested Order Quantity - "Order Now" indicator if reorder_required = TRUE
7. OPERATIONAL PROCEDURES¶
7.1 Daily Check (08:15 AM)¶
- Review Slack message from Scenario 1
- If status = REORDER or CRITICAL:
- Check upcoming billing demand
- Contact co-packer if order needed
- Update ops_events with decision
7.2 Weekly Review (Monday AM)¶
- Review Weekly Planning Report in Slack
- Validate subscription count matches Shopify
- Adjust
target_new_customers_per_weekif acquisition trending differently - Confirm lead time assumption still accurate
- Place production order if reorder_required = TRUE
7.3 Parameter Tuning (Monthly)¶
When to increase safety stock: - Experienced a near-stockout - Co-packer has shown delivery variance - Lab turnaround has exceeded SLA
When to decrease safety stock: - Storage costs becoming significant - Demand very stable for 60+ days - Confident in co-packer reliability
When to adjust new customer allowance: - Actual acquisition 20%+ above/below target for 4+ weeks - Launching new marketing campaign - Pausing paid acquisition
8. PHASE A DEFAULTS¶
For launch with 0-500 customers:
| Parameter | Value | Rationale |
|---|---|---|
| Lead Time | 4 weeks | Conservative until co-packer proves faster |
| Safety Stock | 2 weeks | Buffer for Phase A learning curve |
| New Customers/Week | 20 | Target 80-100/month ramp |
| Avg First Box | 8kg | Most common starter size |
| Reorder Threshold | 28 days | ~1 month warning |
| Critical Threshold | 14 days | 2-week urgent alert |
Phase B Adjustments (500-2,000 customers): - Consider reducing safety stock to 1.5 weeks if reliable - Increase new customer allowance based on actual acquisition rate - Add seasonal adjustment factors (Q4 dip, January spike)
9. FAILURE MODES¶
| Failure | Detection | Response |
|---|---|---|
| Scenario 1 doesn't run | No 08:00 Slack message | Check Make.com history, run manually |
| Subscription count = 0 | Dashboard shows 0 demand | Check Seal sync, investigate data gap |
| Stock status stuck on OK | Never triggers reorder | Validate inventory table has correct kg |
| Coverage shows NULL | Division by zero | Ensure at least 1 active subscription |
| Co-packer misses lead time | Batch arrives late | Increase lead_time_weeks parameter |
10. METABASE DASHBOARD QUERIES¶
10.1 Dashboard Header Cards¶
Card 1: Stock Status
SELECT
stock_status,
CASE stock_status
WHEN 'OK' THEN '✅'
WHEN 'REORDER' THEN '📦'
WHEN 'CRITICAL' THEN '🚨'
WHEN 'STOCKOUT' THEN 'âŒ'
END AS status_icon
FROM analytics.v_inventory_planning_dashboard;
Card 2: Days Coverage
SELECT
stock_days_coverage AS "Days",
stock_weeks_coverage AS "Weeks"
FROM analytics.v_inventory_planning_dashboard;
Card 3: Available Stock
SELECT
available_kg || ' kg' AS "Available",
qa_hold_kg || ' kg' AS "QA Hold"
FROM analytics.v_inventory_planning_dashboard;
Card 4: Weekly Demand
SELECT
total_weekly_demand_kg || ' kg' AS "Weekly Demand",
active_subscription_count AS "Active Subs"
FROM analytics.v_inventory_planning_dashboard;
10.2 Demand Forecast Chart¶
Line chart: 6-week forecast
SELECT
week_starting,
subscription_kg AS "Subscription Demand",
new_customer_allowance_kg AS "New Customer Buffer",
total_forecast_kg AS "Total Forecast"
FROM analytics.v_demand_forecast_weekly
ORDER BY week_starting;
10.3 Stock Drawdown Chart¶
Area chart: Cumulative demand vs available stock
SELECT
week_starting,
cumulative_kg AS "Cumulative Demand",
(SELECT available_kg FROM analytics.v_inventory_planning_dashboard) AS "Current Stock"
FROM analytics.v_demand_forecast_weekly
ORDER BY week_starting;
10.4 Upcoming Billing Table¶
Table: Next 6 weeks of orders
SELECT
billing_date AS "Date",
orders_expected AS "Orders",
kg_required AS "kg Required",
boxes_8kg AS "8kg",
boxes_12kg AS "12kg",
boxes_16kg AS "16kg"
FROM analytics.v_upcoming_billing_demand
ORDER BY billing_date
LIMIT 42;
10.5 Reorder Decision Panel¶
Single value cards
-- Reorder Point
SELECT reorder_point_kg || ' kg' AS "Reorder Point"
FROM analytics.v_inventory_planning_dashboard;
-- Current vs Reorder (for gauge)
SELECT
available_kg AS "Current",
reorder_point_kg AS "Reorder Point",
ROUND((available_kg / NULLIF(reorder_point_kg, 0) * 100)::numeric, 0) AS "% of Reorder Point"
FROM analytics.v_inventory_planning_dashboard;
-- Suggested Order
SELECT
CASE
WHEN reorder_required THEN '⚠ï¸ ORDER NOW: ' || suggested_order_kg || ' kg'
ELSE '✅ No order needed'
END AS "Action"
FROM analytics.v_inventory_planning_dashboard;
10.6 Planning Parameters Reference¶
Table: Current config values
SELECT
config_key AS "Parameter",
config_value AS "Value",
description AS "Description",
updated_at AS "Last Updated"
FROM raw_ops.inventory_planning_config
ORDER BY config_key;
11. DOCUMENT CONTROL¶
Version History:
| Version | Date | Author | Changes |
|---|---|---|---|
| 1.0 DRAFT | 2025-12-10 | Anton + Claude | Initial creation |
| 1.0 PRODUCTION_READY | 2025-12-10 | Anton + Claude | SQL deployed, Make.com scenario built, Metabase queries added |
Deployment Checklist:
- [x] Configuration table created (
raw_ops.inventory_planning_config) - [x] Subscription demand view (
analytics.v_subscription_demand) - [x] Inventory status view (
analytics.v_inventory_status) - [x] Master planning dashboard view (
analytics.v_inventory_planning_dashboard) - [x] Weekly forecast view (
analytics.v_demand_forecast_weekly) - [x] Upcoming billing view (
analytics.v_upcoming_billing_demand) - [x] RPC function for Make.com (
public.get_inventory_reorder_status) - [x] Permissions granted
- [x] Make.com scenario created (
[SOP-INV-02](SOP-INV-02_Demand_Forecasting_v1_0_PRODUCTION_READY.md) Daily Inventory Planning) - [ ] Make.com scenario activated (pending launch)
- [ ] Metabase dashboard built (optional, queries provided)
Approval:
| Role | Name | Date |
|---|---|---|
| Founder | Anton | 2025-12-10 |
Next Review Date: 2026-03-10 (Quarterly)
Document Location:
- Master: Google Drive > Operations > SOPs > [SOP-INV-02](SOP-INV-02_Demand_Forecasting_v1_0_PRODUCTION_READY.md)_v1_0_PRODUCTION_READY.md
Protocol Raw — Verified safe, batch by batch.