"""
Product clients for Snowtrail Research API.

Each product has signals, features, events, and backtest endpoints.
"""
from __future__ import annotations

from datetime import date
from typing import TYPE_CHECKING, Any

import pandas as pd

if TYPE_CHECKING:
    from snowtrail._client import HTTPClient


class BaseProduct:
    """
    Base class for product-specific API clients.

    Each product (gbsi_us, gbsi_eu, etc.) extends this class
    and adds its specific endpoints.
    """

    def __init__(self, client: HTTPClient, product_id: str):
        self._client = client
        self._product_id = product_id

    def _get(
        self,
        endpoint: str,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        as_of: date | str | None = None,
        cursor: str | None = None,
        **filters: Any,
    ) -> pd.DataFrame:
        """
        Generic endpoint fetcher with DataFrame conversion.

        Args:
            endpoint: Endpoint name (e.g., "system_stress")
            latest: Return only most recent record (default: True)
            date_from: Filter from date (overrides latest)
            date_to: Filter to date
            limit: Maximum rows to return
            as_of: Point-in-time date (YYYY-MM-DD). Only returns data
                   published on or before this date. Enables backtest-safe queries.
            cursor: Pagination cursor from previous response's next_cursor field
            **filters: Product-specific filters (geography, basin, etc.)

        Returns:
            pandas DataFrame with the response data
        """
        params: dict[str, Any] = {"latest": latest, "limit": limit}

        if date_from:
            params["date_from"] = str(date_from)
            params["latest"] = False  # Date range overrides latest
        if date_to:
            params["date_to"] = str(date_to)
        if as_of:
            params["as_of"] = str(as_of)
        if cursor:
            params["cursor"] = cursor

        # Add product-specific filters
        params.update(filters)

        path = f"/{self._product_id}/{endpoint}"
        response = self._client.get(path, params=params)

        # Handle both latest (single record) and history (list) responses
        data = response.get("data")
        if data is None:
            return pd.DataFrame()
        if isinstance(data, dict):
            # Single record from latest=True
            return pd.DataFrame([data])
        return pd.DataFrame(data)

    def _get_raw(
        self,
        endpoint: str,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        as_of: date | str | None = None,
        cursor: str | None = None,
        **filters: Any,
    ) -> dict[str, Any]:
        """
        Generic endpoint fetcher returning raw dict response.

        Same as _get() but returns the full API response dict
        including metadata, has_more, next_cursor, etc.
        """
        params: dict[str, Any] = {"latest": latest, "limit": limit}

        if date_from:
            params["date_from"] = str(date_from)
            params["latest"] = False
        if date_to:
            params["date_to"] = str(date_to)
        if as_of:
            params["as_of"] = str(as_of)
        if cursor:
            params["cursor"] = cursor

        params.update(filters)

        path = f"/{self._product_id}/{endpoint}"
        return self._client.get(path, params=params)


# =============================================================================
# GBSI-US: US Natural Gas Balance Stress Index
# =============================================================================


class GbsiUs(BaseProduct):
    """
    GBSI-US: US Natural Gas Balance Stress Index.

    Weekly signals and features based on EIA storage data,
    production trends, and demand pressure metrics.
    """

    def __init__(self, client: HTTPClient):
        super().__init__(client, "gbsi_us")

    # --- Signals ---

    def system_stress(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
    ) -> pd.DataFrame:
        """
        Composite system stress signal (1-5 scale).

        Combines storage, momentum, supply elasticity, and demand pressure.
        Regime 1 = bullish stress, Regime 5 = bearish oversupply.
        """
        return self._get("system_stress", latest=latest, date_from=date_from, date_to=date_to, limit=limit)

    def price_context(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
    ) -> pd.DataFrame:
        """
        Price context signal with Henry Hub price levels and momentum.

        Prevents chasing moves that are already priced in.
        """
        return self._get("price_context", latest=latest, date_from=date_from, date_to=date_to, limit=limit)

    def positioning(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
    ) -> pd.DataFrame:
        """
        CFTC-based positioning signal with crowding indicators.

        Flags when consensus trades become vulnerable to unwinds.
        """
        return self._get("positioning", latest=latest, date_from=date_from, date_to=date_to, limit=limit)

    # --- Features ---

    def balance_momentum(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
    ) -> pd.DataFrame:
        """
        Week-over-week storage balance changes vs seasonal norms.

        Tracks injection/withdrawal pace relative to 5-year averages.
        """
        return self._get("balance_momentum", latest=latest, date_from=date_from, date_to=date_to, limit=limit)

    def storage_inventory(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
    ) -> pd.DataFrame:
        """
        Current storage levels vs 5-year min/max/avg benchmarks.

        Includes deficit/surplus calculations and percentile rankings.
        """
        return self._get("storage_inventory", latest=latest, date_from=date_from, date_to=date_to, limit=limit)

    def supply_elasticity(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
    ) -> pd.DataFrame:
        """
        Supply-side responsiveness metrics.

        Includes dry gas production trends, LNG feedgas demand,
        and pipeline export flows to Mexico.
        """
        return self._get("supply_elasticity", latest=latest, date_from=date_from, date_to=date_to, limit=limit)

    def features(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
    ) -> pd.DataFrame:
        """
        Complete feature matrix with all component z-scores.

        Includes storage_score, momentum_score, supply_score, demand_score.
        """
        return self._get("features", latest=latest, date_from=date_from, date_to=date_to, limit=limit)

    def price_risk(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
    ) -> pd.DataFrame:
        """
        Henry Hub price levels, momentum, and percentile rankings.

        Includes rolling returns, volatility metrics, and mean reversion z-scores.
        """
        return self._get("price_risk", latest=latest, date_from=date_from, date_to=date_to, limit=limit)

    def cot_positioning(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
    ) -> pd.DataFrame:
        """
        CFTC Commitments of Traders positioning metrics.

        Net speculator length, long/short ratios, and concentration indices.
        """
        return self._get("cot_positioning", latest=latest, date_from=date_from, date_to=date_to, limit=limit)

    def regional_dispersion(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
    ) -> pd.DataFrame:
        """
        Regional price dispersion across US natural gas hubs.

        Basis differentials, spread volatility, and convergence indicators.
        """
        return self._get("regional_dispersion", latest=latest, date_from=date_from, date_to=date_to, limit=limit)

    def demand_pressure(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
    ) -> pd.DataFrame:
        """
        Demand pressure indicators relative to seasonal norms.

        Includes residential/commercial consumption and power burn estimates.
        """
        return self._get("demand_pressure", latest=latest, date_from=date_from, date_to=date_to, limit=limit)

    # --- Events ---

    def storage_surprise(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
    ) -> pd.DataFrame:
        """
        EIA weekly storage report surprises.

        Compares actual injection/withdrawal to Bloomberg consensus.
        Triggers on >2 Bcf deviation.
        """
        return self._get("storage_surprise", latest=latest, date_from=date_from, date_to=date_to, limit=limit)

    def regime_shift(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
    ) -> pd.DataFrame:
        """
        GBSI regime transitions between stress levels.

        Captures shifts from bullish to bearish conditions or vice versa.
        """
        return self._get("regime_shift", latest=latest, date_from=date_from, date_to=date_to, limit=limit)

    def momentum_inflection(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
    ) -> pd.DataFrame:
        """
        Balance momentum turning points.

        Early warning signal for regime shifts.
        """
        return self._get("momentum_inflection", latest=latest, date_from=date_from, date_to=date_to, limit=limit)

    def balance_stress_threshold(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
    ) -> pd.DataFrame:
        """Threshold breach events when storage crosses critical boundaries."""
        return self._get(
            "balance_stress_threshold", latest=latest, date_from=date_from, date_to=date_to, limit=limit
        )

    def forecast_shock(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
    ) -> pd.DataFrame:
        """HDD/CDD forecast shock events when weather models shift significantly."""
        return self._get("forecast_shock", latest=latest, date_from=date_from, date_to=date_to, limit=limit)

    # --- Backtests ---

    def backtest_summary(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
    ) -> pd.DataFrame:
        """
        Pre-computed backtest summary for GBSI-US system stress signal.

        Includes regime persistence, flip-flop rate, volatility multiplier,
        lift metrics, and statistical significance.
        """
        return self._get("backtest_summary", latest=latest, date_from=date_from, date_to=date_to, limit=limit)

    def backtest_outcomes(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
    ) -> pd.DataFrame:
        """
        Per-regime outcome metrics from GBSI-US backtest.

        Forward realized volatility, vol_multiplier, tail_lift,
        and regime persistence statistics with unconditional baseline.
        """
        return self._get("backtest_outcomes", latest=latest, date_from=date_from, date_to=date_to, limit=limit)

    def backtest_transitions(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
    ) -> pd.DataFrame:
        """
        Regime transition matrix from GBSI-US backtest.

        Each row is a (from_regime, to_regime) pair with transition probability.
        """
        return self._get("backtest_transitions", latest=latest, date_from=date_from, date_to=date_to, limit=limit)


# =============================================================================
# GBSI-EU: EU Natural Gas Balance Stress Index
# =============================================================================


