Skip to content

feat(stats): show FTW Cloud's anonymous daily energy reports - #104

Merged
frahlg merged 3 commits into
mainfrom
feat/anonymous-energy-stats
Oct 24, 2026
Merged

frahlg merged 3 commits into
mainfrom
feat/anonymous-energy-stats

Conversation

@frahlg

@frahlg frahlg commented Oct 24, 2026 •

Copy link
Copy Markdown
Member

Why

FTW Cloud now publishes energy from anonymous daily reports, sent by each box through an independent Oblivious HTTP relay so that Cloud never learns which box sent which. The fleet report moves onto the same relay as a separate message; /v1/fleet/public, which the Worker already reads, does not change. This shows those sums in the "Energy via FTW Cloud" section of stats.ftw.energy. The hourly usage cells stay as they are, waiting for C8.

What changes

  • stats/src/energy.ts: collectEnergy reads https://cloud.ftw.energy/v1/energy/public with the other Cloud feeds at minute 23 (one more request in that budget: 10 of 50). parseEnergy is as strict as parseFleet: exact fields, schema ftw.energy-public/1, group size 5, meaning "daily reports, not homes", a fresh observed_at, closed dates only (four or more days back, at most 30), no repeated date, a null day all null, a counted day at least 5 reports with each sum a whole number from 0 to 1,000 kWh a report. Anything else stores nothing. A Cloud that does not serve the route yet (404) is recorded as awaiting source, not as a failed run.
  • D1 migration 0004_cloud_energy.sql: the latest copy only, pruned like the fleet view.
  • The page: a "Daily energy reports" block with the newest public day's report count and its solar, grid import and export, battery charge and discharge and vehicle sums, and the public days of the last 30 with their reports and solar total. The notes say a day appears once it closes for reports, needs at least 5, and that these are reports, not homes.
  • README and a patch changeset.
  • The page's copy about local boxes ("Local FTW users can choose to contribute") still describes the earlier question. Change it when a Core release with the one statistics answer ships, not before (feat(stats): one statistics answer, and anonymous daily reports through an OHTTP relay ftw#1820).

Not deployed. The Worker goes out by hand with wrangler.

Paired pull requests

This one can merge in any order: until Cloud serves the route, the page says the reports are still to come.

Evidence

  • npm run verify: svelte-check, 1,133 tests, the 32 Worker tests, the build and the Worker dry run pass.
  • New Worker tests: the parser refuses an extra field, another group size or meaning, a stale feed, a small count, values on a null day, a sum out of range, a fraction, an extra day field, an open date and a repeated date; a collected feed shows the newest day and the window; a 404 waits and an invalid body stores nothing; a redirect is refused without storing.
  • Reviewed in a browser: wrangler dev --local with a seeded feed, at desktop width and 375 px, no console errors.

After review

5f2b942:

  • The closed-day bound uses the earlier of the feed's stamp and the Worker's clock. A feed stamped ahead can no longer pass a day as closed one day early (test: 22:30 Worker time against a stamp of 00:30 the next day).
  • observed_at and each date must be strings; an array no longer passes through String().
  • The daily energy feed has its own freshness dot, "Daily energy", stale after 48 hours because Cloud adds a day once a day. The hourly usage dot is now "Hourly energy". A stale feed says so on the tiles and in the note, also when every day is withheld.
  • Sums Cloud withholds (feat: one Oblivious HTTP gateway for both anonymous daily reports ftw-cloud#50, 79caea0) parse as null. The 30-day window sums each field over the days that show it ("84 kWh solar on 1 of them").

npm run verify passes: 1,133 tests and 36 Worker tests. Reviewed again in a browser with wrangler dev --local: a stale, fully withheld feed, and a fresh one with withheld sums.

🤖 Generated with Claude Code

The "Energy via FTW Cloud" section shows the newest public day of the
anonymous daily energy reports: its report count and its solar, grid,
battery and vehicle sums, and the public days of the last 30. Labelled as
reports, not homes.

The Worker reads https://cloud.ftw.energy/v1/energy/public hourly with the
other Cloud feeds, parses it as strictly as the fleet feed (exact fields,
closed dates only, at least five reports or all null, sums in range) and
keeps only the latest copy in D1 (migration 0004). A Cloud that does not
serve the route yet is recorded as awaiting source, not as a failure.

Pairs with srcfl/ftw and srcfl/ftw-cloud feat/anonymous-energy-ohttp.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
ftw-webapp 5f2b942 Commit Preview URL

Branch Preview URL
Oct 24 2026, 07:05 AM

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-24T06:05:06.521888Z d1ca530 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

…hanged

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
frahlg added a commit that referenced this pull request Oct 24, 2026
The Worker reads FTW Cloud's internal feed (GET /v1/ops, with the
CLOUD_OPS_KEY secret) once an hour at minute 23 and keeps it in D1:
the service's hourly sums for 90 days, and the latest exact totals of
closed fleet and energy report days. The parser takes only the fields
ftw.cloud-ops/1 names, at every level, and refuses the whole answer
otherwise; a test holds it to a feed ftwcloudd wrote.

The private view shows boxes and phones connected, the share on QUIC,
data written this month against AWS's 100 GB of free data out, drops
by reason, push results, assistant calls by outcome, the slowest p99 of
the day, each deploy's drain, and the exact daily reports. Nothing in it
names a home, a device or an address. The public view is unchanged.

Stacked on #104, whose energy fields it shares.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… is stale

From review of #104:
- A feed stamped up to 2 h ahead of the Worker could pass a day as closed
  one day early. The closed-day bound now uses the earlier of the feed's
  stamp and the Worker's clock.
- observed_at and each date must be strings; an array no longer passes
  through String().
- The daily energy feed has its own freshness entry ("Daily energy", stale
  after 48 hours, since Cloud adds a day once a day); the hourly usage dot
  is now "Hourly energy". A stale feed says so in the note and on the tiles,
  also when every day is withheld.
- Cloud now withholds a sum that one to four of a day's reports had any of
  (srcfl/ftw-cloud#50). The parser takes null for such a sum, and the 30-day
  window sums each field over the days that show it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
frahlg added a commit that referenced this pull request Oct 24, 2026
The Worker reads FTW Cloud's internal feed (GET /v1/ops, with the
CLOUD_OPS_KEY secret) once an hour at minute 23 and keeps it in D1:
the service's hourly sums for 90 days, and the latest exact totals of
closed fleet and energy report days. The parser takes only the fields
ftw.cloud-ops/1 names, at every level, and refuses the whole answer
otherwise; a test holds it to a feed ftwcloudd wrote.

The private view shows boxes and phones connected, the share on QUIC,
data written this month against AWS's 100 GB of free data out, drops
by reason, push results, assistant calls by outcome, the slowest p99 of
the day, each deploy's drain, and the exact daily reports. Nothing in it
names a home, a device or an address. The public view is unchanged.

Stacked on #104, whose energy fields it shares.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
frahlg added a commit that referenced this pull request Oct 24, 2026
The public energy feed may now show null for a sum few reports had
(#104). The internal feed is exact and never does; its parser refuses a
null sum.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@frahlg
frahlg merged commit 37f3063 into main Oct 24, 2026
3 checks passed
frahlg added a commit that referenced this pull request Oct 24, 2026
* feat(stats): show FTW Cloud's anonymous daily energy reports

The "Energy via FTW Cloud" section shows the newest public day of the
anonymous daily energy reports: its report count and its solar, grid,
battery and vehicle sums, and the public days of the last 30. Labelled as
reports, not homes.

The Worker reads https://cloud.ftw.energy/v1/energy/public hourly with the
other Cloud feeds, parses it as strictly as the fleet feed (exact fields,
closed dates only, at least five reports or all null, sums in range) and
keeps only the latest copy in D1 (migration 0004). A Cloud that does not
serve the route yet is recorded as awaiting source, not as a failure.

Pairs with srcfl/ftw and srcfl/ftw-cloud feat/anonymous-energy-ohttp.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(stats): the fleet report takes the same relay, with its feed unchanged

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(stats): read the energy feed on the earlier clock and say when it is stale

From review of #104:
- A feed stamped up to 2 h ahead of the Worker could pass a day as closed
  one day early. The closed-day bound now uses the earlier of the feed's
  stamp and the Worker's clock.
- observed_at and each date must be strings; an array no longer passes
  through String().
- The daily energy feed has its own freshness entry ("Daily energy", stale
  after 48 hours, since Cloud adds a day once a day); the hourly usage dot
  is now "Hourly energy". A stale feed says so in the note and on the tiles,
  also when every day is withheld.
- Cloud now withholds a sum that one to four of a day's reports had any of
  (srcfl/ftw-cloud#50). The parser takes null for such a sum, and the 30-day
  window sums each field over the days that show it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(stats): show how FTW Cloud runs in the private view

The Worker reads FTW Cloud's internal feed (GET /v1/ops, with the
CLOUD_OPS_KEY secret) once an hour at minute 23 and keeps it in D1:
the service's hourly sums for 90 days, and the latest exact totals of
closed fleet and energy report days. The parser takes only the fields
ftw.cloud-ops/1 names, at every level, and refuses the whole answer
otherwise; a test holds it to a feed ftwcloudd wrote.

The private view shows boxes and phones connected, the share on QUIC,
data written this month against AWS's 100 GB of free data out, drops
by reason, push results, assistant calls by outcome, the slowest p99 of
the day, each deploy's drain, and the exact daily reports. Nothing in it
names a home, a device or an address. The public view is unchanged.

Stacked on #104, whose energy fields it shares.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(stats): show the fleet report's health counts in the private view

FTW Cloud's /v1/ops carries the health counts of ftw.fleet/2 reports in
its closed fleet days (srcfl/ftw-cloud#52, srcfl/ftw#1821): the slowest
plan and fallback plans of each box's last day, the share of failed
driver reads, and the newest update of its last week. The parser takes a
day with all four, each from its fixed list and none above the day's
reports, or a day with none, and refuses anything between. The private
view shows them beside the other labels. The fixture is ftwcloudd's own
answer with an ftw.fleet/2 report in it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(stats): read the slowest quantile right and keep the panel private

Review of #105:
- A quantile past ftwcloudd's last bucket comes as null beside a count.
  It was dropped or read as 0; it now reads as "over 10 s" (over_10s in
  the API), the slowest case, in every list and in forward_p99_ms.
- The slowest assistant call keeps to the 7 days the panel names.
- The public page no longer carries the panel's markup, copy or script.
  A test reads the public HTML.

Also: a week without hours says so instead of "Nothing dropped"; old
daily totals read stale; missing hours are gaps in the chart, with their
own tooltip; the shards of an hour are added together; data out says
how many of the month's hours it covers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(stats): the exact feed has no withheld energy sum

The public energy feed may now show null for a sum few reports had
(#104). The internal feed is exact and never does; its parser refuses a
null sum.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant