Northwind ERP Fulfillment Solver Skill
Executable experience for solving Northwind ERP fulfillment evaluation tasks against the shared remote API. Covers expedite-queue dispatch, BOM replenishment, supplier incident scorecards, allocation/transfer, and quality-hold procurement review.
API Access
Base URL: <remote-env-url> (remote, HTTP/1.0, closes each connection).
curl -sS --max-time 30 '<url>'
Endpoints (all GET unless noted)
/health — manifest with record counts and seed.
/products and /products/<sku> — fields: sku, name, category, active, safety_stock, overstock_threshold, supplier_id, unit_cost, weight_lb.
/customers and /customers/<customer_id> — fields: customer_id, name, tier, margin_band, account_status (active|review_required|blocked), risk_flag (none|credit_watch|fraud_watch).
/warehouses — warehouse_id (WH_NORTH, WH_CENTRAL, WH_WEST), name, zip, region.
/inventory — filter by ?warehouse_id=&sku=. Fields: on_hand, reserved, quarantined, last_count_date. No record means 0 stock.
/purchase_orders — filter by ?supplier_id=&sku=&status=. Fields: po_id, sku, quantity, eta, status (open|confirmed|received|cancelled), supplier_id, warehouse_id.
/orders — filter by ?wave=&required_date=&customer_id=. Each order has: order_id, customer_id, warehouse_id, destination_zip, shipping_speed (ground|two_day|overnight), priority, required_date, wave, lines[]. Each line: line_id, sku, quantity, unit_price.
/orders/<order_id> — single order.
/shipping/quote?warehouse_id=&destination_zip=&weight_lb=&speed= — returns zone_distance, service_days, total_cost, base_rate, carrier, fuel_surcharge_rate.
/incidents — filter by ?start=&end=&supplier_id=&sku=&incident_type=&status=. Fields: incident_id, supplier_id, sku, warehouse_id, incident_type (RMA|WORK_ORDER), severity (low|medium|high|critical), status (open|closed), open_date, close_date (null if open), resolution_cost, root_cause.
/suppliers — fields: supplier_id, name, region, quality_status (approved|watch|quality_hold).
/boms and /boms/<bom_id> — fields: bom_id, name, warehouse_id, target_date, components[] (sku, quantity_per_kit).
Record counts (seed 7007)
54 products, 40 customers, 3 warehouses, 162 inventory records, 92 POs, 88 orders, 212 incidents, 12 suppliers, 9 BOMs.
Core Business Rules
Effective Available Stock
effective_available = on_hand - reserved - quarantined - safety_stock
This single formula drives inventory_status, shortage/low-stock classification, transfer feasibility, and BOM replenishment gaps. safety_stock comes from the product master, not the inventory record.
Inventory Status Classification (per order)
For each order line at the order's warehouse:
- If product is inactive (active=false): line goes to
inactive_skus list; the SKU is NOT checked for inventory.
- Compute effective_available for active SKUs.
- shortage_skus: SKUs where effective_available < 0 (no stock above safety stock at all). These trigger backorder.
- low_stock_skus: SKUs where 0 ≤ effective_available < line quantity. Some stock exists but not enough to fill the line. These trigger delayed_release if no shortage exists.
- If effective_available ≥ quantity: line is fully available.
Order-level inventory_status (precedence):
inactive_and_shortage — has inactive SKU(s) AND has shortage SKU(s) among active lines
inactive_sku — has inactive SKU(s) but no shortage among active lines
shortage — has shortage SKU(s), no inactive SKUs
low_stock — has low_stock SKU(s), no shortage, no inactive
ready — all lines fully available
Important: low_stock_skus and shortage_skus should BOTH be populated for orders that have both types (they are independent lists, not mutually exclusive at the order level). Each individual SKU goes into exactly one list based on its effective_available and quantity.
Customer Exception Mapping
Check account_status first, then risk_flag:
- account_status =
blocked → customer_exception = account_blocked
- risk_flag =
fraud_watch → customer_exception = fraud_watch
- risk_flag =
credit_watch → customer_exception = credit_watch
- account_status =
review_required → customer_exception = review_required
- otherwise →
none
When account_status is blocked AND risk_flag is credit_watch, use account_blocked (account status takes precedence).
Decision Precedence (expedite queue / allocation)
- account_blocked / fraud_watch / credit_watch → final_decision =
reject_hold, next_action = hold_credit_or_fraud
- review_required → final_decision =
manual_review, next_action = send_account_review
- inactive product → final_decision =
manual_review, next_action = escalate_product_master
- shortage → final_decision =
backorder, next_action = create_backorder
- low_stock → final_decision =
delayed_release, next_action = delay_and_monitor
- ready → final_decision =
ship_now, next_action = release_to_pick
Account status (including review_required) takes precedence over inactive product. Inactive product takes precedence over inventory issues. (Confirmed: review_required → send_account_review is preferred over inactive → escalate_product_master when both apply.)
Shipping Quote
- Total weight = Σ (product.weight_lb × line.quantity) for ALL lines including inactive SKUs.
- Query:
/shipping/quote?warehouse_id=<order_warehouse>&destination_zip=<order_dest>&weight_lb=<total>&speed=<order_shipping_speed>
- Extract: zone_distance (integer), service_days (integer), total_cost_usd = round(total_cost, 2).
- Always compute the shipping quote regardless of the fulfillment decision.
Rounding
- All currency values rounded to 2 decimal places.
- Percentages rounded to 1 decimal place.
- Durations rounded to 2 decimal places.
Task-Specific SOPs
Task 1: Expedite Queue (Dispatch Control)
Input: Wave ID + list of order IDs from a memo.
Output: wave_id, records[], summary.
For each order (sorted by order_id):
- Fetch order, customer, product, inventory data.
- Classify each line: inactive_sku / shortage (eff < 0) / low_stock (0 ≤ eff < qty) / available.
- Determine inventory_status (precedence above).
- Determine customer_exception (mapping above).
- Determine final_decision and next_action (precedence above).
- Compute shipping_quote.
- Populate shortage_skus, inactive_skus, low_stock_skus (each sorted ascending by SKU).
Summary:
- order_count: total orders
- decision_counts: {ship_now, delayed_release, manual_review, backorder, reject_hold} with integer counts
- total_shipping_cost_usd: sum of all shipping_quote.total_cost_usd, rounded to 2 decimals
- blocked_order_ids: orders with reject_hold decision (sorted ascending)
- manual_review_order_ids: orders with manual_review decision (sorted ascending)
- backorder_order_ids: orders with backorder decision (sorted ascending)
- inactive_sku_order_ids: orders with any inactive SKU (sorted ascending)
Task 2: BOM Replenishment
Input: Production memo with BOM IDs, build quantities, build dates, target warehouse.
Output: task_id, plan_date, kit_targets, component_plan, transfer_requests, purchase_requisitions, excluded_components, summary.
Steps:
- For each BOM, compute component demand: total_required = Σ (quantity_per_kit × build_quantity) across all BOMs using that SKU.
- For shared SKUs (used in multiple BOMs), use the earliest build_date as needed_by.
- Compute effective_available at the target warehouse for each component.
- Find timely POs: status in (open, confirmed), warehouse_id = target, eta ≤ needed_by date. Sum their quantities as timely_po_qty.
- Determine exclusion and action (precedence):
- target_overstock: effective_available > overstock_threshold → action =
overstock_excluded, excluded. (This takes precedence even if stock covers the requirement.)
- stocked_no_gap: effective_available ≥ total_required → action =
no_action_stocked, excluded.
- timely_po_covers_gap: effective_available + timely_po_qty ≥ total_required → action =
timely_po_covered, excluded.
- Otherwise: gap = total_required - effective_available - timely_po_qty > 0 → try transfer then purchase.
- For components with a gap:
- Compute spare at each other warehouse: spare = max(0, effective_available at that warehouse).
- Transfer from the warehouse with most spare first (to minimize number of transfers).
- Transfer qty = min(gap, spare). Subtract from gap.
- If gap remains after transfers: purchase_requisition_qty = remaining gap.
- If only transfer covers: final_action =
transfer_only.
- If purchase needed: final_action =
purchase_required.
- Purchase requisition: supplier_id from product, unit_cost from product, extended_cost = unit_cost × qty, needed_by = earliest build date.
- Transfer request fields: sku, from_warehouse_id, to_warehouse_id, quantity, needed_by.
- Sort: sku ascending, then quantity descending, then from_warehouse_id ascending.
- Summary:
- component_count: total components
- total_purchase_units: sum of purchase requisition quantities
- total_purchase_cost: sum of extended costs (2 decimals)
- total_transfer_units: sum of transfer quantities
- timely_po_covered_units: total PO units from timely POs for excluded components (sum of timely_po_qty for components with exclusion_reason = timely_po_covers_gap)
Common misjudgments:
target_overstock takes precedence over stocked_no_gap when effective > overstock_threshold.
- Use the warehouse with the MOST spare for transfers (not alphabetical order).
- timely_po_covered_units = total PO quantity (335), NOT the gap covered (106).
Task 3: Supplier Incident Scorecard
Input: Date filter on open_date, analysis_date, recommendation policy.
Output: analysis_window, summary, supplier_scorecard, top_escalation_suppliers, highest_cost_supplier_id, highest_share_supplier_id.
Steps:
- Filter incidents: open_date between start_date and end_date (inclusive).
- Group by supplier_id.
- For each supplier with filtered incidents:
- incident_count = count
- incident_percentage = round(incident_count / total_filtered_count × 100, 1)
- total_resolution_cost = sum of resolution_cost (2 decimals)
- avg_duration_days: closed incidents = (close_date - open_date).days; open incidents = (analysis_date - open_date).days; average rounded to 2 decimals
- rma_count = count where incident_type = RMA
- work_order_count = count where incident_type = WORK_ORDER
- open_incident_count = count where status = open
- severe_incident_count = count where severity in (high, critical)
- Recommendation code (precedence: ESCALATE_SUPPLIER > PROCESS_REVIEW > WATCHLIST > MONITOR):
- ESCALATE_SUPPLIER: (quality_hold AND incident_count ≥ 3) OR any critical RMA OR (rma_count ≥ 3 AND total_resolution_cost ≥ 15000.00)
- PROCESS_REVIEW: work_order_count ≥ 3 AND work_order_count > rma_count
- WATCHLIST: quality_status in (watch, quality_hold) OR incident_count ≥ 4 OR total_resolution_cost ≥ 12000.00 OR severe_incident_count ≥ 2
- MONITOR: none of the above
- Scorecard sorted by supplier_id ascending.
- top_escalation_suppliers: ESCALATE_SUPPLIER suppliers, sorted by incident_count desc, then total_resolution_cost desc, then supplier_id asc.
- highest_cost_supplier_id: supplier with max total_resolution_cost (ties broken by supplier_id ascending).
- highest_share_supplier_id: supplier with max incident_percentage (ties broken by supplier_id ascending).
- Summary: filtered_incident_count, supplier_count, total_resolution_cost (2 decimals), overall_rma_count, overall_work_order_count.
Task 4: Allocation / Transfer Desk
Input: Wave ID with mixed-warehouse orders.
Output: wave_id, line_actions, transfer_requests, blocked_orders, order_rollup, summary.
For each line (sorted by order_id, then line_id):
- Check account/risk status:
- blocked → manual_review, reason = account_blocked
- fraud_watch → manual_review, reason = fraud_watch
- credit_watch → manual_review, reason = account_review_required
- review_required → manual_review, reason = account_review_required
- If any of these: ship_quantity=0, transfer_from=null, transfer_quantity=0, backorder_quantity=0
- Check product active status:
- If inactive → manual_review, reason = inactive_product
- Check inventory:
- If eff ≥ qty → ship (ship_quantity=qty, no transfer/backorder)
- If eff < qty:
- ship_quantity = max(0, eff) (usable stock at requested warehouse)
- uncovered = qty - ship_quantity
- Check other warehouses for spare (max(0, eff at each))
- If any warehouse has spare ≥ uncovered → transfer from the one with most spare
- transfer_from = that warehouse, transfer_quantity = uncovered
- Else → backorder (backorder_quantity = uncovered)
- reason = insufficient_effective_stock for transfer and backorder
Line action fields: order_id, line_id, sku, requested_warehouse, requested_effective_available, action (ship|transfer|backorder|manual_review), ship_quantity, transfer_from (enum or null), transfer_quantity, backorder_quantity, primary_reason.
transfer_requests: order_id, line_id, sku, from_warehouse, to_warehouse, quantity. Sort by order_id, then line_id ascending.
blocked_orders: orders where ALL lines are manual_review due to account/risk (NOT inactive_product). Sort ascending. This includes orders with account_blocked, fraud_watch, credit_watch, or review_required as the reason.
order_rollup (per order):
manual_review — ALL lines are manual_review
ready_to_ship — ALL lines are ship
needs_transfer — lines are only ship and/or transfer (no backorder, no manual_review)
has_backorder — lines are only ship and/or backorder (no transfer, no manual_review)
mixed_actions — any other combination (including manual_review + backorder, or transfer + manual_review)
Critical: manual_review rollup only applies when ALL lines are manual_review. If an order has manual_review on some lines and other actions on others, the rollup is mixed_actions.
Summary: total_orders, total_lines, ship_lines, transfer_lines, backorder_lines, manual_review_lines, blocked_orders (count), transfer_units, backorder_units.
Task 5: Quality Hold / Procurement Review
Input: Target supplier IDs, analysis window (start/end dates on open_date).
Output: analysis_window, supplier_decisions, held_po_ids, release_supplier_ids, summary.
For each target supplier (sorted by supplier_id):
- Filter incidents by open_date in analysis window and supplier_id.
- Compute: recent_incident_count, recent_rma_count, severe_or_critical_count (high|critical), open_incident_count, affected_skus (sorted unique), sample_incident_ids (sorted, max 5).
- Get all open/confirmed POs for the supplier (all warehouses, all SKUs).
- Decision logic:
- quality_hold →
freeze_new_replenishment
- watch + severe_or_critical_count ≥ 2 →
buyer_review_required
- watch + severe_or_critical_count < 2 →
monitor_only
- approved →
monitor_only
- held_po_ids per supplier: all open/confirmed PO IDs for freeze and buyer_review decisions; empty list for monitor_only.
- release_supplier_ids: suppliers with monitor_only decision (sorted).
Summary: suppliers_reviewed, freeze_count, buyer_review_count, monitor_count, held_po_count, total_recent_incidents.
Common misjudgments:
- A watch supplier with only 1 severe incident should be
monitor_only, not buyer_review_required. The threshold is severe ≥ 2.
- held_po_ids should be populated for both freeze and buyer_review decisions (pending review).
- sample_incident_ids: sorted ascending, maximum 5 entries.
Common Misjudgments and Pitfalls
- Effective stock formula: Always subtract safety_stock from the product master. The inventory record does not contain safety_stock.
- Account status precedence: account_status=blocked produces "account_blocked" exception even when risk_flag=credit_watch is also set. Do not use risk_flag to override account_status for the exception value.
- review_required vs inactive: When both apply (customer under review AND product inactive), the next_action is
send_account_review (account takes precedence), NOT escalate_product_master.
- target_overstock precedence: If effective_available > overstock_threshold, classify as
target_overstock / overstock_excluded EVEN IF the stock covers the requirement. This takes precedence over stocked_no_gap.
- Transfer warehouse selection: Use the warehouse with the MOST spare effective stock first. Do not use alphabetical order. This minimizes the number of transfer requests.
- timely_po_covered_units: This is the total PO quantity (all eligible PO units), NOT the gap that was covered.
- Order rollup manual_review: Only apply
manual_review rollup when ALL lines in the order are manual_review. Mixed manual_review + other actions → mixed_actions.
- blocked_orders: Includes orders stopped at account or risk level (blocked, fraud_watch, credit_watch, review_required). Does NOT include orders with only inactive_product lines.
- low_stock_skus not cleared: When an order has shortage lines, low_stock_skus should still be populated for lines that are in the low_stock range (0 ≤ eff < qty). Do not clear low_stock_skus for shortage orders.
- Shipping weight: Include ALL line items in weight calculation, including inactive SKUs. The shipping quote is always computed regardless of the fulfillment decision.
- Watch supplier threshold: A watch supplier needs severe_or_critical_count ≥ 2 for buyer_review_required. One severe incident is not enough; it stays monitor_only.
- Sample incident cap: sample_incident_ids is capped at 5 entries, sorted ascending.
Reusable SOP Template
1. Identify the task type (expedite / BOM / scorecard / allocation / quality-hold).
2. Fetch all required data from the API (products, customers, inventory, orders, POs, incidents, suppliers, BOMs).
3. Build lookup maps: products by SKU, inventory by (warehouse_id, sku), customers by customer_id, suppliers by supplier_id.
4. For each entity (order/line/component/supplier):
a. Compute effective_available = on_hand - reserved - quarantined - safety_stock.
b. Apply classification rules (inventory_status, customer_exception, etc.).
c. Apply decision precedence (account → product → inventory).
d. Compute derived values (shipping quotes, transfer quantities, purchase costs).
5. Sort all lists by their specified ordering keys.
6. Round all currency to 2 decimals, percentages to 1 decimal, durations to 2 decimals.
7. Build summary from individual records.
8. Validate JSON structure against the answer template.
Output Field Shapes Quick Reference
Expedite Queue Record
{
"order_id": "string",
"inventory_status": "ready|low_stock|shortage|inactive_sku|inactive_and_shortage",
"customer_exception": "none|review_required|account_blocked|fraud_watch|credit_watch",
"final_decision": "ship_now|delayed_release|manual_review|backorder|reject_hold",
"next_action": "release_to_pick|delay_and_monitor|send_account_review|create_backorder|hold_credit_or_fraud|escalate_product_master",
"shortage_skus": ["sorted SKU strings"],
"inactive_skus": ["sorted SKU strings"],
"low_stock_skus": ["sorted SKU strings"],
"shipping_quote": {"zone_distance": int, "service_days": int, "total_cost_usd": float}
}
BOM Component Plan
{
"sku": "string",
"total_required": int,
"target_effective_available": int,
"timely_po_qty": int,
"transfer_qty": int,
"purchase_requisition_qty": int,
"final_action": "no_action_stocked|transfer_only|purchase_required|timely_po_covered|overstock_excluded",
"coverage_po_ids": ["sorted strings"],
"exclusion_reason": "none|target_overstock|timely_po_covers_gap|stocked_no_gap"
}
Supplier Scorecard Row
{
"supplier_id": "string", "supplier_name": "string",
"incident_count": int, "incident_percentage": float (1 decimal),
"total_resolution_cost": float (2 decimals), "avg_duration_days": float (2 decimals),
"rma_count": int, "work_order_count": int,
"open_incident_count": int, "severe_incident_count": int,
"recommendation_code": "ESCALATE_SUPPLIER|PROCESS_REVIEW|WATCHLIST|MONITOR"
}
Allocation Line Action
{
"order_id": "string", "line_id": int, "sku": "string",
"requested_warehouse": "WH_NORTH|WH_CENTRAL|WH_WEST",
"requested_effective_available": int,
"action": "ship|transfer|backorder|manual_review",
"ship_quantity": int, "transfer_from": "string|null",
"transfer_quantity": int, "backorder_quantity": int,
"primary_reason": "none|account_blocked|account_review_required|fraud_watch|inactive_product|insufficient_effective_stock"
}
Quality Hold Supplier Decision
{
"supplier_id": "string", "supplier_name": "string", "quality_status": "approved|watch|quality_hold",
"recent_incident_count": int, "recent_rma_count": int,
"severe_or_critical_count": int, "open_incident_count": int,
"affected_skus": ["sorted strings"], "sample_incident_ids": ["sorted strings, max 5"],
"decision": "freeze_new_replenishment|buyer_review_required|monitor_only",
"held_po_ids": ["sorted strings"]
}
1---2name: reflect-3-attempt-01-143description: Northwind ERP Fulfillment Solver Skill4---5# Northwind ERP Fulfillment Solver Skill67Executable experience for solving Northwind ERP fulfillment evaluation tasks against the shared remote API. Covers expedite-queue dispatch, BOM replenishment, supplier incident scorecards, allocation/transfer, and quality-hold procurement review.89## API Access1011Base URL: `<remote-env-url>` (remote, HTTP/1.0, closes each connection).1213```14curl -sS --max-time 30 '<url>'15```1617### Endpoints (all GET unless noted)18- `/health` — manifest with record counts and seed.19- `/products` and `/products/<sku>` — fields: sku, name, category, active, safety_stock, overstock_threshold, supplier_id, unit_cost, weight_lb.20- `/customers` and `/customers/<customer_id>` — fields: customer_id, name, tier, margin_band, account_status (active|review_required|blocked), risk_flag (none|credit_watch|fraud_watch).21- `/warehouses` — warehouse_id (WH_NORTH, WH_CENTRAL, WH_WEST), name, zip, region.22- `/inventory` — filter by ?warehouse_id=&sku=. Fields: on_hand, reserved, quarantined, last_count_date. No record means 0 stock.23- `/purchase_orders` — filter by ?supplier_id=&sku=&status=. Fields: po_id, sku, quantity, eta, status (open|confirmed|received|cancelled), supplier_id, warehouse_id.24- `/orders` — filter by ?wave=&required_date=&customer_id=. Each order has: order_id, customer_id, warehouse_id, destination_zip, shipping_speed (ground|two_day|overnight), priority, required_date, wave, lines[]. Each line: line_id, sku, quantity, unit_price.25- `/orders/<order_id>` — single order.26- `/shipping/quote?warehouse_id=&destination_zip=&weight_lb=&speed=` — returns zone_distance, service_days, total_cost, base_rate, carrier, fuel_surcharge_rate.27- `/incidents` — filter by ?start=&end=&supplier_id=&sku=&incident_type=&status=. Fields: incident_id, supplier_id, sku, warehouse_id, incident_type (RMA|WORK_ORDER), severity (low|medium|high|critical), status (open|closed), open_date, close_date (null if open), resolution_cost, root_cause.28- `/suppliers` — fields: supplier_id, name, region, quality_status (approved|watch|quality_hold).29- `/boms` and `/boms/<bom_id>` — fields: bom_id, name, warehouse_id, target_date, components[] (sku, quantity_per_kit).3031### Record counts (seed 7007)3254 products, 40 customers, 3 warehouses, 162 inventory records, 92 POs, 88 orders, 212 incidents, 12 suppliers, 9 BOMs.3334---3536## Core Business Rules3738### Effective Available Stock39```40effective_available = on_hand - reserved - quarantined - safety_stock41```42This single formula drives inventory_status, shortage/low-stock classification, transfer feasibility, and BOM replenishment gaps. safety_stock comes from the product master, not the inventory record.4344### Inventory Status Classification (per order)45For each order line at the order's warehouse:46- If product is **inactive** (active=false): line goes to `inactive_skus` list; the SKU is NOT checked for inventory.47- Compute effective_available for active SKUs.48- **shortage_skus**: SKUs where effective_available < 0 (no stock above safety stock at all). These trigger backorder.49- **low_stock_skus**: SKUs where 0 ≤ effective_available < line quantity. Some stock exists but not enough to fill the line. These trigger delayed_release if no shortage exists.50- If effective_available ≥ quantity: line is fully available.5152Order-level inventory_status (precedence):531. `inactive_and_shortage` — has inactive SKU(s) AND has shortage SKU(s) among active lines542. `inactive_sku` — has inactive SKU(s) but no shortage among active lines553. `shortage` — has shortage SKU(s), no inactive SKUs564. `low_stock` — has low_stock SKU(s), no shortage, no inactive575. `ready` — all lines fully available5859**Important**: low_stock_skus and shortage_skus should BOTH be populated for orders that have both types (they are independent lists, not mutually exclusive at the order level). Each individual SKU goes into exactly one list based on its effective_available and quantity.6061### Customer Exception Mapping62Check account_status first, then risk_flag:631. account_status = `blocked` → customer_exception = `account_blocked`642. risk_flag = `fraud_watch` → customer_exception = `fraud_watch`653. risk_flag = `credit_watch` → customer_exception = `credit_watch`664. account_status = `review_required` → customer_exception = `review_required`675. otherwise → `none`6869When account_status is blocked AND risk_flag is credit_watch, use `account_blocked` (account status takes precedence).7071### Decision Precedence (expedite queue / allocation)721. **account_blocked / fraud_watch / credit_watch** → final_decision = `reject_hold`, next_action = `hold_credit_or_fraud`732. **review_required** → final_decision = `manual_review`, next_action = `send_account_review`743. **inactive product** → final_decision = `manual_review`, next_action = `escalate_product_master`754. **shortage** → final_decision = `backorder`, next_action = `create_backorder`765. **low_stock** → final_decision = `delayed_release`, next_action = `delay_and_monitor`776. **ready** → final_decision = `ship_now`, next_action = `release_to_pick`7879Account status (including review_required) takes precedence over inactive product. Inactive product takes precedence over inventory issues. (Confirmed: review_required → send_account_review is preferred over inactive → escalate_product_master when both apply.)8081### Shipping Quote82- Total weight = Σ (product.weight_lb × line.quantity) for ALL lines including inactive SKUs.83- Query: `/shipping/quote?warehouse_id=<order_warehouse>&destination_zip=<order_dest>&weight_lb=<total>&speed=<order_shipping_speed>`84- Extract: zone_distance (integer), service_days (integer), total_cost_usd = round(total_cost, 2).85- Always compute the shipping quote regardless of the fulfillment decision.8687### Rounding88- All currency values rounded to 2 decimal places.89- Percentages rounded to 1 decimal place.90- Durations rounded to 2 decimal places.9192---9394## Task-Specific SOPs9596### Task 1: Expedite Queue (Dispatch Control)97**Input**: Wave ID + list of order IDs from a memo.98**Output**: wave_id, records[], summary.99100For each order (sorted by order_id):1011. Fetch order, customer, product, inventory data.1022. Classify each line: inactive_sku / shortage (eff < 0) / low_stock (0 ≤ eff < qty) / available.1033. Determine inventory_status (precedence above).1044. Determine customer_exception (mapping above).1055. Determine final_decision and next_action (precedence above).1066. Compute shipping_quote.1077. Populate shortage_skus, inactive_skus, low_stock_skus (each sorted ascending by SKU).108109Summary:110- order_count: total orders111- decision_counts: {ship_now, delayed_release, manual_review, backorder, reject_hold} with integer counts112- total_shipping_cost_usd: sum of all shipping_quote.total_cost_usd, rounded to 2 decimals113- blocked_order_ids: orders with reject_hold decision (sorted ascending)114- manual_review_order_ids: orders with manual_review decision (sorted ascending)115- backorder_order_ids: orders with backorder decision (sorted ascending)116- inactive_sku_order_ids: orders with any inactive SKU (sorted ascending)117118### Task 2: BOM Replenishment119**Input**: Production memo with BOM IDs, build quantities, build dates, target warehouse.120**Output**: task_id, plan_date, kit_targets, component_plan, transfer_requests, purchase_requisitions, excluded_components, summary.121122Steps:1231. For each BOM, compute component demand: total_required = Σ (quantity_per_kit × build_quantity) across all BOMs using that SKU.1242. For shared SKUs (used in multiple BOMs), use the **earliest build_date** as needed_by.1253. Compute effective_available at the target warehouse for each component.1264. Find timely POs: status in (open, confirmed), warehouse_id = target, eta ≤ needed_by date. Sum their quantities as timely_po_qty.1275. Determine exclusion and action (precedence):128 - **target_overstock**: effective_available > overstock_threshold → action = `overstock_excluded`, excluded. (This takes precedence even if stock covers the requirement.)129 - **stocked_no_gap**: effective_available ≥ total_required → action = `no_action_stocked`, excluded.130 - **timely_po_covers_gap**: effective_available + timely_po_qty ≥ total_required → action = `timely_po_covered`, excluded.131 - Otherwise: gap = total_required - effective_available - timely_po_qty > 0 → try transfer then purchase.1326. For components with a gap:133 - Compute spare at each other warehouse: spare = max(0, effective_available at that warehouse).134 - Transfer from the warehouse with **most spare first** (to minimize number of transfers).135 - Transfer qty = min(gap, spare). Subtract from gap.136 - If gap remains after transfers: purchase_requisition_qty = remaining gap.137 - If only transfer covers: final_action = `transfer_only`.138 - If purchase needed: final_action = `purchase_required`.1397. Purchase requisition: supplier_id from product, unit_cost from product, extended_cost = unit_cost × qty, needed_by = earliest build date.1408. Transfer request fields: sku, from_warehouse_id, to_warehouse_id, quantity, needed_by.141 - Sort: sku ascending, then quantity descending, then from_warehouse_id ascending.1429. Summary:143 - component_count: total components144 - total_purchase_units: sum of purchase requisition quantities145 - total_purchase_cost: sum of extended costs (2 decimals)146 - total_transfer_units: sum of transfer quantities147 - timely_po_covered_units: total PO units from timely POs for excluded components (sum of timely_po_qty for components with exclusion_reason = timely_po_covers_gap)148149**Common misjudgments**:150- `target_overstock` takes precedence over `stocked_no_gap` when effective > overstock_threshold.151- Use the warehouse with the MOST spare for transfers (not alphabetical order).152- timely_po_covered_units = total PO quantity (335), NOT the gap covered (106).153154### Task 3: Supplier Incident Scorecard155**Input**: Date filter on open_date, analysis_date, recommendation policy.156**Output**: analysis_window, summary, supplier_scorecard, top_escalation_suppliers, highest_cost_supplier_id, highest_share_supplier_id.157158Steps:1591. Filter incidents: open_date between start_date and end_date (inclusive).1602. Group by supplier_id.1613. For each supplier with filtered incidents:162 - incident_count = count163 - incident_percentage = round(incident_count / total_filtered_count × 100, 1)164 - total_resolution_cost = sum of resolution_cost (2 decimals)165 - avg_duration_days: closed incidents = (close_date - open_date).days; open incidents = (analysis_date - open_date).days; average rounded to 2 decimals166 - rma_count = count where incident_type = RMA167 - work_order_count = count where incident_type = WORK_ORDER168 - open_incident_count = count where status = open169 - severe_incident_count = count where severity in (high, critical)1704. Recommendation code (precedence: ESCALATE_SUPPLIER > PROCESS_REVIEW > WATCHLIST > MONITOR):171 - **ESCALATE_SUPPLIER**: (quality_hold AND incident_count ≥ 3) OR any critical RMA OR (rma_count ≥ 3 AND total_resolution_cost ≥ 15000.00)172 - **PROCESS_REVIEW**: work_order_count ≥ 3 AND work_order_count > rma_count173 - **WATCHLIST**: quality_status in (watch, quality_hold) OR incident_count ≥ 4 OR total_resolution_cost ≥ 12000.00 OR severe_incident_count ≥ 2174 - **MONITOR**: none of the above1755. Scorecard sorted by supplier_id ascending.1766. top_escalation_suppliers: ESCALATE_SUPPLIER suppliers, sorted by incident_count desc, then total_resolution_cost desc, then supplier_id asc.1777. highest_cost_supplier_id: supplier with max total_resolution_cost (ties broken by supplier_id ascending).1788. highest_share_supplier_id: supplier with max incident_percentage (ties broken by supplier_id ascending).1799. Summary: filtered_incident_count, supplier_count, total_resolution_cost (2 decimals), overall_rma_count, overall_work_order_count.180181### Task 4: Allocation / Transfer Desk182**Input**: Wave ID with mixed-warehouse orders.183**Output**: wave_id, line_actions, transfer_requests, blocked_orders, order_rollup, summary.184185For each line (sorted by order_id, then line_id):1861. Check account/risk status:187 - blocked → manual_review, reason = account_blocked188 - fraud_watch → manual_review, reason = fraud_watch189 - credit_watch → manual_review, reason = account_review_required190 - review_required → manual_review, reason = account_review_required191 - If any of these: ship_quantity=0, transfer_from=null, transfer_quantity=0, backorder_quantity=01922. Check product active status:193 - If inactive → manual_review, reason = inactive_product1943. Check inventory:195 - If eff ≥ qty → **ship** (ship_quantity=qty, no transfer/backorder)196 - If eff < qty:197 - ship_quantity = max(0, eff) (usable stock at requested warehouse)198 - uncovered = qty - ship_quantity199 - Check other warehouses for spare (max(0, eff at each))200 - If any warehouse has spare ≥ uncovered → **transfer** from the one with most spare201 - transfer_from = that warehouse, transfer_quantity = uncovered202 - Else → **backorder** (backorder_quantity = uncovered)203 - reason = insufficient_effective_stock for transfer and backorder204205Line action fields: order_id, line_id, sku, requested_warehouse, requested_effective_available, action (ship|transfer|backorder|manual_review), ship_quantity, transfer_from (enum or null), transfer_quantity, backorder_quantity, primary_reason.206207transfer_requests: order_id, line_id, sku, from_warehouse, to_warehouse, quantity. Sort by order_id, then line_id ascending.208209blocked_orders: orders where ALL lines are manual_review due to account/risk (NOT inactive_product). Sort ascending. This includes orders with account_blocked, fraud_watch, credit_watch, or review_required as the reason.210211order_rollup (per order):212- `manual_review` — ALL lines are manual_review213- `ready_to_ship` — ALL lines are ship214- `needs_transfer` — lines are only ship and/or transfer (no backorder, no manual_review)215- `has_backorder` — lines are only ship and/or backorder (no transfer, no manual_review)216- `mixed_actions` — any other combination (including manual_review + backorder, or transfer + manual_review)217218**Critical**: manual_review rollup only applies when ALL lines are manual_review. If an order has manual_review on some lines and other actions on others, the rollup is `mixed_actions`.219220Summary: total_orders, total_lines, ship_lines, transfer_lines, backorder_lines, manual_review_lines, blocked_orders (count), transfer_units, backorder_units.221222### Task 5: Quality Hold / Procurement Review223**Input**: Target supplier IDs, analysis window (start/end dates on open_date).224**Output**: analysis_window, supplier_decisions, held_po_ids, release_supplier_ids, summary.225226For each target supplier (sorted by supplier_id):2271. Filter incidents by open_date in analysis window and supplier_id.2282. Compute: recent_incident_count, recent_rma_count, severe_or_critical_count (high|critical), open_incident_count, affected_skus (sorted unique), sample_incident_ids (sorted, max 5).2293. Get all open/confirmed POs for the supplier (all warehouses, all SKUs).2304. Decision logic:231 - quality_hold → `freeze_new_replenishment`232 - watch + severe_or_critical_count ≥ 2 → `buyer_review_required`233 - watch + severe_or_critical_count < 2 → `monitor_only`234 - approved → `monitor_only`2355. held_po_ids per supplier: all open/confirmed PO IDs for freeze and buyer_review decisions; empty list for monitor_only.2366. release_supplier_ids: suppliers with monitor_only decision (sorted).237238Summary: suppliers_reviewed, freeze_count, buyer_review_count, monitor_count, held_po_count, total_recent_incidents.239240**Common misjudgments**:241- A watch supplier with only 1 severe incident should be `monitor_only`, not `buyer_review_required`. The threshold is severe ≥ 2.242- held_po_ids should be populated for both freeze and buyer_review decisions (pending review).243- sample_incident_ids: sorted ascending, maximum 5 entries.244245---246247## Common Misjudgments and Pitfalls2482491. **Effective stock formula**: Always subtract safety_stock from the product master. The inventory record does not contain safety_stock.2502. **Account status precedence**: account_status=blocked produces "account_blocked" exception even when risk_flag=credit_watch is also set. Do not use risk_flag to override account_status for the exception value.2513. **review_required vs inactive**: When both apply (customer under review AND product inactive), the next_action is `send_account_review` (account takes precedence), NOT `escalate_product_master`.2524. **target_overstock precedence**: If effective_available > overstock_threshold, classify as `target_overstock` / `overstock_excluded` EVEN IF the stock covers the requirement. This takes precedence over `stocked_no_gap`.2535. **Transfer warehouse selection**: Use the warehouse with the MOST spare effective stock first. Do not use alphabetical order. This minimizes the number of transfer requests.2546. **timely_po_covered_units**: This is the total PO quantity (all eligible PO units), NOT the gap that was covered.2557. **Order rollup manual_review**: Only apply `manual_review` rollup when ALL lines in the order are manual_review. Mixed manual_review + other actions → `mixed_actions`.2568. **blocked_orders**: Includes orders stopped at account or risk level (blocked, fraud_watch, credit_watch, review_required). Does NOT include orders with only inactive_product lines.2579. **low_stock_skus not cleared**: When an order has shortage lines, low_stock_skus should still be populated for lines that are in the low_stock range (0 ≤ eff < qty). Do not clear low_stock_skus for shortage orders.25810. **Shipping weight**: Include ALL line items in weight calculation, including inactive SKUs. The shipping quote is always computed regardless of the fulfillment decision.25911. **Watch supplier threshold**: A watch supplier needs severe_or_critical_count ≥ 2 for buyer_review_required. One severe incident is not enough; it stays monitor_only.26012. **Sample incident cap**: sample_incident_ids is capped at 5 entries, sorted ascending.261262---263264## Reusable SOP Template265266```2671. Identify the task type (expedite / BOM / scorecard / allocation / quality-hold).2682. Fetch all required data from the API (products, customers, inventory, orders, POs, incidents, suppliers, BOMs).2693. Build lookup maps: products by SKU, inventory by (warehouse_id, sku), customers by customer_id, suppliers by supplier_id.2704. For each entity (order/line/component/supplier):271 a. Compute effective_available = on_hand - reserved - quarantined - safety_stock.272 b. Apply classification rules (inventory_status, customer_exception, etc.).273 c. Apply decision precedence (account → product → inventory).274 d. Compute derived values (shipping quotes, transfer quantities, purchase costs).2755. Sort all lists by their specified ordering keys.2766. Round all currency to 2 decimals, percentages to 1 decimal, durations to 2 decimals.2777. Build summary from individual records.2788. Validate JSON structure against the answer template.279```280281## Output Field Shapes Quick Reference282283### Expedite Queue Record284```json285{286 "order_id": "string",287 "inventory_status": "ready|low_stock|shortage|inactive_sku|inactive_and_shortage",288 "customer_exception": "none|review_required|account_blocked|fraud_watch|credit_watch",289 "final_decision": "ship_now|delayed_release|manual_review|backorder|reject_hold",290 "next_action": "release_to_pick|delay_and_monitor|send_account_review|create_backorder|hold_credit_or_fraud|escalate_product_master",291 "shortage_skus": ["sorted SKU strings"],292 "inactive_skus": ["sorted SKU strings"],293 "low_stock_skus": ["sorted SKU strings"],294 "shipping_quote": {"zone_distance": int, "service_days": int, "total_cost_usd": float}295}296```297298### BOM Component Plan299```json300{301 "sku": "string",302 "total_required": int,303 "target_effective_available": int,304 "timely_po_qty": int,305 "transfer_qty": int,306 "purchase_requisition_qty": int,307 "final_action": "no_action_stocked|transfer_only|purchase_required|timely_po_covered|overstock_excluded",308 "coverage_po_ids": ["sorted strings"],309 "exclusion_reason": "none|target_overstock|timely_po_covers_gap|stocked_no_gap"310}311```312313### Supplier Scorecard Row314```json315{316 "supplier_id": "string", "supplier_name": "string",317 "incident_count": int, "incident_percentage": float (1 decimal),318 "total_resolution_cost": float (2 decimals), "avg_duration_days": float (2 decimals),319 "rma_count": int, "work_order_count": int,320 "open_incident_count": int, "severe_incident_count": int,321 "recommendation_code": "ESCALATE_SUPPLIER|PROCESS_REVIEW|WATCHLIST|MONITOR"322}323```324325### Allocation Line Action326```json327{328 "order_id": "string", "line_id": int, "sku": "string",329 "requested_warehouse": "WH_NORTH|WH_CENTRAL|WH_WEST",330 "requested_effective_available": int,331 "action": "ship|transfer|backorder|manual_review",332 "ship_quantity": int, "transfer_from": "string|null",333 "transfer_quantity": int, "backorder_quantity": int,334 "primary_reason": "none|account_blocked|account_review_required|fraud_watch|inactive_product|insufficient_effective_stock"335}336```337338### Quality Hold Supplier Decision339```json340{341 "supplier_id": "string", "supplier_name": "string", "quality_status": "approved|watch|quality_hold",342 "recent_incident_count": int, "recent_rma_count": int,343 "severe_or_critical_count": int, "open_incident_count": int,344 "affected_skus": ["sorted strings"], "sample_incident_ids": ["sorted strings, max 5"],345 "decision": "freeze_new_replenishment|buyer_review_required|monitor_only",346 "held_po_ids": ["sorted strings"]347}348```