Fix missing GST on all AER-sourced usage rates and supply charges
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 35s

AER's Consumer Data Right unitPrice fields are GST-exclusive, confirmed
by comparing a real, currently-effective EnergyAustralia contract's own
GST-inclusive rates against this plan's cached AER data: usage rates and
the supply charge all matched the cached value multiplied by exactly
1.1 (four independent figures). A real AGL bill confirmed the same
convention separately (excl-GST line items, GST added back as one
combined amount at the end). This understated every one of the ~700+
cached comparison plans' costs by ~10% relative to manually-entered
current plans (which were correctly GST-inclusive, taken from real
bills), biasing every "savings vs current" figure across the board.

Feed-in/solar buy-back rates are the exception -- confirmed GST-free in
that same contract's own terms -- so the multiplier applies only to
usage rates and the supply charge.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
dlawler489 2026-09-11 15:06:39 +10:00
parent 9e66a9a97d
commit e4c41c3eb9
2 changed files with 29 additions and 8 deletions

View file

@ -5,7 +5,7 @@ from datetime import datetime
import pytest
from app.costing.aer_mapper import map_aer_plan
from app.costing.aer_mapper import GST_MULTIPLIER, map_aer_plan
from app.costing.engine import IntervalReading, evaluate
from app.costing.tariff_models import find_usage_coverage_gaps
@ -26,7 +26,7 @@ def test_maps_real_agl_time_of_use_plan():
assert plan.retailer == "AGL"
assert "Energex" in plan.distributors
assert len(plan.usage_components) == 3 # peak, off-peak, shoulder
assert plan.supply_charge_c_per_day == pytest.approx(1.536 * 100)
assert plan.supply_charge_c_per_day == pytest.approx(1.536 * 100 * GST_MULTIPLIER)
labels = {c.label for c in plan.usage_components}
assert {"Time of Use Peak", "Time of Use Off Peak", "Time of Use Shoulder"} <= labels

View file

@ -62,10 +62,27 @@ def _time_windows(entries: list[dict]) -> list[TimeWindow]:
]
# AER's Consumer Data Right "Get Generic Plan Detail" unitPrice fields are
# GST-EXCLUSIVE -- confirmed by comparing a real, currently-effective
# EnergyAustralia contract's own quoted GST-inclusive rates against this
# plan's cached AER data: every usage rate and the supply charge matched
# the cached value multiplied by exactly 1.1, precisely (four independent
# figures, not a coincidence), and a real AGL bill separately confirmed
# the same convention (its line items are excl-GST, with GST added back as
# one combined amount at the end). Feed-in/solar buy-back rates are the
# exception -- confirmed GST-free in that same EnergyAustralia contract's
# own terms ("The rate at which we purchase your electricity does not
# include GST") -- so this multiplier applies only to usage rates and the
# supply charge, never to feed_in_tariffs/tiered_feed_in.
GST_MULTIPLIER = 1.1
def _rate_cents(rates: list[dict]) -> tuple[float, bool]:
"""Returns (cents_per_kwh, is_tiered). Only the first tier is used for
tiered/inclining-block rate structures -- callers should mark the plan
not-modelable when is_tiered is True, since later tiers are ignored."""
"""Returns (cents_per_kwh, is_tiered), GST-exclusive as given by AER --
callers apply GST_MULTIPLIER themselves where it applies (usage rates,
not feed-in). Only the first tier is used for tiered/inclining-block
rate structures -- callers should mark the plan not-modelable when
is_tiered is True, since later tiers are ignored."""
return float(rates[0]["unitPrice"]) * 100, len(rates) > 1
@ -84,13 +101,17 @@ def map_aer_plan(list_entry: dict, detail: dict) -> TariffPlan:
for period in ec.get("tariffPeriod", []):
season_months = _season_months(period.get("startDate", "01-01"), period.get("endDate", "12-31"))
rate_type = period.get("rateBlockUType")
supply_charges_c.append(float(period.get("dailySupplyCharge", 0) or 0) * 100)
supply_charges_c.append(float(period.get("dailySupplyCharge", 0) or 0) * 100 * GST_MULTIPLIER)
if rate_type == "singleRate":
block = period["singleRate"]
rate_c, tiered = _rate_cents(block["rates"])
usage_components.append(
UsageComponent(label=block.get("displayName", "Usage"), rate_c_per_kwh=rate_c, season_months=season_months)
UsageComponent(
label=block.get("displayName", "Usage"),
rate_c_per_kwh=rate_c * GST_MULTIPLIER,
season_months=season_months,
)
)
if tiered:
modelable = False
@ -101,7 +122,7 @@ def map_aer_plan(list_entry: dict, detail: dict) -> TariffPlan:
usage_components.append(
UsageComponent(
label=tou_block.get("displayName", tou_block.get("type", "TOU")),
rate_c_per_kwh=rate_c,
rate_c_per_kwh=rate_c * GST_MULTIPLIER,
time_windows=_time_windows(tou_block.get("timeOfUse", [])),
season_months=season_months,
)