""" Time-path scenario support: lets a scenario describe an evolving trajectory (bell, range/ oscillation, exponential, step, custom points...) for spot shock / IV level / skew tilt / term slope across the days between entry and the scenario horizon, instead of only a single point-in-time shock. The frontend is responsible for turning a shape+params choice into a plain list of {day, value} anchor points (services/lib/scenarioPath.ts) — this module only interpolates whatever anchor points it's given, so it has no notion of "bell" or "oscillation" itself and stays reusable across spot/IV/skew/term alike. A path is optional everywhere it's accepted: when None/empty, every call site here falls back to the scalar shock value it already had (unchanged behavior from before paths existed). """ from typing import Any, Dict, List, Optional def interpolate_path(path: Optional[List[Dict[str, Any]]], day: float, default: float) -> float: """Linear interpolation between anchor points {day, value}, clamped flat beyond the first/last anchor. Falls back to `default` when no path is given at all.""" if not path: return default pts = sorted(path, key=lambda p: p["day"]) if day <= pts[0]["day"]: return pts[0]["value"] if day >= pts[-1]["day"]: return pts[-1]["value"] for p0, p1 in zip(pts, pts[1:]): if p0["day"] <= day <= p1["day"]: span = p1["day"] - p0["day"] w = (day - p0["day"]) / span if span > 1e-9 else 0.0 return p0["value"] + (p1["value"] - p0["value"]) * w return pts[-1]["value"]