Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ You need an Amber API key from [app.amber.com.au/developers](https://app.amber.c
- Multi-site picker when your Amber account has more than one NMI
- Editable Amber daily connection, subscription and demand rates for this financial year and last (days before 1 July 2026 use last year's); an uploaded bill fills the year it belongs to
- More Stats: average import and feed-in prices, price-spike and negative-price exposure, evening import share, grid-free days
- Solar & battery planner: from the last 12 months of meter readings (Amber's, an imported Amber CSV, or any household's NEM12), tries every mix of new solar size, battery size and plan for the network, half hour by half hour, and ranks them by 20-year value, payback or yearly bill. Every option is measured against one yardstick: nothing new on the plan to compare against, by default whichever of the plans you'd use is cheapest with nothing new (switching is free). Companies, or single plans, can be ticked or unticked: only ticked plans are searched and shown. Any of the three can be fixed ("a size I choose", "none", or one plan). Shows next year's bill month by month against today's, a payback chart at today's prices and ±20% (payback is when it's ahead for good, after any battery replacement), worth in today's money and IRR (a battery replaced within the years compared is credited with the life it has left at the end), a "How keen to be" rating (Not worth it, Marginal, Modest, Good or Strong, from the value per $1 up front, whether it still pays with prices 20% lower, and payback, plus whether waiting a year would cost anything), and a 'Where the saving comes from' breakdown for each option (power bought, power sent to the grid, export and demand charges, and where the new panels' power goes: used at home, stored or sent out, lost to the export limit or clipped by the inverter), a "wait a year" check, and what just switching plan would save. Each plan is priced exactly as on Compare (free windows and caps, capped and time-varying feed-in, demand, Amber's export charge); missing Amber feed-in prices are rebuilt from AEMO wholesale prices. The battery charges from spare solar and from free or cheap grid hours, covers the home when power is dear and sells into high feed-in prices, knowing each day's prices in the morning. The export limit applies to the whole home and is measured against what the meter already sent out; a battery stores spare solar before the limit throws the rest away. When a feed-in price is negative the panels switch off by default (a battery soaks up what it can first), so nothing is paid to export; a setting says if they can't, and then exporting at negative prices costs money. Where the feed-in price goes below zero (Amber), the recommendation and "what to ask installers for" say the panels need a "curtailable" inverter (one the retailer can switch off remotely), and on Amber a battery that SmartShift can run. A "Check the numbers" card for the option on show gives the average price paid per kWh bought and earned per kWh sent out (for the yardstick, the same plan with nothing new, and the option), daily and demand charges, the plan's average buy and feed-in prices through the day and how often feed-in is below zero, rules of thumb (kWh a day per kW of panels; what each kWh of new solar or battery output is worth against what it costs per kWh), and the settings it was worked out with. The "How keen to be" rating also checks the result if feed-in prices from 10am to 3pm halve. Solar comes from a PVGIS 5.3 typical year (ERA5) for 22 towns (`data/solar`, fetched by `scripts/update-solar.js`), with sun position, tilt, facing, shade, temperature and inverter clipping, scaled to PVGIS's own yield. Homes that already have panels or a battery: the app spots them in the readings (solar sent out; selling after dark or quiet nights) and pre-fills a guess. Existing panels stay in the readings and new ones are added on top. An existing battery is taken out of the readings (selling to the grid is seen directly; what it covered at home is estimated from its size, the days it filled up and ran flat, and the home's use on nights it was empty; its charging from spare solar first, then from the grid in the cheapest half hours that bought more than usual) and run again on every plan with any new battery, since it would behave differently on another plan. A check card compares the planner's run of that battery on today's plan with the real readings, and shows what the battery saves. Allows for the federal solar (STC) and battery rebates shrinking each year, hardware getting cheaper, panel and battery wear, battery and inverter replacement, the export limit, and planned changes: an electric car, heat-pump hot water, gas heating and cooking to electric (with the gas bill saved)
- Solar & battery planner: from the last 12 months of meter readings (Amber's, an imported Amber CSV, or any household's NEM12), tries every mix of new solar size, battery size and plan for the network, half hour by half hour, and ranks them by 20-year value, payback or yearly bill. Every option is measured against one yardstick: nothing new on the plan to compare against, by default whichever of the plans you'd use is cheapest with nothing new (switching is free). Companies, or single plans, can be ticked or unticked: only ticked plans are searched and shown. Any of the three can be fixed ("a size I choose", "none", or one plan). Shows next year's bill month by month against today's, a payback chart at today's prices and ±20% (with the $0 line, where it's paid back, marked) (payback is when it's ahead for good, after any battery replacement), worth in today's money and IRR (a battery replaced within the years compared is credited with the life it has left at the end), a "How keen to be" rating (Not worth it, Marginal, Modest, Good or Strong, from the value per $1 up front, whether it still pays with prices 20% lower, and payback, plus whether waiting a year would cost anything), and a 'Where the saving comes from' breakdown for each option (power bought, power sent to the grid, export and demand charges, and where the new panels' power goes: used at home, stored or sent out, lost to the export limit or clipped by the inverter), a "wait a year" check, and what just switching plan would save. Each plan is priced exactly as on Compare (free windows and caps, capped and time-varying feed-in, demand, Amber's export charge); missing Amber feed-in prices are rebuilt from AEMO wholesale prices. The battery charges from spare solar and from free or cheap grid hours, covers the home when power is dear and sells into high feed-in prices, knowing each day's prices in the morning. The export limit applies to the whole home and is measured against what the meter already sent out; a battery stores spare solar before the limit throws the rest away. When a feed-in price is negative the panels switch off by default (a battery soaks up what it can first), so nothing is paid to export; a setting says if they can't, and then exporting at negative prices costs money. Where the feed-in price goes below zero (Amber), the recommendation and "what to ask installers for" say the panels need a "curtailable" inverter (one the retailer can switch off remotely), and on Amber a battery that SmartShift can run. A "Check the numbers" card for the option on show gives the average price paid per kWh bought and earned per kWh sent out (for the yardstick, the same plan with nothing new, and the option), daily and demand charges, the plan's average buy and feed-in prices through the day and how often feed-in is below zero, rules of thumb (kWh a day per kW of panels; what each kWh of new solar or battery output is worth against what it costs per kWh), and the settings it was worked out with. The "How keen to be" rating also checks the result if feed-in prices from 10am to 3pm halve. Solar comes from a PVGIS 5.3 typical year (ERA5) for 22 towns (`data/solar`, fetched by `scripts/update-solar.js`), with sun position, tilt, facing, shade, temperature and inverter clipping, scaled to PVGIS's own yield. Homes that already have panels or a battery: the app spots them in the readings (solar sent out; selling after dark or quiet nights) and pre-fills a guess. Existing panels stay in the readings and new ones are added on top. An existing battery is taken out of the readings (selling to the grid is seen directly; what it covered at home is estimated from its size, the days it filled up and ran flat, and the home's use on nights it was empty; its charging from spare solar first, then from the grid in the cheapest half hours that bought more than usual) and run again on every plan with any new battery, since it would behave differently on another plan. A check card compares the planner's run of that battery on today's plan with the real readings, and shows what the battery saves. Allows for the federal solar (STC) and battery rebates shrinking each year, hardware getting cheaper, panel and battery wear, battery and inverter replacement, the export limit, and planned changes: an electric car, heat-pump hot water, gas heating and cooking to electric (with the gas bill saved)

## Data completeness and CSV backups

Expand Down
48 changes: 24 additions & 24 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self'; connect-src 'self' https://api.amber.com.au; img-src 'self' data:; font-src 'self'; worker-src 'self'; base-uri 'self'; form-action 'self'">
<title>Amber Compare &amp; Export v1.59</title>
<title>Amber Compare &amp; Export v1.60</title>
<link rel="icon" href="favicon.ico" type="image/x-icon">
<link rel="stylesheet" href="css/app.css?v=1.59">
<link rel="stylesheet" href="css/app.css?v=1.60">
</head>
<body class="bg-gray-100 text-gray-800">
<a href="#mainTabs" class="sr-only focus:not-sr-only focus:absolute focus:top-2 focus:left-2 focus:z-50 focus:bg-white focus:px-3 focus:py-2 focus:rounded">Skip to the tabs</a>
Expand All @@ -19,7 +19,7 @@ <h1 class="text-2xl sm:text-3xl font-bold text-gray-900 flex items-center">
<img src="favicon.ico" alt="" class="mr-3 h-12 w-12 sm:h-14 sm:w-14"> Amber Compare &amp; Export
</h1>
<p class="mt-2 text-gray-600">See what your electricity really costs on Amber, check an Amber bill, and find out whether another plan (or Amber) would be cheaper for you.</p>
<p class="mt-1 text-xs text-gray-500">Free and private: everything runs in this browser and nothing is uploaded. Version 1.59.</p>
<p class="mt-1 text-xs text-gray-500">Free and private: everything runs in this browser and nothing is uploaded. Version 1.60.</p>
</div>
<div class="relative flex items-center">
<button id="settingsBtn" type="button" aria-label="Settings" aria-haspopup="true" aria-expanded="false" class="p-2 rounded-full text-gray-500 hover:bg-gray-100 hover:text-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
Expand Down Expand Up @@ -798,26 +798,26 @@ <h3 class="text-xl font-bold text-gray-900 mb-1" data-help="planPayback">Paying

<script src="chart.umd.min.js"></script>
<script src="chartjs-plugin-zoom.min.js"></script>
<script src="js/constants.js?v=1.59"></script>
<script src="js/help.js?v=1.59"></script>
<script src="js/time.js?v=1.59"></script>
<script src="js/templates.js?v=1.59"></script>
<script src="js/storage.js?v=1.59"></script>
<script src="js/costs.js?v=1.59"></script>
<script src="js/api.js?v=1.59"></script>
<script src="js/bill.js?v=1.59"></script>
<script src="js/csv.js?v=1.59"></script>
<script src="js/charts.js?v=1.59"></script>
<script src="js/calendar.js?v=1.59"></script>
<script src="js/stats.js?v=1.59"></script>
<script src="js/monthly.js?v=1.59"></script>
<script src="js/redispatch.js?v=1.59"></script>
<script src="js/import.js?v=1.59"></script>
<script src="js/networks.js?v=1.59"></script>
<script src="js/estimate.js?v=1.59"></script>
<script src="js/solar.js?v=1.59"></script>
<script src="js/planner.js?v=1.59"></script>
<script src="js/planner-ui.js?v=1.59"></script>
<script src="js/app.js?v=1.59"></script>
<script src="js/constants.js?v=1.60"></script>
<script src="js/help.js?v=1.60"></script>
<script src="js/time.js?v=1.60"></script>
<script src="js/templates.js?v=1.60"></script>
<script src="js/storage.js?v=1.60"></script>
<script src="js/costs.js?v=1.60"></script>
<script src="js/api.js?v=1.60"></script>
<script src="js/bill.js?v=1.60"></script>
<script src="js/csv.js?v=1.60"></script>
<script src="js/charts.js?v=1.60"></script>
<script src="js/calendar.js?v=1.60"></script>
<script src="js/stats.js?v=1.60"></script>
<script src="js/monthly.js?v=1.60"></script>
<script src="js/redispatch.js?v=1.60"></script>
<script src="js/import.js?v=1.60"></script>
<script src="js/networks.js?v=1.60"></script>
<script src="js/estimate.js?v=1.60"></script>
<script src="js/solar.js?v=1.60"></script>
<script src="js/planner.js?v=1.60"></script>
<script src="js/planner-ui.js?v=1.60"></script>
<script src="js/app.js?v=1.60"></script>
</body>
</html>
2 changes: 1 addition & 1 deletion js/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// Compare "This month" when the page opens with a saved API key (a test can preset
// window.Amber.AUTO_COMPARE = false to start idle).
Amber.AUTO_COMPARE = Amber.AUTO_COMPARE !== false;
Amber.APP_VERSION = '1.59';
Amber.APP_VERSION = '1.60';
Amber.TEMPLATES_AS_AT = '2026-07-01';
// All editable rates (plans, custom, Amber fixed charges) are GST-inclusive,
// matching Energy Made Easy, DMO/VDO, retailer fact sheets and Amber perKwh.
Expand Down
11 changes: 10 additions & 1 deletion js/planner-ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -1115,6 +1115,7 @@
const high = Amber.plannerFinance(scaled(x.opt, 1.2), r.f);
const series = (fin) => [-fin.capex.total].concat(fin.flows.map((y) => y.cumulative));
const labels = ['Now'].concat(x.fin.flows.map((y) => `Year ${y.year}`));
const isZero = (c) => !!(c.tick && Math.abs(c.tick.value) < 1e-6);
if (typeof Chart === 'function') {
ui.chart = new Chart(canvas.getContext('2d'), {
type: 'line',
Expand All @@ -1130,7 +1131,15 @@
responsive: true,
maintainAspectRatio: false,
interaction: { mode: 'index', intersect: false },
scales: { y: { title: { display: true, text: 'Money ahead ($)' }, ticks: { callback: (v) => dollars(v) } } },
// $0 is where it's paid back: always on the chart, and a darker, thicker line.
scales: {
y: {
suggestedMax: 0,
title: { display: true, text: 'Money ahead ($)' },
ticks: { callback: (v) => dollars(v), font: (c) => ({ weight: isZero(c) ? 'bold' : 'normal' }), color: (c) => (isZero(c) ? '#111827' : '#666') },
grid: { color: (c) => (isZero(c) ? '#374151' : 'rgba(0, 0, 0, 0.1)'), lineWidth: (c) => (isZero(c) ? 2 : 1) }
}
},
plugins: { tooltip: { callbacks: { label: (c) => `${c.dataset.label}: ${dollars(c.parsed.y)}` } } }
}
});
Expand Down
7 changes: 7 additions & 0 deletions tests/planner.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,13 @@ test('Solar & battery: a NEM12 household gets a best setup, its year month by mo
await expect(monthRows).toHaveCount(13);
await expect(monthRows.last()).toContainText('Year');
await expect(page.locator('#planPaybackNotes')).toContainText('Paid');
// The $0 line (paid back) stands out on the payback chart.
const zeroLine = await page.evaluate(() => {
const s = window.Chart.getChart('planPaybackChart').scales.y;
const i = s.ticks.findIndex((t) => t.value === 0);
return i < 0 ? null : s.options.grid.setContext(s.getContext(i)).lineWidth;
});
expect(zeroLine).toBe(2);

// Check the numbers: averages for the yardstick and the option, prices through the day, the settings used.
const averages = page.locator('#planNumbersBody [data-numbers="averages"]');
Expand Down
2 changes: 1 addition & 1 deletion tests/unit-time.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ const clockA = Amber.clockPartsForItem(clockItem, vicLocal, 'VIC');
const clockB = Amber.clockPartsForItem(clockItem, vicLocal, 'VIC');
assert(clockA === clockB, 'clock parts are cached on the usage item');
assert(clockA.hours === 17, 'cached Melbourne DST hour is 17');
assert(Amber.APP_VERSION === '1.59', 'app version is 1.59');
assert(Amber.APP_VERSION === '1.60', 'app version is 1.60');
assert(Amber.DEFAULT_AMBER_CONNECTION_CENTS === 125.367 && Amber.PREVIOUS_AMBER_CONNECTION_CENTS === 116.787, 'Amber daily connection defaults (2026-27 and 2025-26)');
assert(Amber.DEFAULT_AMBER_SUBSCRIPTION_CENTS === 82.181, 'Amber subscription default');
assert(Amber.DEFAULT_AMBER_DEMAND_CENTS === 43.431 && Amber.PREVIOUS_AMBER_DEMAND_CENTS === 42.345, 'Amber demand defaults (2026-27 and 2025-26)');
Expand Down
Loading