2026-04-25
Session 39: Native setup() phase 5a: time-series soft drain
Engineering log for session 39.
The TS experiment finally adopts the native dispatcher. _can_use_native_setup accepts TaskType.TIME_SERIES; _native_setup_timeseries populates _fit_state with TS-shape slots so user-facing accessors work for time-series exactly like they do for the other tasks. This is a soft drain: the native path still calls legacy.setup() underneath because the TS verbs (create_model, predict_model, ...) read from legacy state and haven't been drained yet.
ADDED — engine#
ADDED—Experiment._native_setup_timeseries(data, setup_kwargs)inpackages/engine/pycaret/core/experiment.py. Callslegacy.setup(transitionally — phase 5b removes this) to build the sktime model registry, fold generator, and y_train / y_test, then snapshots them into_fit_statealong with TS-specific slots (fh,seasonal_period).ADDED—Experiment._predict_model_legacy(estimator, *, data, verbose). Time-series fallback forpredict_model— the drained sklearn path doesn't apply because sktime'sForecastingPipelinehas no.transform()and forecasters use.predict(fh=...). Phase 5b will drain this.
CHANGED — engine#
CHANGED—_can_use_native_setuppredicate acceptsTaskType.TIME_SERIES. The only "still legacy" branch left is caller-suppliedsetup_kwargs.CHANGED—TimeSeriesExperiment.fit()inpackages/engine/pycaret/tasks/time_series.pymirrors the base dispatcher: native path →_native_setup_timeseries; legacy path →legacy.setup()+_snapshot_fit_state(). Both branches end with_fit_statepopulated; previously the legacy-only path didn't populate_fit_stateat all andexp.y_trainraisedKeyErrorfor TS.CHANGED—Experiment.predict_model()detectstask == TIME_SERIESearly and dispatches to_predict_model_legacy. Without this guard the drained path would callForecastingPipeline.transform(X)and crash withAttributeError.CHANGED—Experiment.models()defers tolegacy.models()for TS. The TS legacy registry has filter rules (model_type∈TSModelTypes) that excludeensemble_forecaster(which requires runtime-built forecasters). Re-implementing those rules in the snapshot path would duplicate logic that lives intime_series/forecasting/oop.py; phase 5b moves both at once.
ADDED — tests#
ADDED—packages/engine/tests/test_session39_native_setup_phase5.py— 9 new tests:- Predicate accepts
TIME_SERIES;setup_kwargsstill forces legacy. - Native path populates
_fit_statewith TS-shape slots (y / y_train / y_test / fh / fold_generator / preprocess_pipeline / model_registry). - User-facing accessors (
exp.y_train,exp.y_test,exp.preprocess_pipeline) work for TS. fold_generatoris sktime'sExpandingWindowSplitter.setup_kwargspath also populates_fit_statevia the snapshot helper.create_model('naive')returns a fitted forecaster.predict_model(forecaster)returns a 12-step forecast matchingfh.models(internal=True)filters outensemble_forecastervia legacy delegation.
- Predicate accepts
CHANGED — existing tests#
CHANGED—test_session35_native_setup::test_can_use_native_setup_predicate— TS assertion flipped fromis Falsetois True. Docstring updated to reflect "Phase 5a — TS adopts the dispatcher (soft drain)".CHANGED—test_session38_native_setup_phase4::test_time_series_still_falls_back_to_legacyrenamed totest_time_series_now_routes_through_native_dispatcher_phase5a. Inverted assertion. Same rename pattern astest_session35_native_setup::test_unsupervised_uses_legacy_setup → test_unsupervised_now_runs_natively_phase4from s38.
INTERNAL#
INTERNAL— Why a soft drain. The supervised + unsupervised drains worked one-shot because their verbs had already been drained (s24 / s28) to read from_fit_state. For TS, none of the verbs have been drained — they all delegate toself._legacy.<verb>. If_native_setup_timeseriesskippedlegacy.setup(), every verb would break. So phase 5a brings TS into the dispatcher and gives us accessor parity, but the actual skip oflegacy.setup()waits for phase 5b/c when verbs migrate.INTERNAL— TS-specific slots in_fit_state.fh(sktime ForecastingHorizon) andseasonal_period(int or None) live alongside the standard slots. Other tasks set them to None. This keeps the dict shape stable across all five task types and lets future verb-drain code branch onself._fit_state["fh"] is not Nonerather than checkingself.task.INTERNAL— Whymodels()defers, not_fit_state["model_registry"]. The snapshot already has the full registry (26 entries includingensemble_forecaster). The legacymodels()filters bymodel_type in TSModelTypesandensemble_forecasterhasmodel_type="ensemble"(not aTSModelTypesmember). Reimplementing that filter on the snapshot is straightforward but would couplecore/experiment.pytoTSModelTypes. Better to keep the deferral until phase 5b where TS verbs and TS-specific filtering both move together.
Session 39 delta summary#
| Metric | Session 38 end | Session 39 end |
|---|---|---|
| Tasks adopting the native dispatcher | clf + reg + clustering + anomaly | + time-series |
legacy.setup() callsites still live | time-series, setup_kwargs | setup_kwargs (transitional: TS native path also still calls it) |
| Engine tests (fast + slow) | 182 | 191 |