valiron 0.1.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.
- valiron-0.1.0/.github/workflows/ci.yml +41 -0
- valiron-0.1.0/.gitignore +42 -0
- valiron-0.1.0/LICENSE +21 -0
- valiron-0.1.0/PKG-INFO +163 -0
- valiron-0.1.0/README.md +109 -0
- valiron-0.1.0/docs/cdsco_guide.md +32 -0
- valiron-0.1.0/docs/eu_ai_act_guide.md +31 -0
- valiron-0.1.0/docs/fda_guide.md +28 -0
- valiron-0.1.0/examples/finance_model/run.py +26 -0
- valiron-0.1.0/examples/healthcare_model/run.py +28 -0
- valiron-0.1.0/examples/hr_model/run.py +26 -0
- valiron-0.1.0/pyproject.toml +57 -0
- valiron-0.1.0/tests/__init__.py +0 -0
- valiron-0.1.0/tests/test_calibration.py +30 -0
- valiron-0.1.0/tests/test_evaluate.py +47 -0
- valiron-0.1.0/tests/test_report.py +41 -0
- valiron-0.1.0/tests/test_subgroups.py +33 -0
- valiron-0.1.0/valiron/__init__.py +7 -0
- valiron-0.1.0/valiron/acp/__init__.py +1 -0
- valiron-0.1.0/valiron/calibration/__init__.py +5 -0
- valiron-0.1.0/valiron/calibration/checker.py +39 -0
- valiron-0.1.0/valiron/evaluate/__init__.py +5 -0
- valiron-0.1.0/valiron/evaluate/checks.py +117 -0
- valiron-0.1.0/valiron/evaluate/runner.py +62 -0
- valiron-0.1.0/valiron/report/__init__.py +5 -0
- valiron-0.1.0/valiron/report/builder.py +43 -0
- valiron-0.1.0/valiron/report/templates/report.html.j2 +59 -0
- valiron-0.1.0/valiron/subgroups/__init__.py +5 -0
- valiron-0.1.0/valiron/subgroups/analyzer.py +33 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.9", "3.10", "3.11", "3.12"]
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
20
|
+
uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: ${{ matrix.python-version }}
|
|
23
|
+
|
|
24
|
+
- name: Install dependencies
|
|
25
|
+
run: |
|
|
26
|
+
python -m pip install --upgrade pip
|
|
27
|
+
pip install -e ".[dev]"
|
|
28
|
+
|
|
29
|
+
- name: Lint
|
|
30
|
+
run: ruff check valiron tests
|
|
31
|
+
|
|
32
|
+
- name: Type check
|
|
33
|
+
run: mypy valiron
|
|
34
|
+
|
|
35
|
+
- name: Test
|
|
36
|
+
run: pytest --cov=valiron --cov-report=xml --cov-fail-under=80
|
|
37
|
+
|
|
38
|
+
- name: Upload coverage
|
|
39
|
+
uses: codecov/codecov-action@v4
|
|
40
|
+
with:
|
|
41
|
+
file: ./coverage.xml
|
valiron-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
*.egg-info/
|
|
9
|
+
.installed.cfg
|
|
10
|
+
*.egg
|
|
11
|
+
|
|
12
|
+
# Virtual environments
|
|
13
|
+
.env
|
|
14
|
+
.venv
|
|
15
|
+
env/
|
|
16
|
+
venv/
|
|
17
|
+
|
|
18
|
+
# Testing
|
|
19
|
+
.coverage
|
|
20
|
+
.coverage.*
|
|
21
|
+
.pytest_cache/
|
|
22
|
+
.tox/
|
|
23
|
+
|
|
24
|
+
# Type checking
|
|
25
|
+
.mypy_cache/
|
|
26
|
+
|
|
27
|
+
# IDEs
|
|
28
|
+
.idea/
|
|
29
|
+
.vscode/
|
|
30
|
+
|
|
31
|
+
# OS
|
|
32
|
+
.DS_Store
|
|
33
|
+
Thumbs.db
|
|
34
|
+
|
|
35
|
+
# Distribution
|
|
36
|
+
*.whl
|
|
37
|
+
*.tar.gz
|
|
38
|
+
|
|
39
|
+
# Secrets
|
|
40
|
+
.env.local
|
|
41
|
+
*.pem
|
|
42
|
+
*.key
|
valiron-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Abhay Sachan
|
|
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.
|
valiron-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: valiron
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: AI regulatory compliance validation for Python
|
|
5
|
+
Project-URL: Homepage, https://github.com/abhaysachan007/valiron
|
|
6
|
+
Project-URL: Issues, https://github.com/abhaysachan007/valiron/issues
|
|
7
|
+
Author: Abhay Sachan
|
|
8
|
+
License: MIT License
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2026 Abhay Sachan
|
|
11
|
+
|
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
in the Software without restriction, including without limitation the rights
|
|
15
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
furnished to do so, subject to the following conditions:
|
|
18
|
+
|
|
19
|
+
The above copyright notice and this permission notice shall be included in all
|
|
20
|
+
copies or substantial portions of the Software.
|
|
21
|
+
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
28
|
+
SOFTWARE.
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
Keywords: ai,compliance,ml,regulation,validation
|
|
31
|
+
Classifier: Development Status :: 3 - Alpha
|
|
32
|
+
Classifier: Intended Audience :: Developers
|
|
33
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
34
|
+
Classifier: Programming Language :: Python :: 3
|
|
35
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
36
|
+
Requires-Python: >=3.9
|
|
37
|
+
Requires-Dist: jinja2>=3.0
|
|
38
|
+
Requires-Dist: numpy>=1.21
|
|
39
|
+
Requires-Dist: pandas>=1.3
|
|
40
|
+
Requires-Dist: scikit-learn>=1.0
|
|
41
|
+
Requires-Dist: scipy>=1.7
|
|
42
|
+
Provides-Extra: dev
|
|
43
|
+
Requires-Dist: mypy>=1.0; extra == 'dev'
|
|
44
|
+
Requires-Dist: pytest-cov>=4; extra == 'dev'
|
|
45
|
+
Requires-Dist: pytest>=7; extra == 'dev'
|
|
46
|
+
Requires-Dist: ruff>=0.1; extra == 'dev'
|
|
47
|
+
Provides-Extra: onnx
|
|
48
|
+
Requires-Dist: onnxruntime>=1.12; extra == 'onnx'
|
|
49
|
+
Provides-Extra: pdf
|
|
50
|
+
Requires-Dist: weasyprint>=57; extra == 'pdf'
|
|
51
|
+
Provides-Extra: torch
|
|
52
|
+
Requires-Dist: torch>=1.10; extra == 'torch'
|
|
53
|
+
Description-Content-Type: text/markdown
|
|
54
|
+
|
|
55
|
+
# Valiron
|
|
56
|
+
|
|
57
|
+
**AI Regulatory Compliance Validation for Python**
|
|
58
|
+
|
|
59
|
+
Valiron is a Python library that helps ML engineers and data scientists validate their AI/ML models against global regulatory frameworks — before deployment.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Problem
|
|
64
|
+
|
|
65
|
+
Deploying AI in regulated industries (healthcare, finance, HR) requires compliance with frameworks like the EU AI Act, FDA AI/ML SaMD guidance, CDSCO MDSW, and RBI ML Model Risk guidelines. Manual compliance checks are slow, inconsistent, and expensive.
|
|
66
|
+
|
|
67
|
+
## Solution
|
|
68
|
+
|
|
69
|
+
Valiron automates compliance validation with a simple API:
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
import valiron
|
|
73
|
+
|
|
74
|
+
result = valiron.evaluate(
|
|
75
|
+
model=my_sklearn_model,
|
|
76
|
+
X_test=X_test,
|
|
77
|
+
y_test=y_test,
|
|
78
|
+
regulation="eu_ai_act",
|
|
79
|
+
use_case="medical_diagnosis"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
report = valiron.report(result, format="pdf")
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Compliance Coverage
|
|
88
|
+
|
|
89
|
+
| Regulation | Status | Key Checks |
|
|
90
|
+
|---|---|---|
|
|
91
|
+
| EU AI Act Annex III | ✅ | High-risk classification, transparency, human oversight |
|
|
92
|
+
| FDA AI/ML SaMD | ✅ | Predetermined change control, performance monitoring |
|
|
93
|
+
| CDSCO MDSW (India) | ✅ | Software as medical device validation |
|
|
94
|
+
| RBI ML Model Risk | ✅ | Model governance, bias detection, explainability |
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Quickstart
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
pip install valiron
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
import valiron
|
|
106
|
+
from sklearn.ensemble import RandomForestClassifier
|
|
107
|
+
|
|
108
|
+
# Train your model
|
|
109
|
+
model = RandomForestClassifier()
|
|
110
|
+
model.fit(X_train, y_train)
|
|
111
|
+
|
|
112
|
+
# Validate compliance
|
|
113
|
+
result = valiron.evaluate(
|
|
114
|
+
model=model,
|
|
115
|
+
X_test=X_test,
|
|
116
|
+
y_test=y_test,
|
|
117
|
+
regulation="cdsco_mdsw",
|
|
118
|
+
use_case="diagnostic_aid",
|
|
119
|
+
sensitive_features=["age", "gender"]
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# Check results
|
|
123
|
+
print(result.compliant) # True/False
|
|
124
|
+
print(result.score) # 0.0 - 1.0
|
|
125
|
+
print(result.failing_checks) # list of failed requirements
|
|
126
|
+
|
|
127
|
+
# Generate report
|
|
128
|
+
valiron.report(result, format="html", output="compliance_report.html")
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Supported Frameworks
|
|
134
|
+
|
|
135
|
+
- scikit-learn
|
|
136
|
+
- PyTorch
|
|
137
|
+
- ONNX
|
|
138
|
+
- Raw predictions (numpy arrays)
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Data Privacy Architecture
|
|
143
|
+
|
|
144
|
+
- **No data leaves your machine.** All validation runs locally.
|
|
145
|
+
- No model weights are transmitted.
|
|
146
|
+
- Reports are generated on-device.
|
|
147
|
+
- Optional: anonymize test data before validation with `valiron.anonymize()`.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Roadmap
|
|
152
|
+
|
|
153
|
+
- [ ] ISO 42001 (AI Management Systems)
|
|
154
|
+
- [ ] HIPAA AI compliance checks
|
|
155
|
+
- [ ] SEBI AI governance framework
|
|
156
|
+
- [ ] CI/CD integration (GitHub Actions, GitLab CI)
|
|
157
|
+
- [ ] VS Code extension
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## License
|
|
162
|
+
|
|
163
|
+
MIT — see [LICENSE](LICENSE)
|
valiron-0.1.0/README.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Valiron
|
|
2
|
+
|
|
3
|
+
**AI Regulatory Compliance Validation for Python**
|
|
4
|
+
|
|
5
|
+
Valiron is a Python library that helps ML engineers and data scientists validate their AI/ML models against global regulatory frameworks — before deployment.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Problem
|
|
10
|
+
|
|
11
|
+
Deploying AI in regulated industries (healthcare, finance, HR) requires compliance with frameworks like the EU AI Act, FDA AI/ML SaMD guidance, CDSCO MDSW, and RBI ML Model Risk guidelines. Manual compliance checks are slow, inconsistent, and expensive.
|
|
12
|
+
|
|
13
|
+
## Solution
|
|
14
|
+
|
|
15
|
+
Valiron automates compliance validation with a simple API:
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
import valiron
|
|
19
|
+
|
|
20
|
+
result = valiron.evaluate(
|
|
21
|
+
model=my_sklearn_model,
|
|
22
|
+
X_test=X_test,
|
|
23
|
+
y_test=y_test,
|
|
24
|
+
regulation="eu_ai_act",
|
|
25
|
+
use_case="medical_diagnosis"
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
report = valiron.report(result, format="pdf")
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Compliance Coverage
|
|
34
|
+
|
|
35
|
+
| Regulation | Status | Key Checks |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| EU AI Act Annex III | ✅ | High-risk classification, transparency, human oversight |
|
|
38
|
+
| FDA AI/ML SaMD | ✅ | Predetermined change control, performance monitoring |
|
|
39
|
+
| CDSCO MDSW (India) | ✅ | Software as medical device validation |
|
|
40
|
+
| RBI ML Model Risk | ✅ | Model governance, bias detection, explainability |
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Quickstart
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install valiron
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
import valiron
|
|
52
|
+
from sklearn.ensemble import RandomForestClassifier
|
|
53
|
+
|
|
54
|
+
# Train your model
|
|
55
|
+
model = RandomForestClassifier()
|
|
56
|
+
model.fit(X_train, y_train)
|
|
57
|
+
|
|
58
|
+
# Validate compliance
|
|
59
|
+
result = valiron.evaluate(
|
|
60
|
+
model=model,
|
|
61
|
+
X_test=X_test,
|
|
62
|
+
y_test=y_test,
|
|
63
|
+
regulation="cdsco_mdsw",
|
|
64
|
+
use_case="diagnostic_aid",
|
|
65
|
+
sensitive_features=["age", "gender"]
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# Check results
|
|
69
|
+
print(result.compliant) # True/False
|
|
70
|
+
print(result.score) # 0.0 - 1.0
|
|
71
|
+
print(result.failing_checks) # list of failed requirements
|
|
72
|
+
|
|
73
|
+
# Generate report
|
|
74
|
+
valiron.report(result, format="html", output="compliance_report.html")
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Supported Frameworks
|
|
80
|
+
|
|
81
|
+
- scikit-learn
|
|
82
|
+
- PyTorch
|
|
83
|
+
- ONNX
|
|
84
|
+
- Raw predictions (numpy arrays)
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Data Privacy Architecture
|
|
89
|
+
|
|
90
|
+
- **No data leaves your machine.** All validation runs locally.
|
|
91
|
+
- No model weights are transmitted.
|
|
92
|
+
- Reports are generated on-device.
|
|
93
|
+
- Optional: anonymize test data before validation with `valiron.anonymize()`.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Roadmap
|
|
98
|
+
|
|
99
|
+
- [ ] ISO 42001 (AI Management Systems)
|
|
100
|
+
- [ ] HIPAA AI compliance checks
|
|
101
|
+
- [ ] SEBI AI governance framework
|
|
102
|
+
- [ ] CI/CD integration (GitHub Actions, GitLab CI)
|
|
103
|
+
- [ ] VS Code extension
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## License
|
|
108
|
+
|
|
109
|
+
MIT — see [LICENSE](LICENSE)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# CDSCO MDSW Compliance Guide
|
|
2
|
+
|
|
3
|
+
## What is CDSCO MDSW?
|
|
4
|
+
|
|
5
|
+
CDSCO (Central Drugs Standard Control Organisation) regulates Medical Device Software (MDSW) in India under the Medical Devices Rules, 2017. AI/ML software used for clinical diagnosis or treatment requires compliance.
|
|
6
|
+
|
|
7
|
+
## Checks Performed by Valiron
|
|
8
|
+
|
|
9
|
+
| Check | Threshold | Description |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| `CDSCO_MDSW_PERFORMANCE` | accuracy ≥ 0.70 | Minimum diagnostic accuracy |
|
|
12
|
+
| `CDSCO_MDSW_DATA_PRIVACY` | local only | No patient data transmitted |
|
|
13
|
+
| `CDSCO_MDSW_REGISTRATION` | warning | Reminder to register with CDSCO |
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
result = valiron.evaluate(
|
|
19
|
+
model=model,
|
|
20
|
+
X_test=X_test,
|
|
21
|
+
y_test=y_test,
|
|
22
|
+
regulation="cdsco_mdsw",
|
|
23
|
+
use_case="diagnostic_aid",
|
|
24
|
+
)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Pre-Deployment Checklist
|
|
28
|
+
|
|
29
|
+
- [ ] Accuracy ≥ 70% on representative Indian patient population
|
|
30
|
+
- [ ] MDSW registered with CDSCO before clinical use
|
|
31
|
+
- [ ] Clinical validation study conducted
|
|
32
|
+
- [ ] Post-market surveillance plan in place
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# EU AI Act Compliance Guide
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The EU AI Act classifies AI systems by risk. Annex III (high-risk) includes medical devices, employment screening, credit scoring, and biometric identification. High-risk systems face mandatory requirements before EU market placement.
|
|
6
|
+
|
|
7
|
+
## Checks Performed by Valiron
|
|
8
|
+
|
|
9
|
+
| Check | Threshold | Description |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| `EU_AI_ACT_PERFORMANCE` | accuracy ≥ 0.70 | Minimum acceptable performance |
|
|
12
|
+
| `EU_AI_ACT_TRANSPARENCY` | model attribute check | Feature importances or coefficients present |
|
|
13
|
+
| `EU_AI_ACT_HIGH_RISK` | use_case keyword scan | Flags Annex III use cases |
|
|
14
|
+
| `EU_AI_ACT_BIAS` | sensitive_features provided | Fairness audit feasibility |
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
result = valiron.evaluate(
|
|
20
|
+
model=model, X_test=X_test, y_test=y_test,
|
|
21
|
+
regulation="eu_ai_act", use_case="recruitment_screening",
|
|
22
|
+
sensitive_features=["gender", "age_group"],
|
|
23
|
+
)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Key Requirements for High-Risk AI
|
|
27
|
+
|
|
28
|
+
- Human oversight mechanisms
|
|
29
|
+
- Transparency and documentation
|
|
30
|
+
- Data governance and bias checks
|
|
31
|
+
- Technical documentation maintained
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# FDA AI/ML SaMD Compliance Guide
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The FDA regulates AI/ML-based Software as a Medical Device (SaMD). The 2021 Action Plan emphasizes a "predetermined change control plan" (PCCP) for adaptive models.
|
|
6
|
+
|
|
7
|
+
## Checks Performed by Valiron
|
|
8
|
+
|
|
9
|
+
| Check | Threshold | Description |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| `FDA_SAMD_PERFORMANCE` | accuracy ≥ 0.75 | Higher bar for medical device context |
|
|
12
|
+
| `FDA_SAMD_PDCP` | warning | Predetermined change control plan required |
|
|
13
|
+
| `FDA_SAMD_MONITORING` | warning | Real-world performance monitoring plan |
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
result = valiron.evaluate(
|
|
19
|
+
model=model, X_test=X_test, y_test=y_test,
|
|
20
|
+
regulation="fda_samd", use_case="diagnostic_imaging_aid",
|
|
21
|
+
)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Key Requirements
|
|
25
|
+
|
|
26
|
+
- **PCCP**: document how model updates without new 510(k) submission
|
|
27
|
+
- **Real-World Monitoring**: define post-market drift thresholds
|
|
28
|
+
- **Labelling**: indicate AI/ML nature of device
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Finance model compliance example — RBI ML Model Risk."""
|
|
2
|
+
|
|
3
|
+
from sklearn.linear_model import LogisticRegression
|
|
4
|
+
from sklearn.datasets import make_classification
|
|
5
|
+
|
|
6
|
+
import valiron
|
|
7
|
+
|
|
8
|
+
X, y = make_classification(n_samples=500, n_features=20, random_state=1)
|
|
9
|
+
X_train, X_test = X[:400], X[400:]
|
|
10
|
+
y_train, y_test = y[:400], y[400:]
|
|
11
|
+
|
|
12
|
+
model = LogisticRegression(max_iter=200, random_state=1)
|
|
13
|
+
model.fit(X_train, y_train)
|
|
14
|
+
|
|
15
|
+
result = valiron.evaluate(
|
|
16
|
+
model=model,
|
|
17
|
+
X_test=X_test,
|
|
18
|
+
y_test=y_test,
|
|
19
|
+
regulation="rbi_ml_risk",
|
|
20
|
+
use_case="credit_scoring",
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
print(f"Compliant: {result.compliant}")
|
|
24
|
+
print(f"Score: {result.score:.1%}")
|
|
25
|
+
valiron.report(result, format="html", output="finance_compliance.html")
|
|
26
|
+
print("Report saved to finance_compliance.html")
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Healthcare model compliance example — CDSCO MDSW."""
|
|
2
|
+
|
|
3
|
+
from sklearn.ensemble import RandomForestClassifier
|
|
4
|
+
from sklearn.datasets import make_classification
|
|
5
|
+
|
|
6
|
+
import valiron
|
|
7
|
+
|
|
8
|
+
X, y = make_classification(n_samples=500, n_features=15, random_state=0)
|
|
9
|
+
X_train, X_test = X[:400], X[400:]
|
|
10
|
+
y_train, y_test = y[:400], y[400:]
|
|
11
|
+
|
|
12
|
+
model = RandomForestClassifier(n_estimators=50, random_state=0)
|
|
13
|
+
model.fit(X_train, y_train)
|
|
14
|
+
|
|
15
|
+
result = valiron.evaluate(
|
|
16
|
+
model=model,
|
|
17
|
+
X_test=X_test,
|
|
18
|
+
y_test=y_test,
|
|
19
|
+
regulation="cdsco_mdsw",
|
|
20
|
+
use_case="diagnostic_aid",
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
print(f"Compliant: {result.compliant}")
|
|
24
|
+
print(f"Score: {result.score:.1%}")
|
|
25
|
+
print(f"Failing: {result.failing_checks or 'none'}")
|
|
26
|
+
|
|
27
|
+
valiron.report(result, format="html", output="healthcare_compliance.html")
|
|
28
|
+
print("Report saved to healthcare_compliance.html")
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""HR model compliance example — EU AI Act (employment/recruitment)."""
|
|
2
|
+
|
|
3
|
+
from sklearn.tree import DecisionTreeClassifier
|
|
4
|
+
from sklearn.datasets import make_classification
|
|
5
|
+
|
|
6
|
+
import valiron
|
|
7
|
+
|
|
8
|
+
X, y = make_classification(n_samples=400, n_features=12, random_state=2)
|
|
9
|
+
X_train, X_test = X[:300], X[300:]
|
|
10
|
+
y_train, y_test = y[:300], y[300:]
|
|
11
|
+
|
|
12
|
+
model = DecisionTreeClassifier(max_depth=5, random_state=2)
|
|
13
|
+
model.fit(X_train, y_train)
|
|
14
|
+
|
|
15
|
+
result = valiron.evaluate(
|
|
16
|
+
model=model,
|
|
17
|
+
X_test=X_test,
|
|
18
|
+
y_test=y_test,
|
|
19
|
+
regulation="eu_ai_act",
|
|
20
|
+
use_case="recruitment_screening",
|
|
21
|
+
sensitive_features=["gender", "age_group"],
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
print(f"Compliant: {result.compliant}")
|
|
25
|
+
print(f"Score: {result.score:.1%}")
|
|
26
|
+
valiron.report(result, format="html", output="hr_compliance.html")
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "valiron"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "AI regulatory compliance validation for Python"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { file = "LICENSE" }
|
|
11
|
+
authors = [{ name = "Abhay Sachan" }]
|
|
12
|
+
requires-python = ">=3.9"
|
|
13
|
+
keywords = ["ai", "compliance", "regulation", "ml", "validation"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
20
|
+
]
|
|
21
|
+
dependencies = [
|
|
22
|
+
"numpy>=1.21",
|
|
23
|
+
"pandas>=1.3",
|
|
24
|
+
"scikit-learn>=1.0",
|
|
25
|
+
"scipy>=1.7",
|
|
26
|
+
"jinja2>=3.0",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.optional-dependencies]
|
|
30
|
+
onnx = ["onnxruntime>=1.12"]
|
|
31
|
+
torch = ["torch>=1.10"]
|
|
32
|
+
pdf = ["weasyprint>=57"]
|
|
33
|
+
dev = [
|
|
34
|
+
"pytest>=7",
|
|
35
|
+
"pytest-cov>=4",
|
|
36
|
+
"ruff>=0.1",
|
|
37
|
+
"mypy>=1.0",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[project.urls]
|
|
41
|
+
Homepage = "https://github.com/abhaysachan007/valiron"
|
|
42
|
+
Issues = "https://github.com/abhaysachan007/valiron/issues"
|
|
43
|
+
|
|
44
|
+
[tool.hatch.build.targets.wheel]
|
|
45
|
+
packages = ["valiron"]
|
|
46
|
+
|
|
47
|
+
[tool.pytest.ini_options]
|
|
48
|
+
testpaths = ["tests"]
|
|
49
|
+
addopts = "--cov=valiron --cov-report=term-missing"
|
|
50
|
+
|
|
51
|
+
[tool.ruff]
|
|
52
|
+
line-length = 100
|
|
53
|
+
target-version = "py39"
|
|
54
|
+
|
|
55
|
+
[tool.mypy]
|
|
56
|
+
python_version = "3.9"
|
|
57
|
+
strict = true
|
|
File without changes
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Tests for calibration checker."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from valiron.calibration.checker import check_calibration, CalibrationResult
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_perfect_calibration():
|
|
9
|
+
y_true = np.array([0, 1, 0, 1, 0, 1, 0, 1, 0, 1])
|
|
10
|
+
y_prob = np.array([0.1, 0.9, 0.1, 0.9, 0.1, 0.9, 0.1, 0.9, 0.1, 0.9])
|
|
11
|
+
result = check_calibration(y_true, y_prob)
|
|
12
|
+
assert isinstance(result, CalibrationResult)
|
|
13
|
+
assert result.well_calibrated is True
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_poor_calibration():
|
|
17
|
+
y_true = np.zeros(100)
|
|
18
|
+
y_prob = np.ones(100) * 0.9
|
|
19
|
+
result = check_calibration(y_true, y_prob)
|
|
20
|
+
assert result.expected_calibration_error > 0.5
|
|
21
|
+
assert result.well_calibrated is False
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_ece_range():
|
|
25
|
+
rng = np.random.default_rng(42)
|
|
26
|
+
y_true = rng.integers(0, 2, size=200)
|
|
27
|
+
y_prob = rng.random(200)
|
|
28
|
+
result = check_calibration(y_true, y_prob)
|
|
29
|
+
assert 0.0 <= result.expected_calibration_error <= 1.0
|
|
30
|
+
assert 0.0 <= result.max_calibration_error <= 1.0
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Tests for valiron.evaluate()."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from sklearn.dummy import DummyClassifier
|
|
5
|
+
from sklearn.datasets import make_classification
|
|
6
|
+
|
|
7
|
+
import valiron
|
|
8
|
+
from valiron.evaluate.runner import EvaluationResult, SUPPORTED_REGULATIONS
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.fixture
|
|
12
|
+
def simple_model():
|
|
13
|
+
X, y = make_classification(n_samples=200, n_features=10, random_state=42)
|
|
14
|
+
model = DummyClassifier(strategy="most_frequent")
|
|
15
|
+
model.fit(X[:100], y[:100])
|
|
16
|
+
return model, X[100:], y[100:]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_evaluate_returns_result(simple_model):
|
|
20
|
+
model, X_test, y_test = simple_model
|
|
21
|
+
result = valiron.evaluate(model, X_test, y_test, "eu_ai_act", "generic classification")
|
|
22
|
+
assert isinstance(result, EvaluationResult)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_evaluate_score_range(simple_model):
|
|
26
|
+
model, X_test, y_test = simple_model
|
|
27
|
+
result = valiron.evaluate(model, X_test, y_test, "rbi_ml_risk", "credit scoring")
|
|
28
|
+
assert 0.0 <= result.score <= 1.0
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_evaluate_unknown_regulation_raises(simple_model):
|
|
32
|
+
model, X_test, y_test = simple_model
|
|
33
|
+
with pytest.raises(ValueError, match="Unknown regulation"):
|
|
34
|
+
valiron.evaluate(model, X_test, y_test, "unknown_reg", "test")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_all_regulations_run(simple_model):
|
|
38
|
+
model, X_test, y_test = simple_model
|
|
39
|
+
for reg in SUPPORTED_REGULATIONS:
|
|
40
|
+
result = valiron.evaluate(model, X_test, y_test, reg, "test case")
|
|
41
|
+
assert result.regulation == reg
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_compliant_flag_consistent(simple_model):
|
|
45
|
+
model, X_test, y_test = simple_model
|
|
46
|
+
result = valiron.evaluate(model, X_test, y_test, "cdsco_mdsw", "diagnostic aid")
|
|
47
|
+
assert result.compliant == (len(result.failing_checks) == 0)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Tests for report builder."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from valiron.evaluate.runner import EvaluationResult
|
|
6
|
+
from valiron.report.builder import report
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _make_result(**kwargs):
|
|
10
|
+
defaults = dict(
|
|
11
|
+
regulation="eu_ai_act", use_case="test", compliant=True, score=0.9,
|
|
12
|
+
passing_checks=["CHECK_A"], failing_checks=[], warnings=["WARN_1"],
|
|
13
|
+
)
|
|
14
|
+
defaults.update(kwargs)
|
|
15
|
+
return EvaluationResult(**defaults)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_report_returns_html_string():
|
|
19
|
+
html = report(_make_result())
|
|
20
|
+
assert isinstance(html, str)
|
|
21
|
+
assert "<html" in html.lower()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_report_contains_regulation():
|
|
25
|
+
html = report(_make_result(regulation="cdsco_mdsw"))
|
|
26
|
+
assert "cdsco_mdsw" in html
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_report_shows_compliant():
|
|
30
|
+
assert "COMPLIANT" in report(_make_result(compliant=True))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_report_shows_non_compliant():
|
|
34
|
+
html = report(_make_result(compliant=False, failing_checks=["FAIL_X"]))
|
|
35
|
+
assert "NON-COMPLIANT" in html
|
|
36
|
+
assert "FAIL_X" in html
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_report_unsupported_format_raises():
|
|
40
|
+
with pytest.raises(ValueError, match="Unsupported format"):
|
|
41
|
+
report(_make_result(), format="xml")
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Tests for subgroup fairness analysis."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pandas as pd
|
|
5
|
+
|
|
6
|
+
from valiron.subgroups.analyzer import analyze_subgroups
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_basic_subgroup_analysis():
|
|
10
|
+
y_true = np.array([1, 1, 0, 0, 1, 0])
|
|
11
|
+
y_pred = np.array([1, 1, 0, 0, 0, 1])
|
|
12
|
+
df = pd.DataFrame({"gender": ["M", "M", "F", "F", "M", "F"]})
|
|
13
|
+
result = analyze_subgroups(y_true, y_pred, df)
|
|
14
|
+
assert "gender" in result
|
|
15
|
+
assert "M" in result["gender"]
|
|
16
|
+
assert "F" in result["gender"]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_accuracy_in_range():
|
|
20
|
+
y_true = np.array([1, 0, 1, 0])
|
|
21
|
+
y_pred = np.array([1, 0, 0, 1])
|
|
22
|
+
df = pd.DataFrame({"group": ["A", "A", "B", "B"]})
|
|
23
|
+
result = analyze_subgroups(y_true, y_pred, df)
|
|
24
|
+
for val in result["group"].values():
|
|
25
|
+
assert 0.0 <= val <= 1.0
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_feature_subset():
|
|
29
|
+
df = pd.DataFrame({"age": ["young", "old", "young"], "gender": ["M", "F", "M"]})
|
|
30
|
+
y = np.array([1, 0, 1])
|
|
31
|
+
result = analyze_subgroups(y, y, df, features=["age"])
|
|
32
|
+
assert "age" in result
|
|
33
|
+
assert "gender" not in result
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""ACP — Audit Control Plane for logging and evidence collection."""
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Probability calibration checker."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class CalibrationResult:
|
|
13
|
+
expected_calibration_error: float
|
|
14
|
+
max_calibration_error: float
|
|
15
|
+
well_calibrated: bool
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def check_calibration(y_true: Any, y_prob: Any, n_bins: int = 10) -> CalibrationResult:
|
|
19
|
+
"""Compute ECE and MCE for probability calibration assessment."""
|
|
20
|
+
y_true = np.asarray(y_true, dtype=float)
|
|
21
|
+
y_prob = np.asarray(y_prob, dtype=float)
|
|
22
|
+
bins = np.linspace(0.0, 1.0, n_bins + 1)
|
|
23
|
+
ece = 0.0
|
|
24
|
+
mce = 0.0
|
|
25
|
+
n = len(y_true)
|
|
26
|
+
|
|
27
|
+
for low, high in zip(bins[:-1], bins[1:]):
|
|
28
|
+
mask = (y_prob >= low) & (y_prob < high)
|
|
29
|
+
if mask.sum() == 0:
|
|
30
|
+
continue
|
|
31
|
+
gap = abs(float(np.mean(y_true[mask])) - float(np.mean(y_prob[mask])))
|
|
32
|
+
ece += gap * mask.sum() / n
|
|
33
|
+
mce = max(mce, gap)
|
|
34
|
+
|
|
35
|
+
return CalibrationResult(
|
|
36
|
+
expected_calibration_error=round(ece, 4),
|
|
37
|
+
max_calibration_error=round(mce, 4),
|
|
38
|
+
well_calibrated=ece < 0.1,
|
|
39
|
+
)
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""Per-regulation compliance checks."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, List, Tuple
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def run_checks(
|
|
11
|
+
model: Any,
|
|
12
|
+
X_test: Any,
|
|
13
|
+
y_test: Any,
|
|
14
|
+
regulation: str,
|
|
15
|
+
use_case: str,
|
|
16
|
+
sensitive_features: List[str],
|
|
17
|
+
feature_names: List[str],
|
|
18
|
+
) -> Tuple[List[str], List[str], List[str]]:
|
|
19
|
+
"""Run all checks for *regulation*, return (passing, failing, warnings)."""
|
|
20
|
+
registry = {
|
|
21
|
+
"eu_ai_act": _eu_ai_act_checks,
|
|
22
|
+
"fda_samd": _fda_samd_checks,
|
|
23
|
+
"cdsco_mdsw": _cdsco_mdsw_checks,
|
|
24
|
+
"rbi_ml_risk": _rbi_ml_risk_checks,
|
|
25
|
+
}
|
|
26
|
+
return registry[regulation](model, X_test, y_test, use_case, sensitive_features)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _predict(model: Any, X: Any) -> Any:
|
|
30
|
+
if hasattr(model, "predict"):
|
|
31
|
+
return model.predict(X)
|
|
32
|
+
if callable(model):
|
|
33
|
+
return model(X)
|
|
34
|
+
raise TypeError(f"Cannot get predictions from {type(model)}")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _accuracy(y_true: Any, y_pred: Any) -> float:
|
|
38
|
+
y_true = np.asarray(y_true)
|
|
39
|
+
y_pred = np.asarray(y_pred)
|
|
40
|
+
return float(np.mean(y_true == y_pred))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _eu_ai_act_checks(model, X_test, y_test, use_case, sensitive_features):
|
|
44
|
+
passing, failing, warnings = [], [], []
|
|
45
|
+
try:
|
|
46
|
+
acc = _accuracy(y_test, _predict(model, X_test))
|
|
47
|
+
if acc >= 0.7:
|
|
48
|
+
passing.append(f"EU_AI_ACT_PERFORMANCE: accuracy={acc:.3f} >= 0.70")
|
|
49
|
+
else:
|
|
50
|
+
failing.append(f"EU_AI_ACT_PERFORMANCE: accuracy={acc:.3f} < 0.70")
|
|
51
|
+
except Exception as e:
|
|
52
|
+
failing.append(f"EU_AI_ACT_PERFORMANCE: {e}")
|
|
53
|
+
|
|
54
|
+
if hasattr(model, "feature_importances_") or hasattr(model, "coef_"):
|
|
55
|
+
passing.append("EU_AI_ACT_TRANSPARENCY: model exposes feature importances")
|
|
56
|
+
else:
|
|
57
|
+
warnings.append("EU_AI_ACT_TRANSPARENCY: model has no native explainability")
|
|
58
|
+
|
|
59
|
+
high_risk = ("medical", "health", "diagnostic", "credit", "employment", "biometric")
|
|
60
|
+
if any(kw in use_case.lower() for kw in high_risk):
|
|
61
|
+
warnings.append("EU_AI_ACT_HIGH_RISK: use case may be Annex III high-risk")
|
|
62
|
+
else:
|
|
63
|
+
passing.append("EU_AI_ACT_HIGH_RISK: not flagged as high-risk")
|
|
64
|
+
|
|
65
|
+
if sensitive_features:
|
|
66
|
+
passing.append(f"EU_AI_ACT_BIAS: sensitive features provided {sensitive_features}")
|
|
67
|
+
else:
|
|
68
|
+
warnings.append("EU_AI_ACT_BIAS: no sensitive_features supplied; fairness audit skipped")
|
|
69
|
+
|
|
70
|
+
return passing, failing, warnings
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _fda_samd_checks(model, X_test, y_test, use_case, sensitive_features):
|
|
74
|
+
passing, failing, warnings = [], [], []
|
|
75
|
+
try:
|
|
76
|
+
acc = _accuracy(y_test, _predict(model, X_test))
|
|
77
|
+
(passing if acc >= 0.75 else failing).append(
|
|
78
|
+
f"FDA_SAMD_PERFORMANCE: accuracy={acc:.3f} {'>=0.75' if acc >= 0.75 else '<0.75'}"
|
|
79
|
+
)
|
|
80
|
+
except Exception as e:
|
|
81
|
+
failing.append(f"FDA_SAMD_PERFORMANCE: {e}")
|
|
82
|
+
warnings.append("FDA_SAMD_PDCP: predetermined change control plan required")
|
|
83
|
+
warnings.append("FDA_SAMD_MONITORING: real-world performance monitoring required")
|
|
84
|
+
return passing, failing, warnings
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _cdsco_mdsw_checks(model, X_test, y_test, use_case, sensitive_features):
|
|
88
|
+
passing, failing, warnings = [], [], []
|
|
89
|
+
try:
|
|
90
|
+
acc = _accuracy(y_test, _predict(model, X_test))
|
|
91
|
+
(passing if acc >= 0.70 else failing).append(
|
|
92
|
+
f"CDSCO_MDSW_PERFORMANCE: accuracy={acc:.3f}"
|
|
93
|
+
)
|
|
94
|
+
except Exception as e:
|
|
95
|
+
failing.append(f"CDSCO_MDSW_PERFORMANCE: {e}")
|
|
96
|
+
warnings.append("CDSCO_MDSW_REGISTRATION: MDSW registration with CDSCO required")
|
|
97
|
+
passing.append("CDSCO_MDSW_DATA_PRIVACY: local evaluation — no patient data transmitted")
|
|
98
|
+
return passing, failing, warnings
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _rbi_ml_risk_checks(model, X_test, y_test, use_case, sensitive_features):
|
|
102
|
+
passing, failing, warnings = [], [], []
|
|
103
|
+
try:
|
|
104
|
+
acc = _accuracy(y_test, _predict(model, X_test))
|
|
105
|
+
(passing if acc >= 0.65 else failing).append(
|
|
106
|
+
f"RBI_ML_PERFORMANCE: accuracy={acc:.3f}"
|
|
107
|
+
)
|
|
108
|
+
except Exception as e:
|
|
109
|
+
failing.append(f"RBI_ML_PERFORMANCE: {e}")
|
|
110
|
+
|
|
111
|
+
if hasattr(model, "feature_importances_") or hasattr(model, "coef_"):
|
|
112
|
+
passing.append("RBI_ML_EXPLAINABILITY: model supports explainability")
|
|
113
|
+
else:
|
|
114
|
+
failing.append("RBI_ML_EXPLAINABILITY: model must support explainability")
|
|
115
|
+
|
|
116
|
+
warnings.append("RBI_ML_GOVERNANCE: model risk management documentation required")
|
|
117
|
+
return passing, failing, warnings
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Main evaluate() entry point."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Any, List, Optional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
SUPPORTED_REGULATIONS = ("eu_ai_act", "fda_samd", "cdsco_mdsw", "rbi_ml_risk")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class EvaluationResult:
|
|
14
|
+
regulation: str
|
|
15
|
+
use_case: str
|
|
16
|
+
compliant: bool
|
|
17
|
+
score: float
|
|
18
|
+
passing_checks: List[str] = field(default_factory=list)
|
|
19
|
+
failing_checks: List[str] = field(default_factory=list)
|
|
20
|
+
warnings: List[str] = field(default_factory=list)
|
|
21
|
+
metadata: dict = field(default_factory=dict)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def evaluate(
|
|
25
|
+
model: Any,
|
|
26
|
+
X_test: Any,
|
|
27
|
+
y_test: Any,
|
|
28
|
+
regulation: str,
|
|
29
|
+
use_case: str,
|
|
30
|
+
sensitive_features: Optional[List[str]] = None,
|
|
31
|
+
feature_names: Optional[List[str]] = None,
|
|
32
|
+
) -> EvaluationResult:
|
|
33
|
+
"""Validate model against regulation for use_case."""
|
|
34
|
+
if regulation not in SUPPORTED_REGULATIONS:
|
|
35
|
+
raise ValueError(
|
|
36
|
+
f"Unknown regulation '{regulation}'. Supported: {SUPPORTED_REGULATIONS}"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
from valiron.evaluate.checks import run_checks
|
|
40
|
+
|
|
41
|
+
passing, failing, warnings = run_checks(
|
|
42
|
+
model=model,
|
|
43
|
+
X_test=X_test,
|
|
44
|
+
y_test=y_test,
|
|
45
|
+
regulation=regulation,
|
|
46
|
+
use_case=use_case,
|
|
47
|
+
sensitive_features=sensitive_features or [],
|
|
48
|
+
feature_names=feature_names or [],
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
total = len(passing) + len(failing)
|
|
52
|
+
score = len(passing) / total if total > 0 else 0.0
|
|
53
|
+
|
|
54
|
+
return EvaluationResult(
|
|
55
|
+
regulation=regulation,
|
|
56
|
+
use_case=use_case,
|
|
57
|
+
compliant=len(failing) == 0,
|
|
58
|
+
score=round(score, 4),
|
|
59
|
+
passing_checks=passing,
|
|
60
|
+
failing_checks=failing,
|
|
61
|
+
warnings=warnings,
|
|
62
|
+
)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Compliance report builder."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from jinja2 import Environment, FileSystemLoader
|
|
9
|
+
|
|
10
|
+
from valiron.evaluate.runner import EvaluationResult
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
_TEMPLATES_DIR = Path(__file__).parent / "templates"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def report(
|
|
17
|
+
result: EvaluationResult,
|
|
18
|
+
format: str = "html",
|
|
19
|
+
output: Optional[str] = None,
|
|
20
|
+
) -> str:
|
|
21
|
+
"""Generate a compliance report from an EvaluationResult."""
|
|
22
|
+
if format not in ("html", "pdf"):
|
|
23
|
+
raise ValueError(f"Unsupported format '{format}'. Use 'html' or 'pdf'.")
|
|
24
|
+
|
|
25
|
+
env = Environment(loader=FileSystemLoader(str(_TEMPLATES_DIR)), autoescape=True)
|
|
26
|
+
template = env.get_template("report.html.j2")
|
|
27
|
+
html = template.render(result=result)
|
|
28
|
+
|
|
29
|
+
if format == "pdf":
|
|
30
|
+
try:
|
|
31
|
+
from weasyprint import HTML as WeasyprintHTML
|
|
32
|
+
except ImportError:
|
|
33
|
+
raise ImportError("PDF export requires weasyprint: pip install valiron[pdf]")
|
|
34
|
+
pdf_bytes = WeasyprintHTML(string=html).write_pdf()
|
|
35
|
+
out_path = output or "compliance_report.pdf"
|
|
36
|
+
Path(out_path).write_bytes(pdf_bytes)
|
|
37
|
+
return out_path
|
|
38
|
+
|
|
39
|
+
if output:
|
|
40
|
+
Path(output).write_text(html, encoding="utf-8")
|
|
41
|
+
return output
|
|
42
|
+
|
|
43
|
+
return html
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<title>Valiron Compliance Report — {{ result.regulation }}</title>
|
|
6
|
+
<style>
|
|
7
|
+
body { font-family: system-ui, sans-serif; max-width: 900px; margin: 40px auto; padding: 0 20px; color: #1a1a1a; }
|
|
8
|
+
h1 { border-bottom: 2px solid #333; padding-bottom: 8px; }
|
|
9
|
+
.badge { display: inline-block; padding: 4px 12px; border-radius: 4px; font-weight: bold; }
|
|
10
|
+
.compliant { background: #d4edda; color: #155724; }
|
|
11
|
+
.non-compliant { background: #f8d7da; color: #721c24; }
|
|
12
|
+
.score { font-size: 2em; font-weight: bold; margin: 16px 0; }
|
|
13
|
+
table { width: 100%; border-collapse: collapse; margin-top: 16px; }
|
|
14
|
+
th, td { padding: 8px 12px; border: 1px solid #dee2e6; text-align: left; }
|
|
15
|
+
th { background: #f8f9fa; }
|
|
16
|
+
.pass { color: #155724; }
|
|
17
|
+
.fail { color: #721c24; font-weight: bold; }
|
|
18
|
+
.warn { color: #856404; }
|
|
19
|
+
footer { margin-top: 40px; font-size: 0.8em; color: #6c757d; }
|
|
20
|
+
</style>
|
|
21
|
+
</head>
|
|
22
|
+
<body>
|
|
23
|
+
<h1>Valiron Compliance Report</h1>
|
|
24
|
+
<p><strong>Regulation:</strong> {{ result.regulation }}</p>
|
|
25
|
+
<p><strong>Use Case:</strong> {{ result.use_case }}</p>
|
|
26
|
+
<p><strong>Status:</strong>
|
|
27
|
+
<span class="badge {{ 'compliant' if result.compliant else 'non-compliant' }}">
|
|
28
|
+
{{ 'COMPLIANT' if result.compliant else 'NON-COMPLIANT' }}
|
|
29
|
+
</span>
|
|
30
|
+
</p>
|
|
31
|
+
<p class="score">Score: {{ (result.score * 100) | round(1) }}%</p>
|
|
32
|
+
|
|
33
|
+
{% if result.failing_checks %}
|
|
34
|
+
<h2>Failing Checks</h2>
|
|
35
|
+
<table><tr><th>Check</th></tr>
|
|
36
|
+
{% for check in result.failing_checks %}
|
|
37
|
+
<tr><td class="fail">✗ {{ check }}</td></tr>
|
|
38
|
+
{% endfor %}</table>
|
|
39
|
+
{% endif %}
|
|
40
|
+
|
|
41
|
+
{% if result.passing_checks %}
|
|
42
|
+
<h2>Passing Checks</h2>
|
|
43
|
+
<table><tr><th>Check</th></tr>
|
|
44
|
+
{% for check in result.passing_checks %}
|
|
45
|
+
<tr><td class="pass">✓ {{ check }}</td></tr>
|
|
46
|
+
{% endfor %}</table>
|
|
47
|
+
{% endif %}
|
|
48
|
+
|
|
49
|
+
{% if result.warnings %}
|
|
50
|
+
<h2>Warnings</h2>
|
|
51
|
+
<table><tr><th>Warning</th></tr>
|
|
52
|
+
{% for w in result.warnings %}
|
|
53
|
+
<tr><td class="warn">⚠ {{ w }}</td></tr>
|
|
54
|
+
{% endfor %}</table>
|
|
55
|
+
{% endif %}
|
|
56
|
+
|
|
57
|
+
<footer>Generated by <strong>Valiron</strong> — AI Regulatory Compliance Validation</footer>
|
|
58
|
+
</body>
|
|
59
|
+
</html>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Subgroup fairness analysis."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Dict, List, Optional
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
import pandas as pd
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def analyze_subgroups(
|
|
12
|
+
y_true: Any,
|
|
13
|
+
y_pred: Any,
|
|
14
|
+
sensitive_df: pd.DataFrame,
|
|
15
|
+
features: Optional[List[str]] = None,
|
|
16
|
+
) -> Dict[str, Dict[str, float]]:
|
|
17
|
+
"""Compute per-subgroup accuracy for each sensitive feature."""
|
|
18
|
+
y_true = np.asarray(y_true)
|
|
19
|
+
y_pred = np.asarray(y_pred)
|
|
20
|
+
cols = features or list(sensitive_df.columns)
|
|
21
|
+
results: Dict[str, Dict[str, float]] = {}
|
|
22
|
+
|
|
23
|
+
for col in cols:
|
|
24
|
+
group_accs: Dict[str, float] = {}
|
|
25
|
+
for val in sensitive_df[col].unique():
|
|
26
|
+
mask = sensitive_df[col] == val
|
|
27
|
+
if mask.sum() == 0:
|
|
28
|
+
continue
|
|
29
|
+
acc = float(np.mean(y_true[mask] == y_pred[mask]))
|
|
30
|
+
group_accs[str(val)] = round(acc, 4)
|
|
31
|
+
results[col] = group_accs
|
|
32
|
+
|
|
33
|
+
return results
|