Functional Weave
Code in Rust

insurance.claim-reserve@1.0.1

impl/rust.rs

3,848 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_add_days::epoch_day_from_iso;  ← from dates.add-days ^1.0.0 · built alongside by fune
use super::money_amount::{assert_same_currency, money, money_from_value, money_to_value};  ← from money.amount ^1.0.0 · built alongside by fune

/// A claim's position after each transaction: paid, recovered, the
/// outstanding case reserve, and incurred = paid - recovered + outstanding.
///
/// A payment erodes the outstanding reserve by what it pays, never below
/// zero: a payment larger than the reserve raises incurred by the excess,
/// which is how an under-reserved claim shows up. A reserve transaction sets
/// the outstanding estimate outright. The movement is what each transaction
/// did to incurred, the figure that goes through the accounts.
///
/// # Panics
/// Panics on transactions out of date order, another currency, a negative
/// reserve, a payment or recovery that is not positive, or an unknown kind.
pub fn claim_reserve_history(transactions: &[ClaimTransaction], currency: &str) -> Vec<ReservePosition> {
    let zero = money(0, currency);
    let (mut paid, mut recovered, mut outstanding, mut incurred) = (0i64, 0i64, 0i64, 0i64);
    let mut previous: Option<(i64, &str)> = None;
    let mut out = Vec::new();
    for t in transactions {
        let day = epoch_day_from_iso(&t.date);
        if let Some((previous_day, previous_date)) = previous {
            if day < previous_day {
                panic!("transactions must be in date order: {} comes after {}", t.date, previous_date);
            }
        }
        previous = Some((day, &t.date));
        assert_same_currency(&zero, &t.amount);
        match t.kind.as_str() {
            "reserve" => {
                if t.amount.minor < 0 {
                    panic!("a reserve must not be negative, received {} on {}", t.amount.minor, t.date);
                }
                outstanding = t.amount.minor;
            }
            "payment" | "recovery" => {
                if t.amount.minor <= 0 {
                    panic!("a {} must be more than zero, received {} on {}", t.kind, t.amount.minor, t.date);
                }
                if t.kind == "payment" {
                    paid += t.amount.minor;
                    outstanding = (outstanding - t.amount.minor).max(0);
                } else {
                    recovered += t.amount.minor;
                }
            }
            other => panic!("unknown transaction kind \"{}\": use reserve, payment or recovery", other),
        }
        let now = paid - recovered + outstanding;
        out.push(ReservePosition {
            date: t.date.clone(),
            kind: t.kind.clone(),
            paid: money(paid, currency),
            recovered: money(recovered, currency),
            outstanding: money(outstanding, currency),
            incurred: money(now, currency),
            movement: money(now - incurred, currency),
        });
        incurred = now;
    }
    out
}

pub fn reserve_position_to_value(p: &ReservePosition) -> Value {
    Value::obj(vec![
        ("date", Value::str(&p.date)),
        ("kind", Value::str(&p.kind)),
        ("paid", money_to_value(&p.paid)),
        ("recovered", money_to_value(&p.recovered)),
        ("outstanding", money_to_value(&p.outstanding)),
        ("incurred", money_to_value(&p.incurred)),
        ("movement", money_to_value(&p.movement)),
    ])
}

pub fn fune_vector(args: &[Value]) -> Value {
    let transactions: Vec<ClaimTransaction> = args[0]
        .as_arr()
        .iter()
        .map(|v| ClaimTransaction {
            date: v.get("date").as_str().to_string(),
            kind: v.get("kind").as_str().to_string(),
            amount: money_from_value(v.get("amount")),
        })
        .collect();
    Value::Arr(
        claim_reserve_history(&transactions, args[1].as_str())
            .iter()
            .map(reserve_position_to_value)
            .collect(),
    )
}