Snake's domain extension gains a third family — and the routing rule that makes it fire on the data that broke v5.5.0.
The problem that demanded it
Point Snake at a number-theory regression — predict Ω(n), the count of prime factors of n with multiplicity, from cheap surface features (digit stats, small-modulus residues n mod 2, 3, 5, …) — and v5.5.0 domain extension hurt: the expansion delta went negative. The signal is real (Erdős–Kac) but it lives almost entirely in divisibility — n mod 2 == 0 already guarantees a factor of 2. A residue's meaning is membership, not magnitude, so routing it through GAUSSIAN's position KPIs (z / density / cdf) was pure noise that crowded the raw signal.
What's new
CATEGORICAL family (pure Python, zero dependencies):
- Cardinality is the router. A column (numeric or text) with
2 ≤ n_unique ≤ 100is categorical, not continuous. It one-hots each recurrent value into acol==v0/1 column — an exact-match bit Snake reads with a single literal, no stochastic threshold to miss. - Flood-filter parity with TF-IDF. A value must recur (
count ≥ min_df, the same floor TOKENSET uses) to be a candidate — one-hotting singletons would memorize row IDs. Flood every recurrent value, then Shannon MI keeps the global top-10 across all sources. - Names surface verbatim in the audit —
mod2==0,status==refurb.
GAUSSIAN normality gate. GAUSSIAN now only fires on a high-card numeric whose empirical CDF agrees with the fitted normal to ≥ 0.95 (a KS-style score; N(0,1)→0.985, U(0,1)→0.940). Non-normal columns get no position KPIs instead of injecting noise.
Global per-family MI cut. The MI gate pools all candidates of a family across every source and keeps the global top-10, instead of top-K per source — fixing a multi-source dilution regression.
MI binning fix. Low-cardinality numerics (incl. one-hot 0/1 columns) now bin by exact value when they recur. The old quantile cut collapsed a 2-value column into one bin and reported MI=0 — silently starving every low-card numeric.
The result
On the prime-factor benchmark (benchmark_categorical.py, 4000 train / 1000 test, 15 layers, seed 42, identical features to RF/GB):
| model | R² | MAE |
|---|---|---|
| baseline (predict mean) | −0.003 | — |
Snake expand=off |
0.345 | 1.113 |
Snake expand="auto" |
0.388 | 1.074 |
| RandomForest | 0.488 | 1.015 |
| GradientBoosting | 0.530 | 0.967 |
Expansion delta flips −0.006 → +0.043 R²: CATEGORICAL turns the family that hurt into one that helps, on the exact problem that exposed the gap. The lesson generalizes past math: expansion helps when the family matches the column's structure (membership) rather than its statistics (position).
Compatibility first — the bar held seamlessly
- v5.4.8 models load intact (
expansions=[], perfect-fit preserved — verified against genuine v5.4.8-trained JSON) - v5.5.1
to_json()round-trips to an identical object (0 prediction mismatches over 250 rows, expansions identical) - 10 top-level
to_jsonkeys unchanged;expand=Falsebyte-exact v5.4.8 - 346 tests (+10 CATEGORICAL), zero regressions