Overview
This walkthrough demonstrates a complete fairness audit of a computer vision model used in healthcare. Unlike tabular classifiers or black-box LLMs, image-based AI systems present unique fairness challenges: demographic representation in training images, performance disparities across skin tones and anatomical variations, and regression-style continuous outputs (urgency scores) alongside binary triage decisions.
By following this guide, you will produce: a validated imaging dataset audit, bias detection across demographic groups, feature attribution analysis, a calibrated triage model, comprehensive fairness metrics with confidence intervals, robustness tests across imaging conditions, CI/CD gate decisions, monitoring dashboards, multi-tier reports, and an EU AI Act–compliant model card for a healthcare AI system — all backed by 22 production-grade SVG artifacts.
The Scenario
Chest X-Ray Triage Model — EU AI Act High-Risk System
A hospital network deploys a deep learning model (DenseNet-121) to prioritize chest X-rays in the emergency department. The model outputs an urgency score (0–100) and a binary triage decision (urgent/routine). Under the EU AI Act (Annex III, Section 5a), AI systems intended to be used for medical diagnosis or triage are classified as high-risk AI, subject to the most stringent compliance requirements.
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
# ── Load imaging metadata + model predictions ──
# Each row = one chest X-ray with radiologist labels + model predictions
df = pd.read_csv("cxr_triage_predictions.csv")
# Columns: study_id, urgency_score (0-100), triage_decision (urgent/routine),
# radiologist_label, gender, race, age_group, imaging_site, device_type
PROTECTED = ["gender", "race", "age_group"]
SCORE_COL = "urgency_score"
OUTCOME_COL = "radiologist_label" # Ground truth from radiologist panel
FEATURES = ["imaging_site", "device_type", "study_hour", "body_region"]
# Binary labels
df["urgent_true"] = (df[OUTCOME_COL] == "urgent").astype(int)
df["urgent_pred"] = (df["triage_decision"] == "urgent").astype(int)
y_true = df["urgent_true"]
y_pred = df["urgent_pred"]
y_scores = df[SCORE_COL] / 100.0 # Normalize to [0, 1]
sensitive = df[PROTECTED]
# Train/test split (temporal: older studies for train, recent for test)
df_train = df[df["study_date"] < "2025-01-01"]
df_test = df[df["study_date"] >= "2025-01-01"]
print(f"Dataset: {len(df)} X-rays ({len(df_train)} train, {len(df_test)} test)")
print(f"Urgent prevalence: {df['urgent_true'].mean():.1%}")
print(f"Protected attributes: {PROTECTED}")
Audit Pipeline
The ten phases below follow the standard vfairness pipeline but address image-specific challenges: representation in visual data, feature attribution for convolutional networks, calibration of continuous urgency scores, and regression fairness across demographic and clinical subgroups.
Validation
Detection
Analysis
Training
Testing
Gating
Card
Data Validation
Validate the imaging dataset for demographic representation, acquisition site diversity, and outcome label quality. Medical imaging datasets frequently suffer from site-specific bias (images from different hospitals have different visual characteristics) and underrepresentation of minority populations.
from vfairness.operations.cicd import DataBiasValidator, DataValidationConfig
validator = DataBiasValidator(config=DataValidationConfig(
min_group_size=100, # Higher minimum for medical imaging
max_missing_rate=0.02, # Strict: clinical data must be complete
proxy_correlation_threshold=0.25,
))
result = validator.validate(df, protected_attrs=PROTECTED, outcome_col="urgent_true")
print(f"Status: {result.status}")
print(f"Issues: {len(result.issues)}")
# ── Generate SVG artifact ──
from vfairness.rendering import data_validation_to_svg
data_validation_to_svg(
result,
explanation="Imaging dataset validation for the chest X-ray triage model. Checks "
"demographic representation across 6 imaging sites, device type distribution, "
"missing metadata rates, and outcome label quality.",
save_path="artifacts/01_data_validation.svg",
)
1 critical: race=Asian underrepresented at 4.2% (population base rate ~6%). 1 warning: imaging site #3 contributes 38% of images (site-specific bias risk). 1 informational: 0.8% of images have missing age_group metadata. Proceed with documented caveats and reweighting.
Bias Detection & Auto-Discovery
Scan the model’s predictions for demographic disparities. In medical imaging, bias can arise from: (1) prevalence differences across populations, (2) image quality variations by site/device, and (3) annotation bias from radiologist panels with limited diversity.
from vfairness.evaluation.vfairness_metrics import (
detect_protected_attributes,
scan_fairness_violations,
discover_intersectional_groups,
)
from vfairness.preprocessing.bias_detection import BiasDetector
from vfairness.rendering import auto_discovery_to_svg, bias_audit_to_svg
# ── Auto-discovery on model predictions ──
candidates = detect_protected_attributes(df, outcome_col="urgent_true")
violations = scan_fairness_violations(y_true, y_pred, sensitive["gender"])
intersectional = discover_intersectional_groups(
df, protected_attrs=PROTECTED, outcome_col="urgent_true"
)
auto_discovery_to_svg(
candidates=candidates,
violations=violations,
group_advantages=intersectional,
explanation="Automated scan of the chest X-ray triage model identifying gender, race, "
"and age_group as attributes correlated with prediction disparities. "
"age_group shows highest correlation due to disease prevalence differences.",
save_path="artifacts/02a_auto_discovery.svg",
)
# ── Full bias audit ──
detector = BiasDetector(df, protected_attributes=PROTECTED, outcome_column="urgent_true")
audit = detector.full_audit()
bias_audit_to_svg(
audit.to_dict(),
explanation="Comprehensive bias audit of the triage model covering outcome disparities, "
"sensitivity/specificity by group, and intersectional analysis. Focus on "
"clinically significant false negative rate differences.",
save_path="artifacts/02b_bias_audit.svg",
)
False negative rate disparity: race=Black patients have 12.4% FNR vs 6.1% overall — urgent cases are more likely to be missed. Intersectional group Black × age_group=65+ has the highest FNR (15.8%). Two high-severity violations flagged for immediate review.
Feature & Metadata Analysis
Analyze correlations between metadata features (imaging site, device type, acquisition parameters) and both outcomes and protected attributes. In medical imaging, site-specific visual signatures can act as demographic proxies — if certain demographics are concentrated at specific hospitals.
from vfairness.preprocessing.feature_engineering import FeatureEngineeringAnalyzer
from vfairness.rendering import (
proxy_risk_to_svg,
correlation_heatmap_to_svg,
)
analyzer = FeatureEngineeringAnalyzer(
df, protected_attributes=PROTECTED, feature_columns=FEATURES
)
report = analyzer.full_analysis()
# ── Proxy risk: imaging_site as demographic proxy ──
proxy_risk_to_svg(
report.proxy_results,
explanation="Proxy analysis for imaging metadata. 'imaging_site' shows critical proxy "
"risk (r=0.68 with race) because hospital catchment areas are demographically "
"stratified. 'device_type' shows moderate risk.",
save_path="artifacts/03a_proxy_risk.svg",
)
# ── Correlation heatmap ──
correlation_heatmap_to_svg(
report.correlation_matrix,
explanation="Metadata-to-demographic correlation matrix. Reveals how imaging acquisition "
"context (site, device, time-of-day) correlates with protected attributes.",
save_path="artifacts/03b_correlation_heatmap.svg",
)
imaging_site is a critical proxy for race (r=0.68). Site #3 (38% of images) is 82% White patients, while site #5 (8% of images) is 71% Black patients. This demographic stratification by site means the model may learn site-specific visual artifacts as demographic shortcuts.
Fair Training with Sample Reweighting
Retrain the model using sample reweighting to correct for demographic imbalance and site-specific bias. For imaging models, fairness-aware training typically uses reweighting (upweighting underrepresented group-outcome combinations) rather than adversarial methods, which can destabilize visual feature learning.
from vfairness.preprocessing.reweighting import compute_reweighting_weights
from vfairness.rendering import (
training_report_to_svg,
reweighting_comparison_report_to_svg,
)
# ── Compute sample weights for fairness-aware training ──
weights = compute_reweighting_weights(
df_train, protected_attrs=PROTECTED, outcome_col="urgent_true"
)
# Retrain DenseNet-121 with sample weights
# model.fit(X_train_images, y_train, sample_weight=weights)
# training_history captured during retraining...
training_report_to_svg(
training_history,
explanation="Training convergence for the reweighted DenseNet-121. Sample weights "
"upweight underrepresented group-outcome combinations (e.g., Black × urgent) "
"to equalize effective representation during optimization.",
save_path="artifacts/04a_training_report.svg",
)
reweighting_comparison_report_to_svg(
{"baseline": baseline_metrics, "reweighted": reweighted_metrics},
explanation="Before/after comparison of reweighting. Shows FNR reduction for "
"underrepresented groups with minimal impact on overall AUC.",
save_path="artifacts/04b_reweighting_comparison.svg",
)
Reweighting reduces the FNR disparity for race=Black from 12.4% to 7.8% (threshold: 8%). Overall AUC drops marginally from 0.912 to 0.905. The trade-off is clinically acceptable — fewer missed urgent cases in underserved populations.
Urgency Score Calibration
The model’s urgency scores (0–100) must be well-calibrated so that a score of “80” means the same clinical urgency regardless of patient demographics. In healthcare, miscalibration has direct patient safety implications.
from vfairness.post_processing.calibration import CalibrationAnalyzer
from vfairness.rendering import (
calibration_report_to_svg,
reliability_diagram_to_svg,
)
cal = CalibrationAnalyzer(y_true, y_scores, sensitive["race"])
cal_report = cal.full_analysis()
calibration_report_to_svg(
cal_report.to_dict(),
explanation="Group-wise calibration analysis for urgency scores. ECE computed per "
"racial group to detect if score '80' means different urgency levels "
"for different populations — a direct patient safety concern.",
save_path="artifacts/05a_calibration_report.svg",
)
reliability_diagram_to_svg(
y_true, y_scores,
explanation="Reliability diagram for the triage urgency scores. Deviation from the "
"diagonal indicates systematic over- or under-estimation of urgency.",
save_path="artifacts/05b_reliability_diagram.svg",
)
After reweighted training: overall ECE = 0.042. Group ECE disparity reduced from 0.051 to 0.018 (threshold: 0.02). Isotonic calibration applied as a final correction, bringing all groups below threshold.
Comprehensive Metrics & Robustness Testing
Evaluate the reweighted and calibrated model across all fairness dimensions. For healthcare AI, equalized odds (equal sensitivity/specificity across groups) is typically the most clinically relevant metric, since both false positives (unnecessary escalation) and false negatives (missed urgent cases) carry clinical consequences.
from vfairness import FairnessAnalyzer
from vfairness.evaluation.vfairness_metrics import (
permutation_test, sensitivity_analysis, subgroup_robustness_audit,
compute_effect_sizes,
)
from vfairness.evaluation.vfairness_metrics.regression import (
get_group_metrics, regression_fairness_to_svg_data,
)
from vfairness.rendering import (
radar_chart_to_svg,
metrics_bar_chart_to_svg,
confidence_intervals_to_svg,
group_comparison_to_svg,
robustness_testing_to_svg,
regression_fairness_to_svg,
)
analyzer = FairnessAnalyzer(
y_true, y_pred, sensitive["race"],
y_prob=y_scores, fair_explainer=True,
)
report = analyzer.get_report(include_ci=True, include_explanations=True)
radar_chart_to_svg(report,
explanation="Multi-metric radar chart for the chest X-ray triage model. "
"Equalized odds is the primary clinical fairness metric.",
save_path="artifacts/06a_radar_chart.svg")
metrics_bar_chart_to_svg(report["metrics"],
explanation="All fairness metrics against healthcare-specific thresholds. "
"FNR parity is stricter (0.05) due to patient safety requirements.",
save_path="artifacts/06b_metrics_bar_chart.svg")
confidence_intervals_to_svg(report["confidence_intervals"],
explanation="95% bootstrap confidence intervals for each metric.",
save_path="artifacts/06c_confidence_intervals.svg")
group_comparison_to_svg(report["group_metrics"],
explanation="Per-group sensitivity, specificity, PPV, and NPV comparison.",
save_path="artifacts/06d_group_comparison.svg")
# ── Robustness testing ──
perm = permutation_test(y_true, y_pred, sensitive["race"])
sens = sensitivity_analysis(y_true, y_pred, sensitive["race"])
audit = subgroup_robustness_audit(y_true, y_pred, sensitive["race"])
robustness_testing_to_svg(
permutation_results=[perm], sensitivity_results=[sens],
subgroup_results=[audit],
explanation="Robustness validation across imaging conditions (site, device, time). "
"Tests metric stability under data perturbation.",
save_path="artifacts/06e_robustness_testing.svg")
# ── Regression fairness on urgency scores ──
regression_fairness_to_svg(
group_metrics=get_group_metrics(y_true.astype(float), y_scores, sensitive["race"]),
explanation="Regression fairness analysis of continuous urgency scores. Evaluates MAE "
"parity, mean prediction difference, and residual bias across racial groups.",
save_path="artifacts/06f_regression_fairness.svg")
After reweighting + calibration: equalized odds difference = 0.04 (pass), FNR parity = 0.03 (pass, healthcare threshold 0.05), demographic parity difference = 0.07 (pass). Regression MAE parity = 0.08 (pass). All permutation tests significant (p < 0.001). Robustness score: 0.81 (PASS).
CI/CD Gating
Medical AI deployments require stringent gates. The fairness gate is complemented by clinical performance gates (minimum sensitivity, maximum FNR) that must be satisfied per demographic group before deployment.
from vfairness.operations.cicd import ModelFairnessGate, GateConfig
from vfairness.operations.cicd.gate import evaluate_hierarchical, HierarchicalGateConfig
from vfairness.rendering import cicd_pipeline_to_svg, hierarchical_gate_to_svg
gate = ModelFairnessGate(config=GateConfig(
thresholds={
"demographic_parity_difference": 0.10,
"equalized_odds_difference": 0.08, # Stricter for healthcare
"equal_opportunity_difference": 0.05, # FNR parity — patient safety
},
require_min_samples=100,
))
decision = gate.evaluate(y_true, y_pred, sensitive["race"], y_prob=y_scores)
cicd_pipeline_to_svg(decision.to_dict(),
explanation="CI/CD fairness gate for the chest X-ray triage model. Healthcare-grade "
"thresholds: stricter equalized odds (0.08) and FNR parity (0.05).",
save_path="artifacts/07a_cicd_pipeline.svg")
hier_config = HierarchicalGateConfig(
global_thresholds={"equalized_odds_difference": 0.08},
per_intersection_thresholds={
"Black_65+": {"equal_opportunity_difference": 0.08},
},
min_subgroup_size=50,
)
hier_decision = evaluate_hierarchical(y_true, y_pred, sensitive, config=hier_config)
hierarchical_gate_to_svg(hier_decision,
explanation="Hierarchical gate with clinical-grade intersectional evaluation. "
"Elevated thresholds for previously identified vulnerable group (Black × 65+).",
save_path="artifacts/07b_hierarchical_gate.svg")
All global and per-group thresholds satisfied after reweighting. The Black × 65+ subgroup passes its elevated threshold (FNR parity = 0.04, threshold 0.08). One SmallSampleWarning for Asian × 18-24 (n=58).
Production Monitoring
Medical imaging models face unique drift sources: new imaging equipment, seasonal disease patterns, and changes in patient population demographics. Monitoring must track both fairness metrics and clinical performance per subgroup.
from vfairness.operations.monitoring import FairnessMonitor, FairnessMonitorConfig
from vfairness.operations.monitoring import FairnessDriftDetector
from vfairness.rendering import monitoring_dashboard_to_svg, drift_report_to_svg
monitor = FairnessMonitor(config=FairnessMonitorConfig(
metrics=["equalized_odds_difference", "equal_opportunity_difference"],
window_size="24h", # Daily windows for hospital volumes
alert_thresholds={"equal_opportunity_difference": 0.06},
))
monitor.log_predictions(y_prod_true, y_prod_pred, sensitive_prod)
monitoring_dashboard_to_svg(monitor,
explanation="Real-time fairness monitoring for the triage model. Tracks FNR parity "
"and equalized odds across 24-hour windows. Alerts on new imaging equipment "
"or seasonal population shifts.",
save_path="artifacts/08a_monitoring_dashboard.svg")
detector = FairnessDriftDetector()
drift = detector.detect_drift(y_prod_true, y_prod_pred, sensitive_prod,
baseline_metrics=report["metrics"])
drift_report_to_svg(drift,
explanation="Drift analysis comparing production metrics against audit baseline. "
"Monitors for equipment-related drift and seasonal population changes.",
save_path="artifacts/08b_drift_report.svg")
No significant drift detected. FNR parity stable at 0.03 ± 0.01. Automated alerts configured for equipment changes (new device types trigger mandatory re-evaluation) and seasonal thresholds.
Multi-Tier Reporting
Generate three report tiers. The executive report for hospital leadership emphasizes patient safety metrics. The operational report for radiology teams includes site-specific performance. The technical report provides full statistical evidence for regulatory submission.
from vfairness.operations.reporting import ReportGenerator, ReportConfig, MetricsStore
from vfairness.rendering import reporting_dashboard_to_svg
store = MetricsStore()
store.record_metric("equalized_odds_difference", 0.04, group="race")
store.record_metric("equal_opportunity_difference", 0.03, group="race")
store.record_metric("demographic_parity_difference", 0.07, group="race")
store.record_metric("calibration_difference", 0.018, group="race")
generator = ReportGenerator(config=ReportConfig(
title="Chest X-Ray Triage Model Fairness Report",
))
exec_report = generator.generate(store, tier="executive", format="markdown")
reporting_dashboard_to_svg(exec_report, tier="executive",
explanation="Hospital leadership summary: patient safety metrics, FNR parity status, "
"and compliance for CE marking under the EU AI Act / MDR.",
save_path="artifacts/09a_executive_dashboard.svg")
ops_report = generator.generate(store, tier="operational", format="markdown")
reporting_dashboard_to_svg(ops_report, tier="operational",
explanation="Radiology team dashboard: per-site performance, device-specific metrics, "
"and recommendations for imaging protocol adjustments.",
save_path="artifacts/09b_operational_dashboard.svg")
tech_report = generator.generate(store, tier="technical", format="json")
reporting_dashboard_to_svg(tech_report, tier="technical",
explanation="Full statistical evidence for regulatory submission. Includes bootstrap "
"CIs, regression fairness, and subgroup robustness audit results.",
save_path="artifacts/09c_technical_dashboard.svg")
EU AI Act Model Card (Healthcare)
Healthcare AI systems have dual regulatory obligations: the EU AI Act (Annex III, Section 5a) for high-risk AI, and the Medical Device Regulation (MDR 2017/745) when the system qualifies as a medical device. The model card must address both frameworks.
from vfairness.operations.cicd.gate import FairnessReportCard
from vfairness.rendering import report_card_to_svg
card = FairnessReportCard(
model_name="CXR-Triage-DenseNet-v3.2",
model_version="3.2.0",
description="DenseNet-121 convolutional neural network for chest X-ray urgency "
"triage. Outputs urgency score (0-100) and binary triage decision "
"(urgent/routine). Trained on 45,000 chest X-rays from 6 hospital sites.",
intended_use="Prioritization of chest X-rays in emergency department workflow. "
"All triage decisions reviewed by a radiologist within 4 hours. "
"Model serves as a prioritization aid, not a diagnostic tool.",
risk_classification="HIGH", # EU AI Act Annex III, Section 5a (medical devices)
protected_attributes=PROTECTED,
fairness_metrics={
"equalized_odds_difference": 0.04,
"equal_opportunity_difference": 0.03,
"demographic_parity_difference": 0.07,
"calibration_difference": 0.018,
"mae_parity_difference": 0.08,
},
training_data_summary={
"size": 45_000,
"source": "Multi-site hospital network, 6 imaging centers, 2021-2024",
"preprocessing": "Sample reweighting applied for demographic balance. "
"Site-aware augmentation to reduce acquisition bias.",
"demographics": "Gender: 48% F / 52% M. Race: 62% White, 18% Black, "
"12% Hispanic, 4.2% Asian, 3.8% Other. Age: 18-90+.",
},
evaluation_summary={
"test_size": 9_000,
"temporal_split": "Train < 2025-01-01, Test >= 2025-01-01",
"bootstrap_samples": 1_000,
"ci_level": 0.95,
},
gate_decision=decision.to_dict(),
monitoring_config={
"drift_detection": "enabled",
"alert_thresholds": {"equal_opportunity_difference": 0.06},
"review_cadence": "monthly + triggered by new equipment",
"clinical_validation": "Quarterly radiologist panel review",
},
human_oversight="All triage decisions reviewed by board-certified radiologist "
"within 4 hours. Monthly fairness review by clinical AI committee. "
"Model override documented and audited.",
limitations=[
"Trained on data from 6 sites in one geographic region — may not generalize.",
"Asian population underrepresented (4.2%) — larger validation needed.",
"Pediatric X-rays excluded (age < 18) — model not validated for children.",
"Performance on portable/bedside X-rays may differ from upright studies.",
"Site-specific visual artifacts may affect cross-site deployment.",
],
references=[
"EU AI Act, Regulation 2024/1689, Annex III Section 5(a) (medical devices)",
"Medical Device Regulation (EU) 2017/745 — AI as Software as Medical Device",
"FDA Guidance: Clinical Decision Support Software (2022)",
"WHO Ethics & Governance of AI for Health (2021)",
],
)
report_card_to_svg(card,
explanation="EU AI Act-compliant model card for the chest X-ray triage system. "
"Addresses both AI Act (Annex III, 5a) and MDR obligations. Documents "
"training data demographics, reweighting methodology, site-specific "
"validation, human oversight, and known limitations.",
save_path="artifacts/10_model_card.svg")
Healthcare AI systems face overlapping regulations:
- EU AI Act (Annex III, 5a) — High-risk AI classification for medical device AI systems
- MDR 2017/745 — CE marking requirements for Software as a Medical Device (SaMD)
- Article 9 — Risk management integrates with ISO 14971 (medical device risk management)
- Article 10 — Data governance aligns with GSPR Annex I requirements for clinical evidence
- Article 15 — Accuracy/robustness requirements map to clinical performance evaluation
Complete Artifact Inventory
The table below lists every artifact generated during this medical imaging audit.
| Phase | Artifact | Function | Module |
|---|---|---|---|
| 1 | Data Validation Report | data_validation_to_svg() | Preprocessing |
| 2 | Auto-Discovery Scanner | auto_discovery_to_svg() | Preprocessing |
| 2 | Bias Audit Dashboard | bias_audit_to_svg() | Preprocessing |
| 3 | Proxy Risk Assessment | proxy_risk_to_svg() | Preprocessing |
| 3 | Correlation Heatmap | correlation_heatmap_to_svg() | Preprocessing |
| 4 | Training Report | training_report_to_svg() | Training |
| 4 | Reweighting Comparison | reweighting_comparison_report_to_svg() | Training |
| 5 | Calibration Report | calibration_report_to_svg() | Post-Processing |
| 5 | Reliability Diagram | reliability_diagram_to_svg() | Post-Processing |
| 6 | Fairness Radar Chart | radar_chart_to_svg() | Evaluation |
| 6 | Metrics Bar Chart | metrics_bar_chart_to_svg() | Evaluation |
| 6 | Confidence Intervals | confidence_intervals_to_svg() | Evaluation |
| 6 | Group Comparison | group_comparison_to_svg() | Evaluation |
| 6 | Robustness Testing | robustness_testing_to_svg() | Evaluation |
| 6 | Regression Fairness | regression_fairness_to_svg() | Evaluation |
| 7 | CI/CD Pipeline Gate | cicd_pipeline_to_svg() | Operations |
| 7 | Hierarchical Gate | hierarchical_gate_to_svg() | Operations |
| 8 | Monitoring Dashboard | monitoring_dashboard_to_svg() | Operations |
| 8 | Drift Report | drift_report_to_svg() | Operations |
| 9 | Executive Dashboard | reporting_dashboard_to_svg() | Reporting |
| 9 | Operational Dashboard | reporting_dashboard_to_svg() | Reporting |
| 9 | Technical Dashboard | reporting_dashboard_to_svg() | Reporting |
| 10 | EU AI Act Model Card | report_card_to_svg() | Reporting |
Workflow Integration
The audit above produces artifacts manually. To automate and enforce these checks in your development pipeline, vfairness provides workflow integration tools that plug directly into MLOps, CI/CD, version control, and testing infrastructure. For medical imaging, this is especially critical given MDR post-market surveillance requirements. See the Workflow Integration guide for full documentation.
1. Experiment Tracking
Log every model retraining’s fairness metrics to MLflow or Weights & Biases. This is essential for medical imaging models that must be revalidated after equipment changes, site additions, or algorithm updates.
# Option A: explicit logging after Phase 6
from vfairness import log_fairness_to_mlflow
import mlflow
with mlflow.start_run(run_name="chest-xray-resnet50-v2.3"):
logged = log_fairness_to_mlflow(report, prefix="xray_v2.3")
print(f"Logged {logged} metrics to MLflow")
# Option B: auto-logging decorator — wraps any evaluation function
from vfairness import auto_log_fairness
@auto_log_fairness(backend="wandb", prefix="xray")
def evaluate_imaging_model(model, X_test, y_test, sensitive):
y_pred = model.predict(X_test)
return y_pred, y_test, sensitive # triggers automatic fairness logging
2. Pre-Commit Hooks
Enforce documentation standards before code reaches the repository. For medical AI, this ensures fairness configs and model cards are always up-to-date — a key MDR requirement for technical documentation.
# .pre-commit-config.yaml
repos:
- repo: https://github.com/your-org/vfairness
rev: v0.0.8
hooks:
- id: vfairness-check-config # validates fairness JSON configs
- id: vfairness-check-model-card # ensures model cards include fairness sections
3. pytest Integration
Embed fairness assertions in your test suite. For medical imaging, test that diagnostic accuracy is equitable across demographic groups and acquisition sites — same checks as Phase 6, but automated on every commit.
import pytest
from vfairness import assert_fairness, FairnessTestSuite
# Test diagnostic fairness across patient demographics
def test_imaging_model_fairness():
assert_fairness(
y_true, y_pred, sensitive_test["ethnicity"],
metrics=["demographic_parity_difference", "equalized_odds_difference"],
thresholds={"demographic_parity_difference": 0.08, "equalized_odds_difference": 0.10},
)
# Full test suite covering demographics and acquisition sites
suite = FairnessTestSuite(
protected_attributes=["ethnicity", "sex", "age_group", "acquisition_site"],
metrics=["demographic_parity_difference", "equalized_odds_difference"],
thresholds={"demographic_parity_difference": 0.08, "equalized_odds_difference": 0.10},
)
results = suite.test_predictions(y_true, y_pred, sensitive_test)
xml = suite.to_junit_xml() # attach to CI pipeline artifacts
4. CI/CD Automation & PR Comments
Gate decisions from Phase 7 can be posted directly to GitHub as check results and PR comments. For medical imaging, this prevents model updates from reaching production without fairness validation — aligning with MDR change control requirements.
# Post gate result as a GitHub Check (from Phase 7)
check_payload = gate.create_github_check(decision)
# → POST to /repos/{owner}/{repo}/check-runs
# Generate PR comment with full fairness report card
from vfairness import FairnessReportCard
card = FairnessReportCard(decision, model_name="ChestXRay-ResNet50-v2.3")
comment_payload = card.to_github_comment_payload()
# → POST to /repos/{owner}/{repo}/issues/{pr_number}/comments
# .github/workflows/fairness-checks.yml
name: Medical Imaging Fairness Gate
on: [pull_request]
jobs:
fairness:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pip install vfairness
- run: python -m pytest tests/ -m fairness --junitxml=fairness-results.xml
- run: python scripts/evaluate_imaging_gate.py # runs gate + posts PR comment
Phase 6 (Metrics) → @auto_log_fairness + assert_fairness()
Phase 7 (CI/CD) → create_github_check() + GitHub Actions YAML
Phase 10 (Model Card) → FairnessReportCard.to_github_comment_payload()
Every commit → Pre-commit hooks validate configs + model cards
Equipment change → Re-run full suite + log to experiment tracker for MDR traceability
EU AI Act Compliance Checklist
Healthcare AI has the most stringent compliance requirements. Each item below maps to specific audit artifacts and addresses the overlap between the EU AI Act and the Medical Device Regulation.
This walkthrough used demo data for illustration. To apply this process to your own medical imaging model:
- Ensure your imaging dataset has linked demographic metadata (may require IRB approval)
- Validate representation across all acquisition sites and equipment types
- Set clinically appropriate thresholds (consult with clinical AI committees)
- Apply sample reweighting before training; do not rely solely on post-hoc calibration
- Schedule equipment-triggered re-evaluations and quarterly clinical validation panels
- Coordinate EU AI Act and MDR documentation into a single conformity assessment
Compare with the Credit Scoring Audit (tabular data) or the LLM Audit (black-box foundation model). Explore all 49 SVG templates in the SVG Gallery.