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.

CV
Computer vision specifics: Medical imaging models process unstructured image data where protected attributes are often not explicit in the input but are encoded in visual features (body habitus, bone density patterns, device artifacts). Fairness evaluation requires linking image predictions to demographic metadata collected separately, and auditing performance at the intersection of clinical and demographic subgroups.
What you will build

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.

45,000
Chest X-rays
3
Protected Attributes
22
SVG Artifacts
10
Audit Phases
python
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.

1Data
Validation
2Bias
Detection
3Feature
Analysis
4Fair
Training
5Calibration
6Metrics &
Testing
7CI/CD
Gating
8Monitoring
9Dashboards
10Model
Card
CV
Image-specific adaptations: Phase 1 validates demographic representation in imaging datasets (acquisition site diversity, device type distribution). Phase 3 uses correlation analysis between metadata features and outcomes instead of pixel-level feature engineering. Phase 4 applies sample reweighting to correct demographic imbalance in training images. Phase 6 includes regression fairness for the continuous urgency score output.
1 Preprocessing

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.

python
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",
)
Data Validation Report
Data Validation Report data_validation_to_svg()
Findings

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.

2 Preprocessing

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.

python
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",
)
Auto-Discovery Scanner
Auto-Discovery Scanner auto_discovery_to_svg()
Bias Audit Dashboard
Bias Audit Dashboard bias_audit_to_svg()
Findings

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.

3 Preprocessing

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.

python
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",
)
Proxy Risk Assessment
Proxy Risk Assessment proxy_risk_to_svg()
Correlation Heatmap
Correlation Heatmap correlation_heatmap_to_svg()
Findings

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.

4 Training

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.

python
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",
)
Training Report
Training Report training_report_to_svg()
Reweighting Comparison
Reweighting Comparison reweighting_comparison_report_to_svg()
Findings

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.

5 Post-Processing

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.

python
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",
)
Calibration Report
Calibration Report calibration_report_to_svg()
Reliability Diagram
Reliability Diagram reliability_diagram_to_svg()
Findings

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.

6 Evaluation

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.

python
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")
Fairness Radar Chart
Fairness Radar Chart radar_chart_to_svg()
Metrics Bar Chart
Metrics Bar Chart metrics_bar_chart_to_svg()
Confidence Intervals
Confidence Intervals confidence_intervals_to_svg()
Group Comparison
Group Comparison group_comparison_to_svg()
Robustness Testing
Robustness Testing Dashboard robustness_testing_to_svg()
Regression Fairness
Regression Fairness (Urgency Scores) regression_fairness_to_svg()
Findings

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).

7 Operations

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.

python
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")
CI/CD Pipeline Gate
CI/CD Pipeline Gate cicd_pipeline_to_svg()
Hierarchical Gate
Hierarchical Gate hierarchical_gate_to_svg()
Gate Decision: PASS

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).

8 Operations

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.

python
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")
Monitoring Dashboard
Monitoring Dashboard monitoring_dashboard_to_svg()
Drift Report
Drift Report drift_report_to_svg()
Findings

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.

9 Reporting

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.

python
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")
Executive Dashboard
Executive Dashboard reporting_dashboard_to_svg(tier="executive")
Operational Dashboard
Operational Dashboard reporting_dashboard_to_svg(tier="operational")
Technical Dashboard
Technical Dashboard reporting_dashboard_to_svg(tier="technical")
10 Compliance

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.

python
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")
EU AI Act Model Card
EU AI Act Model Card (Healthcare) report_card_to_svg()
Dual Regulatory Framework: EU AI Act + MDR

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.

PhaseArtifactFunctionModule
1Data Validation Reportdata_validation_to_svg()Preprocessing
2Auto-Discovery Scannerauto_discovery_to_svg()Preprocessing
2Bias Audit Dashboardbias_audit_to_svg()Preprocessing
3Proxy Risk Assessmentproxy_risk_to_svg()Preprocessing
3Correlation Heatmapcorrelation_heatmap_to_svg()Preprocessing
4Training Reporttraining_report_to_svg()Training
4Reweighting Comparisonreweighting_comparison_report_to_svg()Training
5Calibration Reportcalibration_report_to_svg()Post-Processing
5Reliability Diagramreliability_diagram_to_svg()Post-Processing
6Fairness Radar Chartradar_chart_to_svg()Evaluation
6Metrics Bar Chartmetrics_bar_chart_to_svg()Evaluation
6Confidence Intervalsconfidence_intervals_to_svg()Evaluation
6Group Comparisongroup_comparison_to_svg()Evaluation
6Robustness Testingrobustness_testing_to_svg()Evaluation
6Regression Fairnessregression_fairness_to_svg()Evaluation
7CI/CD Pipeline Gatecicd_pipeline_to_svg()Operations
7Hierarchical Gatehierarchical_gate_to_svg()Operations
8Monitoring Dashboardmonitoring_dashboard_to_svg()Operations
8Drift Reportdrift_report_to_svg()Operations
9Executive Dashboardreporting_dashboard_to_svg()Reporting
9Operational Dashboardreporting_dashboard_to_svg()Reporting
9Technical Dashboardreporting_dashboard_to_svg()Reporting
10EU AI Act Model Cardreport_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.

python
# 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.

yaml
# .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.

python
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.

python
# 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
yaml
# .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
Mapping audit phases to workflow tools

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.

Art. 9 — Risk Management
Integrated with ISO 14971. Data validation, bias detection, proxy analysis, and robustness testing. Site-specific and equipment-specific risks documented.
Art. 10 — Data Governance
Training data fully documented: 45,000 images, 6 sites, demographic breakdown. Sample reweighting methodology for representation balance.
Art. 11 — Technical Documentation
22 SVG artifacts plus model card. Architecture documentation (DenseNet-121), training methodology, and clinical validation evidence.
Art. 12 — Record-Keeping
MetricsStore with automated logging. Per-study prediction records with timestamps, device metadata, and radiologist confirmation status.
Art. 13 — Transparency
Multi-tier reporting for hospital leadership, radiology teams, and regulatory bodies. Patient-facing information about AI-assisted triage.
Art. 14 — Human Oversight
All triage decisions reviewed by board-certified radiologist within 4 hours. Model serves as prioritization aid, not autonomous diagnostic.
Art. 15 — Accuracy & Robustness
Calibration analysis, permutation tests, sensitivity analysis, regression fairness. Equipment-specific robustness validation across imaging devices.
Art. 72 — Post-Market Monitoring
Continuous monitoring with equipment-triggered re-evaluation. Quarterly clinical validation panels. Monthly fairness metric reviews.
MDR 2017/745 — CE Marking
Clinical performance evaluation per GSPR Annex I. This fairness audit complements the clinical evidence dossier required for SaMD Class IIa CE marking.
Next Steps

This walkthrough used demo data for illustration. To apply this process to your own medical imaging model:

  1. Ensure your imaging dataset has linked demographic metadata (may require IRB approval)
  2. Validate representation across all acquisition sites and equipment types
  3. Set clinically appropriate thresholds (consult with clinical AI committees)
  4. Apply sample reweighting before training; do not rely solely on post-hoc calibration
  5. Schedule equipment-triggered re-evaluations and quarterly clinical validation panels
  6. 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.