diff --git a/.gitattributes b/.gitattributes
index 998ae5d..c0dd956 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,3 +1,3 @@
-# Git LFS tracking for binary outputs in claude-skill/
-claude-skill/**/*.png filter=lfs diff=lfs merge=lfs -text
-claude-skill/**/*.gif filter=lfs diff=lfs merge=lfs -text
+# Git LFS tracking for binary outputs in timesfm-forecasting skill
+timesfm-forecasting/**/*.png filter=lfs diff=lfs merge=lfs -text
+timesfm-forecasting/**/*.gif filter=lfs diff=lfs merge=lfs -text
diff --git a/AGENTS.md b/AGENTS.md
deleted file mode 100644
index 5b654ce..0000000
--- a/AGENTS.md
+++ /dev/null
@@ -1,762 +0,0 @@
-# TimesFM — AI Agent Skill
-
-> Contributed by Clayton Young / Superior Byte Works, LLC ([@borealBytes](https://github.com/borealBytes))
-> Apache 2.0 — same license as this repository
-
-This file is automatically loaded by AI coding agents (Claude Code / OpenCode / Cursor / Codex) when
-you open this repository. It teaches your agent how to use TimesFM correctly — safe model loading,
-the right API calls, covariate workflows, anomaly detection, and how to avoid the most common bugs.
-
----
-
-## Overview
-
-TimesFM (Time Series Foundation Model) is a pretrained decoder-only foundation model developed by
-Google Research for time-series forecasting. It works **zero-shot** — feed it any univariate time
-series and it returns point forecasts with calibrated quantile prediction intervals, no training
-required.
-
-This skill includes a **mandatory preflight system checker** that verifies RAM, GPU memory, and
-disk space before the model is ever loaded so the agent never crashes the user's machine.
-
-> **Key numbers**: TimesFM 2.5 uses 200M parameters (~800 MB on disk, ~1.5 GB in RAM on CPU,
-> ~1 GB VRAM on GPU). The archived v1/v2 500M-parameter model needs ~32 GB RAM. Always run the
-> system checker first.
-
-## When to Use This Skill
-
-Use this skill when:
-
-- Forecasting **any univariate time series** (sales, demand, sensor, vitals, price, weather)
-- You need **zero-shot forecasting** without training a custom model
-- You want **probabilistic forecasts** with calibrated prediction intervals (quantiles)
-- You have time series of **any length** (the model handles 1–16,384 context points)
-- You need to **batch-forecast** hundreds or thousands of series efficiently
-- You want a **foundation model** approach instead of hand-tuning ARIMA/ETS parameters
-
-Do **not** use this skill when:
-
-- You need classical statistical models with coefficient interpretation → use `statsmodels`
-- You need time series classification or clustering → use `aeon`
-- You need multivariate vector autoregression or Granger causality → use `statsmodels`
-- Your data is tabular (not temporal) → use `scikit-learn`
-
-> **Note on Anomaly Detection**: TimesFM does not have built-in anomaly detection, but you can
-> use the **quantile forecasts as prediction intervals** — values outside the 90% CI (q10–q90)
-> are statistically unusual. See `claude-skill/examples/anomaly-detection/` for a full example.
-
----
-
-## ⚠️ Mandatory Preflight: System Requirements Check
-
-**CRITICAL — ALWAYS run the system checker before loading the model for the first time.**
-
-```bash
-python claude-skill/scripts/check_system.py
-```
-
-This script checks:
-
-1. **Available RAM** — warns if below 4 GB, blocks if below 2 GB
-2. **GPU availability** — detects CUDA/MPS devices and VRAM
-3. **Disk space** — verifies room for the ~800 MB model download
-4. **Python version** — requires 3.10+
-5. **Existing installation** — checks if `timesfm` and `torch` are installed
-
-> **Note:** Model weights are **NOT stored in this repository**. TimesFM weights (~800 MB)
-> download on-demand from HuggingFace on first use and cache in `~/.cache/huggingface/`.
-> The preflight checker ensures sufficient resources before any download begins.
-
-```mermaid
-flowchart TD
- start["🚀 Run check_system.py"] --> ram{"RAM ≥ 4 GB?"}
- ram -->|"Yes"| gpu{"GPU available?"}
- ram -->|"No (2-4 GB)"| warn_ram["⚠️ Warning: tight RAM
CPU-only, small batches"]
- ram -->|"No (< 2 GB)"| block["🛑 BLOCKED
Insufficient memory"]
- warn_ram --> disk
- gpu -->|"CUDA / MPS"| vram{"VRAM ≥ 2 GB?"}
- gpu -->|"CPU only"| cpu_ok["✅ CPU mode
Slower but works"]
- vram -->|"Yes"| gpu_ok["✅ GPU mode
Fast inference"]
- vram -->|"No"| cpu_ok
- gpu_ok --> disk{"Disk ≥ 2 GB free?"}
- cpu_ok --> disk
- disk -->|"Yes"| ready["✅ READY
Safe to load model"]
- disk -->|"No"| block_disk["🛑 BLOCKED
Need space for weights"]
-
- classDef ok fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#14532d
- classDef warn fill:#fef9c3,stroke:#ca8a04,stroke-width:2px,color:#713f12
- classDef block fill:#fee2e2,stroke:#dc2626,stroke-width:2px,color:#7f1d1d
- classDef neutral fill:#f3f4f6,stroke:#6b7280,stroke-width:2px,color:#1f2937
-
- class ready,gpu_ok,cpu_ok ok
- class warn_ram warn
- class block,block_disk block
- class start,ram,gpu,vram,disk neutral
-```
-
-### Hardware Requirements by Model Version
-
-| Model | Parameters | RAM (CPU) | VRAM (GPU) | Disk | Context |
-| ----- | ---------- | --------- | ---------- | ---- | ------- |
-| **TimesFM 2.5** (recommended) | 200M | ≥ 4 GB | ≥ 2 GB | ~800 MB | up to 16,384 |
-| TimesFM 2.0 (archived) | 500M | ≥ 16 GB | ≥ 8 GB | ~2 GB | up to 2,048 |
-| TimesFM 1.0 (archived) | 200M | ≥ 8 GB | ≥ 4 GB | ~800 MB | up to 2,048 |
-
-> **Recommendation**: Always use TimesFM 2.5 unless you have a specific reason to use an
-> older checkpoint. It is smaller, faster, and supports 8× longer context.
-
----
-
-## 🔧 Installation
-
-### Step 1: Verify System (always first)
-
-```bash
-python claude-skill/scripts/check_system.py
-```
-
-### Step 2: Install TimesFM
-
-```bash
-# Using uv (fast)
-uv pip install timesfm[torch]
-
-# Or using pip
-pip install timesfm[torch]
-
-# For JAX/Flax backend (faster on TPU/GPU)
-uv pip install timesfm[flax]
-```
-
-### Step 3: Install PyTorch for Your Hardware
-
-```bash
-# CUDA 12.1 (NVIDIA GPU)
-pip install torch>=2.0.0 --index-url https://download.pytorch.org/whl/cu121
-
-# CPU only
-pip install torch>=2.0.0 --index-url https://download.pytorch.org/whl/cpu
-
-# Apple Silicon (MPS)
-pip install torch>=2.0.0 # MPS support is built-in
-```
-
-### Step 4: Verify Installation
-
-```python
-import timesfm
-import numpy as np
-print(f"TimesFM version: {timesfm.__version__}")
-print("Installation OK")
-```
-
----
-
-## 🎯 Quick Start
-
-### Minimal Example (5 Lines)
-
-```python
-import torch, numpy as np, timesfm
-
-torch.set_float32_matmul_precision("high")
-
-model = timesfm.TimesFM_2p5_200M_torch.from_pretrained(
- "google/timesfm-2.5-200m-pytorch"
-)
-model.compile(timesfm.ForecastConfig(
- max_context=1024, max_horizon=256, normalize_inputs=True,
- use_continuous_quantile_head=True, force_flip_invariance=True,
- infer_is_positive=True, fix_quantile_crossing=True,
-))
-
-point, quantiles = model.forecast(horizon=24, inputs=[
- np.sin(np.linspace(0, 20, 200)), # any 1-D array
-])
-# point.shape == (1, 24) — median forecast
-# quantiles.shape == (1, 24, 10) — 10th–90th percentile bands
-```
-
-### Forecast from CSV
-
-```python
-import pandas as pd, numpy as np
-
-df = pd.read_csv("monthly_sales.csv", parse_dates=["date"], index_col="date")
-
-# Convert each column to a list of arrays
-inputs = [df[col].dropna().values.astype(np.float32) for col in df.columns]
-
-point, quantiles = model.forecast(horizon=12, inputs=inputs)
-
-# Build a results DataFrame
-for i, col in enumerate(df.columns):
- last_date = df[col].dropna().index[-1]
- future_dates = pd.date_range(last_date, periods=13, freq="MS")[1:]
- forecast_df = pd.DataFrame({
- "date": future_dates,
- "forecast": point[i],
- "lower_80": quantiles[i, :, 2], # 20th percentile
- "upper_80": quantiles[i, :, 8], # 80th percentile
- })
- print(f"\n--- {col} ---")
- print(forecast_df.to_string(index=False))
-```
-
-### Forecast with Covariates (XReg)
-
-TimesFM 2.5+ supports exogenous variables through `forecast_with_covariates()`. Requires `timesfm[xreg]`.
-
-```python
-# Requires: pip install timesfm[xreg]
-point, quantiles = model.forecast_with_covariates(
- inputs=inputs,
- dynamic_numerical_covariates={"price": price_arrays},
- dynamic_categorical_covariates={"holiday": holiday_arrays},
- static_categorical_covariates={"region": region_labels},
- xreg_mode="xreg + timesfm", # or "timesfm + xreg"
-)
-```
-
-| Covariate Type | Description | Example |
-| -------------- | ----------- | ------- |
-| `dynamic_numerical` | Time-varying numeric | price, temperature, promotion spend |
-| `dynamic_categorical` | Time-varying categorical | holiday flag, day of week |
-| `static_numerical` | Per-series numeric | store size, account age |
-| `static_categorical` | Per-series categorical | store type, region, product category |
-
-**XReg Modes:**
-- `"xreg + timesfm"` (default): TimesFM forecasts first, then XReg adjusts residuals
-- `"timesfm + xreg"`: XReg fits first, then TimesFM forecasts residuals
-
-> See `claude-skill/examples/covariates-forecasting/` for a complete example with synthetic retail data.
-
-### Anomaly Detection (via Quantile Intervals)
-
-TimesFM does not have built-in anomaly detection, but the **quantile forecasts naturally provide
-prediction intervals** that can detect anomalies:
-
-```python
-point, q = model.forecast(horizon=H, inputs=[values])
-
-# 90% prediction interval
-lower_90 = q[0, :, 1] # 10th percentile
-upper_90 = q[0, :, 9] # 90th percentile
-
-# Detect anomalies: values outside the 90% CI
-actual = test_values # your holdout data
-anomalies = (actual < lower_90) | (actual > upper_90)
-
-# Severity levels
-is_warning = (actual < q[0, :, 2]) | (actual > q[0, :, 8]) # outside 80% CI
-is_critical = anomalies # outside 90% CI
-```
-
-| Severity | Condition | Interpretation |
-| -------- | --------- | -------------- |
-| **Normal** | Inside 80% CI | Expected behavior |
-| **Warning** | Outside 80% CI | Unusual but possible |
-| **Critical** | Outside 90% CI | Statistically rare (< 10% probability) |
-
-> See `claude-skill/examples/anomaly-detection/` for a complete example with visualization.
-
----
-
-## 📊 Understanding the Output
-
-### Quantile Forecast Structure
-
-TimesFM returns `(point_forecast, quantile_forecast)`:
-
-- **`point_forecast`**: shape `(batch, horizon)` — the median (0.5 quantile)
-- **`quantile_forecast`**: shape `(batch, horizon, 10)` — ten slices:
-
-| Index | Quantile | Use |
-| ----- | -------- | --- |
-| 0 | Mean | Average prediction |
-| 1 | 0.1 | Lower bound of 80% PI |
-| 2 | 0.2 | Lower bound of 60% PI |
-| 3 | 0.3 | — |
-| 4 | 0.4 | — |
-| **5** | **0.5** | **Median (= `point_forecast`)** |
-| 6 | 0.6 | — |
-| 7 | 0.7 | — |
-| 8 | 0.8 | Upper bound of 60% PI |
-| 9 | 0.9 | Upper bound of 80% PI |
-
-### Extracting Prediction Intervals
-
-```python
-point, q = model.forecast(horizon=H, inputs=data)
-
-# 80% prediction interval (most common)
-lower_80 = q[:, :, 1] # 10th percentile
-upper_80 = q[:, :, 9] # 90th percentile
-
-# 60% prediction interval (tighter)
-lower_60 = q[:, :, 2] # 20th percentile
-upper_60 = q[:, :, 8] # 80th percentile
-
-# Median (same as point forecast)
-median = q[:, :, 5]
-```
-
-```mermaid
-flowchart LR
- input["📈 Input Series
1-D array"] --> model["🤖 TimesFM
compile + forecast"]
- model --> point["📍 Point Forecast
(batch, horizon)"]
- model --> quant["📊 Quantile Forecast
(batch, horizon, 10)"]
- quant --> pi80["80% PI
q[:,:,1] – q[:,:,9]"]
- quant --> pi60["60% PI
q[:,:,2] – q[:,:,8]"]
- quant --> median["Median
q[:,:,5]"]
-
- classDef data fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#1e3a5f
- classDef model fill:#f3e8ff,stroke:#9333ea,stroke-width:2px,color:#581c87
- classDef output fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#14532d
-
- class input data
- class model model
- class point,quant,pi80,pi60,median output
-```
-
----
-
-## 🔧 ForecastConfig Reference
-
-All forecasting behavior is controlled by `timesfm.ForecastConfig`:
-
-```python
-timesfm.ForecastConfig(
- max_context=1024, # Max context window (truncates longer series)
- max_horizon=256, # Max forecast horizon
- normalize_inputs=True, # Normalize inputs (RECOMMENDED for stability)
- per_core_batch_size=32, # Batch size per device (tune for memory)
- use_continuous_quantile_head=True, # Better quantile accuracy for long horizons
- force_flip_invariance=True, # Ensures f(-x) = -f(x) (mathematical consistency)
- infer_is_positive=True, # Clamp forecasts ≥ 0 when all inputs > 0
- fix_quantile_crossing=True, # Ensure q10 ≤ q20 ≤ ... ≤ q90
- return_backcast=False, # Return backcast (for covariate workflows)
-)
-```
-
-| Parameter | Default | When to Change |
-| --------- | ------- | -------------- |
-| `max_context` | 0 | Set to match your longest historical window (e.g., 512, 1024, 4096) |
-| `max_horizon` | 0 | Set to your maximum forecast length |
-| `normalize_inputs` | False | **Always set True** — prevents scale-dependent instability |
-| `per_core_batch_size` | 1 | Increase for throughput; decrease if OOM |
-| `use_continuous_quantile_head` | False | **Set True** for calibrated prediction intervals |
-| `force_flip_invariance` | True | Keep True unless profiling shows it hurts |
-| `infer_is_positive` | True | Set False for series that can be negative (temperature, returns) |
-| `fix_quantile_crossing` | False | **Set True** to guarantee monotonic quantiles |
-
----
-
-## 📋 Common Workflows
-
-### Workflow 1: Single Series Forecast
-
-```mermaid
-flowchart TD
- check["1. Run check_system.py"] --> load["2. Load model
from_pretrained()"]
- load --> compile["3. Compile with ForecastConfig"]
- compile --> prep["4. Prepare data
pd.read_csv → np.array"]
- prep --> forecast["5. model.forecast()
horizon=N"]
- forecast --> extract["6. Extract point + PI"]
- extract --> plot["7. Plot or export results"]
-
- classDef step fill:#f3f4f6,stroke:#6b7280,stroke-width:2px,color:#1f2937
- class check,load,compile,prep,forecast,extract,plot step
-```
-
-```python
-import torch, numpy as np, pandas as pd, timesfm
-
-# 1. System check (run once)
-# python claude-skill/scripts/check_system.py
-
-# 2-3. Load and compile
-torch.set_float32_matmul_precision("high")
-model = timesfm.TimesFM_2p5_200M_torch.from_pretrained(
- "google/timesfm-2.5-200m-pytorch"
-)
-model.compile(timesfm.ForecastConfig(
- max_context=512, max_horizon=52, normalize_inputs=True,
- use_continuous_quantile_head=True, fix_quantile_crossing=True,
-))
-
-# 4. Prepare data
-df = pd.read_csv("weekly_demand.csv", parse_dates=["week"])
-values = df["demand"].values.astype(np.float32)
-
-# 5. Forecast
-point, quantiles = model.forecast(horizon=52, inputs=[values])
-
-# 6. Extract prediction intervals
-forecast_df = pd.DataFrame({
- "forecast": point[0],
- "lower_80": quantiles[0, :, 1],
- "upper_80": quantiles[0, :, 9],
-})
-
-# 7. Plot
-import matplotlib
-matplotlib.use("Agg")
-import matplotlib.pyplot as plt
-fig, ax = plt.subplots(figsize=(12, 5))
-ax.plot(values[-104:], label="Historical")
-x_fc = range(len(values[-104:]), len(values[-104:]) + 52)
-ax.plot(x_fc, forecast_df["forecast"], label="Forecast", color="tab:orange")
-ax.fill_between(x_fc, forecast_df["lower_80"], forecast_df["upper_80"],
- alpha=0.2, color="tab:orange", label="80% PI")
-ax.legend()
-ax.set_title("52-Week Demand Forecast")
-plt.tight_layout()
-plt.savefig("forecast.png", dpi=150)
-print("Saved forecast.png")
-```
-
-### Workflow 2: Batch Forecasting (Many Series)
-
-```python
-import pandas as pd, numpy as np
-
-# Load wide-format CSV (one column per series)
-df = pd.read_csv("all_stores.csv", parse_dates=["date"], index_col="date")
-inputs = [df[col].dropna().values.astype(np.float32) for col in df.columns]
-
-# Forecast all series at once (batched internally)
-point, quantiles = model.forecast(horizon=30, inputs=inputs)
-
-# Collect results
-results = {}
-for i, col in enumerate(df.columns):
- results[col] = {
- "forecast": point[i].tolist(),
- "lower_80": quantiles[i, :, 1].tolist(),
- "upper_80": quantiles[i, :, 9].tolist(),
- }
-
-# Export
-import json
-with open("batch_forecasts.json", "w") as f:
- json.dump(results, f, indent=2)
-print(f"Forecasted {len(results)} series → batch_forecasts.json")
-```
-
-### Workflow 3: Evaluate Forecast Accuracy
-
-```python
-import numpy as np
-
-# Hold out the last H points for evaluation
-H = 24
-train = values[:-H]
-actual = values[-H:]
-
-point, quantiles = model.forecast(horizon=H, inputs=[train])
-pred = point[0]
-
-# Metrics
-mae = np.mean(np.abs(actual - pred))
-rmse = np.sqrt(np.mean((actual - pred) ** 2))
-mape = np.mean(np.abs((actual - pred) / actual)) * 100
-
-# Prediction interval coverage
-lower = quantiles[0, :, 1]
-upper = quantiles[0, :, 9]
-coverage = np.mean((actual >= lower) & (actual <= upper)) * 100
-
-print(f"MAE: {mae:.2f}")
-print(f"RMSE: {rmse:.2f}")
-print(f"MAPE: {mape:.1f}%")
-print(f"80% PI Coverage: {coverage:.1f}% (target: 80%)")
-```
-
----
-
-## ⚙️ Performance Tuning
-
-### GPU Acceleration
-
-```python
-import torch
-
-# Check GPU availability
-if torch.cuda.is_available():
- print(f"GPU: {torch.cuda.get_device_name(0)}")
- print(f"VRAM: {torch.cuda.get_device_properties(0).total_mem / 1e9:.1f} GB")
-elif hasattr(torch.backends, "mps") and torch.backends.mps.is_available():
- print("Apple Silicon MPS available")
-else:
- print("CPU only — inference will be slower but still works")
-
-# Always set this for Ampere+ GPUs (A100, RTX 3090, etc.)
-torch.set_float32_matmul_precision("high")
-```
-
-### Batch Size Tuning
-
-```python
-# Start conservative, increase until OOM
-# GPU with 8 GB VRAM: per_core_batch_size=64
-# GPU with 16 GB VRAM: per_core_batch_size=128
-# GPU with 24 GB VRAM: per_core_batch_size=256
-# CPU with 8 GB RAM: per_core_batch_size=8
-# CPU with 16 GB RAM: per_core_batch_size=32
-# CPU with 32 GB RAM: per_core_batch_size=64
-
-model.compile(timesfm.ForecastConfig(
- max_context=1024,
- max_horizon=256,
- per_core_batch_size=32, # <-- tune this
- normalize_inputs=True,
- use_continuous_quantile_head=True,
- fix_quantile_crossing=True,
-))
-```
-
-### Memory-Constrained Environments
-
-```python
-import gc, torch
-
-# Force garbage collection before loading
-gc.collect()
-if torch.cuda.is_available():
- torch.cuda.empty_cache()
-
-# Load model
-model = timesfm.TimesFM_2p5_200M_torch.from_pretrained(
- "google/timesfm-2.5-200m-pytorch"
-)
-
-# Use small batch size on low-memory machines
-model.compile(timesfm.ForecastConfig(
- max_context=512, # Reduce context if needed
- max_horizon=128, # Reduce horizon if needed
- per_core_batch_size=4, # Small batches
- normalize_inputs=True,
- use_continuous_quantile_head=True,
- fix_quantile_crossing=True,
-))
-
-# Process series in chunks to avoid OOM
-CHUNK = 50
-all_results = []
-for i in range(0, len(inputs), CHUNK):
- chunk = inputs[i:i+CHUNK]
- p, q = model.forecast(horizon=H, inputs=chunk)
- all_results.append((p, q))
- gc.collect() # Clean up between chunks
-```
-
----
-
-## 📚 Available Scripts
-
-### `claude-skill/scripts/check_system.py`
-
-**Mandatory preflight checker.** Run before first model load.
-
-```bash
-python claude-skill/scripts/check_system.py
-```
-
-Output example:
-```
-=== TimesFM System Requirements Check ===
-
-[RAM] Total: 32.0 GB | Available: 24.3 GB ✅ PASS
-[GPU] NVIDIA RTX 4090 | VRAM: 24.0 GB ✅ PASS
-[Disk] Free: 142.5 GB ✅ PASS
-[Python] 3.12.1 ✅ PASS
-[timesfm] Installed (2.5.0) ✅ PASS
-[torch] Installed (2.4.1+cu121) ✅ PASS
-
-VERDICT: ✅ System is ready for TimesFM 2.5 (GPU mode)
-Recommended: per_core_batch_size=128
-```
-
-### `claude-skill/scripts/forecast_csv.py`
-
-End-to-end CSV forecasting with automatic system check.
-
-```bash
-python claude-skill/scripts/forecast_csv.py input.csv \
- --horizon 24 \
- --date-col date \
- --value-cols sales,revenue \
- --output forecasts.csv
-```
-
----
-
-## 📖 Reference Documentation
-
-Detailed guides in `claude-skill/references/`:
-
-| File | Contents |
-| ---- | -------- |
-| `references/system_requirements.md` | Hardware tiers, GPU/CPU selection, memory estimation formulas |
-| `references/api_reference.md` | Full `ForecastConfig` docs, `from_pretrained` options, output shapes |
-| `references/data_preparation.md` | Input formats, NaN handling, CSV loading, covariate setup |
-
----
-
-## 🧪 Examples
-
-Three fully-working reference examples live in `claude-skill/examples/`. Use them as ground truth
-for correct API usage and expected output shape.
-
-| Example | Directory | What It Demonstrates | When To Use It |
-| ------- | --------- | -------------------- | -------------- |
-| **Global Temperature Forecast** | `examples/global-temperature/` | Basic `model.forecast()` call, CSV → PNG → GIF pipeline, 36-month NOAA context | Starting point; copy-paste baseline for any univariate series |
-| **Anomaly Detection** | `examples/anomaly-detection/` | Two-phase detection: linear detrend + Z-score on context, quantile PI on forecast; 2-panel viz | Any task requiring outlier detection on historical + forecasted data |
-| **Covariates (XReg)** | `examples/covariates-forecasting/` | `forecast_with_covariates()` API (TimesFM 2.5), covariate decomposition, 2×2 shared-axis viz | Retail, energy, or any series with known exogenous drivers |
-
-### Running the Examples
-
-```bash
-# Global temperature (basic forecast + visualization)
-cd claude-skill/examples/global-temperature && python run_forecast.py && python visualize_forecast.py
-
-# Anomaly detection (two-phase: detrend + Z-score on context, quantile PI on forecast)
-cd claude-skill/examples/anomaly-detection && python detect_anomalies.py
-
-# Covariates (API demo — requires timesfm[xreg] for real inference)
-cd claude-skill/examples/covariates-forecasting && python demo_covariates.py
-```
-
-### Expected Outputs
-
-| Example | Key output files | Acceptance criteria |
-| ------- | ---------------- | ------------------- |
-| global-temperature | `output/forecast_output.json`, `output/forecast_visualization.png` | `point_forecast` has 12 values; PNG shows context + forecast + PI bands |
-| anomaly-detection | `output/anomaly_detection.json`, `output/anomaly_detection.png` | Sep 2023 flagged CRITICAL (z >= 3.0); >= 2 forecast CRITICAL from injected anomalies |
-| covariates-forecasting | `output/sales_with_covariates.csv`, `output/covariates_data.png` | CSV has 108 rows (3 stores × 36 weeks); stores have **distinct** price arrays |
-
----
-
-## Common Pitfalls
-
-1. **Not running system check** → model load crashes on low-RAM machines. Always run `check_system.py` first.
-2. **Forgetting `model.compile()`** → `RuntimeError: Model is not compiled`. Must call `compile()` before `forecast()`.
-3. **Not setting `normalize_inputs=True`** → unstable forecasts for series with large values.
-4. **Using v1/v2 on machines with < 32 GB RAM** → use TimesFM 2.5 (200M params) instead.
-5. **Not setting `fix_quantile_crossing=True`** → quantiles may not be monotonic (q10 > q50).
-6. **Huge `per_core_batch_size` on small GPU** → CUDA OOM. Start small, increase.
-7. **Passing 2-D arrays** → TimesFM expects a **list of 1-D arrays**, not a 2-D matrix.
-8. **Forgetting `torch.set_float32_matmul_precision("high")`** → slower inference on Ampere+ GPUs.
-9. **Not handling NaN in output** → edge cases with very short series. Always check `np.isnan(point).any()`.
-10. **Using `infer_is_positive=True` for series that can be negative** → clamps forecasts at zero. Set False for temperature, returns, etc.
-
----
-
-## Model Versions
-
-```mermaid
-timeline
- section 2024
- TimesFM 1.0 : 200M params, 2K context, JAX only
- TimesFM 2.0 : 500M params, 2K context, PyTorch + JAX
- section 2025
- TimesFM 2.5 : 200M params, 16K context, quantile head, no frequency indicator
-```
-
-| Version | Params | Context | Quantile Head | Frequency Flag | Status |
-| ------- | ------ | ------- | ------------- | -------------- | ------ |
-| **2.5** | 200M | 16,384 | ✅ Continuous (30M) | ❌ Removed | **Latest** |
-| 2.0 | 500M | 2,048 | ✅ Fixed buckets | ✅ Required | Archived |
-| 1.0 | 200M | 2,048 | ✅ Fixed buckets | ✅ Required | Archived |
-
-**Hugging Face checkpoints:**
-
-- `google/timesfm-2.5-200m-pytorch` (recommended)
-- `google/timesfm-2.5-200m-flax`
-- `google/timesfm-2.0-500m-pytorch` (archived)
-- `google/timesfm-1.0-200m-pytorch` (archived)
-
----
-
-## Resources
-
-- **Paper**: [A Decoder-Only Foundation Model for Time-Series Forecasting](https://arxiv.org/abs/2310.10688) (ICML 2024)
-- **Repository**: https://github.com/google-research/timesfm
-- **Hugging Face**: https://huggingface.co/collections/google/timesfm-release-66e4be5fdb56e960c1e482a6
-- **Google Blog**: https://research.google/blog/a-decoder-only-foundation-model-for-time-series-forecasting/
-- **BigQuery Integration**: https://cloud.google.com/bigquery/docs/timesfm-model
-
----
-
-## Quality Checklist
-
-Run this checklist after every TimesFM task before declaring success:
-
-- [ ] **Output shape correct** — `point_fc` shape is `(n_series, horizon)`, `quant_fc` is `(n_series, horizon, 10)`
-- [ ] **Quantile indices** — index 0 = mean, 1 = q10, 2 = q20 ... 9 = q90. **NOT** 0 = q0, 1 = q10.
-- [ ] **Frequency flag** — TimesFM 1.0/2.0: pass `freq=[0]` for monthly data. TimesFM 2.5: no freq flag.
-- [ ] **Series length** — context must be >= 32 data points (model minimum). Warn if shorter.
-- [ ] **No NaN** — `np.isnan(point_fc).any()` should be False. Check input series for gaps first.
-- [ ] **Visualization axes** — if multiple panels share data, use `sharex=True`. All time axes must cover the same span.
-- [ ] **Binary outputs tracked** — PNG and GIF files should be tracked via Git LFS (`.gitattributes`).
-- [ ] **No large datasets committed** — any real dataset > 1 MB should be downloaded to `tempfile.mkdtemp()` and annotated in code.
-- [ ] **`matplotlib.use('Agg')`** — must appear before any pyplot import when running headless.
-- [ ] **`infer_is_positive`** — set `False` for temperature anomalies, financial returns, or any series that can be negative.
-
----
-
-## Common Mistakes
-
-These bugs appeared during development. Learn from them:
-
-1. **Quantile index off-by-one** — The most common mistake. `quant_fc[..., 0]` is the **mean**, not q0. q10 = index 1, q90 = index 9. Always define named constants: `IDX_Q10, IDX_Q20, IDX_Q80, IDX_Q90 = 1, 2, 8, 9`.
-
-2. **Variable shadowing in comprehensions** — If you build per-series covariate dicts inside a loop, do NOT use the loop variable as the comprehension variable. Accumulate into separate `dict[str, ndarray]` outside the loop, then assign.
- ```python
- # WRONG — outer `store_id` gets shadowed:
- covariates = {store_id: arr[store_id] for store_id in stores} # inside outer loop over store_id
- # CORRECT — use a different name or accumulate beforehand:
- prices_by_store: dict[str, np.ndarray] = {}
- for store_id, config in stores.items():
- prices_by_store[store_id] = compute_price(config)
- ```
-
-3. **Wrong CSV column name** — The global-temperature CSV uses `anomaly_c`, not `anomaly`. Always `print(df.columns)` before accessing.
-
-4. **`tight_layout()` warning with `sharex=True`** — Harmless; suppress with `plt.tight_layout(rect=[0, 0, 1, 0.97])` or ignore.
-
-5. **TimesFM 2.5 required for `forecast_with_covariates()`** — TimesFM 1.0 does NOT have this method. Install `pip install timesfm[xreg]` and use checkpoint `google/timesfm-2.5-200m-pytorch`.
-
-6. **Future covariates must span the full horizon** — Dynamic covariates (price, promotions, holidays) must have values for BOTH the context AND the forecast horizon. You cannot pass context-only arrays.
-
-7. **Anomaly thresholds must be defined once** — Define `CRITICAL_Z = 3.0`, `WARNING_Z = 2.0` as module-level constants. Never hardcode `3` or `2` inline.
-
-8. **Context anomaly detection uses residuals, not raw values** — Always detrend first (`np.polyfit` linear, or seasonal decomposition), then Z-score the residuals. Raw-value Z-scores are misleading on trending data.
-
----
-
-## Validation & Verification
-
-Use the example outputs as regression baselines. If you change forecasting logic, verify:
-
-```bash
-# Anomaly detection regression check:
-python -c "
-import json
-d = json.load(open('claude-skill/examples/anomaly-detection/output/anomaly_detection.json'))
-ctx = d['context_summary']
-assert ctx['critical'] >= 1, 'Sep 2023 must be CRITICAL'
-assert any(r['date'] == '2023-09' and r['severity'] == 'CRITICAL'
- for r in d['context_detections']), 'Sep 2023 not found'
-print('Anomaly detection regression: PASS')"
-
-# Covariates regression check:
-python -c "
-import pandas as pd
-df = pd.read_csv('claude-skill/examples/covariates-forecasting/output/sales_with_covariates.csv')
-assert len(df) == 108, f'Expected 108 rows, got {len(df)}'
-prices = df.groupby('store_id')['price'].mean()
-assert prices['store_A'] > prices['store_B'] > prices['store_C'], 'Store price ordering wrong'
-print('Covariates regression: PASS')"
-```
diff --git a/timesfm-forecasting/SKILL.md b/timesfm-forecasting/SKILL.md
new file mode 100644
index 0000000..2c57c35
--- /dev/null
+++ b/timesfm-forecasting/SKILL.md
@@ -0,0 +1,448 @@
+---
+name: timesfm-forecasting
+description: >
+ Zero-shot time series forecasting with Google's TimesFM foundation model. Use this
+ skill when forecasting ANY univariate time series — sales, sensor readings, stock prices,
+ energy demand, patient vitals, weather, or scientific measurements — without training a
+ custom model. Automatically checks system RAM/GPU before loading the model, supports
+ CSV/DataFrame/array inputs, and returns point forecasts with calibrated prediction
+ intervals. Includes a preflight system checker script that MUST be run before first use
+ to verify the machine can load the model.
+license: Apache-2.0
+metadata:
+ author: Clayton Young (@borealBytes)
+ version: "1.0.0"
+---
+
+# TimesFM Forecasting
+
+## Overview
+
+TimesFM (Time Series Foundation Model) is a pretrained decoder-only foundation model
+developed by Google Research for time-series forecasting. It works **zero-shot** — feed it
+any univariate time series and it returns point forecasts with calibrated quantile
+prediction intervals, no training required.
+
+This skill includes a **mandatory preflight system checker** that verifies RAM, GPU memory,
+and disk space before the model is ever loaded so the agent never crashes the user's machine.
+
+> **Key numbers**: TimesFM 2.5 uses 200M parameters (~800 MB on disk, ~1.5 GB in RAM on
+> CPU, ~1 GB VRAM on GPU). The archived v1/v2 500M-parameter model needs ~32 GB RAM.
+> Always run the system checker first.
+
+## When to Use This Skill
+
+Use this skill when:
+
+- Forecasting **any univariate time series** (sales, demand, sensor, vitals, price, weather)
+- You need **zero-shot forecasting** without training a custom model
+- You want **probabilistic forecasts** with calibrated prediction intervals (quantiles)
+- You have time series of **any length** (the model handles 1–16,384 context points)
+- You need to **batch-forecast** hundreds or thousands of series efficiently
+- You want a **foundation model** approach instead of hand-tuning ARIMA/ETS parameters
+
+Do **not** use this skill when:
+
+- You need classical statistical models with coefficient interpretation → use `statsmodels`
+- You need time series classification or clustering → use `aeon`
+- You need multivariate vector autoregression or Granger causality → use `statsmodels`
+- Your data is tabular (not temporal) → use `scikit-learn`
+
+> **Note on Anomaly Detection**: TimesFM does not have built-in anomaly detection, but you
+> can use the **quantile forecasts as prediction intervals** — values outside the 90% CI
+> (q10–q90) are statistically unusual. See `examples/anomaly-detection/` for a full example.
+
+## ⚠️ Mandatory Preflight: System Requirements Check
+
+**CRITICAL — ALWAYS run the system checker before loading the model for the first time.**
+
+```bash
+python scripts/check_system.py
+```
+
+This script checks:
+
+1. **Available RAM** — warns if below 4 GB, blocks if below 2 GB
+2. **GPU availability** — detects CUDA/MPS devices and VRAM
+3. **Disk space** — verifies room for the ~800 MB model download
+4. **Python version** — requires 3.10+
+5. **Existing installation** — checks if `timesfm` and `torch` are installed
+
+> **Note:** Model weights are **NOT stored in this repository**. TimesFM weights (~800 MB)
+> download on-demand from HuggingFace on first use and cache in `~/.cache/huggingface/`.
+
+```mermaid
+flowchart TD
+ start["🚀 Run check_system.py"] --> ram{"RAM ≥ 4 GB?"}
+ ram -->|"Yes"| gpu{"GPU available?"}
+ ram -->|"No (2-4 GB)"| warn_ram["⚠️ Warning: tight RAM
CPU-only, small batches"]
+ ram -->|"No (< 2 GB)"| block["🛑 BLOCKED
Insufficient memory"]
+ warn_ram --> disk
+ gpu -->|"CUDA / MPS"| vram{"VRAM ≥ 2 GB?"}
+ gpu -->|"CPU only"| cpu_ok["✅ CPU mode
Slower but works"]
+ vram -->|"Yes"| gpu_ok["✅ GPU mode
Fast inference"]
+ vram -->|"No"| cpu_ok
+ gpu_ok --> disk{"Disk ≥ 2 GB free?"}
+ cpu_ok --> disk
+ disk -->|"Yes"| ready["✅ READY
Safe to load model"]
+ disk -->|"No"| block_disk["🛑 BLOCKED
Need space for weights"]
+```
+
+### Hardware Requirements by Model Version
+
+| Model | Parameters | RAM (CPU) | VRAM (GPU) | Disk | Context |
+| ----- | ---------- | --------- | ---------- | ---- | ------- |
+| **TimesFM 2.5** (recommended) | 200M | ≥ 4 GB | ≥ 2 GB | ~800 MB | up to 16,384 |
+| TimesFM 2.0 (archived) | 500M | ≥ 16 GB | ≥ 8 GB | ~2 GB | up to 2,048 |
+| TimesFM 1.0 (archived) | 200M | ≥ 8 GB | ≥ 4 GB | ~800 MB | up to 2,048 |
+
+> **Recommendation**: Always use TimesFM 2.5 unless you have a specific reason to use an
+> older checkpoint. It is smaller, faster, and supports 8× longer context.
+
+## 🔧 Installation
+
+### Step 1: Verify System (always first)
+
+```bash
+python scripts/check_system.py
+```
+
+### Step 2: Install TimesFM
+
+```bash
+# Using uv (fast)
+uv pip install timesfm[torch]
+
+# Or using pip
+pip install timesfm[torch]
+
+# For JAX/Flax backend (faster on TPU/GPU)
+uv pip install timesfm[flax]
+```
+
+### Step 3: Install PyTorch for Your Hardware
+
+```bash
+# CUDA 12.1 (NVIDIA GPU)
+pip install torch>=2.0.0 --index-url https://download.pytorch.org/whl/cu121
+
+# CPU only
+pip install torch>=2.0.0 --index-url https://download.pytorch.org/whl/cpu
+
+# Apple Silicon (MPS)
+pip install torch>=2.0.0 # MPS support is built-in
+```
+
+## 🎯 Quick Start
+
+### Minimal Example
+
+```python
+import torch, numpy as np, timesfm
+
+torch.set_float32_matmul_precision("high")
+
+model = timesfm.TimesFM_2p5_200M_torch.from_pretrained(
+ "google/timesfm-2.5-200m-pytorch"
+)
+model.compile(timesfm.ForecastConfig(
+ max_context=1024, max_horizon=256, normalize_inputs=True,
+ use_continuous_quantile_head=True, force_flip_invariance=True,
+ infer_is_positive=True, fix_quantile_crossing=True,
+))
+
+point, quantiles = model.forecast(horizon=24, inputs=[
+ np.sin(np.linspace(0, 20, 200)), # any 1-D array
+])
+# point.shape == (1, 24) — median forecast
+# quantiles.shape == (1, 24, 10) — 10th–90th percentile bands
+```
+
+### Forecast with Covariates (XReg)
+
+TimesFM 2.5+ supports exogenous variables through `forecast_with_covariates()`.
+Requires `pip install timesfm[xreg]`.
+
+```python
+point, quantiles = model.forecast_with_covariates(
+ inputs=inputs,
+ dynamic_numerical_covariates={"price": price_arrays},
+ dynamic_categorical_covariates={"holiday": holiday_arrays},
+ static_categorical_covariates={"region": region_labels},
+ xreg_mode="xreg + timesfm", # or "timesfm + xreg"
+)
+```
+
+### Anomaly Detection (via Quantile Intervals)
+
+```python
+point, q = model.forecast(horizon=H, inputs=[values])
+
+lower_90 = q[0, :, 1] # 10th percentile
+upper_90 = q[0, :, 9] # 90th percentile
+
+actual = test_values
+anomalies = (actual < lower_90) | (actual > upper_90)
+```
+
+| Severity | Condition | Interpretation |
+| -------- | --------- | -------------- |
+| **Normal** | Inside 80% CI | Expected behavior |
+| **Warning** | Outside 80% CI | Unusual but possible |
+| **Critical** | Outside 90% CI | Statistically rare (< 10% probability) |
+
+> See `examples/anomaly-detection/` for a complete worked example with visualization.
+
+## 📊 Understanding the Output
+
+TimesFM returns `(point_forecast, quantile_forecast)`:
+
+- **`point_forecast`**: shape `(batch, horizon)` — the median (0.5 quantile)
+- **`quantile_forecast`**: shape `(batch, horizon, 10)` — ten quantile slices:
+
+| Index | Quantile | Use |
+| ----- | -------- | --- |
+| 0 | Mean | Average prediction |
+| 1 | 0.1 | Lower bound of 80% PI |
+| 2 | 0.2 | Lower bound of 60% PI |
+| **5** | **0.5** | **Median (= `point_forecast`)** |
+| 8 | 0.8 | Upper bound of 60% PI |
+| 9 | 0.9 | Upper bound of 80% PI |
+
+```python
+point, q = model.forecast(horizon=H, inputs=data)
+
+lower_80 = q[:, :, 1] # 10th percentile
+upper_80 = q[:, :, 9] # 90th percentile
+median = q[:, :, 5]
+```
+
+## 🔧 ForecastConfig Reference
+
+All forecasting behavior is controlled by `timesfm.ForecastConfig`:
+
+```python
+timesfm.ForecastConfig(
+ max_context=1024, # Max context window
+ max_horizon=256, # Max forecast horizon
+ normalize_inputs=True, # RECOMMENDED — prevents scale instability
+ per_core_batch_size=32, # Tune for memory
+ use_continuous_quantile_head=True, # Better quantile accuracy for long horizons
+ force_flip_invariance=True, # Ensures f(-x) = -f(x)
+ infer_is_positive=True, # Clamp forecasts ≥ 0 when all inputs > 0
+ fix_quantile_crossing=True, # Ensure q10 ≤ q20 ≤ ... ≤ q90
+ return_backcast=False, # Return backcast (for covariate workflows)
+)
+```
+
+| Parameter | Default | When to Change |
+| --------- | ------- | -------------- |
+| `max_context` | 0 | Set to match your longest historical window |
+| `normalize_inputs` | False | **Always set True** |
+| `use_continuous_quantile_head` | False | **Set True** for calibrated PIs |
+| `infer_is_positive` | True | Set False for series that can be negative |
+| `fix_quantile_crossing` | False | **Set True** for monotonic quantiles |
+
+See `references/api_reference.md` for the complete parameter reference.
+
+## 📋 Common Workflows
+
+### Single Series Forecast
+
+```python
+import torch, numpy as np, pandas as pd, timesfm, matplotlib
+matplotlib.use("Agg")
+import matplotlib.pyplot as plt
+
+torch.set_float32_matmul_precision("high")
+model = timesfm.TimesFM_2p5_200M_torch.from_pretrained(
+ "google/timesfm-2.5-200m-pytorch"
+)
+model.compile(timesfm.ForecastConfig(
+ max_context=512, max_horizon=52, normalize_inputs=True,
+ use_continuous_quantile_head=True, fix_quantile_crossing=True,
+))
+
+df = pd.read_csv("weekly_demand.csv", parse_dates=["week"])
+values = df["demand"].values.astype(np.float32)
+
+point, quantiles = model.forecast(horizon=52, inputs=[values])
+
+fig, ax = plt.subplots(figsize=(12, 5))
+ax.plot(values[-104:], label="Historical")
+x_fc = range(len(values[-104:]), len(values[-104:]) + 52)
+ax.plot(x_fc, point[0], label="Forecast", color="tab:orange")
+ax.fill_between(x_fc, quantiles[0, :, 1], quantiles[0, :, 9],
+ alpha=0.2, color="tab:orange", label="80% PI")
+ax.legend(); ax.set_title("52-Week Demand Forecast")
+plt.tight_layout(); plt.savefig("forecast.png", dpi=150)
+```
+
+### Batch Forecasting (Many Series)
+
+```python
+df = pd.read_csv("all_stores.csv", parse_dates=["date"], index_col="date")
+inputs = [df[col].dropna().values.astype(np.float32) for col in df.columns]
+
+point, quantiles = model.forecast(horizon=30, inputs=inputs)
+
+import json
+results = {col: {"forecast": point[i].tolist(),
+ "lower_80": quantiles[i, :, 1].tolist(),
+ "upper_80": quantiles[i, :, 9].tolist()}
+ for i, col in enumerate(df.columns)}
+with open("batch_forecasts.json", "w") as f:
+ json.dump(results, f, indent=2)
+```
+
+### Evaluate Forecast Accuracy
+
+```python
+H = 24
+train, actual = values[:-H], values[-H:]
+point, quantiles = model.forecast(horizon=H, inputs=[train])
+pred = point[0]
+
+mae = np.mean(np.abs(actual - pred))
+rmse = np.sqrt(np.mean((actual - pred) ** 2))
+mape = np.mean(np.abs((actual - pred) / actual)) * 100
+coverage = np.mean((actual >= quantiles[0, :, 1]) & (actual <= quantiles[0, :, 9])) * 100
+
+print(f"MAE: {mae:.2f} | RMSE: {rmse:.2f} | MAPE: {mape:.1f}% | 80% PI Coverage: {coverage:.1f}%")
+```
+
+## ⚙️ Performance Tuning
+
+```python
+# Always set on Ampere+ GPUs (A100, RTX 3090+)
+torch.set_float32_matmul_precision("high")
+
+# Batch size guidelines:
+# GPU 8 GB VRAM: per_core_batch_size=64
+# GPU 16 GB VRAM: per_core_batch_size=128
+# CPU 8 GB RAM: per_core_batch_size=8
+# CPU 16 GB RAM: per_core_batch_size=32
+
+# Memory-constrained: process in chunks
+CHUNK = 50
+results = []
+for i in range(0, len(inputs), CHUNK):
+ p, q = model.forecast(horizon=H, inputs=inputs[i:i+CHUNK])
+ results.append((p, q))
+```
+
+## 📚 Available Scripts
+
+### `scripts/check_system.py`
+
+Mandatory preflight checker — run before first model load.
+
+```bash
+python scripts/check_system.py
+```
+
+### `scripts/forecast_csv.py`
+
+End-to-end CSV forecasting CLI.
+
+```bash
+python scripts/forecast_csv.py input.csv \
+ --horizon 24 \
+ --date-col date \
+ --value-cols sales,revenue \
+ --output forecasts.csv
+```
+
+## 📖 Reference Documentation
+
+| File | Contents |
+| ---- | -------- |
+| `references/system_requirements.md` | Hardware tiers, GPU/CPU selection, memory estimation |
+| `references/api_reference.md` | Full `ForecastConfig` docs, output shapes, model options |
+| `references/data_preparation.md` | Input formats, NaN handling, CSV loading, covariate setup |
+
+## 🧪 Examples
+
+| Example | Directory | What It Demonstrates |
+| ------- | --------- | -------------------- |
+| **Global Temperature Forecast** | `examples/global-temperature/` | Basic `model.forecast()`, CSV → PNG → GIF pipeline |
+| **Anomaly Detection** | `examples/anomaly-detection/` | Two-phase detrend + Z-score + quantile PI, 2-panel viz |
+| **Covariates (XReg)** | `examples/covariates-forecasting/` | `forecast_with_covariates()`, 2×2 shared-axis viz |
+
+```bash
+# Run all three examples:
+cd examples/global-temperature && python run_forecast.py && python visualize_forecast.py
+cd examples/anomaly-detection && python detect_anomalies.py
+cd examples/covariates-forecasting && python demo_covariates.py
+```
+
+### Expected Outputs
+
+| Example | Key output files | Acceptance criteria |
+| ------- | ---------------- | ------------------- |
+| global-temperature | `output/forecast_output.json`, `output/forecast_visualization.png` | `point_forecast` has 12 values; PNG shows context + forecast + PI bands |
+| anomaly-detection | `output/anomaly_detection.json`, `output/anomaly_detection.png` | Sep 2023 flagged CRITICAL (z ≥ 3.0) |
+| covariates-forecasting | `output/sales_with_covariates.csv`, `output/covariates_data.png` | 108 rows (3 stores × 36 weeks); distinct price arrays per store |
+
+## Model Versions
+
+| Version | Params | Context | Status | HuggingFace checkpoint |
+| ------- | ------ | ------- | ------ | ---------------------- |
+| **2.5** | 200M | 16,384 | **Latest** | `google/timesfm-2.5-200m-pytorch` |
+| 2.0 | 500M | 2,048 | Archived | `google/timesfm-2.0-500m-pytorch` |
+| 1.0 | 200M | 2,048 | Archived | `google/timesfm-1.0-200m-pytorch` |
+
+- TimesFM 1.0/2.0: must pass `freq=[0]` for monthly data
+- TimesFM 2.5: no frequency flag — it was removed
+
+## Resources
+
+- **Paper**: [A Decoder-Only Foundation Model for Time-Series Forecasting](https://arxiv.org/abs/2310.10688) (ICML 2024)
+- **HuggingFace**: https://huggingface.co/collections/google/timesfm-release-66e4be5fdb56e960c1e482a6
+- **Google Blog**: https://research.google/blog/a-decoder-only-foundation-model-for-time-series-forecasting/
+- **BigQuery Integration**: https://cloud.google.com/bigquery/docs/timesfm-model
+
+## Quality Checklist
+
+Run after every TimesFM task before declaring success:
+
+- [ ] **Output shape** — `point_fc` is `(n_series, horizon)`, `quant_fc` is `(n_series, horizon, 10)`
+- [ ] **Quantile indices** — index 0 = mean, 1 = q10 ... 9 = q90. NOT 0 = q0.
+- [ ] **Frequency flag** — TimesFM 1.0/2.0: pass `freq=[0]` for monthly. TimesFM 2.5: omit.
+- [ ] **Series length** — context must be ≥ 32 data points.
+- [ ] **No NaN** — `np.isnan(point_fc).any()` must be False.
+- [ ] **Axes** — multiple panels sharing data must use `sharex=True`.
+- [ ] **`matplotlib.use('Agg')`** — before any pyplot import when running headless.
+- [ ] **`infer_is_positive`** — set False for temperature, financial returns, negatives.
+
+## Common Mistakes
+
+1. **Quantile index off-by-one** — `quant_fc[..., 0]` is the **mean**, not q0. q10 = index 1, q90 = index 9. Define: `IDX_Q10, IDX_Q90 = 1, 9`.
+
+2. **Variable shadowing in covariate loops** — don't use the outer loop variable as a comprehension variable when building per-series covariate dicts.
+
+3. **Wrong CSV column name** — global-temperature CSV uses `anomaly_c`, not `anomaly`. Print `df.columns` first.
+
+4. **TimesFM 2.5 required for `forecast_with_covariates()`** — TimesFM 1.0 does NOT have this method.
+
+5. **Future covariates must span the full horizon** — dynamic covariates need values for BOTH context AND forecast windows.
+
+6. **Context anomaly detection uses residuals** — detrend first, then Z-score. Raw Z-scores mislead on trending data.
+
+## Validation & Verification
+
+```bash
+# Anomaly detection regression:
+python -c "
+import json
+d = json.load(open('examples/anomaly-detection/output/anomaly_detection.json'))
+assert d['context_summary']['critical'] >= 1, 'Sep 2023 must be CRITICAL'
+print('Anomaly detection: PASS')"
+
+# Covariates regression:
+python -c "
+import pandas as pd
+df = pd.read_csv('examples/covariates-forecasting/output/sales_with_covariates.csv')
+assert len(df) == 108, f'Expected 108 rows, got {len(df)}'
+print('Covariates: PASS')"
+```
diff --git a/claude-skill/examples/anomaly-detection/detect_anomalies.py b/timesfm-forecasting/examples/anomaly-detection/detect_anomalies.py
similarity index 100%
rename from claude-skill/examples/anomaly-detection/detect_anomalies.py
rename to timesfm-forecasting/examples/anomaly-detection/detect_anomalies.py
diff --git a/claude-skill/examples/anomaly-detection/output/anomaly_detection.json b/timesfm-forecasting/examples/anomaly-detection/output/anomaly_detection.json
similarity index 100%
rename from claude-skill/examples/anomaly-detection/output/anomaly_detection.json
rename to timesfm-forecasting/examples/anomaly-detection/output/anomaly_detection.json
diff --git a/claude-skill/examples/anomaly-detection/output/anomaly_detection.png b/timesfm-forecasting/examples/anomaly-detection/output/anomaly_detection.png
similarity index 100%
rename from claude-skill/examples/anomaly-detection/output/anomaly_detection.png
rename to timesfm-forecasting/examples/anomaly-detection/output/anomaly_detection.png
diff --git a/claude-skill/examples/covariates-forecasting/demo_covariates.py b/timesfm-forecasting/examples/covariates-forecasting/demo_covariates.py
similarity index 100%
rename from claude-skill/examples/covariates-forecasting/demo_covariates.py
rename to timesfm-forecasting/examples/covariates-forecasting/demo_covariates.py
diff --git a/claude-skill/examples/covariates-forecasting/output/covariates_data.png b/timesfm-forecasting/examples/covariates-forecasting/output/covariates_data.png
similarity index 100%
rename from claude-skill/examples/covariates-forecasting/output/covariates_data.png
rename to timesfm-forecasting/examples/covariates-forecasting/output/covariates_data.png
diff --git a/claude-skill/examples/covariates-forecasting/output/covariates_metadata.json b/timesfm-forecasting/examples/covariates-forecasting/output/covariates_metadata.json
similarity index 100%
rename from claude-skill/examples/covariates-forecasting/output/covariates_metadata.json
rename to timesfm-forecasting/examples/covariates-forecasting/output/covariates_metadata.json
diff --git a/claude-skill/examples/covariates-forecasting/output/sales_with_covariates.csv b/timesfm-forecasting/examples/covariates-forecasting/output/sales_with_covariates.csv
similarity index 100%
rename from claude-skill/examples/covariates-forecasting/output/sales_with_covariates.csv
rename to timesfm-forecasting/examples/covariates-forecasting/output/sales_with_covariates.csv
diff --git a/claude-skill/examples/global-temperature/README.md b/timesfm-forecasting/examples/global-temperature/README.md
similarity index 100%
rename from claude-skill/examples/global-temperature/README.md
rename to timesfm-forecasting/examples/global-temperature/README.md
diff --git a/claude-skill/examples/global-temperature/generate_animation_data.py b/timesfm-forecasting/examples/global-temperature/generate_animation_data.py
similarity index 100%
rename from claude-skill/examples/global-temperature/generate_animation_data.py
rename to timesfm-forecasting/examples/global-temperature/generate_animation_data.py
diff --git a/claude-skill/examples/global-temperature/generate_gif.py b/timesfm-forecasting/examples/global-temperature/generate_gif.py
similarity index 100%
rename from claude-skill/examples/global-temperature/generate_gif.py
rename to timesfm-forecasting/examples/global-temperature/generate_gif.py
diff --git a/claude-skill/examples/global-temperature/generate_html.py b/timesfm-forecasting/examples/global-temperature/generate_html.py
similarity index 100%
rename from claude-skill/examples/global-temperature/generate_html.py
rename to timesfm-forecasting/examples/global-temperature/generate_html.py
diff --git a/claude-skill/examples/global-temperature/output/animation_data.json b/timesfm-forecasting/examples/global-temperature/output/animation_data.json
similarity index 100%
rename from claude-skill/examples/global-temperature/output/animation_data.json
rename to timesfm-forecasting/examples/global-temperature/output/animation_data.json
diff --git a/claude-skill/examples/global-temperature/output/forecast_animation.gif b/timesfm-forecasting/examples/global-temperature/output/forecast_animation.gif
similarity index 100%
rename from claude-skill/examples/global-temperature/output/forecast_animation.gif
rename to timesfm-forecasting/examples/global-temperature/output/forecast_animation.gif
diff --git a/claude-skill/examples/global-temperature/output/forecast_output.csv b/timesfm-forecasting/examples/global-temperature/output/forecast_output.csv
similarity index 100%
rename from claude-skill/examples/global-temperature/output/forecast_output.csv
rename to timesfm-forecasting/examples/global-temperature/output/forecast_output.csv
diff --git a/claude-skill/examples/global-temperature/output/forecast_output.json b/timesfm-forecasting/examples/global-temperature/output/forecast_output.json
similarity index 100%
rename from claude-skill/examples/global-temperature/output/forecast_output.json
rename to timesfm-forecasting/examples/global-temperature/output/forecast_output.json
diff --git a/claude-skill/examples/global-temperature/output/forecast_visualization.png b/timesfm-forecasting/examples/global-temperature/output/forecast_visualization.png
similarity index 100%
rename from claude-skill/examples/global-temperature/output/forecast_visualization.png
rename to timesfm-forecasting/examples/global-temperature/output/forecast_visualization.png
diff --git a/claude-skill/examples/global-temperature/output/interactive_forecast.html b/timesfm-forecasting/examples/global-temperature/output/interactive_forecast.html
similarity index 100%
rename from claude-skill/examples/global-temperature/output/interactive_forecast.html
rename to timesfm-forecasting/examples/global-temperature/output/interactive_forecast.html
diff --git a/claude-skill/examples/global-temperature/run_example.sh b/timesfm-forecasting/examples/global-temperature/run_example.sh
similarity index 100%
rename from claude-skill/examples/global-temperature/run_example.sh
rename to timesfm-forecasting/examples/global-temperature/run_example.sh
diff --git a/claude-skill/examples/global-temperature/run_forecast.py b/timesfm-forecasting/examples/global-temperature/run_forecast.py
similarity index 100%
rename from claude-skill/examples/global-temperature/run_forecast.py
rename to timesfm-forecasting/examples/global-temperature/run_forecast.py
diff --git a/claude-skill/examples/global-temperature/temperature_anomaly.csv b/timesfm-forecasting/examples/global-temperature/temperature_anomaly.csv
similarity index 100%
rename from claude-skill/examples/global-temperature/temperature_anomaly.csv
rename to timesfm-forecasting/examples/global-temperature/temperature_anomaly.csv
diff --git a/claude-skill/examples/global-temperature/visualize_forecast.py b/timesfm-forecasting/examples/global-temperature/visualize_forecast.py
similarity index 100%
rename from claude-skill/examples/global-temperature/visualize_forecast.py
rename to timesfm-forecasting/examples/global-temperature/visualize_forecast.py
diff --git a/claude-skill/references/api_reference.md b/timesfm-forecasting/references/api_reference.md
similarity index 100%
rename from claude-skill/references/api_reference.md
rename to timesfm-forecasting/references/api_reference.md
diff --git a/claude-skill/references/data_preparation.md b/timesfm-forecasting/references/data_preparation.md
similarity index 100%
rename from claude-skill/references/data_preparation.md
rename to timesfm-forecasting/references/data_preparation.md
diff --git a/claude-skill/references/system_requirements.md b/timesfm-forecasting/references/system_requirements.md
similarity index 100%
rename from claude-skill/references/system_requirements.md
rename to timesfm-forecasting/references/system_requirements.md
diff --git a/claude-skill/scripts/check_system.py b/timesfm-forecasting/scripts/check_system.py
similarity index 100%
rename from claude-skill/scripts/check_system.py
rename to timesfm-forecasting/scripts/check_system.py
diff --git a/claude-skill/scripts/forecast_csv.py b/timesfm-forecasting/scripts/forecast_csv.py
similarity index 100%
rename from claude-skill/scripts/forecast_csv.py
rename to timesfm-forecasting/scripts/forecast_csv.py