# Annual bill calculation guide

This guide explains how to apply an annual interval load profile to the source-normalised tariff catalogue, produce one auditable result per retailer plan, and summarise results by state, distributor, retailer, and tariff type.

The release contains tariff inputs only. Any calculated bill is an analytical output created by the user and must retain the snapshot date, profile identifier, assumptions, and validation status.

## 1. Files normally required

| Purpose | File |
|---|---|
| Select plans | `catalogue/tariff_plans.csv` |
| Daily supply charges and seasonal periods | `catalogue/tariff_periods.csv` |
| Flat, block, and TOU usage rates/windows | `catalogue/tariff_rates.csv` |
| Guaranteed and conditional discounts | `catalogue/tariff_discounts.csv` |
| Check restrictions | `catalogue/tariff_eligibility.csv` |
| Confirm postcode availability | `availability/plan_postcode_availability.csv` |

Specialist plans may also require files in `specialist-pricing/`.

## 2. Joining keys and row grain

Use this geography-aware plan key:

```text
snapshot_date
retailer_slug
plan_id
jurisdiction
distributor
```

Add `period_ix` when joining rates to tariff periods. Child files contain multiple rows per plan. Do not join rates, discounts, eligibility, and postcodes into one wide table before calculating: doing so creates a many-to-many row explosion and can duplicate costs.

Recommended process:

```text
1. Select one plan row.
2. Retrieve that plan's periods and rates.
3. Calculate its base bill.
4. Retrieve and apply discounts separately.
5. Save one result row for the plan.
```

## 3. Select plans

Typical residential electricity market-offer filters:

```text
customer_type = RESIDENTIAL
fuel_type = ELECTRICITY
plan_type = MARKET
jurisdiction = selected state
distributor = selected DNSP/network
```

For a standard import-only customer, consider excluding:

```text
has_controlled_load = true
has_demand_charge = true
is_solar_specific = true
```

`is_mainstream_candidate` and name-derived restriction flags are screening aids. Review eligibility and plan terms before treating a plan as generally available.

If modelling a specific address, first identify eligible plans in `plan_postcode_availability.csv`.

## 4. Prepare the load profile

Recommended long format:

```csv
timestamp,import_kwh,export_kwh,controlled_load_kwh
2025-01-01 00:00:00,0.21,0.00,0.00
2025-01-01 00:30:00,0.19,0.00,0.00
```

Minimum requirements:

```text
- a complete calculation year;
- explicit interval duration, normally 30 minutes;
- timestamps in the relevant state local timezone;
- imported energy in kWh;
- exports and controlled-load channels kept separate;
- a documented profile ID and annual kWh total.
```

For a standard 365-day half-hour profile, expect 17,520 intervals. Handle daylight saving deliberately: do not silently drop or duplicate local-clock intervals. Preserve the original timestamp and timezone or UTC offset.

## 5. Determine applicable tariff periods

For each day, select the plan's applicable row in `tariff_periods.csv` using `start_date` and `end_date`. Tariff periods may be annual or seasonal and may wrap across year-end.

Supply cost:

```text
annual_supply_cost = sum(daily_supply_charge for each applicable day)
```

Require complete day coverage. A day matching zero or multiple tariff periods is a calculation error unless the source rule explicitly permits it.

## 6. Flat single-rate plans

For one anytime rate:

```text
usage_cost = sum(import_kwh) × unit_price
base_bill = annual_supply_cost + usage_cost
```

If rates change seasonally, price each interval using the rate in its applicable tariff period rather than applying one annual average rate.

## 7. Block single-rate plans

Use `volume`, `period`, `rate_block_type`, rate ordering, and tariff-period information to allocate consumption to blocks. Apply the exact published reset period.

Example daily blocks:

```text
first 10 kWh/day at rate 1
remaining kWh/day at rate 2
```

For a 14 kWh day:

```text
block 1 = 10 kWh
block 2 = 4 kWh
```

Reset accumulators daily, monthly, quarterly, or annually as specified. Never average block rates. If reset semantics or block ordering are ambiguous, retain the plan in the catalogue but exclude it from calculated distributions with a recorded reason.

## 8. Time-of-use plans

For every interval:

```text
1. Find the applicable tariff period.
2. Determine local date, day type, and time.
3. Retrieve that plan's candidate rate windows.
4. Match the interval to exactly one peak, shoulder, or off-peak window.
5. Multiply interval import kWh by that row's unit price.
6. Sum interval costs.
```

Retailers may define different windows. Never impose one common peak definition across plans.

A window crossing midnight, such as `22:00–07:00`, matches times at or after 22:00 or before 07:00. Parse applicable-day arrays into structured values; do not use substring matching. Apply public-holiday rules only where the source schedule defines them and use the appropriate state calendar.

## 9. Specialist pricing

Use the additional files only when relevant:

| Component | File and requirement |
|---|---|
| Demand | `specialist-pricing/demand_charges.csv`; requires interval demand and exact measurement/charge rules |
| Controlled load | controlled-load files; requires a separate controlled-load channel |
| Solar FIT | solar FIT files; requires interval or annual exports as appropriate |
| Fees | `catalogue/tariff_fees.csv`; include only fees triggered by the modelled behaviour |
| Metering | metering charges; determine whether recurring and unavoidable |
| Green power | green-power charges; apply only for the selected participation level |

Do not infer missing customer channels.

## 10. Discounts and incentives

Calculate and retain three bill bases:

```text
base_bill
  Supply and usage charges before discounts.

guaranteed_bill
  Base bill after guaranteed discounts.

all_discounts_bill
  Base bill after guaranteed and conditional discounts,
  assuming all stated conditions are met.
```

Apply each discount using its source method: whole-bill percentage, usage percentage, or fixed amount. Keep incentives, rewards, and gift cards separate from recurring tariff bills unless reporting an explicitly labelled first-year effective cost.

## 11. Price units and GST

The export preserves source numeric values and performs no GST conversion. Before financial use, verify each price's unit and GST treatment against AER/CDR documentation or plan terms. Record the adopted interpretation in calculated output.

## 12. Mandatory validation checks

For every plan, record:

```text
calculation_status
exclusion_reason
matched_intervals
unmatched_intervals
overlapping_intervals
covered_days
profile_annual_kwh
priced_annual_kwh
```

A headline bill should require:

```text
- every import interval matches exactly one usage rule;
- no overlapping rates;
- all 365 days have a supply-charge period;
- priced kWh reconciles to profile kWh;
- block reset rules are unambiguous;
- timezone and daylight-saving treatment are explicit;
- required specialist channels are present.
```

Exclude rather than guess when a rule cannot be interpreted safely.

## 13. Suggested calculated output

One row per plan/profile calculation:

```text
snapshot_date
profile_id
jurisdiction
distributor
retailer_slug
brand_name
plan_id
display_name
pricing_model
annual_import_kwh
annual_supply_cost
annual_usage_cost
annual_demand_cost
annual_controlled_load_cost
annual_solar_fit_credit
annual_recurring_fees
base_bill
guaranteed_bill
all_discounts_bill
calculation_status
exclusion_reason
```

## 14. Summarising the market

Calculate plan bills first. Then produce separate distributions by:

```text
snapshot date
jurisdiction
distributor
pricing model (single rate versus TOU)
bill basis (base, guaranteed, all discounts)
```

Useful offer-level statistics:

```text
P1, P10, P25, median, P75, offer count, retailer count
```

A retailer can publish multiple plans. Also produce a retailer-level view by reducing each retailer/DNSP to a clearly defined statistic, such as:

```text
- cheapest generally available eligible plan; and/or
- median qualifying plan.
```

Show AGL, Origin, and EnergyAustralia separately where present. Do not describe offer-level percentiles as customer-weighted market shares.

## 15. Python implementation outline

```python
plans = read_csv("catalogue/tariff_plans.csv")
periods = read_csv("catalogue/tariff_periods.csv")
rates = read_csv("catalogue/tariff_rates.csv")
discounts = read_csv("catalogue/tariff_discounts.csv")
load = read_and_validate_profile("load_profile.csv")

for plan in select_eligible_plans(plans):
    plan_periods = select_children(periods, plan)
    plan_rates = select_children(rates, plan)

    result = price_plan_intervals(
        plan=plan,
        periods=plan_periods,
        rates=plan_rates,
        load=load,
    )

    validate_result(result, load)
    result = apply_discount_scenarios(result, select_children(discounts, plan))
    write_one_plan_result(result)
```

Implement rate matching as tested functions for tariff-period dates, applicable days, overnight windows, public holidays, and block resets. Keep an audit table of plans that fail validation.
