An aggregate pass can hide a group the model fails. For de-identification that
failure has a name: under-protection — PHI that leaks more often for one
demographic group than another. openmed.eval.fairness_report slices leakage and
recall by gold-span group so disparities surface before deployment, not after a
breach.
For each surrogate group fairness_report returns:
max - min leakage across groups (the gap to close).Group membership comes from a group tag in each gold span's metadata (keys
group, demographic_group, or surrogate_group); ungrouped spans fall into
unspecified.
from openmed.eval import fairness_report
# Gold fixtures must tag spans with a surrogate group, e.g.
# {"start": 4, "end": 12, "label": "PERSON", "metadata": {"group": "female"}}
fair = fairness_report(
"OpenMed/Privacy-PII-Detection",
"golden", # named suite, or pass a list of fixtures
device="cpu",
)
print("leakage disparity:", fair.leakage_disparity)
print("worst group :", fair.worst_group, fair.worst_group_leakage)
for group, m in sorted(fair.per_group.items()):
print(f" {group:14s} recall={m.recall:.3f} leakage={m.leakage_rate:.4f}")
# Under-protection alarm: any group leaking more than the rest.
LEAKAGE_GAP_LIMIT = 0.0 # leakage-first: ideally zero leakage everywhere
assert fair.leakage_disparity <= LEAKAGE_GAP_LIMIT or fair.worst_group_leakage == 0
FairnessReport.to_dict() is JSON-ready and PHI-free — drop it straight into a
model card.
group to each PHI span's
metadata (sex, age band, race/ethnicity surrogate). Use synthetic surrogates,
not real protected attributes (see building-gold-corpus).fairness_report on the model + suite.leakage_disparity) and locate worst_group.
Equalized-odds framing: equal true-positive (recall) and equal leakage
across groups.building-gold-corpus: supplies group-tagged synthetic fixtures.evaluating-with-leakage-gates: an aggregate RELEASABLE decision
should be paired with this audit — overall pass, subgroup fail is exactly the
trap this catches.authoring-model-cards: FairnessReport.to_dict() fills the
quantitative-analysis / subgroup section.benchmarking-clinical-ner: same run, different slice (label vs
group).unspecified is not a real group. A pile of spans in unspecified means
your gold isn't tagged; fix the corpus before trusting the disparity.span_count,
total_chars) alongside rates; a 1-of-2 leak isn't a 50% population rate.openmed/eval/fairness.py
(fairness_report, FairnessReport, FairnessGroupMetrics).