From e4c41c3eb9210da7f21f94bd1a2e3bafe6004e01 Mon Sep 17 00:00:00 2001 From: dlawler489 <104159223@student.swin.edu.au> Date: Fri, 11 Sep 2026 15:06:39 +1000 Subject: [PATCH] Fix missing GST on all AER-sourced usage rates and supply charges 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 --- app/aer/tests/test_aer_mapper.py | 4 ++-- app/costing/aer_mapper.py | 33 ++++++++++++++++++++++++++------ 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/app/aer/tests/test_aer_mapper.py b/app/aer/tests/test_aer_mapper.py index 31486af..3e80238 100644 --- a/app/aer/tests/test_aer_mapper.py +++ b/app/aer/tests/test_aer_mapper.py @@ -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 diff --git a/app/costing/aer_mapper.py b/app/costing/aer_mapper.py index c179ab7..7bff694 100644 --- a/app/costing/aer_mapper.py +++ b/app/costing/aer_mapper.py @@ -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, )