Northwind ERP Fulfillment — Solver Skill
Executable experience for solving Northwind Components ERP fulfillment tasks against the
shared remote API. Five recurring task families are covered. Apply the rules below at
test time; they were reverse-engineered from the live API and internal business
consistency (there are no gold answers to check against).
0. Environment & API Reference
Base URL: the URL given in the task's environment_access.md (a remote host, e.g.
<remote-env-url>). Tasks reference http://127.0.0.1:8007 in prose, but the
REAL entrypoint is the remote base URL in environment_access.md. Always use that.
Calling notes (critical):
- The server speaks HTTP/1.0 and closes each connection. Use a fresh connection per call:
curl -sS --max-time 30 '<url>' or Python urllib.request with timeout=30.
- All endpoints are GET. Parameters are query-string.
- Do NOT try to read
env/, server.py, data files, or start a local server. Only the API.
Endpoints:
| Endpoint |
Returns |
Notes |
GET /health |
manifest (record counts, seed) + status |
sanity check first |
GET /products , /products/<sku> |
SKU master |
active, safety_stock, overstock_threshold, unit_cost, weight_lb, supplier_id, category |
GET /customers , /customers/<id> |
account/risk |
account_status∈{active,blocked,review_required}, risk_flag∈{none,fraud_watch,credit_watch}, tier |
GET /warehouses |
warehouse_id∈{WH_NORTH,WH_CENTRAL,WH_WEST}, zip, region |
3 warehouses |
GET /inventory?warehouse_id=&sku= |
stock row |
on_hand, reserved, quarantined, last_count_date |
GET /purchase_orders?supplier_id=&sku=&status= |
POs |
status∈{open,confirmed,received,cancelled}, eta, quantity, warehouse_id, supplier_id |
GET /orders?wave=&required_date=&customer_id= , /orders/<id> |
orders |
wave, warehouse_id, customer_id, destination_zip, shipping_speed∈{ground,two_day,overnight}, lines[]{line_id,sku,quantity,unit_price} |
GET /shipping/quote?warehouse_id=&destination_zip=&weight_lb=&speed= |
quote |
zone_distance, service_days, total_cost (USD, 2dp), also base_rate,fuel_surcharge_rate |
GET /incidents?start=&end=&supplier_id=&sku=&incident_type=&status= |
incidents |
open_date,close_date(null if open),incident_type∈{RMA,WORK_ORDER},severity∈{low,medium,high,critical},status∈{open,closed},resolution_cost,supplier_id,sku,warehouse_id |
GET /suppliers |
suppliers |
supplier_id, name, quality_status∈{approved,watch,quality_hold}, region |
GET /boms , /boms/<id> |
BOMs |
bom_id, name, warehouse_id, target_date, components[]{sku, quantity_per_kit} |
Data scale (manifest seed 7007): 54 products, 40 customers, 3 warehouses, 9 BOMs,
92 POs, 88 orders, 162 inventory rows, 212 incidents, 12 suppliers.
SOP: pull-and-cache. At the start of any task, fetch /products, /customers,
/inventory, /purchase_orders, /suppliers, /boms once and index them in memory
(dict by sku / customer_id / (warehouse_id,sku) / supplier_id / bom_id). Then fetch only
the task-specific orders. This minimizes round-trips (server closes connections).
1. Core Business Primitives (used by ALL task families)
1.1 Effective available stock
effective_available = on_hand - reserved - quarantined
on_hand, reserved, quarantined come from /inventory for a (warehouse_id, sku).
- Reserved and quarantined units are NOT freely shippable. Always subtract both.
effective_available can be NEGATIVE (e.g. quarantined=1 → -1). Treat
negative as 0 usable when computing shippable quantity, but REPORT the raw value where a
field asks for "effective available" / "requested_effective_available".
1.2 Safety stock & overstock threshold (from /products)
safety_stock: protected buffer. A warehouse's spare (donatable) stock =
effective_available - safety_stock. Only POSITIVE spare can be transferred/donated.
overstock_threshold: if a warehouse's effective_available EXCEEDS this, the SKU is
OVERSTOCKED there → do NOT add more stock (exclusion). Used in BOM replenishment.
1.3 Product active flag
active: false means the SKU is discontinued/inactive. Inactive SKUs cannot be
auto-shipped or replenished regardless of on-hand quantity — they force a manual review
(escalate to product master) in dispatch tasks, and block transfer/ship in allocation.
- Known inactive SKUs in this dataset: NW-1007, NW-1019, NW-1033, NW-1048 (4 total). Always
check
active per SKU; do not assume.
1.4 Customer/account exception precedence
Derived from account_status + risk_flag:
| account_status |
risk_flag |
customer_exception |
| blocked |
(any) |
account_blocked |
| review_required |
(any) |
review_required |
| active |
fraud_watch |
fraud_watch |
| active |
credit_watch |
credit_watch |
| active |
none |
none |
Precedence (most severe first): account_blocked > fraud_watch > credit_watch > review_required > none.
account_status drives blocked/review; risk_flag only matters when account_status == active
1.5 Shipping quote computation
weight_lb = sum over ALL order lines of (product.weight_lb * line.quantity)
quote = GET /shipping/quote?warehouse_id=<order.warehouse_id>
&destination_zip=<order.destination_zip>
&weight_lb=<computed weight>
&speed=<order.shipping_speed>
- Use the ORDER's own
shipping_speed (ground / two_day / overnight), even if the order
is not going to be released. Quotes are required for every order in the queue/memo.
- The API returns
total_cost (already 2dp), service_days (int), zone_distance (int).
- Sum
total_cost across all records for the summary total_shipping_cost_usd.
1.6 Timely purchase-order coverage
A PO is eligible / timely for a build/need at warehouse W with deadline D iff:
status ∈ {open, confirmed} (NOT received, NOT cancelled)
warehouse_id == W (same warehouse as the build)
eta <= D (arrival on or before the build/need date)
received POs are EXCLUDED — their stock is already inside on_hand (would double-count).
timely_po_qty = sum of quantity over all eligible POs for that SKU at that warehouse
(report the full eligible PO quantity, do NOT cap to the gap).
1.7 Rounding & formatting
- Currency (USD): round to 2 decimals everywhere (
round(x, 2)).
- Percentages (incident share): round to 1 decimal.
- Durations (days): round to 2 decimals.
- Counts/quantities: integers.
- Lists of IDs/SKUs: sorted ascending (strings sort lexicographically — "NW-1003" < "NW-1011" < "NW-1049").
- Always keep records sorted by the field the template names (usually order_id / sku / supplier_id ascending).
2. Task Family A — Expedite Queue Dispatch (wave decision)
Inputs: a queue memo listing order_ids + wave_id. Output: records (per order) +
summary. See the answer_template for exact keys: wave_id, records[], summary{}.
2.1 Per-order computation
For each order (sorted ascending by order_id):
- Fetch
/orders/<order_id>. Note customer_id, warehouse_id, destination_zip,
shipping_speed, lines[].
- Customer exception: look up customer, apply §1.4 table.
- Per-line classification (for each line: sku, quantity):
- product =
/products/<sku>; inventory = /inventory?warehouse_id=<wh>&sku=<sku>
eff = on_hand - reserved - quarantined
- shortage line if
eff < quantity → add sku to shortage_skus
- inactive line if
product.active == false → add sku to inactive_skus
- low_stock line if
quantity <= eff < product.safety_stock (can fill but below
safety buffer) → add sku to low_stock_skus
(a shortage line is NOT also low_stock; low_stock requires eff >= quantity)
- inventory_status (order rollup, in this priority):
- has inactive SKU AND has shortage →
inactive_and_shortage
- has inactive SKU (no shortage) →
inactive_sku
- has shortage (no inactive) →
shortage
- has any low_stock (no inactive, no shortage) →
low_stock
- else →
ready
- final_decision + next_action — apply the PRECEDENCE LADDER (first match wins):
| Priority |
Condition |
final_decision |
next_action |
| 1 |
customer_exception == account_blocked |
reject_hold |
hold_credit_or_fraud |
| 2 |
inventory_status in {inactive_sku, inactive_and_shortage} |
manual_review |
escalate_product_master |
| 3 |
customer_exception == fraud_watch |
manual_review |
hold_credit_or_fraud |
| 4 |
customer_exception == credit_watch |
manual_review |
hold_credit_or_fraud |
| 5 |
customer_exception == review_required |
manual_review |
send_account_review |
| 6 |
inventory_status == shortage |
backorder |
create_backorder |
| 7 |
inventory_status == low_stock |
delayed_release |
delay_and_monitor |
| 8 |
inventory_status == ready |
ship_now |
release_to_pick |
- Account/risk/product issues (manual_review / reject_hold) take PRECEDENCE over
inventory issues (backorder / delayed_release). Rationale: an order under account
review or with a discontinued product cannot be released regardless of stock.
- The inactive-product rung (escalate_product_master) sits ABOVE the account-review
rung: when both an inactive product and a review_required account are present, the
next_action is escalate_product_master (product-master risk is the primary blocker).
- shipping_quote: compute per §1.5, store
{zone_distance, service_days, total_cost_usd}.
- SKU exception lists:
shortage_skus, inactive_skus, low_stock_skus — each sorted
ascending, only the SKUs that qualify (empty list [] if none).
2.2 Summary
order_count: number of records.
decision_counts: object keyed by the 5 final_decision values → integer counts
(include all 5 keys even if 0).
total_shipping_cost_usd: sum of every record's shipping_quote.total_cost_usd, 2dp.
blocked_order_ids: orders with final_decision == reject_hold, sorted.
manual_review_order_ids: final_decision == manual_review, sorted.
backorder_order_ids: final_decision == backorder, sorted.
inactive_sku_order_ids: orders whose inventory_status is inactive_sku OR
inactive_and_shortage (i.e. any inactive SKU present), sorted.
2.3 Common misjudgments (Family A)
- Forgetting that
review_required/fraud_watch/credit_watch BLOCK release even when
stock is plentiful → wrongly shipping instead of manual_review.
- Putting a low_stock SKU into shortage_skus (it must have eff >= quantity to be low_stock).
- Computing shipping weight with only some lines — use ALL lines, and multiply
weight_lb by quantity per line.
- Letting a shortage override an inactive product or account review — inventory NEVER
overrides account/product manual-review conditions in this family.
3. Task Family B — Kit Build Replenishment (BOM expansion)
Inputs: a production memo with target_builds[] (bom_id, target_build_quantity,
target_build_date) at a planning warehouse. Output: task_id, plan_date,
kit_targets[], component_plan[], transfer_requests[], purchase_requisitions[],
excluded_components[], summary{}.
3.1 BOM expansion → total_required
- For each build: fetch
/boms/<bom_id>. For each component {sku, quantity_per_kit}:
component_required = quantity_per_kit * target_build_quantity.
- A SKU appearing in multiple BOMs: sum its requirements across all builds.
total_required[sku] = summed requirement.
- Each SKU's relevant build date = the EARLIEST
target_build_date among the BOMs
that contain it (stock must be on hand by the first build that needs it). Use this date
as the need deadline for timely-PO and needed_by.
3.2 Per-component plan (one row per SKU, sorted by sku ascending)
For each SKU in total_required:
target_effective_available = effective_available at the planning warehouse (§1.1).
timely_po_qty = sum of eligible PO qty (§1.6) at the planning warehouse with
eta <= earliest_build_date.
stock_gap = total_required - target_effective_available
- Determine exclusion / final_action in this priority:
| Priority |
Condition |
exclusion_reason |
final_action |
| 1 |
target_effective_available > product.overstock_threshold |
target_overstock |
overstock_excluded |
| 2 |
stock_gap > 0 AND timely_po_qty >= stock_gap |
timely_po_covers_gap |
timely_po_covered |
| 3 |
stock_gap <= 0 (stock already covers, not overstock) |
stocked_no_gap |
no_action_stocked |
| 4 |
stock_gap - timely_po_qty > 0 (real remaining gap) |
none |
(transfer/purchase, see 3.3) |
coverage_po_ids: for the timely case, the eligible PO ids (sorted). Empty list
otherwise (including overstock — overstock exclusion has no coverage POs).
- Report
transfer_qty = 0 and purchase_requisition_qty = 0 for all excluded rows.
3.3 Filling a real remaining gap (exclusion_reason == none)
remaining_gap = stock_gap - timely_po_qty # > 0
spare[other_wh] = effective_available(other_wh) - product.safety_stock # only if > 0
- Draw transfers from OTHER warehouses' positive spare. Take from the warehouse with the
LARGEST spare first; if one warehouse's spare already covers
remaining_gap, take only
what's needed from that single warehouse; otherwise exhaust the largest, then the next,
until the gap is filled or all spare is used.
transfer_qty = min(remaining_gap, sum_of_all_positive_spare)
purchase_requisition_qty = remaining_gap - transfer_qty
final_action: if purchase_requisition_qty > 0 → purchase_required;
elif transfer_qty > 0 → transfer_only.
- Each transfer draw becomes a
transfer_requests row: {sku, from_warehouse_id, to_warehouse_id=<planning warehouse>, quantity, needed_by=<earliest build date>}.
3.4 transfer_requests (list)
Sorted by: sku ascending, then quantity descending, then from_warehouse_id ascending.
So within one SKU, the biggest transfer batch is listed first.
3.5 purchase_requisitions (list)
One row per SKU that needs purchasing (purchase_requisition_qty > 0), sorted by sku ascending.
supplier_id: from product.supplier_id.
warehouse_id: the planning warehouse.
quantity: purchase_requisition_qty.
needed_by: earliest build date for that SKU.
unit_cost: product.unit_cost (2dp).
extended_cost = quantity * unit_cost (2dp).
3.6 excluded_components (list)
One row per SKU whose exclusion_reason != none, sorted by sku ascending.
reason: target_overstock | timely_po_covers_gap | stocked_no_gap (matches exclusion_reason).
supporting_po_ids: for timely_po_covers_gap → the coverage PO ids (sorted); for
target_overstock and stocked_no_gap → empty list [].
3.7 kit_targets (list)
Sorted by bom_id ascending. Each: {bom_id, kit_name (from BOM .name), warehouse_id (planning warehouse), build_quantity, build_date}.
3.8 summary
component_count: number of rows in component_plan.
total_purchase_units: sum of purchase_requisition_qty.
total_purchase_cost: sum of extended_cost (2dp).
total_transfer_units: sum of transfer_qty across all components.
timely_po_covered_units: sum of timely_po_qty across all components (the full
eligible PO quantities, not capped to gaps).
3.9 plan_date
Use the memo's issued_at date portion (YYYY-MM-DD), or an as_of_date/planning date if
the memo provides one.
3.10 Common misjudgments (Family B)
- Counting
received POs as timely coverage (they're already in on_hand → double count).
- Using the LATEST build date for timeliness on a multi-BOM component — use the EARLIEST.
- Forgetting overstock exclusion takes precedence over stocked_no-gap when
effective > overstock_threshold AND effective >= required.
- Drawing transfer from a warehouse whose spare is negative (below its own safety stock).
- Splitting a transfer across extra warehouses when one warehouse's spare already covers
the gap (take from the single largest-spare source).
- Capping timely_po_qty to the gap — report the full eligible PO quantity.
4. Task Family C — Supplier Incident Scorecard
Inputs: a scorecard request with an incident_date_filter (field=open_date,
start/end inclusive), analysis_date, duration/percentage/recommendation rules.
Output: analysis_window, summary, supplier_scorecard[], top_escalation_suppliers,
highest_cost_supplier_id, highest_share_supplier_id.
4.1 Filter the incident population
- Filter
/incidents by open_date within [start_date, end_date] INCLUSIVE.
- The filter field is
open_date (NOT close_date, NOT a created_at). Confirmed by the
request's incident_date_filter.field.
total_filtered = len(filtered).
4.2 Per-supplier rollup (only suppliers with >= 1 filtered incident)
For each such supplier (sorted by supplier_id ascending):
incident_count: count of that supplier's filtered incidents.
incident_percentage: incident_count / total_filtered * 100, rounded to 1 decimal.
total_resolution_cost: sum of resolution_cost, 2dp.
rma_count: filtered incidents with incident_type == RMA.
work_order_count: filtered incidents with incident_type == WORK_ORDER.
open_incident_count: filtered incidents with status == open.
severe_incident_count: filtered incidents with severity in {high, critical}.
avg_duration_days:
- closed incident → calendar days from
open_date to close_date.
- open incident (close_date null) → calendar days from
open_date to analysis_date.
- average over the supplier's filtered incidents, rounded to 2 decimals.
supplier_name: from /suppliers.
recommendation_code: see §4.3.
4.3 Recommendation code (precedence: ESCALATE > PROCESS_REVIEW > WATCHLIST > MONITOR)
Evaluate top-down; assign the first matching code.
- ESCALATE_SUPPLIER — supplier
quality_status == quality_hold AND incident_count >= 3,
OR supplier has any critical-severity RMA incident (incident_type==RMA AND
severity==critical), 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.
NOTE: thresholds (3, 15000, 12000, 4, 2) and the severe set {high,critical} come from the
request's recommendation_policy. If a future request changes them, read and apply the
request's stated values, not these constants.
4.4 Derived outputs
top_escalation_suppliers: supplier_id list where recommendation_code ==
ESCALATE_SUPPLIER, ordered by incident_count DESC, then total_resolution_cost DESC,
then supplier_id ASC.
highest_cost_supplier_id: supplier_id with the max total_resolution_cost (ties →
supplier_id ascending).
highest_share_supplier_id: supplier_id with the max incident_count (ties → max cost,
then supplier_id ascending).
4.5 analysis_window
{start_date, end_date, analysis_date} from the request (YYYY-MM-DD).
4.6 summary
filtered_incident_count: total_filtered.
supplier_count: number of suppliers with >= 1 filtered incident.
total_resolution_cost: sum across ALL filtered incidents, 2dp.
overall_rma_count: RMA incidents in the filtered population.
overall_work_order_count: WORK_ORDER incidents in the filtered population.
4.7 Common misjudgments (Family C)
- Filtering on close_date or created_at instead of open_date.
- Computing open-incident duration to today instead of to
analysis_date.
- Wrong precedence: applying WATCHLIST before ESCALATE/SUPPLIER checks, or checking
PROCESS_REVIEW before ESCALATE. The policy precedence is ESCALATE > PROCESS_REVIEW >
WATCHLIST > MONITOR — evaluate strictly in that order.
- Forgetting that quality_hold + >=3 incidents triggers ESCALATE even without critical
RMAs or cost thresholds.
- Rounding percentage to 2 decimals (use 1) or duration to 0/1 (use 2).
5. Task Family D — Mixed-Warehouse Allocation / Transfer Wave
Inputs: an allocation memo naming a wave (e.g. TRAIN_TRANSFER_B). Output:
wave_id, line_actions[], transfer_requests[], blocked_orders[], order_rollup[],
summary{}.
5.1 Per-line action (sorted by order_id, then line_id ascending)
For each order in the wave, for each line:
- Account-level gate (applies to the WHOLE order):
account_status == blocked → action manual_review, primary_reason account_blocked,
all quantities 0.
account_status == review_required → manual_review, account_review_required, 0s.
account_status == active and risk_flag == fraud_watch → manual_review,
fraud_watch, 0s.
- (active + credit_watch: not exercised in train data. Treat as a manual_review risk
gate consistent with Family A — reason: account_review_required — but verify if a
test task exposes it.)
These account gates apply to EVERY line of the order (the whole order is stopped).
- Product gate (per line): if
product.active == false → manual_review,
inactive_product, 0s. (Only reached when the account is clean.)
- Inventory decision (account & product clean):
eff = effective_available at the line's requested_warehouse for the sku.
usable = max(0, eff).
- if
eff >= quantity: action ship, ship_quantity=quantity, primary_reason none.
- else (shortfall):
gap = quantity - usable. Look for ONE other warehouse whose
spare (effective_available - safety_stock, §1.2) is >= gap. If found →
action transfer: ship_quantity=usable, transfer_from=that warehouse,
transfer_quantity=gap, backorder=0. Choose the warehouse with the LARGEST spare
(ties → warehouse_id alphabetical). Only ONE source warehouse per line ("choose one
source warehouse").
- if no single warehouse can cover the full gap → action
backorder:
ship_quantity=usable (ship what's available), backorder_quantity=quantity - usable,
primary_reason insufficient_effective_stock, transfer 0.
(Symmetric with transfer: the usable requested-warehouse stock ships; the uncovered
remainder is backordered. The explicit "leave usable as ship_quantity" instruction in
the memo covers transfer; apply the same usable-ship convention to backorder.)
requested_effective_available field: report the raw eff (may be negative).
5.2 transfer_requests (list)
One row per transfer line, with {order_id, line_id, sku, from_warehouse, to_warehouse, quantity}. Sorted by order_id, then line_id ascending.
5.3 blocked_orders (list of order_id strings)
Orders stopped at the ACCOUNT or customer-RISK level (blocked / review_required /
fraud_watch) — NOT orders stopped only by inactive-product lines. Sorted ascending.
Rationale (template meaning): "Orders stopped at account or customer-risk level, not
line-only product reviews."
5.4 order_rollup (list, sorted by order_id)
Outcome per order (based on the set of its line actions):
- all lines
ship → ready_to_ship
- all lines
transfer → needs_transfer
- all lines
backorder → has_backorder
- all lines
manual_review → manual_review
- any mix of different actions →
mixed_actions
5.5 summary (all integers)
total_orders, total_lines, ship_lines, transfer_lines, backorder_lines,
manual_review_lines (counts of lines by action), blocked_orders (count of blocked
order ids), transfer_units (sum of transfer_quantity), backorder_units (sum of
backorder_quantity).
5.6 Common misjudgments (Family D)
- Classifying an inactive-product line as
ship/backorder because stock exists —
inactive products are ALWAYS manual_review.
- Putting inactive-product-only orders into
blocked_orders — they are line-level product
reviews, not account/risk stops. Only account/risk-stopped orders belong there.
- Allowing a transfer from a warehouse whose spare is below zero (ignoring its own safety
stock) — spare must be positive AND cover the full gap.
- Splitting one line's shortfall across multiple source warehouses — only ONE source per
line; if no single source covers the full gap, the line is backorder.
- Forgetting that account-level gates (blocked/review/fraud) stop ALL lines of an order,
including lines that would otherwise ship.
- Reporting
requested_effective_available as 0 when it is negative — report the raw
computed value.
6. Task Family E — Procurement Quality-Hold Review
Inputs: a memo with analysis_window{start,end}, target_supplier_ids[], policy.
Output: analysis_window, supplier_decisions[], held_po_ids, release_supplier_ids,
summary{}.
6.1 Per-supplier decision (sorted by supplier_id ascending)
For each target supplier, fetch its incidents filtered by open_date within
[window.start, window.end] inclusive, and its POs:
recent_incident_count: count of filtered incidents.
recent_rma_count: filtered incidents with incident_type == RMA.
severe_or_critical_count: filtered incidents with severity in {high, critical}.
open_incident_count: filtered incidents with status == open.
affected_skus: sorted unique SKUs from the filtered incidents.
sample_incident_ids: sorted incident_ids, capped at 5 (the first 5 when sorted
ascending).
quality_status: from /suppliers.
held_po_ids: the supplier's open/confirmed PO ids that are HELD (see §6.3), sorted.
6.2 Decision policy (inferred — apply top-down)
- freeze_new_replenishment —
quality_status == quality_hold. (The hold itself freezes
all replenishment.) Hold ALL open/confirmed POs.
- buyer_review_required — NOT quality_hold, but elevated risk: has at least one
CRITICAL-severity incident in the window, OR
open_incident_count >= 1. Hold ALL
open/confirmed POs pending buyer review.
- monitor_only — otherwise (no critical incident and no open incident). No PO holds;
release proceeds.
Result: a clean three-way split. release_supplier_ids = suppliers whose decision is
monitor_only (sorted).
NOTE: this policy is reverse-engineered. If a test memo states explicit thresholds, use
those instead. The signal that matters most: quality_hold → freeze; critical or open
recent incident → buyer_review; otherwise monitor. Severe-but-closed (high, closed)
incidents alone trend toward monitor, not buyer_review, in this framing.
6.3 held_po_ids
- For
freeze_new_replenishment and buyer_review_required suppliers: hold ALL of the
supplier's open/confirmed POs (every PO with status in {open, confirmed},
regardless of SKU, regardless of eta/warehouse). Sorted unique.
(Rationale: both decisions halt automatic PO release — freeze stops everything, buyer
review holds pending the buyer's decision. Only monitor_only releases.)
Alternative reading: only freeze holds POs, buyer_review holds none. If a test task's
numbers seem off, re-evaluate whether buyer_review holds. The conservative default here
is to hold for both freeze and buyer_review.
- For
monitor_only: held_po_ids = [].
6.4 held_po_ids (top-level)
Sorted unique union of every supplier's held_po_ids (the distinct set of all held PO ids).
6.5 summary
suppliers_reviewed: count of target suppliers.
freeze_count, buyer_review_count, monitor_count: counts by decision.
held_po_count: length of the top-level held_po_ids list.
total_recent_incidents: sum of recent_incident_count across reviewed suppliers.
6.6 analysis_window
{start, end} (YYYY-MM-DD) from the memo.
6.7 Common misjudgments (Family E)
- Filtering incidents by close_date or by a different field instead of open_date.
- Counting received/cancelled POs as held (only open/confirmed are held).
- Holding POs for monitor_only suppliers (they are released).
- Not capping sample_incident_ids at 5.
- Using POs for ALL suppliers in top-level held_po_ids instead of only reviewed suppliers'
held POs.
7. Cross-Cutting Reusable SOPs
Read the answer_template FIRST. It defines exact key names, allowed enum values,
ordering, and types. Match them verbatim — a valid enum string spelled differently or a
missing key fails. Re-read it before emitting JSON.
Pull + cache core entities (§0) before per-task logic. Index by natural key.
Effective stock is always on_hand - reserved - quarantined. Never use on_hand
alone. Never ignore quarantined. Report raw (can be negative); use max(0,·) for shippable.
Check product.active and customer account_status/risk_flag before inventory.
Account and product gates precede inventory logic in Families A and D.
Spare = effective - safety_stock; only positive spare donates. A warehouse below
its own safety stock cannot be a transfer source.
POs: only open/confirmed are future supply; received is already in on_hand.
Timely = open/confirmed + same warehouse + eta <= deadline.
One source warehouse per transfer line (Family D). All eligible sources for Family B
can combine (multiple transfer_requests rows), drawing largest-spare first.
Incident filtering is on open_date, inclusive of both endpoints.
Sort everything as the template dictates. Default ascending by the natural id
(order_id / sku / supplier_id / bom_id). For multi-key sort, follow the template's
stated tie-breakers exactly.
Round: currency 2dp, percentage 1dp, duration 2dp. Quantities/counts are integers.
Emit ONLY the JSON object (no narrative, no markdown fences) unless the prompt
explicitly allows text. Match the template's top-level key set exactly.
Sanity-check internally: sums in the summary must reconcile with the line/record
lists (e.g. total_shipping_cost = sum of quotes; decision_counts sum to order_count;
ship+transfer+backorder+manual_review lines = total_lines). Reconciliation is the only
correctness signal available (no gold/judge).
Dates: parse as YYYY-MM-DD. Calendar-day differences use date subtraction
(later - earlier).days. Inclusive range means start <= d <= end.
Wave filtering: GET /orders?wave=<WAVE_ID> returns exactly that wave's orders.
For expedite/allocation tasks, this is the order set.
8. Quick Reference — Decision Precedence Summaries
Family A (expedite) final_decision ladder:
account_blocked→reject_hold ▸ inactive_sku→manual_review(escalate) ▸
fraud_watch→manual_review(hold) ▸ credit_watch→manual_review(hold) ▸
review_required→manual_review(account_review) ▸ shortage→backorder ▸
low_stock→delayed_release ▸ ready→ship_now.
Family B (BOM) component resolution:
overstock(exclusion) ▸ timely_po_covers(exclusion) ▸ stocked_no_gap(exclusion) ▸
[remaining gap] → transfer from largest spare, then purchase the rest.
Family C (scorecard) recommendation:
ESCALATE(buyback) ▸ PROCESS_REVIEW ▸ WATCHLIST ▸ MONITOR.
Family D (allocation) line action:
account_blocked ▸ review_required ▸ fraud_watch ▸ inactive_product ▸
ship(eff>=qty) ▸ transfer(one source covers gap) ▸ backorder(no source covers gap).
Family E (procurement):
quality_hold→freeze ▸ critical-or-open incident→buyer_review ▸ else→monitor.
9. Field-Shapes Cheat Sheet (per family)
A records: {order_id, inventory_status, customer_exception, final_decision, next_action, shortage_skus[], inactive_skus[], low_stock_skus[], shipping_quote{zone_distance,service_days,total_cost_usd}}
summary{order_count, decision_counts{...}, total_shipping_cost_usd, blocked_order_ids[], manual_review_order_ids[], backorder_order_ids[], inactive_sku_order_ids[]}.
B: {task_id, plan_date, kit_targets[], component_plan[], transfer_requests[], purchase_requisitions[], excluded_components[], summary{}}.
component_plan row: {sku, total_required, target_effective_available, timely_po_qty, transfer_qty, purchase_requisition_qty, final_action, coverage_po_ids[], exclusion_reason}.
C: {analysis_window, summary{}, supplier_scorecard[], top_escalation_suppliers[], highest_cost_supplier_id, highest_share_supplier_id}.
D: {wave_id, line_actions[], transfer_requests[], blocked_orders[], order_rollup[], summary{}}.
line_action row: {order_id, line_id, sku, requested_warehouse, requested_effective_available, action, ship_quantity, transfer_from(nullable), transfer_quantity, backorder_quantity, primary_reason}.
E: {analysis_window, supplier_decisions[], held_po_ids[], release_supplier_ids[], summary{}}.
supplier_decision row: {supplier_id, supplier_name, quality_status, recent_incident_count, recent_rma_count, severe_or_critical_count, open_incident_count, affected_skus[], sample_incident_ids[], decision, held_po_ids[]}.
End of skill. Apply the rules; reconcile summary totals against record lists; emit only
the JSON object matching the template.
1---2name: self-attempt-01-113description: Northwind ERP Fulfillment — Solver Skill4---5# Northwind ERP Fulfillment — Solver Skill67Executable experience for solving Northwind Components ERP fulfillment tasks against the8shared remote API. Five recurring task families are covered. Apply the rules below at9test time; they were reverse-engineered from the live API and internal business10consistency (there are no gold answers to check against).1112---1314## 0. Environment & API Reference1516**Base URL:** the URL given in the task's `environment_access.md` (a remote host, e.g.17`<remote-env-url>`). Tasks reference `http://127.0.0.1:8007` in prose, but the18REAL entrypoint is the remote base URL in `environment_access.md`. Always use that.1920**Calling notes (critical):**21- The server speaks HTTP/1.0 and closes each connection. Use a fresh connection per call:22 `curl -sS --max-time 30 '<url>'` or Python `urllib.request` with `timeout=30`.23- All endpoints are GET. Parameters are query-string.24- Do NOT try to read `env/`, `server.py`, data files, or start a local server. Only the API.2526**Endpoints:**27| Endpoint | Returns | Notes |28|---|---|---|29| `GET /health` | manifest (record counts, seed) + status | sanity check first |30| `GET /products` , `/products/<sku>` | SKU master | `active`, `safety_stock`, `overstock_threshold`, `unit_cost`, `weight_lb`, `supplier_id`, `category` |31| `GET /customers` , `/customers/<id>` | account/risk | `account_status`∈{active,blocked,review_required}, `risk_flag`∈{none,fraud_watch,credit_watch}, `tier` |32| `GET /warehouses` | `warehouse_id`∈{WH_NORTH,WH_CENTRAL,WH_WEST}, `zip`, `region` | 3 warehouses |33| `GET /inventory?warehouse_id=&sku=` | stock row | `on_hand`, `reserved`, `quarantined`, `last_count_date` |34| `GET /purchase_orders?supplier_id=&sku=&status=` | POs | `status`∈{open,confirmed,received,cancelled}, `eta`, `quantity`, `warehouse_id`, `supplier_id` |35| `GET /orders?wave=&required_date=&customer_id=` , `/orders/<id>` | orders | `wave`, `warehouse_id`, `customer_id`, `destination_zip`, `shipping_speed`∈{ground,two_day,overnight}, `lines[]`{line_id,sku,quantity,unit_price} |36| `GET /shipping/quote?warehouse_id=&destination_zip=&weight_lb=&speed=` | quote | `zone_distance`, `service_days`, `total_cost` (USD, 2dp), also `base_rate`,`fuel_surcharge_rate` |37| `GET /incidents?start=&end=&supplier_id=&sku=&incident_type=&status=` | incidents | `open_date`,`close_date`(null if open),`incident_type`∈{RMA,WORK_ORDER},`severity`∈{low,medium,high,critical},`status`∈{open,closed},`resolution_cost`,`supplier_id`,`sku`,`warehouse_id` |38| `GET /suppliers` | suppliers | `supplier_id`, `name`, `quality_status`∈{approved,watch,quality_hold}, `region` |39| `GET /boms` , `/boms/<id>` | BOMs | `bom_id`, `name`, `warehouse_id`, `target_date`, `components[]`{sku, quantity_per_kit} |4041**Data scale (manifest seed 7007):** 54 products, 40 customers, 3 warehouses, 9 BOMs,4292 POs, 88 orders, 162 inventory rows, 212 incidents, 12 suppliers.4344**SOP: pull-and-cache.** At the start of any task, fetch `/products`, `/customers`,45`/inventory`, `/purchase_orders`, `/suppliers`, `/boms` once and index them in memory46(dict by sku / customer_id / (warehouse_id,sku) / supplier_id / bom_id). Then fetch only47the task-specific orders. This minimizes round-trips (server closes connections).4849---5051## 1. Core Business Primitives (used by ALL task families)5253### 1.1 Effective available stock54```55effective_available = on_hand - reserved - quarantined56```57- `on_hand`, `reserved`, `quarantined` come from `/inventory` for a (warehouse_id, sku).58- Reserved and quarantined units are NOT freely shippable. Always subtract both.59- `effective_available` can be NEGATIVE (e.g. on_hand=0, quarantined=1 → -1). Treat60 negative as 0 usable when computing shippable quantity, but REPORT the raw value where a61 field asks for "effective available" / "requested_effective_available".6263### 1.2 Safety stock & overstock threshold (from /products)64- `safety_stock`: protected buffer. A warehouse's **spare** (donatable) stock =65 `effective_available - safety_stock`. Only POSITIVE spare can be transferred/donated.66- `overstock_threshold`: if a warehouse's effective_available EXCEEDS this, the SKU is67 OVERSTOCKED there → do NOT add more stock (exclusion). Used in BOM replenishment.6869### 1.3 Product active flag70- `active: false` means the SKU is discontinued/inactive. Inactive SKUs cannot be71 auto-shipped or replenished regardless of on-hand quantity — they force a manual review72 (escalate to product master) in dispatch tasks, and block transfer/ship in allocation.73- Known inactive SKUs in this dataset: NW-1007, NW-1019, NW-1033, NW-1048 (4 total). Always74 check `active` per SKU; do not assume.7576### 1.4 Customer/account exception precedence77Derived from `account_status` + `risk_flag`:78| account_status | risk_flag | customer_exception |79|---|---|---|80| blocked | (any) | **account_blocked** |81| review_required | (any) | **review_required** |82| active | fraud_watch | **fraud_watch** |83| active | credit_watch | **credit_watch** |84| active | none | **none** |8586Precedence (most severe first): account_blocked > fraud_watch > credit_watch > review_required > none.87`account_status` drives blocked/review; `risk_flag` only matters when `account_status == active`8889### 1.5 Shipping quote computation90```91weight_lb = sum over ALL order lines of (product.weight_lb * line.quantity)92quote = GET /shipping/quote?warehouse_id=<order.warehouse_id>93 &destination_zip=<order.destination_zip>94 &weight_lb=<computed weight>95 &speed=<order.shipping_speed>96```97- Use the ORDER's own `shipping_speed` (ground / two_day / overnight), even if the order98 is not going to be released. Quotes are required for every order in the queue/memo.99- The API returns `total_cost` (already 2dp), `service_days` (int), `zone_distance` (int).100- Sum `total_cost` across all records for the summary `total_shipping_cost_usd`.101102### 1.6 Timely purchase-order coverage103A PO is **eligible / timely** for a build/need at warehouse W with deadline D iff:104- `status` ∈ {open, confirmed} (NOT received, NOT cancelled)105- `warehouse_id == W` (same warehouse as the build)106- `eta <= D` (arrival on or before the build/need date)107- `received` POs are EXCLUDED — their stock is already inside `on_hand` (would double-count).108- `timely_po_qty` = sum of `quantity` over all eligible POs for that SKU at that warehouse109 (report the full eligible PO quantity, do NOT cap to the gap).110111### 1.7 Rounding & formatting112- Currency (USD): round to 2 decimals everywhere (`round(x, 2)`).113- Percentages (incident share): round to 1 decimal.114- Durations (days): round to 2 decimals.115- Counts/quantities: integers.116- Lists of IDs/SKUs: sorted ascending (strings sort lexicographically — "NW-1003" < "NW-1011" < "NW-1049").117- Always keep records sorted by the field the template names (usually order_id / sku / supplier_id ascending).118119---120121## 2. Task Family A — Expedite Queue Dispatch (wave decision)122123**Inputs:** a queue memo listing `order_ids` + `wave_id`. **Output:** records (per order) +124summary. See the answer_template for exact keys: `wave_id`, `records[]`, `summary{}`.125126### 2.1 Per-order computation127For each order (sorted ascending by order_id):1281. Fetch `/orders/<order_id>`. Note `customer_id`, `warehouse_id`, `destination_zip`,129 `shipping_speed`, `lines[]`.1302. **Customer exception:** look up customer, apply §1.4 table.1313. **Per-line classification** (for each line: sku, quantity):132 - product = `/products/<sku>`; inventory = `/inventory?warehouse_id=<wh>&sku=<sku>`133 - `eff = on_hand - reserved - quarantined`134 - shortage line if `eff < quantity` → add sku to `shortage_skus`135 - inactive line if `product.active == false` → add sku to `inactive_skus`136 - low_stock line if `quantity <= eff < product.safety_stock` (can fill but below137 safety buffer) → add sku to `low_stock_skus`138 (a shortage line is NOT also low_stock; low_stock requires eff >= quantity)1394. **inventory_status** (order rollup, in this priority):140 - has inactive SKU AND has shortage → `inactive_and_shortage`141 - has inactive SKU (no shortage) → `inactive_sku`142 - has shortage (no inactive) → `shortage`143 - has any low_stock (no inactive, no shortage) → `low_stock`144 - else → `ready`1455. **final_decision + next_action** — apply the PRECEDENCE LADDER (first match wins):146 | Priority | Condition | final_decision | next_action |147 |---|---|---|---|148 | 1 | customer_exception == account_blocked | `reject_hold` | `hold_credit_or_fraud` |149 | 2 | inventory_status in {inactive_sku, inactive_and_shortage} | `manual_review` | `escalate_product_master` |150 | 3 | customer_exception == fraud_watch | `manual_review` | `hold_credit_or_fraud` |151 | 4 | customer_exception == credit_watch | `manual_review` | `hold_credit_or_fraud` |152 | 5 | customer_exception == review_required | `manual_review` | `send_account_review` |153 | 6 | inventory_status == shortage | `backorder` | `create_backorder` |154 | 7 | inventory_status == low_stock | `delayed_release` | `delay_and_monitor` |155 | 8 | inventory_status == ready | `ship_now` | `release_to_pick` |156 - Account/risk/product issues (manual_review / reject_hold) take PRECEDENCE over157 inventory issues (backorder / delayed_release). Rationale: an order under account158 review or with a discontinued product cannot be released regardless of stock.159 - The inactive-product rung (escalate_product_master) sits ABOVE the account-review160 rung: when both an inactive product and a review_required account are present, the161 next_action is escalate_product_master (product-master risk is the primary blocker).1626. **shipping_quote:** compute per §1.5, store `{zone_distance, service_days, total_cost_usd}`.1637. SKU exception lists: `shortage_skus`, `inactive_skus`, `low_stock_skus` — each sorted164 ascending, only the SKUs that qualify (empty list `[]` if none).165166### 2.2 Summary167- `order_count`: number of records.168- `decision_counts`: object keyed by the 5 final_decision values → integer counts169 (include all 5 keys even if 0).170- `total_shipping_cost_usd`: sum of every record's `shipping_quote.total_cost_usd`, 2dp.171- `blocked_order_ids`: orders with final_decision == reject_hold, sorted.172- `manual_review_order_ids`: final_decision == manual_review, sorted.173- `backorder_order_ids`: final_decision == backorder, sorted.174- `inactive_sku_order_ids`: orders whose inventory_status is inactive_sku OR175 inactive_and_shortage (i.e. any inactive SKU present), sorted.176177### 2.3 Common misjudgments (Family A)178- Forgetting that `review_required`/`fraud_watch`/`credit_watch` BLOCK release even when179 stock is plentiful → wrongly shipping instead of manual_review.180- Putting a low_stock SKU into shortage_skus (it must have eff >= quantity to be low_stock).181- Computing shipping weight with only some lines — use ALL lines, and multiply182 weight_lb by quantity per line.183- Letting a shortage override an inactive product or account review — inventory NEVER184 overrides account/product manual-review conditions in this family.185186---187188## 3. Task Family B — Kit Build Replenishment (BOM expansion)189190**Inputs:** a production memo with `target_builds[]` (bom_id, target_build_quantity,191target_build_date) at a planning warehouse. **Output:** `task_id`, `plan_date`,192`kit_targets[]`, `component_plan[]`, `transfer_requests[]`, `purchase_requisitions[]`,193`excluded_components[]`, `summary{}`.194195### 3.1 BOM expansion → total_required196- For each build: fetch `/boms/<bom_id>`. For each component `{sku, quantity_per_kit}`:197 `component_required = quantity_per_kit * target_build_quantity`.198- A SKU appearing in multiple BOMs: **sum** its requirements across all builds.199- `total_required[sku]` = summed requirement.200- Each SKU's relevant **build date** = the EARLIEST `target_build_date` among the BOMs201 that contain it (stock must be on hand by the first build that needs it). Use this date202 as the need deadline for timely-PO and needed_by.203204### 3.2 Per-component plan (one row per SKU, sorted by sku ascending)205For each SKU in total_required:2061. `target_effective_available` = effective_available at the planning warehouse (§1.1).2072. `timely_po_qty` = sum of eligible PO qty (§1.6) at the planning warehouse with208 `eta <= earliest_build_date`.2093. `stock_gap = total_required - target_effective_available`2104. Determine **exclusion / final_action** in this priority:211 | Priority | Condition | exclusion_reason | final_action |212 |---|---|---|---|213 | 1 | target_effective_available > product.overstock_threshold | `target_overstock` | `overstock_excluded` |214 | 2 | stock_gap > 0 AND timely_po_qty >= stock_gap | `timely_po_covers_gap` | `timely_po_covered` |215 | 3 | stock_gap <= 0 (stock already covers, not overstock) | `stocked_no_gap` | `no_action_stocked` |216 | 4 | stock_gap - timely_po_qty > 0 (real remaining gap) | `none` | (transfer/purchase, see 3.3) |217 - `coverage_po_ids`: for the timely case, the eligible PO ids (sorted). Empty list218 otherwise (including overstock — overstock exclusion has no coverage POs).219 - Report `transfer_qty = 0` and `purchase_requisition_qty = 0` for all excluded rows.220221### 3.3 Filling a real remaining gap (exclusion_reason == none)222```223remaining_gap = stock_gap - timely_po_qty # > 0224spare[other_wh] = effective_available(other_wh) - product.safety_stock # only if > 0225```226- Draw transfers from OTHER warehouses' positive spare. Take from the warehouse with the227 LARGEST spare first; if one warehouse's spare already covers `remaining_gap`, take only228 what's needed from that single warehouse; otherwise exhaust the largest, then the next,229 until the gap is filled or all spare is used.230- `transfer_qty = min(remaining_gap, sum_of_all_positive_spare)`231- `purchase_requisition_qty = remaining_gap - transfer_qty`232- `final_action`: if `purchase_requisition_qty > 0` → `purchase_required`;233 elif `transfer_qty > 0` → `transfer_only`.234- Each transfer draw becomes a `transfer_requests` row: `{sku, from_warehouse_id,235 to_warehouse_id=<planning warehouse>, quantity, needed_by=<earliest build date>}`.236237### 3.4 transfer_requests (list)238Sorted by: sku ascending, then quantity descending, then from_warehouse_id ascending.239So within one SKU, the biggest transfer batch is listed first.240241### 3.5 purchase_requisitions (list)242One row per SKU that needs purchasing (purchase_requisition_qty > 0), sorted by sku ascending.243- `supplier_id`: from `product.supplier_id`.244- `warehouse_id`: the planning warehouse.245- `quantity`: purchase_requisition_qty.246- `needed_by`: earliest build date for that SKU.247- `unit_cost`: `product.unit_cost` (2dp).248- `extended_cost = quantity * unit_cost` (2dp).249250### 3.6 excluded_components (list)251One row per SKU whose exclusion_reason != none, sorted by sku ascending.252- `reason`: target_overstock | timely_po_covers_gap | stocked_no_gap (matches exclusion_reason).253- `supporting_po_ids`: for timely_po_covers_gap → the coverage PO ids (sorted); for254 target_overstock and stocked_no_gap → empty list `[]`.255256### 3.7 kit_targets (list)257Sorted by bom_id ascending. Each: `{bom_id, kit_name (from BOM .name), warehouse_id258(planning warehouse), build_quantity, build_date}`.259260### 3.8 summary261- `component_count`: number of rows in component_plan.262- `total_purchase_units`: sum of purchase_requisition_qty.263- `total_purchase_cost`: sum of extended_cost (2dp).264- `total_transfer_units`: sum of transfer_qty across all components.265- `timely_po_covered_units`: sum of `timely_po_qty` across all components (the full266 eligible PO quantities, not capped to gaps).267268### 3.9 plan_date269Use the memo's `issued_at` date portion (YYYY-MM-DD), or an `as_of_date`/planning date if270the memo provides one.271272### 3.10 Common misjudgments (Family B)273- Counting `received` POs as timely coverage (they're already in on_hand → double count).274- Using the LATEST build date for timeliness on a multi-BOM component — use the EARLIEST.275- Forgetting overstock exclusion takes precedence over stocked_no-gap when276 effective > overstock_threshold AND effective >= required.277- Drawing transfer from a warehouse whose spare is negative (below its own safety stock).278- Splitting a transfer across extra warehouses when one warehouse's spare already covers279 the gap (take from the single largest-spare source).280- Capping timely_po_qty to the gap — report the full eligible PO quantity.281282---283284## 4. Task Family C — Supplier Incident Scorecard285286**Inputs:** a scorecard request with an `incident_date_filter` (field=open_date,287start/end inclusive), `analysis_date`, duration/percentage/recommendation rules.288**Output:** `analysis_window`, `summary`, `supplier_scorecard[]`, `top_escalation_suppliers`,289`highest_cost_supplier_id`, `highest_share_supplier_id`.290291### 4.1 Filter the incident population292- Filter `/incidents` by `open_date` within [start_date, end_date] INCLUSIVE.293- The filter field is `open_date` (NOT close_date, NOT a created_at). Confirmed by the294 request's `incident_date_filter.field`.295- `total_filtered = len(filtered)`.296297### 4.2 Per-supplier rollup (only suppliers with >= 1 filtered incident)298For each such supplier (sorted by supplier_id ascending):299- `incident_count`: count of that supplier's filtered incidents.300- `incident_percentage`: `incident_count / total_filtered * 100`, rounded to 1 decimal.301- `total_resolution_cost`: sum of `resolution_cost`, 2dp.302- `rma_count`: filtered incidents with `incident_type == RMA`.303- `work_order_count`: filtered incidents with `incident_type == WORK_ORDER`.304- `open_incident_count`: filtered incidents with `status == open`.305- `severe_incident_count`: filtered incidents with `severity in {high, critical}`.306- `avg_duration_days`:307 - closed incident → calendar days from `open_date` to `close_date`.308 - open incident (close_date null) → calendar days from `open_date` to `analysis_date`.309 - average over the supplier's filtered incidents, rounded to 2 decimals.310- `supplier_name`: from `/suppliers`.311- `recommendation_code`: see §4.3.312313### 4.3 Recommendation code (precedence: ESCALATE > PROCESS_REVIEW > WATCHLIST > MONITOR)314Evaluate top-down; assign the first matching code.315- **ESCALATE_SUPPLIER** — supplier `quality_status == quality_hold` AND `incident_count >= 3`,316 OR supplier has any critical-severity RMA incident (`incident_type==RMA` AND317 `severity==critical`), OR (`rma_count >= 3` AND `total_resolution_cost >= 15000.00`).318- **PROCESS_REVIEW** — `work_order_count >= 3` AND `work_order_count > rma_count`.319- **WATCHLIST** — `quality_status in {watch, quality_hold}`, OR `incident_count >= 4`,320 OR `total_resolution_cost >= 12000.00`, OR `severe_incident_count >= 2`.321- **MONITOR** — none of the above.322323NOTE: thresholds (3, 15000, 12000, 4, 2) and the severe set {high,critical} come from the324request's `recommendation_policy`. If a future request changes them, read and apply the325request's stated values, not these constants.326327### 4.4 Derived outputs328- `top_escalation_suppliers`: supplier_id list where recommendation_code ==329 ESCALATE_SUPPLIER, ordered by incident_count DESC, then total_resolution_cost DESC,330 then supplier_id ASC.331- `highest_cost_supplier_id`: supplier_id with the max total_resolution_cost (ties →332 supplier_id ascending).333- `highest_share_supplier_id`: supplier_id with the max incident_count (ties → max cost,334 then supplier_id ascending).335336### 4.5 analysis_window337`{start_date, end_date, analysis_date}` from the request (YYYY-MM-DD).338339### 4.6 summary340- `filtered_incident_count`: total_filtered.341- `supplier_count`: number of suppliers with >= 1 filtered incident.342- `total_resolution_cost`: sum across ALL filtered incidents, 2dp.343- `overall_rma_count`: RMA incidents in the filtered population.344- `overall_work_order_count`: WORK_ORDER incidents in the filtered population.345346### 4.7 Common misjudgments (Family C)347- Filtering on close_date or created_at instead of open_date.348- Computing open-incident duration to today instead of to `analysis_date`.349- Wrong precedence: applying WATCHLIST before ESCALATE/SUPPLIER checks, or checking350 PROCESS_REVIEW before ESCALATE. The policy precedence is ESCALATE > PROCESS_REVIEW >351 WATCHLIST > MONITOR — evaluate strictly in that order.352- Forgetting that quality_hold + >=3 incidents triggers ESCALATE even without critical353 RMAs or cost thresholds.354- Rounding percentage to 2 decimals (use 1) or duration to 0/1 (use 2).355356---357358## 5. Task Family D — Mixed-Warehouse Allocation / Transfer Wave359360**Inputs:** an allocation memo naming a wave (e.g. TRAIN_TRANSFER_B). **Output:**361`wave_id`, `line_actions[]`, `transfer_requests[]`, `blocked_orders[]`, `order_rollup[]`,362`summary{}`.363364### 5.1 Per-line action (sorted by order_id, then line_id ascending)365For each order in the wave, for each line:3661. **Account-level gate (applies to the WHOLE order):**367 - `account_status == blocked` → action `manual_review`, primary_reason `account_blocked`,368 all quantities 0.369 - `account_status == review_required` → `manual_review`, `account_review_required`, 0s.370 - `account_status == active` and `risk_flag == fraud_watch` → `manual_review`,371 `fraud_watch`, 0s.372 - (active + credit_watch: not exercised in train data. Treat as a manual_review risk373 gate consistent with Family A — reason: account_review_required — but verify if a374 test task exposes it.)375 These account gates apply to EVERY line of the order (the whole order is stopped).3762. **Product gate (per line):** if `product.active == false` → `manual_review`,377 `inactive_product`, 0s. (Only reached when the account is clean.)3783. **Inventory decision (account & product clean):**379 - `eff = effective_available` at the line's `requested_warehouse` for the sku.380 - `usable = max(0, eff)`.381 - if `eff >= quantity`: action `ship`, ship_quantity=quantity, primary_reason `none`.382 - else (shortfall): `gap = quantity - usable`. Look for ONE other warehouse whose383 **spare** (`effective_available - safety_stock`, §1.2) is `>= gap`. If found →384 action `transfer`: ship_quantity=usable, transfer_from=that warehouse,385 transfer_quantity=gap, backorder=0. Choose the warehouse with the LARGEST spare386 (ties → warehouse_id alphabetical). Only ONE source warehouse per line ("choose one387 source warehouse").388 - if no single warehouse can cover the full gap → action `backorder`:389 ship_quantity=usable (ship what's available), backorder_quantity=`quantity - usable`,390 primary_reason `insufficient_effective_stock`, transfer 0.391 (Symmetric with transfer: the usable requested-warehouse stock ships; the uncovered392 remainder is backordered. The explicit "leave usable as ship_quantity" instruction in393 the memo covers transfer; apply the same usable-ship convention to backorder.)3944. `requested_effective_available` field: report the raw `eff` (may be negative).395396### 5.2 transfer_requests (list)397One row per transfer line, with `{order_id, line_id, sku, from_warehouse, to_warehouse,398quantity}`. Sorted by order_id, then line_id ascending.399400### 5.3 blocked_orders (list of order_id strings)401Orders stopped at the ACCOUNT or customer-RISK level (blocked / review_required /402fraud_watch) — NOT orders stopped only by inactive-product lines. Sorted ascending.403Rationale (template meaning): "Orders stopped at account or customer-risk level, not404line-only product reviews."405406### 5.4 order_rollup (list, sorted by order_id)407Outcome per order (based on the set of its line actions):408- all lines `ship` → `ready_to_ship`409- all lines `transfer` → `needs_transfer`410- all lines `backorder` → `has_backorder`411- all lines `manual_review` → `manual_review`412- any mix of different actions → `mixed_actions`413414### 5.5 summary (all integers)415`total_orders`, `total_lines`, `ship_lines`, `transfer_lines`, `backorder_lines`,416`manual_review_lines` (counts of lines by action), `blocked_orders` (count of blocked417order ids), `transfer_units` (sum of transfer_quantity), `backorder_units` (sum of418backorder_quantity).419420### 5.6 Common misjudgments (Family D)421- Classifying an inactive-product line as `ship`/`backorder` because stock exists —422 inactive products are ALWAYS manual_review.423- Putting inactive-product-only orders into `blocked_orders` — they are line-level product424 reviews, not account/risk stops. Only account/risk-stopped orders belong there.425- Allowing a transfer from a warehouse whose spare is below zero (ignoring its own safety426 stock) — spare must be positive AND cover the full gap.427- Splitting one line's shortfall across multiple source warehouses — only ONE source per428 line; if no single source covers the full gap, the line is backorder.429- Forgetting that account-level gates (blocked/review/fraud) stop ALL lines of an order,430 including lines that would otherwise ship.431- Reporting `requested_effective_available` as 0 when it is negative — report the raw432 computed value.433434---435436## 6. Task Family E — Procurement Quality-Hold Review437438**Inputs:** a memo with `analysis_window`{start,end}, `target_supplier_ids[]`, policy.439**Output:** `analysis_window`, `supplier_decisions[]`, `held_po_ids`, `release_supplier_ids`,440`summary{}`.441442### 6.1 Per-supplier decision (sorted by supplier_id ascending)443For each target supplier, fetch its incidents filtered by `open_date` within444[window.start, window.end] inclusive, and its POs:445- `recent_incident_count`: count of filtered incidents.446- `recent_rma_count`: filtered incidents with `incident_type == RMA`.447- `severe_or_critical_count`: filtered incidents with `severity in {high, critical}`.448- `open_incident_count`: filtered incidents with `status == open`.449- `affected_skus`: sorted unique SKUs from the filtered incidents.450- `sample_incident_ids`: sorted incident_ids, capped at 5 (the first 5 when sorted451 ascending).452- `quality_status`: from `/suppliers`.453- `held_po_ids`: the supplier's open/confirmed PO ids that are HELD (see §6.3), sorted.454455### 6.2 Decision policy (inferred — apply top-down)456- **freeze_new_replenishment** — `quality_status == quality_hold`. (The hold itself freezes457 all replenishment.) Hold ALL open/confirmed POs.458- **buyer_review_required** — NOT quality_hold, but elevated risk: has at least one459 CRITICAL-severity incident in the window, OR `open_incident_count >= 1`. Hold ALL460 open/confirmed POs pending buyer review.461- **monitor_only** — otherwise (no critical incident and no open incident). No PO holds;462 release proceeds.463464Result: a clean three-way split. `release_supplier_ids` = suppliers whose decision is465monitor_only (sorted).466467NOTE: this policy is reverse-engineered. If a test memo states explicit thresholds, use468those instead. The signal that matters most: quality_hold → freeze; critical or open469recent incident → buyer_review; otherwise monitor. Severe-but-closed (high, closed)470incidents alone trend toward monitor, not buyer_review, in this framing.471472### 6.3 held_po_ids473- For `freeze_new_replenishment` and `buyer_review_required` suppliers: hold ALL of the474 supplier's open/confirmed POs (every PO with `status in {open, confirmed}`,475 regardless of SKU, regardless of eta/warehouse). Sorted unique.476 (Rationale: both decisions halt automatic PO release — freeze stops everything, buyer477 review holds pending the buyer's decision. Only monitor_only releases.)478 Alternative reading: only freeze holds POs, buyer_review holds none. If a test task's479 numbers seem off, re-evaluate whether buyer_review holds. The conservative default here480 is to hold for both freeze and buyer_review.481- For `monitor_only`: held_po_ids = `[]`.482483### 6.4 held_po_ids (top-level)484Sorted unique union of every supplier's held_po_ids (the distinct set of all held PO ids).485486### 6.5 summary487- `suppliers_reviewed`: count of target suppliers.488- `freeze_count`, `buyer_review_count`, `monitor_count`: counts by decision.489- `held_po_count`: length of the top-level held_po_ids list.490- `total_recent_incidents`: sum of recent_incident_count across reviewed suppliers.491492### 6.6 analysis_window493`{start, end}` (YYYY-MM-DD) from the memo.494495### 6.7 Common misjudgments (Family E)496- Filtering incidents by close_date or by a different field instead of open_date.497- Counting received/cancelled POs as held (only open/confirmed are held).498- Holding POs for monitor_only suppliers (they are released).499- Not capping sample_incident_ids at 5.500- Using POs for ALL suppliers in top-level held_po_ids instead of only reviewed suppliers'501 held POs.502503---504505## 7. Cross-Cutting Reusable SOPs5065071. **Read the answer_template FIRST.** It defines exact key names, allowed enum values,508 ordering, and types. Match them verbatim — a valid enum string spelled differently or a509 missing key fails. Re-read it before emitting JSON.5105112. **Pull + cache core entities** (§0) before per-task logic. Index by natural key.5125133. **Effective stock is always `on_hand - reserved - quarantined`.** Never use on_hand514 alone. Never ignore quarantined. Report raw (can be negative); use max(0,·) for shippable.5155164. **Check product.active and customer account_status/risk_flag before inventory.**517 Account and product gates precede inventory logic in Families A and D.5185195. **Spare = effective - safety_stock; only positive spare donates.** A warehouse below520 its own safety stock cannot be a transfer source.5215226. **POs: only open/confirmed are future supply; received is already in on_hand.**523 Timely = open/confirmed + same warehouse + eta <= deadline.5245257. **One source warehouse per transfer line** (Family D). All eligible sources for Family B526 can combine (multiple transfer_requests rows), drawing largest-spare first.5275288. **Incident filtering is on open_date, inclusive** of both endpoints.5295309. **Sort everything as the template dictates.** Default ascending by the natural id531 (order_id / sku / supplier_id / bom_id). For multi-key sort, follow the template's532 stated tie-breakers exactly.53353410. **Round: currency 2dp, percentage 1dp, duration 2dp.** Quantities/counts are integers.53553611. **Emit ONLY the JSON object** (no narrative, no markdown fences) unless the prompt537 explicitly allows text. Match the template's top-level key set exactly.53853912. **Sanity-check internally:** sums in the summary must reconcile with the line/record540 lists (e.g. total_shipping_cost = sum of quotes; decision_counts sum to order_count;541 ship+transfer+backorder+manual_review lines = total_lines). Reconciliation is the only542 correctness signal available (no gold/judge).54354413. **Dates:** parse as YYYY-MM-DD. Calendar-day differences use date subtraction545 (later - earlier).days. Inclusive range means `start <= d <= end`.54654714. **Wave filtering:** `GET /orders?wave=<WAVE_ID>` returns exactly that wave's orders.548 For expedite/allocation tasks, this is the order set.549550---551552## 8. Quick Reference — Decision Precedence Summaries553554**Family A (expedite) final_decision ladder:**555account_blocked→reject_hold ▸ inactive_sku→manual_review(escalate) ▸556fraud_watch→manual_review(hold) ▸ credit_watch→manual_review(hold) ▸557review_required→manual_review(account_review) ▸ shortage→backorder ▸558low_stock→delayed_release ▸ ready→ship_now.559560**Family B (BOM) component resolution:**561overstock(exclusion) ▸ timely_po_covers(exclusion) ▸ stocked_no_gap(exclusion) ▸562[remaining gap] → transfer from largest spare, then purchase the rest.563564**Family C (scorecard) recommendation:**565ESCALATE(buyback) ▸ PROCESS_REVIEW ▸ WATCHLIST ▸ MONITOR.566567**Family D (allocation) line action:**568account_blocked ▸ review_required ▸ fraud_watch ▸ inactive_product ▸569ship(eff>=qty) ▸ transfer(one source covers gap) ▸ backorder(no source covers gap).570571**Family E (procurement):**572quality_hold→freeze ▸ critical-or-open incident→buyer_review ▸ else→monitor.573574---575576## 9. Field-Shapes Cheat Sheet (per family)577578- **A records:** `{order_id, inventory_status, customer_exception, final_decision,579 next_action, shortage_skus[], inactive_skus[], low_stock_skus[], shipping_quote{zone_distance,service_days,total_cost_usd}}`580 + `summary{order_count, decision_counts{...}, total_shipping_cost_usd, blocked_order_ids[], manual_review_order_ids[], backorder_order_ids[], inactive_sku_order_ids[]}`.581582- **B:** `{task_id, plan_date, kit_targets[], component_plan[], transfer_requests[],583 purchase_requisitions[], excluded_components[], summary{}}`.584 component_plan row: `{sku, total_required, target_effective_available, timely_po_qty,585 transfer_qty, purchase_requisition_qty, final_action, coverage_po_ids[], exclusion_reason}`.586587- **C:** `{analysis_window, summary{}, supplier_scorecard[], top_escalation_suppliers[],588 highest_cost_supplier_id, highest_share_supplier_id}`.589590- **D:** `{wave_id, line_actions[], transfer_requests[], blocked_orders[],591 order_rollup[], summary{}}`.592 line_action row: `{order_id, line_id, sku, requested_warehouse, requested_effective_available,593 action, ship_quantity, transfer_from(nullable), transfer_quantity, backorder_quantity, primary_reason}`.594595- **E:** `{analysis_window, supplier_decisions[], held_po_ids[], release_supplier_ids[], summary{}}`.596 supplier_decision row: `{supplier_id, supplier_name, quality_status, recent_incident_count,597 recent_rma_count, severe_or_critical_count, open_incident_count, affected_skus[],598 sample_incident_ids[], decision, held_po_ids[]}`.599600---601602*End of skill. Apply the rules; reconcile summary totals against record lists; emit only603the JSON object matching the template.*