feat: v1.5.0 — savings goal, early payoff, FIRE calculators#57
feat: v1.5.0 — savings goal, early payoff, FIRE calculators#57
Conversation
- solveContributionForGoal / solveYearsToGoal — FV annuity solver for reaching a target either by contribution or by time. - earlyMortgagePayoff — repayment schedule with extra monthly payments and one-off lump sums; reports months and interest saved vs baseline. - fireNumber / yearsToFire — savings target from annual spend and withdrawal rate, and years-to-FIRE solver. - Shared toDecimalRate helper extracted to calc/helpers.ts. 45 tests, tsc clean, lint clean. Intended for v1.5.0.
New savings-goal, early-payoff, and FIRE calculators. See CHANGELOG.md.
There was a problem hiding this comment.
Code Review
This pull request introduces version 1.5.0 of the finance library, adding new calculators for savings goals, early mortgage payoffs, and FIRE targets, along with a shared rate normalization helper. The review feedback highlights several mathematical and financial edge cases to address, including preventing potential NaN values in logarithmic formulas, minimizing cumulative rounding errors by using unrounded intermediate values, and refining input validation for withdrawal rates and interest rate heuristics.
- prettier --check was failing on calc/earlyPayoff.test.ts; ran the formatter. - mortgageCalculator had two unreachable guards: 'Home value cannot be negative' and 'Deposit cannot be greater than home value' were both caught by the 'Principal cannot be negative' check before the specific messages could surface. Reordered the validation so user-facing guards fire before the derived 'principal < 0' check (which is now unreachable and removed). - Added tests for every thrown error message across all five calc modules: mortgage (7 guards), earlyPayoff (8 guards including lump sum shape), fireNumber.yearsToFire (4 guards), savingsGoal (7 guards including the zero-denominator path), compoundInterest (the debtRepayment + accrualOfPaymentsPerAnnum combination guard). - Widened vitest coverage excludes to actually skip index.ts, .eslintrc.*, *.config.* with proper glob patterns. Coverage: 90.47% -> 97.16% stmts, 75.54% -> 86.08% branches, 95.39% -> 99.58% lines. 45 -> 57 tests.
Review NIT: index.ts (re-export barrel) still showed in the coverage per-file table. Added /* istanbul ignore file */ and widened the config globs to types/** and **/index.ts. Aggregate coverage numbers unaffected.
There was a problem hiding this comment.
Pull request overview
Adds new financial calculators (savings-goal solvers, early mortgage payoff simulation, and FIRE calculators) and updates public exports/docs/tests for the v1.5.0 release.
Changes:
- Introduces new calculation modules:
savingsGoal,earlyPayoff,fireNumber, plus new exported APIs viaindex.ts. - Adds shared
toDecimalRatehelper and new types for the added calculators. - Expands tests, adjusts coverage excludes, and updates README/CHANGELOG/version for the v1.5.0 release.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| vitest.config.ts | Updates coverage exclude patterns. |
| types/calculator.ts | Adds option/result types for savings goal, early payoff, and FIRE calculators. |
| package.json | Bumps package version to 1.5.0. |
| index.ts | Exports the new calculator modules. |
| calc/savingsGoal.ts | Adds savings-goal inverse solvers (contribution and years). |
| calc/savingsGoal.test.ts | Adds unit tests for the savings-goal solvers. |
| calc/mortgageCalculator.ts | Refines input validation ordering/messages. |
| calc/mortgageCalculator.test.ts | Adds validation-focused tests. |
| calc/helpers.ts | Adds toDecimalRate helper for rate normalization. |
| calc/fireNumber.ts | Adds FIRE number + years-to-FIRE calculators. |
| calc/fireNumber.test.ts | Adds tests for FIRE calculators. |
| calc/earlyPayoff.ts | Adds early mortgage payoff simulation with extras/lump sums. |
| calc/earlyPayoff.test.ts | Adds tests for early payoff simulation. |
| calc/compoundInterest.test.ts | Adds coverage for an invalid option-combination guard. |
| README.md | Updates package overview and documents new APIs. |
| CHANGELOG.md | Adds v1.5.0 changelog entry. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Closes or supersedes Renovate PRs: - #56 typescript v6 (5.5 -> 6.0.3) - #55 vite-node v6 (3 -> 6) - #50 vitest v4 (3 -> 4.1.5) + @vitest/coverage-istanbul v4 - #46 / #51 node (20.19 -> 24) + CI matrix now [20, 22, 24] - #49 / #53 actions/setup-node + actions/checkout v4 -> v5 - #39 eslint-config-prettier v9 -> v10 - @types/node 22 -> 24 - esbuild added as explicit devDep (was transitive, removed by vitest 4) Surface fixes: - tsconfig.json: drop deprecated baseUrl + paths (no code used the @/* alias; TS 7 will drop the option entirely). - vitest.config.ts: switch from vite's defineConfig to vitest/config so the `test` key type-checks under TS 6. - calc/compoundInterest.test.ts: the interestOnly debtRepayment test was silently under-asserting the year-2 interestMatrix array (missing the last two months). vitest 4's toMatchObject is stricter on nested Maps and caught it. Added the missing 288769.25 + 290521 entries. Left alone: eslint v10 and typescript-eslint v8 require flat-config migration. Low reward for this small library. 57/57 tests pass, 97.44% stmts, 92.09% branches, 100% lines.
Addresses Gemini Code Assist + reviewer comments on PR #57. Calc bugs: - fireNumber yearsToFire: also check numerator <= 0 (was only checking denominator). Negative returns that outpace contributions would return NaN instead of throwing. - savingsGoal solveYearsToGoal: same numerator <= 0 check added. - savingsGoal solveYearsToGoal: the zero-rate branch returned an unrounded float; the non-zero branch rounded to 2dp. Rounded both for consistency. - savingsGoal solveContributionForGoal: interestEarned could go negative when the starting balance already outgrew the target. Clamp to 0 and compute from growthOfStartingBalance in that case. - fireNumber: withdrawalRate == 1.0 is now accepted (one year of expenses saved). Previous `>= 1` guard rejected a legitimate edge case. - earlyPayoff baselineTotalInterest: use the exact PMT value for the internal calculation instead of the 2dp-rounded display value. Cumulative rounding was distorting the baseline total over a 300-month term. - earlyPayoff: reject non-integer lump sum months (they'd never match the whole-month schedule), and reject terms longer than the 100-year simulation cap. - earlyPayoff: guard against negative amortization (scheduled payment <= monthly interest) so the loan can't silently fail to pay off. - earlyPayoff: throw if the simulation hits MAX_MONTHS with a non-zero balance, instead of returning a misleading "paid off" result. Defensive — should be unreachable given the years guard + PMT properties — but protects future edits. Docs: - README: scope the "rates accept percentage or decimal" claim to the three new calculators. mortgageCalculator + compoundInterest- PerPeriod still take percentages only. - CHANGELOG: same scoping clarification on the toDecimalRate note. Left unchanged: - helpers.ts toDecimalRate 0/1 boundary heuristic (reviewer flagged as ambiguous). Fixing properly means a breaking API shape change; queued as a minor-bump follow-up. Test deltas: +4 (total 61), coverage 96.45% → 96.8% stmts, 91.19% → 91.7% branches.
pnpm-lock.yaml was created by the sibling finance-calculator repo's pnpm link ../compound-interest during local iteration. This library uses npm; add the lockfile to .gitignore and untrack it.
|
Addressed all review feedback in 6fcc18a (plus d567b31 for a stray Calc fixes
Docs
Not addressed (explained inline)
Tests: 57 → 61 passing. Coverage 96.45% → 96.8% stmts, 91.19% → 91.7% branches. All quality gates green. Ready for re-review. |
Summary
Adds three new calculation families to the library and extracts a shared rate helper. All existing exports unchanged. Intended for release as v1.5.0.
New exports
solveContributionForGoal(options)/solveYearsToGoal(options)— inverse savings-goal solvers using the future-value-of-annuity formula.earlyMortgagePayoff(options)— repayment schedule with extra monthly payments + optional lump sums; returns months and interest saved vs the baseline schedule.fireNumber(options)/yearsToFire(options)— FIRE number from annual spend + safe withdrawal rate, and a years-to-FIRE solver.Shared internal helper
toDecimalRateextracted tocalc/helpers.ts— the three new calcs and the two existing ones now normalise rate inputs the same way (percentage or decimal interchangeably).Test coverage
npm run tsc:check,npm run lint,npm test,npm run buildall green.Release
Tag this after merge as
v1.5.0and create a GitHub Release —release.ymlwill bumppackage.json, build, andnpm publishautomatically.Test plan
node testsworkflow passes on Node 18 + 20v1.5.0from main — workflow publishes