Background
Deferred follow-up from the PR #252 fresh-audit (Finding B). The audit fix was reverted in bef07c3 so it could be handled separately with the simulation proof that statistical-evaluation changes require.
Problem
In gate_diagnostics (src/skdr_eval/recommendation.py), the overlap/match-rate and ESS checks coerce their inputs with a NaN-only guard:
min_ps_val = (
float(min_ps) if min_ps is not None and not np.isnan(float(min_ps)) else None
)
This treats a non-finite ±inf as a real value rather than "missing". Because the overlap and ESS failures only fire on the finite-small side, an +inf can't currently produce a wrong fail, but it is inconsistent with the recommendation/card paths, which route the same diagnostics through the shared _coerce_optional_float helper (which maps NaN and ±inf → None).
Proposed change
Route min_pscore, match_rate, and ESS through _coerce_optional_float in gate_diagnostics, so non-finite values are reported as "not available" consistently with the rest of the pipeline.
Critical caveat — do NOT coerce pareto_k
pareto_k must keep its current NaN-only guard. A non-finite (+inf) Pareto-k is the catastrophic-tail case and must remain a hard fail; only NaN ("not estimated") should be treated as missing. Coercing pareto_k through _coerce_optional_float would silently downgrade a catastrophic result to "not available" — a real verdict regression.
Acceptance criteria
References
Background
Deferred follow-up from the PR #252 fresh-audit (Finding B). The audit fix was reverted in
bef07c3so it could be handled separately with the simulation proof that statistical-evaluation changes require.Problem
In
gate_diagnostics(src/skdr_eval/recommendation.py), the overlap/match-rate and ESS checks coerce their inputs with a NaN-only guard:This treats a non-finite
±infas a real value rather than "missing". Because the overlap and ESS failures only fire on the finite-small side, an+infcan't currently produce a wrong fail, but it is inconsistent with the recommendation/card paths, which route the same diagnostics through the shared_coerce_optional_floathelper (which maps NaN and ±inf →None).Proposed change
Route
min_pscore,match_rate, andESSthrough_coerce_optional_floatingate_diagnostics, so non-finite values are reported as "not available" consistently with the rest of the pipeline.Critical caveat — do NOT coerce
pareto_kpareto_kmust keep its current NaN-only guard. A non-finite (+inf) Pareto-k is the catastrophic-tail case and must remain a hardfail; only NaN ("not estimated") should be treated as missing. Coercingpareto_kthrough_coerce_optional_floatwould silently downgrade a catastrophic result to "not available" — a real verdict regression.Acceptance criteria
min_pscore/match_rate/ESSnon-finite → reported as missing (valueNone, no spurious fail).pareto_k == +inf→ stillfail/overall == "fail".pareto_k == NaN→ missing (valueNone, not a fail)..claude/CLAUDE.md§2 anddocs/agent-context/invariants.md.make validatepasses.References
bef07c3_coerce_optional_floatinsrc/skdr_eval/reporting.pybde2a73history if useful as a starting point.