tenseleyflow/sway / 15d4314

Browse files

probes/_zscore: refuse when stats['degenerate'] is set (F02)

Authored by espadonne
SHA
15d4314fc2940a66490307e9b2576bbf130c9003
Parents
edd5a03
Tree
8321e2a

1 changed file

StatusFile+-
M src/dlm_sway/probes/_zscore.py 9 1
src/dlm_sway/probes/_zscore.pymodified
@@ -39,7 +39,11 @@ def z_score(raw: float, stats: Mapping[str, float] | None) -> float | None:
3939
     Returns ``None`` when:
4040
 
4141
     - ``stats`` is missing (no calibration ran for this kind)
42
-    - ``std`` is below :data:`MIN_STD` (degenerate null distribution)
42
+    - ``stats["degenerate"]`` is truthy (F02 Audit 03 — null ran but
43
+      was too narrow to calibrate against: ``runs: 1``, or multi-seed
44
+      raws that collapsed to an effectively-zero variance)
45
+    - ``std`` is below :data:`MIN_STD` (belt-and-suspenders guard
46
+      for stats dicts that predate the ``degenerate`` field)
4347
     - ``raw`` or ``mean`` is non-finite
4448
 
4549
     Callers that get ``None`` are expected to fall back to their probe's
@@ -54,6 +58,10 @@ def z_score(raw: float, stats: Mapping[str, float] | None) -> float | None:
5458
         return None
5559
     if not (math.isfinite(raw) and math.isfinite(mean) and math.isfinite(std)):
5660
         return None
61
+    # ``degenerate`` is stored as a float (1.0 / 0.0) so the stats
62
+    # dict stays Mapping[str, float] across every consumer.
63
+    if stats.get("degenerate", 0.0) >= 0.5:
64
+        return None
5765
     if std < MIN_STD:
5866
         return None
5967
     return float((raw - mean) / std)