Skip to content

fix: fleet running costs subtracted a EUR tariff from a USD gross - #199

Merged
GeiserX merged 1 commit into
mainfrom
fix/beads-batch-16
Aug 4, 2026
Merged

fix: fleet running costs subtracted a EUR tariff from a USD gross#199
GeiserX merged 1 commit into
mainfrom
fix/beads-batch-16

Conversation

@GeiserX

@GeiserX GeiserX commented Aug 4, 2026

Copy link
Copy Markdown
Owner

The defect

Every gross in /api/fleet/economics comes from get_earned_by_platform, which is USD by contract. The electricity price came straight from the tariff config, in whatever currency the user set. machine_economics then does net = gross - cost.

Live, that rendered:

gross 5.00 (USD) − cost 14.24 (EUR, 65 W at €0.30/kWh) = net −9.24, verdict "losing money", with "turning it off would save that"

Advice to switch off hardware, wrong by the whole FX spread — the true net was about −$11.35, not −9.24 of any currency. With a weaker tariff currency the error is not 15% but orders of magnitude.

The payload also carried no currency field at all, and fleet.html rendered Number(v).toFixed(2) — bare numbers the user could not attribute to a unit, while those numbers silently mixed two.

The fix

Same design /api/earnings/net settled on in #196: convert the tariff to USD, so the endpoint stays canonical USD like the rest of the API and the frontend's display-currency layer renders it in whatever the viewer reads in.

When no rate is available the cost is None, not zero — a zero cost renders gross as net and overstates earnings, the mistake this module already guards against when no tariff is set at all. The payload says so explicitly (fx_unavailable, tariff_currency, cost_unavailable_reason).

formatCurrency is now exported on CP so fleet.html can reach the one function that knows the viewer's display currency. null still renders as an em dash, so "not reporting" stays distinct from "earns nothing".

Evidence

ruff check + ruff format --check clean, node --check clean, 2636 passed.

Negative control, each part of the fix separately:

reverted tests that fail
don't convert the tariff (the original bug) 2
treat a missing rate as a usable price 1
drop the currency label 1
restore the bare-number renderer 1

No overlap — each part is independently load-bearing.

Closes CashPilot-dlr

Every gross in /api/fleet/economics comes from get_earned_by_platform, which
is USD by contract. The electricity price came straight from the tariff config,
in whatever currency the user set, and machine_economics does net = gross-cost.

Live that rendered gross 5.00 (USD) minus cost 14.24 (EUR, 65 W at EUR
0.30/kWh) = net -9.24, verdict "losing money", with "turning it off would save
that" — advice to switch off hardware, wrong by the whole FX spread. The true
net was about -$11.35, not -9.24 of any currency. With a weaker tariff currency
the error is not 15% but orders of magnitude.

The payload also carried no currency field at all, and fleet.html rendered
Number(v).toFixed(2), so the user saw bare numbers they could not attribute to
a unit — while those numbers silently mixed two.

Fixed the way /api/earnings/net was in #196: convert the TARIFF to USD, so the
endpoint stays canonical USD like the rest of the API and the frontend's
display-currency layer renders it in whatever the viewer reads in. When no rate
is available the cost is None, not zero — a zero cost would render gross as net
and overstate earnings, which is the mistake this module already guards against
when no tariff is set at all.

formatCurrency is now exported on CP so fleet.html can reach the one function
that knows the viewer's display currency. null still renders as an em dash, so
"not reporting" stays distinct from "earns nothing".

Negative control, each part separately: not converting the tariff fails 2 tests;
treating a missing rate as usable fails 1; dropping the currency label fails 1;
restoring the bare-number renderer fails 1. Nothing overlaps.

Closes CashPilot-dlr
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@GeiserX, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 6 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: fc8cc090-d24d-45d1-98be-b31f278efa6d

📥 Commits

Reviewing files that changed from the base of the PR and between 52b6ad6 and 3fa0a74.

📒 Files selected for processing (4)
  • app/main.py
  • app/static/js/app.js
  • app/templates/fleet.html
  • tests/test_beads_batch_16.py

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Aug 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 95.08%. Comparing base (52b6ad6) to head (3fa0a74).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #199      +/-   ##
==========================================
+ Coverage   95.07%   95.08%   +0.01%     
==========================================
  Files          46       46              
  Lines        5912     5925      +13     
==========================================
+ Hits         5621     5634      +13     
  Misses        291      291              
Files with missing lines Coverage Δ
app/main.py 97.37% <100.00%> (+0.02%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@GeiserX
GeiserX merged commit c094d53 into main Aug 4, 2026
8 checks passed
@GeiserX
GeiserX deleted the fix/beads-batch-16 branch August 4, 2026 15:02
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