"""Shared fixtures: a spec matching Attachment 1 and helpers for tiny markets.""" from __future__ import annotations import numpy as np import pandas as pd import pytest from battery_dispatch.config import BatterySpec, RunConfig from battery_dispatch.markets import MARKET_1, MARKET_2, Market # Equal to Attachment 1, but stated literally so the tests do not depend on the # workbook being present or unchanged. SPEC = BatterySpec( max_charge_mw=2.0, max_discharge_mw=2.0, capacity_mwh=4.0, charge_efficiency=0.95, discharge_efficiency=0.95, lifetime_years=10.0, lifetime_cycles=5000.0, degradation_pct_per_cycle=0.001, capex_gbp=500_000.0, fixed_opex_gbp_per_year=5_000.0, ) @pytest.fixture def spec() -> BatterySpec: return SPEC @pytest.fixture def config() -> RunConfig: return RunConfig() def markets_from( market_1_prices: list[float], market_2_prices: list[float] | None = None, ) -> list[Market]: """Build both markets from a half-hourly Market 1 price list. ``market_2_prices`` is given per *hour*; when omitted Market 2 is priced so low that it never trades, isolating Market 1 behaviour. """ n = len(market_1_prices) if market_2_prices is None: broadcast = np.zeros(n) else: assert n == 2 * len(market_2_prices), "one Market 2 price per hour" broadcast = np.repeat(np.asarray(market_2_prices, dtype=float), 2) return [ Market(MARKET_1, 1, np.asarray(market_1_prices, dtype=float)), Market(MARKET_2, 2, broadcast), ] def prices_frame( market_1_prices: list[float], market_2_prices: list[float], start: str = "2018-01-01", ) -> pd.DataFrame: """A price frame shaped like the one ``load_prices`` returns.""" n = len(market_1_prices) index = pd.date_range(start, periods=n, freq="30min") return pd.DataFrame( { "market_1_price": np.asarray(market_1_prices, dtype=float), "market_2_price": np.repeat(np.asarray(market_2_prices, dtype=float), 2), "hour_index": np.arange(n) // 2, }, index=index, )