power-compare/app/scheduler/jobs.py
dlawler489 84c4922bbc Initial commit: solar usage vs AER plan comparison app
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>
2026-09-09 11:07:01 +10:00

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)