Skip to content

test(precision): characterize and guard the Decimal->float JSON boundary#205

Closed
robcohen wants to merge 1 commit into
feat/192-expand-pads-drop-jsonrpcfrom
test/number-precision-boundary
Closed

test(precision): characterize and guard the Decimal->float JSON boundary#205
robcohen wants to merge 1 commit into
feat/192-expand-pads-drop-jsonrpcfrom
test/number-precision-boundary

Conversation

@robcohen

@robcohen robcohen commented Jul 1, 2026

Copy link
Copy Markdown
Member

Layer 2 of the correctness plan — pins the Decimal→float64 JSON boundary (C2 in the analysis).

What I found investigating C2

  • The JSON API serializes every Decimal via float() (core/charts.py:_json_default). But the frontend has no decimal type (Amount.number is a JS number) and renders through Intl.NumberFormat at display precision — so typical currency values are not visibly wrong in the rendered UI. C2 is therefore a data-fidelity defect (CSV/Excel export via util/excel.py, raw JSON API consumers, column sorting, and high-precision values), not a wrong-number-in-the-report defect.
  • Measured boundary: values up to ~16 significant digits round-trip exactly (all fiat, 8dp crypto); loss begins beyond that (division results like 620/7, very large magnitudes, e.g. 9999999999999999.01 → 1e16).
  • A clean exact-wire fix is not a safe standalone change: stdlib json can't emit exact decimal literals (its C encoder ignores a float subclass's __repr__), so it needs a new dependency (simplejson/use_decimal), it regenerates all 53 snapshots, and it only reaches the user once the frontend gains decimal support. That's an epic to scope deliberately, not an isolated PR.

What this PR does

Adds tests/test_number_precision_boundary.py to make the boundary explicit and regression-proof:

  • test_typical_values_are_lossless — currency/crypto/bounded magnitudes must cross the wire exactly (guards the common case).
  • test_high_precision_is_lostxfail(strict) documenting the loss beyond ~16 sig digits; flips green automatically when numbers are emitted exactly, so it's the acceptance test for the eventual fix.
  • test_api_numbers_are_json_numbers_not_strings — pins the current wire contract so a format switch can't happen silently.

9 passed, 3 xfailed; ruff + mypy clean.

Recommendation

Given the UI is protected by display rounding, I'd prioritize the genuinely-wrong-number risks next (silent mixed-currency conversion totals, and cost-lot identity/ghost lots) over the exact-wire epic. This PR locks C2's boundary in the meantime.

Stacked on #202 (base branch) for green CI while main is red from the auto-merged bump PRs; retarget to main after #202 merges.

🤖 Generated with Claude Code

@robcohen robcohen force-pushed the test/number-precision-boundary branch from 48868ed to 7eba21d Compare July 1, 2026 13:27
@robcohen robcohen force-pushed the feat/192-expand-pads-drop-jsonrpc branch from a343b17 to bb9790c Compare July 1, 2026 13:29
The JSON API serializes Decimals via float() (core/charts.py), so exact ledger
values cross the wire as float64. The frontend renders at display precision, so
typical currency values are unaffected in the UI — but exports, raw API
consumers, and high-precision values see the rounding.

Layer 2 of the correctness plan: pin the boundary so it can't regress and a fix
is measurable.
- test_typical_values_are_lossless: currency + 8dp crypto + bounded magnitudes
  round-trip exactly (guards the common case).
- test_high_precision_is_lost: xfail(strict) documenting that values beyond
  ~16 significant digits are rounded on the wire; flips green when numbers are
  emitted exactly (needs an exact JSON encoder + frontend decimal support, which
  also regenerates all snapshots — that epic, not an isolated wire change).
- test_api_numbers_are_json_numbers_not_strings: pins the current wire contract
  so any format switch is deliberate.
@robcohen robcohen force-pushed the test/number-precision-boundary branch from 7eba21d to 6fa8318 Compare July 1, 2026 13:30
@robcohen robcohen deleted the branch feat/192-expand-pads-drop-jsonrpc July 1, 2026 13:52
@robcohen robcohen closed this Jul 1, 2026
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