Rebuild centjes-chart: pure report IR, diagrams backend, tests ## Summary Restructures the `centjes-chart` package around a **backend-agnostic, pure report** so the data pipeline is total and testable and rendering is fully isolated. Net diff is the `centjes-chart` package plus its wiring (no `Balance.hs` change — the earlier WIP re-export hack is reverted by this work). ## Architecture | Module | Role | |---|---| | `Centjes.Chart.Report` | Pure IR (`ChartReport`/`ChartSeries`), typed `ChartError` with a composing `ToReport`, total `produceChartReport`, `sampleStepFunction` | | `Centjes.Chart.Render` | The *only* place precise money becomes `Double`; stacked-area SVG via `Chart` + **`Chart-diagrams`** | | `Command/All` + `OptParse` | Wires Report→Render; currency / filter / output / title are settings | ## Highlights - **No partial functions** in the pipeline — `fromJust`/`minimum`/`maximum` replaced by typed `ChartError`s rendered through the usual diagnostic machinery. - **Exactness to the edge** — `really-safe-money` values flow into the IR; only the renderer converts to `Double`. - **Dropped the cairo C dependency** by rendering through the pure-Haskell `Chart-diagrams` backend (confirmed in both the stack snapshot and nixpkgs). - **No hardcoding** — currency, account filter (reusing `Centjes.Filter`), output path and title are CLI/config settings. - **Daily revaluation** — held balances are re-valued at each day's exchange rate; the per-day price graph is shared across accounts. ## Tests (new `centjes-chart-test`) - `sampleStepFunction` property tests (carry-forward invariants) - A valid `ChartReport` golden - Error goldens for every `ChartError` constructor: unknown-currency, no-data, missing-price ## Verification - `stack test --pedantic` — 7/7 - pre-commit (ormolu, hlint, hpack, cabal2nix, deadnix, nixpkgs-fmt) — pass - Nix `weeder-check` — compiles `-O2 -Werror`, no dead code - End-to-end: `centjes-chart all` writes a valid 1920x1080 SVG (incl. into a nonexistent output dir via `ensureDir`) ## Notes / out of scope - One chart kind today (assets stacked-area); the IR is built so additional kinds are mostly new `produce*Report` + subcommands. - Stacked-area assumes non-negative series; negative balances invert a band (inherent charting limitation, pre-existing).