Skip to content

Tariff dashboard engine over-states TOU tariffs (Agile +38%) — bills consumption × avg rate instead of import × half-hourly rate #426

@albinati

Description

@albinati

Symptom

GET /api/v1/tariffs/dashboard's projection for the user's current
half-hourly tariff (AGILE-24-10-01) was £108.27/month, while the
real realised cost for the SAME month was £78.43. The engine over-
states Agile by ~38 %.

=== ACTUAL realised May cost (from /energy/period — real half-hourly) ===
  import £69.43 + standing £19.29 - export £10.29 = NET £78.43

=== Tariffs dashboard projection for Agile (same month) ===
  total = £108.27
  unit_rate avg = 29.59p · standing = 62p/d

Effect: the comparison table showed Agile costing MORE than BG Fixed
v58 (£80.56), when in fact the user beats BG every month. Users on
solar+battery+TOU tariffs see a comparison that contradicts their
real bills.

Likely cause

The engine appears to use a flat-rate model:

total_pence = consumption_kwh × unit_rate_pence + days × standing_per_day

For flat tariffs (BG Fixed, Cosy Fixed) this is accurate.
For half-hourly tariffs (Agile, Intelligent Flux) it's wrong because:

  1. It bills CONSUMPTION (total load) instead of IMPORT (what actually
    came from the grid) — ignoring battery + solar self-use.
  2. It uses the AVERAGE rate instead of the per-slot rate — ignoring the
    user's deliberate strategy of charging the battery during cheap
    slots and discharging during peak slots.

The dashboard's pricing field already distinguishes
half_hourly vs time_of_use vs flat — the engine could
branch on this and replay the half-hour profile against the tariff's
own half-hourly rates (analogous to _realised_import_pence in
src/analytics/pnl.py but indexed by the candidate tariff's
agile_rates rather than the active one).

Workaround shipped (UI)

src/components/home/TariffComparisonWidget.tsx now overrides the
current row's projected total with the realised cost from
/energy/period?period=month (real half-hourly × measured import).
Other rows keep the engine projection, with foot text noting the basis
difference:

Your current tariff = real realised cost (half-hourly Agile ×
measured grid import). Others = engine projection (flat avg rate ×
usage, no battery-arbitrage credit), so they over-state TOU tariffs.

Asks

  1. Backend tariff engine — replay measured grid_import_kw half-hour
    buckets against each tariff's own half-hourly rates (when
    pricing in {"half_hourly","time_of_use"}) instead of
    consumption × avg rate. Use db.half_hourly_grid_import_kwh_for_day
    the same way _realised_import_pence does. Standing + export
    remain straightforward.
  2. Where a TOU tariff has time-of-use bands (Octopus Go cheap nights
    etc.), replay each slot against the band the slot falls into.
  3. Remove the UI workaround once the engine reports realistic numbers.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions