Functional Weave
Code in TypeScript

insurance.mid-term-adjustment@1.0.1

impl/rust.rs

2,851 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::dates_days_between::days_between;  ← from dates.days-between ^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::{assert_same_currency, money, money_from_value, money_to_value, Money};  ← from money.amount ^1.0.0 · built alongside by fune

/// The additional or return premium for a change part-way through a policy term.
///
/// The difference between the two full-term premiums is pro-rated over the
/// days left, with one rounding. Pro-rating the old and new premiums
/// separately and subtracting rounds twice and can be a penny out.
///
/// # Panics
/// Panics on mixed currencies, negative premiums or fee, or dates out of order.
pub fn mid_term_adjustment(
    old_premium: &Money,
    new_premium: &Money,
    inception_date: &str,
    expiry_date: &str,
    change_date: &str,
    admin_fee: &Money,
    mode: &str,
) -> MidTermAdjustment {
    assert_same_currency(old_premium, new_premium);
    assert_same_currency(old_premium, admin_fee);
    if old_premium.minor < 0 || new_premium.minor < 0 {
        panic!("premiums must not be negative");
    }
    if admin_fee.minor < 0 {
        panic!("adminFee must not be negative, received {}", admin_fee.minor);
    }
    let total_days = days_between(inception_date, expiry_date);
    if total_days <= 0 {
        panic!("expiryDate must be after inceptionDate, received {} to {}", inception_date, expiry_date);
    }
    let remaining_days = days_between(change_date, expiry_date);
    if remaining_days < 0 || remaining_days > total_days {
        panic!("changeDate must be from inceptionDate to expiryDate, received {}", change_date);
    }
    let change = round_div((new_premium.minor - old_premium.minor) * remaining_days, total_days, mode);
    let currency = &old_premium.currency;
    let kind = if change > 0 {
        "additional"
    } else if change < 0 {
        "return"
    } else {
        "none"
    };
    MidTermAdjustment {
        total_days,
        remaining_days,
        premium_change: money(change, currency),
        admin_fee: admin_fee.clone(),
        amount_due: money(change + admin_fee.minor, currency),
        kind: kind.to_string(),
    }
}

pub fn mid_term_adjustment_to_value(a: &MidTermAdjustment) -> Value {
    Value::obj(vec![
        ("totalDays", Value::Int(a.total_days)),
        ("remainingDays", Value::Int(a.remaining_days)),
        ("premiumChange", money_to_value(&a.premium_change)),
        ("adminFee", money_to_value(&a.admin_fee)),
        ("amountDue", money_to_value(&a.amount_due)),
        ("kind", Value::str(&a.kind)),
    ])
}

pub fn fune_vector(args: &[Value]) -> Value {
    mid_term_adjustment_to_value(&mid_term_adjustment(
        &money_from_value(&args[0]),
        &money_from_value(&args[1]),
        args[2].as_str(),
        args[3].as_str(),
        args[4].as_str(),
        &money_from_value(&args[5]),
        args[6].as_str(),
    ))
}