Reference
referenceAccrues day by day in high-precision Decimal and rounds half-even only at each posting.
02 · Interest
Accrue daily interest on a CAD balance and post it at month ends, using Actual/365 and banker's rounding.
referenceAccrues day by day in high-precision Decimal and rounds half-even only at each posting.
alt_validClosed-form exact rational segments and manual integer half-even rounding instead of daywise Decimal accrual.
wrong_actual_actualUses Actual/Actual with a 366-day denominator in leap years instead of Actual/365 Fixed.
wrong_floatBinary floating point loses precision on ties and large principals.
wrong_half_upRounds ties away from zero instead of to even.
no_validateExpect: rejectObserved: rejectedOmits input validation, violating the finite Decimal, whole-cent, date and method validation rules.
daily_rate_first_prec28Expect: rejectObserved: rejectedDivides the rate first at precision 28, violating the clarified PROBLEM.md lines 20-23 requiring at least 34 digits or exact arithmetic with exact half-even ties (original ticket permitted precision 28; F-403).
carry_residualExpect: rejectObserved: rejectedCarries rounding residual into later segments, violating the rule to discard residual after each posting.
simple_noncompoundingExpect: rejectObserved: rejectedUses the original principal for every segment, violating compounding at posting boundaries.
naive_end_posting_only_month_endExpect: rejectObserved: rejectedPosts only on month ends, violating the additional end-minus-one-day posting rule.
Each row groups a test’s parameterized cases; any failing case marks the row as Fail. * marks a wrong solution’s declared must_fail test.
| Test | Starter | Reference | Alternative | actual actual | float | half up |
|---|---|---|---|---|---|---|
| Half cent ties bankers | Fail | Pass | Pass | Pass | Fail * | Fail * |
| Invalid money raises | Pass | Pass | Pass | Pass | Pass | Pass |
| Invalid dates and method raise | Pass | Pass | Pass | Pass | Pass | Pass |
| Large principal exact | Fail | Pass | Pass | Fail * | Pass * | Pass |
| Small balance daily accrues | Pass | Pass | Pass | Pass | Pass | Pass |
| Leap and partial | Fail | Pass | Pass | Fail * | Pass | Pass |
| Accrual properties | Fail | Pass | Pass | Fail * | Pass | Pass |
| Test | Starter | Reference | Alternative | actual actual | float | half up |
|---|---|---|---|---|---|---|
| Examples | Pass | Pass | Pass | Pass | Pass | Pass |
| Ticket worked example | Pass | Pass | Pass | Pass | Pass | Pass |
| Empty period and reversed dates | Pass | Pass | Pass | Pass | Pass | Pass |
The synthetic CAD account needs correct daily accrual and explicit posting boundaries. Keep interest.accrual.accrue(principal: Decimal, annual_rate: Decimal, start: date, end: date, method: Literal["simple_monthly","daily_compound"]) -> list[Posting] and Posting(posting_date: date, interest: Decimal, balance: Decimal) (frozen dataclass).
CAD has 2 decimal places. Principal is a nonnegative finite Decimal in whole cents; rate is a nonnegative finite Decimal (a fraction, e.g. 0.05 means 5%). Invalid values/method or end before start raise ValueError. These are dates, not timestamps; any datetime instead of date is invalid.
[start,end).end - 1 day if it is not a month end. Return chronological postings. start == end returns [].simple_monthly: interest at a posting equals current posted balance × rate × segment days / 365. Add each posting to the balance; thus it compounds at posting boundaries.daily_compound: each date, accrued += (posted balance + accrued) × rate / 365 with full intermediate precision. At posting, round accrued, add to balance, reset accrued to zero; discard the unrounded residual.Worked example: principal 1000.00, rate 0.0365 (so 0.0001 per day), start 2026-01-01, end 2026-03-01. Both methods post on 2026-01-31 (31 days) and 2026-02-28 (28 days). January: 1000.00 x 0.0365 x 31 / 365 = 3.10, balance 1003.10. February: 1003.10 x 0.0365 x 28 / 365 = 2.8087..., posted as 2.81, balance 1005.91. (With daily_compound the unrounded January interest is 3.1048...; both methods round to the same cents here.)
Only the declared interface is graded; internal representation and algorithms are unrestricted. Run from starter: python -m pytest tests -q. All examples and accounts are synthetic.