Expose the amortising base in the loan offer fixtures
The fixture struct published borrowed_capital and total_duration_in_months, but monthly_repayment is not a function of those two. It is a function of the capital the amortisation actually runs on and the number of amortising instalments, and both differ from the published fields as soon as there is a deferral. On offre B the six months of total deferral capitalise 439 000 EUR into 444 405,27 EUR, amortised over 294 instalments rather than 300. Feeding the formula with the fields the fixture exposed gave 1 958,39 EUR against the table's 2 011,86 EUR. The two deferred fixtures were therefore unusable, and misleading about it: a 53 EUR monthly gap reads like a rounding bug in the implementation rather than a missing input. The generator already computed both values and wrote them to the JSON output. Only render_rust dropped them, so the Rust fixtures were strictly less informative than the JSON they were generated alongside. All four fixtures now reproduce their reference instalment to the cent when fed amortised_capital and amortisation_in_months, including the two deferred offers that could not be tested at all before. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
6a47fc8ccd
commit
8bf701e010
2 changed files with 38 additions and 0 deletions
|
|
@ -40,6 +40,20 @@ pub struct LoanOfferFixture {
|
||||||
pub annual_nominal_rate: InterestRate,
|
pub annual_nominal_rate: InterestRate,
|
||||||
pub total_duration_in_months: u64,
|
pub total_duration_in_months: u64,
|
||||||
pub deferral_in_months: u64,
|
pub deferral_in_months: u64,
|
||||||
|
/// Capital sur lequel court l'amortissement, après capitalisation des
|
||||||
|
/// intérêts du différé. Égal à `borrowed_capital` hors différé total.
|
||||||
|
///
|
||||||
|
/// C'est **ce** capital, et non `borrowed_capital`, qui alimente la formule
|
||||||
|
/// de mensualité. Sur l'offre B, le différé total porte les 439 000 EUR
|
||||||
|
/// empruntés à 444 405,27 EUR amortissables.
|
||||||
|
pub amortised_capital: Euros,
|
||||||
|
/// Nombre d'échéances amortissantes, différé exclu. C'est le `n` de la
|
||||||
|
/// formule de mensualité, à ne pas confondre avec `total_duration_in_months`.
|
||||||
|
///
|
||||||
|
/// Alimenter la formule avec `borrowed_capital` et `total_duration_in_months`
|
||||||
|
/// sur l'offre B donne 1 958,39 EUR au lieu des 2 011,86 EUR du tableau :
|
||||||
|
/// l'écart est un cumul des deux confusions, pas une dérive d'arrondi.
|
||||||
|
pub amortisation_in_months: u64,
|
||||||
pub monthly_repayment: Euros,
|
pub monthly_repayment: Euros,
|
||||||
pub total_credit_cost: Euros,
|
pub total_credit_cost: Euros,
|
||||||
pub total_paid: Euros,
|
pub total_paid: Euros,
|
||||||
|
|
@ -5826,6 +5840,8 @@ pub fn offre_a() -> LoanOfferFixture {
|
||||||
annual_nominal_rate: InterestRate::from_fraction(Decimal::new(341, 4)),
|
annual_nominal_rate: InterestRate::from_fraction(Decimal::new(341, 4)),
|
||||||
total_duration_in_months: 240,
|
total_duration_in_months: 240,
|
||||||
deferral_in_months: 0,
|
deferral_in_months: 0,
|
||||||
|
amortised_capital: Euros::from_cents_as_i64(150_000_00),
|
||||||
|
amortisation_in_months: 240,
|
||||||
monthly_repayment: Euros::from_cents_as_i64(863_02),
|
monthly_repayment: Euros::from_cents_as_i64(863_02),
|
||||||
total_credit_cost: Euros::from_cents_as_i64(57_124_30),
|
total_credit_cost: Euros::from_cents_as_i64(57_124_30),
|
||||||
total_paid: Euros::from_cents_as_i64(207_124_30),
|
total_paid: Euros::from_cents_as_i64(207_124_30),
|
||||||
|
|
@ -5841,6 +5857,8 @@ pub fn offre_b_differe_total() -> LoanOfferFixture {
|
||||||
annual_nominal_rate: InterestRate::from_fraction(Decimal::new(245, 4)),
|
annual_nominal_rate: InterestRate::from_fraction(Decimal::new(245, 4)),
|
||||||
total_duration_in_months: 300,
|
total_duration_in_months: 300,
|
||||||
deferral_in_months: 6,
|
deferral_in_months: 6,
|
||||||
|
amortised_capital: Euros::from_cents_as_i64(444_405_27),
|
||||||
|
amortisation_in_months: 294,
|
||||||
monthly_repayment: Euros::from_cents_as_i64(2_011_86),
|
monthly_repayment: Euros::from_cents_as_i64(2_011_86),
|
||||||
total_credit_cost: Euros::from_cents_as_i64(152_486_18),
|
total_credit_cost: Euros::from_cents_as_i64(152_486_18),
|
||||||
total_paid: Euros::from_cents_as_i64(591_486_18),
|
total_paid: Euros::from_cents_as_i64(591_486_18),
|
||||||
|
|
@ -5856,6 +5874,8 @@ pub fn offre_b_differe_partiel() -> LoanOfferFixture {
|
||||||
annual_nominal_rate: InterestRate::from_fraction(Decimal::new(245, 4)),
|
annual_nominal_rate: InterestRate::from_fraction(Decimal::new(245, 4)),
|
||||||
total_duration_in_months: 300,
|
total_duration_in_months: 300,
|
||||||
deferral_in_months: 6,
|
deferral_in_months: 6,
|
||||||
|
amortised_capital: Euros::from_cents_as_i64(439_000_00),
|
||||||
|
amortisation_in_months: 294,
|
||||||
monthly_repayment: Euros::from_cents_as_i64(1_987_39),
|
monthly_repayment: Euros::from_cents_as_i64(1_987_39),
|
||||||
total_credit_cost: Euros::from_cents_as_i64(150_669_69),
|
total_credit_cost: Euros::from_cents_as_i64(150_669_69),
|
||||||
total_paid: Euros::from_cents_as_i64(589_669_69),
|
total_paid: Euros::from_cents_as_i64(589_669_69),
|
||||||
|
|
@ -5871,6 +5891,8 @@ pub fn offre_c_ptz() -> LoanOfferFixture {
|
||||||
annual_nominal_rate: InterestRate::from_fraction(Decimal::new(0, 0)),
|
annual_nominal_rate: InterestRate::from_fraction(Decimal::new(0, 0)),
|
||||||
total_duration_in_months: 120,
|
total_duration_in_months: 120,
|
||||||
deferral_in_months: 0,
|
deferral_in_months: 0,
|
||||||
|
amortised_capital: Euros::from_cents_as_i64(95_000_00),
|
||||||
|
amortisation_in_months: 120,
|
||||||
monthly_repayment: Euros::from_cents_as_i64(791_67),
|
monthly_repayment: Euros::from_cents_as_i64(791_67),
|
||||||
total_credit_cost: Euros::from_cents_as_i64(0_00),
|
total_credit_cost: Euros::from_cents_as_i64(0_00),
|
||||||
total_paid: Euros::from_cents_as_i64(95_000_00),
|
total_paid: Euros::from_cents_as_i64(95_000_00),
|
||||||
|
|
|
||||||
|
|
@ -575,6 +575,20 @@ pub struct LoanOfferFixture {{
|
||||||
pub annual_nominal_rate: InterestRate,
|
pub annual_nominal_rate: InterestRate,
|
||||||
pub total_duration_in_months: u64,
|
pub total_duration_in_months: u64,
|
||||||
pub deferral_in_months: u64,
|
pub deferral_in_months: u64,
|
||||||
|
/// Capital sur lequel court l'amortissement, après capitalisation des
|
||||||
|
/// intérêts du différé. Égal à `borrowed_capital` hors différé total.
|
||||||
|
///
|
||||||
|
/// C'est **ce** capital, et non `borrowed_capital`, qui alimente la formule
|
||||||
|
/// de mensualité. Sur l'offre B, le différé total porte les 439 000 EUR
|
||||||
|
/// empruntés à 444 405,27 EUR amortissables.
|
||||||
|
pub amortised_capital: Euros,
|
||||||
|
/// Nombre d'échéances amortissantes, différé exclu. C'est le `n` de la
|
||||||
|
/// formule de mensualité, à ne pas confondre avec `total_duration_in_months`.
|
||||||
|
///
|
||||||
|
/// Alimenter la formule avec `borrowed_capital` et `total_duration_in_months`
|
||||||
|
/// sur l'offre B donne 1 958,39 EUR au lieu des 2 011,86 EUR du tableau :
|
||||||
|
/// l'écart est un cumul des deux confusions, pas une dérive d'arrondi.
|
||||||
|
pub amortisation_in_months: u64,
|
||||||
pub monthly_repayment: Euros,
|
pub monthly_repayment: Euros,
|
||||||
pub total_credit_cost: Euros,
|
pub total_credit_cost: Euros,
|
||||||
pub total_paid: Euros,
|
pub total_paid: Euros,
|
||||||
|
|
@ -657,6 +671,8 @@ def render_rust(offers: list[LoanOffer], path: str) -> str:
|
||||||
f" annual_nominal_rate: {rust_interest_rate(offer.annual_nominal_rate)},\n"
|
f" annual_nominal_rate: {rust_interest_rate(offer.annual_nominal_rate)},\n"
|
||||||
f" total_duration_in_months: {offer.total_duration_in_months},\n"
|
f" total_duration_in_months: {offer.total_duration_in_months},\n"
|
||||||
f" deferral_in_months: {offer.deferral_in_months},\n"
|
f" deferral_in_months: {offer.deferral_in_months},\n"
|
||||||
|
f" amortised_capital: {rust_euros(offer.amortised_capital)},\n"
|
||||||
|
f" amortisation_in_months: {offer.amortisation_in_months},\n"
|
||||||
f" monthly_repayment: {rust_euros(offer.monthly_repayment)},\n"
|
f" monthly_repayment: {rust_euros(offer.monthly_repayment)},\n"
|
||||||
f" total_credit_cost: {rust_euros(offer.total_credit_cost(rows))},\n"
|
f" total_credit_cost: {rust_euros(offer.total_credit_cost(rows))},\n"
|
||||||
f" total_paid: {rust_euros(offer.total_paid(rows))},\n"
|
f" total_paid: {rust_euros(offer.total_paid(rows))},\n"
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue