Skip to main content
Glama
arashnicoomanesh

MCP Zero Shot Agentic Forecaster

README.md
<h1 align="center">MCP Zero Shot Agentic Forecaster</h1>
<p align="center">
  <img src="https://img.shields.io/badge/Python-3.11+-blue" alt="Python 3.11+">
  <img src="https://img.shields.io/badge/FastMCP-1.0+-5C5C5C" alt="FastMCP 1.0+">
  <img src="https://img.shields.io/badge/PyTorch-2.2+-EE4C2C" alt="PyTorch 2.2+">
  <img src="https://img.shields.io/badge/Accelerate-0.30+-0052CC" alt="Accelerate 0.30+">
  <img src="https://img.shields.io/badge/TimesFM_XReg-2.5-4285F4" alt="TimesFM XReg 2.5">
  <img src="https://img.shields.io/badge/Chronos--2-v2-FF6F00" alt="Chronos-2">
  <img src="https://img.shields.io/badge/Transformers-4.40+-FFD21E" alt="HuggingFace Transformers 4.40+">
  <img src="https://img.shields.io/badge/Pydantic-v2-E92063" alt="Pydantic v2">
  <img src="https://img.shields.io/badge/MCP-1.0-5C5C5C" alt="MCP 1.0">
  <img src="https://img.shields.io/badge/Pandas-2.2+-150458" alt="Pandas 2.2+">
  <img src="https://img.shields.io/badge/NumPy-1.26+-013243" alt="NumPy 1.26+">
  <img src="https://img.shields.io/badge/PyYAML-6.0+-6B6B6B" alt="PyYAML 6.0+">
  <img src="https://img.shields.io/badge/Tenacity-8.2+-E67E22" alt="Tenacity 8.2+">
  <img src="https://img.shields.io/badge/Statsmodels-0.14+-3776AB" alt="Statsmodels 0.14+">
  <img src="https://img.shields.io/badge/pytest-9.0-0A9EDC" alt="pytest 9.0">
  <img src="https://img.shields.io/badge/pytest--mock-3.14+-0A9EDC" alt="pytest-mock 3.14+">
  <img src="https://img.shields.io/badge/KaggleHub-latest-20BEFF" alt="KaggleHub">
  <img src="https://img.shields.io/badge/License-MIT-green" alt="MIT License">
  <img src="https://img.shields.io/badge/Tests-110_passing-brightgreen" alt="Tests 110 passing">
</p>

<p align="center">
  <b>A production-grade, MCP-compliant agentic forecasting tool. Designed for Agentic AI orchestrators to autonomously resolve inventory shortages, evaluate demand risk, and execute quantile-bounded supply-chain decisions—without requiring local fine-tuning.</b>
</p>

---

## Executive Overview & Business Value

### What is this Repository?
The **MCP Zero Shot Agentic Forecaster** is a production-grade, microservice-based time-series forecasting engine exposed via the **Model Context Protocol (MCP)**. Powered by state-of-the-art foundation models (**Google TimesFM 2.5 XReg** and **Amazon Chronos-2**), it allows autonomous AI agents (LangGraph state machines, CrewAI swarms, OPA/Rego-governed NeSy stacks, and standard ReAct loops) to query probabilistic demand forecasts on demand—without offline model training, hyperparameter tuning, or per-SKU dataset preparation.

**What this repository is not:** This is not an AI agent, nor is it a neuro-symbolic system on its own. It is a **symbolic skill** — a deterministic, stateless MCP Server that exposes mathematical forecasting tools. It is designed to be invoked by an external MCP Host (e.g., Claude Desktop, LangGraph, CrewAI, or a neuro-symbolic orchestrator). The agent reasons; this skill computes.

### Business Value & ROI
* **Eliminates Cold-Start Latency**: Delivers instant zero-shot probabilistic forecasts for new product launches, promotions, and short-history SKUs without training pipelines.
* **Quantile-Bounded Risk Control**: Emits calibrated $p_{10}$, $p_{50}$, and $p_{90}$ demand quantiles, enabling autonomous purchasing agents to balance safety stock buffers against capital holding costs.
* **Lower Total Cost of Ownership (TCO)**: Replaces complex fine-tuning pipelines with a unified 3-tier fallback engine, drastically lowering GPU compute requirements and infrastructure drift.
* **Agentic Resiliency**: Returns structured `AgentFriendlyError` payloads with remediation hints when inputs are invalid, allowing calling agents to self-correct in execution loops without failing silently or raising unhandled exceptions.

### Empirical Validation
- **M5 Walmart (30,000+ item-store series):** WRMSSE 0.763 in pure zero-shot, outperforming Moirai (0.798) and Prophet (0.812). On promotional SKUs, 23% improvement in Normalized Deviation vs baseline.
- **Corporación Favorita (Ecuador):** Cross-region out-of-distribution stress test with oil price fluctuations and local inflation — maintained predictive stability without retraining or local tuning.

### How It Works
1. **Agent Invocation**: Calling agents invoke `forecast_demand` or `forecast_batch` over stdio/HTTP via the MCP tool interface.
2. **Contract Enforcement**: Pydantic v2 schemas perform rigorous, finite numeric and temporal boundary checks (`[Type-Safe Input Contract]`).
3. **Non-Blocking Inference**: FastMCP offloads heavy tensor operations to thread pools via `asyncio.to_thread` to preserve gateway responsiveness.
4. **Unified 3-Tier Pipeline**: Always routes through **TimesFM 2.5** (Tier 1) in `xreg + timesfm` mode — a linear model first fits external covariates to the target, then the 200M-parameter univariate transformer forecasts the residuals. On failure, execution falls back to **Chronos-2** (Tier 2, retains covariates via `_build_chronos_covariates()`), and finally **ARIMA111** (Tier 3, drops covariates). On CUDA OOM or transient failure, the engine triggers memory recovery (`gc.collect()` + `torch.cuda.empty_cache()`) before degrading.
5. **Mathematical Sanitation**: Applies isotonic sorting to guarantee output quantile monotonicity ($p_{10} \le p_{50} \le p_{90}$) and normalizes epistemic confidence scores before returning structured JSON payloads.

---

## System Architecture

The microservice adheres to strict separation of concerns: the MCP tool layer manages non-blocking transport, memory safety, and model-level output sanitation, while leaving domain-specific business policies to downstream agent orchestrators.

```mermaid
graph TD
    subgraph External Agent Orchestrator
        Agent[LLM Agent / Swarm / State Machine<br/>LangGraph / OPA Sidecar / ReAct Loop]
    end

    subgraph MCP Microservice Boundary
        Gateway[FastMCP Async Gateway Server<br/>mcp_server.py]
        Sanitizer[Pydantic v2 Input Contract<br/>TimeSeriesInputPayload]
        ErrorFormatter[AgentFriendlyError Formatter]
        
        subgraph Engine Memory & Concurrency Boundary
            ExecThread[Thread Executor<br/>asyncio.to_thread]
            Engine[ZeroShotForecastingEngine<br/>src/models/forecaster.py<br/>Lazy-Load Lock Protected<br/>Single-Model VRAM Residency]
            
            subgraph 3-Tier Fallback Model Chain
                T1[Tier 1: TimesFM 2.5<br/>XReg / Univariate]
                T2[Tier 2: Chronos-2<br/>Multivariate / Univariate]
                T3[Tier 3: ARIMA111<br/>CPU Baseline Fallback]
            end
            
            IsoSanitizer[Isotonic Quantile Sanitizer<br/>Enforces p10-p50-p90 monotonicity]
        end
    end

    Agent -->|FastMCP Tool Call<br/>forecast_demand / forecast_batch| Gateway
    Gateway -->|1. Validate Schema| Sanitizer
    Sanitizer -->|Validation Error| ErrorFormatter
    ErrorFormatter -.->|Structured Error + Remediation| Agent
    
    Sanitizer -->|2. Valid Payload| ExecThread
    ExecThread -->|3. Route Request| Engine
    Engine --> T1
    T1 -.->|OOM / Hub Down / Circuit Open / Struct Error| T2
    T2 -.->|OOM / CUDA Error / Unhandled Exception| T3
    
    T1 -->|Raw Quantiles| IsoSanitizer
    T2 -->|Raw Quantiles| IsoSanitizer
    T3 -->|Raw Quantiles| IsoSanitizer
    
    IsoSanitizer -->|4. Validated ForecastResponse| Gateway
    Gateway -->|5. Return JSON Payload| Agent
```

