Overview
This walkthrough demonstrates a complete fairness audit of a ranking/recommender system using the vfairness library. Unlike binary classifiers, ranking systems introduce unique fairness challenges: exposure allocation, position bias, and feedback loops where biased rankings generate biased engagement data that reinforces the original bias.
By following this guide, you will produce: a validated click-data audit, exposure parity analysis, engagement proxy detection, fair re-ranking evaluation, ranking-specific metrics (NDKL, attention fairness), CI/CD gates with ranking thresholds, feedback-loop monitoring, an A/B experiment comparing fair vs. baseline ranking, causal decomposition of ranking bias, and an EU AI Act–compliant model card — all backed by 24 production-grade SVG artifacts.
The Scenario
Job Platform Candidate Ranking — EU AI Act High-Risk System
A professional hiring platform deploys a learning-to-rank model (LambdaMART) to order candidates for recruiter search results. Under the EU AI Act (Annex III, Section 4a), AI systems intended to be used for recruitment or selection of natural persons, particularly for screening, filtering, or ranking candidates, are classified as high-risk AI, requiring comprehensive fairness documentation, ongoing monitoring, and human oversight.
import pandas as pd
import numpy as np
from lightgbm import LGBMRanker
# ── Load the candidate ranking dataset ──
df = pd.read_csv("candidate_profiles.csv")
clicks = pd.read_csv("recruiter_clicks.csv") # historical engagement data
# Protected attributes
PROTECTED = ["gender", "ethnicity", "age_group"]
FEATURES = ["skills_match", "experience_years", "education_score",
"profile_completeness", "past_engagement_rate",
"recency_score", "keyword_overlap"]
# Merge engagement signals
df = df.merge(clicks.groupby("candidate_id").agg(
click_rate=("clicked", "mean"),
impressions=("impression_id", "count"),
), on="candidate_id", how="left").fillna(0)
# Train learning-to-rank model
ranker = LGBMRanker(objective="lambdarank", n_estimators=300, max_depth=6)
ranker.fit(
X_train[FEATURES], y_train_relevance,
group=train_query_groups,
eval_set=[(X_val[FEATURES], y_val_relevance)],
eval_group=[val_query_groups],
)
scores = ranker.predict(X_test[FEATURES])
rankings = scores.argsort()[::-1] # descending score → rank
Audit Pipeline
The ten phases below are adapted for ranking systems. Key differences from classification audits: Phase 3 targets engagement-signal proxies, Phase 4 uses post-processing re-ranking (not in-processing), Phase 6 adds ranking-specific metrics (NDKL, exposure parity), and Phase 9 runs a controlled A/B experiment with causal decomposition.
Validation
Detection
Detection
Re-ranking
Calibration
Fairness
Gating
Testing
Card
Data Validation
Validate the training data — both candidate profiles and historical click/engagement data — for representation balance, missing patterns, and position-bias contamination. Click data is particularly vulnerable to feedback loops: candidates historically ranked lower receive fewer impressions, generating less engagement data regardless of their true relevance.
from vfairness.operations.cicd import DataBiasValidator, DataValidationConfig
validator = DataBiasValidator(config=DataValidationConfig(
min_group_size=200, # higher bar for ranking (more groups, more queries)
max_missing_rate=0.03,
proxy_correlation_threshold=0.25, # stricter for engagement features
))
result = validator.validate(df, protected_attrs=PROTECTED, outcome_col="click_rate")
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="Pre-training data quality assessment for the candidate ranking dataset. "
"Checks group representation, missing patterns, engagement feature correlations, "
"and position-bias contamination in historical click data.",
save_path="artifacts/01_data_validation.svg",
)
1 critical issue (engagement data click_rate is confounded with historical position — candidates from underrepresented groups had 40% fewer average impressions), 2 warnings (low representation for age_group=55+), 2 informational notes. Position-bias decontamination required before model training.
Bias Detection & Auto-Discovery
Run the auto-discovery scanner to identify protected attributes, then perform a full bias audit. For ranking systems, bias detection examines both representation in the candidate pool and exposure distribution in historical rankings — who appears in the top positions and how often.
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: confirm protected attributes ──
candidates = detect_protected_attributes(df, outcome_col="click_rate")
violations = scan_fairness_violations(
y_true=y_test_relevance, y_pred=scores, sensitive=sensitive_test["gender"]
)
intersectional = discover_intersectional_groups(
df, protected_attrs=PROTECTED, outcome_col="click_rate"
)
auto_discovery_to_svg(
candidates=candidates, violations=violations,
group_advantages=intersectional,
explanation="Automated scan confirming gender, ethnicity, and age_group as protected "
"attributes. Intersectional analysis reveals compounding disadvantage.",
save_path="artifacts/02a_auto_discovery.svg",
)
# ── Full bias audit ──
detector = BiasDetector(df, protected_attributes=PROTECTED, outcome_column="click_rate")
audit = detector.full_audit()
bias_audit_to_svg(
audit.to_dict(),
explanation="Comprehensive bias audit of historical engagement data. Click rates "
"confounded with exposure — disparities may reflect position bias, not preference.",
save_path="artifacts/02b_bias_audit.svg",
)
Historical click rates show gender disparity of 0.18 (threshold 0.10). Intersectional analysis reveals female × minority × 45+ subgroup received 62% fewer top-10 placements than the overall average. Critical: this disparity is confounded with position bias and cannot be attributed to candidate quality.
Proxy Detection — Engagement Signals
In ranking systems, engagement features (click-through rate, time-on-profile, response rate) are potent proxy variables. They encode historical recruiter preferences that systematically disadvantage certain groups. Detect and mitigate these proxy correlations before training the ranker.
from vfairness.preprocessing.feature_engineering import FeatureEngineeringAnalyzer
from vfairness.rendering import (
proxy_risk_to_svg, correlation_heatmap_to_svg,
transformation_comparison_to_svg,
)
analyzer = FeatureEngineeringAnalyzer(
df, protected_attributes=PROTECTED, feature_columns=FEATURES + ["click_rate"]
)
report = analyzer.full_analysis()
# ── Proxy risk assessment ──
proxy_risk_to_svg(
report.proxy_results,
explanation="Engagement features (click_rate, past_engagement_rate) flagged as "
"critical proxies — they encode historical position bias and recruiter "
"preference patterns that correlate with protected attributes (r=0.45 with ethnicity).",
save_path="artifacts/03a_proxy_risk.svg",
)
# ── Correlation heatmap ──
correlation_heatmap_to_svg(
report.correlation_matrix,
explanation="Feature-to-protected-attribute correlations. past_engagement_rate and "
"click_rate show strong proxy signals. profile_completeness shows moderate risk.",
save_path="artifacts/03b_correlation_heatmap.svg",
)
# ── Apply transformation ──
X_fair = analyzer.transform(method="correlation_reduction")
transformation_comparison_to_svg(
{"before": report.correlation_matrix, "after": X_fair.correlation_matrix},
explanation="Correlation reduction applied to engagement features. Average proxy "
"correlation reduced from 0.29 to 0.06, below the 0.10 threshold.",
save_path="artifacts/03c_transformation_comparison.svg",
)
Engagement proxy correlations mitigated. click_rate correlation with ethnicity reduced from 0.45 to 0.04. past_engagement_rate reduced from 0.38 to 0.06. All features now below the 0.10 proxy threshold. The ranker will be retrained on decontaminated features.
Fair Re-ranking
Apply fairness-constrained re-ranking as a post-processing step. The ranker produces relevance scores; the re-ranker interleaves candidates to achieve exposure parity while minimizing NDCG loss. Compare multiple re-ranking strategies (unconstrained, proportional, equal exposure) on the accuracy–fairness Pareto frontier.
from vfairness.post_processing import ThresholdOptimizer
from vfairness.rendering import (
tradeoff_analysis_to_svg, pareto_frontier_to_svg, method_comparison_to_svg,
)
# ── Compare re-ranking strategies ──
strategies = {
"Unconstrained": {"exposure_constraint": None},
"Proportional": {"exposure_constraint": "proportional"}, # exposure ∝ group size
"Equal Exposure": {"exposure_constraint": "equal"}, # equal exposure per group
}
results = {}
for name, config in strategies.items():
optimizer = ThresholdOptimizer(constraint=config["exposure_constraint"])
results[name] = optimizer.optimize(scores, sensitive_test, rankings)
# ── Pareto frontier: NDCG vs. exposure parity ──
pareto_frontier_to_svg(
results,
explanation="Accuracy-fairness Pareto frontier. 'Proportional' re-ranking achieves "
"exposure parity 0.04 with only 2.1% NDCG degradation — the optimal tradeoff.",
save_path="artifacts/04a_pareto_frontier.svg",
)
# ── Method comparison ──
method_comparison_to_svg(
results,
explanation="Head-to-head comparison of three re-ranking strategies across NDCG@10, "
"exposure parity, NDKL, and attention fairness.",
save_path="artifacts/04b_method_comparison.svg",
)
Proportional re-ranking selected. NDCG@10 dropped from 0.847 to 0.826 (2.5% loss) while exposure parity improved from 0.22 to 0.04. Equal-exposure was too aggressive (8.1% NDCG loss). The proportional strategy is deployed as the production ranking policy.
Score Calibration
Calibrate the underlying relevance scores so they are comparable across demographic groups. A score of 0.8 should mean the same thing regardless of the candidate’s gender or ethnicity. This is essential for downstream thresholding and for explaining scores to recruiters.
from vfairness.post_processing import FairnessCalibrator
from vfairness.rendering import (
reliability_diagram_to_svg, calibration_report_to_svg,
group_calibration_to_svg,
)
calibrator = FairnessCalibrator(method="platt", groupwise=True)
calibrated_scores = calibrator.fit_transform(scores, sensitive_test)
cal_report = calibrator.report()
reliability_diagram_to_svg(
cal_report,
explanation="Reliability diagram showing predicted relevance vs. observed engagement "
"across decile bins. Post-calibration curves align closely with the diagonal.",
save_path="artifacts/05a_reliability.svg",
)
calibration_report_to_svg(
cal_report,
explanation="Calibration metrics pre- and post-adjustment. ECE reduced from 0.09 to 0.02.",
save_path="artifacts/05b_calibration.svg",
)
group_calibration_to_svg(
cal_report,
explanation="Per-group calibration curves. Before adjustment, female candidates were "
"systematically under-scored by 0.06 points. Post-calibration gap: 0.008.",
save_path="artifacts/05c_group_calibration.svg",
)
Group-wise Platt scaling successfully applied. Expected calibration error (ECE) reduced from 0.09 to 0.02. The systematic under-scoring of female candidates eliminated (gap reduced from 0.06 to 0.008). Calibrated scores are now suitable for recruiter-facing explanations.
Ranking Fairness Metrics
Evaluate the re-ranked results using ranking-specific fairness metrics that go beyond standard classification metrics. These measure not just outcome parity but attention allocation — how much recruiter visibility each group receives based on their position in ranked lists.
from vfairness.evaluation.vfairness_metrics import FairnessAnalyzer
from vfairness.evaluation.vfairness_metrics.ranking import (
get_ranking_fairness_metrics, get_ranking_group_metrics,
)
from vfairness.evaluation.vfairness_metrics.robustness import (
permutation_test, sensitivity_analysis, subgroup_audit,
)
from vfairness.rendering import (
ranking_fairness_to_svg, radar_chart_to_svg, metrics_bar_chart_to_svg,
confidence_intervals_to_svg, effect_sizes_to_svg, group_comparison_to_svg,
robustness_testing_to_svg, intersectional_analysis_to_svg,
)
# ── Ranking-specific metrics ──
ranking_result = get_ranking_fairness_metrics(
rankings=fair_rankings, sensitive=sensitive_test["gender"],
scores=calibrated_scores,
)
group_metrics = get_ranking_group_metrics(
rankings=fair_rankings, sensitive=sensitive_test["gender"],
scores=calibrated_scores,
)
ranking_fairness_to_svg(
ranking_result, group_metrics,
explanation="Ranking fairness dashboard: exposure parity 0.04 (pass), NDKL 0.08 (pass), "
"attention fairness 0.06 (pass). All groups receive proportional visibility.",
save_path="artifacts/06a_ranking_fairness.svg",
)
# ── Standard fairness metrics on binarized top-k ──
analyzer = FairnessAnalyzer(task_type="classification")
report = analyzer.analyze(
y_true=(y_test_relevance >= 3).astype(int), # relevant = top category
y_pred=(fair_rankings <= 10).astype(int), # predicted top-10
sensitive=sensitive_test,
)
radar_chart_to_svg(report, explanation="...", save_path="artifacts/06b_radar.svg")
metrics_bar_chart_to_svg(report, explanation="...", save_path="artifacts/06c_bar_chart.svg")
confidence_intervals_to_svg(report, explanation="...", save_path="artifacts/06d_ci.svg")
effect_sizes_to_svg(report, explanation="...", save_path="artifacts/06e_effects.svg")
group_comparison_to_svg(report, explanation="...", save_path="artifacts/06f_group.svg")
# ── Robustness testing ──
perm = permutation_test(y_test_relevance, fair_rankings, sensitive_test["gender"])
sens = sensitivity_analysis(y_test_relevance, fair_rankings, sensitive_test["gender"])
sub = subgroup_audit(y_test_relevance, fair_rankings, sensitive_test)
robustness_testing_to_svg(
perm, sens, sub,
explanation="Robustness: 3/3 permutation tests pass, sensitivity stable under 5% "
"perturbation, no flagged subgroups.",
save_path="artifacts/06g_robustness.svg",
)
# ── Intersectional deep-dive ──
intersectional_analysis_to_svg(
report,
explanation="Intersectional exposure analysis across gender × ethnicity × age. "
"No subgroup falls below 80% of proportional exposure threshold.",
save_path="artifacts/06h_intersectional.svg",
)
All ranking fairness metrics pass. Exposure parity 0.04 (threshold 0.10), NDKL 0.08 (threshold 0.15), attention fairness 0.06 (threshold 0.10). Standard metrics on binarized top-10 also pass: demographic parity 0.05, equalized odds 0.07. Robustness tests confirm stability under perturbation. No intersectional subgroup is flagged.
CI/CD Gating
Configure CI/CD gates with ranking-specific thresholds. The hierarchical gate evaluates standard fairness metrics at Level 1, ranking metrics (exposure parity, NDKL) at Level 2, and intersectional checks at Level 3. A model must pass all levels to be deployed.
from vfairness.operations.cicd import FairnessGate, GateConfig
from vfairness.operations.cicd.gate import (
evaluate_hierarchical, HierarchicalGateConfig,
)
from vfairness.rendering import cicd_pipeline_to_svg, hierarchical_gate_to_svg
# ── Simple gate ──
gate = FairnessGate(config=GateConfig(
thresholds={
"demographic_parity_difference": 0.10,
"exposure_parity_difference": 0.10,
"ndkl": 0.15,
},
block_on_fail=True,
))
decision = gate.evaluate(report["metrics"])
cicd_pipeline_to_svg(
decision,
explanation="CI/CD gate with ranking-specific thresholds. Model passes all checks: "
"demographic parity 0.05, exposure parity 0.04, NDKL 0.08.",
save_path="artifacts/07a_cicd.svg",
)
# ── Hierarchical gate ──
hier_config = HierarchicalGateConfig(
level_1_thresholds={"demographic_parity_difference": 0.10},
level_2_thresholds={"exposure_parity_difference": 0.10, "ndkl": 0.15},
level_3_thresholds={"attention_fairness": 0.10},
)
hier_decision = evaluate_hierarchical(report["metrics"], hier_config)
hierarchical_gate_to_svg(
hier_decision,
explanation="Three-level hierarchical gate: L1 standard fairness → L2 ranking fairness "
"→ L3 intersectional attention. All levels PASS.",
save_path="artifacts/07b_hierarchical.svg",
)
All three gate levels passed. The model is cleared for production deployment with the proportional re-ranking policy active.
Monitoring & Feedback Loop Detection
Deploy continuous monitoring with special attention to feedback loops. In ranking systems, biased rankings generate biased click data, which retrains the model to produce even more biased rankings. Monitor temporal trends in exposure parity and alert on divergence patterns that indicate loop amplification.
from vfairness.operations.monitoring import MetricsStore, DriftDetector, AlertManager
from vfairness.rendering import (
monitoring_dashboard_to_svg, drift_report_to_svg,
alert_timeline_to_svg, temporal_analysis_to_svg,
)
store = MetricsStore(backend="sqlite", path="ranking_fairness_metrics.db")
detector = DriftDetector(method="cusum", threshold=0.02, window=30)
alerter = AlertManager(channels=["slack", "email"], severity_threshold="warning")
# ── Log daily metrics ──
store.log(report["metrics"], tags={"model": "LambdaMART-v3.1", "policy": "proportional"})
# ── Generate monitoring artifacts ──
monitoring_dashboard_to_svg(
store.get_history(days=90),
explanation="90-day monitoring dashboard showing exposure parity, NDKL, and "
"attention fairness trends. No drift detected.",
save_path="artifacts/08a_monitoring.svg",
)
drift_report_to_svg(
detector.report(),
explanation="CUSUM drift detection across ranking fairness metrics. All metrics "
"within control limits. No feedback-loop amplification detected.",
save_path="artifacts/08b_drift.svg",
)
alert_timeline_to_svg(
alerter.get_history(days=90),
explanation="Alert timeline for the past 90 days. Two informational alerts for "
"minor exposure shifts, both auto-resolved within 24 hours.",
save_path="artifacts/08c_alerts.svg",
)
temporal_analysis_to_svg(
store.get_history(days=90),
explanation="Temporal analysis of position distribution stability. No feedback-loop "
"amplification pattern detected — exposure parity remains stable at 0.04 ± 0.01.",
save_path="artifacts/08d_temporal.svg",
)
90-day monitoring shows stable fairness metrics. No feedback-loop amplification detected — exposure parity remains at 0.04 ± 0.01. CUSUM detector shows all metrics within control limits. Two minor alerts were auto-resolved. Monthly model retraining schedule confirmed safe.
A/B Testing & Causal Analysis
Run a controlled A/B experiment comparing the baseline (unfair) ranking against the proportional re-ranking policy. Use the experimentation framework to determine statistical significance, estimate the causal effect of the fairness intervention, and decompose direct vs. mediated effects.
from vfairness.operations.experimentation import (
ExperimentRunner, ExperimentConfig, PowerAnalyzer,
CausalAnalyzer,
)
from vfairness.rendering import (
experiment_results_to_svg, experiment_recommendation_to_svg,
power_analysis_to_svg, causal_decomposition_to_svg,
)
# ── Pre-experiment power analysis ──
power = PowerAnalyzer(effect_size=0.05, alpha=0.05, power=0.80)
power_result = power.analyze(n_per_group=5000)
power_analysis_to_svg(
power_result,
explanation="Power analysis: n=5,000 per arm sufficient to detect exposure parity "
"difference of 0.05 with 80% power at α=0.05.",
save_path="artifacts/09a_power.svg",
)
# ── Run A/B experiment ──
config = ExperimentConfig(
control="baseline_ranking",
treatment="proportional_reranking",
metric="exposure_parity_difference",
duration_days=14,
min_sample_size=5000,
)
runner = ExperimentRunner(config)
result = runner.analyze(control_data, treatment_data)
experiment_results_to_svg(
result,
explanation="A/B experiment: proportional re-ranking reduces exposure parity from "
"0.22 to 0.04 (Δ=−0.18, p<0.001) with no significant NDCG loss (Δ=−0.021, p=0.12).",
save_path="artifacts/09b_experiment.svg",
)
experiment_recommendation_to_svg(
result,
explanation="Recommendation: deploy proportional re-ranking. Statistically significant "
"fairness improvement with non-significant accuracy impact.",
save_path="artifacts/09c_recommendation.svg",
)
# ── Causal decomposition ──
causal = CausalAnalyzer()
decomposition = causal.decompose(
treatment="reranking_policy",
mediator="recruiter_shortlist_diversity",
outcome="exposure_parity",
data=experiment_data,
)
causal_decomposition_to_svg(
decomposition,
explanation="Causal decomposition: 62% of fairness improvement is direct (re-ordering), "
"38% is mediated through changed recruiter behavior (more diverse shortlists).",
save_path="artifacts/09d_causal.svg",
)
Experiment confirms causal fairness improvement. Proportional re-ranking reduces exposure parity from 0.22 to 0.04 (p<0.001). NDCG impact is not statistically significant (p=0.12). Causal decomposition: 62% direct effect (re-ordering), 38% mediated through recruiter behavior. Recommendation: deploy treatment arm to 100% of traffic.
EU AI Act Model Card
The final deliverable: a FairnessReportCard that consolidates all audit findings into a regulation-ready document. For ranking systems under EU AI Act Annex III Section 4(a), the card must address recruitment-specific transparency requirements including the ranking criteria, exposure allocation methodology, and feedback-loop mitigation strategy.
from vfairness.operations.cicd.gate import FairnessReportCard
from vfairness.rendering import report_card_to_svg
card = FairnessReportCard(
model_name="CandidateRanker-LambdaMART-v3.1",
model_version="3.1.0",
description="Learning-to-rank model for candidate search results. Produces relevance "
"scores with proportional fairness re-ranking applied post-hoc.",
intended_use="Ranking job candidates for recruiter search queries on the hiring platform. "
"Recruiters see ranked lists and make independent contact decisions.",
risk_classification="HIGH", # EU AI Act Annex III, Section 4(a)
protected_attributes=PROTECTED,
fairness_metrics={
**report["metrics"],
"exposure_parity_difference": 0.04,
"ndkl": 0.08,
"attention_fairness": 0.06,
},
training_data_summary={
"size": 120_000,
"source": "Platform candidate profiles and engagement data, 2023-2025",
"preprocessing": "Engagement proxy decontamination, position-bias correction",
},
evaluation_summary={
"test_size": 36_000,
"ab_experiment": "14-day, n=10,000 (5k per arm)",
"causal_analysis": "Baron-Kenny mediation, 62% direct / 38% mediated",
},
gate_decision=decision.to_dict(),
monitoring_config={
"drift_detection": "CUSUM on exposure_parity, ndkl, attention_fairness",
"feedback_loop_detection": "enabled — temporal stability monitoring",
"alert_thresholds": {"exposure_parity_difference": 0.08},
"review_cadence": "bi-weekly (ranking systems require faster review cycles)",
},
human_oversight="Recruiters make all contact decisions independently. Monthly fairness "
"review board with data science, legal, and HR representation. "
"Quarterly external audit by independent assessor.",
limitations=[
"Model trained on historical engagement data that may encode past recruiter preferences.",
"Feedback-loop mitigation reduces but does not eliminate amplification risk.",
"Proportional re-ranking optimized for gender; multi-attribute Pareto is approximate.",
"Small sample sizes for non-binary gender group (n=850) and 55+ age group (n=1,200).",
],
references=[
"EU AI Act, Regulation 2024/1689, Annex III Section 4(a)",
"Singh & Joachims (2018) — Fairness of exposure in rankings",
"Zehlike et al. (2022) — Fair ranking: a critical review",
],
)
report_card_to_svg(
card,
explanation="EU AI Act-compliant model card for the ranking system. Addresses Annex III "
"Section 4(a) recruitment requirements including ranking criteria transparency, "
"exposure allocation methodology, feedback-loop mitigation, and A/B evidence.",
save_path="artifacts/10_model_card.svg",
)
This model card, combined with the 24 SVG artifacts generated throughout the audit, addresses the following EU AI Act requirements for high-risk AI systems in recruitment:
- Article 9 — Risk management system (Phases 1-3: data validation, bias detection, proxy mitigation)
- Article 10 — Data governance (Phase 1: engagement data decontamination, position-bias correction)
- Article 11 — Technical documentation (All phases: 24 SVG artifacts + model card)
- Article 13 — Transparency (Phase 10: ranking criteria, exposure allocation methodology)
- Article 14 — Human oversight (Recruiters make independent decisions; bi-weekly review board)
- Article 15 — Accuracy & robustness (Phase 6: robustness testing, Phase 9: A/B causal evidence)
- Article 26 — Deployer obligations (Feedback-loop monitoring, quarterly external audit)
- Article 72 — Post-market monitoring (Phase 8: CUSUM drift detection, temporal stability)
Complete Artifact Inventory
The table below lists every artifact generated during this ranking fairness audit. Each SVG is a self-contained, print-ready visualization suitable for regulatory submissions.
| 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 |
| 3 | Transformation Comparison | transformation_comparison_to_svg() | Preprocessing |
| 4 | Pareto Frontier | pareto_frontier_to_svg() | Post-Processing |
| 4 | Method Comparison | method_comparison_to_svg() | Post-Processing |
| 5 | Reliability Diagram | reliability_diagram_to_svg() | Post-Processing |
| 5 | Calibration Report | calibration_report_to_svg() | Post-Processing |
| 5 | Group Calibration | group_calibration_to_svg() | Post-Processing |
| 6 | Ranking Fairness Dashboard | ranking_fairness_to_svg() | Evaluation |
| 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 | Effect Sizes | effect_sizes_to_svg() | Evaluation |
| 6 | Robustness Testing | robustness_testing_to_svg() | Evaluation |
| 6 | Intersectional Analysis | intersectional_analysis_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 |
| 8 | Alert Timeline | alert_timeline_to_svg() | Operations |
| 8 | Temporal Analysis | temporal_analysis_to_svg() | Operations |
| 9 | Power Analysis | power_analysis_to_svg() | Experimentation |
| 9 | Experiment Results | experiment_results_to_svg() | Experimentation |
| 9 | Experiment Recommendation | experiment_recommendation_to_svg() | Experimentation |
| 9 | Causal Decomposition | causal_decomposition_to_svg() | Experimentation |
| 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 ranking systems with feedback loops, automation is especially important to catch drift before biased rankings reinforce themselves. See the Workflow Integration guide for full documentation.
1. Experiment Tracking
Log every ranker evaluation’s fairness metrics to MLflow or Weights & Biases. Track exposure parity, NDKL, and attention fairness across model versions and re-ranking strategies.
# Option A: explicit logging after Phase 6
from vfairness import log_fairness_to_mlflow
import mlflow
with mlflow.start_run(run_name="lambdamart-reranked-v4.2"):
logged = log_fairness_to_mlflow(report, prefix="ranking_v4.2")
print(f"Logged {logged} metrics to MLflow")
# Option B: auto-logging decorator — wraps ranking evaluation
from vfairness import auto_log_fairness
@auto_log_fairness(backend="wandb", prefix="ranking")
def evaluate_ranker(rankings, relevance_labels, sensitive):
return rankings, relevance_labels, sensitive # triggers fairness logging
2. Pre-Commit Hooks
Enforce documentation standards before code reaches the repository. Catches missing fairness thresholds in config files and incomplete model cards before they are committed.
# .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 ranking systems, test that exposure parity and position fairness hold across demographic groups — same checks as Phase 6, but automated on every commit.
import pytest
from vfairness import assert_fairness, FairnessTestSuite
# Test ranking fairness across candidate demographics
def test_ranking_fairness():
assert_fairness(
y_true, y_pred, sensitive_test["gender"],
metrics=["demographic_parity_difference"],
thresholds={"demographic_parity_difference": 0.10},
)
# Full test suite covering all protected attributes
suite = FairnessTestSuite(
protected_attributes=["gender", "ethnicity", "age_group"],
metrics=["demographic_parity_difference"],
thresholds={"demographic_parity_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 ranking systems, this is critical: every model update or re-ranking parameter change must be fairness-validated before affecting real candidates.
# 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="JobRank-LambdaMART-v4.2")
comment_payload = card.to_github_comment_payload()
# → POST to /repos/{owner}/{repo}/issues/{pr_number}/comments
# .github/workflows/fairness-checks.yml
name: Ranking 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_ranking_gate.py # runs gate + posts PR comment
Phase 6 (Ranking Fairness) → @auto_log_fairness + assert_fairness()
Phase 7 (CI/CD) → create_github_check() + GitHub Actions YAML
Phase 9 (A/B Testing) → Log experiment results to MLflow for treatment comparison
Phase 10 (Model Card) → FairnessReportCard.to_github_comment_payload()
Every commit → Pre-commit hooks validate configs + model cards
EU AI Act Compliance Checklist
Each requirement below is addressed by one or more artifacts from this audit. This checklist can be submitted alongside the model card as part of your conformity assessment documentation for recruitment AI under Annex III Section 4(a).
This walkthrough used demo data for illustration. To apply this process to your own ranking system:
- Replace the dataset and ranker with your own learning-to-rank model
- Identify engagement features that may serve as proxy variables and decontaminate
- Evaluate multiple re-ranking strategies on the accuracy–fairness Pareto frontier
- Run an A/B experiment to establish causal evidence before full deployment
- Configure feedback-loop monitoring with tighter review cycles (bi-weekly recommended)
- Submit the model card and artifact bundle as part of your conformity assessment
Explore all 49 SVG templates in the SVG Gallery, or learn about the full library architecture in the Getting Started guide.