class GbsiEu(BaseProduct):
    """
    GBSI-EU: EU Natural Gas Balance Stress Index.

    Daily signals and features based on GIE AGSI+ storage data
    across major EU countries.
    """

    def __init__(self, client: HTTPClient):
        super().__init__(client, "gbsi_eu")

    # --- Signals ---

    def system_stress(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        country: str | None = None,
    ) -> pd.DataFrame:
        """
        Daily EU-wide system stress signal (1-5 scale).

        Updated after daily GIE publication (~18:00 CET).

        Args:
            country: Filter by country code (e.g., 'DE', 'FR', 'NL')
        """
        return self._get(
            "system_stress", latest=latest, date_from=date_from, date_to=date_to, limit=limit, country=country
        )

    def composite(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        country: str | None = None,
    ) -> pd.DataFrame:
        """
        Weighted composite signal across major EU storage countries.

        Combines storage stress, momentum, and supply metrics.
        """
        return self._get(
            "composite", latest=latest, date_from=date_from, date_to=date_to, limit=limit, country=country
        )

    def dispersion(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        country: str | None = None,
    ) -> pd.DataFrame:
        """
        Cross-country stress dispersion measuring regional divergence.

        High dispersion indicates arbitrage opportunities.
        """
        return self._get(
            "dispersion", latest=latest, date_from=date_from, date_to=date_to, limit=limit, country=country
        )

    def price_context(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        country: str | None = None,
    ) -> pd.DataFrame:
        """
        EU price context signal with TTF price levels and momentum.

        Assesses whether price has already moved relative to fundamentals.
        """
        return self._get(
            "price_context", latest=latest, date_from=date_from, date_to=date_to, limit=limit, country=country
        )

    # --- Features ---

    def balance_momentum(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        country: str | None = None,
    ) -> pd.DataFrame:
        """Daily injection/withdrawal momentum vs seasonal norms."""
        return self._get(
            "balance_momentum", latest=latest, date_from=date_from, date_to=date_to, limit=limit, country=country
        )

    def storage_inventory(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        country: str | None = None,
    ) -> pd.DataFrame:
        """EU aggregate and country-level storage fill rates."""
        return self._get(
            "storage_inventory", latest=latest, date_from=date_from, date_to=date_to, limit=limit, country=country
        )

    def supply_elasticity(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        country: str | None = None,
    ) -> pd.DataFrame:
        """Supply flexibility metrics including LNG and Norwegian flows."""
        return self._get(
            "supply_elasticity", latest=latest, date_from=date_from, date_to=date_to, limit=limit, country=country
        )

    def features(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        country: str | None = None,
    ) -> pd.DataFrame:
        """Complete EU feature matrix with country-level breakdowns."""
        return self._get(
            "features", latest=latest, date_from=date_from, date_to=date_to, limit=limit, country=country
        )

    def price_context_features(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        country: str | None = None,
    ) -> pd.DataFrame:
        """EU price context feature data including TTF spot/forward prices."""
        return self._get(
            "price_context_features", latest=latest, date_from=date_from, date_to=date_to, limit=limit, country=country
        )

    # --- Events ---

    def storage_surprise(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        country: str | None = None,
    ) -> pd.DataFrame:
        """GIE storage report surprises vs seasonal expectations."""
        return self._get(
            "storage_surprise", latest=latest, date_from=date_from, date_to=date_to, limit=limit, country=country
        )

    def regime_shift(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        country: str | None = None,
    ) -> pd.DataFrame:
        """GBSI-EU regime transitions between stress levels."""
        return self._get(
            "regime_shift", latest=latest, date_from=date_from, date_to=date_to, limit=limit, country=country
        )

    def momentum_inflection(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        country: str | None = None,
    ) -> pd.DataFrame:
        """EU balance momentum turning points."""
        return self._get(
            "momentum_inflection", latest=latest, date_from=date_from, date_to=date_to, limit=limit, country=country
        )

    def weather_demand_shock(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        country: str | None = None,
    ) -> pd.DataFrame:
        """Weather-driven demand shock events in EU markets."""
        return self._get(
            "weather_demand_shock", latest=latest, date_from=date_from, date_to=date_to, limit=limit, country=country
        )

    def stress_threshold(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        country: str | None = None,
    ) -> pd.DataFrame:
        """Threshold breach events when EU storage stress crosses critical boundaries."""
        return self._get(
            "stress_threshold", latest=latest, date_from=date_from, date_to=date_to, limit=limit, country=country
        )

    def dispersion_spike(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        country: str | None = None,
    ) -> pd.DataFrame:
        """Cross-country dispersion spike events."""
        return self._get(
            "dispersion_spike", latest=latest, date_from=date_from, date_to=date_to, limit=limit, country=country
        )

    def contagion_threshold(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        country: str | None = None,
    ) -> pd.DataFrame:
        """Contagion threshold events when stress spreads between EU countries."""
        return self._get(
            "contagion_threshold", latest=latest, date_from=date_from, date_to=date_to, limit=limit, country=country
        )

    def lng_constraint(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        country: str | None = None,
    ) -> pd.DataFrame:
        """LNG import constraint alerts when regasification utilization is critical."""
        return self._get(
            "lng_constraint", latest=latest, date_from=date_from, date_to=date_to, limit=limit, country=country
        )

    def core_periphery_divergence(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        country: str | None = None,
    ) -> pd.DataFrame:
        """Core-periphery divergence events between major and smaller EU countries."""
        return self._get(
            "core_periphery_divergence", latest=latest, date_from=date_from, date_to=date_to, limit=limit, country=country
        )

    # --- Backtests ---

    def backtest_summary(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        country: str | None = None,
    ) -> pd.DataFrame:
        """Pre-computed backtest summary for GBSI-EU system stress signal."""
        return self._get(
            "backtest_summary", latest=latest, date_from=date_from, date_to=date_to, limit=limit, country=country
        )

    def backtest_outcomes(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        country: str | None = None,
    ) -> pd.DataFrame:
        """Per-regime outcome metrics from GBSI-EU backtest."""
        return self._get(
            "backtest_outcomes", latest=latest, date_from=date_from, date_to=date_to, limit=limit, country=country
        )

    def backtest_transitions(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        country: str | None = None,
    ) -> pd.DataFrame:
        """Regime transition matrix from GBSI-EU backtest."""
        return self._get(
            "backtest_transitions", latest=latest, date_from=date_from, date_to=date_to, limit=limit, country=country
        )


# =============================================================================
# PEMI: Power Event Market Intelligence
# =============================================================================


class Pemi(BaseProduct):
    """
    PEMI: Power Event Market Intelligence.

    EU power market stress signals from Nord Pool UMM outage data.
    """

    def __init__(self, client: HTTPClient):
        super().__init__(client, "pemi")

    # --- Signals ---

    def grid_stress(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        bidding_zone: str | None = None,
    ) -> pd.DataFrame:
        """
        Primary grid stress signal combining critical infrastructure risk.

        Includes nuclear offline, supply pressure, and persistent grid stress.

        Args:
            bidding_zone: Filter by bidding zone
        """
        return self._get(
            "grid_stress",
            latest=latest,
            date_from=date_from,
            date_to=date_to,
            limit=limit,
            bidding_zone=bidding_zone,
        )

    def event_escalation(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        bidding_zone: str | None = None,
    ) -> pd.DataFrame:
        """
        Event escalation context signal with market tightness.

        Includes surprise event clusters and unplanned outages.
        """
        return self._get(
            "event_escalation",
            latest=latest,
            date_from=date_from,
            date_to=date_to,
            limit=limit,
            bidding_zone=bidding_zone,
        )

    def weather_amplification(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        bidding_zone: str | None = None,
    ) -> pd.DataFrame:
        """
        Weather amplification conviction signal (cross-product with WRSI).

        Flags when weather volatility regime amplifies grid stress.
        """
        return self._get(
            "weather_amplification",
            latest=latest,
            date_from=date_from,
            date_to=date_to,
            limit=limit,
            bidding_zone=bidding_zone,
        )

    # --- Features ---

    def features(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        bidding_zone: str | None = None,
    ) -> pd.DataFrame:
        """
        Outage event features including capacity offline (MW).

        Includes duration estimates and bidding zone concentration.
        """
        return self._get(
            "features",
            latest=latest,
            date_from=date_from,
            date_to=date_to,
            limit=limit,
            bidding_zone=bidding_zone,
        )

    def price_impact(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        bidding_zone: str | None = None,
    ) -> pd.DataFrame:
        """
        Price impact features measuring outage effects on power prices.

        Includes pre/post-event price changes and volatility response.
        """
        return self._get(
            "price_impact",
            latest=latest,
            date_from=date_from,
            date_to=date_to,
            limit=limit,
            bidding_zone=bidding_zone,
        )

    def gas_substitution(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        bidding_zone: str | None = None,
    ) -> pd.DataFrame:
        """
        Gas-for-power substitution features during nuclear outages.

        Includes gas burn displacement and merit order shift metrics.
        """
        return self._get(
            "gas_substitution",
            latest=latest,
            date_from=date_from,
            date_to=date_to,
            limit=limit,
            bidding_zone=bidding_zone,
        )

    # --- Events ---

    def critical_infra_alert(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        bidding_zone: str | None = None,
    ) -> pd.DataFrame:
        """Critical infrastructure alert when major generation units go offline."""
        return self._get(
            "critical_infra_alert",
            latest=latest,
            date_from=date_from,
            date_to=date_to,
            limit=limit,
            bidding_zone=bidding_zone,
        )

    def market_tightness_spike(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        bidding_zone: str | None = None,
    ) -> pd.DataFrame:
        """Market tightness spike when outage-driven supply loss meets elevated demand."""
        return self._get(
            "market_tightness_spike",
            latest=latest,
            date_from=date_from,
            date_to=date_to,
            limit=limit,
            bidding_zone=bidding_zone,
        )

    def surprise_cluster(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        bidding_zone: str | None = None,
    ) -> pd.DataFrame:
        """Surprise cluster events when multiple unplanned outages occur in succession."""
        return self._get(
            "surprise_cluster",
            latest=latest,
            date_from=date_from,
            date_to=date_to,
            limit=limit,
            bidding_zone=bidding_zone,
        )

    def persistent_stress(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        bidding_zone: str | None = None,
    ) -> pd.DataFrame:
        """Persistent stress events when grid stress remains elevated beyond typical durations."""
        return self._get(
            "persistent_stress",
            latest=latest,
            date_from=date_from,
            date_to=date_to,
            limit=limit,
            bidding_zone=bidding_zone,
        )

    def weather_amplified(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        bidding_zone: str | None = None,
    ) -> pd.DataFrame:
        """Weather-amplified events when temperature extremes compound grid stress."""
        return self._get(
            "weather_amplified",
            latest=latest,
            date_from=date_from,
            date_to=date_to,
            limit=limit,
            bidding_zone=bidding_zone,
        )

    # --- Backtests ---

    def backtest_summary(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        bidding_zone: str | None = None,
    ) -> pd.DataFrame:
        """Pre-computed backtest summary for PEMI grid stress signal."""
        return self._get(
            "backtest_summary",
            latest=latest,
            date_from=date_from,
            date_to=date_to,
            limit=limit,
            bidding_zone=bidding_zone,
        )

    def backtest_outcomes(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        bidding_zone: str | None = None,
    ) -> pd.DataFrame:
        """Per-regime outcome metrics from PEMI backtest."""
        return self._get(
            "backtest_outcomes",
            latest=latest,
            date_from=date_from,
            date_to=date_to,
            limit=limit,
            bidding_zone=bidding_zone,
        )

    def backtest_transitions(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        bidding_zone: str | None = None,
    ) -> pd.DataFrame:
        """Regime transition matrix from PEMI backtest."""
        return self._get(
            "backtest_transitions",
            latest=latest,
            date_from=date_from,
            date_to=date_to,
            limit=limit,
            bidding_zone=bidding_zone,
        )


# =============================================================================
# GLMI: Global LNG Market Intelligence
# =============================================================================


class Glmi(BaseProduct):
    """
    GLMI: Global LNG Market Intelligence.

    LNG marginality regime indicators based on GIIGNL data.
    """

    def __init__(self, client: HTTPClient):
        super().__init__(client, "glmi")

    # --- Signals ---

    def marginality(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        basin: str | None = None,
    ) -> pd.DataFrame:
        """
        LNG marginality regime indicator.

        Identifies whether Europe or Asia sets the marginal price
        for flexible LNG cargoes.

        Args:
            basin: Filter by basin ('atlantic', 'pacific', 'middle_east')
        """
        return self._get(
            "marginality", latest=latest, date_from=date_from, date_to=date_to, limit=limit, basin=basin
        )

    def price_context(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        basin: str | None = None,
    ) -> pd.DataFrame:
        """
        LNG price context signal with JKM/TTF spread dynamics.

        Assesses whether inter-basin spreads have already moved.
        """
        return self._get(
            "price_context", latest=latest, date_from=date_from, date_to=date_to, limit=limit, basin=basin
        )

    # --- Features ---

    def liquefaction(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        basin: str | None = None,
    ) -> pd.DataFrame:
        """
        Supply-side liquefaction metrics by basin.

        Includes terminal count, capacity (MTPA/BCM), and availability.
        """
        return self._get(
            "liquefaction", latest=latest, date_from=date_from, date_to=date_to, limit=limit, basin=basin
        )

    def regasification(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        basin: str | None = None,
    ) -> pd.DataFrame:
        """
        Demand-side regasification metrics by basin.

        Includes capacity, utilization, and headroom.
        """
        return self._get(
            "regasification", latest=latest, date_from=date_from, date_to=date_to, limit=limit, basin=basin
        )

    def trade_flows(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        basin: str | None = None,
    ) -> pd.DataFrame:
        """
        Inter-basin trade flow metrics.

        Includes inbound/outbound volumes, net flow, and spot/contract ratios.
        """
        return self._get(
            "trade_flows", latest=latest, date_from=date_from, date_to=date_to, limit=limit, basin=basin
        )

    def features(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        basin: str | None = None,
    ) -> pd.DataFrame:
        """Composite GLMI feature matrix with tightness scores."""
        return self._get(
            "features", latest=latest, date_from=date_from, date_to=date_to, limit=limit, basin=basin
        )

    def price_spread(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        basin: str | None = None,
    ) -> pd.DataFrame:
        """
        Inter-basin LNG price spread features.

        Includes JKM-TTF differential, rolling spread volatility, and percentiles.
        """
        return self._get(
            "price_spread", latest=latest, date_from=date_from, date_to=date_to, limit=limit, basin=basin
        )

    # --- Events ---

    def regime_shift(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        basin: str | None = None,
    ) -> pd.DataFrame:
        """GLMI marginality regime transitions between pricing basins."""
        return self._get(
            "regime_shift", latest=latest, date_from=date_from, date_to=date_to, limit=limit, basin=basin
        )

    def price_spike_alert(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        basin: str | None = None,
    ) -> pd.DataFrame:
        """LNG price spike alerts when inter-basin spreads breach critical thresholds."""
        return self._get(
            "price_spike_alert", latest=latest, date_from=date_from, date_to=date_to, limit=limit, basin=basin
        )

    def flexibility_critical(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        basin: str | None = None,
    ) -> pd.DataFrame:
        """Flexibility critical events when global LNG supply flexibility drops below thresholds."""
        return self._get(
            "flexibility_critical", latest=latest, date_from=date_from, date_to=date_to, limit=limit, basin=basin
        )

    def spread_regime_shift(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
    ) -> pd.DataFrame:
        """JKM-TTF spread regime transitions (daily). Fires when Asian premium regime shifts."""
        return self._get(
            "spread_regime_shift", latest=latest, date_from=date_from, date_to=date_to, limit=limit
        )

    def competition_shift(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        basin: str | None = None,
    ) -> pd.DataFrame:
        """Competition intensity shifts within the current marginal basin regime."""
        return self._get(
            "competition_shift", latest=latest, date_from=date_from, date_to=date_to, limit=limit, basin=basin
        )

    # --- Backtests ---

    def backtest_summary(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        basin: str | None = None,
    ) -> pd.DataFrame:
        """Pre-computed backtest summary for GLMI marginality signal."""
        return self._get(
            "backtest_summary", latest=latest, date_from=date_from, date_to=date_to, limit=limit, basin=basin
        )

    def backtest_outcomes(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        basin: str | None = None,
    ) -> pd.DataFrame:
        """Per-regime outcome metrics from GLMI backtest."""
        return self._get(
            "backtest_outcomes", latest=latest, date_from=date_from, date_to=date_to, limit=limit, basin=basin
        )

    def backtest_transitions(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        basin: str | None = None,
    ) -> pd.DataFrame:
        """Regime transition matrix from GLMI backtest."""
        return self._get(
            "backtest_transitions", latest=latest, date_from=date_from, date_to=date_to, limit=limit, basin=basin
        )

    def backtest_combinations(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        basin: str | None = None,
    ) -> pd.DataFrame:
        """Two-signal combination results (4 basin regimes x 3 price contexts)."""
        return self._get(
            "backtest_combinations", latest=latest, date_from=date_from, date_to=date_to, limit=limit, basin=basin
        )


