v0.6.3¶
Patch release closing out defect #3 from the 2026-07-14 review — the
equal_freq WOE-plot silent failure that 0.6.2 only half-fixed.
Why 0.6.2 wasn't the fix¶
0.6.2 addressed defect #3 by:
- making
WOE_Master.plot_bivar_graph(..., group=None, ...)optional, - explicitly passing
group=Noneat the pipeline's ungrouped call site, and - replacing
_plot_woe's bareexcept Exception: returnwith alogger.warning(...).
That fix rested on an incorrect assumption: "the underlying
WOE_Plot_Tool.get_bivar_graph primitive has always accepted group=None."
The signature default was there, but the function body's if group: guard
sat after the crash point:
# 0.6.2 and earlier — Modeling_Tool/WOE/WOE_Plot_Tool.py :: get_bivar_graph
# Reference WOE Table Plot — this loop runs FIRST, writes PNGs successfully
for var in varlist:
plot_woe(..., fig_name=f"{var}.png") # ①
# Group WOE Table Plot — runs UNCONDITIONALLY, even when group is None
grp_woe_res = get_mapped_woe_summary(..., grp_name=[group]) # ② <- crash here
grp_woe_res = align_bin_num(..., grp_name=[group]) # ③
if group: # ④ <- guard too late
for var in varlist:
plot_woe_group(..., fig_name=f"{var}_{group}.png")
When group=None, ② invokes data.groupby([None]) internally, raising
TypeError: 'NoneType' object is not callable. The if group: guard at ④
never gets a chance to skip that crash.
Net effect on 0.6.2 in the field¶
| engine | base PNGs | by_<group>/*.png |
plot exception |
|---|---|---|---|
| monotone | 6 | 3 | none |
| equal_freq | 3 (from ① before crash) | 0 | TypeError every run |
Base PNGs still appeared for equal_freq because loop ① completed before
loop ② crashed. The 0.6.2 logger.warning then caught the TypeError, and
the pipeline's subsequent for group in cfg.woe_plot_groups: loop that
would emit by_<group>/*.png never ran — so grouped plots were still
missing.
The fix¶
Hoist if group: in WOE_Plot_Tool.get_bivar_graph so the guard wraps the
whole summary + align + plot triplet — the summary/align never run for the
ungrouped case, and there is no groupby([None]) to crash on:
# 0.6.3
if group:
grp_woe_res = get_mapped_woe_summary(..., grp_name=[group])
grp_woe_res = align_bin_num(..., grp_name=[group])
for var in varlist:
plot_woe_group(..., fig_name=f"{var}_{group}.png")
Also fixed the WOE_Master.plot_bivar_graph docstring that claimed
get_bivar_graph "has always accepted group=None" — the source of the
0.6.2 wrong-assumption chain.
Verification¶
Coordinated regression coverage in SuperModelingFactory_pytest:
- TestN46b — pipeline base plots emit no
WOE plot generation failedwarning (previous D5 only counted PNGs, which is why the 0.6.2 half-fix slipped through — see "Durable lesson" below). - TestN48 —
equal_freqengine +woe_plot_groups=['chan']producesfigs/woe/<target>/by_chan/*.png > 0. - TestN49 — direct call
get_bivar_graph(..., group=None)does not raise (white-box guard against reintroducing an unconditionalgroupby([None])).
All three fail on 0.6.2, pass on 0.6.3. Full suite on Py3.14 editable: 665 passed / 0 skipped / 0 failed.
Durable lesson¶
Two mistakes, one shared root cause: checking a signature without reading the function body.
- In 0.6.1 the diagnosis was "the underlying primitive already supports
group=None" — based only on the default in the signature. It didn't. - In 0.6.2 the fix acted on that same wrong assumption and stopped at the caller's ungrouped call.
Neither round would have shipped if we'd read get_bivar_graph's body to
where the guard actually lived.
Second lesson, on test design: asserting "output exists" is not the same
as asserting "no error occurred." Loop ① wrote 3 PNGs, so a "produced ≥ 1
figure" assertion passed even while every run also raised. Any test path
where the failure would be caught by a surrounding try/except must
assert both the presence of the desired artifact and the absence of
warnings/exceptions from the operation.
The 0.6.2 logger.warning — the visibility half of the 0.6.2 fix — is what
finally made the residual crash observable on the first 0.6.3 pipeline run.
That was worth shipping on its own.