Reporting, comparison, tuning, MLflow, and tariff operations
Once candidates have a valid outer-test result, Azoic reuses the same Run for
human-readable reports, comparison, optional experiment tracking, and tariff
export.
Write model cards and comparisons
from pathlib import Path
from azoic.reporting import comparison_dashboard, comparison_table, model_card
Path("model-card.md").write_text(model_card(run), encoding="utf-8")
comparison = comparison_table([baseline_run, candidate_run])
comparison.to_csv("comparison.csv", index=False)
Path("comparison.html").write_text(
comparison_dashboard([baseline_run, candidate_run]),
encoding="utf-8",
)
A model card is Markdown and records the experiment data contract, fingerprint, split, features, model parameters, held-out metrics, and a calibration preview (first 12 rows; write full tables separately when every segment is needed).
Workflow runs snapshot the configuration and nested model parameters, so later changes to the caller's config do not rewrite a result. Public dictionaries and DataFrames remain mutable. The fingerprint includes categorical vocabulary (even unused levels), its dtype, declared category order, and the ordered flag. Category-aware fingerprints intentionally change; evaluate older runs again before comparing them with fresh results.
Comparison tables and dashboards warn once when runs differ in dataset fingerprint, target/exposure columns, or holdout membership. The warning names the affected runs and preserves every row for descriptive comparison. Different orders of the same holdout positions count as the same evaluated sample.
The CLI can run several configs and write their comparison table:
azoic compare baseline.yaml candidate.yaml --out comparison.csv
The Python-only comparison_dashboard API returns a standalone HTML string with
Plotly JavaScript embedded and requires the plot extra; the CSV table does
not. Write the string to a file as above and open it in a browser. There is no
dashboard CLI command.
Tune without touching outer test
uv add "azoic[tune]"
azoic tune --config experiment.yaml
Put optional trial settings and model-specific scalar distributions in the experiment YAML:
tuning:
n_trials: 30
calibration_penalty: 2.0
search_space:
tweedie-glm:
alpha:
type: float
low: 1.0e-6
high: 0.1
log: true
l1_ratio:
type: float
low: 0.0
high: 1.0
tweedie-gbm:
num_leaves:
type: int
low: 8
high: 64
step: 8
max_depth:
type: categorical
choices: [-1, 4, 6]
A custom entry replaces that model's built-in search space; it is not merged
with the defaults. A model without a custom entry keeps the existing GLM or GBM
space exactly. --trials and --calibration-penalty override YAML values.
Omitted flags defer to YAML, then to the legacy defaults of 20 trials and 1.0.
tune_experiment still creates an inner split of outer training data,
minimizes Tweedie deviance plus a numeric O/P penalty, refits selected
parameters on all outer training data, and evaluates outer test once.
Configuration changes the candidate values, not this leakage boundary.
Invalid bounds, non-finite values, incompatible step and log settings,
empty spaces or categorical choices, unknown models or estimator parameters,
and extra fields fail while parsing the config. Family, link, objective,
Tweedie powers, exposure routing, and random seeds cannot be tuned.
Frequency-severity tuning remains unsupported.
Scale the calibration penalty
abs(1 - O/P) is unitless while Tweedie deviance depends on the portfolio.
Increase the penalty only when calibration loss is too small to affect trial
ordering; do not tune it on the final test result.
Log a completed run to MLflow
from azoic.mlops import log_run
run_id = log_run(
run,
tracking_uri="sqlite:///mlflow.db",
experiment_name="motor-pricing",
artifacts=["model-card.md", "comparison.csv"],
)
print(run_id)
Install azoic[mlops] first. log_run records the data fingerprint,
experiment fields, model parameters, finite metrics, and the requested files or
directories. The run name defaults to the experiment config name. Artifacts
upload under artifact_path="artifacts" by default; a directory's contents
nest under artifacts/<dir_name> so the directory name is preserved, and a
missing artifact path raises FileNotFoundError. NaN and infinite metrics are
skipped because MLflow rejects non-finite values. Remote tracking remains an
environment concern; Azoic does not invent credentials or deployment policy.
Export a multiplicative tariff
A fitted log-link RiskGLM or pipeline ending in one can be written to a
three-sheet workbook:
azoic export-tariff \
--config experiment.yaml \
--model tweedie-glm \
--out tariff.xlsx
| Sheet | Contents |
|---|---|
base_rate |
Multiplicative base, family, resolved link, intercept, and recalibration metadata |
factors |
Numeric per-unit factors and categorical level relativities |
mappings |
Feature roles, levels, references, and fitted bin/group mappings |
Application sums the linear predictor before exponentiating. Invalid or
unrepresentable factors and rates raise ValueError; export also rejects
unrepresentable numeric per-unit factors. Zero observed claims can recalibrate
the base to zero. Workbook labels are literal text, including =1+1; numeric
and boolean level labels keep their types.
Recalibration is on by default and shifts the base to reproduce the observed
claim amount on Run.train_indices only. Direct GLMs and distilled students
keep the existing fit and holdout; the CLI does not refit on the full frame.
Holdout outcomes cannot change the exported rates or calibration factor.
Use --no-recalibrate when the structural model total is required. The tutorial
exports with recalibrate=False plus training-frame metadata and reports a
separate external training multiplier. Library export_tariff callers choose
their calibration frame explicitly and must supply a finite, non-negative 1D
vector of aggregate claim amounts with one value per calibration row. Keep
evaluation rows separate from that frame.
A positive-objective GBM is not itself a multiplicative table. Distill a held-out GLM student explicitly:
azoic export-tariff \
--config experiment.yaml \
--model tweedie-gbm \
--distill \
--out distilled-tariff.xlsx
The workbook describes the student and includes held-out teacher/student
fidelity metadata. The student is a log-link RiskGLM that inherits the
teacher's objective as its family -- and the Tweedie power when the objective
is tweedie -- fits with the RiskGLM default alpha=0.001, and reuses a copy
of the teacher's already-fitted preprocessing, so bins and group mappings are
not relearned from the holdout. Teacher predictions and exposure must be
positive and finite on both the fit and validation frames. Fidelity measures
agreement with teacher predictions, not accuracy against claims. The CLI uses
the run's stored training and test positions. Direct distill_gbm callers must
supply disjoint observations: the object-identity guard rejects the same frame
object, but separate objects or copies do not prove row independence. The
freMTPL2 tutorial applies the workbook
after fitted preprocessing and compares it with the direct GLM, raw GBM, and
structured teacher on the same held-out claims. It exports with
recalibrate=False and training-frame feature metadata, then reports raw metrics
and a separate external multiplier based on each candidate's own training O/P,
including the applied workbook. Calibration, Lorenz, and common training-bin age
comparisons accompany deviance, D², Gini, O/P, and portfolio totals.
Application boundary
Tariff application rejects unseen categorical levels and non-finite numeric inputs. Do not silently map an unknown quote-time category into a factor. Resolve the data contract or publish an explicitly approved mapping first.
Score an outcome-free frame
Fitted workflow pipelines do not need outcome columns at prediction time. Select exactly the fitted predictors, in order, followed by the configured exposure column. Keep policy identifiers and other metadata alongside the output, outside the prediction inputs:
from azoic.workflow import run_experiment
run, estimators = run_experiment(config, return_estimators=True)
pipeline = estimators["tweedie-glm"]
unlabeled = test[[*run.feature_names, config.spec.exposure]]
predictions = pipeline.predict(unlabeled)
scored = test[["policy_id"]].copy()
scored["predicted_rate"] = predictions
scored["expected_period_cost"] = predictions * test[config.spec.exposure].to_numpy()
Here test is your scoring DataFrame and policy_id is its identifier column;
substitute your identifier name. Merely dropping outcomes can leave unused
columns that the fitted estimator rejects.
When the config declares preprocessing, the fitted binner and grouper accept
outcome-free or feature-only frames at prediction time, and a drop_specials
step removes any present outcome columns before the final estimator so targets
never leak into prediction inputs. Frequency-severity pipelines strip the same
columns inside the meta-estimator instead.
Operational checklist
- Preserve the config, data fingerprint, and generated report together.
- Compare candidates on the same held-out rows.
- Record optional integrations without moving business logic into them.
- Check workbook totals and reference levels before deployment.
- Score an outcome-free frame to prove target and claim-count columns are not required at prediction time.
- Treat tariff deployment, approvals, monitoring, and rollback as downstream controls, not assumptions hidden inside a modelling library.
Review configuration and CLI details Open the freMTPL2 operations example