ARCNM

Concepts

Deep Cost Modeling

Every quote is a curve over batch size, not one number: setup amortises, fixtures amortise once, tooling has a finite life, and learning compounds per unit.

Every ARCNM quote is a curve over batch size, not a single number. Setup amortises over more units, fixtures amortise once, tooling has a finite life, learning compounds. Deep Cost Modeling decomposes all four — bottom-up, per process, per coefficient — so every euro of the unit price traces back to a named source.


The cost equation

unit_cost = material_cost
          + (setup_time / n) × labour_rate           ← amortised setup
          + cycle_time × labour_rate × attendance    ← run labour
          + cycle_time × machine_rate                ← machine burden
          + tooling_amortisation
          + overhead_variable + overhead_fixed
          + calibration_adjustment                   ← see Calibration

attendance is the share of an operator the machine consumes while it runs — one operator tending two machines is 0.5. Setup is always fully attended.

One overhead system

overhead_variable is a percentage of a stated base — direct and machine cost by default, or material, labour or conversion cost when the environment's rate row says so. The platform default is 15 % on direct and machine cost. The Zuschlagskalkulation ladder (material overhead on material, residual production overhead on wages, then administration and selling, packaging, freight, duty, the profit mark-up and the sales terms) charges the same burden a second way, so the two are one system: the moment an environment states a material or production overhead, the platform's default variable overhead is switched off (GET /environments/{id}/assumptions reports it as disabled_by_ladder, value 0). A variable overhead the environment states itself — 0 included — is always honoured beside the ladder.

The totals, one meaning each, on every surface:

unit cost (Stückkosten)   = direct and machine cost + overhead_variable + overhead_fixed
manufacturing cost        = unit cost + rejects + material overhead
  (Herstellkosten)          + residual production overhead
total cost (Selbstkosten) = manufacturing cost + administration & selling
                            + packaging + freight + duty
cash sales price          = total cost × (1 + profit mark-up)
  (Barverkaufspreis)
invoice price             = cash sales price ÷ (1 − cash discount − sales commission)
  (Zielverkaufspreis)
offer price               = invoice price ÷ (1 − customer discount)
  (Angebotspreis = Listenverkaufspreis)

Rejects are charged on the material drawn for them, or — with reject_rate_base: "herstellkosten" — on the whole part before scrap (r ÷ (1 − r) of that base; one method, never both). The profit mark-up (margin_pct) is a mark-up on the total cost, not a margin on the price: 25 % mark-up is a 20 % margin. The cash discount, sales commission and customer discount are shares of the price that contains them ("im Hundert"); the cash discount and the commission share one denominator.

unit_cost on the calculation is the unit cost; analytics.offer_price is the offer price, and it is the figure the app's headline and the cost sheet's last row both show. They coincide on an environment that states no ladder rate. Benchmark rates (automotive supplier mark-ups on total manufacturing cost, the usual public-contract profit) are listed at GET /environments/surcharge-presets; applying one is a PATCH /environments/{env_id}/economics with its economics.

Does your machine rate include the operator?

Both conventions are in everyday use: a machine-only rate with labour billed beside it, and an all-in workstation rate with the operator inside. The engine prices in the first, so a machine carries rate_operator_eur_per_h — the operator wage its rate already contains. Leave it at 0 (the default) if your hourly rate covers the machine only. Set it if your rate is all-in: that wage is then lifted off the machine line and billed once, on the labour line, instead of twice.

A rate produced by the VDI 3258 calculator is all-in, and the calculator fills this in for you.

Each coefficient (labour rate, machine rate, feed, overhead) resolves through the environment cascade. Material mass uses a discipline-aware blank:

  • Milling: rectangular blank (raw bar minus envelope + chip-overrun)
  • Turning: cylindrical bar
  • Sheet metal: sheet blank
  • Casting: poured-mass-plus-runner

The material line

The material line is the stock bought, less what a reusable remnant is worth back in stock. What the scrap sells for is credited on a row of its own — the scrap credit, scrap_credit — below the unit cost and before the overheads on material:

material_cost  = material_purchased_cost − remnant_credit
purchased      = purchased_mass_kg × material price
remnant credit = remnant_mass_kg × material price × remnant_value_factor
scrap credit   = Σ each kind of scrap's mass × its scrap price
net material   = material_cost − scrap_credit

A scrap price is your net return per kilogram: what the dealer pays less what sorting, containers and haulage cost you. Where no price is stated for a kind of scrap, the environment's flat return per kilogram (scrap_credit_eur_per_kg) is used; analytics.assumptions.scrap_prices names which applied (price_source). A reusable remnant is never credited as scrap as well.

The purchased mass is the part's share of the stock it is cut from — one sheet over the parts nested on it, one bar over the pieces sawn from it, the section sawn for a block — at the theoretical mass a mill invoices: nominal size and thickness at the grade's density. analytics.material_resolution.stock names that stock and where its metal goes, purchased_mass_kg = part_mass_kg + scrap_mass_kg + remnant_mass_kg; analytics.cost_decomposition carries material_purchased_cost, remnant_credit and scrap_credit beside material_cost, in the calculation's currency and equal to the material line, its remnant row and the scrap credit row on the calculation's cost sheet. A calibration per cost driver leaves the material line at mass × price; only an environment still calibrated by one factor on the whole price carries that factor on the material line too. The masses and prices a cost-sheet row states are in the environment's rate currency (analytics.assumptions.rate_currency), its amount in the calculation's: on a calculation in another currency every amount also carries the conversion, analytics.assumptions.currency_factor, so the material line is mass × price × that factor. Metal that ships inside the part — the extra thickness where the size bought was rounded up — counts in scrap_mass_kg and is never credited.

Both credits are 0 until the environment states them, through the shop-practice questions scrap_credit (you sell your scrap) and remnant_credit (usable remnants go back into stock: a rest of a sheet, or the end a bar or a tube leaves past its part). What a kind of scrap fetches is a dated price under /environments/{id}/rates/scrap; what a remnant is worth against new stock, and the size it needs to count as one, are /environments/{id}/tuning settings. analytics.assumptions records whether each credit applied and at which prices.

Material overhead is charged on the net material — the stock bought less the remnant credit and less the scrap credit — never on the stock bought: a remnant that returns to stock carries its overhead when a later job uses it. The variable overhead follows its base — the direct-cost and the material base both contain the material cost (the stock bought less the remnant credit); the scrap credit is taken off afterwards and moves no variable overhead.

Setup time is amortised into labour (operator-present during setup — HGB §255 / IAS 2 convention), not split into a separate line item.


The lot-size curve

POST /calculations always returns the full curve. Default grid:

n = 1, 5, 10, 25, 50, 100, 250, 500, 1000, 2500

Each grid point carries its money in the calculation's currency — lot_size_curve.currency declares it, and every money field on the curve is named without a currency suffix:

{
  "quantity":             50,
  "unit_cost":           12.84,
  "offer_price":         14.12,
  "direct_unit_cost":    10.62,
  "effective_unit_cost": 13.10,
  "setup_time_s":       1800,
  "unit_time_s":         206.2
}

Every money figure on a curve point and on the optimum is in the calculation's currency, calibrated — the same basis as the top-level unit_cost and offer_price, so the point at the ordered quantity reproduces both exactly and no conversion is needed to compare a point to the headline. lot_size_curve.currency names that currency, and basis_uncertain is set when the curve could not be carried onto the headline basis exactly.

direct_unit_cost is everything before overhead at that lot — material, machine, labour, programming, tooling, inspection, finishing and subcontract, with the setup amortised over the lot — so it falls with quantity like unit_cost, and the gap up to unit_cost is the variable and fixed overhead. At the ordered lot it equals cost_decomposition.direct_unit_cost. Points of a calculation priced before 2026-10-07 leave it out.

cost_at(n) linearly interpolates between grid points. For curves where you need a non-default grid (e.g. n = 7, 350), pass lot_grid on the request and the exact points are computed.

Why machine_id_at_n?

Optimal machine choice changes with batch size:

  • n = 1: a 3-axis vertical mill with manual setup.
  • n = 50: 5-axis mill with a vise tombstone.
  • n = 1000: a dedicated horizontal machining center with a pallet changer.

machine_id_at_n records which machine was selected at each point so the audit row tells you why the price stepped.


Breakpoints — the economically meaningful kinks

The curve isn't smooth; it has kinks. We surface them as breakpoint records so a UI can render "the price drops at n=50 because setup amortises here":

{
  "breakpoints": [
    {
      "kind":         "setup_run_crossover",
      "quantity":      25,
      "unit_cost":     18.42,
      "rationale":     "Per-unit amortised setup falls below cycle-time labour"
    },
    {
      "kind":         "machine_class_crossover",
      "quantity":     250,
      "unit_cost":    11.20,
      "rationale":     "5-axis machining-center cheaper than 3-axis at this volume"
    }
  ]
}

All breakpoint kinds — every one computed from that calculation's own figures, never from generic assumptions:

kind What it means
setup_run_crossover Amortised setup falls below 50 % of the unit cost
fixture_amortisation The fixture hardware (tiered by the part's own setup count × envelope) amortises below €1/part
machine_class_crossover A different machine class becomes cheaper at this volume
subcontract_lot_minimum An external lot minimum (heat treat, coating) amortises below €1/part

Walk breakpoints directly to drive a tooltip UI.


The lot-size optimum — production plus inventory

The cheapest €/piece on the curve is always the biggest lot — that is amortisation, not an optimum. The recommendation ARCNM makes instead minimises the total annual cost, decomposed the way real manufacturing economics work: lot-fixed costs (setup, transfer, order minimums — the per-lot cost K that degrades with lot size) plus lot-variable cost (cycle time, material — proportional to quantity, taken at cumulative-annual learning so a big lot is never credited with learning the shop reaches anyway once it produces the year's demand in any lot split) plus the cost of holding the resulting inventory (capital tied up in stock at an environment-tunable rate, plus physical storage per kg on the finished part's mass), capped at one year of demand.

The model, stated. For a year's demand D made in lots of q, the total annual cost is

TC(q) = D · c_D(q) + (q / 2) · h(c_D(q))

and the figure every curve point carries as effective_unit_cost is that per piece, effective(q) = c_D(q) + h(c_D(q)) · q / (2·D). c_D(q) is the route's own restatement at lot q (lot_kernel.annual_effective_unit_cost_eur): the setup and every other per-lot constant over q, the bought-in minimum-order shortfall at q, the nest sharing of a sheet and every other lot-dependent step of the variable cost, the competition loser where it prices the point — with two changes against the quote for that lot. The learning curve is evaluated at the year's volume D for every q (Wright learning is a function of units built, which the shop reaches within the year under any lot split, so no split earns a credit another does not), and the NC program is charged once for the year, P / D, not once per order. h(unit) is the holding cost per unit-year on that unit value — the environment's capital rate on the value plus its storage charge on the part's mass — on the average stock q / 2 (Eversheim §4.5.1: Zins- und Lagerhaltungskosten).

Where the variable cost has no lot-dependent step, c_D(q) is exactly v + (K + S(q))/q and the minimum is the closed form: with S = 0, d/dq [v + K/q + (i·(v + K/q) + s·m)·q/(2D)] = 0 gives the Andler / Harris lot q* = √(2·D·K / (i·v + s·m)) — the interest on the amortised K is the constant i·K/(2D) and moves nothing. That closed form is computed as a cross-check (andler_quantity) and its quantity is a grid point; the recommendation is the banded search over the real basis, which is the standard quantity-discount procedure generalised from price brackets to a continuous per-part curve.

The model's assumptions, so a reader knows what the number is:

  • Constant, known demand; the whole lot arrives at once, so the average stock is q / 2 (Harris 1913, Andler 1929). The finite- production-rate correction (1 − D/P) describes stock that builds up while the lot is still being made; a job-shop order ships complete, so it does not apply here.
  • No forgetting between lots. Learning is Wright's on cumulative units; the learn–forget model (Jaber & Bonney 1996) shows the optimum moving UP where production breaks are long enough to forget. The platform does not model breaks, so its optimum is a lower bound where they matter.
  • Stock is valued on the holder's own basis — the buyer's at its purchase price after discounts, the supplier's at production cost with its overheads (see Scenarios below).
  • One year of coverage is a policy cap, not a cost result. On a low-volume part with a long setup the closed form can exceed D (q* > D ⇔ K > h·D/2); the recommendation stops at D and says so (coverage_capped), the chart keeps drawing the basis beyond it — rising, by the holding term — so the reader sees why.

Two refinements make the recommendation realistic rather than mathematically cute:

  • Flat-optimum band. The total-cost curve is famously flat around its minimum (running x·q* costs ½(x + 1/x) of the optimum's fixed/holding trade — +37 % quantity is within 5 %). The raw argmin would ride a sub-percent slope to the grid maximum; ARCNM instead recommends the smallest lot within 5 % of the minimum — near- identical cost, least capital tied up, and headroom for MOQs, pack sizes, storage limits and shelf-life (the literature's own non-cost order-quantity gates). band_min_quantity/band_max_quantity bound the near-optimal range; an ordered lot already inside the band is reported as optimal instead of churned.
  • Stock reach. Every lot-size recommendation carries its Reichweite (months of stock) — the standard over-stocking Kennzahl — so "lot 2 500 at 100/yr" reads as 25 years of stock, not as a price win.

The NC program is not in K. It is written once per part and every repeat lot reuses it (loading and proving it out again is setup), so splitting a year's demand into more lots does not repeat it. Each point of the curve is the price of an order of that size and carries the program over its own quantity; the lot decision leaves it out.

K is derived from the curve itself — K = n·(c(n) − c_variable(n)), where c(n) is priced without the program and c_variable re-prices the same lot with every per-lot charge zeroed — rather than restated as a separate formula. "Every per-lot charge" includes the two a per-part plane hides: the first article's check beyond the recurring one every part gets (the inspection line is a lot blend of the two), and the once-per-order despatch inside the bench time — so c_variable(n) is flat in n, one recurring check and one per-piece packing per part. That guarantees K carries exactly the uplifts the curve carries (burden rate, per-machine setup rate overrides, FAI, variable overhead, calibration). Re-deriving it by hand had silently dropped all of those, which reclassified real fixed cost as variable and biased the recommendation toward smaller lots.

One basis applies on both sides of one year of demand. Wright learning is a function of cumulative units built, which the shop reaches within the year under any lot split, so an over-coverage lot earns no extra learning credit — it is priced on the same annual basis and penalised honestly by the growing holding term. (An earlier revision switched lots above D back to the raw quote price, which made the series discontinuous at D and let over-coverage lots win admissions they lose like-for-like.) The persisted grid ends at the largest decision-relevant quantity — usually D — and the chart continues the series past it on the closed form anchored at the last rung; it used to repeat the last rung's value there, which drew a FLAT line past the demand marker, as if two years' stock cost no more to hold than one.

Before 2026-09-25 the basis was reconstructed from one anchor point as v_annual + (K + S(q))/q + h·q/(2D) — exact where the variable cost is the same at every lot, and blind to the nest sharing (a lot of one pays a whole sheet's handling) and to a competition loser cheaper at large lots. The reconstruction remains the fallback for a point no kernel priced.

analytics.lot_size_curve.optimum:

{
  "quantity":                      250,
  "unit_cost":                     11.20,
  "effective_unit_cost":           11.64,
  "current_quantity":              50,
  "current_unit_cost":             12.84,
  "current_effective_unit_cost":   12.95,
  "net_annual_saving":             1310.0,
  "annual_volume":                     1000,
  "andler_quantity":                   231,
  "band_min_quantity":                 250,
  "band_max_quantity":                 500
}

effective_unit_cost is the per-piece cost on that annual-total basis (each curve point also carries its own value, so the UI draws exactly the curve the recommendation came from); net_annual_saving is what moving to the recommended lot is worth per year, inventory included. All of them are in the curve's currency.

Scenarios — the supply arrangement. One "optimal lot" is the wrong answer for a buyer and a contract manufacturer alike, because the annual cost the lot controls depends on how the part is ordered, released, delivered and owned. The research doc's §4.3 table surveys the arrangements industrial practice names (spot order, repeat buying to stock, frame contract with call-offs, consignment/VMI, JIT/kanban, make-to-order, supplier make-to-stock, last-time buy) and the model each one has; the five below are the ones an RFQ's own data can price. The curve is computed once and every scenario's basis is read off the same route restatement (lot_kernel.scenario_effective_unit_costs); each point carries all five in effective_by_scenario, the optimum carries every scenario's recommendation in scenario_optima, and the card asks the reader's arrangement in plain words (Order & stock, Supplier stocks, Use on arrival, Frame contract), remembers the answer, and asks nothing when one lot covers the year. With c the cost at that lot at the year's learning, p = ρ·c the price the buyer pays — its purchase price after the supplier's discounts, the Einstandspreis of a German purchase costing (Bezugskalkulation): ρ = offer price × (1 − customer discount) × (1 − cash discount) ÷ manufacturing cost at the ordered lot (price_to_cost_ratio) — σ·c the value of a piece in the supplier's stock, its production cost with material and production overheads (Herstellkosten: the lower bound HGB §255 allows for inventory, and the IAS 2 cost; σ = supplier_value_to_cost_ratio), both 1 where no costing scheme is stated and both read off the same costing ladder as the quoted price, A the buyer's cost per order or delivery (the environment's ordering_cost_eur_per_order — order handling and the inbound freight per delivery where the buyer pays it; 0 where none is stated), h(x) the holding cost per unit-year on the value x, D the year's demand and P the pieces the route makes in a year of its machines' VDI 3258 run hours. In the table h(c) is the supplier's stock and reads h(σ·c); v is the lot-variable cost, and h(v) likewise reads h(σ·v):

Scenario Per-piece basis Closed form Source
buyer_stock — the buyer orders in lots and holds the stock (the default, and what the curve stated before the scenarios existed) p + A/q + h(p)·q/(2D) q* = √(2D(ρK + A) / h(ρv)) — the EOQ on the price with the ordering cost; the search over the real curve is the all-units quantity-discount procedure Harris 1913; Andler 1929; Hadley & Whitin 1963 §2-11
supplier_stock — the supplier produces in lots ahead of a steady call-off and holds the stock at production cost (σ·c) c + (1 − D/P)·h(c)·q/(2D) q* = √(2DK / (h(v)·(1 − D/P))) — the economic production quantity Taft 1918
make_to_order — the order ships complete and is consumed on delivery; only the supplier's work in progress is held p + A/q + (D/P)·h(c)·q/(2D) q* = √(2P(ρK + A) / h(v)) with a stated P; none without (the basis only falls with the lot, no_holding_optimum) Banerjee 1986 (lot-for-lot vendor)
joint — a frame contract with call-offs, coordinated: the supplier makes n call-offs in one lot and ships q as produced (a fixed call-off — kanban, JIT — reads the same way at its quantity) (c − K/q) + min_n [(A + K/n)/q + q/(2D)·(h(p) + h(c)·(n(1 − D/P) − 1 + 2D/P))] q(n) = √(2D(A + K/n) / (h(p) + h(c)·g(n))), n by search; the recommendation names the call-off, n and the supplier's lot Banerjee 1986; Goyal 1988; Lu 1995; reviews Ben-Daya et al. 2008, Glock 2012
consignment — the same frame contract under a consignment-stock (VMI) agreement: the supplier owns the stock at the buyer's site until it is drawn, so the capital of every unit is tied up at the supplier's cost and only the storage is paid where the unit sits (c − K/q) + min_n [(A + K/n)/q + q/(2D)·h(c)·(1 + n(1 − D/P) − 1 + 2D/P)] — the joint form with the buyer's h(p) replaced by h(c); the per-delivery cost A kept q(n) = √(2D(A + K/n) / (h(c)·(1 + g(n)))), n by search Braglia & Zavanella 2003; Valentini & Zavanella 2003 (Table 2)

The joint form reproduces Goyal's own example (D = 1 000, P = 3 200, K = 400, A = 25, h_b = 5, h_v = 4): five call-offs of 110 in a lot of 550 at 1 903 a year; the consignment form, with the buyer's 5 split as Valentini & Zavanella do (2.5 storage, plus the supplier's 2.0 capital on the stock at the buyer's site), moves it to four call-offs of 134 at 1 871 — 1.7 % less for the system. The platform charges one storage rate on both sites, so on the same data its own consignment figure is four call-offs of 136 at 1 837 a year, 3.5 % below the joint optimum (all three pinned in test_lot_scenarios.py). Where the environment states no ladder the two coincide, and the card is silent about the clause; where they differ, the card states the clause's saving per year under the frame contract rather than adding a fifth switch position. The supplier's basis and the make-to-order basis need a production rate: it is derived from the chosen machine's VDI run hours over the part's machine seconds (the slowest segment on a composed route) and is absent where no machine states annual hours — the classic instantaneous replenishment then applies and the card says so. The buyer's ordering cost is the one input the platform cannot derive; German procurement benchmarks (BME) put it at about €30 for lean organisations and €100–120 on average, and the environment states it. Every scenario keeps the one-year coverage cap, the materiality band and the setups floor of the default optimum.

What the scenarios do NOT model, and why: a per-period call-off schedule (Wagner–Whitin, Silver–Meal, the SAP optimum lot-sizing procedures — they need the schedule the RFQ does not carry), demand uncertainty and safety stock (separable from the lot to first order), the release windows of an automotive frame contract (the 4/8-week Fertigungs-/Materialfreigabe that splits who carries which stock), packaging units and truckload steps (no pack size on the RFQ; the flat freight per delivery rides in A), the last-time buy (a demand distribution and a horizon), imperfect-quality holding (second order for machined parts; the reject rate is in the piece cost) and an emission price per setup or delivery (the same shape as K and A; a price per delivery can be carried in A today). The lean view — cut the setup rather than grow the lot (SMED, EPEI) — is the reduce_setup_time recommendation beside the curve.

The optimum states which basis its figures are on, valuation_basis: purchase_price as above, or offer_price on a calculation made before — the buyer then valued at the offer price (which carries the customer-discount gross-up the buyer never pays) and the supplier's stock at manufacturing cost. Recalculate such a calculation to restate it; its figures are never relabelled.

Perspectives. The holding rate is environment-scoped for a reason: the scenarios already value each side's stock on its own basis (above), so the rate is the cost of capital and storage alone — add obsolescence/insurance components for parts that carry that risk; German practice totals 15–30 %/yr where the platform default (8 % + storage) is deliberately conservative. Time-phased demand methods (Wagner-Whitin, Silver-Meal) and the finite-production-rate EPQ correction are documented research directions — they need order schedules the platform does not ingest today, and the EPQ correction is a near-no-op for job-shop parts.

Next to it, analytics.lot_size_curve.recommendations carries at most two machine-readable optimization directions — a deliberate choice: two solid, defensible recommendations beat ten fragile ones.

kind Fires when
adjust_lot_size The recommended lot differs from the ordered one and the net annual saving is material
reduce_setup_time Setup still dominates (≥ 30 %) the lot cost at the recommended lot — the SMED direction, with the annual saving of halving setup quantified

The same directions are available through the arcnm_optimize_lot_size MCP tool and, per part, on the portfolio economics read.


Optimization directions — the full findings surface

Lot sizing is one lever of several. analytics.optimization.findings carries every research-grounded direction derived from the calculation's own results, and GET /calculations/{id}/optimization merges them with live supplier-quote findings — one endpoint that the arcnm_optimization_directions MCP tool and the in-app AI agents wrap identically.

Each finding is tagged three ways:

  • audience — human findings are few, materiality-gated headline directions (rendered as sentences in the app); agent findings are denser signals for multi-criteria optimization by an AI agent (agents read both).
  • confidence — quantified figures are exact engine math; directional findings state a lever whose € outcome needs a requote, RFQ, or product decision.
  • method — the industry method implemented (e.g. total_cost_optimum_andler, smed, chessboard_D6_process_benchmarking, near_net_shape, sampling_skip_lot).

The v1 catalog:

kind audience fires when
adjust_lot_size human recommended lot ≠ ordered and the net annual saving (incl. inventory holding) is material
reduce_setup_time human setup ≥ 30 % of the lot cost at the recommended lot (SMED direction, halving quantified)
review_tolerance_cost human tolerance callouts drive ≥ 10 % of the unit cost (exact closed-form uplift; the relaxed-class € needs a re-price)
improve_material_utilization human/agent buy-to-fly ≥ 3:1 with a material share ≥ 15 % (human at volume, agent below the near-net crossover)
benchmark_secondary_processes human external processing (heat treat / coating / weld) ≥ 25 % of the unit cost
supplier_price_gap human latest same-currency quote ≥ 10 % above should-cost — renegotiate or run an RFQ
reduce_inspection_cost agent inspection ≥ 8 % of the unit cost (sampling / skip-lot direction)
reduce_programming_cost agent programming ≥ 15 % of the unit cost (CAM reuse / family parts)
volume_bundling_leverage agent the part's own curve prices ≥ 8 % reduction for doubling the lot
review_dfm_issues agent medium+ severity DFM issues exist (full instances listed)
calibrate_should_cost agent latest quote ≥ 15 % below should-cost — the model, not the supplier, is the outlier

Findings arrive sorted by annual saving. Nothing in the list is a generic tip: every figure derives from that calculation's decomposition, curve, geometry, or quotes, and absent basis means an absent finding.


Learning curve

Cumulative-average learning is applied by default — as cumulative volume grows, the average time per unit comes down. The learning rate is environment-tunable per discipline and per machine class through calibration. If your shop learns faster on aluminium 5-axis than on titanium turning, calibration picks that up from your observations.


VDI 3258 (overhead-rate framework)

For German manufacturing customers, ARCNM emits a VDI 3258-conformant overhead breakdown — variable vs fixed, machine hour vs labour hour, allocated vs absorbed:

{
  "vdi_3258": {
    "machine_hour_rate_eur":  78.40,
    "labour_hour_rate_eur":   42.10,
    "overhead_variable_pct":  18.5,
    "overhead_fixed_pct":     22.0,
    "absorbed_at_n":          50
  }
}

Auditors recognise the structure; finance teams reconcile it directly against ERP.


Cost decomposition by line item

A cost breakdown like this is available within the calculation's analytics blob (the top-level response carries unit_cost, total_cost, setup_cost, and currency — see the Calculations reference):

{
  "material":          3.21,
  "setup":             1.05,
  "cycle_labour":      4.12,
  "machine":           3.66,
  "tooling":           0.18,
  "overhead_variable": 0.62,
  "overhead_fixed":    0.85
}

The line items sum to the unit cost. The top-level setup_cost is the setup line times the lot size, on the same basis and in the same currency as unit_cost, so it is already inside unit_cost and total_cost.


Cost drivers by feature (value engineering)

The line-item breakdown answers what kind of cost you're paying (material vs machine vs labour). The cost-driver breakdown answers where on the part that cost lives — which hole, pocket, face, or turned diameter takes the most time and money. It's the first thing a value-engineering review needs: "if I want this part cheaper, what do I change first?"

ARCNM re-derives this by attributing the time-driven cost lines back onto the feature each machining operation was planned for, then ranking features by attributed cost. It's a pure reporting view over the same numbers as the line-item breakdown — it never re-prices the part.

It's available in the calculation's analytics.cost_drivers blob (REST and MCP alike):

The blob is self-contained — a consumer can rank, sum, and reconcile the drivers without reaching back into the rest of the payload:

{
  "cost_drivers": {
    "attributed_cost_share": 0.61,
    "shared_cost": 5.00,
    "shared_cost_share": 0.39,
    "offer_price": 14.12,
    "drivers": [
      {
        "rank": 1,
        "feature_kind": "pocket",
        "label": "4000 mm² pocket, 20.0 mm deep",
        "time_share": 0.893,
        "cost": 5.91,
        "cost_share": 0.46,
        "reasons": ["deep pocket — multiple Z passes"]
      },
      {
        "rank": 2,
        "feature_kind": "hole",
        "label": "Ø8.0×24 mm through hole",
        "time_share": 0.107,
        "cost": 1.93,
        "cost_share": 0.15,
        "reasons": []
      }
    ]
  }
}

Field notes:

Field Meaning
rank 1 = the single biggest cost driver (drivers are pre-sorted by cost descending).
feature_kind hole, pocket, face, turning_face, bend, … (or operation when an op has no resolvable source feature).
label Human-readable feature description an estimator would recognise.
time_share The feature's fraction of total per-part machining time.
cost Attributed cost per part, in the calculation's currency and on the same basis as the headline unit_cost.
cost_share Fraction of the unit cost. cost_share × unit_cost recovers cost.
reasons Short physics notes explaining why the operation took as long as it did.

attributed_cost_share (the sum of all drivers) plus shared_cost_share (amortised setup, programming, inspection, material, tooling, overhead — everything not attributable to a single feature) reconcile to 1.0, and the drivers' cost values plus shared_cost sum back to the headline unit_cost, so the breakdown always sums to the headline price. Every value is a plain JSON scalar/array — no custom decoding.

Per-operation phase times are also surfaced under analytics.time_breakdown.operations (one record per operation, each tagged with its feature_id) if you want to roll the numbers up a different way.

cost_drivers is null for parts routed to catalogue-buy or bar-stock-saw, which have no machining operations to attribute.


Where calibration enters

Geometry, features, machine selection, and process planning are derived from industry-standard manufacturing theory and don't change as you calibrate — calibration applies a conservative, fully-logged adjustment to the final price. This separation is what makes the audit trail defensible: you can always tell what's "the engine's view of reality" and what's "your environment's correction".

A conservative safe band caps the adjustment per quote; values outside that range are logged + suppressed, never silently applied.


Currencies

All cost fields are EUR by default. Pass currency on the request to get the response converted at the daily ECB middle rate:

{ "currency": "USD" }

Supported currencies: EUR, USD, GBP, CHF, JPY, CAD. Conversion is at quote time, not at retrieval time — re-quote to pick up a fresher FX rate.


See also