# =============================================================================
# WRSI: Weather Risk Signal Intelligence
# =============================================================================


class Wrsi(BaseProduct):
    """
    WRSI: Weather Risk Signal Intelligence.

    Weather forecast volatility regime signals from NOAA models.
    """

    def __init__(self, client: HTTPClient):
        super().__init__(client, "wrsi")

    # --- Signals ---

    def forecast_stress(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        geography: str | None = None,
        region_type: str | None = None,
    ) -> pd.DataFrame:
        """
        Primary weather risk signal with volatility regime and WRSI score.

        Tracks ensemble spread and run-to-run forecast changes in NOAA models.

        Args:
            geography: Filter by geography (e.g., 'US', 'ERCOT')
            region_type: Filter by region type
        """
        return self._get(
            "forecast_stress",
            latest=latest,
            date_from=date_from,
            date_to=date_to,
            limit=limit,
            geography=geography,
            region_type=region_type,
        )

    def seasonal_context(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        geography: str | None = None,
        region_type: str | None = None,
    ) -> pd.DataFrame:
        """
        Seasonal context signal and climatological benchmarks.

        Indicates whether current weather matters for energy markets.
        """
        return self._get(
            "seasonal_context",
            latest=latest,
            date_from=date_from,
            date_to=date_to,
            limit=limit,
            geography=geography,
            region_type=region_type,
        )

    def model_agreement(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        geography: str | None = None,
        region_type: str | None = None,
    ) -> pd.DataFrame:
        """
        Model agreement conviction signal measuring cross-model consensus.

        Includes agreement score and ensemble confidence metrics.
        """
        return self._get(
            "model_agreement",
            latest=latest,
            date_from=date_from,
            date_to=date_to,
            limit=limit,
            geography=geography,
            region_type=region_type,
        )

    # --- Features ---

    def forecast_dynamics(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        geography: str | None = None,
        region_type: str | None = None,
    ) -> pd.DataFrame:
        """Run-to-run forecast changes measuring outlook evolution."""
        return self._get(
            "forecast_dynamics",
            latest=latest,
            date_from=date_from,
            date_to=date_to,
            limit=limit,
            geography=geography,
            region_type=region_type,
        )

    def forecast_volatility(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        geography: str | None = None,
        region_type: str | None = None,
    ) -> pd.DataFrame:
        """GEFS ensemble spread metrics across forecast horizons."""
        return self._get(
            "forecast_volatility",
            latest=latest,
            date_from=date_from,
            date_to=date_to,
            limit=limit,
            geography=geography,
            region_type=region_type,
        )

    def seasonal_context_features(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        geography: str | None = None,
        region_type: str | None = None,
    ) -> pd.DataFrame:
        """Climatological benchmarks and seasonal demand leverage factors."""
        return self._get(
            "seasonal_context_features",
            latest=latest,
            date_from=date_from,
            date_to=date_to,
            limit=limit,
            geography=geography,
            region_type=region_type,
        )

    def features(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        geography: str | None = None,
        region_type: str | None = None,
    ) -> pd.DataFrame:
        """Complete WRSI feature matrix."""
        return self._get(
            "features",
            latest=latest,
            date_from=date_from,
            date_to=date_to,
            limit=limit,
            geography=geography,
            region_type=region_type,
        )

    def ecmwf_dynamics(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        geography: str | None = None,
        region_type: str | None = None,
    ) -> pd.DataFrame:
        """
        ECMWF ensemble forecast dynamics.

        Includes run-to-run temperature changes and forecast skill metrics.
        """
        return self._get(
            "ecmwf_dynamics",
            latest=latest,
            date_from=date_from,
            date_to=date_to,
            limit=limit,
            geography=geography,
            region_type=region_type,
        )

    def forecast_skill(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        geography: str | None = None,
        region_type: str | None = None,
    ) -> pd.DataFrame:
        """
        Forecast skill metrics comparing predictions against observed outcomes.

        Includes RMSE, bias, and skill scores by horizon and season.
        """
        return self._get(
            "forecast_skill",
            latest=latest,
            date_from=date_from,
            date_to=date_to,
            limit=limit,
            geography=geography,
            region_type=region_type,
        )

    # --- Events ---

    def regime_shift(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        geography: str | None = None,
        region_type: str | None = None,
    ) -> pd.DataFrame:
        """WRSI regime shift events when weather volatility regime transitions."""
        return self._get(
            "regime_shift",
            latest=latest,
            date_from=date_from,
            date_to=date_to,
            limit=limit,
            geography=geography,
            region_type=region_type,
        )

    def extreme_risk(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        geography: str | None = None,
        region_type: str | None = None,
    ) -> pd.DataFrame:
        """Extreme weather risk events with unusually high forecast uncertainty."""
        return self._get(
            "extreme_risk",
            latest=latest,
            date_from=date_from,
            date_to=date_to,
            limit=limit,
            geography=geography,
            region_type=region_type,
        )

    def flip_risk_alert(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        geography: str | None = None,
        region_type: str | None = None,
    ) -> pd.DataFrame:
        """Flip risk alerts when model runs show rapid directional reversals."""
        return self._get(
            "flip_risk_alert",
            latest=latest,
            date_from=date_from,
            date_to=date_to,
            limit=limit,
            geography=geography,
            region_type=region_type,
        )

    def tail_risk_alert(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        geography: str | None = None,
        region_type: str | None = None,
    ) -> pd.DataFrame:
        """Tail risk alerts when forecast distributions indicate extreme temperature probability."""
        return self._get(
            "tail_risk_alert",
            latest=latest,
            date_from=date_from,
            date_to=date_to,
            limit=limit,
            geography=geography,
            region_type=region_type,
        )

    # --- Backtests ---

    def backtest_summary(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        geography: str | None = None,
        region_type: str | None = None,
    ) -> pd.DataFrame:
        """Pre-computed backtest summary for WRSI forecast stress signal."""
        return self._get(
            "backtest_summary",
            latest=latest,
            date_from=date_from,
            date_to=date_to,
            limit=limit,
            geography=geography,
            region_type=region_type,
        )

    def backtest_outcomes(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        geography: str | None = None,
        region_type: str | None = None,
    ) -> pd.DataFrame:
        """Per-regime outcome metrics from WRSI backtest."""
        return self._get(
            "backtest_outcomes",
            latest=latest,
            date_from=date_from,
            date_to=date_to,
            limit=limit,
            geography=geography,
            region_type=region_type,
        )

    def backtest_transitions(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        geography: str | None = None,
        region_type: str | None = None,
    ) -> pd.DataFrame:
        """Regime transition matrix from WRSI backtest."""
        return self._get(
            "backtest_transitions",
            latest=latest,
            date_from=date_from,
            date_to=date_to,
            limit=limit,
            geography=geography,
            region_type=region_type,
        )


# =============================================================================
# WSSI-US: Weather Storage Shock Index
# =============================================================================


class WssiUs(BaseProduct):
    """
    WSSI-US: Weather Storage Shock Index.

    Weather-driven storage demand shock predictor.
    """

    def __init__(self, client: HTTPClient):
        super().__init__(client, "wssi_us")

    # --- Signals ---

    def demand_shock(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        region_id: str | None = None,
        region_type: str | None = None,
    ) -> pd.DataFrame:
        """
        Primary demand shock signal predicting EIA storage surprises.

        Includes WSSI score, regime, direction, and shock magnitude.

        Args:
            region_id: Filter by region ID
            region_type: Filter by region type
        """
        return self._get(
            "demand_shock",
            latest=latest,
            date_from=date_from,
            date_to=date_to,
            limit=limit,
            region_id=region_id,
            region_type=region_type,
        )

    def seasonal_context(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        region_id: str | None = None,
        region_type: str | None = None,
    ) -> pd.DataFrame:
        """
        Seasonal context signal indicating dominant weather driver.

        Includes HDD/CDD driver, forecast horizon, and validity window.
        """
        return self._get(
            "seasonal_context",
            latest=latest,
            date_from=date_from,
            date_to=date_to,
            limit=limit,
            region_id=region_id,
            region_type=region_type,
        )

    def model_agreement(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        region_id: str | None = None,
        region_type: str | None = None,
    ) -> pd.DataFrame:
        """
        Model agreement conviction signal measuring GEFS vs GFS consensus.

        Includes agreement score, ensemble confidence, and disagreement metrics.
        """
        return self._get(
            "model_agreement",
            latest=latest,
            date_from=date_from,
            date_to=date_to,
            limit=limit,
            region_id=region_id,
            region_type=region_type,
        )

    # --- Features ---

    def hdd_cdd(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        region_id: str | None = None,
        region_type: str | None = None,
    ) -> pd.DataFrame:
        """
        Population-weighted HDD/CDD features from GEFS and GFS.

        Includes p10/p50/p90 percentiles with ensemble spread by region.
        """
        return self._get(
            "hdd_cdd",
            latest=latest,
            date_from=date_from,
            date_to=date_to,
            limit=limit,
            region_id=region_id,
            region_type=region_type,
        )

    def forecast_shock(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        region_id: str | None = None,
        region_type: str | None = None,
    ) -> pd.DataFrame:
        """
        Run-to-run forecast shock features.

        Includes delta HDD/CDD, absolute deltas, and spread direction.
        """
        return self._get(
            "forecast_shock",
            latest=latest,
            date_from=date_from,
            date_to=date_to,
            limit=limit,
            region_id=region_id,
            region_type=region_type,
        )

    def model_disagreement(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        region_id: str | None = None,
        region_type: str | None = None,
    ) -> pd.DataFrame:
        """
        Cross-model disagreement features comparing GEFS vs GFS.

        Includes disagreement score and bucket classification.
        """
        return self._get(
            "model_disagreement",
            latest=latest,
            date_from=date_from,
            date_to=date_to,
            limit=limit,
            region_id=region_id,
            region_type=region_type,
        )

    def features(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        region_id: str | None = None,
        region_type: str | None = None,
    ) -> pd.DataFrame:
        """Composite WSSI feature matrix for signal consumption."""
        return self._get(
            "features",
            latest=latest,
            date_from=date_from,
            date_to=date_to,
            limit=limit,
            region_id=region_id,
            region_type=region_type,
        )

    def forecast_skill(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        region_id: str | None = None,
        region_type: str | None = None,
    ) -> pd.DataFrame:
        """
        Forecast skill metrics for demand shock predictions.

        Includes hit rate, false positive rate, and skill scores by season.
        """
        return self._get(
            "forecast_skill",
            latest=latest,
            date_from=date_from,
            date_to=date_to,
            limit=limit,
            region_id=region_id,
            region_type=region_type,
        )

    # --- Events ---

    def regime_shift(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        region_id: str | None = None,
        region_type: str | None = None,
    ) -> pd.DataFrame:
        """WSSI regime shift events when demand shock regime transitions."""
        return self._get(
            "regime_shift",
            latest=latest,
            date_from=date_from,
            date_to=date_to,
            limit=limit,
            region_id=region_id,
            region_type=region_type,
        )

    def extreme_volatility(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        region_id: str | None = None,
        region_type: str | None = None,
    ) -> pd.DataFrame:
        """Extreme volatility events when weather-driven demand shocks are unusually large."""
        return self._get(
            "extreme_volatility",
            latest=latest,
            date_from=date_from,
            date_to=date_to,
            limit=limit,
            region_id=region_id,
            region_type=region_type,
        )

    def model_divergence(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        region_id: str | None = None,
        region_type: str | None = None,
    ) -> pd.DataFrame:
        """Model divergence events when GEFS and GFS produce materially different signals."""
        return self._get(
            "model_divergence",
            latest=latest,
            date_from=date_from,
            date_to=date_to,
            limit=limit,
            region_id=region_id,
            region_type=region_type,
        )

    # --- Backtests ---

    def backtest_summary(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        region_id: str | None = None,
        region_type: str | None = None,
    ) -> pd.DataFrame:
        """Pre-computed backtest summary for WSSI-US demand shock signal."""
        return self._get(
            "backtest_summary",
            latest=latest,
            date_from=date_from,
            date_to=date_to,
            limit=limit,
            region_id=region_id,
            region_type=region_type,
        )

    def backtest_outcomes(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        region_id: str | None = None,
        region_type: str | None = None,
    ) -> pd.DataFrame:
        """Per-regime outcome metrics from WSSI-US backtest."""
        return self._get(
            "backtest_outcomes",
            latest=latest,
            date_from=date_from,
            date_to=date_to,
            limit=limit,
            region_id=region_id,
            region_type=region_type,
        )

    def backtest_transitions(
        self,
        latest: bool = True,
        date_from: date | str | None = None,
        date_to: date | str | None = None,
        limit: int = 200,
        region_id: str | None = None,
        region_type: str | None = None,
    ) -> pd.DataFrame:
        """Regime transition matrix from WSSI-US backtest."""
        return self._get(
            "backtest_transitions",
            latest=latest,
            date_from=date_from,
            date_to=date_to,
            limit=limit,
            region_id=region_id,
            region_type=region_type,
        )
