agentsclimarketplace

Fairness testing

Skill obielin/responsible-ai-skills/skills/fairness-testing

Skills framework for coding agents that enforces responsible AI practices — bias assessment, fairness testing, explainability, governance documentation, and alignment review. Auto-activates when building AI systems.

Install
npx -y skills add obielin/responsible-ai-skills --skill fairness-testing

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Use when writing tests for any ML model, classifier, or AI system that produces outputs affecting people. Fairness tests must be written before the model is used in production.

SKILL.md

8.0 KB, as published. Nobody here has run it

Fairness Testing

Fairness is not a property you check once — it's a property you test continuously. These tests run in CI and fail the build if the model regresses on fairness metrics.

The RED-GREEN-REFACTOR Cycle for Fairness

RED: Write a fairness test. Run it. Watch it fail (or confirm the threshold matters). GREEN: Adjust the model or data until the test passes. REFACTOR: Clean up. Do not delete the test.

This is non-negotiable. If you write no fairness tests, you have no fairness guarantees.


Test Structure

Every fairness test follows this pattern:

def test_<metric>_parity_across_<attribute>(model, test_data):
    """
    <metric> must not differ by more than <threshold> across <attribute> groups.
    
    Regulatory basis: Equality Act 2010 s.149 (PSED) / EU AI Act Art. 10
    """
    results = {}
    for group in test_data[attribute].unique():
        mask = test_data[attribute] == group
        results[group] = compute_metric(
            model, test_data[mask]
        )
    
    max_gap = max(results.values()) - min(results.values())
    assert max_gap <= THRESHOLD, (
        f"{metric} gap across {attribute} is {max_gap:.3f} "
        f"(max allowed: {THRESHOLD}). "
        f"Group breakdown: {results}"
    )

Required Test Suite

Write ALL of these. Skip none.

Test 1: Demographic Parity

import pytest
import numpy as np

DEMOGRAPHIC_PARITY_THRESHOLD = 0.05

def test_demographic_parity(model, test_df):
    """Positive prediction rate must not vary by more than 5% across protected groups."""
    protected_attrs = ['sex', 'ethnicity', 'age_group']  # adjust to your attributes
    
    for attr in protected_attrs:
        if attr not in test_df.columns:
            continue
        
        rates = {}
        for group in test_df[attr].unique():
            mask = test_df[attr] == group
            if mask.sum() < 30:  # skip groups too small to be meaningful
                continue
            X = test_df[mask].drop(columns=['label'])
            rates[group] = model.predict(X).mean()
        
        if len(rates) < 2:
            continue
        
        gap = max(rates.values()) - min(rates.values())
        assert gap <= DEMOGRAPHIC_PARITY_THRESHOLD, (
            f"Demographic parity violation on '{attr}': gap={gap:.3f} "
            f"(threshold={DEMOGRAPHIC_PARITY_THRESHOLD}). "
            f"Rates: {rates}"
        )

Test 2: Equal Opportunity (True Positive Rate Parity)

from sklearn.metrics import recall_score

EQUAL_OPPORTUNITY_THRESHOLD = 0.05

def test_equal_opportunity(model, test_df):
    """True positive rate must not vary by more than 5% across protected groups."""
    for attr in ['sex', 'ethnicity', 'age_group']:
        if attr not in test_df.columns:
            continue
        
        tprs = {}
        for group in test_df[attr].unique():
            mask = (test_df[attr] == group) & (test_df['label'] == 1)
            if mask.sum() < 20:
                continue
            X = test_df[mask].drop(columns=['label'])
            y_true = test_df[mask]['label']
            y_pred = model.predict(X)
            tprs[group] = recall_score(y_true, y_pred, zero_division=0)
        
        if len(tprs) < 2:
            continue
        
        gap = max(tprs.values()) - min(tprs.values())
        assert gap <= EQUAL_OPPORTUNITY_THRESHOLD, (
            f"Equal opportunity violation on '{attr}': gap={gap:.3f}. TPRs: {tprs}"
        )

Test 3: False Positive Rate Parity

from sklearn.metrics import confusion_matrix

FPR_THRESHOLD = 0.05

def test_false_positive_rate_parity(model, test_df):
    """False positive rate must not vary by more than 5% across groups.
    
    A higher FPR for one group means they are MORE LIKELY to be incorrectly
    flagged — a critical fairness concern in benefits, criminal justice, and hiring.
    """
    for attr in ['sex', 'ethnicity', 'age_group']:
        if attr not in test_df.columns:
            continue
        
        fprs = {}
        for group in test_df[attr].unique():
            mask = test_df[attr] == group
            if mask.sum() < 30:
                continue
            X = test_df[mask].drop(columns=['label'])
            y_true = test_df[mask]['label']
            y_pred = model.predict(X)
            tn, fp, fn, tp = confusion_matrix(y_true, y_pred, labels=[0, 1]).ravel()
            fprs[group] = fp / (fp + tn) if (fp + tn) > 0 else 0
        
        if len(fprs) < 2:
            continue
        
        gap = max(fprs.values()) - min(fprs.values())
        assert gap <= FPR_THRESHOLD, (
            f"FPR parity violation on '{attr}': gap={gap:.3f}. FPRs: {fprs}"
        )

Test 4: Performance Does Not Degrade for Minority Groups

from sklearn.metrics import f1_score

MIN_F1_THRESHOLD = 0.70  # adjust for your use case
MIN_GROUP_SIZE = 30

def test_minority_group_performance(model, test_df):
    """No group should fall below minimum acceptable F1 score.
    
    A model that is 95% accurate overall but 40% accurate for a minority group
    is not an acceptable model.
    """
    for attr in ['sex', 'ethnicity', 'age_group']:
        if attr not in test_df.columns:
            continue
        
        for group in test_df[attr].unique():
            mask = test_df[attr] == group
            if mask.sum() < MIN_GROUP_SIZE:
                continue
            X = test_df[mask].drop(columns=['label'])
            y_true = test_df[mask]['label']
            y_pred = model.predict(X)
            f1 = f1_score(y_true, y_pred, average='weighted', zero_division=0)
            
            assert f1 >= MIN_F1_THRESHOLD, (
                f"Performance below threshold for {attr}='{group}': "
                f"F1={f1:.3f} (min={MIN_F1_THRESHOLD}). "
                f"Group size: {mask.sum()}"
            )

Test 5: Fairness Regression Guard

import json
from pathlib import Path

BASELINE_FILE = "tests/fairness_baseline.json"

def test_fairness_does_not_regress(model, test_df):
    """Current fairness metrics must not be worse than the recorded baseline.
    
    Run `python skills/fairness-testing/scripts/record_baseline.py` to update
    the baseline after intentional model changes.
    """
    if not Path(BASELINE_FILE).exists():
        pytest.skip("No fairness baseline recorded. Run record_baseline.py first.")
    
    baseline = json.loads(Path(BASELINE_FILE).read_text())
    
    for attr, metrics in baseline.items():
        if attr not in test_df.columns:
            continue
        for metric_name, baseline_value in metrics.items():
            current = compute_fairness_metric(model, test_df, attr, metric_name)
            # Allow 2% degradation tolerance
            assert current >= baseline_value - 0.02, (
                f"Fairness regression: {metric_name} on '{attr}' "
                f"dropped from {baseline_value:.3f} to {current:.3f}"
            )

CI Integration

Add to your CI pipeline (pytest.ini or pyproject.toml):

[pytest]
markers =
    fairness: marks tests as fairness tests (run with -m fairness)

Tag all fairness tests:

@pytest.mark.fairness
def test_demographic_parity(...):
    ...

Run fairness tests in CI:

- name: Run fairness tests
  run: pytest -m fairness --tb=short

Fairness test failures are build failures. Do not merge code that fails fairness tests.


Completion Checklist

  • Demographic parity test written and passing
  • Equal opportunity test written and passing
  • False positive rate parity test written and passing
  • Minority group performance test written and passing
  • Fairness regression guard set up with baseline recorded
  • Fairness tests integrated into CI pipeline
  • Tests are marked with @pytest.mark.fairness

You may now proceed. Run alignment-review before marking the feature complete.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.