FastAPI app that pulls interval usage from Sungrow iSolarCloud (OAuth2), caches AER Consumer Data Right electricity plan data, and compares actual metered usage against every available plan to estimate real annual cost (TOU, tiered feed-in, and flat tariffs). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
54 lines
2.1 KiB
Python
54 lines
2.1 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
from datetime import datetime, timedelta, timezone
|
|
|
|
from app.aer.cache import refresh_plan_cache
|
|
from app.config import settings
|
|
from app.db.repositories import get_household_config, get_sync_state, set_sync_state, upsert_interval_readings
|
|
from app.db.session import get_session
|
|
from app.isolarcloud import get_isolarcloud_client
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
POLL_STATE_KEY = "isolarcloud_last_poll_ts"
|
|
AER_STATE_KEY = "aer_last_refresh_ts"
|
|
|
|
# Sungrow's own real-time data resolution is ~5 minutes -- don't try to
|
|
# backfill further back than this on first run; a fresh install just starts
|
|
# accumulating history from here.
|
|
DEFAULT_BACKFILL = timedelta(days=1)
|
|
|
|
|
|
def poll_isolarcloud_job() -> None:
|
|
now = datetime.now(timezone.utc)
|
|
with get_session() as session:
|
|
last_poll = get_sync_state(session, POLL_STATE_KEY)
|
|
since = datetime.fromisoformat(last_poll) if last_poll else now - DEFAULT_BACKFILL
|
|
|
|
client = get_isolarcloud_client()
|
|
try:
|
|
points = client.fetch_interval_data(since, now)
|
|
except Exception:
|
|
_LOGGER.exception("iSolarCloud poll failed (since=%s)", since)
|
|
return
|
|
|
|
source = "mock" if settings.use_mock_isolarcloud else "isolarcloud"
|
|
rows = [p.as_row(source) for p in points]
|
|
with get_session() as session:
|
|
inserted = upsert_interval_readings(session, rows)
|
|
set_sync_state(session, POLL_STATE_KEY, now.isoformat())
|
|
_LOGGER.info("iSolarCloud poll: fetched %d points, inserted %d new rows", len(points), inserted)
|
|
|
|
|
|
def refresh_aer_plans_job() -> None:
|
|
with get_session() as session:
|
|
distributor = get_household_config(session).distributor or settings.distributor
|
|
try:
|
|
count = refresh_plan_cache(distributor_filter=distributor)
|
|
except Exception:
|
|
_LOGGER.exception("AER plan refresh failed")
|
|
return
|
|
with get_session() as session:
|
|
set_sync_state(session, AER_STATE_KEY, datetime.now(timezone.utc).isoformat())
|
|
_LOGGER.info("AER plan refresh: cached %d plans for distributor '%s'", count, distributor)
|