SMF 0.4.1 — LOW-severity defensive hardening¶
Patch release. Three defensive fixes, no interface changes, no behavior changes on healthy inputs. All three make previously-silent failure modes loud, so callers hear about them instead of shipping bad numbers.
Fixes¶
L1 — RejectInferencePipeline._default_hard_cutoff (reject_inference.py)¶
Before: When both bad-sample scores and the overall score column were
entirely NaN, _default_hard_cutoff returned float(NaN). That NaN then
propagated into every score < cutoff comparison downstream, producing
a completely empty inferred rejected set with no diagnostic.
After:
- If the fallback median is not finite, raise
ValueErrorwith an actionable message pointing tori_method_params['hard_cutoff']['cutoff']. - If bad samples have no valid scores but the overall median is finite,
emit a
RuntimeWarningand continue with the median as cutoff. - Healthy path (bad samples with valid scores) is unchanged.
L2 — predict_positive shape/length/finiteness validation (_common.py)¶
Before: Model output shapes were partially handled: 2-D (N, 2) took
column 1, everything else was reshape-flattened. Silent failures included
row-count mismatches (model dropped rows), unexpected 2-D shapes with
3+ columns, and all-NaN predictions.
After:
- Validate
ndim ∈ {1, 2}and 2-D column count∈ {1, 2}. RaiseValueErrorotherwise. - Assert output length matches input row count. Raise
ValueErroron mismatch with a message identifying the model class. - Warn (RuntimeWarning) when any prediction is NaN or Inf, reporting the count and model class.
L3 — _ivks_from_binner mixed Interval + NaN groupby (feature_validation.py)¶
Before: The IV/KS-from-binner report path called
groupby("bin", dropna=False) on a mixed Interval + NaN key column.
Under pandas <2.0 the default sort=True on that mixed dtype raises
TypeError: '<' not supported between instances of 'Interval' and 'float'.
Under pandas ≥2.0 it works but relies on implementation detail.
After:
- Cast bins to
objectdtype and replace NaN with the explicit sentinel"__MISSING__"before grouping. - Set
sort=Falseto make the code order-independent. - Behavior on healthy pandas ≥2.0 is numerically identical (same bin
counts, same IV, same KS) — only the sort order of the output rows may
differ, and downstream code re-sorts by
bad_rateanyway.
Non-changes¶
- No public API surface change.
- No new config field.
- No default value change.
- All 0.4.0 regression tests continue to pass (49 old + 10 new = 59 green).
Upgrade¶
No code changes required for existing pipelines. Two new callable behaviors to be aware of:
- If you were relying on
_default_hard_cutoffreturning NaN in degenerate cases (unlikely — nobody was), that now raises. - If your model wrapper drops rows silently inside
predict_proba, you will now see an explicitValueErrorat scoring time instead of a shape-mismatched score column later.
Test coverage¶
test_pipeline_low_0401.py (10 tests, 3 classes):
TestL1DefaultHardCutoffNaNPropagation— 3 tests (all-NaN raises, bad-only-NaN warns, healthy path silent).TestL2PredictPositiveShapeValidation— 5 tests (1-D pass-through, 2-D positive-column extraction, length mismatch raises, unexpected shape raises, NaN predictions warn).TestL3IVKSFromBinnerMixedBinTypes— 2 tests (end-to-end mixed-Interval+NaN via monkeypatched adapter; direct sentinel behavior).