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.
@@ -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
@@ -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)
@@ -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,7 @@
1
+ """Valiron — AI regulatory compliance validation."""
2
+
3
+ from valiron.evaluate.runner import evaluate
4
+ from valiron.report.builder import report
5
+
6
+ __all__ = ["evaluate", "report"]
7
+ __version__ = "0.1.0"
@@ -0,0 +1 @@
1
+ """ACP — Audit Control Plane for logging and evidence collection."""
@@ -0,0 +1,5 @@
1
+ """Calibration checks — reliability of predicted probabilities."""
2
+
3
+ from valiron.calibration.checker import check_calibration
4
+
5
+ __all__ = ["check_calibration"]
@@ -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,5 @@
1
+ """Evaluate module — runs compliance checks against a regulation."""
2
+
3
+ from valiron.evaluate.runner import evaluate
4
+
5
+ __all__ = ["evaluate"]
@@ -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,5 @@
1
+ """Report generation — HTML and PDF compliance reports."""
2
+
3
+ from valiron.report.builder import report
4
+
5
+ __all__ = ["report"]
@@ -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,5 @@
1
+ """Subgroup analysis — fairness checks across demographic slices."""
2
+
3
+ from valiron.subgroups.analyzer import analyze_subgroups
4
+
5
+ __all__ = ["analyze_subgroups"]
@@ -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