power-compare/app/isolarcloud/schemas.py
dlawler489 9e66a9a97d
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 24s
Add battery telemetry for plants with a hybrid inverter/ESS
Confirmed against Sungrow's own docs and live-tested against a real
battery plant: SOC and charge/discharge power live at the *device*
level (getDeviceRealTimeData against the hybrid inverter's ps_key,
device_type 14), not in the plant-aggregate points used elsewhere in
this client -- querying them via the plant-level endpoint just returns
null. Battery status is fetched once per live "today" poll (not
backfilled historically) and stored in three new nullable columns,
self-migrated on startup since there's no Alembic here.

Also fixes the dashboard's self-consumption line to derive from
load - import rather than generation - export, which overstated
"solar -> house" for battery systems (a chunk of that gap was going
into charging, not the house). The live flow diagram now shows a
Battery node (SOC + charge/discharge) that stays hidden entirely for
solar-only plants like Kallangur.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-09 12:25:09 +10:00

32 lines
1.1 KiB
Python

from datetime import datetime
from pydantic import BaseModel
class IntervalDataPoint(BaseModel):
ts: datetime
generation_kwh: float
load_kwh: float
import_kwh: float
export_kwh: float
resolution_minutes: int
# Only populated for plants with a battery (from a live device-level
# poll, not backfillable historical data) -- None for solar-only plants
# and for every historical/backfilled point.
battery_soc_percent: float | None = None
battery_charge_w: float | None = None
battery_discharge_w: float | None = None
def as_row(self, source: str) -> dict:
return {
"ts": self.ts,
"source": source,
"generation_kwh": self.generation_kwh,
"load_kwh": self.load_kwh,
"import_kwh": self.import_kwh,
"export_kwh": self.export_kwh,
"resolution_minutes": self.resolution_minutes,
"battery_soc_percent": self.battery_soc_percent,
"battery_charge_w": self.battery_charge_w,
"battery_discharge_w": self.battery_discharge_w,
}