# Formulas & Resolved Tariffs Formulas define how tariffs combine into a final rate at a specific location. You configure a formula per location and direction, and the system evaluates it to produce a resolved tariff timeseries. ## Configuring a Formula A formula has three parts: 1. **Variables** — a map from short names to tariff IDs 2. **Formula** — an expression that combines the variables 3. **Direction** — whether this formula is for import or export ### Example: Energy + Grid Fee + Margin Given two existing tariffs — an energy tariff (`energy-import`) and a grid tariff (`grid-import`) — you can combine them with a fixed margin: ```http PUT /flex/locations/{locationId}/tariff-formulas Content-Type: application/json { "direction": "import", "variables": { "energy": "energy-import", "grid": "grid-import" }, "formula": "energy + grid + 0.02" } ``` This computes the final rate as: energy tariff rate + grid tariff rate + 2 cents/kWh margin. ### Example: Spot Price with Floor and Markup ```http PUT /flex/locations/{locationId}/tariff-formulas Content-Type: application/json { "direction": "import", "variables": { "spot": "spot-energy", "grid": "grid-import" }, "formula": "max(spot, 0) * 1.15 + grid + 0.03" } ``` This floors the spot price at zero (no negative prices), applies a 15% markup, adds the grid fee, and adds a 3 cent/kWh margin. ## Formula Syntax ### Operators | Operator | Description | Example | |----------|-------------|---------| | `+` | Addition | `energy + grid` | | `-` | Subtraction | `gross - discount` | | `-` | Negation | `-spot` | | `*` | Multiplication | `1.15 * spot` | | `/` | Division | `spot / 1000` | | `( )` | Grouping | `(spot + grid) * markup` | ### Common Functions | Function | Description | Example | |----------|-------------|---------| | `min(a, b)` | Minimum of two values | `min(spot, 0.50)` | | `max(a, b)` | Maximum of two values | `max(spot, 0)` | | `clamp(x, lo, hi)` | Constrain between bounds | `clamp(spot, 0, 0.50)` | | `abs(x)` | Absolute value | `abs(spot)` | | `round(x, n)` | Round to n decimal places | `round(spot + grid, 4)` | Additional arithmetic functions are available beyond those listed here, but the above cover the vast majority of tariff use cases. ### Decimal Literals You can use decimal numbers directly in formulas: `0.03`, `1.15`, `0`. These are treated as dimensionless scalars when used with `*` or `/`, or as rates (currency/kWh) when used with `+` or `-`. ## Dimensional Rules The formula system enforces dimensional correctness. There are two types of values: - **Rate** — a price in currency per kWh (e.g., 0.28 EUR/kWh). Tariffs with `per: "kWh"` produce rate values. - **Scalar** — a dimensionless multiplier (e.g., 1.15). Tariffs with `per: "scalar"` produce scalar values. | Operation | Result | Valid? | |-----------|--------|--------| | rate + rate | rate | Yes | | rate - rate | rate | Yes | | scalar × rate | rate | Yes | | rate × rate | — | No | | scalar + rate | — | No | The formula must evaluate to a **rate**. The API validates dimensional correctness when you create a formula and returns an error if the formula is invalid. ### Currency and Direction Rules All rate-valued tariffs in a formula must share the same currency. The formula's direction must match the direction of all referenced tariffs (including scalar tariffs). The API validates these constraints when you create a formula. ## Scalar Tariffs If a coefficient in your formula changes over time, use a scalar tariff instead of a literal. For example, if the spot markup factor varies, create a scalar tariff to hold it: ```http POST /flex/tariffs/spot-markup Content-Type: application/json { "direction": "import", "per": "scalar" } ``` Scalar tariffs have no `currency` since they're dimensionless. They still require a `direction`, which is validated against the formula's direction. Push coefficient values: ```http PUT /flex/tariffs/spot-markup/timeseries Content-Type: application/json Idempotency-Key: a1b2c3d4-e5f6-7890-abcd-ef1234567890 { "to": "2024-06-16T00:00:00+02:00", "values": [ { "at": "2024-06-15T00:00:00+02:00", "rate": 1.15 }, { "at": "2024-06-15T12:00:00+02:00", "rate": 1.20 } ] } ``` Then use it in a formula: ```http PUT /flex/locations/{locationId}/tariff-formulas Content-Type: application/json { "direction": "import", "variables": { "spot": "spot-energy", "markup": "spot-markup", "grid": "grid-import" }, "formula": "max(spot, 0) * markup + grid + 0.02" } ``` Now the markup changes from 1.15 to 1.20 at midday without updating the formula itself. ## Retrieving Formulas ```http GET /flex/locations/{locationId}/tariff-formulas ``` Returns formulas for all directions. Filter by direction: ```http GET /flex/locations/{locationId}/tariff-formulas?direction=import ``` ## Deleting a Formula ```http DELETE /flex/locations/{locationId}/tariff-formulas?direction=import ``` ## Resolved Tariffs The resolved tariffs endpoint evaluates the location's formula over a time range and returns the computed rate at each interval. ```http GET /flex/locations/{locationId}/tariffs/resolved?from=2024-06-15&to=2024-06-16&direction=import ``` ```json { "locationId": "4eaeb363-296d-4ccc-a973-7805e6f400bd", "direction": "import", "currency": "EUR", "per": "kWh", "from": "2024-06-15", "to": "2024-06-16", "timezoneName": "Europe/Berlin", "intervals": [ { "type": "resolved", "startAt": "2024-06-15T00:00:00+02:00", "endAt": "2024-06-15T01:00:00+02:00", "formula": "max(spot, 0) * 1.15 + grid + 0.03", "rate": 0.248 }, { "type": "resolved", "startAt": "2024-06-15T01:00:00+02:00", "endAt": "2024-06-15T02:00:00+02:00", "formula": "max(spot, 0) * 1.15 + grid + 0.03", "rate": 0.225 } ] } ``` ### How Intervals Work Each interval has a `type` — either `resolved` (with a computed rate) or `unresolved` (indicating missing data). A new resolved interval starts whenever any input tariff's rate changes (e.g., the spot price updates hourly). ### Unavailable Data If a tariff in the formula doesn't have data covering the entire requested range, intervals outside the tariff's available range are returned as unresolved. The available range is determined by the data that has been pushed — each tariff's `availableFrom` and `availableTo` define the window where rates are valid. Beyond this window, rates are not carried forward, preventing stale data from silently producing incorrect prices. For example, if `grid` has data pushed until 12:00 (`availableTo` = 12:00), querying 00:00–24:00 returns: ```json { "intervals": [ { "type": "resolved", "startAt": "2024-06-15T00:00:00+02:00", "endAt": "2024-06-15T12:00:00+02:00", "formula": "max(spot, 0) * 1.15 + grid + 0.03", "rate": 0.248 }, { "type": "unresolved", "startAt": "2024-06-15T12:00:00+02:00", "endAt": "2024-06-16T00:00:00+02:00" } ] } ``` Only intervals where all input tariffs have data produce resolved results. If any tariff's available range doesn't cover a portion of the requested range, that portion is unresolved. When multiple tariffs are used, only the intersection of their available ranges produces resolved intervals. ## Endpoint Reference | Task | Method | Endpoint | |------|--------|----------| | Get formulas | GET | `/flex/locations/{locationId}/tariff-formulas` | | Set formula | PUT | `/flex/locations/{locationId}/tariff-formulas` | | Delete formula | DELETE | `/flex/locations/{locationId}/tariff-formulas?direction={direction}` | | Get resolved tariffs | GET | `/flex/locations/{locationId}/tariffs/resolved` |