bdp-model-gate 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. bdp_model_gate-0.2.0/LICENSE +21 -0
  2. bdp_model_gate-0.2.0/PKG-INFO +374 -0
  3. bdp_model_gate-0.2.0/README.md +332 -0
  4. bdp_model_gate-0.2.0/bdp_model_gate/__init__.py +67 -0
  5. bdp_model_gate-0.2.0/bdp_model_gate/_logging.py +27 -0
  6. bdp_model_gate-0.2.0/bdp_model_gate/cli.py +248 -0
  7. bdp_model_gate-0.2.0/bdp_model_gate/config.py +114 -0
  8. bdp_model_gate-0.2.0/bdp_model_gate/core/__init__.py +6 -0
  9. bdp_model_gate-0.2.0/bdp_model_gate/core/base.py +47 -0
  10. bdp_model_gate-0.2.0/bdp_model_gate/core/context.py +49 -0
  11. bdp_model_gate-0.2.0/bdp_model_gate/core/gate.py +111 -0
  12. bdp_model_gate-0.2.0/bdp_model_gate/core/report.py +101 -0
  13. bdp_model_gate-0.2.0/bdp_model_gate/core/validation.py +120 -0
  14. bdp_model_gate-0.2.0/bdp_model_gate/exceptions.py +21 -0
  15. bdp_model_gate-0.2.0/bdp_model_gate/metrics.py +217 -0
  16. bdp_model_gate-0.2.0/bdp_model_gate/py.typed +0 -0
  17. bdp_model_gate-0.2.0/bdp_model_gate/registry.py +59 -0
  18. bdp_model_gate-0.2.0/bdp_model_gate/structured/__init__.py +56 -0
  19. bdp_model_gate-0.2.0/bdp_model_gate/structured/compliance.py +106 -0
  20. bdp_model_gate-0.2.0/bdp_model_gate/structured/fairness.py +299 -0
  21. bdp_model_gate-0.2.0/bdp_model_gate/structured/performance.py +141 -0
  22. bdp_model_gate-0.2.0/bdp_model_gate/structured/security.py +219 -0
  23. bdp_model_gate-0.2.0/bdp_model_gate/unstructured/__init__.py +34 -0
  24. bdp_model_gate-0.2.0/bdp_model_gate.egg-info/PKG-INFO +374 -0
  25. bdp_model_gate-0.2.0/bdp_model_gate.egg-info/SOURCES.txt +39 -0
  26. bdp_model_gate-0.2.0/bdp_model_gate.egg-info/dependency_links.txt +1 -0
  27. bdp_model_gate-0.2.0/bdp_model_gate.egg-info/entry_points.txt +2 -0
  28. bdp_model_gate-0.2.0/bdp_model_gate.egg-info/requires.txt +26 -0
  29. bdp_model_gate-0.2.0/bdp_model_gate.egg-info/top_level.txt +1 -0
  30. bdp_model_gate-0.2.0/pyproject.toml +97 -0
  31. bdp_model_gate-0.2.0/setup.cfg +4 -0
  32. bdp_model_gate-0.2.0/tests/test_check_coverage.py +145 -0
  33. bdp_model_gate-0.2.0/tests/test_cli.py +363 -0
  34. bdp_model_gate-0.2.0/tests/test_edge_cases.py +178 -0
  35. bdp_model_gate-0.2.0/tests/test_metrics.py +179 -0
  36. bdp_model_gate-0.2.0/tests/test_package.py +27 -0
  37. bdp_model_gate-0.2.0/tests/test_registry.py +12 -0
  38. bdp_model_gate-0.2.0/tests/test_registry_plugins.py +71 -0
  39. bdp_model_gate-0.2.0/tests/test_structured_gate.py +83 -0
  40. bdp_model_gate-0.2.0/tests/test_unstructured.py +16 -0
  41. bdp_model_gate-0.2.0/tests/test_validation.py +114 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kera Health
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,374 @@
1
+ Metadata-Version: 2.4
2
+ Name: bdp-model-gate
3
+ Version: 0.2.0
4
+ Summary: Automated pre-deployment ML model governance gate: fairness, performance, compliance, and security checks.
5
+ Author: Vangelis Oden
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/vanjy-eng/model-gate
8
+ Project-URL: Repository, https://github.com/vanjy-eng/model-gate
9
+ Project-URL: Changelog, https://github.com/vanjy-eng/model-gate/blob/main/CHANGELOG.md
10
+ Project-URL: Issues, https://github.com/vanjy-eng/model-gate/issues
11
+ Keywords: ml,governance,fairness,compliance,mlops,responsible-ai,data-science,predictive-modelling
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Topic :: Software Development :: Libraries
17
+ Classifier: Typing :: Typed
18
+ Requires-Python: >=3.9
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: numpy<3.0,>=1.23
22
+ Requires-Dist: pandas<3.0,>=1.5
23
+ Requires-Dist: joblib<2.0,>=1.2
24
+ Provides-Extra: structured
25
+ Requires-Dist: scikit-learn<2.0,>=1.2; extra == "structured"
26
+ Requires-Dist: fairlearn<0.13,>=0.9; extra == "structured"
27
+ Requires-Dist: shap<0.47,>=0.44; extra == "structured"
28
+ Provides-Extra: yaml
29
+ Requires-Dist: pyyaml<7.0,>=6.0; extra == "yaml"
30
+ Provides-Extra: toml
31
+ Requires-Dist: tomli<3.0,>=2.0; python_version < "3.11" and extra == "toml"
32
+ Provides-Extra: dev
33
+ Requires-Dist: pytest<9.0,>=7.0; extra == "dev"
34
+ Requires-Dist: pytest-cov<6.0,>=4.1; extra == "dev"
35
+ Requires-Dist: build<2.0,>=1.0; extra == "dev"
36
+ Requires-Dist: mypy<2.0,>=1.8; extra == "dev"
37
+ Requires-Dist: ruff<1.0,>=0.4; extra == "dev"
38
+ Requires-Dist: pandas-stubs; extra == "dev"
39
+ Requires-Dist: pyyaml<7.0,>=6.0; extra == "dev"
40
+ Requires-Dist: types-PyYAML<7.0,>=6.0; extra == "dev"
41
+ Dynamic: license-file
42
+
43
+ # BDP Model Gate
44
+
45
+ Automated pre-deployment ML model governance: fairness, performance,
46
+ compliance, and security checks, run as a single gate that gives you a
47
+ `PASS` / `NEEDS_REVIEW` / `BLOCKED` status to wire into CI before a model
48
+ is promoted to production.
49
+
50
+ Currently covers **structured data models**. Unstructured (text, image,
51
+ audio) support is planned — see `bdp_model_gate.unstructured` for the reserved
52
+ interface and roadmap notes.
53
+
54
+ ## Install
55
+
56
+ ```bash
57
+ # core (context/report/gate objects only — no check logic that needs ML libs)
58
+ pip install bdp-model-gate
59
+
60
+ # structured-data checks (fairlearn, shap, scikit-learn) — install this for real use
61
+ pip install bdp-model-gate[structured]
62
+
63
+ # for running the test suite
64
+ pip install bdp-model-gate[dev]
65
+ ```
66
+
67
+ Compliance and security checks (model card validation, adversarial
68
+ robustness, PII scanning, prompt-injection testing) work with just the core
69
+ install. Fairness checks need `fairlearn`/`shap`, and every performance
70
+ metric except `accuracy` needs `scikit-learn` — install the `structured`
71
+ extra to get all of it. On a core-only install the default `metric="auto"`
72
+ falls back to `accuracy` and says so loudly; see
73
+ [Choosing the performance metric](#choosing-the-performance-metric).
74
+
75
+ ## Quickstart
76
+
77
+ ```python
78
+ from bdp_model_gate import StructuredGateContext, ModelGate
79
+
80
+ context = StructuredGateContext(
81
+ model=my_model,
82
+ X=X_val,
83
+ y_true=y_val,
84
+ y_pred=y_pred,
85
+ protected_df=protected_val, # optional — enables fairness checks
86
+ latencies_ms=benchmark_latencies, # optional — enables performance checks
87
+ cost_per_inference=0.0008, # optional
88
+ model_card=my_model_card, # optional — enables compliance checks
89
+ generate_fn=None, # optional — set if there's a generative side-car
90
+ )
91
+
92
+ report = ModelGate().run(context)
93
+ print(report.summary())
94
+ report.to_json("gate_report.json")
95
+
96
+ if report.gate_status == "BLOCKED":
97
+ raise SystemExit("Model failed governance gate — see gate_report.json")
98
+ ```
99
+
100
+ Or the one-liner:
101
+
102
+ ```python
103
+ from bdp_model_gate import run_structured_gate
104
+
105
+ report = run_structured_gate(model, X_val, y_val, y_pred, protected_df=protected_val)
106
+ ```
107
+
108
+ ## What each category checks
109
+
110
+ **Fairness** (non-blocking by default — routes to `NEEDS_REVIEW`, since some
111
+ flags need human judgment)
112
+ - `ProxyCorrelationCheck` — input features that correlate with a protected attribute
113
+ - `DisparateImpactCheck` — outcome-level demographic parity
114
+ - `ShapSubgroupCheck` — features whose SHAP contribution differs across groups
115
+ - `CounterfactualFlipCheck` — prediction shift when a protected attribute is flipped
116
+
117
+ **Performance** (blocking)
118
+ - `PerformanceThresholdCheck` — model score on a metric you choose, p95
119
+ latency, cost-per-inference. See [Choosing the performance metric](#choosing-the-performance-metric).
120
+
121
+ **Compliance** (blocking)
122
+ - `ComplianceMappingCheck` — model card completeness, DPIA trigger for
123
+ high-risk use cases, explainability requirement for models affecting a person
124
+
125
+ **Security** (blocking)
126
+ - `AdversarialRobustnessCheck` — prediction flip rate under small feature perturbation
127
+ - `PIILeakageCheck` — regex scan of string columns for PII patterns
128
+ - `PromptInjectionCheck` — canned jailbreak prompts against any generative side-car
129
+
130
+ ## Customizing thresholds
131
+
132
+ ```python
133
+ from bdp_model_gate import GateConfig
134
+ from bdp_model_gate.structured import default_structured_checks
135
+ from bdp_model_gate import ModelGate
136
+
137
+ config = GateConfig()
138
+ config.performance.metric = "roc_auc"
139
+ config.performance.min_score = 0.85
140
+ config.fairness.disparity_threshold = 0.05
141
+
142
+ gate = ModelGate(checks=default_structured_checks(config))
143
+ report = gate.run(context)
144
+ ```
145
+
146
+ ## Choosing the performance metric
147
+
148
+ `PerformanceConfig.metric` decides what the model is scored on, and
149
+ `min_score` is the threshold that score must clear. Set the two together —
150
+ `min_score` means nothing on its own.
151
+
152
+ ```python
153
+ config = GateConfig()
154
+ config.performance.metric = "f1" # what to measure
155
+ config.performance.min_score = 0.75 # what it has to beat
156
+ ```
157
+
158
+ Built-in names: `roc_auc`, `average_precision`, `accuracy`,
159
+ `balanced_accuracy`, `f1`, `precision`, `recall`. All except `accuracy`
160
+ require scikit-learn (the `structured` extra).
161
+
162
+ **Label-based metrics need hard classes.** `accuracy`, `balanced_accuracy`,
163
+ `f1`, `precision`, and `recall` binarize continuous `y_pred` at
164
+ `config.performance.decision_threshold` (default `0.5`). Predictions already
165
+ in `{0, 1}` are left alone. Ranking metrics (`roc_auc`,
166
+ `average_precision`) use the raw scores and ignore the threshold.
167
+
168
+ **Your own metric.** Any `fn(y_true, y_pred) -> float` works, and is called
169
+ with `y_pred` exactly as you supplied it — no thresholding, since only you
170
+ know what your metric expects:
171
+
172
+ ```python
173
+ from sklearn.metrics import fbeta_score
174
+
175
+
176
+ def f2(y_true, y_pred):
177
+ return fbeta_score(y_true, (y_pred >= 0.3).astype(int), beta=2)
178
+
179
+
180
+ config.performance.metric = f2 # reported under the name "f2"
181
+ ```
182
+
183
+ **`"auto"` (the default)** uses `roc_auc` when scikit-learn is installed and
184
+ falls back to `accuracy` when it isn't. The fallback is never silent: it's
185
+ logged at `WARNING`, marked `metric_is_fallback: true` in the result
186
+ metadata, and spelled out in the check's detail string. A score is only
187
+ comparable to `min_score` if you know which metric produced it, so the
188
+ report always names it:
189
+
190
+ ```json
191
+ {
192
+ "gate_status": "PASS",
193
+ "model_metric": "roc_auc",
194
+ "model_score": 0.9132
195
+ }
196
+ ```
197
+
198
+ Naming a metric explicitly opts out of fallback entirely — if
199
+ `metric="roc_auc"` can't run, the gate reports a blocking `CHECK_ERROR`
200
+ rather than quietly scoring you on something else. A typo'd metric name
201
+ raises `GateConfigurationError` as soon as the check is constructed.
202
+
203
+ From the CLI, `--metric`, `--min-score`, and `--decision-threshold` do the
204
+ same thing, and take precedence over a `--config` file:
205
+
206
+ ```bash
207
+ bdp-model-gate --model model.joblib --data validation.csv --target-col label \
208
+ --metric f1 --min-score 0.75 --output gate_report.json
209
+ ```
210
+
211
+ > **Migrating from 0.1.0:** `min_accuracy` is now `min_score`, and the old
212
+ > name was misleading — it was compared against ROC AUC whenever
213
+ > scikit-learn was installed, and accuracy otherwise. `min_accuracy` still
214
+ > works (in Python and in `--config` files) but emits a `DeprecationWarning`.
215
+ > Likewise `GateReport.model_auc` is superseded by `model_metric` /
216
+ > `model_score`, and now returns `None` unless the metric really was AUC.
217
+
218
+ ## Writing your own check
219
+
220
+ ```python
221
+ from bdp_model_gate import BaseCheck, CheckResult
222
+
223
+
224
+ class MyCustomCheck(BaseCheck):
225
+ name = "my_custom_check"
226
+ category = "compliance" # fairness | performance | compliance | security
227
+ blocking = True
228
+
229
+ def run(self, context):
230
+ # inspect context.model, context.X, context.model_card, etc.
231
+ return [CheckResult(self.name, self.category, "OK", "looks fine", self.blocking)]
232
+
233
+
234
+ gate = ModelGate(checks=[MyCustomCheck()])
235
+ ```
236
+
237
+ ## Using it as a pre-deployment CI/CD gate
238
+
239
+ Installing the package gives you an `bdp-model-gate` console script, meant to
240
+ run as a **pre-deployment step** — after a model is trained/built, before
241
+ it's promoted to a registry or prod endpoint. It is not intended to run on
242
+ every PR.
243
+
244
+ ```bash
245
+ bdp-model-gate \
246
+ --model model.joblib \
247
+ --data validation.csv \
248
+ --target-col label \
249
+ --protected protected.csv \
250
+ --model-card model_card.json \
251
+ --cost-per-inference 0.0008 \
252
+ --output gate_report.json
253
+ ```
254
+
255
+ Exit codes are chosen so a pipeline can distinguish three outcomes:
256
+
257
+ | Exit code | Status | Pipeline behavior |
258
+ |---|---|---|
259
+ | `0` | `PASS` | proceed to deploy automatically |
260
+ | `2` | `NEEDS_REVIEW` | stop and require a human sign-off (fairness flags need judgment) |
261
+ | `1` | `BLOCKED` | hard fail — performance, compliance, or security check failed |
262
+
263
+ A ready-to-adapt **Azure Pipelines** example is in
264
+ [`ci_examples/azure-pipelines.model-gate.yml`](ci_examples/azure-pipelines.model-gate.yml),
265
+ and a **GitHub Actions** equivalent (a reusable `workflow_call` workflow) is in
266
+ [`ci_examples/github-actions.model-gate.yml`](ci_examples/github-actions.model-gate.yml).
267
+ Both structure this as three stages/jobs: run the gate, a manual-approval
268
+ step gated behind exit code `2` (GitHub Environments / Azure Environments
269
+ with required reviewers), and a deploy step that only runs if the gate
270
+ passed outright or was manually approved. Point them at wherever your
271
+ training pipeline publishes `model.joblib` / `validation.csv` /
272
+ `protected.csv` / `model_card.json` as a build artifact.
273
+
274
+ Config overrides for the CLI can be JSON, YAML, or TOML — pick whichever
275
+ matches your repo's conventions:
276
+
277
+ ```yaml
278
+ # config.yaml
279
+ performance:
280
+ metric: f1
281
+ min_score: 0.85
282
+ decision_threshold: 0.5
283
+ fairness:
284
+ disparity_threshold: 0.05
285
+ ```
286
+
287
+ ```bash
288
+ bdp-model-gate --model model.joblib --data validation.csv --target-col label \
289
+ --config config.yaml --output gate_report.json
290
+ ```
291
+
292
+ YAML configs need `pip install pyyaml` (or `bdp-model-gate[dev]`, which
293
+ already includes it); TOML needs `tomli` on Python < 3.11 (3.11+ has
294
+ `tomllib` built in).
295
+
296
+ Pass `-v`/`--verbose` for debug-level logging (per-check timing, which
297
+ checks ran/skipped and why) — the library uses the standard `logging`
298
+ module throughout, so it composes with whatever logging setup your
299
+ pipeline already has.
300
+
301
+ ## Extending with plugins
302
+
303
+ Third-party packages can register additional checks without forking this
304
+ library, via the `bdp_model_gate.checks` entry-point group:
305
+
306
+ ```toml
307
+ # in your plugin package's pyproject.toml
308
+ [project.entry-points."bdp_model_gate.checks"]
309
+ my_check = "my_package.checks:MyCustomCheck"
310
+ ```
311
+
312
+ Once installed alongside `bdp-model-gate`, `default_structured_checks()`
313
+ picks it up automatically (pass `include_plugins=False` to opt out). A
314
+ plugin that fails to import or isn't a `BaseCheck` subclass is logged and
315
+ skipped rather than crashing the gate.
316
+
317
+ ## Error handling
318
+
319
+ Bad inputs fail fast with a clear message rather than a confusing
320
+ exception from deep inside a check:
321
+
322
+ ```python
323
+ from bdp_model_gate import ModelGate, StructuredGateContext
324
+ from bdp_model_gate.exceptions import GateValidationError
325
+
326
+ try:
327
+ report = ModelGate().run(context)
328
+ except GateValidationError as exc:
329
+ print(f"Fix your inputs: {exc}")
330
+ ```
331
+
332
+ Validation covers: the model exposes `.predict()`, `X` is a non-empty
333
+ DataFrame, `y_true`/`y_pred`/`X` are aligned in length, `y_true` has at
334
+ least two classes, `protected_df` is row-aligned and has no all-NaN
335
+ columns, `model_card` is a dict, `generate_fn` is callable, and
336
+ `latencies_ms` has no negative values.
337
+
338
+ ## Roadmap
339
+
340
+ - Unstructured data support (text/image/audio) — `bdp_model_gate.unstructured` reserves
341
+ the shape (`UnstructuredGateContext`, a matching check suite) but raises
342
+ `NotImplementedError` until it lands.
343
+ - HTML/Markdown report rendering alongside `to_json()`.
344
+
345
+ ## Development
346
+
347
+ ```bash
348
+ pip install -e ".[dev,structured]"
349
+
350
+ ruff check . # lint
351
+ ruff format . # format
352
+ mypy bdp_model_gate # type check
353
+ pytest -q # test (85% coverage floor enforced)
354
+ ```
355
+
356
+ `.pre-commit-config.yaml` runs ruff, mypy, and basic hygiene checks on
357
+ every commit — install with `pip install pre-commit && pre-commit install`.
358
+
359
+ CI (`.github/workflows/ci.yml`) runs lint, type-check, and the test suite
360
+ across Python 3.9–3.12 on every push/PR, plus a **core-install job** with
361
+ no `structured` extra — that job is what keeps the graceful-degradation
362
+ paths (`NOT_APPLICABLE` results, metric fallback) honest. Tests that need
363
+ a real estimator `importorskip` on scikit-learn rather than failing there.
364
+
365
+ The 3.12 ceiling tracks the `shap<0.47` pin, which has no wheels beyond
366
+ 3.12; raise both together. Note `[tool.mypy] python_version` is pinned to
367
+ 3.12 for numpy's stubs, so 3.9 compatibility is enforced by ruff's
368
+ `target-version` and the 3.9 test job rather than by the type checker.
369
+
370
+ This is all separate from `ci_examples/`, which are pre-deployment gates
371
+ for models *built by* consumers of this library, not for the library's own
372
+ code.
373
+
374
+ See [`CHANGELOG.md`](CHANGELOG.md) for release history.