SKILL: Northwind Components ERP — Inventory / Order-Fulfillment Operations
Transferable playbook for the Northwind Components ERP domain (inventory status,
order release/allocation, kit replenishment, supplier incident scorecards,
procurement quality control, shipping quotes). Built by working real records on
the shared API. Use it to solve unseen TEST tasks in the same domain.
0. REMOTE API — how to use it
Base URL (always use the runner-provided remote one; ignore any 127.0.0.1:8007
or task_group/.../env path mentioned in prompts):
<remote-env-url>
GET endpoints (all JSON):
/health — manifest + record counts (sanity check; data is seeded/static).
/products / /products/<sku>
/customers / /customers/<customer_id>
/suppliers
/warehouses (3: WH_NORTH 07102, WH_CENTRAL 60607, WH_WEST 89502)
/inventory?warehouse_id=&sku= (omit params to list all; filters are ANDed)
/purchase_orders?supplier_id=&sku=&status=
/orders?wave=&required_date=&customer_id= / /orders/<order_id>
/shipping/quote?warehouse_id=&destination_zip=&weight_lb=&speed= (speed: ground|two_day|overnight)
/incidents?start=&end=&supplier_id=&sku=&incident_type=&status=
/boms / /boms/<bom_id>
Practical notes:
- Pull the wave/order set first (
/orders?wave=...), then fan out to
/products/<sku>, /customers/<id>, /inventory?... for each referenced id.
/incidents date window filters on open_date and is INCLUSIVE on both ends
(start and end are both kept). Open incidents have close_date: null.
/shipping/quote returns the fully-computed quote server-side
(zone_distance, service_days, base_rate, fuel_surcharge_rate,
total_cost). DO NOT recompute it — call the endpoint and read the fields.
The only thing you must compute is the input weight_lb (see §6).
- Always answer ONLY with JSON matching the task's
answer_template.json. No prose.
Record shapes (field names you will rely on)
- product:
sku, name, active(bool), category, supplier_id, unit_cost, weight_lb, safety_stock, overstock_threshold
- customer:
customer_id, name, account_status(active|review_required|blocked), risk_flag(none|credit_watch|fraud_watch), tier, margin_band
- inventory (per warehouse+sku):
warehouse_id, sku, on_hand, reserved, quarantined, last_count_date
- order:
order_id, customer_id, warehouse_id, destination_zip, shipping_speed, priority, required_date, wave, lines[{line_id, sku, quantity, unit_price}]
- purchase_order:
po_id, sku, supplier_id, warehouse_id, quantity, status (open|confirmed|received|cancelled), eta
- supplier:
supplier_id, name, region, quality_status(approved|watch|quality_hold)
- incident:
incident_id, supplier_id, sku, warehouse_id, incident_type (RMA|WORK_ORDER), severity(low|medium|high|critical), status(open|closed), open_date, close_date(nullable), resolution_cost, root_cause
- bom:
bom_id, name, warehouse_id, target_date, components[{sku, quantity_per_kit}]
1. CORE CONCEPT — Effective Available stock
The single most important quantity across fulfillment/allocation/replenishment.
Protected stock is NOT freely usable: subtract reserved, quarantined, AND the
product's safety_stock ("normal operating buffer"). Floor at zero.
effective_available = max(0, on_hand - reserved - quarantined - safety_stock)
- Compute it PER (warehouse, sku). Inventory is warehouse-scoped.
- A missing inventory row (no record for that warehouse+sku) = 0 on hand → 0.
- The allocation/replenishment templates expose this as
requested_effective_available / target_effective_available.
- A separate "net available" (on_hand - reserved - quarantined, WITHOUT safety)
is used only to detect a low-buffer condition (see §2 low_stock).
2. INVENTORY STATUS classification (per order, expedite/release tasks)
Enum: ready | low_stock | shortage | inactive_sku | inactive_and_shortage.
Per line of the order (at the order's warehouse_id):
- inactive line:
product.active == false.
- shortage line: product active AND
quantity > effective_available.
- low line: product active, line is fillable (
quantity <= net_available), but
the warehouse buffer is thin — net_available < safety_stock
(net_available = on_hand - reserved - quarantined). [Tie-break convention; if a
task spec gives an explicit low_stock rule, follow that instead.]
Roll lines up to the order status (precedence):
- has inactive line AND has shortage line →
inactive_and_shortage
- has inactive line (no shortage) →
inactive_sku
- has shortage line →
shortage
- has low line →
low_stock
- otherwise →
ready
SKU exception lists (always sorted ascending, de-duplicated):
shortage_skus, inactive_skus, low_stock_skus.
3. CUSTOMER EXCEPTION classification
Enum: none | review_required | account_blocked | fraud_watch | credit_watch.
Derive from the customer record with this PRECEDENCE (a blocked account that is
also credit_watch is account_blocked):
account_status == "blocked" → account_blocked
risk_flag == "fraud_watch" → fraud_watch
risk_flag == "credit_watch" → credit_watch
account_status == "review_required"→ review_required
- else →
none
4. EXPEDITE / RELEASE DECISION (expedite-queue tasks)
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
Decision precedence — ACCOUNT/RISK gates first, then product master, then stock:
- account_blocked →
reject_hold / hold_credit_or_fraud
- fraud_watch or credit_watch →
manual_review / hold_credit_or_fraud
(a hard risk hold; a same-week customer ask does NOT override an account block)
- review_required →
manual_review / send_account_review
- inventory has inactive sku (inactive_sku or inactive_and_shortage) →
manual_review / escalate_product_master
- inventory shortage →
backorder / create_backorder
- inventory low_stock →
delayed_release / delay_and_monitor
- clean & ready →
ship_now / release_to_pick
ALWAYS produce a shipping_quote for EVERY order, even when the decision is not a
release (memos explicitly request the quote regardless of disposition). Use the
order's own shipping_speed and warehouse (see §6).
Summary block (typical keys): order_count, decision_counts (one int per
final_decision enum value, zeros included), total_shipping_cost_usd (sum of all
order quote totals, 2 dp), and id lists sorted ascending —
blocked_order_ids (reject_hold), manual_review_order_ids,
backorder_order_ids, inactive_sku_order_ids (orders with any inactive sku).
Records sorted by order_id ascending.
5. MIXED-WAREHOUSE ALLOCATION (line-level transfer/backorder tasks)
action ∈ ship | transfer | backorder | manual_review
primary_reason ∈ none | account_blocked | account_review_required | fraud_watch |
inactive_product | insufficient_effective_stock
Per line (requested warehouse = order.warehouse_id):
- Account/risk gate (whole order): blocked / review_required / fraud / credit →
manual_review, reason = matching account reason, ship=transfer=backorder=0.
These orders also go into blocked_orders (account/risk level only — NOT
product-only line reviews).
- Inactive product line →
manual_review, reason inactive_product.
- Else compare line
quantity vs requested-warehouse effective_available:
- qty <= eff →
ship, ship_quantity = qty.
- eff partially covers, and ANOTHER warehouse's effective_available can cover
the remainder →
transfer: ship_quantity = eff at requested wh,
transfer_quantity = remaining, transfer_from = the source warehouse,
backorder_quantity = 0. Pick ONE source warehouse for the uncovered qty;
do not draw on protected stock at the source (use its effective_available).
- no combination clears it →
backorder: backorder_quantity = uncovered qty,
ship_quantity = eff used at requested wh, reason insufficient_effective_stock.
- Emit a
transfer_requests row for each transfer line
(order_id, line_id, sku, from_warehouse, to_warehouse, quantity).
order_rollup outcome per order: all ship → ready_to_ship; any
manual_review (account/product) → manual_review; single action type →
needs_transfer / has_backorder; multiple differing actions → mixed_actions.
Sort line_actions by order_id then line_id. Summary keys are integer counts/units.
6. SHIPPING QUOTE
Per order: weight_lb = Σ over lines (product.weight_lb * line.quantity) using the
EXACT float (do not pre-round the weight). Then call:
/shipping/quote?warehouse_id=<order.warehouse_id>&destination_zip=<order.destination_zip>
&weight_lb=<total>&speed=<order.shipping_speed>
Read zone_distance (int), service_days (int), total_cost from the response.
Output object keys are typically zone_distance, service_days,
total_cost_usd (= response total_cost, rounded to 2 dp).
Formula the server uses (for sanity-checking only — prefer the live endpoint):
base_rate = 18.95 + 1.18*weight_lb + 3.4*(zone_distance - 3)
total_cost = base_rate * (1 + fuel_surcharge_rate) * speed_multiplier
with fuel ≈ 0.0925 and speed_multiplier ground=1.0, two_day=1.75, overnight=2.65.
zone_distance/service_days come from warehouse↔destination-zip distance;
ground service_days grows with zone, two_day=2, overnight=1.
7. KIT / BOM REPLENISHMENT (production replenishment tasks)
For each target build {bom_id, build_quantity, build_date} pull the BOM components.
Plan at the BOM's planning warehouse (the memo's planning_site, e.g. WH_WEST).
Per component sku (aggregate across all builds that use it):
total_required = Σ (quantity_per_kit * build_quantity) over every build using it.
needed_by = EARLIEST build_date among builds requiring that sku.
target_effective_available = effective_available at the planning warehouse
(§1: on_hand - reserved - quarantined - safety_stock, floored).
gap = max(0, total_required - target_effective_available).
Coverage decision (in order; final_action enum:
no_action_stocked | transfer_only | purchase_required | timely_po_covered | overstock_excluded):
- gap == 0:
- if
target_effective_available > overstock_threshold →
overstock_excluded (exclusion_reason target_overstock); never add stock.
- else
no_action_stocked (exclusion_reason stocked_no_gap).
- gap > 0 — check TIMELY POs first:
timely_po_qty = Σ quantity of POs that are SAME planning warehouse,
status in {open, confirmed}, AND eta <= needed_by. (received/cancelled and
wrong-warehouse and late-eta POs are NOT timely.)
- if
timely_po_qty >= gap → timely_po_covered
(exclusion_reason timely_po_covers_gap; coverage_po_ids = those PO ids,
sorted). No transfer/purchase.
- gap still uncovered — TRANSFER from other warehouses:
transfer_qty = min(gap, Σ effective_available of OTHER warehouses) using
each source's effective_available (respect protected stock).
- if transfer fully covers the gap →
transfer_only.
- Remainder after transfer → PURCHASE:
purchase_requisition_qty = gap - transfer_qty (only the still-uncovered part).
final_action = purchase_required. Requisition uses
supplier_id = product.supplier_id, unit_cost = product.unit_cost,
extended_cost = unit_cost * quantity (2 dp), warehouse_id = planning site,
needed_by.
Emit transfer_requests (sku, from_warehouse_id, to_warehouse_id, quantity,
needed_by) and purchase_requisitions. excluded_components lists components with
final_action overstock_excluded / timely_po_covered / no-gap, with their reason +
supporting_po_ids. Summary: component_count, total_purchase_units,
total_purchase_cost (2 dp), total_transfer_units, timely_po_covered_units.
Sort component_plan / requisitions / exclusions by sku; transfer_requests by sku,
then quantity desc, then from_warehouse_id asc.
8. SUPPLIER INCIDENT SCORECARD (quality-review tasks)
Filter incidents by the requested window on open_date (inclusive both ends), e.g.
/incidents?start=2026-01-01&end=2026-03-31. Group by supplier_id. Per supplier:
incident_count, incident_percentage = 100*count/filtered_total (1 dp).
total_resolution_cost = Σ resolution_cost (2 dp).
avg_duration_days (2 dp) where per-incident duration is calendar days:
- closed:
close_date - open_date.
- open:
analysis_date - open_date (analysis_date from the request).
rma_count (incident_type RMA), work_order_count (WORK_ORDER).
open_incident_count (status open).
severe_incident_count = severity in {high, critical}.
recommendation_code — apply in STRICT precedence, FIRST match wins
(ESCALATE_SUPPLIER > PROCESS_REVIEW > WATCHLIST > MONITOR):
- ESCALATE_SUPPLIER: supplier
quality_status == quality_hold AND >=3 filtered
incidents; OR any critical RMA (incident_type RMA & severity critical);
OR >=3 RMAs AND total filtered 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 rows sorted by supplier_id asc. Include a supplier row only if it has
=1 filtered incident. summary: filtered_incident_count, supplier_count,
total_resolution_cost (2 dp), overall_rma_count, overall_work_order_count.
top_escalation_suppliers = supplier_ids with code ESCALATE_SUPPLIER, ordered by
incident_count desc, then total_resolution_cost desc, then supplier_id asc.
highest_cost_supplier_id = supplier with max total_resolution_cost;
highest_share_supplier_id = supplier with max incident_count/share.
9. PROCUREMENT QUALITY CONTROL (replenishment-freeze tasks)
For each target supplier over the requested window (filter /incidents by
supplier_id + start/end on open_date). 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).
held_po_ids for a supplier = that supplier's purchase orders with status in
{open, confirmed} (received/cancelled never held), sorted.
decision ∈ freeze_new_replenishment | buyer_review_required | monitor_only:
- freeze_new_replenishment: supplier
quality_status == quality_hold
(hard quality hold → freeze and hold all its open/confirmed POs).
- buyer_review_required:
quality_status == watch, OR recent quality signals are
elevated (e.g. recent RMAs, severe/critical incidents, or open incidents present).
- monitor_only: approved supplier with no concerning recent signals.
Top-level held_po_ids = sorted unique union of all suppliers' held POs.
release_supplier_ids = suppliers whose decision is monitor_only (sorted).
Summary: suppliers_reviewed, freeze_count, buyer_review_count, monitor_count,
held_po_count, total_recent_incidents. Sort supplier_decisions by supplier_id asc.
10. OUTPUT / FORMATTING CONVENTIONS (apply everywhere)
- Return ONLY the JSON object the template specifies; no narrative, no extra keys.
- Honor every
required_value literally (e.g. wave_id, task_id).
- Currency / cost fields: round to 2 decimals. Percentages: 1 decimal (unless the
request says otherwise). Durations: 2 decimals. Counts/units/zone/days: integers.
- Round at the OUTPUT field; sum unrounded then round totals (avoid double-rounding
drift on
total_* fields).
- Sorting: respect each list's stated order. Default for id/sku lists is ascending,
de-duplicated. Records usually sort by order_id (then line_id) or by sku/supplier_id.
- Enum fields must use EXACTLY the allowed literal values; include all required keys
even when a list is empty
[], a count is 0, or a reason is "none".
- Use
null for nullable enum fields (e.g. transfer_from when no transfer).
11. COMMON MISJUDGMENTS / EXCLUSION RULES (avoid these)
- Forgetting safety_stock: effective_available MUST subtract reserved AND
quarantined AND safety_stock. Using raw on_hand overstates availability.
- Treating reserved/quarantined stock as shippable. It is protected.
- Letting a customer "exception request" override a hard account block or risk
flag. Account/risk gates win over inventory; never ship a blocked account.
- Counting
received or cancelled POs as coverage. Only open/confirmed POs are
active; a PO is only "timely" if same-warehouse AND eta <= needed_by.
- Adding replenishment to an already overstocked component
(target_effective_available > overstock_threshold → overstock_excluded).
- Shipping/replenishing an inactive SKU (
active == false) → escalate/manual_review.
- Incident window off-by-one: window is inclusive both ends on open_date; open
incidents have null close_date — use analysis_date for their duration.
- Misordering recommendation/decision precedence — always evaluate highest-severity
condition first and stop at the first match.
- Recomputing shipping cost by hand and drifting from the endpoint — call the API.
- Omitting required template keys, empty lists, zero counts, or
"none" reasons.
1---2name: self-attempt-03-253description: SKILL: Northwind Components ERP — Inventory / Order-Fulfillment Operations4---5# SKILL: Northwind Components ERP — Inventory / Order-Fulfillment Operations67Transferable playbook for the Northwind Components ERP domain (inventory status,8order release/allocation, kit replenishment, supplier incident scorecards,9procurement quality control, shipping quotes). Built by working real records on10the shared API. Use it to solve unseen TEST tasks in the same domain.1112---1314## 0. REMOTE API — how to use it1516Base URL (always use the runner-provided remote one; ignore any `127.0.0.1:8007`17or `task_group/.../env` path mentioned in prompts):1819 <remote-env-url>2021GET endpoints (all JSON):22- `/health` — manifest + record counts (sanity check; data is seeded/static).23- `/products` / `/products/<sku>`24- `/customers` / `/customers/<customer_id>`25- `/suppliers`26- `/warehouses` (3: WH_NORTH 07102, WH_CENTRAL 60607, WH_WEST 89502)27- `/inventory?warehouse_id=&sku=` (omit params to list all; filters are ANDed)28- `/purchase_orders?supplier_id=&sku=&status=`29- `/orders?wave=&required_date=&customer_id=` / `/orders/<order_id>`30- `/shipping/quote?warehouse_id=&destination_zip=&weight_lb=&speed=` (speed: ground|two_day|overnight)31- `/incidents?start=&end=&supplier_id=&sku=&incident_type=&status=`32- `/boms` / `/boms/<bom_id>`3334Practical notes:35- Pull the wave/order set first (`/orders?wave=...`), then fan out to36 `/products/<sku>`, `/customers/<id>`, `/inventory?...` for each referenced id.37- `/incidents` date window filters on `open_date` and is INCLUSIVE on both ends38 (`start` and `end` are both kept). Open incidents have `close_date: null`.39- `/shipping/quote` returns the fully-computed quote server-side40 (`zone_distance`, `service_days`, `base_rate`, `fuel_surcharge_rate`,41 `total_cost`). DO NOT recompute it — call the endpoint and read the fields.42 The only thing you must compute is the input `weight_lb` (see §6).43- Always answer ONLY with JSON matching the task's `answer_template.json`. No prose.4445### Record shapes (field names you will rely on)46- product: `sku, name, active(bool), category, supplier_id, unit_cost,47 weight_lb, safety_stock, overstock_threshold`48- customer: `customer_id, name, account_status(active|review_required|blocked),49 risk_flag(none|credit_watch|fraud_watch), tier, margin_band`50- inventory (per warehouse+sku): `warehouse_id, sku, on_hand, reserved,51 quarantined, last_count_date`52- order: `order_id, customer_id, warehouse_id, destination_zip, shipping_speed,53 priority, required_date, wave, lines[{line_id, sku, quantity, unit_price}]`54- purchase_order: `po_id, sku, supplier_id, warehouse_id, quantity, status55 (open|confirmed|received|cancelled), eta`56- supplier: `supplier_id, name, region, quality_status(approved|watch|quality_hold)`57- incident: `incident_id, supplier_id, sku, warehouse_id, incident_type58 (RMA|WORK_ORDER), severity(low|medium|high|critical), status(open|closed),59 open_date, close_date(nullable), resolution_cost, root_cause`60- bom: `bom_id, name, warehouse_id, target_date, components[{sku, quantity_per_kit}]`6162---6364## 1. CORE CONCEPT — Effective Available stock6566The single most important quantity across fulfillment/allocation/replenishment.67Protected stock is NOT freely usable: subtract reserved, quarantined, AND the68product's safety_stock ("normal operating buffer"). Floor at zero.6970 effective_available = max(0, on_hand - reserved - quarantined - safety_stock)7172- Compute it PER (warehouse, sku). Inventory is warehouse-scoped.73- A missing inventory row (no record for that warehouse+sku) = 0 on hand → 0.74- The allocation/replenishment templates expose this as75 `requested_effective_available` / `target_effective_available`.76- A separate "net available" (on_hand - reserved - quarantined, WITHOUT safety)77 is used only to detect a low-buffer condition (see §2 low_stock).7879---8081## 2. INVENTORY STATUS classification (per order, expedite/release tasks)8283Enum: `ready | low_stock | shortage | inactive_sku | inactive_and_shortage`.8485Per line of the order (at the order's `warehouse_id`):86- inactive line: `product.active == false`.87- shortage line: product active AND `quantity > effective_available`.88- low line: product active, line is fillable (`quantity <= net_available`), but89 the warehouse buffer is thin — `net_available < safety_stock`90 (net_available = on_hand - reserved - quarantined). [Tie-break convention; if a91 task spec gives an explicit low_stock rule, follow that instead.]9293Roll lines up to the order status (precedence):941. has inactive line AND has shortage line → `inactive_and_shortage`952. has inactive line (no shortage) → `inactive_sku`963. has shortage line → `shortage`974. has low line → `low_stock`985. otherwise → `ready`99100SKU exception lists (always sorted ascending, de-duplicated):101`shortage_skus`, `inactive_skus`, `low_stock_skus`.102103---104105## 3. CUSTOMER EXCEPTION classification106107Enum: `none | review_required | account_blocked | fraud_watch | credit_watch`.108Derive from the customer record with this PRECEDENCE (a blocked account that is109also credit_watch is `account_blocked`):1101. `account_status == "blocked"` → `account_blocked`1112. `risk_flag == "fraud_watch"` → `fraud_watch`1123. `risk_flag == "credit_watch"` → `credit_watch`1134. `account_status == "review_required"`→ `review_required`1145. else → `none`115116---117118## 4. EXPEDITE / RELEASE DECISION (expedite-queue tasks)119120`final_decision` ∈ ship_now | delayed_release | manual_review | backorder | reject_hold121`next_action` ∈ release_to_pick | delay_and_monitor | send_account_review |122 create_backorder | hold_credit_or_fraud | escalate_product_master123124Decision precedence — ACCOUNT/RISK gates first, then product master, then stock:1251. account_blocked → `reject_hold` / `hold_credit_or_fraud`1262. fraud_watch or credit_watch → `manual_review` / `hold_credit_or_fraud`127 (a hard risk hold; a same-week customer ask does NOT override an account block)1283. review_required → `manual_review` / `send_account_review`1294. inventory has inactive sku (inactive_sku or inactive_and_shortage) →130 `manual_review` / `escalate_product_master`1315. inventory shortage → `backorder` / `create_backorder`1326. inventory low_stock → `delayed_release` / `delay_and_monitor`1337. clean & ready → `ship_now` / `release_to_pick`134135ALWAYS produce a `shipping_quote` for EVERY order, even when the decision is not a136release (memos explicitly request the quote regardless of disposition). Use the137order's own `shipping_speed` and warehouse (see §6).138139Summary block (typical keys): `order_count`, `decision_counts` (one int per140final_decision enum value, zeros included), `total_shipping_cost_usd` (sum of all141order quote totals, 2 dp), and id lists sorted ascending —142`blocked_order_ids` (reject_hold), `manual_review_order_ids`,143`backorder_order_ids`, `inactive_sku_order_ids` (orders with any inactive sku).144Records sorted by `order_id` ascending.145146---147148## 5. MIXED-WAREHOUSE ALLOCATION (line-level transfer/backorder tasks)149150`action` ∈ ship | transfer | backorder | manual_review151`primary_reason` ∈ none | account_blocked | account_review_required | fraud_watch |152 inactive_product | insufficient_effective_stock153154Per line (requested warehouse = order.warehouse_id):1551. Account/risk gate (whole order): blocked / review_required / fraud / credit →156 `manual_review`, reason = matching account reason, ship=transfer=backorder=0.157 These orders also go into `blocked_orders` (account/risk level only — NOT158 product-only line reviews).1592. Inactive product line → `manual_review`, reason `inactive_product`.1603. Else compare line `quantity` vs requested-warehouse `effective_available`:161 - qty <= eff → `ship`, ship_quantity = qty.162 - eff partially covers, and ANOTHER warehouse's effective_available can cover163 the remainder → `transfer`: ship_quantity = eff at requested wh,164 transfer_quantity = remaining, transfer_from = the source warehouse,165 backorder_quantity = 0. Pick ONE source warehouse for the uncovered qty;166 do not draw on protected stock at the source (use its effective_available).167 - no combination clears it → `backorder`: backorder_quantity = uncovered qty,168 ship_quantity = eff used at requested wh, reason `insufficient_effective_stock`.1694. Emit a `transfer_requests` row for each transfer line170 (order_id, line_id, sku, from_warehouse, to_warehouse, quantity).1715. `order_rollup` outcome per order: all ship → `ready_to_ship`; any172 manual_review (account/product) → `manual_review`; single action type →173 `needs_transfer` / `has_backorder`; multiple differing actions → `mixed_actions`.174Sort line_actions by order_id then line_id. Summary keys are integer counts/units.175176---177178## 6. SHIPPING QUOTE179180Per order: `weight_lb = Σ over lines (product.weight_lb * line.quantity)` using the181EXACT float (do not pre-round the weight). Then call:182183 /shipping/quote?warehouse_id=<order.warehouse_id>&destination_zip=<order.destination_zip>184 &weight_lb=<total>&speed=<order.shipping_speed>185186Read `zone_distance` (int), `service_days` (int), `total_cost` from the response.187Output object keys are typically `zone_distance`, `service_days`,188`total_cost_usd` (= response `total_cost`, rounded to 2 dp).189190Formula the server uses (for sanity-checking only — prefer the live endpoint):191- `base_rate = 18.95 + 1.18*weight_lb + 3.4*(zone_distance - 3)`192- `total_cost = base_rate * (1 + fuel_surcharge_rate) * speed_multiplier`193 with fuel ≈ 0.0925 and speed_multiplier ground=1.0, two_day=1.75, overnight=2.65.194- `zone_distance`/`service_days` come from warehouse↔destination-zip distance;195 ground service_days grows with zone, two_day=2, overnight=1.196197---198199## 7. KIT / BOM REPLENISHMENT (production replenishment tasks)200201For each target build {bom_id, build_quantity, build_date} pull the BOM components.202Plan at the BOM's planning warehouse (the memo's `planning_site`, e.g. WH_WEST).203204Per component sku (aggregate across all builds that use it):205- `total_required = Σ (quantity_per_kit * build_quantity)` over every build using it.206- `needed_by` = EARLIEST build_date among builds requiring that sku.207- `target_effective_available` = effective_available at the planning warehouse208 (§1: on_hand - reserved - quarantined - safety_stock, floored).209- `gap = max(0, total_required - target_effective_available)`.210211Coverage decision (in order; `final_action` enum:212no_action_stocked | transfer_only | purchase_required | timely_po_covered | overstock_excluded):2131. gap == 0:214 - if `target_effective_available > overstock_threshold` →215 `overstock_excluded` (exclusion_reason `target_overstock`); never add stock.216 - else `no_action_stocked` (exclusion_reason `stocked_no_gap`).2172. gap > 0 — check TIMELY POs first:218 - `timely_po_qty` = Σ quantity of POs that are SAME planning warehouse,219 status in {open, confirmed}, AND `eta <= needed_by`. (received/cancelled and220 wrong-warehouse and late-eta POs are NOT timely.)221 - if `timely_po_qty >= gap` → `timely_po_covered`222 (exclusion_reason `timely_po_covers_gap`; `coverage_po_ids` = those PO ids,223 sorted). No transfer/purchase.2243. gap still uncovered — TRANSFER from other warehouses:225 - `transfer_qty` = min(gap, Σ effective_available of OTHER warehouses) using226 each source's effective_available (respect protected stock).227 - if transfer fully covers the gap → `transfer_only`.2284. Remainder after transfer → PURCHASE:229 - `purchase_requisition_qty = gap - transfer_qty` (only the still-uncovered part).230 - `final_action = purchase_required`. Requisition uses231 `supplier_id = product.supplier_id`, `unit_cost = product.unit_cost`,232 `extended_cost = unit_cost * quantity` (2 dp), `warehouse_id = planning site`,233 `needed_by`.234235Emit `transfer_requests` (sku, from_warehouse_id, to_warehouse_id, quantity,236needed_by) and `purchase_requisitions`. `excluded_components` lists components with237final_action overstock_excluded / timely_po_covered / no-gap, with their reason +238supporting_po_ids. Summary: component_count, total_purchase_units,239total_purchase_cost (2 dp), total_transfer_units, timely_po_covered_units.240Sort component_plan / requisitions / exclusions by sku; transfer_requests by sku,241then quantity desc, then from_warehouse_id asc.242243---244245## 8. SUPPLIER INCIDENT SCORECARD (quality-review tasks)246247Filter incidents by the requested window on `open_date` (inclusive both ends), e.g.248`/incidents?start=2026-01-01&end=2026-03-31`. Group by supplier_id. Per supplier:249- `incident_count`, `incident_percentage` = 100*count/filtered_total (1 dp).250- `total_resolution_cost` = Σ resolution_cost (2 dp).251- `avg_duration_days` (2 dp) where per-incident duration is calendar days:252 - closed: `close_date - open_date`.253 - open: `analysis_date - open_date` (analysis_date from the request).254- `rma_count` (incident_type RMA), `work_order_count` (WORK_ORDER).255- `open_incident_count` (status open).256- `severe_incident_count` = severity in {high, critical}.257258`recommendation_code` — apply in STRICT precedence, FIRST match wins259(ESCALATE_SUPPLIER > PROCESS_REVIEW > WATCHLIST > MONITOR):260- ESCALATE_SUPPLIER: supplier `quality_status == quality_hold` AND >=3 filtered261 incidents; OR any critical RMA (incident_type RMA & severity critical);262 OR >=3 RMAs AND total filtered resolution cost >= 15000.00.263- PROCESS_REVIEW: WORK_ORDER count >= 3 AND WORK_ORDER count > RMA count.264- WATCHLIST: quality_status in {watch, quality_hold}; OR incident_count >= 4;265 OR total resolution cost >= 12000.00; OR severe_incident_count >= 2.266- MONITOR: none of the above.267268Scorecard rows sorted by supplier_id asc. Include a supplier row only if it has269>=1 filtered incident. `summary`: filtered_incident_count, supplier_count,270total_resolution_cost (2 dp), overall_rma_count, overall_work_order_count.271`top_escalation_suppliers` = supplier_ids with code ESCALATE_SUPPLIER, ordered by272incident_count desc, then total_resolution_cost desc, then supplier_id asc.273`highest_cost_supplier_id` = supplier with max total_resolution_cost;274`highest_share_supplier_id` = supplier with max incident_count/share.275276---277278## 9. PROCUREMENT QUALITY CONTROL (replenishment-freeze tasks)279280For each target supplier over the requested window (filter `/incidents` by281supplier_id + start/end on open_date). Compute recent_incident_count,282recent_rma_count, severe_or_critical_count (high|critical), open_incident_count,283affected_skus (sorted unique), sample_incident_ids (sorted, MAX 5).284285`held_po_ids` for a supplier = that supplier's purchase orders with status in286{open, confirmed} (received/cancelled never held), sorted.287288`decision` ∈ freeze_new_replenishment | buyer_review_required | monitor_only:289- freeze_new_replenishment: supplier `quality_status == quality_hold`290 (hard quality hold → freeze and hold all its open/confirmed POs).291- buyer_review_required: `quality_status == watch`, OR recent quality signals are292 elevated (e.g. recent RMAs, severe/critical incidents, or open incidents present).293- monitor_only: approved supplier with no concerning recent signals.294295Top-level `held_po_ids` = sorted unique union of all suppliers' held POs.296`release_supplier_ids` = suppliers whose decision is monitor_only (sorted).297Summary: suppliers_reviewed, freeze_count, buyer_review_count, monitor_count,298held_po_count, total_recent_incidents. Sort supplier_decisions by supplier_id asc.299300---301302## 10. OUTPUT / FORMATTING CONVENTIONS (apply everywhere)303304- Return ONLY the JSON object the template specifies; no narrative, no extra keys.305- Honor every `required_value` literally (e.g. wave_id, task_id).306- Currency / cost fields: round to 2 decimals. Percentages: 1 decimal (unless the307 request says otherwise). Durations: 2 decimals. Counts/units/zone/days: integers.308- Round at the OUTPUT field; sum unrounded then round totals (avoid double-rounding309 drift on `total_*` fields).310- Sorting: respect each list's stated order. Default for id/sku lists is ascending,311 de-duplicated. Records usually sort by order_id (then line_id) or by sku/supplier_id.312- Enum fields must use EXACTLY the allowed literal values; include all required keys313 even when a list is empty `[]`, a count is `0`, or a reason is `"none"`.314- Use `null` for nullable enum fields (e.g. transfer_from when no transfer).315316---317318## 11. COMMON MISJUDGMENTS / EXCLUSION RULES (avoid these)319320- Forgetting safety_stock: effective_available MUST subtract reserved AND321 quarantined AND safety_stock. Using raw on_hand overstates availability.322- Treating reserved/quarantined stock as shippable. It is protected.323- Letting a customer "exception request" override a hard account block or risk324 flag. Account/risk gates win over inventory; never ship a blocked account.325- Counting `received` or `cancelled` POs as coverage. Only open/confirmed POs are326 active; a PO is only "timely" if same-warehouse AND eta <= needed_by.327- Adding replenishment to an already overstocked component328 (target_effective_available > overstock_threshold → overstock_excluded).329- Shipping/replenishing an inactive SKU (`active == false`) → escalate/manual_review.330- Incident window off-by-one: window is inclusive both ends on open_date; open331 incidents have null close_date — use analysis_date for their duration.332- Misordering recommendation/decision precedence — always evaluate highest-severity333 condition first and stop at the first match.334- Recomputing shipping cost by hand and drifting from the endpoint — call the API.335- Omitting required template keys, empty lists, zero counts, or `"none"` reasons.