diff --git a/.changeset/loadmodel-outlier-lockout.md b/.changeset/loadmodel-outlier-lockout.md deleted file mode 100644 index f4c95218..00000000 --- a/.changeset/loadmodel-outlier-lockout.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -"ftw": patch ---- - -Load model: replace the MAE-band outlier filter with a physical ceiling -taken from the main fuse. The band rejected the upper half of the real -load distribution, and could lock the model out of a level it had not seen -before, permanently. - -A rejected sample updates neither `MAE` nor `Samples`, so the band -`max(MAE × 10, 200)` never grew in response to being persistently wrong. -Measured on a clean model: an hour at 400 W left MAE at 57 W and the band -at 570 W; a following week at 5 kW was rejected in full, 100% of samples, -and the prediction never moved off 1794 W. - -The band was the wrong instrument, not merely mistuned. Household load is -multimodal — a few hundred watts of baseline, then 11 kW when the sauna, -oven and car overlap — and nothing about a residual's size separates -"unusual but real" from "wrong". A band fitted to the quiet hours always -excludes the busy ones. Short-term measurement noise is already handled a -layer down, by the Kalman filter in telemetry whose smoothed output this -model reads, so the second filter was both harmful and redundant. - -What remains is the rejection physics licenses: a house cannot draw more -than its main fuse passes. The ceiling is `fuse capacity × 1.25`, passed in -from configuration and never derived from what the model has learned, so it -holds from the first sample and cannot be talked down by a model that has -mislearned. With no fuse configured the check disables itself rather than -inventing a limit. - -A sustained shift to a new load level is now learned directly. A one-minute -spike still trains — it is real consumption — and the bucket EMA damps it -to a tenth of the gap, which is what keeps an hour from being defined by -its loudest minute. diff --git a/.changeset/support-help-report.md b/.changeset/support-help-report.md deleted file mode 100644 index 9ee3e457..00000000 --- a/.changeset/support-help-report.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -"ftw": minor ---- - -Help report: `GET /api/support/report` returns a single Markdown file -describing what FTW is doing and why, reachable from a "Something looks -wrong?" button under the plan chart and from any driver's Diagnose modal. -It exists because answering "why is it discharging when the plan says -charge?" currently takes a screenshot round-trip per question — one thread -needed nine of them to establish that a load forecast said 383 W while the -house was drawing 7.9 kW. - -The file opens with `Findings`: automated checks written in the order a -person would run them — stale plan, fallback solver, offline or faulted -devices, active safety limits, and a forecast-versus-reality comparison for -both load and solar. Under that come the live site state, the plan slot -covering *this moment* (stated before anything about the next one, since -confusing the two is what sends these threads sideways), the surrounding -slots with the solver's reasoning, forecast accuracy, driver health, -component versions, and the recent warning and error log. - -It is one file rather than a bundle because people paste it into a chat and -ask for help there; a tarball of JSON does not survive that. The plan window -is trimmed to a few hours either side of now and logs to warnings and errors, -which keeps it small enough to upload and to read in full. - -`/api/support/dump` is unchanged and remains the deep bundle for cases the -report cannot close. diff --git a/CHANGELOG.md b/CHANGELOG.md index d0ce5f8b..0b43a9b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,67 @@ # Changelog +## 1.15.0 + +### Minor Changes + +- 4212dbb: Help report: `GET /api/support/report` returns a single Markdown file + describing what FTW is doing and why, reachable from a "Something looks + wrong?" button under the plan chart and from any driver's Diagnose modal. + It exists because answering "why is it discharging when the plan says + charge?" currently takes a screenshot round-trip per question — one thread + needed nine of them to establish that a load forecast said 383 W while the + house was drawing 7.9 kW. + + The file opens with `Findings`: automated checks written in the order a + person would run them — stale plan, fallback solver, offline or faulted + devices, active safety limits, and a forecast-versus-reality comparison for + both load and solar. Under that come the live site state, the plan slot + covering _this moment_ (stated before anything about the next one, since + confusing the two is what sends these threads sideways), the surrounding + slots with the solver's reasoning, forecast accuracy, driver health, + component versions, and the recent warning and error log. + + It is one file rather than a bundle because people paste it into a chat and + ask for help there; a tarball of JSON does not survive that. The plan window + is trimmed to a few hours either side of now and logs to warnings and errors, + which keeps it small enough to upload and to read in full. + + `/api/support/dump` is unchanged and remains the deep bundle for cases the + report cannot close. + +### Patch Changes + +- 41e59ef: Load model: replace the MAE-band outlier filter with a physical ceiling + taken from the main fuse. The band rejected the upper half of the real + load distribution, and could lock the model out of a level it had not seen + before, permanently. + + A rejected sample updates neither `MAE` nor `Samples`, so the band + `max(MAE × 10, 200)` never grew in response to being persistently wrong. + Measured on a clean model: an hour at 400 W left MAE at 57 W and the band + at 570 W; a following week at 5 kW was rejected in full, 100% of samples, + and the prediction never moved off 1794 W. + + The band was the wrong instrument, not merely mistuned. Household load is + multimodal — a few hundred watts of baseline, then 11 kW when the sauna, + oven and car overlap — and nothing about a residual's size separates + "unusual but real" from "wrong". A band fitted to the quiet hours always + excludes the busy ones. Short-term measurement noise is already handled a + layer down, by the Kalman filter in telemetry whose smoothed output this + model reads, so the second filter was both harmful and redundant. + + What remains is the rejection physics licenses: a house cannot draw more + than its main fuse passes. The ceiling is `fuse capacity × 1.25`, passed in + from configuration and never derived from what the model has learned, so it + holds from the first sample and cannot be talked down by a model that has + mislearned. With no fuse configured the check disables itself rather than + inventing a limit. + + A sustained shift to a new load level is now learned directly. A one-minute + spike still trains — it is real consumption — and the bucket EMA damps it + to a tenth of the gap, which is what keeps an hour from being defined by + its loudest minute. + ## 1.14.0 ### Minor Changes diff --git a/package-lock.json b/package-lock.json index 724291b3..e182cb75 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ftw", - "version": "1.14.0", + "version": "1.15.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ftw", - "version": "1.14.0", + "version": "1.15.0", "devDependencies": { "@changesets/cli": "^2.27.10" } diff --git a/package.json b/package.json index 01f1c76c..4415a636 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ftw", - "version": "1.14.0", + "version": "1.15.0", "private": true, "type": "module", "description": "FTW — local-first home energy coordination. Version metadata only; the runtime is Go and the mathematical planner is Python/CVXPY.",