Skip to content

feat(earnings): running yesterday/7d/30d confirmed earnings on the dashboard and in Telegram (#787) - #789

Open
VijitSingh97 wants to merge 1 commit into
developfrom
claude/running-earnings-display-7ejqpj
Open

feat(earnings): running yesterday/7d/30d confirmed earnings on the dashboard and in Telegram (#787)#789
VijitSingh97 wants to merge 1 commit into
developfrom
claude/running-earnings-display-7ejqpj

Conversation

@VijitSingh97

Copy link
Copy Markdown
Collaborator

What & why

Everything earnings-related was a forward-looking estimateservice/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_summary moves out of web/views.py into service/earnings.py as public confirmed_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. Yesterday and Confirmed 24h are 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. /earnings appends 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 (mirroring formatXtm in logic.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 test passes locally (lint + dashboard pytest ≥ coverage gate + pithead shell suite + compose validation)
  • pithead and test scripts are shellcheck-clean (no new warnings)
  • Docs in docs/ (and the README, if relevant) are updated for any user-facing change, in the house voice (docs/dev/STYLE.md)
  • New/changed behaviour is tested at the right tier (docs/dev/testing-strategy.md); patch coverage clears the gate
  • This PR is focused on a single logical change
  • A linked issue exists for non-trivial changes

Notes on the checklist

  • Ran locally: 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-compose and lint-sh did not run in this environment — shellcheck isn't installed and the shell suite hits an unbound USER. This diff touches no shell or compose files (Python, .mjs, and docs only), so those suites are unaffected; CI covers them.
  • Docs: docs/dashboard.md (a table of what each confirmed figure sums, plus the partial-marking rule), docs/telegram.md (/earnings), and a scenario row in docs/dev/testing-strategy.md.
  • No CHANGELOG.md entry — this repo rolls the changelog at release-prep, not per feature PR.

Testing (tier 1 throughout — the lowest tier that proves it)

What Where
Window math: trailing 24h/7d/30d buckets, yesterday as a half-open calendar day, since_ts from positive stamps only, Tari divisor/keys tests/service/test_earnings.py::TestConfirmedPayoutsSummary
Day boundary, including a real Europe/London spring-forward (asserts the 23-hour day) tests/service/test_earnings.py::TestPreviousLocalDay
/earnings rendering, partial marking + footnote, survival without network data, per-chain config gating tests/service/test_telegram_commands.py
Card renders the new windows; only flagged windows carry the marker; empty-history wording tests/frontend/components.test.mjs
format_xtm mirrors format_xmr's precision and edge cases tests/helper/test_utils.py::TestFormatXtm

Generated by Claude Code

…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

Copy link
Copy Markdown
Collaborator Author

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:

gitleaks git . --config .gitleaks.toml --log-opts="origin/develop..HEAD"
  1 commits scanned.  no leaks found

Reproducing CI's full-history scan locally matches its numbers exactly (574 commits, 1 leak), and the finding resolves to:

  • Rule: generic-api-key
  • File: build/dashboard/tests/frontend/wizard.test.mjs:75
  • Commit: 7c6d4ec"feat(wizard): preflight remote nodes, and hand the credentials over ON the page"
  • Branch: feat/phase2-bakery-image only — git merge-base --is-ancestor confirms it is not on develop and not an ancestor of this branch

Why it lands on unrelated PRs. The job checks out with fetch-depth: 0 and gitleaks git scans every ref, not just the PR's commits. So any commit on any pushed branch fails the check for everyone. The dependabot PR that ran 20 minutes before this one (run 30384394008) failed on the same single job, with every other job green — same cause.

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 generic-api-key. Worth a glance to confirm it was never a real generated value before deciding how to clear it.

Two ways to clear it, both outside this PR's scope:

  1. On feat/phase2-bakery-image, swap the fixture for something obviously inert ("example-password-not-a-real-secret") and keep the assertions. No shared config changes, and the scanner keeps its teeth.
  2. Add an allowlist entry to .gitleaks.toml scoped to that path.

I'd suggest (1) — (2) widens a shared security config to accommodate one test, and this PR touches only Python, .mjs, and docs, so folding either into it would conflate two unrelated changes. Happy to push (1) as its own PR if you want it.

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

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.

Show actual running earnings (yesterday / 7-day / 30-day) in Telegram and the dashboard

2 participants