Skip to content

Repository files navigation

Cost Estimation Engine

CI

A should-cost estimation system for discrete manufactured parts (CNC machining, injection molding, sheet metal, die casting). It combines a transparent parametric cost engine with a machine-learned layer that predicts what a real supplier quote is likely to look like, flags quotes that look like outliers, and explains why.

Should-costing is the practice of independently estimating what a part should cost to make — from material, process, and labor logic — so a buyer has a baseline to negotiate against instead of just trusting whatever number a supplier sends back. It's a real, recurring problem in sourcing and DFM (design-for-manufacturability) work; this project is a from-scratch implementation of that workflow, not a wrapper around someone else's tool.

On the data: every number in this repo comes from a synthetic dataset generated by a documented parametric model (see Data). No client, employer, or proprietary quote data is used anywhere.

Architecture

architecture

Two layers, kept deliberately separate:

  • Analytical engine (cost_engine/engine/) — deterministic, fully interpretable. Given a part spec and the assumptions in cost_engine/config/cost_assumptions.yaml, it returns a full cost breakdown (material / machine / labor / tooling / overhead / margin). No training, no data dependency — it's just engineering-economics logic and it's always available, even for a part type the ML model has never seen priced.
  • Learned layer (cost_engine/ml/, cost_engine/analysis/) — trained on a large synthetic dataset of (part spec → engine should-cost → simulated supplier quote) rows. Its job isn't to reproduce the engine — it's to predict the systematic bias and variance real quotes show around the should-cost baseline, give a calibrated uncertainty range, and flag quotes that don't fit the pattern.

Quickstart

python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt

# 1. generate the synthetic dataset (~8,000 rows, a few seconds)
python -m cost_engine.data.generator --n-rows 8000 --seed 7 --out data/generated/quotes_dataset.csv

# 2. train + evaluate all three models, run outlier detection, build every
#    plot in this README from scratch
python scripts/run_demo.py

# 3. estimate a single part
python -m cost_engine.cli estimate --process cnc_machining --material aluminum_6061 \
  --mass-g 220 --lot-size 500 --tolerance-class precision \
  --surface-finish as_machined --removed-volume-cm3 140 --explain

# 4. estimate a whole BOM/RFQ file
python -m cost_engine.cli batch --bom data/sample_bom.csv --out /tmp/bom_estimates.csv

Run the test suite with python -m pytest tests/ -q (15 tests, no network or pretrained model needed — they train small models on the fly).

Example: estimating one part

$ python -m cost_engine.cli estimate --process cnc_machining --material aluminum_6061 \
    --mass-g 220 --lot-size 500 --tolerance-class precision \
    --surface-finish as_machined --removed-volume-cm3 140 --explain
{
  "should_cost_breakdown": {
    "material_cost": 0.93, "machine_cost": 9.41, "labor_cost": 1.88,
    "tooling_cost_amortized": 0.0, "overhead_cost": 3.42,
    "margin_sga_cost": 2.81, "total_should_cost": 18.45
  },
  "assumption_uncertainty": {"p10": 15.32, "p50": 18.47, "p90": 21.69},
  "ml_quote_prediction": {
    "predicted_quote_p10": 16.25, "predicted_quote_p50": 19.65, "predicted_quote_p90": 23.37
  },
  "explanation_top_factors": [
    {"feature": "log_total_should_cost", "shap_contribution": -0.27},
    {"feature": "removed_volume_cm3", "shap_contribution": 0.017},
    {"feature": "log_lot_size", "shap_contribution": 0.013}
  ]
}

Two different uncertainty ranges are reported on purpose:

  • assumption_uncertainty — Monte Carlo over the engine's own assumptions (material price, machine rate, cycle time, overhead %, labor rate each perturbed randomly, 2,000 draws). Answers: how sensitive is my should-cost estimate to the assumptions I plugged in?
  • ml_quote_prediction — P10/P50/P90 from gradient-boosted quantile models trained on the simulated quote dataset. Answers: given how real quotes have varied historically, what should I expect a supplier to actually say?

cost_engine/engine/sensitivity.py also produces a full tornado breakdown per factor — see assets/sensitivity_tornado.png for the same example part.

Model comparison

Three models predict quoted_cost, evaluated on the same 20% held-out split (1,600 rows), stratified by process:

Model MAE MAPE
engine_baseline — should-cost × one calibration factor per process $27.07 16.28% 0.964
ridge — log-linear model on the full feature set $26.69 15.95% 0.965
hist_gbm — gradient-boosted trees on the full feature set $28.87 16.62% 0.951

model comparison

The nonlinear model doesn't win here, and that's the honest result, not a bug. The synthetic quote generator (cost_engine/data/generator.py) applies a per-process multiplicative bias plus lognormal noise to the engine's should-cost — which means the true relationship is, by construction, close to log-linear in should_cost and process. Ridge is a near-perfect functional match for that; HistGBM's extra flexibility has nothing real to fit and just adds variance. The SHAP summary below confirms it directly — log_total_should_cost swamps every other feature:

shap summary

So why keep hist_gbm in the repo at all? Two reasons: (1) it's what makes the P10/P90 quantile interval possible — Ridge doesn't natively produce a calibrated, heteroscedastic uncertainty band, and quote spread in this dataset genuinely does vary by tolerance class; (2) running this comparison is the deliverable — knowing when the fancier model isn't worth it is as useful a skill as building it. Per-process metrics (where the picture is similar) are in assets/results.json.

Interval calibration — a real limitation, not glossed over

Target: an 80% (P10–P90) interval should contain the true quote 80% of the time. Measured on the held-out set: 72.3% — the model is somewhat overconfident (intervals too narrow). Documented in Limitations rather than hidden; conformal calibration is the natural next step.

Outlier detection

cost_engine/analysis/outlier_detection.py flags quotes whose deviation from the model's expected price (per-process z-score on the log ratio) exceeds a threshold. Because the generator injects known outlier quotes (is_synthetic_outlier, ~5% of rows, never seen by the model), the detector can be scored against ground truth instead of just eyeballed:

Metric Value
Precision 72.7%
Recall 38.1%
F1 0.50
True outliers in test set 84 / 1,600

outlier scatter

Recall is the weaker number — at z >= 2.5 the detector is conservative by design (fewer false alarms for a human reviewer to chase). Lowering the threshold trades precision for recall; see Limitations.

Repository layout

cost_engine/config/cost_assumptions.yaml   material $/kg, machine rates, labor, overhead — all editable, nothing hardcoded
cost_engine/engine/                    the deterministic should-cost engine + sensitivity analysis
cost_engine/data/                      BOM/RFQ schema + parsing, synthetic dataset generator
cost_engine/ml/                        feature engineering, training, evaluation, SHAP explanations
cost_engine/analysis/                  outlier detection, scored against ground truth
cost_engine/cli.py                     estimate / batch / sensitivity commands
scripts/run_demo.py            regenerates every number and plot in this README from scratch
scripts/build_notebook.py      regenerates notebooks/01_exploratory_analysis.ipynb
data/                          dataset docs + a 10-line sample BOM for the batch demo
tests/                         15 tests covering the engine, generator, schema, and ML pipeline
assets/                        architecture diagram, plots, results.json

Data — nothing confidential here

The dataset is fully synthetic; see data/README.md for exactly how it's generated and why. In short: sampled part specs run through the deterministic engine, then a documented noise model simulates realistic supplier quote behavior around that baseline. No proprietary or client cost data — mine or anyone else's — appears anywhere in this repository.

Limitations & future improvements

  • Interval calibration is off (72.3% vs. target 80%) — worth fixing with conformal prediction before trusting the band for anything real.
  • The synthetic noise model is intentionally simple (per-process bias + lognormal spread). Real supplier quotes have more structure — regional cost differences, capacity cycles, relationship pricing — that a richer simulator (or, better, real anonymized quote history) would surface.
  • Outlier recall is conservative. A precision/recall curve across z-thresholds (rather than one fixed cutoff) would let a user pick their own tolerance for false alarms vs. missed outliers.
  • Cost assumptions are illustrative, not live market data — the whole point of keeping them in one YAML file is that they're meant to be replaced with real numbers for real use.
  • No CAD ingestion. Geometry features (removed_volume_cm3, cavity/bend counts) are inputs today; estimating them from an actual STEP/STL file would remove the main piece of manual BOM entry.

License

MIT — see LICENSE.

About

Should-cost estimation for manufactured parts: a parametric cost engine (CNC/injection molding/sheet metal/die casting) plus a gradient-boosted quote-prediction model with SHAP explanations, uncertainty intervals, and outlier detection against a synthetic RFQ dataset.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages