tsagentkit 1.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. tsagentkit/__init__.py +126 -0
  2. tsagentkit/anomaly/__init__.py +130 -0
  3. tsagentkit/backtest/__init__.py +48 -0
  4. tsagentkit/backtest/engine.py +788 -0
  5. tsagentkit/backtest/metrics.py +244 -0
  6. tsagentkit/backtest/report.py +342 -0
  7. tsagentkit/calibration/__init__.py +136 -0
  8. tsagentkit/contracts/__init__.py +133 -0
  9. tsagentkit/contracts/errors.py +275 -0
  10. tsagentkit/contracts/results.py +418 -0
  11. tsagentkit/contracts/schema.py +44 -0
  12. tsagentkit/contracts/task_spec.py +300 -0
  13. tsagentkit/covariates/__init__.py +340 -0
  14. tsagentkit/eval/__init__.py +285 -0
  15. tsagentkit/features/__init__.py +20 -0
  16. tsagentkit/features/covariates.py +328 -0
  17. tsagentkit/features/extra/__init__.py +5 -0
  18. tsagentkit/features/extra/native.py +179 -0
  19. tsagentkit/features/factory.py +187 -0
  20. tsagentkit/features/matrix.py +159 -0
  21. tsagentkit/features/tsfeatures_adapter.py +115 -0
  22. tsagentkit/features/versioning.py +203 -0
  23. tsagentkit/hierarchy/__init__.py +39 -0
  24. tsagentkit/hierarchy/aggregation.py +62 -0
  25. tsagentkit/hierarchy/evaluator.py +400 -0
  26. tsagentkit/hierarchy/reconciliation.py +232 -0
  27. tsagentkit/hierarchy/structure.py +453 -0
  28. tsagentkit/models/__init__.py +182 -0
  29. tsagentkit/models/adapters/__init__.py +83 -0
  30. tsagentkit/models/adapters/base.py +321 -0
  31. tsagentkit/models/adapters/chronos.py +387 -0
  32. tsagentkit/models/adapters/moirai.py +256 -0
  33. tsagentkit/models/adapters/registry.py +171 -0
  34. tsagentkit/models/adapters/timesfm.py +440 -0
  35. tsagentkit/models/baselines.py +207 -0
  36. tsagentkit/models/sktime.py +307 -0
  37. tsagentkit/monitoring/__init__.py +51 -0
  38. tsagentkit/monitoring/alerts.py +302 -0
  39. tsagentkit/monitoring/coverage.py +203 -0
  40. tsagentkit/monitoring/drift.py +330 -0
  41. tsagentkit/monitoring/report.py +214 -0
  42. tsagentkit/monitoring/stability.py +275 -0
  43. tsagentkit/monitoring/triggers.py +423 -0
  44. tsagentkit/qa/__init__.py +347 -0
  45. tsagentkit/router/__init__.py +37 -0
  46. tsagentkit/router/bucketing.py +489 -0
  47. tsagentkit/router/fallback.py +132 -0
  48. tsagentkit/router/plan.py +23 -0
  49. tsagentkit/router/router.py +271 -0
  50. tsagentkit/series/__init__.py +26 -0
  51. tsagentkit/series/alignment.py +206 -0
  52. tsagentkit/series/dataset.py +449 -0
  53. tsagentkit/series/sparsity.py +261 -0
  54. tsagentkit/series/validation.py +393 -0
  55. tsagentkit/serving/__init__.py +39 -0
  56. tsagentkit/serving/orchestration.py +943 -0
  57. tsagentkit/serving/packaging.py +73 -0
  58. tsagentkit/serving/provenance.py +317 -0
  59. tsagentkit/serving/tsfm_cache.py +214 -0
  60. tsagentkit/skill/README.md +135 -0
  61. tsagentkit/skill/__init__.py +8 -0
  62. tsagentkit/skill/recipes.md +429 -0
  63. tsagentkit/skill/tool_map.md +21 -0
  64. tsagentkit/time/__init__.py +134 -0
  65. tsagentkit/utils/__init__.py +20 -0
  66. tsagentkit/utils/quantiles.py +83 -0
  67. tsagentkit/utils/signature.py +47 -0
  68. tsagentkit/utils/temporal.py +41 -0
  69. tsagentkit-1.0.2.dist-info/METADATA +371 -0
  70. tsagentkit-1.0.2.dist-info/RECORD +72 -0
  71. tsagentkit-1.0.2.dist-info/WHEEL +4 -0
  72. tsagentkit-1.0.2.dist-info/licenses/LICENSE +201 -0
tsagentkit/__init__.py ADDED
@@ -0,0 +1,126 @@
1
+ """tsagentkit - Robust execution engine for time-series forecasting agents.
2
+
3
+ This library provides a strict, production-grade workflow skeleton for
4
+ external coding agents (LLMs/AI agents) performing time-series forecasting tasks.
5
+
6
+ Basic usage:
7
+ >>> from tsagentkit import TaskSpec, validate_contract, run_forecast
8
+ >>> spec = TaskSpec(h=7, freq="D")
9
+ >>> result = run_forecast(data, spec)
10
+ """
11
+
12
+ __version__ = "1.0.2"
13
+
14
+ # Import commonly used items from contracts
15
+ from tsagentkit.anomaly import detect_anomalies
16
+ from tsagentkit.backtest import (
17
+ BacktestReport,
18
+ mase,
19
+ rolling_backtest,
20
+ smape,
21
+ wape,
22
+ )
23
+ from tsagentkit.calibration import apply_calibrator, fit_calibrator
24
+ from tsagentkit.contracts import (
25
+ ECovariateLeakage,
26
+ ESplitRandomForbidden,
27
+ ForecastResult,
28
+ ModelArtifact,
29
+ Provenance,
30
+ RunArtifact,
31
+ TaskSpec,
32
+ # Errors
33
+ TSAgentKitError,
34
+ ValidationReport,
35
+ validate_contract,
36
+ )
37
+ from tsagentkit.eval import MetricFrame, ScoreSummary, evaluate_forecasts
38
+ from tsagentkit.qa import run_qa
39
+ from tsagentkit.router import (
40
+ BucketConfig,
41
+ BucketProfile,
42
+ BucketStatistics,
43
+ # Bucketing (v0.2)
44
+ DataBucketer,
45
+ FallbackLadder,
46
+ PlanSpec,
47
+ SeriesBucket,
48
+ compute_plan_signature,
49
+ execute_with_fallback,
50
+ get_candidate_models,
51
+ make_plan,
52
+ )
53
+ from tsagentkit.series import (
54
+ SparsityClass,
55
+ SparsityProfile,
56
+ TSDataset,
57
+ build_dataset,
58
+ )
59
+ from tsagentkit.serving import MonitoringConfig, run_forecast
60
+
61
+ # Structured logging (v1.0)
62
+ from tsagentkit.serving.provenance import (
63
+ StructuredLogger,
64
+ format_event_json,
65
+ log_event,
66
+ )
67
+
68
+ # v0.2 imports (optional - use directly from submodules)
69
+ # from tsagentkit.features import FeatureFactory, FeatureMatrix, compute_feature_hash
70
+ # from tsagentkit.monitoring import DriftDetector, StabilityMonitor, TriggerEvaluator
71
+
72
+ __all__ = [
73
+ "__version__",
74
+ # Core contracts
75
+ "TaskSpec",
76
+ "ValidationReport",
77
+ "ForecastResult",
78
+ "ModelArtifact",
79
+ "Provenance",
80
+ "RunArtifact",
81
+ "validate_contract",
82
+ # Key errors
83
+ "TSAgentKitError",
84
+ "ESplitRandomForbidden",
85
+ "ECovariateLeakage",
86
+ # Series
87
+ "TSDataset",
88
+ "SparsityProfile",
89
+ "SparsityClass",
90
+ "build_dataset",
91
+ # QA
92
+ "run_qa",
93
+ # Calibration + Anomaly + Eval
94
+ "fit_calibrator",
95
+ "apply_calibrator",
96
+ "detect_anomalies",
97
+ "evaluate_forecasts",
98
+ "MetricFrame",
99
+ "ScoreSummary",
100
+ # Router
101
+ "PlanSpec",
102
+ "compute_plan_signature",
103
+ "get_candidate_models",
104
+ "make_plan",
105
+ "FallbackLadder",
106
+ "execute_with_fallback",
107
+ # Router Bucketing (v0.2)
108
+ "DataBucketer",
109
+ "BucketConfig",
110
+ "BucketProfile",
111
+ "BucketStatistics",
112
+ "SeriesBucket",
113
+ # Backtest
114
+ "BacktestReport",
115
+ "rolling_backtest",
116
+ "wape",
117
+ "smape",
118
+ "mase",
119
+ # Serving
120
+ "run_forecast",
121
+ "MonitoringConfig",
122
+ # Structured Logging (v1.0)
123
+ "log_event",
124
+ "format_event_json",
125
+ "StructuredLogger",
126
+ ]
@@ -0,0 +1,130 @@
1
+ """Anomaly detection based on forecast intervals/quantiles."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import warnings
6
+ from dataclasses import dataclass
7
+ from typing import Any, Literal
8
+
9
+ import numpy as np
10
+ import pandas as pd
11
+
12
+ from tsagentkit.contracts import EAnomalyFail
13
+ from tsagentkit.utils import parse_quantile_column
14
+
15
+ AnomalyMethod = Literal["interval_breach", "conformal", "mad_residual"]
16
+ AnomalyScore = Literal["margin", "normalized_margin", "zscore"]
17
+
18
+
19
+ @dataclass(frozen=True)
20
+ class AnomalyReport:
21
+ """Anomaly detection report."""
22
+
23
+ frame: pd.DataFrame
24
+ method: str
25
+ level: int
26
+ score: str
27
+ summary: dict[str, Any]
28
+
29
+ def to_dict(self) -> dict[str, Any]:
30
+ return {
31
+ "method": self.method,
32
+ "level": self.level,
33
+ "score": self.score,
34
+ "summary": self.summary,
35
+ "frame": self.frame.to_dict("records"),
36
+ }
37
+
38
+
39
+ def detect_anomalies(
40
+ forecast_with_y: pd.DataFrame,
41
+ method: AnomalyMethod = "interval_breach",
42
+ level: int = 99,
43
+ score: AnomalyScore = "normalized_margin",
44
+ calibrator: Any | None = None,
45
+ strict: bool = False,
46
+ id_col: str = "unique_id",
47
+ ds_col: str = "ds",
48
+ actual_col: str = "y",
49
+ pred_col: str = "yhat",
50
+ ) -> AnomalyReport:
51
+ """Detect anomalies using forecast uncertainty."""
52
+ if forecast_with_y.empty:
53
+ raise EAnomalyFail("Forecast frame is empty.")
54
+
55
+ if strict and calibrator is None:
56
+ raise EAnomalyFail(
57
+ "Strict mode requires calibrated intervals/quantiles for anomaly detection."
58
+ )
59
+ if not strict and calibrator is None and method != "interval_breach":
60
+ warnings.warn(
61
+ "Anomaly detection requested without calibrated intervals/quantiles. "
62
+ "Proceeding may increase false positives.",
63
+ UserWarning,
64
+ stacklevel=2,
65
+ )
66
+
67
+ df = forecast_with_y.copy()
68
+
69
+ lo_col = f"yhat_lo_{level}"
70
+ hi_col = f"yhat_hi_{level}"
71
+ if lo_col not in df.columns or hi_col not in df.columns:
72
+ # Try to infer from quantiles
73
+ lower_q = (1 - level / 100.0) / 2
74
+ upper_q = 1 - lower_q
75
+ q_cols = {parse_quantile_column(c): c for c in df.columns}
76
+ lo_col = q_cols.get(lower_q)
77
+ hi_col = q_cols.get(upper_q)
78
+ if lo_col is None or hi_col is None:
79
+ raise EAnomalyFail(
80
+ "No interval/quantile columns available for anomaly detection.",
81
+ context={"expected_level": level},
82
+ )
83
+
84
+ y = df[actual_col].to_numpy(dtype=float)
85
+ yhat = df[pred_col].to_numpy(dtype=float) if pred_col in df.columns else None
86
+ lo = df[lo_col].to_numpy(dtype=float)
87
+ hi = df[hi_col].to_numpy(dtype=float)
88
+
89
+ margin = np.zeros_like(y, dtype=float)
90
+ margin = np.where(y < lo, lo - y, margin)
91
+ margin = np.where(y > hi, y - hi, margin)
92
+
93
+ if score == "margin":
94
+ anomaly_score = margin
95
+ elif score == "normalized_margin":
96
+ width = np.maximum(hi - lo, 1e-8)
97
+ anomaly_score = margin / width
98
+ else: # zscore
99
+ if yhat is None:
100
+ raise EAnomalyFail("yhat is required for zscore anomaly score.")
101
+ width = np.maximum(hi - lo, 1e-8)
102
+ anomaly_score = np.abs(y - yhat) / (0.5 * width)
103
+
104
+ anomaly_flag = margin > 0
105
+
106
+ frame = df[[id_col, ds_col, actual_col, pred_col]].copy()
107
+ frame["lo"] = lo
108
+ frame["hi"] = hi
109
+ frame["anomaly"] = anomaly_flag
110
+ frame["anomaly_score"] = anomaly_score
111
+ frame["threshold"] = level
112
+ frame["method"] = method
113
+ frame["score"] = score
114
+
115
+ summary = {
116
+ "total": int(len(frame)),
117
+ "anomalies": int(np.sum(anomaly_flag)),
118
+ "anomaly_rate": float(np.mean(anomaly_flag)) if len(frame) > 0 else 0.0,
119
+ }
120
+
121
+ return AnomalyReport(
122
+ frame=frame,
123
+ method=method,
124
+ level=level,
125
+ score=score,
126
+ summary=summary,
127
+ )
128
+
129
+
130
+ __all__ = ["AnomalyReport", "detect_anomalies"]
@@ -0,0 +1,48 @@
1
+ """Backtest module for tsagentkit.
2
+
3
+ Provides rolling window backtesting and report structures. Legacy metric
4
+ helpers are still exported for compatibility but are deprecated; prefer
5
+ ``tsagentkit.eval.evaluate_forecasts`` for new evaluation flows.
6
+ """
7
+
8
+ from .engine import cross_validation_split, rolling_backtest
9
+ from .metrics import (
10
+ compute_all_metrics,
11
+ compute_metrics_by_series,
12
+ mae,
13
+ mase,
14
+ pinball_loss,
15
+ rmse,
16
+ smape,
17
+ wape,
18
+ wql,
19
+ )
20
+ from .report import (
21
+ BacktestReport,
22
+ SegmentMetrics,
23
+ SeriesMetrics,
24
+ TemporalMetrics,
25
+ WindowResult,
26
+ )
27
+
28
+ __all__ = [
29
+ # Engine
30
+ "rolling_backtest",
31
+ "cross_validation_split",
32
+ # Report
33
+ "BacktestReport",
34
+ "WindowResult",
35
+ "SeriesMetrics",
36
+ "SegmentMetrics",
37
+ "TemporalMetrics",
38
+ # Metrics
39
+ "wape",
40
+ "smape",
41
+ "mase",
42
+ "mae",
43
+ "rmse",
44
+ "pinball_loss",
45
+ "wql",
46
+ "compute_all_metrics",
47
+ "compute_metrics_by_series",
48
+ ]