
Demand Forecasting
- 110 installs
- 41 repo stars
- Updated March 13, 2026
- finsilabs/awesome-ecommerce-skills
Predict inventory needs from sales history, seasonality, and lead times to set reorder points and prevent stockouts or overstock.
About
Recommends inventory-planning tools per platform and covers building custom time-series forecasting with safety-stock formulas. A developer uses it when stockouts or overstock signal that reorder points are set by intuition, or for seasonal planning.
- Per-platform and multi-channel forecasting tool table (Inventory Planner, ATUM, Linnworks)
- Custom moving-average and seasonal-decomposition approach on the order database
Demand Forecasting by the numbers
- 110 all-time installs (skills.sh)
- Ranked #786 of 2,064 Data Science & ML skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill demand-forecastingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 110 |
|---|---|
| repo stars | ★ 41 |
| Last updated | March 13, 2026 |
| Repository | finsilabs/awesome-ecommerce-skills ↗ |
What it does
Predict inventory needs from sales history, seasonality, and lead times to set reorder points and prevent stockouts or overstock.
Files
Demand Forecasting
Overview
Demand forecasting uses historical sales data, seasonal patterns, and lead times to predict how much inventory you'll need and when to reorder. Chronic stockouts or overstock situations are usually a sign that reorder points are based on intuition rather than data. Purpose-built inventory planning tools handle this for most merchants — custom forecasting code is only necessary for unique operational requirements.
When to Use This Skill
- When chronic stockouts or overstock situations indicate that current reorder points are set incorrectly
- When building automated replenishment recommendations to reduce manual inventory review
- When planning inventory for seasonal peaks (Black Friday, back-to-school, holiday season)
- When you have 12+ months of sales history and want to extract meaningful demand patterns
- When integrating with supplier lead times and purchase order workflows for end-to-end replenishment
Core Instructions
Step 1: Determine your platform and choose the right forecasting tool
| Platform | Recommended Tool | Why |
|---|---|---|
| Shopify | Inventory Planner (Shopify App Store) or Cogsy | Inventory Planner connects directly to Shopify, analyzes 12+ months of sales history, calculates reorder points, and generates purchase orders |
| WooCommerce | ATUM Inventory Management (free/premium) or Inventory Planner | ATUM provides reorder point management natively in WooCommerce; Inventory Planner has a WooCommerce connector for advanced forecasting |
| BigCommerce | Inventory Planner or Linnworks | Both have BigCommerce native integrations and handle multi-location inventory forecasting |
| Multi-channel | Skubana (now Extensiv) or Linnworks | Handles inventory forecasting across Shopify, WooCommerce, Amazon, and eBay from a single dashboard |
| Custom / Headless | Build a time-series analysis layer on top of your order database | Use moving averages, seasonal decomposition, and safety stock formulas against your historical sales data |
Step 2: Set up sales history data collection
Accurate forecasting requires clean historical data. Before running any forecast:
1. Ensure cancelled and refunded orders are excluded from your sales totals — most forecasting tools handle this automatically when connected to your platform 2. Tag promotional periods — flash sales, holiday spikes, and influencer-driven demand should be flagged as abnormal; they inflate baseline demand estimates if included uncritically 3. You need at least 6 months of history for basic seasonal pattern detection; 12+ months is required to see year-over-year trends
Shopify
Using Inventory Planner: 1. Install Inventory Planner from the Shopify App Store (14-day free trial, then $99+/month) 2. Inventory Planner pulls your full Shopify sales history automatically on connection 3. Connect your suppliers in Inventory Planner → Suppliers with their lead times (e.g., Supplier A = 14 days, Supplier B = 7 days) 4. Set your desired service level (e.g., 95% — meaning you want to have stock for 95% of demand scenarios) in Settings → Forecasting 5. Inventory Planner calculates reorder points and recommended order quantities per SKU, updated daily
Shopify Analytics (built-in, no app needed for basic trends): 1. Go to Analytics → Reports → Inventory 2. The "Days of inventory remaining" report shows how many days of stock you have at current sell-through rate 3. Go to Analytics → Reports → Sales over time → group by product to see monthly sales trends 4. Use these as inputs for manual reorder decisions if you don't want to pay for a forecasting app
WooCommerce
Using ATUM Inventory Management (free tier available): 1. Install ATUM Inventory Management for WooCommerce from WordPress.org (free) or purchase the premium version 2. ATUM adds a master inventory list view with real-time stock levels, daily sales rates, and low-stock alerts 3. In ATUM → Settings → Reorder Points, configure your reorder levels and safety stock per SKU 4. ATUM's premium Purchase Orders module generates POs automatically when stock hits the reorder point
Using Inventory Planner for WooCommerce: 1. Connect Inventory Planner to WooCommerce via their native API connector 2. Same workflow as Shopify — Inventory Planner analyzes your WooCommerce sales history and generates forecasts
BigCommerce
Using Inventory Planner: 1. Connect Inventory Planner via the BigCommerce API (Inventory Planner → Settings → Connect Store) 2. Inventory Planner pulls sales history from BigCommerce and generates replenishment recommendations
BigCommerce built-in low-stock alerts: 1. Go to Products → [Product] → Inventory 2. Set "Low stock level" for each product — BigCommerce emails you when stock drops below this threshold 3. This is a simple alert, not a forecast — use it as a backstop while you set up a proper forecasting tool
Step 3: Configure reorder points and safety stock
Reorder point = demand during lead time + safety stock buffer.
In Inventory Planner: 1. Inventory Planner calculates this automatically based on your sales history and supplier lead times 2. Review the recommendations in Inventory Planner → Replenishment — items are sorted by urgency (days of stock remaining vs. lead time) 3. Adjust recommendations manually before creating purchase orders (e.g., if you know a supplier has extra lead time for a specific product) 4. Export purchase orders directly from Inventory Planner to email to your suppliers
Manual calculation (if not using a forecasting tool):
- Average daily demand = total units sold in last 30 days / 30
- Safety stock = (maximum daily demand – average daily demand) × lead time in days
- Reorder point = (average daily demand × lead time in days) + safety stock
- Example: Average daily demand = 5 units, lead time = 14 days, max daily demand = 8 units
- Safety stock = (8–5) × 14 = 42 units
- Reorder point = (5 × 14) + 42 = 112 units
Step 4: Plan for seasonal demand
Seasonality is the biggest cause of forecast errors. Plan for it explicitly.
In Inventory Planner: 1. Go to Inventory Planner → Settings → Seasonality 2. Inventory Planner detects seasonal patterns automatically from your sales history 3. For first-year merchants (no prior year data): manually set seasonal multipliers — e.g., December = 3x normal demand for holiday products
Building a seasonal calendar manually: 1. Export your sales by month for the past 2+ years from Shopify Reports / WooCommerce / BigCommerce 2. Calculate the ratio of each month's sales to the annual average (December / average month = seasonal index) 3. Apply the seasonal index to your daily demand forecast when placing orders for the upcoming peak season
Promotional calendar:
- Flag planned promotions (flash sales, influencer campaigns) in your forecasting tool
- Most forecasting tools allow manual demand overrides for specific date ranges
- For Black Friday/Cyber Monday: increase your forecast by your historical BFCM lift percentage (typically 3–8x for e-commerce)
Step 5: Generate and approve replenishment recommendations
In Inventory Planner: 1. Go to Inventory Planner → Replenishment → Review recommendations 2. Filter by "Critical" (stock-out in fewer days than lead time) and "Warning" (stock-out within 2x lead time) 3. Adjust quantities if you have market intelligence the model doesn't know (planned sales, anticipated supply issues) 4. Click "Create Purchase Order" — Inventory Planner generates a PO to send to your supplier 5. Track the PO status in Inventory Planner → Purchase Orders; when received, update actual receipt dates to improve future lead time estimates
Step 6: Custom / Headless — demand forecasting from sales data
// Compute average daily demand from your order database
async function computeAverageDailyDemand(
productId: string,
lookbackDays: number = 30
): Promise<number> {
const since = new Date();
since.setDate(since.getDate() - lookbackDays);
const result = await db.raw(`
SELECT COALESCE(SUM(ol.quantity), 0) AS total_units
FROM order_lines ol
JOIN orders o ON o.id = ol.order_id
WHERE ol.product_id = ?
AND o.status NOT IN ('cancelled', 'refunded')
AND o.created_at >= ?
`, [productId, since]);
return result.rows[0].total_units / lookbackDays;
}
// Calculate reorder point with safety stock
function calculateReorderPoint(params: {
avgDailyDemand: number;
maxDailyDemand: number; // observed peak daily demand
leadTimeDays: number;
}): number {
const safetyStock = (params.maxDailyDemand - params.avgDailyDemand) * params.leadTimeDays;
return Math.ceil(params.avgDailyDemand * params.leadTimeDays + safetyStock);
}
// Generate replenishment recommendations for all active products
async function generateReplenishmentReport(): Promise<{
productId: string;
sku: string;
currentStock: number;
reorderPoint: number;
daysOfSupply: number;
urgency: 'critical' | 'warning' | 'ok';
}[]> {
const products = await db.products.findAll({ is_active: true, track_inventory: true });
const recommendations = [];
for (const product of products) {
const inventory = await db.inventory.findByProductId(product.id);
const avgDemand = await computeAverageDailyDemand(product.id, 30);
const maxDemand = await computeAverageDailyDemand(product.id, 7); // shorter window = more volatile
const leadTimeDays = product.supplier_lead_time_days ?? 14;
const reorderPoint = calculateReorderPoint({ avgDailyDemand: avgDemand, maxDailyDemand: maxDemand, leadTimeDays });
const daysOfSupply = avgDemand > 0 ? Math.floor(inventory.quantity_on_hand / avgDemand) : 999;
const urgency = daysOfSupply < leadTimeDays ? 'critical' : daysOfSupply < leadTimeDays * 2 ? 'warning' : 'ok';
if (urgency !== 'ok') {
recommendations.push({ productId: product.id, sku: product.sku, currentStock: inventory.quantity_on_hand, reorderPoint, daysOfSupply, urgency });
}
}
return recommendations.sort((a, b) => a.daysOfSupply - b.daysOfSupply);
}Best Practices
- Start with a tool like Inventory Planner before building custom forecasting — $100–$300/month is far cheaper than engineering time, and the models are more accurate than a hand-rolled moving average
- Set a minimum of 6 months of sales history before trusting any forecast; for new products, use a category-average demand rate as a proxy
- Adjust forecasts for known events — planned promotions, seasonal campaigns, and supply chain disruptions should be entered as manual overrides in your forecasting tool
- Track forecast accuracy monthly — compare forecast units to actual units sold; if you're off by more than 25% consistently, recalibrate your assumptions or look for a structural change in demand patterns
- Account for pending purchase orders — subtract quantity on order from your recommended replenishment qty before placing a new PO; double-ordering is a common and expensive mistake
Common Pitfalls
| Problem | Solution |
|---|---|
| New products have no history to forecast from | Use category average demand rate for the first 90 days; Inventory Planner has a "new product" mode that adjusts for this |
| Forecast doesn't account for supplier stockouts | Track supplier fill rates in your vendor management system; if a supplier consistently ships 80% of ordered qty, order 25% more to compensate |
| Safety stock too low, stockouts still happen | Increase your service level setting in Inventory Planner from 90% to 95–98% for high-velocity SKUs |
| Replenishment recommendation ignores open POs | Always check "quantity on order" before creating a new PO; Inventory Planner shows this automatically, but manual calculations often miss it |
Related Skills
- @order-management-system
- @vendor-management
- @multi-channel-selling
{
"context": "Tests whether the agent correctly implements: the daily_sales materialized view with the right schema and exclusion filter, the nightly refresh strategy, the new-product fallback (< 90 days), the category-level seasonal index fallback, and MAPE monitoring with a 30% threshold.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Materialized view name",
"max_score": 5,
"description": "The SQL creates a materialized view named exactly `daily_sales`"
},
{
"name": "View columns",
"max_score": 6,
"description": "The daily_sales view produces columns: product_id, sale_date, units_sold"
},
{
"name": "Cancelled orders excluded",
"max_score": 8,
"description": "The view or aggregation query excludes orders with status 'cancelled'"
},
{
"name": "Refunded orders excluded",
"max_score": 8,
"description": "The view or aggregation query excludes orders with status 'refunded'"
},
{
"name": "Index created",
"max_score": 7,
"description": "An index named `idx_daily_sales_product_date` (or equivalent) is created on (product_id, sale_date)"
},
{
"name": "CONCURRENTLY refresh",
"max_score": 8,
"description": "The nightly refresh uses `REFRESH MATERIALIZED VIEW CONCURRENTLY daily_sales` (not a DROP+RECREATE or non-CONCURRENTLY variant)"
},
{
"name": "New product threshold 90 days",
"max_score": 10,
"description": "Products with fewer than 90 days of sales history are identified and routed to a fallback rather than the main decomposition model"
},
{
"name": "Category-average fallback",
"max_score": 10,
"description": "New products (< 90 days old) use a category-average demand rate as their demand proxy, not zero or a hard-coded value"
},
{
"name": "Category seasonal index fallback",
"max_score": 8,
"description": "When a product has insufficient history for its own seasonal index, the system falls back to a category-level seasonal index"
},
{
"name": "MAPE formula correct",
"max_score": 10,
"description": "MAPE is computed as mean of |actual - forecast| / actual * 100 (absolute percentage error averaged)"
},
{
"name": "MAPE 30% threshold",
"max_score": 10,
"description": "The system flags or alerts when MAPE exceeds 30% — this threshold is explicitly used in the code or output"
},
{
"name": "MAPE computed monthly",
"max_score": 5,
"description": "MAPE is computed on a monthly basis (per month or aggregated per calendar month), not daily or yearly"
},
{
"name": "Order lines joined correctly",
"max_score": 5,
"description": "The aggregation joins order_lines to orders (not a single table) to get product_id and created_at"
}
]
}
Demand Forecasting Data Layer and Model Health Monitoring
Problem/Feature Description
A fast-growing online marketplace is building the foundational data infrastructure for an automated demand forecasting system. Their transactional database stores orders in an orders table and line items in an order_lines table. Before any machine-learning or statistical forecasting can run, the engineering team needs a reliable daily aggregation layer that feeds those models — one that can handle the real-world messiness of order cancellations and refunds without inflating demand numbers.
The marketplace also faces two operational challenges. First, they frequently launch new products that have very little sales history — the forecasting model can't decompose a product's demand if it was listed only two weeks ago, yet the system still needs to make some inventory recommendation for it. Second, the forecasting team has seen accuracy drift over time on some products and wants a monthly health check that automatically flags any SKU whose model is performing poorly, so that planners can manually intervene rather than blindly trust bad predictions.
Output Specification
Produce the following files:
1. schema.sql — PostgreSQL DDL that sets up the data aggregation layer, including the materialized view and any supporting objects 2. refresh_job.sh — A shell script (or cron-compatible command) that performs the nightly refresh of the aggregation layer 3. forecast_service.ts — A TypeScript module that implements:
- A function to determine whether a product has enough history to run the standard decomposition model, and what fallback to use when it doesn't
- A function to compute monthly forecast accuracy for a given product using provided actual vs. forecast data, and return a health status based on whether the model needs recalibration
4. accuracy_report.json — Run the MAPE computation against the sample data below and write the results
Input Files
The following files are provided as inputs. Extract them before beginning.
=============== FILE: inputs/category_stats.json =============== { "categories": [ { "category_id": "CAT-ELECTRONICS", "avg_daily_demand": 8.4, "seasonal_indices": [0.82, 0.91, 0.95, 1.02, 1.08, 1.31, 1.24] }, { "category_id": "CAT-APPAREL", "avg_daily_demand": 22.1, "seasonal_indices": [0.78, 0.88, 0.93, 1.05, 1.12, 1.38, 1.19] }, { "category_id": "CAT-HOME", "avg_daily_demand": 5.2, "seasonal_indices": [0.85, 0.90, 0.97, 1.01, 1.06, 1.25, 1.15] } ] }
=============== FILE: inputs/products_meta.json =============== [ { "id": "SKU-NEW-001", "name": "Smart Watch Band", "category_id": "CAT-ELECTRONICS", "first_sale_date": "2026-02-15" }, { "id": "SKU-NEW-002", "name": "Running Shorts", "category_id": "CAT-APPAREL", "first_sale_date": "2026-01-10" }, { "id": "SKU-EST-003", "name": "Coffee Table", "category_id": "CAT-HOME", "first_sale_date": "2024-06-01" } ]
=============== FILE: inputs/forecast_actuals.json =============== { "month": "2026-02", "products": [ { "productId": "SKU-EST-003", "daily_records": [ {"date": "2026-02-01", "actual": 5, "forecast": 4.8}, {"date": "2026-02-02", "actual": 3, "forecast": 4.2}, {"date": "2026-02-03", "actual": 8, "forecast": 6.1}, {"date": "2026-02-04", "actual": 7, "forecast": 7.3}, {"date": "2026-02-05", "actual": 6, "forecast": 5.9}, {"date": "2026-02-06", "actual": 4, "forecast": 4.5}, {"date": "2026-02-07", "actual": 9, "forecast": 7.8}, {"date": "2026-02-08", "actual": 5, "forecast": 5.1}, {"date": "2026-02-09", "actual": 3, "forecast": 4.0}, {"date": "2026-02-10", "actual": 11, "forecast": 7.2}, {"date": "2026-02-11", "actual": 7, "forecast": 7.5}, {"date": "2026-02-12", "actual": 6, "forecast": 5.8}, {"date": "2026-02-13", "actual": 4, "forecast": 4.6}, {"date": "2026-02-14", "actual": 15, "forecast": 8.0} ] } ] }
{
"context": "Tests whether the agent implements the correct demand decomposition algorithm: 7-day moving average smoothing, linear regression on MA values for trend, day-of-week seasonal indices normalized to overall mean, residual std dev, and a forward forecast combining baseline + trend + seasonality.",
"type": "weighted_checklist",
"checklist": [
{
"name": "7-day moving average",
"max_score": 10,
"description": "Smoothing uses a window of exactly 7 days (not 5, not 14); the window shrinks at the start of the series rather than being skipped"
},
{
"name": "Trend via linear regression on MA",
"max_score": 12,
"description": "Trend (slope) is derived from linear regression applied to the 7-day moving average values — NOT raw sales values"
},
{
"name": "Day-of-week seasonal indices",
"max_score": 12,
"description": "Seasonality is computed as 7 day-of-week indices (not weekly buckets or monthly) normalized so that the overall mean equals 1.0"
},
{
"name": "Residual std dev computed",
"max_score": 8,
"description": "residualStdDev is computed as sqrt(mean squared residuals) where residuals = actual - (trend * seasonal_index)"
},
{
"name": "364-day history window",
"max_score": 8,
"description": "Historical data for decomposition uses exactly 364 days (52 weeks) of history, with missing days filled as zero demand"
},
{
"name": "26-week history guard",
"max_score": 8,
"description": "System detects or documents when fewer than 26 weeks of data exist and refuses / warns rather than producing unreliable seasonal indices"
},
{
"name": "Forecast clamps to zero",
"max_score": 8,
"description": "Forecasted daily demand is clamped to a minimum of 0 (Math.max(0, ...)) — negative predictions are not emitted"
},
{
"name": "Forecast rounding",
"max_score": 6,
"description": "Each forecasted value is rounded to 1 decimal place (e.g. Math.round(x * 10) / 10), not left as a raw float"
},
{
"name": "30-day baseline average",
"max_score": 8,
"description": "The baseline used in the forecast is the average daily demand over the most recent 30 days, not a longer or shorter window"
},
{
"name": "Forecast combines all three components",
"max_score": 10,
"description": "Each forecasted day = (baseline + trend * d) * seasonal_index[dow], i.e. baseline + trend adjustment multiplied by seasonality"
},
{
"name": "Output covers 30 days",
"max_score": 5,
"description": "The forecast output array covers 30 future days (default forecastDays = 30)"
},
{
"name": "Default forecast window",
"max_score": 5,
"description": "The forecast function defaults to 30 days when no forecastDays argument is provided"
}
]
}
Sales Demand Decomposition and Forecasting Module
Problem/Feature Description
A regional wholesale distributor is modernizing its planning tools. Their analysts currently produce demand forecasts in spreadsheets by eyeballing charts, which is error-prone and doesn't scale to their 400-SKU catalog. The data engineering team has built a data warehouse with a clean daily sales fact table, and they now want a programmatic forecasting library that a planning application can call to get a 30-day demand outlook for any individual product.
The analysts' key insight is that most products show predictable cyclical shopping patterns and a slow upward or downward trend over time. Capturing these two signals separately and measuring the remaining noise will help the team understand forecast confidence. The library should work purely from historical sales data (no external data sources needed) and should produce consistent, reproducible forecasts given the same input.
Output Specification
Write a self-contained TypeScript module (forecast.ts) that exports:
- A
computeMovingAveragefunction - A
decomposeProductDemandfunction that returns trend, seasonality indices, and residual noise - A
forecastDemandfunction that returns a 30-day demand array
The module should work with the sample data provided below (loaded from a JSON file rather than a database). Write a runner.ts script that imports the module, runs decomposition and forecasting for the product in the sample data, and writes the results to forecast_output.json.
The output file should contain: the trend slope, the seasonal index values, the residualStdDev, and the 30-day forecast array.
Input Files
The following files are provided as inputs. Extract them before beginning.
=============== FILE: inputs/sales_history.json =============== { "productId": "PROD-42", "records": [ {"sale_date": "2025-03-14", "units_sold": 12}, {"sale_date": "2025-03-15", "units_sold": 8}, {"sale_date": "2025-03-16", "units_sold": 5}, {"sale_date": "2025-03-17", "units_sold": 9}, {"sale_date": "2025-03-18", "units_sold": 14}, {"sale_date": "2025-03-19", "units_sold": 18}, {"sale_date": "2025-03-20", "units_sold": 20}, {"sale_date": "2025-03-21", "units_sold": 11}, {"sale_date": "2025-03-22", "units_sold": 7}, {"sale_date": "2025-03-23", "units_sold": 6}, {"sale_date": "2025-03-24", "units_sold": 10}, {"sale_date": "2025-03-25", "units_sold": 15}, {"sale_date": "2025-03-26", "units_sold": 19}, {"sale_date": "2025-03-27", "units_sold": 21}, {"sale_date": "2025-03-28", "units_sold": 13}, {"sale_date": "2025-03-29", "units_sold": 9}, {"sale_date": "2025-03-30", "units_sold": 8}, {"sale_date": "2025-03-31", "units_sold": 11}, {"sale_date": "2025-04-01", "units_sold": 16}, {"sale_date": "2025-04-02", "units_sold": 22}, {"sale_date": "2025-04-03", "units_sold": 23}, {"sale_date": "2025-04-04", "units_sold": 14}, {"sale_date": "2025-04-05", "units_sold": 10}, {"sale_date": "2025-04-06", "units_sold": 9}, {"sale_date": "2025-04-07", "units_sold": 12}, {"sale_date": "2025-04-08", "units_sold": 17}, {"sale_date": "2025-04-09", "units_sold": 20}, {"sale_date": "2025-04-10", "units_sold": 24}, {"sale_date": "2025-04-11", "units_sold": 15}, {"sale_date": "2025-04-12", "units_sold": 10}, {"sale_date": "2025-04-13", "units_sold": 8}, {"sale_date": "2025-04-14", "units_sold": 13}, {"sale_date": "2025-04-15", "units_sold": 18}, {"sale_date": "2025-04-16", "units_sold": 21}, {"sale_date": "2025-04-17", "units_sold": 25}, {"sale_date": "2025-04-18", "units_sold": 16}, {"sale_date": "2025-04-19", "units_sold": 11}, {"sale_date": "2025-04-20", "units_sold": 9}, {"sale_date": "2025-04-21", "units_sold": 14}, {"sale_date": "2025-04-22", "units_sold": 19}, {"sale_date": "2025-04-23", "units_sold": 23}, {"sale_date": "2025-04-24", "units_sold": 26}, {"sale_date": "2025-04-25", "units_sold": 17}, {"sale_date": "2025-04-26", "units_sold": 12}, {"sale_date": "2025-04-27", "units_sold": 10}, {"sale_date": "2025-04-28", "units_sold": 15}, {"sale_date": "2025-04-29", "units_sold": 20}, {"sale_date": "2025-04-30", "units_sold": 24}, {"sale_date": "2025-05-01", "units_sold": 27}, {"sale_date": "2025-05-02", "units_sold": 18}, {"sale_date": "2025-05-03", "units_sold": 13}, {"sale_date": "2025-05-04", "units_sold": 11}, {"sale_date": "2025-05-05", "units_sold": 16}, {"sale_date": "2025-05-06", "units_sold": 21}, {"sale_date": "2025-05-07", "units_sold": 25}, {"sale_date": "2025-05-08", "units_sold": 28}, {"sale_date": "2025-05-09", "units_sold": 19} ], "baseline_30day_avg": 14.2 }
{
"context": "Tests whether the agent correctly implements reorder point, safety stock, order quantity, and urgency classification logic according to the demand forecasting skill. The scenario provides raw daily sales and inventory data and asks for replenishment recommendations.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Z-score 1.645 used",
"max_score": 8,
"description": "Uses Z = 1.645 (95% service level) as the default z-score for safety stock calculation"
},
{
"name": "Safety stock formula",
"max_score": 10,
"description": "Safety stock is computed as Z * residualStdDev * sqrt(leadTimeDays), not a simpler heuristic"
},
{
"name": "Reorder point formula",
"max_score": 10,
"description": "Reorder point = avgDailyDemand * leadTimeDays + safetyStock (ceiling applied)"
},
{
"name": "Default lead time 7 days",
"max_score": 6,
"description": "When a product has no specified lead time, defaults to 7 days"
},
{
"name": "Order qty formula",
"max_score": 10,
"description": "Recommended order quantity = max(ceil(avgDailyDemand * 30), min_order_quantity)"
},
{
"name": "Urgency critical threshold",
"max_score": 8,
"description": "Urgency is 'critical' when daysOfSupply < leadTimeDays"
},
{
"name": "Urgency warning threshold",
"max_score": 8,
"description": "Urgency is 'warning' when daysOfSupply < leadTimeDays * 2 (and not critical)"
},
{
"name": "daysOfSupply formula",
"max_score": 8,
"description": "daysOfSupply = floor(quantity_on_hand / avgDailyDemand), returns 999 (or equivalent large number) when avgDailyDemand is 0"
},
{
"name": "Pending PO subtraction",
"max_score": 10,
"description": "quantity_on_order (open purchase orders) is subtracted from the recommended order quantity before output"
},
{
"name": "High-velocity Z adjustment",
"max_score": 8,
"description": "Uses Z = 2.05 (98% service level) for products flagged as high-velocity/high-margin"
},
{
"name": "Slow-mover Z adjustment",
"max_score": 8,
"description": "Uses Z = 1.28 (90% service level) for products flagged as slow movers"
},
{
"name": "Report filters ok urgency",
"max_score": 6,
"description": "The output report / recommendations list does NOT include products with 'ok' urgency — only 'critical' and 'warning' items are present"
}
]
}
Inventory Replenishment Recommendations
Problem/Feature Description
A mid-size e-commerce retailer carries about 200 active SKUs across several product categories. The purchasing team currently relies on gut instinct to decide when and how much to reorder, which has led to a string of costly stockouts during peak periods and excessive overstock on slower-moving items. Management wants an automated replenishment recommendation engine that ingests a product catalog with inventory levels and recent demand data, then outputs a prioritized list of items that need to be ordered — along with exactly how much to order.
The company works with suppliers that have varying lead times, and some products have minimum order quantities imposed by the supplier. There are also several open purchase orders already in flight that must be accounted for so the system doesn't double-order. The purchasing team should only see items that actually need attention; anything with comfortable stock levels should not appear in the report.
Output Specification
Write a self-contained TypeScript script (replenishment.ts) that:
1. Reads the product and inventory data provided below 2. For each product, computes the recommended replenishment action 3. Writes a JSON file replenishment_report.json containing only the products that need action, sorted so the most urgent items appear first
The report should include for each item: productId, currentStock, reorderPoint, recommendedOrderQty, daysOfSupply, and urgency.
Also write a brief notes.md (3-5 bullet points) explaining the key formulas and thresholds your implementation uses.
Input Files
The following files are provided as inputs. Extract them before beginning.
=============== FILE: inputs/products.json =============== [ { "id": "SKU-001", "name": "Wireless Headphones", "supplier_lead_time_days": 10, "min_order_quantity": 50, "velocity_tier": "high" }, { "id": "SKU-002", "name": "Phone Case 6-inch", "supplier_lead_time_days": 5, "min_order_quantity": 100, "velocity_tier": "high" }, { "id": "SKU-003", "name": "Laptop Stand", "supplier_lead_time_days": 14, "min_order_quantity": 20, "velocity_tier": "low" }, { "id": "SKU-004", "name": "USB-C Cable 2m", "supplier_lead_time_days": null, "min_order_quantity": 200, "velocity_tier": "high" }, { "id": "SKU-005", "name": "Desk Lamp", "supplier_lead_time_days": 7, "min_order_quantity": 10, "velocity_tier": "low" } ]
=============== FILE: inputs/inventory.json =============== [ { "productId": "SKU-001", "quantity_on_hand": 45, "quantity_on_order": 0 }, { "productId": "SKU-002", "quantity_on_hand": 310, "quantity_on_order": 200 }, { "productId": "SKU-003", "quantity_on_hand": 8, "quantity_on_order": 0 }, { "productId": "SKU-004", "quantity_on_hand": 180, "quantity_on_order": 0 }, { "productId": "SKU-005", "quantity_on_hand": 95, "quantity_on_order": 50 } ]
=============== FILE: inputs/demand_stats.json =============== [ { "productId": "SKU-001", "avg_daily_demand": 4.8, "residual_std_dev": 2.1 }, { "productId": "SKU-002", "avg_daily_demand": 18.5, "residual_std_dev": 5.3 }, { "productId": "SKU-003", "avg_daily_demand": 0.6, "residual_std_dev": 0.4 }, { "productId": "SKU-004", "avg_daily_demand": 12.2, "residual_std_dev": 3.8 }, { "productId": "SKU-005", "avg_daily_demand": 0.0, "residual_std_dev": 0.1 } ]
{
"name": "finsi/demand-forecasting",
"version": "0.1.0",
"summary": "Inventory demand prediction using sales history, seasonality, and trends",
"skills": {
"demand-forecasting": {
"path": "SKILL.md"
}
}
}