56 lines
1.7 KiB
Python
56 lines
1.7 KiB
Python
|
|
"""Decimal-string discipline (docs/11 §3.3) at the skill boundary.
|
||
|
|
|
||
|
|
Contracts carry money/energy/prices as fixed-point decimal *strings*. Inside a
|
||
|
|
skill we compute with numpy floats, then quantize back to strings with an
|
||
|
|
explicit scale — and any headline figure (revenue, energy) is re-derived with
|
||
|
|
``decimal.Decimal`` from the already-quantized curves, so what a report cites
|
||
|
|
is exactly reproducible from the curves it cites.
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from decimal import ROUND_HALF_EVEN, Decimal
|
||
|
|
from typing import Iterable
|
||
|
|
|
||
|
|
import numpy as np
|
||
|
|
|
||
|
|
INTERVALS = 96
|
||
|
|
INTERVAL_HOURS = Decimal("0.25")
|
||
|
|
|
||
|
|
SCALE_MW = 3
|
||
|
|
SCALE_MWH = 3
|
||
|
|
SCALE_PRICE = 2
|
||
|
|
SCALE_MONEY = 2
|
||
|
|
|
||
|
|
|
||
|
|
def to_array(values: Iterable[str]) -> np.ndarray:
|
||
|
|
return np.array([float(Decimal(v)) for v in values], dtype=float)
|
||
|
|
|
||
|
|
|
||
|
|
def quantize(x: float | Decimal, scale: int) -> str:
|
||
|
|
q = Decimal(1).scaleb(-scale)
|
||
|
|
d = x if isinstance(x, Decimal) else Decimal(repr(float(x)))
|
||
|
|
out = d.quantize(q, rounding=ROUND_HALF_EVEN)
|
||
|
|
if out == 0:
|
||
|
|
out = abs(out) # normalise "-0.000"
|
||
|
|
return format(out, "f")
|
||
|
|
|
||
|
|
|
||
|
|
def curve(values: np.ndarray | list[str], date: str, scale: int) -> dict:
|
||
|
|
if isinstance(values, np.ndarray):
|
||
|
|
vals = [quantize(float(v), scale) for v in values.tolist()]
|
||
|
|
else:
|
||
|
|
vals = list(values)
|
||
|
|
if len(vals) != INTERVALS:
|
||
|
|
raise ValueError(f"curve must have {INTERVALS} values, got {len(vals)}")
|
||
|
|
return {"interval_minutes": 15, "date": date, "values": vals}
|
||
|
|
|
||
|
|
|
||
|
|
def dsum(values: Iterable[str]) -> Decimal:
|
||
|
|
return sum((Decimal(v) for v in values), Decimal(0))
|
||
|
|
|
||
|
|
|
||
|
|
def dot(a: Iterable[str], b: Iterable[str]) -> Decimal:
|
||
|
|
"""Exact Σ a_i·b_i over decimal strings."""
|
||
|
|
return sum((Decimal(x) * Decimal(y) for x, y in zip(a, b, strict=True)), Decimal(0))
|