# 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
```