Skip to content

feat(json): emit Decimals as exact number literals (frontend-decimal epic, step A)#215

Merged
robcohen merged 1 commit into
mainfrom
feat/exact-decimal-json
Jul 1, 2026
Merged

feat(json): emit Decimals as exact number literals (frontend-decimal epic, step A)#215
robcohen merged 1 commit into
mainfrom
feat/exact-decimal-json

Conversation

@robcohen

@robcohen robcohen commented Jul 1, 2026

Copy link
Copy Markdown
Member

Step A of the frontend-decimal epic — closes the wire half of C2 and fixes R8, and it landed far smaller than the original "regenerate 53 snapshots" estimate.

What

The JSON provider serialized every Decimal via float() (core/charts.py), so monetary values crossed the wire as lossy float64. Switch dumps to simplejson with use_decimal=True → Decimals emit as exact number literals (88.571428571428571428571428571 instead of 88.57142857142857).

  • Exact wire — a decimal-aware API consumer recovers the exact ledger value; Python's lossy float() intermediate is gone.
  • R8 fixed — custom/budget amount values are now exact, consistent with Balance/Price (which already stringified).
  • Transparent to the frontend — exact literals are valid JSON numbers, so JSON.parse still reads them (float64 for display, same as today). No frontend change, no snapshot churn (the snapshot fixture re-parses to float before comparing).

Scope & verification

Diff is 3 files (charts.py, pyproject.toml, uv.lock) + the boundary test. Full py suite 100% coverage, 544 passed; JS 86 pass; just mypy clean.

test_number_precision_boundary rewritten: every value now crosses the wire exactly (the old high-precision xfail flips to passing), with one test documenting the residual — a plain JSON.parse/json.loads read still narrows to float64.

To weigh

  • New core dependency: simplejson (+ types-simplejson stub). It's the clean way to emit exact Decimal literals (stdlib json can't — its C encoder ignores a float subclass's __repr__). loads stays stdlib json, so request parsing is unaffected.
  • Step B (exact numbers displayed in the browser) is the remaining, larger part: a frontend decimal library on the read side. Lower value since the UI already rounds normal values for display; worth doing only if high-precision display matters.

🤖 Generated with Claude Code

…epic, step A)

The JSON provider serialized every Decimal via float() (core/charts.py), so
monetary values crossed the wire as lossy float64 — visible to raw API
consumers, exports, and high-precision values, and it leaked custom/budget
amounts as float (R8).

Switch dumps to simplejson with use_decimal=True: Decimals now emit as exact
number literals (100.00, 88.571428571428571428571428571, ...). This is:
- exact on the wire — a decimal-aware consumer recovers the exact value;
- R8 fixed — custom amount values are exact, matching Balance/Price;
- transparent to the frontend — the literals are valid JSON numbers, so
  JSON.parse still reads them (as float64 for display); no frontend change and
  no snapshot churn (the snapshot fixture re-parses to float before comparing).

loads stays stdlib json (request parsing unaffected). The residual float64
narrowing on a plain-JSON.parse read is the frontend-decimal step (step B),
tracked separately. Adds simplejson as a core dep + types-simplejson stub.

test_number_precision_boundary rewritten: every value now crosses the wire
exactly (the old high-precision xfail flips), with a test documenting the
remaining float-parse narrowing on the read side.
@robcohen robcohen merged commit 9ffb67d into main Jul 1, 2026
22 of 24 checks passed
@robcohen robcohen deleted the feat/exact-decimal-json branch July 1, 2026 15:40
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