Skip to content

audit: formatter edge cases — $1000K rollover, ASCII negative signs, asymmetric half-bucket rounding #32

Description

@tkowalczyk

Evidence (all reproduced by executing the real functions)

src/lib/statement-format/usd-compact.ts:

  • formatUsdCompact(999_999)"$1000K" (and 999_950"$1000K") — should roll over to "$1M".
  • formatUsdCompact(-50_000)"$-50K", -1_500_000"$-1.5M", -2_143"$-2,143" — ASCII hyphen inside the dollar amount, inconsistent with formatSignedUsd's typographic −$ convention (src/lib/statement-format/signed-usd.ts).

src/lib/statement-format/bucketed-usd.ts:

  • Half-bucket rounding is asymmetric: formatBucketedUsd(250, 500)"~+$500" but formatBucketedUsd(-250, 500)"<$500" (because Math.round(-0.5) rounds toward +∞). Positive and negative PnL of equal magnitude disclose differently — for a disclosure-sensitive surface this should be sign-symmetric (round half away from zero).
  • "<$500" for a negative near-zero value reads as "small positive". The live site currently shows <$25 for wallets that lost money (e.g. wallet_254c at −1.60, wallet_e68b at −6.25 on /statements/2026-05-31-weekly). Consider ±<$25 or ≈$0 wording.
  • bucketUsdToNearest(-1.6, 25) returns -0 (negative zero) — harmless today (-0 === 0), worth normalizing while in there.

TDD plan (failing tests first)

Extend the existing test files (usd-compact.test.ts, bucketed-usd.test.ts):

  1. formatUsdCompact: 999_999 → "$1M", 999_950 → "$1M", -999_999 → "−$1M", -50_000 → "−$50K", -2_143 → "−$2,143", -12_500 → "−$12.5K" (decide sign-then-dollar order to match formatSignedUsd).
  2. bucketUsdToNearest: (-250, 500) → -500 (half away from zero); (250, 500) → 500; Object.is(bucketUsdToNearest(-1.6, 25), 0) (no -0).
  3. formatBucketedUsd: symmetric output for ±250; chosen wording for negative sub-bucket values.

Note: current tests only cover positive values ≥ 0 and miss the 999_999 boundary entirely — that's the gap that let these ship.

Acceptance criteria

AC Test
K→M rollover at the formatting boundary unit
Single minus-sign convention across all USD formatters unit (assert the exact glyph)
Sign-symmetric bucketing unit

Metadata

Metadata

Assignees

No one assigned

    Labels

    afkAgent-grabbable: implementable without further inputbugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions