Skip to content

CSV export shows raw cents instead of a formatted amount #62

Open

Description

@Param-Harrison

What happened

The amount column in the CSV export (and the CLI's export command,
which shares the same code) prints the raw integer cents, e.g. 1000,
instead of a formatted decimal amount like 10.00.

Expected

The amount column should read 10.00, matching the money formatting
convention used everywhere else (the web page, the balances output).

Steps to reproduce

curl -s http://localhost:3200/api/groups/flat-4b/export.csv -H "Authorization: Bearer flat-priya"

or

bun run src/cli.ts export flat-4b

Look at the amount column: values like 4500, 1200, 3000 instead of
45.00, 12.00, 30.00.

Area

export

Severity

low

Notes for whoever picks this up

src/csv.ts's exportCsv builds the row with r.amountCents directly.
It should use formatCents from src/money/cents.ts (see the
handling-money skill: "format only at the edge").

Activity

  1. added
    factory:readyHuman marked this ready for the loop to pick up
    factory:triagingfactory-triage is classifying this issue
    and removed
    factory:readyHuman marked this ready for the loop to pick up
    on Oct 8, 2026
  2. Param-Harrison commented on Oct 8, 2026

    @Param-Harrison
    ContributorAuthor

    Triage

    Disposition: proceed
    Type: bug
    Risk: low
    Confidence: 0.9

    Done when: exportCsv emits amounts formatted via formatCents (e.g. 10.00), proven by a new test, and .factory/gates.sh passes.

    Files expected: src/csv.ts, tests/csv.test.ts (new)

    Gate level: full

  3. added
    factory:planningfactory-plan is writing the plan comment
    and removed
    factory:triagingfactory-triage is classifying this issue
    on Oct 8, 2026
  4. Param-Harrison commented on Oct 8, 2026

    @Param-Harrison
    ContributorAuthor

    Plan (rev 1)

    Goal: Make exportCsv write the amount column as a formatted decimal (formatCents, e.g. 10.00) instead of raw integer cents.

    Acceptance criteria

    • AC-1: Every data row from exportCsv has an amount field equal to formatCents(amountCents) for that expense, e.g. a 4500-cent expense exports as 45.00, not 4500. Checked by bun test tests/csv.test.ts (new test exportCsv formats the amount column with formatCents).
    • AC-2: The CSV header (date,payer,description,amount), the column order, the row count and the escaping of payer/description stay the same, and the CLI export command uses the same output. Checked by bun test tests/csv.test.ts tests/routes.test.ts tests/cli.test.ts.
    • AC-3: The full gate passes: typecheck, tests and skills validation. Checked by make check.

    Non-goals (binding — a diff that crosses one fails verify)

    • NG-1: No change to src/money/**. formatCents is reused exactly as it is.
    • NG-2: No change to the CSV header, column set or column order. No currency symbol or thousands separator is added.
    • NG-3: No change to src/routes/**, src/cli.ts, src/db.ts or any protected path (src/auth/**, .factory/**, .claude/**, .agents/**, .github/**).
    • NG-4: No change to csvEscape's behaviour. The separate gap where values containing \r are not quoted is left for another issue.
    • NG-5: No float arithmetic on money anywhere in the change.

    Files to touch

    • src/csv.ts: import formatCents from ./money and use formatCents(r.amountCents) in the row template.
    • tests/csv.test.ts (new): use an in-memory Database(":memory:") with initSchema and seedDemoData.

    Tests to write first

    • tests/csv.test.ts › exportCsv formats the amount column with formatCents: seeds the database, exports flat-4b, and asserts that every data row's last field matches /^-?\d+\.\d{2}$/ and equals formatCents of the stored cents, including a known value such as 45.00. It must fail on the current code, which writes 4500.
    • tests/csv.test.ts › exportCsv keeps the header and one row per expense: guards against regressions in the existing header and row count.

    Repo skills to apply

    handling-money: format to a decimal string only at the output boundary, and use integer cents everywhere else.

    Risk: low

    Charter low tier: "a change confined to a single non-protected module with no schema or API-shape change". Only src/csv.ts changes, plus a new test file. The CSV columns and the route stay the same; only the amount column's value representation changes. Nothing touches or comes near a protected path.

    Gate level: full

    Low risk: eligible for auto-approve if the toggle is on. Otherwise, reply
    /factory approve to proceed, or /factory revise <what to change>.

  5. added
    factory:buildingfactory-build is making the change on factory/issue-N
    and removed
    factory:planningfactory-plan is writing the plan comment
    on Oct 8, 2026
  6. Param-Harrison commented on Oct 8, 2026

    @Param-Harrison
    ContributorAuthor

    Status

    Stage: build — finished
    Progress: 3/5
    Elapsed: n/a
    Gate line: FACTORY_GATES: status=GREEN passed=3 failed=0 skipped=1 failed_gates=audit

    exportCsv now writes the amount column via formatCents (4500 → 45.00); the new tests/csv.test.ts failed first on 4500 and now passes. typecheck, test and skills gates pass. The advisory audit gate was skipped: it reports existing hono advisories, which this change doesn't touch.

  7. added a commit that references this issue on Oct 8, 2026
  8. added
    factory:verifyingfactory-verifier and factory-reviewer are checking the diff
    and removed
    factory:buildingfactory-build is making the change on factory/issue-N
    on Oct 8, 2026
  9. Param-Harrison commented on Oct 8, 2026

    @Param-Harrison
    ContributorAuthor

    Verdict: pass

    Acceptance criteria evidence

    • AC-1: pass. bun test tests/csv.test.ts gives 2 pass, 0 fail. The test exportCsv formats the amount column with formatCents checks every flat-4b row against formatCents of the stored cents (45.00, 12.00, 30.00) and the pattern ^-?\d+\.\d{2}$.
    • AC-2: pass. bun test tests/csv.test.ts tests/routes.test.ts tests/cli.test.ts gives 16 pass, 0 fail across 3 files. The header test passes, and the CLI (src/cli.ts:53) and the route (src/routes/export.ts) both still call exportCsv unchanged.
    • AC-3: pass. The runner's gate after build reported FACTORY_GATES: status=GREEN passed=3 failed=0 skipped=1 failed_gates=audit. That result was measured on tree 1742ad30d7ff7bc8db023c18804dcf62d200fcb5, which is the current HEAD^{tree}, so the gate was not re-run.

    The test that bites

    exportCsv > exportCsv formats the amount column with formatCents fails on the base branch. With src/csv.ts restored from main, it expected ["45.00","12.00","30.00"] but received ["4500","1200","3000"] (1 pass, 1 fail). It passes here (bun test tests/csv.test.ts → 2 pass, 0 fail). The fix was then restored and git status was clean.

    Reviewer findings

    • could, confidence 3, at tests/csv.test.ts:15-19. storedCents and fetchExpenseRows both order by created_at ASC with no tiebreaker. Seed timestamps are strictly increasing (src/seed.ts:58), so the two queries agree today, but rows with the same timestamp could make the test flaky. Fix: add , id as a secondary sort, or compare sorted arrays.
    • could, confidence 3, at tests/csv.test.ts. No test covers negative or sub-dollar amounts. formatCents already handles them correctly (-0.05, 0.05). Fix: add a test that inserts a 5-cent row and a negative row.

    Neither finding blocks this verdict.

    Non-goals respected

    • NG-1: src/money/** is untouched; formatCents is imported as it is.
    • NG-2: The header, column set and column order are unchanged. formatCents emits only [-]digits.dd, with no symbol or thousands separator.
    • NG-3: Only src/csv.ts and the new tests/csv.test.ts changed. No route, CLI, db or protected-path files were touched.
    • NG-4: csvEscape is unchanged.
    • NG-5: The change adds no arithmetic on money.

    Note: the factory-verifier subagent was denied Bash in this session, so verify ran its revert check and AC commands directly. The outputs above come from those runs.

  10. added
    factory:in-reviewDraft PR open, waiting on human review and merge
    and removed
    factory:verifyingfactory-verifier and factory-reviewer are checking the diff
    on Oct 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething behaves incorrectlyfactory:in-reviewDraft PR open, waiting on human review and merge

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions