# Changelog
All notable changes to Millwright are recorded here. The format follows
[Keep a Changelog](https://keepachangelog.com/), and the project aims at
[Semantic Versioning](https://semver.org/).
## [Unreleased]
## [0.2.0] - 2026-08-23
### Added
- **rayon parallelism.** Cross-validation folds and search candidates now
evaluate in parallel: `cross_val_score` is fold-parallel, `Bagging` fits its
base estimators in parallel, and `AutoML::parallel()` adds candidate-level
parallelism. The core contract traits gained `Send + Sync` bounds (every
concrete model already satisfied them). Results stay seed-reproducible.
- **`Boosting`** — SAMME adaptive boosting over any weak learner (an
`alpha`-weighted vote of models each reweighted toward the last round's
mistakes), joining `Voting`/`Bagging`/`Stacking`.
- **Three more models over the smartcore backend:** `Knn` (k-nearest-neighbours),
`Svc` (support vector classifier, linear or RBF, one-vs-one for multiclass),
and `NaiveBayes` (Gaussian). All implement the same `Estimator`/`Predictor`
contract, so they drop into pipelines, ensembles, and search unchanged — and
they are exposed to Python too (`mw.Knn`, `mw.Svc` / `mw.Svc.rbf()`,
`mw.NaiveBayes`, plus `pipe.knn()` / `pipe.svc()` / `pipe.naive_bayes()`).
- **Python: a scikit-learn-shaped object API.** `mw.Frame` with `from_pandas` /
`from_numpy` / `from_rows` ingest; composable transformer/estimator objects
(`StandardScaler`, `MinMaxScaler`, `SimpleImputer`, `OneHotEncoder`,
`RandomForest`, `LinearRegression`) added via `pipe.step(name, obj)` /
`pipe.estimator(name, obj)`; `fit`/`predict`/`evaluate` accept a `Frame`.
- **Python: `pipeline.explain(...)`** returns SHAP feature importance
(`Explainer.kernel()` configurable), and **`pipeline.export_onnx(path)`**
writes the fitted pipeline to a single ONNX file. The `python` wheel now
bundles the `model-selection`, `explain`, and `onnx` engines.
- **Python: `GridSearch` / `KFold` / `StratifiedKFold`** over a pipeline, with
a `SearchResult` (`best_score`, `best_params()`, `predict()`).
- **`InferenceModel` is now an `Estimator` + `Predictor`**, so a pre-trained
ONNX model (from scikit-learn, PyTorch, …) can be dropped into a `Pipeline`
as a frozen estimator behind Millwright's preprocessing — in Rust and, via
`mw.OnnxModel(path)`, from Python.
- **Python: `mw.Table` + `mw.Profile`** — dtype-aware CSV/Parquet ingest and
automated EDA (`Profile.of(table_or_frame).to_html(path)`). The wheel now
bundles the `eda` (polars) engine, so it is larger than the pure-model build.
- **`Table::from_frame`** (Rust): build a numeric `Table` from a `Frame`, so the
numeric world can round-trip back into the typed one (e.g. to profile it).
- **Richer EDA in `Profile`** — excess `kurtosis` and z-score outlier counts per
numeric column; a **Spearman** rank-correlation matrix beside the Pearson one;
a **co-missing** map (columns whose null patterns correlate); high-cardinality
categoricals now suggest `TargetEncoder`; and `suggest_pipeline` adds a
train-time **SMOTE** balancer on class imbalance (with `preprocessing`).
- **AutoML is seeded by EDA.** With the `eda` engine on, the search fixes its
preprocessing to `Profile::suggest_pipeline()` and varies only the model,
pruning the space (it falls back to a scaler sweep without `eda`).
### Fixed
- **Cross-validated F1 no longer returns `NaN`.** A fold whose predictions
contain no true positives left smartcore's F1 evaluating `0/0`; a single NaN
fold poisoned the CV mean and a search's `best_score`. An undefined F1 is now
0.0 (scikit-learn's `zero_division=0` convention), so `GridSearch(...).scoring(F1)`
yields finite, comparable scores.
## [0.1.1]
### Added
- **`LogisticRegression`** — a native, core binary classifier with genuine
`predict_proba`: the framework's first real `ProbaPredictor`.
- **`calibration` feature** — `PlattScaling`, `IsotonicRegression`,
`reliability_curve`, and `CalibratedClassifier`, which wraps any
`ProbaPredictor` and returns calibrated probabilities.
- **`anomaly` feature** — `Mahalanobis` and `KnnScore`, unified behind an
`OutlierDetector` trait.
- **`eda` feature** — a polars-backed, dtype-aware `Table` (CSV/Parquet ingest
that lowers to the numeric `Frame`) and a typed `Profile` with an HTML report,
actionable alerts, and `suggest_pipeline()`.
- **Transformers** — `Winsorize`, `PowerTransform` (Yeo-Johnson),
`ColumnTransformer`, and the supervised `TargetEncoder`.
- **Convenience** — `Frame::from_csv` (dependency-free numeric loader),
`Table::head`.
- **Python** — `min_max_scaler`, `simple_imputer`, `one_hot`,
`linear_regression`, and `evaluate()`.
- **Examples** — `explore` (ingest → profile → pipeline) and `trust`
(calibration → reliability → anomaly detection).
- **Benchmarks** — `benches/throughput.rs` (criterion): the boundary conversion
and core fit/predict, backing the "Rust speed" claim.
- **One-hot ingest** — `Table::to_frame_with` / `into_dataset_with` and a
`CategoryEncoding` enum: lower nominal categories to 0/1 indicator columns
instead of ordinal codes.
- **Schema-aware preprocessing** — `Frame` carries a per-column `Dtype`; `Table`
marks categoricals as it lowers; scalers / `Winsorize` / `PowerTransform` pass
categorical columns through untouched, and `OneHotEncoder` encodes by dtype
rather than a value heuristic when the schema is known.
- **Real-data validation** — an end-to-end integration test on Quinlan's
PlayTennis (`tests/real_data.rs`): CSV → profile → suggested pipeline → fit.
### Changed
- Exact-version pins on every engine crate; `Cargo.lock` committed; a
feature-matrix CI (fmt, clippy `-D warnings`, docs, matrix, OS, examples,
benches, publish dry-run, wheel).
- `selection.rs` split into `selection/{scoring,cv,search}`.
- MSRV is **1.95** (dep-dictated — `sysinfo` via tract, and polars); enforced by
cargo via `rust-version` rather than a dedicated CI job (which would break on
every transitive bump). The default install needs 1.85.
- De-staled the crate and module docs (no more "Phase 0 · the spine").
### Fixed
- Golden tests and the crate doctest build under every feature subset (they were
unconditionally referencing backend-gated types).
- Float sorts use `f64::total_cmp`, closing a NaN-driven panic class.
## [0.1.0]
- Phases 0–8: the `Frame`/trait spine and smartcore backend, preprocessing and
model selection, a second backend (linfa) and HPO, evaluation/diagnostics/
explainability, ONNX export and inference, serving + drift monitoring + a model
registry, time-series and out-of-core estimators, AutoML, and 1.0 hardening.