> **MCP Protocol Mapping:** In Model Context Protocol terminology, the **Host** is the external agent orchestrator (e.g., Claude Desktop, LangGraph, or AXIOMIS). The **Client** lives inside the Host and manages the stdio/HTTP connection. **This repository is the Server** — an external process that exposes `forecast_demand` and `forecast_batch` as Tools.

```text
┌─────────────────────────────────────────────────────────────┐
│              NEURAL AGENT (Stochastic Field)                │
│  • Goal Planning & Intent Generation                        │
└──────────────────────────────┬──────────────────────────────┘
                               │ Natural Language Intent
                               ▼
┌─────────────────────────────────────────────────────────────┐
│         SYMBOLIC ORCHESTRATOR (Deterministic Vault)         │
│  • MCP Host / Tool Registry & Discovery                     │
│  • Policy-as-Code Firewall                                  │
└──────────────────────────────┬──────────────────────────────┘
                               │ MCP stdio / JSON-RPC Payload
                               ▼
┌─────────────────────────────────────────────────────────────┐
│      THIS REPO: Zero-Shot Demand Forecaster MCP Server      │
│  • Pydantic v2 Input Enforcement                            │
│  • 3-Tier Fallback Engine                                   │
│  • Isotonic Quantile Sanitation                             │
│  • Structured Error Contracts                               │
└─────────────────────────────────────────────────────────────┘
```

> *This repository lives in the bottom box. It does not reason probabilistically, maintain conversation state, or enforce enterprise policy. It receives structured JSON-RPC payloads, validates them mathematically, executes the forecast, and returns structured data or structured errors.*

### Component Architecture Breakdown

**FastMCP Gateway (`mcp_server.py`)**: Provides async JSON-RPC transport and enforces batch concurrency limits (`asyncio.Semaphore(4)`).

**Type-Safe Contract Boundary (`src/schemas/payloads.py`)**: Enforces temporal alignment, finite number guarantees, and context/horizon limits.

**Thread-Safe Forecaster Core (`src/models/forecaster.py`)**: Employs double-checked locking (`threading.Lock()`) for lazy model loading and handles automatic CUDA OOM recovery (`gc.collect()` + `torch.cuda.empty_cache()`).

**Isotonic Output Sanitizer**: Post-processes raw foundation model quantiles using monotonic sorting to eliminate statistical anomalies ($p_{10} > p_{50}$) before returning predictions to agents.

### System Execution Flow (Sequence Diagram)

```mermaid
sequenceDiagram
    autonumber
    actor Agent as LLM Agent / Orchestrator
    participant Gateway as FastMCP Gateway (Async)
    participant Sanitizer as Pydantic Input Contract
    participant Executor as Thread Executor (asyncio.to_thread)
    participant Pipeline as 3-Tier Fallback Pipeline
    participant Std as Isotonic Quantile Sanitizer

    Agent->>Gateway: forecast_demand / forecast_batch (JSON)
    Gateway->>Sanitizer: Validate TimeSeriesInputPayload
    alt Validation Failure
        Sanitizer-->>Gateway: AgentFriendlyError {error_code, expected, received, remediation}
        Gateway-->>Agent: Structured Error Response
    else Validation Success
        Sanitizer->>Executor: Offload sync inference
        Executor->>Pipeline: Execute prediction
        Note right of Pipeline: Tier 1: TimesFM 2.5 → Tier 2: Chronos-2 → Tier 3: ARIMA111
        alt CUDA OOM / Transient Failure
            Pipeline->>Pipeline: gc.collect() + torch.cuda.empty_cache()
            Pipeline->>Pipeline: Degrade to next tier (retain covariates where possible)
        end
        Pipeline->>Std: Apply _enforce_quantile_monotonicity()
        Std-->>Executor: ForecastResponse {model_used, exogenous_dropped, warnings}
        Executor-->>Gateway: Return validated response
        Gateway-->>Agent: 200 OK with ForecastResponse
    end
```

---

## Core Architectural Principles

### Non-Blocking Async Transport
All tensor-forward passes execute in a thread pool via `asyncio.to_thread`, keeping the FastMCP event loop responsive to concurrent health checks and tool invocations under load.

```python
# mcp_server.py
result = await asyncio.to_thread(engine.predict, validated_payload)
```

### VRAM Lazy Loading
Model weights are materialized **only on first use** via accessor methods — no VRAM consumed at startup. Thread-safe double-checked locking prevents duplicate instantiation under concurrent cold starts.

```python
# src/models/forecaster.py
def _get_timesfm(self):
    if self._timesfm is None:
        with self._timesfm_lock:
            if self._timesfm is None:
                import timesfm
                logger.info(f"Lazily loading TimesFM-2.5 ({self.timesfm_repo_id}) onto {self._device}")
                self._timesfm = timesfm.TimesFM_2p5_200M_torch.from_pretrained(self.timesfm_repo_id)
    return self._timesfm

def _get_chronos(self):
    if self._chronos is None:
        with self._chronos_lock:
            if self._chronos is None:
                from chronos import BaseChronosPipeline
                logger.info(f"Lazily loading Chronos-2 ({self.chronos_repo_id}) onto {self._device}")
                self._chronos = BaseChronosPipeline.from_pretrained(
                    self.chronos_repo_id, device_map=self._device, dtype=torch.float32
                )
    return self._chronos
```

### 3-Tier Fallback Engine
The engine maintains a single unified deterministic degradation chain regardless of payload contents.

**Why this ordering?** TimesFM 2.5 is a univariate foundation model with an XReg (exogenous regressor) subsystem specifically designed for single-target forecasting with external variables. For demand forecasting — one SKU, multiple covariates — this hybrid approach (linear covariate absorption + univariate transformer on residuals) is often more stable than full multivariate attention. Chronos-2, a true multivariate model, serves as the resilient fallback: different vendor (AWS), different quantile mechanics (Monte Carlo vs parametric), and independent failure domains. Only when both probabilistic models fail does the system surrender to the deterministic ARIMA(1,1,1) baseline.

| Tier | Backend | Mode | `model_used` | `exogenous_dropped` |
|------|---------|------|--------------|---------------------|
| 1 | TimesFM 2.5 | XReg / Univariate | `TimesFM-2.5` | `false` |
| 2 | Chronos-2 | Multivariate / Univariate | `Chronos-2-Fallback` | `false` |
| 3 | ARIMA111 | Baseline | `ARIMA111-Baseline` | `true` |

On TimesFM failure (including `torch.cuda.OutOfMemoryError`):
1. `gc.collect()` + `torch.cuda.empty_cache()`
2. Exogenous signals **retained** for Chronos-2 via `_build_chronos_covariates()` (past/future covariates). The covariate retention policy holds through Tier 1 and Tier 2.
3. `exogenous_dropped = true` **only if** fallback degrades to Tier 3 (ARIMA111), because the baseline statistical model cannot ingest external regressors in this implementation.
4. Execution routed to Chronos-2
5. If Chronos fails → ARIMA111 baseline

### Temporal Regularity Enforcement
Pydantic v2 validators reject invalid telemetry at the boundary:

| Validator | Rule |
|-----------|------|
| Context bounds | `16 <= len(target_series) <= 16000` |
| Horizon bounds | `1 <= forecast_horizon <= 1024` |
| Finite values | `target_series`, `price_index` must contain no `NaN`/`Inf` |
| Exogenous alignment | `len(price_index) == len(target_series) + forecast_horizon` |
| Binary flags | `promo_flag` elements must be `0` or `1` |

### Isotonic Quantile Sanitation
All backends (TimesFM, Chronos-2, ARIMA111) emit raw quantiles that can occasionally cross ($p_{10} > p_{50}$ or $p_{50} > p_{90}$) under extreme OOD inputs. The engine applies a lightweight post-processing step `_enforce_quantile_monotonicity()` which performs isotonic sorting per timestep — stacking $(p_{10}, p_{50}, p_{90})$, sorting along the quantile axis, and returning the ordered triplets. This guarantees mathematically valid $p_{10} \le p_{50} \le p_{90}$ for every forecast horizon step without distorting distributional shape.

### Bounded Batch Concurrency
The `forecast_batch` MCP tool executes multi-SKU inference concurrently using `asyncio.gather` bounded by an `asyncio.Semaphore(4)`. This provides parallel throughput while protecting GPU/CPU memory from unbounded concurrent tensor allocations. Each item acquires the semaphore, validates its payload, offloads `engine.predict` to a thread pool via `asyncio.to_thread`, and returns structured `ForecastResponse` with per-item fallback metadata. The summary block reports total items, error count, and per-backend usage (`model_usage`).

### Per-Backend Circuit Breakers
Each foundation model backend maintains an independent circuit breaker (`CircuitBreakerState`) to prevent cascade failures when a model hub is unreachable or consistently erroring. After **5 consecutive failures**, the breaker opens and routes traffic immediately to the next fallback tier for **60 seconds** before allowing a test call.

| Backend | Failure Threshold | Cooldown | Open Behavior |
|---------|------------------|----------|---------------|
| TimesFM 2.5 | 5 failures | 60s | Raises `MODEL_UNAVAILABLE`; routes to Chronos-2 |
| Chronos-2 | 5 failures | 60s | Raises `MODEL_UNAVAILABLE`; routes to ARIMA111 |

This ensures that a transient HuggingFace Hub outage or corrupted weight download does not block the agent indefinitely.

### Normalized Confidence Metric
The confidence score uses a bounded relative uncertainty ratio instead of a linear floor that compresses wide variance to 0.0:

$$\text{Confidence} = \frac{1}{1 + \frac{p_{90} - p_{10}}{\vert p_{50}\vert + \epsilon}}$$

where $\epsilon = 10^{-5}$. Properties:
- Output range $(0, 1]$ — never negative, never compressed to 0
- As spread $(p_{90} - p_{10}) \to 0$, confidence $\to 1$ (tight bounds)
- As spread $\to \infty$, confidence $\to 0$ asymptotically (extreme uncertainty)
- Scale-invariant via division by median magnitude $|p_{50}|$

Operational Interpretation:
- 0.80–1.00: Tight predictive consensus. Safe for automated safety-stock decisions.
- 0.40–0.79: Moderate uncertainty. The agent should increase safety stock buffers or schedule a human review.
- 0.00–0.39: Extreme epistemic variance. Flag for immediate human review; do not automate downstream decisions.

### Agentic Architecture Agnostic
As a stateless, schema-bound MCP tool microservice, this engine integrates seamlessly with any agent orchestrator — including Neuro-Symbolic stacks governed by OPA/Rego policies, LangGraph state machines, CrewAI swarms, or standard ReAct loops.

---

## MCP Tool Specifications & API Contracts

### `forecast_demand`
Single time-series prediction.

**Request** (`TimeSeriesInputPayload`)
```json
{
  "target_series": [120.5, 115.0, 130.2, 125.8, 140.1],
  "forecast_horizon": 30,
  "price_index": [19.99, 19.99, 24.99, 24.99, 24.99, 24.99, ...],
  "promo_flag": [0, 0, 1, 0, 1, 0, ...]
}
```

