package eeo import "testing" // point builds a FairnessPoint with the given impact ratio string for the pure alert logic tests. func point(ratio string) FairnessPoint { return FairnessPoint{ImpactRatio: ratio, SelectionRate: "0.5000"} } func TestAlertWhenLatestBelowThreshold(t *testing.T) { // The series ends below 0.80, so it must alert regardless of the prior trajectory. fs := &FairnessSeries{Points: []FairnessPoint{point("0.9000"), point("0.7500")}} evaluateAlert(fs) if !fs.Alert { t.Fatalf("a latest ratio of 0.7500 must alert, reason %q", fs.AlertReason) } } func TestAlertOnDriftDropEvenAboveThreshold(t *testing.T) { // Both points are at or above 0.80, but the drop 0.95 to 0.89 is 0.06, more than the 0.05 drift // limit, so a best in world tool warns before the group ever crosses the threshold. fs := &FairnessSeries{Points: []FairnessPoint{point("0.9500"), point("0.8900")}} evaluateAlert(fs) if !fs.Alert { t.Fatalf("a 0.06 drop above the threshold must alert, reason %q", fs.AlertReason) } } func TestNoAlertOnSmallDriftAboveThreshold(t *testing.T) { // A 0.03 drop, both above 0.80, is within the drift limit and above the threshold, so no alert. fs := &FairnessSeries{Points: []FairnessPoint{point("0.9500"), point("0.9200")}} evaluateAlert(fs) if fs.Alert { t.Fatalf("a 0.03 drop above the threshold must not alert, reason %q", fs.AlertReason) } } func TestExactDriftDeltaDoesNotAlert(t *testing.T) { // A drop of exactly 0.05 is NOT more than the limit (the alert is strictly greater than driftDelta), // so the boundary case does not alert when both points stay above the threshold. fs := &FairnessSeries{Points: []FairnessPoint{point("0.9500"), point("0.9000")}} evaluateAlert(fs) if fs.Alert { t.Fatalf("a drop of exactly 0.05 must not alert, reason %q", fs.AlertReason) } } func TestNAPointsDoNotContributeToDrift(t *testing.T) { // An n/a ratio (the reference group or a cut with too few known groups) is skipped, so a series of // only n/a points never alerts and never panics. fs := &FairnessSeries{Points: []FairnessPoint{point("n/a"), point("n/a")}} evaluateAlert(fs) if fs.Alert { t.Fatalf("a series of only n/a points must not alert") } } func TestSingleBelowThresholdPointAlerts(t *testing.T) { // One snapshot, already below 0.80, alerts without a prior to compare against. fs := &FairnessSeries{Points: []FairnessPoint{point("0.6000")}} evaluateAlert(fs) if !fs.Alert { t.Fatalf("a single below threshold point must alert") } } func TestSnapshotRowFromImpactReferenceGroupHasNoRatio(t *testing.T) { // The reference group's report ratio is "n/a", so the snapshot row carries a nil impact ratio and is // not below threshold (a null impact_ratio in the column, never a fabricated 1.0). row := snapshotRowFromImpact("selection :: by sex", ImpactRow{Group: "male", Selected: 8, Considered: 10, Rate: "0.8000", ImpactRatio: "n/a"}) if row.impactRatio != nil { t.Fatalf("a reference group must store a null impact ratio, got %v", row.impactRatio) } if row.belowThreshold { t.Fatalf("a reference group must not be below threshold") } if !row.selectionRate.Equal(decimalOrZero("0.8000")) { t.Fatalf("selection rate not preserved, got %s", row.selectionRate) } } func TestSnapshotRowFromImpactFlagsBelowThreshold(t *testing.T) { // A group with ratio 0.5000 is below 0.80, so below_threshold must be true and the ratio preserved. row := snapshotRowFromImpact("selection :: by sex", ImpactRow{Group: "female", Selected: 4, Considered: 10, Rate: "0.4000", ImpactRatio: "0.5000"}) if row.impactRatio == nil || !row.impactRatio.Equal(decimalOrZero("0.5000")) { t.Fatalf("impact ratio not preserved, got %v", row.impactRatio) } if !row.belowThreshold { t.Fatalf("a 0.5000 ratio must be below threshold") } } func TestSnapshotRowsCoverBothStagesAndAllCuts(t *testing.T) { // One ImpactRow in every cut of both stages must produce six snapshot rows, so the time series covers // selection and screening across by sex, by race or ethnicity, and intersectional. one := []ImpactRow{{Group: "g", Selected: 1, Considered: 2, Rate: "0.5000", ImpactRatio: "1.0000"}} rep := Report{ Selection: AnalysisSection{Stage: "selection (recorded slate decisions)", ByGender: one, ByRace: one, Intersectional: one}, Screening: AnalysisSection{Stage: "screening (served scores)", ByGender: one, ByRace: one, Intersectional: one}, } rows := snapshotRowsFromReport(rep) if len(rows) != 6 { t.Fatalf("expected 6 snapshot rows across both stages and three cuts, got %d", len(rows)) } seen := map[string]bool{} for _, r := range rows { seen[r.decisionContext] = true } if len(seen) != 6 { t.Fatalf("expected 6 distinct decision_context labels, got %d: %v", len(seen), seen) } }