BDP Model Gate 0.7.0
Pre-deployment ML governance

Every model gets a verdict before it ships.

Validation, fairness, performance, compliance and security checks — and a comparison against the model already in production — that run after training and before promotion — reduced to one status your pipeline can branch on. Built for decisions that answer to someone: credit scoring, underwriting, pricing and claims.

Including the measures a pricing review actually uses: exposure-weighted actual-versus-expected by decile, the Lorenz Gini against the ceiling your book allows, filed monotonicity constraints checked by partial dependence, and how much of the book moves when a tariff replaces its incumbent.

Every threshold now carries a confidence interval, and where it sits decides the verdict: a finding, a clean pass, or the data cannot say — which is a governance conversation rather than a broken build. On a floor set where a model actually sits, that took a gate from disagreeing with itself on nine of ten random halvings of the same data to none.

And it tests the generative side-car bolted to your model for indirect prompt injection — untrusted text arriving as a claim note or a customer email rather than as a chat message, which is the attack a regulated pipeline actually faces. Canary-based, so a leak is proof rather than a guess.

Get started Documentation $pip install "bdp-model-gate[structured]"
PASS exit 0

Deploy automatically

Nothing flagged. The pipeline promotes the model without a human in the loop.

NEEDS_REVIEW exit 2

Stop for sign-off

Only non-blocking flags. Fairness findings need judgement, so they route to a person rather than failing the build.

BLOCKED exit 1

Hard fail

A validation, performance, compliance or security check failed. The build stops and the report says which.

What the reviewer gets

NEEDS_REVIEW hands the decision to a person.

So hand them a page, not a blob. report.to_html("gate-report.html") writes one self-contained file: the verdict in plain words, every finding with its evidence, and a chart beside each number whose shape is what has to be judged — a reliability curve, a threshold sweep showing whether the verdict survives a different cutoff, an actual-versus-expected curve showing whether the book is short everywhere or only in its top decile.

No JavaScript, no stylesheet, no font, nothing fetched from anywhere. It opens offline years later and prints clean, because that is what a governance record has to do.

See what is in the report

Documentation

Where to go next

Thirty-three checks, six categories

What gets checked

Optional inputs degrade rather than fail — omit protected attributes and the fairness checks report NOT_APPLICABLE instead of erroring. The gate grades what you give it.

Validation

Blocking
  • target_leakage — does one column do what the whole model does?
  • split_overlap — has the model already seen these rows?
  • validation_strategy — out-of-time holdout, or a random split?
  • feature_contract — the right columns, in the right order
  • feature_drift — does validation still look like training?

Comparison

Blocking
  • comparison_integrity — can the two models be compared at all?
  • challenger_performance — at least as good as the model it replaces?
  • challenger_guardrails — did a secondary metric regress?
  • subgroup_regression — better on average, and worse for someone? (review)
  • fairness_delta — did a disparity widen? (review)
  • decision_churn — fixes against breaks (review)
  • operational_delta — slower or dearer to run? (review)

Fairness

Needs review
  • proxy_correlation — features that encode a protected attribute
  • disparate_impact — demographic parity
  • shap_subgroup_gap — per-group contribution gaps
  • counterfactual_flip — prediction shift on attribute flip
  • equalised_odds — equal opportunity, and the wider TPR/FPR gap
  • subgroup_calibration — does a score mean the same for every group?

Fairness, regression

Needs review
  • loss_ratio_parity — margin charged over each group's own expected loss
  • group_mean_gap — spread in mean prediction
  • error_parity — is the model worse for one group?
  • calibration_parity — systematic over- or under-prediction

Performance

Blocking
  • Score on a metric you choose — thirteen built in, or your own callable
  • calibration — do the stated probabilities match reality?
  • Every threshold with a confidence interval, and UNCERTAIN when the data cannot decide
  • p95 latency against a benchmark run
  • Cost per inference

Pricing

Blocking
  • actual_vs_expected — the rate level, then the same ratio decile by decile
  • risk_discrimination — the Lorenz Gini, against the ceiling this book allows
  • monotonicity — does premium still rise with prior claims?
  • prediction_dislocation — who moves, and by how much

Compliance

Blocking
  • Model card completeness
  • DPIA required for a high-risk use case
  • Explainability documented where a decision affects a person

Security

Blocking
  • adversarial_robustness — gradient-directed or random perturbation
  • pii_leakage — raw identifiers in feature columns
  • prompt_injection — a categorised corpus, on the direct and indirect surfaces
  • report_injection — instruction-shaped text this report would carry onward

Yours

Your call
  • Subclass BaseCheck, implement run(context)
  • Declare blocking and supported_tasks
  • Ship checks from another package via an entry point
Coverage

Any task, any model

Nothing here imports a deep-learning framework. Pass an estimator with .predict(), or a plain fn(DataFrame) -> array — the boundary is deliberately narrow, so tensor conversion, batching and auth stay in your code. The validation checks apply to all three tasks; the fairness suite changes shape with the target.

TaskMetricsFairness
Binary
credit, fraud, lapse
roc_auc, average_precision, f1, … All three families: parity, equalised odds, subgroup calibration — plus proxy, SHAP, counterfactual
Multiclass & ordinal
underwriting decisions
quadratic_kappa, ordinal_mae, macro-averaged All three families, against a favourable outcome you define
Regression
pricing, severity, frequency
rmse, mape, poisson_deviance, lorenz_gini, r2 Loss-ratio, mean gap, error parity, calibration — all exposure-weighted
ModelWhat you pass
scikit-learn, LightGBM, XGBoost sklearn APImodel=estimator
Kerasmodel=, plus predict_proba_fn for the (n, 1) output
PyTorch nn.Modulepredict_fn, predict_proba_fn, gradient_fn
XGBoost native Boosterpredict_fn wrapping a DMatrix
Remote scoring endpointpredict_fn only — no model object at all
In your pipeline

Three lines, or one command

Build a context, run the gate, branch on the verdict. The same suite runs from Python or as a console script, and writes a JSON report your reviewers can read months later — it records which metric ran, which checks were skipped, and why.

Exit codes separate deploy from ask a human, which is the distinction most CI gates collapse.

from bdp_model_gate import ModelGate, StructuredGateContext

context = StructuredGateContext(
    model=pricing_model,
    X=X_val, y_true=loss_cost, y_pred=quoted_premium,
    protected_df=protected_val,
    exposure=earned_vehicle_years,    # the target is a rate
    baseline_pred=premium_v3,         # what this replaces
    expected_loss=technical_premium,  # margin parity
    task="regression",
)

report = ModelGate().run(context)
report.to_json("gate_report.json")

if report.gate_status == "BLOCKED":
    raise SystemExit(1)
Configurable to your regime

Out of the box the compliance and PII checks target NDPA/NDPR — patterns for NIN, BVN and Nigerian phone formats, with pricing, underwriting, credit scoring and claims decisioning as DPIA triggers. Every pattern, required field and high-risk use case is a plain list you replace, so the same suite runs against GDPR, CCPA or an internal standard. Defaults are a starting point, not regulatory advice.