# AmperWise Optimizatorius

## Paskirtis

Optimizatorius iš rinkos kainų, baterijos būsenos ir objekto apkrovos sudaro 15 minučių baterijos veiksmų intervalus.

Dabar pagrindinis planavimo endpointas yra vienas:

```http
POST /optimize
```

Algoritmas pasirenkamas pagal `plan_type`:

| `plan_type` | Naudojimas | Algoritmas |
|---|---|---|
| `solar` | Saulės parkai / pardavimas į tinklą | Iškrauti bateriją per brangiausius intervalus |
| `self-use` | Savoms reikmėms / pigus baterijos krovimas | Krauti bateriją per pigiausius intervalus |

`/optimize-charge` paliktas tik suderinamumui su senais kvietėjais. Naujos integracijos turi naudoti `/optimize`.

## Veikimas

- Portas: `3003` (`PORT`)
- Sveikata: `GET /health`
- Dokumentacijos puslapis: `GET /`
- Pagrindinis endpointas: `POST /optimize`
- Senas paprastas intervalų generatorius: `POST /solve`
- Suderinamumo wrapperis: `POST /optimize-charge`

## `POST /optimize` su `plan_type: "solar"`

`solar` režimas skirtas saulės parkams ir pardavimo / iškrovimo planavimui.

Tikslas: pagal baterijos įkrovą, minimalią SOC ribą ir kainas parinkti brangiausius 15 minučių intervalus, kuriuose verta iškrauti bateriją į tinklą.

Algoritmo funkcija:

```text
optimizeSolarPlan(input)
  -> buildMostProfitableExportSlots(...)
```

Užklausos pavyzdys:

```jsonc
{
  "plan_type": "solar",
  "optimizeUntil": "18:00",
  "minPricePerMWh": 0,
  "maxDischargeKw": 49.9,
  "maxDischargeEfficiencyPct": 95,
  "battery": {
    "soc_pct": 75,
    "capacity_kwh": 500,
    "min_soc_pct": 9,
    "max_soc_pct": 100
  }
}
```

Atsakyme grąžinama:

- `objectiveEur` — numatomos pajamos;
- `slots[]` su `mode: "DISCHARGE"`;
- `powerKw`, `energyKWh`, `price`, `expectedRevenueEur`, `predictedRemainingSocPct`.

`powerKw` visada yra teigiama reikšmė. Kryptį nusako `mode`.

## `POST /optimize` su `plan_type: "self-use"`

`self-use` režimas skirtas baterijos krovimui iš tinklo, kai norima rasti pigiausius intervalus ir sutaupyti.

Tikslas: užkrauti bateriją nuo dabartinio SOC iki `targetSocPct` (jei nenurodyta — iki `battery.max_soc_pct`) per pigiausius 15 minučių intervalus, neviršijant `maxChargeKw`, įvertinant objekto apkrovą ir, jei įjungta, istorinius vartojimo duomenis.

Algoritmo funkcija:

```text
optimizeSelfUsePlan(input)
  -> buildCheapestChargeSlots(...)
```

Užklausos pavyzdys:

```jsonc
{
  "plan_type": "self-use",
  "optimizeUntil": "08:00",
  "maxPricePerMWh": 1000,
  "maxChargeKw": 49.9,
  "currentLoadKw": 14.7,
  "chipId": 255484665314348,
  "useLatestTelemetry": true,
  "useHistoricalLoad": true,
  "historicalLoadDays": 30,
  "maxChargeEfficiencyPct": 95,
  "targetSocPct": 100,
  "battery": {
    "soc_pct": 75,
    "capacity_kwh": 500,
    "min_soc_pct": 9,
    "max_soc_pct": 100
  }
}
```

Atsakyme grąžinama:

- `objectiveEur` — numatomos krovimo išlaidos;
- `recommendedChargePricePerMWh` — rekomenduojama kainos riba pagal pasirinktus pigiausius intervalus;
- `slots[]` su `mode: "CHARGE"`;
- `powerKw`, `energyKWh`, `price`, `expectedCostEur`, `estimatedLoadKw`, `predictedRemainingSocPct`.

`powerKw` visada yra teigiama reikšmė. Kryptį nusako `mode`.

## Apkrovos įvertinimas `self-use` režime

Pirmiausia skaičiuojama laisva krovimo galia:

```text
effectiveChargeKw = max(0, maxChargeKw - currentLoadKw)
```

Jei `useHistoricalLoad: true` ir pateiktas `chipId`, optimizatorius naudoja `bridge_logs` telemetriją. Vartojimas (`load_p`) suskirstomas į 96 paros 15 minučių intervalus. Kiekvienam būsimam intervalui naudojamas tik to paties paros laiko istorinis vidurkis.

Jei konkrečiam laikui nėra pakankamai istorinių duomenų, naudojama gyva `currentLoadKw` reikšmė. Nenaudojamas bendras visos paros vidurkis, nes jis iškraipytų konkretaus laiko apkrovą.

Jei `useLatestTelemetry: true`, optimizatorius gali pats nuskaityti naujausią `soc_pct` ir `currentLoadKw` iš `bridge_logs` pagal `chipId`. Tai naudinga servisams, tokiems kaip `dispatcher-agent`, kai Home Assistant būsena gali būti neprieinama arba dar neatsistačiusi po perkrovimo.

## Laiko langas

`optimizeUntil` / `optimize_until` priima:

- ISO datą ir laiką (`2026-08-21T18:00:00+03:00`);
- Unix timestamp;
- paprastą vietinį laiką (`"08:00"`), kuris interpretuojamas kaip artimiausias toks laikas;
- jei nenurodyta — naudojamas kitas 10:00 vietinis laikas.

Laiko zona pagal nutylėjimą yra `Europe/Vilnius`, keičiama per `OPTIMIZER_TIMEZONE`.

Kainos skaitomos iš pirmos kolekcijos, kurioje randama duomenų:

1. `PRICE_COLLECTION` (numatyta `price_market`)
2. `price_marker`
3. `price_balancing`

Kainų užklausa įtraukia ir intervalus, kurie jau prasidėjo, bet dar nesibaigė. Todėl kvietimas `12:45:05` vis dar mato intervalą `12:45:00-13:00:00`.

## Rezervinis SOC

Rezervinis SOC (`input_number.gtv_1_reserved_soc` / `gtv_2_reserved_soc`) šiuo metu yra Home Assistant valdiklis ir būsimos valdymo logikos parametras. Pats iškrovimo ribojimas pagal šią reikšmę dar nėra prijungtas prie `solar` algoritmo.

## Seni endpointai

### `POST /optimize-charge`

Suderinamumo wrapperis seniems krovimo kvietėjams. Viduje jis naudoja tą patį algoritmą kaip:

```json
{ "plan_type": "self-use" }
```

Naujoms integracijoms naudoti `/optimize`.

### `POST /solve`

Senas paprastas intervalų generatorius. Jis pagal pateiktą `prices[]` masyvą suskirsto intervalus į CHARGE / DISCHARGE / IDLE pagal pigiausių ir brangiausių kainų trečdalius. Mongo kainų ir baterijos būsenos nenaudoja.

## Aplinkos kintamieji

- `PORT` — HTTP portas, numatyta `3003`
- `MONGO_URI`, `MONGO_DB`, `MONGO_AUTH_SOURCE` — MongoDB prisijungimas
- `PRICE_COLLECTION` — pagrindinė kainų kolekcija, numatyta `price_market`
- `BRIDGE_LOGS_COLLECTION` — telemetrijos kolekcija, numatyta `bridge_logs`
- `OPTIMIZER_TIMEZONE` — laiko zona, numatyta `Europe/Vilnius`
- `OPTIMIZER_DISCHARGE_KW`, `OPTIMIZER_DISCHARGE_EFFICIENCY_PCT` — `solar` numatytos reikšmės
- `OPTIMIZER_CHARGE_KW`, `OPTIMIZER_CHARGE_EFFICIENCY_PCT` — `self-use` numatytos reikšmės
- `OPTIMIZER_HISTORICAL_LOAD_DAYS` — istorinių duomenų langas, numatyta `30`
- `OPTIMIZER_MIN_HISTORICAL_BUCKET_SAMPLES` — minimalus įrašų kiekis istoriniam intervalui, numatyta `2`

## Paleidimas

```bash
npm start
```