ARCNM

Concepts

Deep Cost Modeling

Bottom-up cost decomposition, lot-size economics, learning curves, and breakpoints.

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.

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

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:

{
  "quantity":           50,
  "unit_cost_eur":     12.84,
  "direct_unit_cost":  10.62,
  "overhead_var":       1.45,
  "overhead_fixed":     0.77,
  "machine_id_at_n":   "machine_haas_vf2"
}

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_eur": 18.42,
      "rationale":     "Per-unit amortised setup falls below cycle-time labour"
    },
    {
      "kind":         "machine_class_crossover",
      "quantity":     250,
      "unit_cost_eur": 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, per-lot programming, 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.

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 classic Andler / EOQ closed form (q* = √(2·D·K/h), with the true un-amortised per-lot cost as K and h evaluated on the ordered lot's unit value) is computed as a cross-check and its quantity gets an exact curve point; the authoritative optimum is the banded search over the real curve — the standard quantity-discount-EOQ procedure generalised from price brackets to a continuous per-part curve.

K is derived from the curve itselfK = n·(c(n) − c_variable(n)), where c_variable re-prices the same lot with every per-lot charge zeroed — rather than restated as a separate formula. That guarantees K carries exactly the uplifts the curve carries (burden rate, per-machine setup/programming 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.)

analytics.lot_size_curve.optimum:

{
  "quantity":                          250,
  "unit_cost_eur":                     11.20,
  "effective_unit_cost_eur":           11.64,
  "current_quantity":                  50,
  "current_unit_cost_eur":             12.84,
  "current_effective_unit_cost_eur":   12.95,
  "net_annual_saving_eur":             1310.0,
  "annual_volume":                     1000,
  "andler_quantity":                   231,
  "band_min_quantity":                 250,
  "band_max_quantity":                 500
}

effective_unit_cost_eur 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_eur is what moving to the recommended lot is worth per year, inventory included.

Perspectives. The holding rate is environment-scoped for a reason: a manufacturer values stock at manufacturing cost, while a buyer's capital is tied up at the purchase price (cost plus supplier margin) — scale holding_cost_rate_per_year accordingly (e.g. 8 % × price/cost ratio), and 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:

  • audiencehuman 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).
  • confidencequantified 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.


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": {
    "currency": "EUR",
    "unit_cost": 12.84,
    "unit_cost_pre_fx": 12.84,
    "attributed_cost_eur": 7.84,
    "attributed_cost_share": 0.61,
    "shared_cost_eur": 5.00,
    "shared_cost_share": 0.39,
    "total_machining_time_s": 206.2,
    "drivers": [
      {
        "rank": 1,
        "feature_id": "f3a1…",
        "face_uuid": null,
        "feature_kind": "pocket",
        "label": "4000 mm² pocket, 20.0 mm deep",
        "op_codes": ["pocket_mill"],
        "time_s": 184.2,
        "time_share": 0.893,
        "cost_eur": 5.91,
        "cost_share": 0.46,
        "cost_components": {
          "machine_eur": 3.55,
          "labour_eur": 2.07,
          "finishing_eur": 0.29
        },
        "reasons": ["deep pocket — multiple Z passes"]
      },
      {
        "rank": 2,
        "feature_id": "9c02…",
        "face_uuid": null,
        "feature_kind": "hole",
        "label": "Ø8.0×24 mm through hole",
        "op_codes": ["drill", "ream"],
        "time_s": 22.0,
        "time_share": 0.107,
        "cost_eur": 1.93,
        "cost_share": 0.15,
        "reasons": []
      }
    ]
  }
}

Field notes:

Field Meaning
rank 1 = the single biggest cost driver (drivers are pre-sorted by cost_eur descending).
feature_id / face_uuid Stable ids — face_uuid is the durable cross-extraction face identity (present for face drivers).
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.
op_codes The operations applied to that feature (e.g. a hole that is drilled then reamed).
time_s / time_share Per-part machining time attributed to the feature, and its fraction of total machining time.
cost_eur Attributed cost in pre-FX EUR (the line-item basis).
cost_share Fraction of the unit cost. Multiply by the top-level unit_cost to recover the figure in the display currency.
cost_components Run-cost split of cost_eurmachine_eur + labour_eur + finishing_eur sum to it exactly, so a consumer can trace each driver's figure back to the machine, labour, and finishing lines. Absent on calculations persisted before the field existed — gate on the key.
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 attributed_cost_eur + shared_cost_eur == unit_cost_pre_fx, 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