2026-04-26
Session 45: Phase 5d: strip legacy.setup() from _native_setup_timeseries
Engineering log for session 45.
The TS drain is complete. _native_setup_timeseries no longer calls legacy.setup() at all. Together with the verb drains in s40-s44, every default TS workflow runs without ever touching the legacy directory. Phase 6 (deletion of pycaret/internal/pycaret_experiment/) is now unblocked.
ADDED — engine#
ADDED—_TSContextProxyinpackages/engine/pycaret/core/experiment.py.__slots__-bound class with the 14 attrs that TS containers / util helpers read offexperiment.<x>:seed,gpu_param,n_jobs_param,seasonality_present,primary_sp_to_use,strictly_positive,seasonality_type,all_sps_to_use,X_train,is_multiclass,enforce_pi,enforce_exogenous,exogenous_present,fe_target_rr,index_type.fe_target_rrdefaults toNoneto match legacy default —[]would break the cds_dt recursive forecasters viatruncate_startvalidation.ADDED—Experiment._auto_detect_seasonality(y)static method. Lightweight port of legacy seasonality detection: derive candidate sp fromy.index.freqstrviapycaret.utils.time_series.get_sp_from_str, run sktime'sautocorrelation_seasonality_teston each, return(seasonality_present, primary_sp_to_use, all_sps_to_use)whereprimaryis the largest significant sp.ADDED—Experiment._build_ts_fold_generator(...). Same math legacy uses:step_length = len(fh),initial_window = len(y_train) - ((fold - 1) * step_length + max(fh)), with defensive shrink ton_train // 2when folds wouldn't fit. ReturnsExpandingWindowSplitter(default) orSlidingWindowSplitterbased onfold_strategy.
CHANGED — engine#
CHANGED—Experiment._native_setup_timeseriesrewritten end-to-end. Nolegacy.setup()call. Coerces input → univariate Series + optional exogenous DataFrame, buildsForecastingHorizonfromself.fh, auto-detects seasonality, splits viatemporal_train_test_split, builds fold generator + model registry through the proxy, and constructs a minimalForecastingPipeline(placeholderNaiveForecaster) aspreprocess_pipeline.CHANGED—Experiment.models()now filters bymodel_type ∈ TSModelTypesdirectly on the snapshot registry for TS, matching legacy's filter that excludesensemble_forecaster. No morelegacy.models()deferral for TS.CHANGED—TimeSeriesExperiment.predict_modeldetects fitted bare forecasters (viais_fittedattr or presence of_y) and uses them directly instead of wrapping in the placeholder pipeline (which is unfit and would invalidate the forecaster's state).
ADDED — tests#
ADDED—packages/engine/tests/test_session45_ts_native_setup_full_drain.py— 9 new tests:- Drain-lock for
legacy.setup(poison + verify native succeeds with_native_setup_used is True). - Seasonality auto-detection on monthly airline →
seasonality_present=True,sp=12,seasonality_type ∈ {mul, add},strictly_positive=True. - No-frequency fallback →
(False, 1, [1]). ExpandingWindowSplitterdefault withn_splits == self.fold.SlidingWindowSplitterwhenfold_strategy='sliding'.- Full sktime model registry populates through the proxy (naive / arima / ets / theta / etc. all present).
- End-to-end
create + tune + finalize + predictchain with all 6 legacy verbs poisoned includingsetup. setup_kwargsstill routes to legacy (compatibility preserved).- Univariate Series input accepted directly.
- Drain-lock for
INTERNAL#
INTERNAL— Why a proxy instead of a real legacy instance. The proxy is a 30-line__slots__class. A real legacy instance pulls in 10K LoC and runs Fourier analysis + a dozen other pre-flight checks before even getting to model construction. The proxy is fast (<1ms to instantiate) and has zero dependency on the directory we're about to delete in phase 6.INTERNAL—fe_target_rr=Nonevs[]. Legacy initializesfe_target_rrtoNone(unset) by default. Containers either pass it through unchanged (cds_dt recursive forecasters expectNoneso they use their internal default), or replace it. Passing[]instead causes the cds_dt models to try to apply zero feature engineering steps and trips sktime'sReduce.fitvalidation: "Reduce must either have window length as argument or needs to have it passed by transformer via truncate_start". One-character fix; significant test recovery.INTERNAL— WhyExperiment.models()no longer defers to legacy for TS. With phase 5d,_fit_state["model_registry"]is fully populated through the proxy — it has the same_all_models_internalshape as legacy. The legacy filter (model_type in TSModelTypes) is applied directly on the snapshot. No reason to calllegacy.models()anymore, and doing so would crash anyway becauselegacy.setup()was never called → nolegacy.seed.INTERNAL— Why bare-forecaster bypass inpredict_model. The placeholderForecastingPipelinewe create in_native_setup_timeseriesis unfit (it's a template). When the user passes an already-fitted bare forecaster topredict_model, wrapping it via_add_model_to_pipelineproduces an unfit pipeline (the wrapper fits all steps top-down), sopipeline.predict(fh)raisesNotFittedError. The fix: detect that the bare forecaster is already fitted (viais_fittedor_y) and use it directly.
Session 45 delta summary#
| Metric | Session 44 end | Session 45 end |
|---|---|---|
| TS drain status | All 5 verbs native; setup still legacy | Fully drained |
legacy.<anything> callsites for default TS | 1 (legacy.setup) | 0 |
| Engine tests (fast + slow) | 235 | 244 |