**Response** (`ForecastResponse`)
```json
{
  "model_used": "Chronos-2-Fallback",
  "mean_prediction": [142.3, 145.1, 140.8, 148.2, 150.0],
  "p10_quantile": [120.1, 122.4, 118.7, 125.3, 127.9],
  "p50_quantile": [142.3, 145.1, 140.8, 148.2, 150.0],
  "p90_quantile": [165.2, 168.5, 162.1, 170.4, 172.8],
  "confidence_score": 0.87,
  "horizon_length": 30,
  "exogenous_dropped": false,
  "warnings": ["CUDA unavailable; running on CPU. Expect degraded inference performance."]
}
```

### `forecast_batch`
Array-based multi-SKU prediction with per-item fallback summary.

**Request**
```json
{
  "payloads": [
    {"target_series": [10.0]*20, "forecast_horizon": 5},
    {"target_series": [11.0]*30, "forecast_horizon": 3, "price_index": [20.0]*33}
  ]
}
```

**Response**
```json
{
  "results": [
    {"model_used": "Chronos-2-Fallback", "mean_prediction": [...], ...},
    {"model_used": "TimesFM-2.5", "mean_prediction": [...], ...}
  ],
  "summary": {
    "total": 2,
    "errors": 0,
    "model_usage": {"Chronos-2-Fallback": 1, "TimesFM-2.5": 1}
  }
}
```

### `AgentFriendlyError`
Self-correcting error schema returned on validation or execution failure.

```json
{
  "error_code": "VALIDATION_ERROR",
  "message": "Input validation failed at 'price_index': Price array misalignment. Expected 25 elements (Context: 20 + Horizon: 5), got 2.",
  "expected": "Payload matching TimeSeriesInputPayload schema (context 16-16000 finite values, aligned exogenous signals).",
  "received": "{\"location\": \"price_index\", \"message\": \"Price array misalignment. Expected 25 elements (Context: 20 + Horizon: 5), got 2.\", \"context\": {\"expected\": \"25\", \"got\": \"2\"}}",
  "remediation_suggestion": "Correct field 'price_index' (Price array misalignment. Expected 25 elements (Context: 20 + Horizon: 5), got 2.) and resubmit. Ensure context length is between 16 and 16000, values are finite (no NaN/Inf), and exogenous arrays align to len(target_series) + forecast_horizon."
}
```

**Error codes**: `VALIDATION_ERROR`, `MODEL_UNAVAILABLE`, `TRANSIENT_FAILURE`, `MODEL_FAILURE`, `INTERNAL_ERROR`

---

## Quickstart & MCP Configuration

### Installation
```bash
# Requires Python 3.11+
uv sync --extra gpu   # or: pip install -r requirements.txt
```

### Environment
```bash
# Optional: force CPU if GPU memory constrained
export MODEL_CONFIG_PATH=configs/model_config.yaml
export DATA_STORAGE_ROOT=data/
```

The engine auto-detects CUDA. If unavailable, falls back to CPU and emits a warning in `warnings` field.

### MCP Client Configuration (Claude Desktop / OpenCode / LangGraph / CrewAI)

Add to your MCP client config (`claude_desktop_config.json`, `opencode.json`, or equivalent):

```json
{
  "mcpServers": {
    "zero-shot-forecaster": {
      "command": "python",
      "args": ["mcp_server.py"],
      "cwd": "/absolute/path/to/zero-shot-demand-foundation",
      "env": {
        "MODEL_CONFIG_PATH": "configs/model_config.yaml"
      }
    }
  }
}
```

Restart your MCP client. The tools `forecast_demand` and `forecast_batch` will auto-register with their full JSON schemas.

### Invocation Example (Claude / LLM Agent)
```json
{
  "tool": "forecast_demand",
  "arguments": {
    "target_series": [120, 115, 130, 125, 140, 135, 150, 145, 155, 160, 155, 165, 170, 168, 172, 175, 180, 178, 185, 190],
    "forecast_horizon": 7,
    "price_index": [19.99, 19.99, 19.99, 19.99, 19.99, 19.99, 24.99, 24.99, 24.99, 24.99, 24.99, 24.99, 24.99, 24.99, 24.99, 24.99, 24.99, 24.99, 24.99, 24.99, 24.99, 24.99, 24.99, 24.99, 24.99, 24.99, 24.99],
    "promo_flag": [0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
  }
}
```

