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
5 changes: 5 additions & 0 deletions .changeset/remove-home-use-timing-note.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ftw": patch
---

Remove the timing note under the Overview energy flow. Home use still shows current power, and the diagram still marks missing or paused data.
9 changes: 0 additions & 9 deletions web/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -565,15 +565,6 @@
}
var opts = energyFlowOpts(data); opts.live = lastFlowLive;
lastFlowReadings = window.ftwFlowReadingsFromStatus(data, opts);
var homeNote = document.getElementById("home-use-estimate");
if (!homeNote) {
homeNote = document.createElement("p");
homeNote.id = "home-use-estimate";
homeNote.className = "muted";
flowEl.insertAdjacentElement("afterend", homeNote);
}
homeNote.textContent = lastFlowReadings.homeEstimateNote || "";
homeNote.hidden = !homeNote.textContent;
(lastFlowReadings.planets || []).forEach(function (p) {
if (p.role !== "ev" || p.placeholder || !p.name) return;
var lpEv = loadpointsByDriver && loadpointsByDriver[p.name];
Expand Down
34 changes: 0 additions & 34 deletions web/components/energy-flow-readings.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,39 +39,6 @@ export function fmtKwhShort(kwh) {
return kwh.toFixed(2);
}

function usableHomeEstimate(estimate) {
const from = num(estimate?.from_ms), to = num(estimate?.to_ms);
const age = num(estimate?.end_age_ms);
return num(estimate?.watts) !== null && estimate.watts >= 0 &&
from !== null && to !== null && from > 0 && to > from && to - from <= 300000 &&
to < 8640000000000000 && age !== null && age >= 0 && age <= 180000;
}

export function homeEstimateNote(estimate, live = true, powerBalanceW = null) {
if (!estimate) return "";
if (!live) return "Home use estimate paused · no live data";
const current = powerBalanceW === null ? "" :
`Estimated home use ${(powerBalanceW / 1000).toFixed(1)} kW · latest power readings; device delays may differ.`;
if (current && !usableHomeEstimate(estimate)) return current;
if (num(estimate.watts) === null) {
if (estimate.reason === "charger_source_time_unavailable") return "Home use estimate unavailable · charger source time unknown";
const sourceAge = num(estimate.source_age_ms);
return "Home use estimate unavailable · waiting for source observations" +
(sourceAge > 0 ? ` · last source report ${Math.ceil(sourceAge / 1000)} s old` : "");
}
if (!usableHomeEstimate(estimate)) {
return "Home use estimate unavailable · readings are too old";
}
const from = new Date(estimate.from_ms), to = new Date(estimate.to_ms);
const options = {hour:"2-digit", minute:"2-digit", second:"2-digit", hourCycle:"h23", timeZoneName:"short"};
if (from.toDateString() !== to.toDateString()) Object.assign(options, {year:"numeric", month:"short", day:"numeric"});
const period = new Intl.DateTimeFormat("en-GB", options).formatRange(from, to);
const age = estimate.end_age_ms === 0 ? "ended now" : `ended ${Math.ceil(estimate.end_age_ms / 1000)} s ago`;
const basis = {counters:"energy counters", power_integral:"sampled power", mixed:"charger energy + sampled power"}[estimate.energy_basis] || "energy estimate";
const timing = estimate.timing_basis === "source" ? "Device clocks may differ." : "Includes arrival times; device delay is unknown.";
return `${current || "Home use unavailable · no current power balance."} Energy mean ${(estimate.watts / 1000).toFixed(1)} kW · ${Math.round((estimate.to_ms - estimate.from_ms) / 1000)} s window · ${period} · ${age} · ${basis}. ${timing}`;
}

function planetColor(role, watts, thresholdW) {
if (watts === undefined || watts === null) {
if (role === "grid" || role === "pv") return "var(--fg-muted)";
Expand Down Expand Up @@ -259,7 +226,6 @@ export function flowReadingsFromStatus(status, opts) {
return {
load: loadW === null ? null : loadW / 1000,
loadEstimated: hasEstimate,
homeEstimateNote: homeEstimateNote(status?.load_estimate, opts?.live !== false, powerBalanceW),
planets: withControlMarks(planets, status?.control_feedback, opts?.live !== false),
selfPoweredPctToday,
};
Expand Down
27 changes: 3 additions & 24 deletions web/energy-flow-readings.test.mjs
Original file line number Diff line number Diff line change
@@ -1,28 +1,17 @@
import assert from "node:assert/strict";
import { describe, it } from "node:test";

import { flowReadingsFromStatus, fmtKwhShort, homeEstimateNote } from "./components/energy-flow-readings.js";
import { flowReadingsFromStatus, fmtKwhShort } from "./components/energy-flow-readings.js";

it("shows current household power and keeps the energy mean with its own period and age", () => {
it("shows current household power without a note about the past energy mean", () => {
const estimate = {watts: 886, from_ms: Date.UTC(2026,9,18,10), to_ms: Date.UTC(2026,9,18,10,1), age_ms: 90000, end_age_ms:60000, energy_basis:'mixed', timing_basis: 'mixed'};
const status = {load_w: 1800, load_estimate: estimate, energy:{today:{load_wh:2000,import_wh:500,history_quality:'estimated'}}};
const result = flowReadingsFromStatus(status);
assert.equal(result.load, 1.8);
assert.equal(result.loadEstimated, true);
assert.equal(status.load_w, 1800);
assert.equal(result.selfPoweredPctToday, null);
assert.match(result.homeEstimateNote, /Estimated home use 1.8 kW/);
assert.match(result.homeEstimateNote, /Energy mean 0.9 kW/);
assert.match(result.homeEstimateNote, /60 s window/);
assert.match(result.homeEstimateNote, /\d{2}:00:00.+\d{2}:01:00/);
assert.match(result.homeEstimateNote, /ended 60 s ago/);
assert.match(result.homeEstimateNote, /charger energy \+ sampled power/);
assert.match(result.homeEstimateNote, /Includes arrival times/);
assert.match(homeEstimateNote(estimate, false), /paused/);
assert.match(homeEstimateNote({...estimate, end_age_ms: 181000}), /too old/);
assert.match(homeEstimateNote({...estimate, from_ms:estimate.to_ms-300000,age_ms:210000}), /300 s window/);
assert.match(homeEstimateNote({watts:null,reason:'charger_source_time_unavailable'}), /source time unknown/);
assert.match(homeEstimateNote({watts: null}), /unavailable/);
assert.equal("homeEstimateNote" in result, false);
assert.equal(flowReadingsFromStatus({...status,load_estimate:{watts:null}}).load, 1.8);
assert.equal(flowReadingsFromStatus({...status,load_estimate:{...estimate,end_age_ms:181000}}).load, 1.8);
assert.equal(flowReadingsFromStatus({...status,load_w:null}).load, null);
Expand All @@ -32,13 +21,8 @@ it("never lets a past zero mean overwrite a positive current Load", () => {
const status={load_w:1234,history_quality:"complete",load_estimate:{watts:0,from_ms:1000,to_ms:11000,end_age_ms:60000}};
const result=flowReadingsFromStatus(status);
assert.equal(result.load,1.234);
assert.match(result.homeEstimateNote,/Estimated home use 1.2 kW/);
assert.match(result.homeEstimateNote,/Energy mean 0.0 kW/);
assert.match(result.homeEstimateNote,/ended 60 s ago/);
const missing=flowReadingsFromStatus({...status,load_w:null,history_quality:"incomplete"});
assert.equal(missing.load,null);
assert.match(missing.homeEstimateNote,/no current power balance/);
assert.match(missing.homeEstimateNote,/Energy mean 0.0 kW/);
});

it("keeps a qualified power balance visible while the energy interval is unavailable", () => {
Expand All @@ -47,10 +31,6 @@ it("keeps a qualified power balance visible while the energy interval is unavail
const result = flowReadingsFromStatus(status);
assert.equal(result.load, 1.234);
assert.equal(result.loadEstimated, true);
assert.match(result.homeEstimateNote, /latest power readings/);
assert.match(result.homeEstimateNote, /device delays may differ/);
assert.doesNotMatch(result.homeEstimateNote, /\d+ s mean|unavailable|ended now/);
assert.match(flowReadingsFromStatus(status,{live:false}).homeEstimateNote, /paused/);
assert.equal(status.load_estimate.watts, null);
}
});
Expand All @@ -61,7 +41,6 @@ it("distinguishes a real zero balance from missing, invalid or stale inputs", ()
for (const value of [undefined, null, "0", NaN, Infinity, -1]) {
const result = flowReadingsFromStatus({...base,load_w:value});
assert.equal(result.load,null);
assert.match(result.homeEstimateNote,/unavailable/);
}
});

Expand Down
Loading