> ## Documentation Index
> Fetch the complete documentation index at: https://docs.energy.nlead.ch/llms.txt
> Use this file to discover all available pages before exploring further.

# Architecture

> Clean core, adapters at the edges, fakes for every port

```mermaid theme={null}
flowchart TB
    subgraph api ["api/ — FastAPI"]
        R[external router<br/>POST /v1/forecasts]
        P[process router<br/>/v1/process/*]
    end
    subgraph svc ["service.py"]
        SV[pipeline steps + orchestrator<br/>hash-based change detection]
    end
    subgraph dom ["domain/ — pure, no I/O"]
        I[intervals<br/>DST-aware]
        A[aggregation<br/>net / open / total]
        D[distribution<br/>schedules + trade aggregation + invariant]
    end
    subgraph ad ["adapters/ — each with a fake"]
        S[sftp — paramiko]
        E[engrate — httpx, one POST per TSO]
        CSV[csv_format]
        AU[audit — JSON files + state]
    end
    R --> SV
    P --> SV
    SV --> A & D & I
    SV --> S & E & CSV & AU
```

## Layers

| Layer        | Rule                                                                 | Contents                                                                                                                                          |
| ------------ | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `domain/`    | Pure functions, no I/O, no framework imports                         | `intervals.py` (96/92/100 DST handling), `aggregation.py`, `distribution.py` (schedule building, per-counterparty trade aggregation), `models.py` |
| `adapters/`  | All I/O, each behind a `Protocol` with an in-memory fake             | `sftp.py`, `engrate.py`, `csv_format.py`, `audit.py`                                                                                              |
| `api/`       | Transport only — scoped auth, validation schemas, HTTP error mapping | `routes.py`, `schemas.py`                                                                                                                         |
| `service.py` | Pipeline steps, state tracking, orchestrator                         | `BridgeService`                                                                                                                                   |
| `config.py`  | pydantic-settings from environment variables                         | `Settings`, `AreaMarketIds`                                                                                                                       |
| `main.py`    | Wiring — the only place real adapters are constructed                | `create_app(...)`                                                                                                                                 |

`create_app()` takes every adapter as an optional argument, which is exactly
how the tests inject the fakes — no monkeypatching, no network.

## The Engrate adapter

The client implements Engrate's documented
[Create Schedule endpoint](https://docs.engrate.io/api-reference/schedules/create-schedule):
one `POST /schedule-management/v1/schedules` per TSO per delivery date, API
key raw in the `authorization` header, amounts in MWh per quarter-hour,
direction encoded via `out_party`/`in_party` EIC codes. Series mapping:

| Internal component                                                | Engrate series type |
| ----------------------------------------------------------------- | ------------------- |
| `production_forecast`                                             | `production`        |
| `consumption_forecast`                                            | `consumption`       |
| `internal_transfer` (between our groups, different control areas) | `external`          |
| `exchange_delivery` (ECC counterparty, home area)                 | `internal`          |

All market identifiers come from config (`AREA_MARKET_IDS`); nothing else in
the service knows Engrate's shape, so contract changes stay in one file.

## Testing strategy

| Suite                                         | What it proves                                                                                                          |
| --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `test_aggregation.py`, `test_distribution.py` | Core algorithm unit-by-unit, incl. trade aggregation per direction/counterparty and error cases                         |
| `test_invariant.py`                           | Zero-sum schedules, transfer conservation and trade aggregation across random data × 4 home areas × 3 day lengths       |
| `test_intervals.py`                           | 96/92/100 DST interval math                                                                                             |
| `test_adapters.py`                            | CSV format, audit roundtrip, Engrate payload mapping (types, EIC direction encoding, MWh conversion, exchange-leg flag) |
| `test_api.py`                                 | Every endpoint through HTTP with fakes, incl. orchestrator change detection and idempotency                             |
| `test_auth.py`                                | Scope separation: external key cannot reach process endpoints                                                           |