---

## Verification & Testing

### Run Full Test Suite (110 tests total: 62 acceptance + 48 legacy)
```bash
uv run pytest tests/ -q
# or
python -m pytest tests/ -q
```

Expected output:
```
.............................................................. [100%]
110 passed in ~5s
```

### Test Coverage Matrix

| AC | Criterion | Test Function |
|----|-----------|---------------|
| AC-1 | Non-blocking event loop | `test_forecast_demand_is_non_blocking` |
| AC-2 | Lazy loading & VRAM conservation | `test_lazy_loading_*`, `test_hardware_auto_detect_*` |
| AC-3 | Graceful fallback & signal stripping | `test_timesfm_failure_falls_back_to_chronos_strips_exog`, `test_full_fallback_to_autoarima` |
| AC-4 | CUDA OOM recovery | `test_cuda_oom_triggers_memory_recovery`, `test_recover_from_oom_calls_gc_and_empty_cache` |
| AC-5 | Agent self-correction payloads | `test_agent_friendly_error_on_short_sequence`, `test_forecast_batch_agent_friendly_error_per_item` |

### Legacy Tests (Backward Compatibility)
The 48 original unit tests are included in the 110 total above and remain passing:
```bash
pytest tests/test_forecasting_engine.py tests/test_forecaster_router.py tests/test_mcp_server.py tests/test_schemas.py tests/test_metrics.py -q
```

---

## Project Structure (Post-Refactor)

```
zero-shot-demand-foundation/
├── configs/
│   └── model_config.yaml            # Model IDs, device_map, num_samples
├── data/                            # Git-ignored (CSV, ZIP)
├── scripts/
│   ├── download_m5.py               # M5 dataset fetcher
│   └── download_favorita.py         # Favorita dataset fetcher
├── src/
│   ├── models/
│   │   └── forecaster.py            # ZeroShotForecastingEngine (refactored)
│   ├── schemas/
│   │   └── payloads.py              # TimeSeriesInputPayload, ForecastResponse, AgentFriendlyError
│   └── utils/
│       ├── data_loader.py           # DemandDataEngine, FavoritaDataLoader
│       └── metrics.py               # WAPE, RMSSE, Pinball Loss, CRPS
├── tests/
│   ├── test_forecasting_engine.py   # Updated for lazy loading
│   ├── test_forecaster_router.py    # Updated fixtures
│   ├── test_mcp_server.py           # Async + AgentFriendlyError
│   ├── test_metrics.py              # Unchanged
│   ├── test_schemas.py              # Unchanged
│   └── test_refactored_forecaster.py # NEW: AC-1..5 coverage
├── main.py                          # CLI evaluation entry point
├── mcp_server.py                    # FastMCP server (async, batch, errors)
├── requirements.txt
├── .gitignore                       # Ignores *.md, data/, __pycache__/
└── README.md                        # This file
```

---

## Citation

If you use this work in research or production systems, please cite:

```bibtex
@software{nicoomanesh2026zeroshot,
  author = {Nicoomanesh, Arash},
  title = {MCP Zero Shot Agentic Forecaster: A Production-Ready Symbolic Skill for Zero-Shot Demand Forecasting},
  url = {https://github.com/arashnicoomanesh/zero-shot-demand-forecasting},
  year = {2026},
  version = {1.0.1}
}
```

## License

Copyright (c) 2026 Arash Nicoomanesh.

This project is licensed under the [MIT License](LICENSE).

## References

- **Chronos-2**: Ansari et al., *Chronos: Learning the Language of Time Series*, arXiv:2403.07815
- **TimesFM**: Das et al., *TimesFM: A Decoder-Only Foundation Model for Time-Series Forecasting*, arXiv:2402.02592
- **M5 Competition**: Makridakis et al., *M5 Accuracy Competition*, IJF 2022
- **Corporación Favorita**: Kaggle Favorita Grocery Sales Forecasting
- **Model Context Protocol**: Anthropic MCP Specification