Recipes
Recipe — teach an environment from your actuals
Close the calibration loop over the API: POST your realised costs, recalibrate an environment, and read back the tightened prediction interval it produces.
Adaptive Calibration is what turns a generic benchmark into a cortex for your shop. The dashboard drives it interactively; this recipe drives the same thing over the API, so you can wire it into your ERP close or a nightly job.
The loop:
- Collect realised costs — work-order actuals from your ERP / MES.
- POST them to a costing environment.
- ARCNM pairs each actual with that part's current quote, fits the delta, and tightens the interval.
- Every subsequent quote on that environment uses the calibrated fit.
Calibration writes need parts:write; the status read needs
parts:read.
1. The one-call path — actual unit costs
If you already have per-part actual unit costs, POST them to
auto-calibrate. You don't supply a predicted value — the service pairs
each part with its most recent quote at that lot size in this environment
and fits the difference. Nothing is re-quoted: only a quote priced by the
current version of the costing model counts, so after the model changes,
quote the parts again before you calibrate:
import os, requests
BASE = "https://api.arcnm.io/api/v1"
H = {"X-API-Key": os.environ["ARCNM_API_KEY"]} # scope: parts:write
r = requests.post(
f"{BASE}/calibration/environments/{env_id}/auto-calibrate",
headers=H,
json={"actuals": [
{"part_revision_id": "1f…", "actual_unit_cost": 12.40, "lot_size": 50},
{"part_revision_id": "2a…", "actual_unit_cost": 8.10, "lot_size": 100},
]},
)
r.raise_for_status()
report = r.json()
# { matched, unmatched_part_ids, n_train, n_holdout, holdout_mape, holdout_coverage, … }
Pass lot_size whenever you have it (the work-order quantity) — without
it the fit can't separate one-time setup from per-unit cost. Parts with
no current quote at that lot size — never quoted, or quoted only before
the costing model last changed — come back in unmatched_part_ids; quote
them, then retry.
2. The ERP-native path — raw work orders
Most ERPs export work orders, not unit costs. auto-calibrate-from-erp
takes the raw columns (SAP AUFK/AFKO, Oracle WIP_DISCRETE_JOBS) and
does part-number resolution + unit-cost derivation for you:
r = requests.post(
f"{BASE}/calibration/environments/{env_id}/auto-calibrate-from-erp",
headers=H,
json={"work_orders": [
{"part_number": "BRACKET-001", "work_order_id": "WO-5512",
"quantity": 50, "total_cost": 620.00},
]},
)
work_order_id is the idempotency key — re-POSTing the same work order
is a no-op, so you can stream your last 12 months once and replay safely.
Unknown part numbers come back in unknown_part_numbers.
3. Check calibration health
GET .../environments/{env_id}/status is the safe, read-only endpoint to
call first (and after) — it reports whether the env is calibrated, when
it last ran, and how many observations are queued:
{
"env_id": "env_2a…",
"is_calibrated": true,
"last_fit_at": "2026-05-28T14:00:00Z",
"observations": [{ "oracle": "erp", "target_metric": "unit_cost", "count": 142 }]
}
is_calibrated: false means the env is still on platform
benchmarks — submit a batch to calibrate it.
4. Read the tightened interval
Once calibrated, every quote on that environment carries a narrower quote-level interval (see Audit & provenance). Use it to set a margin buffer, or to trigger a re-quote when it's too wide for the customer:
{ "unit_cost": 12.84, "currency": "EUR", "interval": [12.20, 13.50] }
Pick the right entry point
| Endpoint | Use it when |
|---|---|
auto-calibrate |
You have per-part actual unit costs. |
auto-calibrate-from-erp |
You have raw work orders (quantity + total cost). |
teach |
You have explicit (predicted, actual) pairs. |
learning-curve-fit |
You have lot-progression actuals for one part. |
status |
Always — read calibration health before and after a run. |
See also
- Concepts → Adaptive Calibration — the model behind this.
- API → Calibration — every endpoint and payload.
- Recipe → ERP integration — the sync-out side of the same loop.