diff --git a/src/sktime_mcp/runtime/executor.py b/src/sktime_mcp/runtime/executor.py index 0dc2673f..6708445f 100644 --- a/src/sktime_mcp/runtime/executor.py +++ b/src/sktime_mcp/runtime/executor.py @@ -42,6 +42,22 @@ def _discover_demo_datasets() -> dict: DEMO_DATASETS = _discover_demo_datasets() +def _get_index_frequency_metadata( + index: pd.Index, + fallback: str | None = None, +) -> str | None: + """Return a stable frequency label for metadata without assuming datetime-only indexes.""" + if isinstance(index, (pd.DatetimeIndex, pd.PeriodIndex)): + freq = getattr(index, "freq", None) + if freq is not None: + return str(freq) + inferred = pd.infer_freq(index) + if inferred is not None: + return inferred + + return fallback + + class Executor: """ Execution runtime for sktime estimators. @@ -772,6 +788,7 @@ def format_data_handle( "missing_filled": 0, "gaps_filled": 0, } + original_frequency = data_info["metadata"].get("frequency") # 1. Remove duplicates if remove_duplicates and y.index.duplicated().any(): @@ -788,9 +805,9 @@ def format_data_handle( # 3. Infer and set frequency if auto_infer_freq: - freq = y.index.freq + freq = getattr(y.index, "freq", None) - if freq is None: + if freq is None and isinstance(y.index, (pd.DatetimeIndex, pd.PeriodIndex)): # Try to infer freq = pd.infer_freq(y.index) @@ -853,7 +870,10 @@ def format_data_handle( "metadata": { **data_info["metadata"], "formatted": True, - "frequency": str(y.index.freq) if y.index.freq else changes_made.get("frequency"), + "frequency": _get_index_frequency_metadata( + y.index, + fallback=changes_made.get("frequency") or original_frequency, + ), "rows": len(y), "start_date": str(y.index.min()), "end_date": str(y.index.max()), diff --git a/tests/test_data_sources.py b/tests/test_data_sources.py index 757dd53c..a62e4685 100644 --- a/tests/test_data_sources.py +++ b/tests/test_data_sources.py @@ -68,6 +68,25 @@ def test_load_validate_convert(self): class TestExecutorDataIntegration: """Executor can load data sources and run fit_predict via data handles.""" + def test_load_data_source_auto_format_supports_rangeindex(self, caplog): + """Integer-indexed data should not trip datetime-only auto-format logic.""" + executor = get_executor() + + config = { + "type": "pandas", + "data": { + "y": [1, 2, 3, 4], + }, + } + + with caplog.at_level("WARNING"): + result = executor.load_data_source(config) + + assert result["success"] + assert result["formatted"] is True + assert result["metadata"]["frequency"] == "Integer" + assert "Auto-formatting failed" not in caplog.text + def test_load_and_predict_with_data_handle(self): executor = get_executor()