Add 'cycles/yr' column — total throughput including grid arbitrage
This commit is contained in:
parent
9d43e1802b
commit
1804606ff6
3 changed files with 16 additions and 6 deletions
|
|
@ -126,6 +126,7 @@ function renderTable(rows) {
|
||||||
td(`€${fmtNum(r.lp_year1, 0)}`),
|
td(`€${fmtNum(r.lp_year1, 0)}`),
|
||||||
td(fmtPayback(r.lp_payback)),
|
td(fmtPayback(r.lp_payback)),
|
||||||
td(`€${fmtNum(r.ten_year_profit_eur, 0)}`, { className: tenYrCls }),
|
td(`€${fmtNum(r.ten_year_profit_eur, 0)}`, { className: tenYrCls }),
|
||||||
|
td(fmtNum(r.cycles_per_year, 0)),
|
||||||
td(fmtNum(r.captured_kwh, 0)),
|
td(fmtNum(r.captured_kwh, 0)),
|
||||||
td(`${fmtNum(r.captured_pct, 0)}%`),
|
td(`${fmtNum(r.captured_pct, 0)}%`),
|
||||||
td(`${fmtNum(r.pct_hours_battery_full, 0)}%`),
|
td(`${fmtNum(r.pct_hours_battery_full, 0)}%`),
|
||||||
|
|
@ -225,10 +226,11 @@ function captureInitialFromDOM() {
|
||||||
lp_year1: parseFloat(cells[5].textContent.replace("€", "")),
|
lp_year1: parseFloat(cells[5].textContent.replace("€", "")),
|
||||||
lp_payback: cells[6].textContent.trim() === "—" ? null : parseFloat(cells[6].textContent),
|
lp_payback: cells[6].textContent.trim() === "—" ? null : parseFloat(cells[6].textContent),
|
||||||
ten_year_profit_eur: parseFloat(cells[7].textContent.replace("€", "")),
|
ten_year_profit_eur: parseFloat(cells[7].textContent.replace("€", "")),
|
||||||
captured_kwh: parseFloat(cells[8].textContent),
|
cycles_per_year: parseFloat(cells[8].textContent),
|
||||||
captured_pct: parseFloat(cells[9].textContent),
|
captured_kwh: parseFloat(cells[9].textContent),
|
||||||
pct_hours_battery_full: parseFloat(cells[10].textContent),
|
captured_pct: parseFloat(cells[10].textContent),
|
||||||
pct_hours_charge_saturated: parseFloat(cells[11].textContent),
|
pct_hours_battery_full: parseFloat(cells[11].textContent),
|
||||||
|
pct_hours_charge_saturated: parseFloat(cells[12].textContent),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>Honest battery payback — vs thuisbatterijgids.nl</title>
|
<title>Honest battery payback — vs thuisbatterijgids.nl</title>
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}?v=13">
|
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}?v=14">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
|
@ -104,6 +104,7 @@
|
||||||
<th data-sort="lp_year1" data-default-dir="desc" title="Honest year-1 savings (our LP)">LP €/yr</th>
|
<th data-sort="lp_year1" data-default-dir="desc" title="Honest year-1 savings (our LP)">LP €/yr</th>
|
||||||
<th data-sort="lp_payback" data-default-dir="asc" title="Honest payback with inflation">LP yr</th>
|
<th data-sort="lp_payback" data-default-dir="asc" title="Honest payback with inflation">LP yr</th>
|
||||||
<th data-sort="ten_year_profit_eur" data-default-dir="desc" title="Cumulative savings over 10 yrs (with inflation) minus the battery cost">10-yr net</th>
|
<th data-sort="ten_year_profit_eur" data-default-dir="desc" title="Cumulative savings over 10 yrs (with inflation) minus the battery cost">10-yr net</th>
|
||||||
|
<th data-sort="cycles_per_year" data-default-dir="desc" title="Total discharge throughput / capacity. Includes both surplus capture AND grid arbitrage.">cycles</th>
|
||||||
<th data-sort="captured_kwh" data-default-dir="desc" title="kWh of solar surplus the battery actually absorbed (vs total surplus shown above)">kWh capt</th>
|
<th data-sort="captured_kwh" data-default-dir="desc" title="kWh of solar surplus the battery actually absorbed (vs total surplus shown above)">kWh capt</th>
|
||||||
<th data-sort="captured_pct" data-default-dir="desc" title="Captured kWh as % of total available solar surplus">% capt</th>
|
<th data-sort="captured_pct" data-default-dir="desc" title="Captured kWh as % of total available solar surplus">% capt</th>
|
||||||
<th data-sort="pct_hours_battery_full" data-default-dir="desc" title="% of surplus hours where the battery was already full and couldn't store more">% full</th>
|
<th data-sort="pct_hours_battery_full" data-default-dir="desc" title="% of surplus hours where the battery was already full and couldn't store more">% full</th>
|
||||||
|
|
@ -121,6 +122,7 @@
|
||||||
<td>€{{ '%.0f' % r.lp_year1 }}</td>
|
<td>€{{ '%.0f' % r.lp_year1 }}</td>
|
||||||
<td>{% if r.lp_payback %}{{ '%.1f' % r.lp_payback }}{% else %}—{% endif %}</td>
|
<td>{% if r.lp_payback %}{{ '%.1f' % r.lp_payback }}{% else %}—{% endif %}</td>
|
||||||
<td class="{% if r.ten_year_profit_eur < 0 %}neg{% else %}pos{% endif %}">€{{ '%.0f' % r.ten_year_profit_eur }}</td>
|
<td class="{% if r.ten_year_profit_eur < 0 %}neg{% else %}pos{% endif %}">€{{ '%.0f' % r.ten_year_profit_eur }}</td>
|
||||||
|
<td>{{ '%.0f' % r.cycles_per_year }}</td>
|
||||||
<td>{{ '%.0f' % r.captured_kwh }}</td>
|
<td>{{ '%.0f' % r.captured_kwh }}</td>
|
||||||
<td>{{ '%.0f' % r.captured_pct }}%</td>
|
<td>{{ '%.0f' % r.captured_pct }}%</td>
|
||||||
<td>{{ '%.0f' % r.pct_hours_battery_full }}%</td>
|
<td>{{ '%.0f' % r.pct_hours_battery_full }}%</td>
|
||||||
|
|
@ -134,6 +136,7 @@
|
||||||
<dt>LP €/yr</dt> <dd>Honest year-1 savings from our linear-program oracle (perfect 24h foresight).</dd>
|
<dt>LP €/yr</dt> <dd>Honest year-1 savings from our linear-program oracle (perfect 24h foresight).</dd>
|
||||||
<dt>LP yr</dt> <dd>Years until cumulative savings (with the chosen inflation rate) recoup the battery cost.</dd>
|
<dt>LP yr</dt> <dd>Years until cumulative savings (with the chosen inflation rate) recoup the battery cost.</dd>
|
||||||
<dt>10-yr net</dt> <dd>Total profit over 10 years: cumulative savings (compounded by inflation) minus the battery cost. Red = doesn't break even within 10 yr.</dd>
|
<dt>10-yr net</dt> <dd>Total profit over 10 years: cumulative savings (compounded by inflation) minus the battery cost. Red = doesn't break even within 10 yr.</dd>
|
||||||
|
<dt>cycles</dt> <dd>Total discharge throughput per year, divided by capacity. Includes <em>both</em> surplus capture and grid arbitrage (charging during cheap hours from the grid even when no surplus is exporting). Useful as a "how busy is the battery" metric.</dd>
|
||||||
<dt>kWh capt</dt> <dd>kWh of capturable surplus (i.e. of grid exports that would happen without a battery) that this battery absorbs.</dd>
|
<dt>kWh capt</dt> <dd>kWh of capturable surplus (i.e. of grid exports that would happen without a battery) that this battery absorbs.</dd>
|
||||||
<dt>% capt</dt> <dd>That, as a percentage of the scenario's capturable surplus shown in the heading. <em>Note:</em> the denominator is exports, not gross PV — solar that's directly self-consumed during sunny hours never crosses the meter and is invisible to a battery.</dd>
|
<dt>% capt</dt> <dd>That, as a percentage of the scenario's capturable surplus shown in the heading. <em>Note:</em> the denominator is exports, not gross PV — solar that's directly self-consumed during sunny hours never crosses the meter and is invisible to a battery.</dd>
|
||||||
<dt>% full</dt> <dd>Of all surplus hours, the % when the battery was already full and couldn't store more — capacity-bottlenecked.</dd>
|
<dt>% full</dt> <dd>Of all surplus hours, the % when the battery was already full and couldn't store more — capacity-bottlenecked.</dd>
|
||||||
|
|
@ -152,6 +155,6 @@
|
||||||
</p>
|
</p>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<script src="{{ url_for('static', filename='app.js') }}?v=13"></script>
|
<script src="{{ url_for('static', filename='app.js') }}?v=14"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -124,11 +124,13 @@ def _worker_run_lp(spec):
|
||||||
pct_full = 0.0
|
pct_full = 0.0
|
||||||
pct_saturated = 0.0
|
pct_saturated = 0.0
|
||||||
|
|
||||||
|
discharge_total = float(out["discharge_kwh"].sum())
|
||||||
return spec, {
|
return spec, {
|
||||||
"year1": float(out["savings"].sum()),
|
"year1": float(out["savings"].sum()),
|
||||||
"captured_kwh": captured_kwh,
|
"captured_kwh": captured_kwh,
|
||||||
"pct_full": pct_full,
|
"pct_full": pct_full,
|
||||||
"pct_saturated": pct_saturated,
|
"pct_saturated": pct_saturated,
|
||||||
|
"discharge_kwh": discharge_total,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -240,6 +242,8 @@ def compute_leaderboard(
|
||||||
captured_kwh = payload["captured_kwh"]
|
captured_kwh = payload["captured_kwh"]
|
||||||
pct_full = payload["pct_full"]
|
pct_full = payload["pct_full"]
|
||||||
pct_saturated = payload["pct_saturated"]
|
pct_saturated = payload["pct_saturated"]
|
||||||
|
discharge_kwh = payload["discharge_kwh"]
|
||||||
|
cycles_per_year = discharge_kwh / cap if cap > 0 else 0.0
|
||||||
captured_pct = (captured_kwh / total_surplus_kwh * 100.0) if total_surplus_kwh > 0 else 0.0
|
captured_pct = (captured_kwh / total_surplus_kwh * 100.0) if total_surplus_kwh > 0 else 0.0
|
||||||
lp_payback = payback_years(lp_year1, price, inflation)
|
lp_payback = payback_years(lp_year1, price, inflation)
|
||||||
|
|
||||||
|
|
@ -272,6 +276,7 @@ def compute_leaderboard(
|
||||||
"lp_year1": round(lp_year1, 2),
|
"lp_year1": round(lp_year1, 2),
|
||||||
"lp_payback": round(lp_payback, 2) if np.isfinite(lp_payback) else None,
|
"lp_payback": round(lp_payback, 2) if np.isfinite(lp_payback) else None,
|
||||||
"ten_year_profit_eur": round(_lifetime_profit(lp_year1, price, 10, inflation), 2),
|
"ten_year_profit_eur": round(_lifetime_profit(lp_year1, price, 10, inflation), 2),
|
||||||
|
"cycles_per_year": round(cycles_per_year, 1),
|
||||||
"captured_kwh": round(captured_kwh, 1),
|
"captured_kwh": round(captured_kwh, 1),
|
||||||
"captured_pct": round(captured_pct, 1),
|
"captured_pct": round(captured_pct, 1),
|
||||||
"pct_hours_battery_full": round(pct_full, 1),
|
"pct_hours_battery_full": round(pct_full, 1),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue