feat(earnings): running yesterday/7d/30d confirmed earnings on the dashboard and in Telegram (#787) - #789
Conversation
…shboard and in Telegram (#787) The earnings card and /earnings only ever answered "what should this hashrate earn?". The confirmed-payout side (#381/#462) already recorded what actually arrived, but only as 24h / 7d / all-time totals, and only on the dashboard. Adds the running windows an operator actually checks: yesterday, 7d, 30d, on both surfaces. - Move the payout roll-up out of web/views.py into service/earnings.py as confirmed_payouts_summary, so the dashboard card and the bot read one implementation instead of each rolling their own (#61/#387). - "Yesterday" is the previous full *calendar* day in the dashboard container's timezone — the same clock the daily summary fires on — not a trailing 24h. The day boundary steps back through noon so a 23/25-hour DST day still lands on yesterday's date. - Mark a running window partial when it reaches back further than the oldest payout on record, with a footnote naming where the history starts. It is a "may be incomplete" signal: stored payouts alone can't tell "nothing arrived" apart from "we weren't watching yet", so it over-warns rather than presenting a short window as a full one. - /earnings appends the confirmed totals per chain. They come off the wallet, not the network figures, so they still report when the estimate is waiting on network data. - Add format_xtm, mirroring formatXtm in logic.mjs, so a confirmed Tari total reads the same in the bot as on the card. Tier 1 throughout (the lowest tier that proves it): window math and the DST boundary in test_earnings.py, the bot's rendering and config gating in test_telegram_commands.py, the card's partial marking in components.test.mjs. Patch coverage 100%. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y6dPxWC8EVdCQNgyyR3C9x
|
CI is 15/16 green. The one red check — Secret scan (gitleaks) — is not caused by anything in this PR, and I'm not fixing it here because the fix isn't mine to make. Details so it's actionable: It isn't this branch. Scanning only this PR's commit is clean: Reproducing CI's full-history scan locally matches its numbers exactly (574 commits, 1 leak), and the finding resolves to:
Why it lands on unrelated PRs. The job checks out with What it actually is. A hard-coded fixture password in a frontend unit test, asserting that the wizard's handoff card renders the credentials it was handed. It's a literal in a test file, not a credential to any live system — but it's high-entropy and shaped exactly like a generated password, which is what trips Two ways to clear it, both outside this PR's scope:
I'd suggest (1) — (2) widens a shared security config to accommodate one test, and this PR touches only Python, Nothing here blocks review of this diff; the check should go green on a re-run once that branch's fixture is dealt with. Generated by Claude Code |
What & why
Everything earnings-related was a forward-looking estimate —
service/earnings.py's linear rate, rendered by the dashboard calculator and/earnings. The confirmed-payout side (#381/#462) already recorded what actually arrived, but only as 24h / 7d / all-time totals, and only on the dashboard. There was no way to answer "what did I actually earn yesterday / this week / this month?" without opening a wallet.This adds the running windows an operator actually checks — yesterday, 7d, 30d — on both surfaces, beside the existing figures.
One roll-up, two surfaces.
_confirmed_payouts_summarymoves out ofweb/views.pyintoservice/earnings.pyas publicconfirmed_payouts_summary. The dashboard card and the Telegram bot now read the same implementation instead of each rolling their own, so the two can't drift apart (#61, and the class of problem #387 tracks)."Yesterday" is a calendar day, not a trailing 24h. Midnight-to-midnight in the dashboard container's timezone (
dashboard.timezone) — the same clock the daily summary fires on and the chart's payout dates are stamped in. The boundary steps back through noon rather than subtracting 86 400 from today's midnight, so a 23- or 25-hour DST day still lands on yesterday's date.YesterdayandConfirmed 24hare deliberately different spans and will disagree during the day; the docs table says so.Partial windows are marked, never presented as full. A running window that reaches back further than the oldest payout on record gets a
*and a footnote naming where the recorded history starts. This is a may be incomplete signal, not is incomplete: the payouts table alone can't tell "no payout arrived" apart from "we weren't watching yet", so a wallet that genuinely earned nothing for six weeks is marked too. Over-warning is the safe direction. A fresh install marks every running window until history builds up behind it.Telegram.
/earningsappends the confirmed totals per chain under the estimate. They come off the wallet rather than the network figures, so they still report when the estimate is waiting on network data — the one case where the reply used to be a single "unavailable" line.Also adds
format_xtm(mirroringformatXtminlogic.mjs) so a confirmed Tari total reads identically in the bot and on the card. The existing Tari estimate line is untouched.Related issue
Closes #787
Checklist
make testpasses locally (lint + dashboard pytest ≥ coverage gate +pitheadshell suite + compose validation)pitheadand test scripts are shellcheck-clean (no new warnings)docs/(and the README, if relevant) are updated for any user-facing change, in the house voice (docs/dev/STYLE.md)docs/dev/testing-strategy.md); patch coverage clears the gateNotes on the checklist
lint-py,lint-js,lint-md,lint-docs-voice,test-dashboard(1630 passed, 96.89% total),test-frontend(264 passed),test-fakes(23 passed),test-patch-coverage(100%).test-stack/test-composeandlint-shdid not run in this environment —shellcheckisn't installed and the shell suite hits an unboundUSER. This diff touches no shell or compose files (Python,.mjs, and docs only), so those suites are unaffected; CI covers them.docs/dashboard.md(a table of what each confirmed figure sums, plus the partial-marking rule),docs/telegram.md(/earnings), and a scenario row indocs/dev/testing-strategy.md.CHANGELOG.mdentry — this repo rolls the changelog at release-prep, not per feature PR.Testing (tier 1 throughout — the lowest tier that proves it)
since_tsfrom positive stamps only, Tari divisor/keystests/service/test_earnings.py::TestConfirmedPayoutsSummarytests/service/test_earnings.py::TestPreviousLocalDay/earningsrendering, partial marking + footnote, survival without network data, per-chain config gatingtests/service/test_telegram_commands.pytests/frontend/components.test.mjsformat_xtmmirrorsformat_xmr's precision and edge casestests/helper/test_utils.py::TestFormatXtmGenerated by Claude Code