Functional Weave
Code in TypeScript

lending.amortisation-schedule@1.0.0

impl/rust.rs

3,817 bytes · the Rust implementation · view raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

use super::funejson::Value;  ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::lending_loan_payment::{loan_payment, payment_count};  ← from lending.loan-payment ^1.0.0 · built alongside by fune
use super::math_round_div::round_div;  ← from math.round-div ^1.0.0 · built alongside by fune
use super::money_amount::{money, money_from_value, money_to_value, Money};  ← from money.amount ^1.0.0 · built alongside by fune

/// Interest for one period on a positive balance: balance × b / D, rounded
/// half-up to the minor unit, which is what gets posted to the account.
pub fn period_interest(balance: i64, annual_rate_basis_points: i64, payments_per_year: i64) -> i64 {
    if balance <= 0 || annual_rate_basis_points == 0 {
        return 0;
    }
    let product = balance as i128 * annual_rate_basis_points as i128;
    if product > i64::MAX as i128 {
        panic!("balance too large for exact interest");
    }
    round_div(product as i64, 10000 * payments_per_year, "half-up")
}

/// The schedule a lender's system produces: each period's interest is
/// computed on the opening balance and rounded to a whole minor unit, the
/// level payment repays interest first and principal with the rest, and the
/// last payment is whatever clears the balance, so it ends at exactly zero.
///
/// # Panics
/// Panics on the same arguments lending.loan-payment refuses.
pub fn amortisation_schedule(
    principal: &Money,
    annual_rate_basis_points: i64,
    term_months: i64,
    payments_per_year: i64,
    mode: &str,
) -> AmortisationSchedule {
    let payment = loan_payment(principal, annual_rate_basis_points, term_months, payments_per_year, mode);
    let n = payment_count(annual_rate_basis_points, term_months, payments_per_year);
    let currency = principal.currency.as_str();
    let mut rows: Vec<AmortisationRow> = Vec::new();
    let mut balance = principal.minor;
    let mut total_interest = 0i64;
    let mut total_paid = 0i64;
    let mut period = 1i64;
    while period <= n && balance > 0 {
        let interest = period_interest(balance, annual_rate_basis_points, payments_per_year);
        // The last period, or one where the level payment would overshoot (a
        // rounded-up payment can clear the loan a period early), pays exactly
        // what is owed.
        let due = if period == n || balance + interest <= payment.minor {
            balance + interest
        } else {
            payment.minor
        };
        let repaid = due - interest;
        balance -= repaid;
        total_interest += interest;
        total_paid += due;
        rows.push(AmortisationRow {
            period,
            payment: money(due, currency),
            interest: money(interest, currency),
            principal: money(repaid, currency),
            balance: money(balance, currency),
        });
        period += 1;
    }
    AmortisationSchedule {
        payment,
        rows,
        total_interest: money(total_interest, currency),
        total_paid: money(total_paid, currency),
    }
}

pub fn amortisation_row_to_value(row: &AmortisationRow) -> Value {
    Value::obj(vec![
        ("period", Value::Int(row.period)),
        ("payment", money_to_value(&row.payment)),
        ("interest", money_to_value(&row.interest)),
        ("principal", money_to_value(&row.principal)),
        ("balance", money_to_value(&row.balance)),
    ])
}

pub fn amortisation_schedule_to_value(schedule: &AmortisationSchedule) -> Value {
    Value::obj(vec![
        ("payment", money_to_value(&schedule.payment)),
        ("rows", Value::Arr(schedule.rows.iter().map(amortisation_row_to_value).collect())),
        ("totalInterest", money_to_value(&schedule.total_interest)),
        ("totalPaid", money_to_value(&schedule.total_paid)),
    ])
}

pub fn fune_vector(args: &[Value]) -> Value {
    amortisation_schedule_to_value(&amortisation_schedule(
        &money_from_value(&args[0]),
        args[1].as_i64(),
        args[2].as_i64(),
        args[3].as_i64(),
        args[4].as_str(),
    ))
}