Functional Weave
Code in Python

insurance.sum-insured-average@1.0.1

impl/rust.rs

3,332 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::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

/// Settle a claim under an average clause.
///
/// A property insured for less than it is worth is paid only the share of
/// the loss that the sum insured bears to the value at risk. With a special
/// condition of average the clause only applies once the sum insured falls
/// below that share of the value. The payout never exceeds the loss or the
/// sum insured.
///
/// # Panics
/// Panics on mixed currencies, negative amounts, a value at risk that is not
/// positive, a loss above it, or a condition outside 1..=10000.
pub fn average_clause_settlement(
    loss: &Money,
    sum_insured: &Money,
    value_at_risk: &Money,
    condition_basis_points: i64,
    mode: &str,
) -> AverageSettlement {
    assert_same_currency(loss, sum_insured);
    assert_same_currency(loss, value_at_risk);
    if value_at_risk.minor <= 0 {
        panic!("valueAtRisk must be more than zero, received {}", value_at_risk.minor);
    }
    if loss.minor < 0 {
        panic!("loss must not be negative, received {}", loss.minor);
    }
    if sum_insured.minor < 0 {
        panic!("sumInsured must not be negative, received {}", sum_insured.minor);
    }
    if loss.minor > value_at_risk.minor {
        panic!("loss must not exceed the value at risk");
    }
    if !(1..=10000).contains(&condition_basis_points) {
        panic!("conditionBasisPoints must be from 1 to 10000, received {}", condition_basis_points);
    }
    // i128: a sum insured and a loss in pence can overflow i64 when multiplied.
    let average_applied =
        (sum_insured.minor as i128) * 10000 < (value_at_risk.minor as i128) * (condition_basis_points as i128);
    let mut payout = if average_applied {
        let wide = (loss.minor as i128) * (sum_insured.minor as i128);
        let whole = wide / (value_at_risk.minor as i128);
        let remainder = wide % (value_at_risk.minor as i128);
        // Divide in i128, then round the remainder the same way round_div would.
        whole as i64 + round_div(remainder as i64, value_at_risk.minor, mode)
    } else {
        loss.minor
    };
    payout = payout.min(loss.minor).min(sum_insured.minor);
    let c = &loss.currency;
    AverageSettlement {
        payout: money(payout, c),
        average_applied,
        insured_proportion_basis_points: round_div(sum_insured.minor * 10000, value_at_risk.minor, "down").min(10000),
        shortfall: money(loss.minor - payout, c),
    }
}

pub fn average_settlement_to_value(s: &AverageSettlement) -> Value {
    Value::obj(vec![
        ("payout", money_to_value(&s.payout)),
        ("averageApplied", Value::Bool(s.average_applied)),
        ("insuredProportionBasisPoints", Value::Int(s.insured_proportion_basis_points)),
        ("shortfall", money_to_value(&s.shortfall)),
    ])
}

pub fn fune_vector(args: &[Value]) -> Value {
    if let Value::Float(f) = args[3] {
        panic!("conditionBasisPoints must be from 1 to 10000, received {}", f);
    }
    average_settlement_to_value(&average_clause_settlement(
        &money_from_value(&args[0]),
        &money_from_value(&args[1]),
        &money_from_value(&args[2]),
        args[3].as_i64(),
        args[4].as_str(),
    ))
}