Where: faircode/manifest.py's ProtectedAttribute.disadvantaged_mask(), categorical "both lists given" branch.
The gap: when both disadvantaged_values and advantaged_values are declared, the mask logic is:
known = col.isin(disadvantaged_values) | col.isin(advantaged_values)
disadv = col.isin(disadvantaged_values)
If a manifest author typos the same value into both lists, that value is silently treated as known=True, disadv=True (unambiguously "disadvantaged") with no error - inconsistent with the rest of this dataclass, which otherwise validates aggressively (missing threshold, bad disadvantaged value, missing value/values fields, a RowFilter needing an operator - several added specifically to close exactly this kind of silent-ambiguity gap, e.g. #514, #547, #549).
Repro:
import pandas as pd
from faircode.manifest import ProtectedAttribute
pa = ProtectedAttribute(name='g', type='categorical', column='g',
disadvantaged_values=['a', 'b'], advantaged_values=['b', 'c'])
df = pd.DataFrame({'g': ['a', 'b', 'c']})
disadv, known = pa.disadvantaged_mask(df)
print(disadv.tolist()) # [True, True, False] - 'b' silently resolved as disadvantaged
print(known.tolist()) # [True, True, True] - no error, no warning
Fix direction: raise a clear ValueError at construction (or in disadvantaged_mask) when disadvantaged_values and advantaged_values overlap, naming the offending value(s) - matching this class's existing "fail loudly on ambiguous input" pattern.
Where:
faircode/manifest.py'sProtectedAttribute.disadvantaged_mask(), categorical "both lists given" branch.The gap: when both
disadvantaged_valuesandadvantaged_valuesare declared, the mask logic is:If a manifest author typos the same value into both lists, that value is silently treated as
known=True, disadv=True(unambiguously "disadvantaged") with no error - inconsistent with the rest of this dataclass, which otherwise validates aggressively (missing threshold, baddisadvantagedvalue, missing value/values fields, aRowFilterneeding an operator - several added specifically to close exactly this kind of silent-ambiguity gap, e.g. #514, #547, #549).Repro:
Fix direction: raise a clear
ValueErrorat construction (or indisadvantaged_mask) whendisadvantaged_valuesandadvantaged_valuesoverlap, naming the offending value(s) - matching this class's existing "fail loudly on ambiguous input" pattern.