Functional Weave
Code in Rust

insurance.claim-reserve

Claim reserve history: paid, recovered, outstanding and incurred after each transaction, and the movement.

1.0.0 (not the latest) · published 2026-10-03 by charlie · Anterra

Pinned by 14 tests, run in TypeScript, Python and Rust.

Not professional advice. This capability calculates insurance figures from published rules. It is a software component for developers, not financial advice. Rules change and every rate here has an effective date. Check that the dates cover your case. Verify results against the official sources listed in its README, and have an actuary review how you use it, before anyone relies on the output. Provided “as is” under its licence, without warranty.

What it does

The history of one claim's figures: after each transaction, what has been paid, what has been recovered, the outstanding case reserve, the incurred total and the movement in incurred.

incurred = paid - recovered + outstanding

For example

  • claim_reserve_history(transactions ×6, GBP) → ×6 a claim's life: reserve, part payment, strengthening, an overpayment of the reserve, a recovery, closure
  • claim_reserve_history(, GBP) → no transactions, no positions
  • claim_reserve_history(transactions ×2, GBP) → ×2 a claim withdrawn: the reserve released to nil is a negative movement

The function

The same function in TypeScript, Python and Rust, pinned by the same tests. Pick your language; the choice follows you around the registry.

pub fn claim_reserve_history(transactions: &[ClaimTransaction], currency: &str) -> Vec<ReservePosition>
transactionsClaimTransaction[]one claim's transactions in date order; same-day ones in the order they happened
currencystringthe claim's currency
returnsReservePosition[]the claim's position after each transaction, in the same order

The types it declares, generated into your project

// ClaimTransactionKind is a string in Rust, one of: "reserve", "payment", "recovery".
// Parameters take it as &str and results hold it as String.

/// One event on a claim.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ClaimTransaction {
    pub date: String,
    /// reserve sets the outstanding estimate; payment pays; recovery brings money back
    pub kind: String,
    /// reserve: the new outstanding estimate, 0 or more; payment and recovery: more than 0
    pub amount: Money,
}

/// The claim after one transaction.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReservePosition {
    pub date: String,
    pub kind: String,
    /// paid to date
    pub paid: Money,
    /// salvage and subrogation recovered to date
    pub recovered: Money,
    /// the case reserve: what is still expected to be paid
    pub outstanding: Money,
    /// paid minus recovered plus outstanding
    pub incurred: Money,
    /// the change in incurred this transaction made
    pub movement: Money,
}

Your code names it in one line, in the file that uses it

fune!(insurance.claim-reserve@^1);  // then call claim_reserve_history(…)
impl/rust.rs · 94 lines · open · 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(),
    )
}

Install

fune build

With that line in your source, in a Rust project (language rust in fune.project), fune build resolves it and its 2 dependencies, pins them in fune.lock, downloads only the Rust package of each, and builds the code above into your project’s .fune/build, one readable file per capability with a header linking back here. A crate’s build.rs runs it before every compile. Or pin a range in fune.project and build in one step:

fune add insurance.claim-reserve
Download for Rust insurance.claim-reserve-1.0.0-rust.fune · 22,475 bytes sha256 ed2c375f4796604fc2b435b1b98f32af7e5cfc68a727e04a05e65f827c3dd1e0

The manifest, vectors and README with only the Rust implementation. Install it without the registry with fune add ./insurance.claim-reserve-1.0.0-rust.fune, or fetch it from a terminal with fune pull insurance.claim-reserve@1.0.0:rust.

The whole function, every language, is one file too: insurance.claim-reserve-1.0.0.fune, 27,810 bytes, sha256 09a69f155083930260e05e9d44f683c59f91be69e9928b61feb994038927b87e. It installs into a project of any language.

Customise it in your app

The seams this capability offers. Put a marker directly above a function of your own and fune build wires it into the built code; the package on the registry is not changed, the built file’s header lists it under CUSTOMISED, and fune hooks lists every hook in the project. How hooks work.

before — your function gets the arguments and returns them, changed or not, or throws to refuse the call.

// fune: before insurance.claim-reserve

after — your function gets the result and the arguments, and returns the final result.

// fune: after insurance.claim-reserve

replace — inside this capability’s code only, calls to a dependency go to your function, with the same signature. Other capabilities that use it are unaffected; write in * to replace it everywhere.

// fune: replace dates.add-days in insurance.claim-reserve
// fune: replace money.amount in insurance.claim-reserve

step — your function runs at a numbered point inside the function’s body, receives the in-scope values it names as parameters, and may return replacements. List the points with fune show insurance.claim-reserve --steps.

// fune: step insurance.claim-reserve after <n|label>

Tests

A version published now needs at least 8 tests for every function, and one that expects the error for each function that throws; the registry refuses it otherwise. fune verify --all runs each case in TypeScript, Python and Rust, and a project runs them again with fune verify. This page lists the cases; it does not run them. The exact JSON is vectors.json.

CaseArgumentsExpected
a claim's life: reserve, part payment, strengthening, an overpayment of the reserve, a recovery, closure transactions ×6, GBP → ×6
no transactions, no positions , GBP →
a claim withdrawn: the reserve released to nil is a negative movement transactions ×2, GBP → ×2
a payment with no reserve set raises incurred by the whole payment transactions ×1, GBP → ×1
a payment within the reserve moves nothing transactions ×2, GBP → ×2
a reserve cut after a payment transactions ×3, GBP → ×3
recoveries can take incurred below zero on a claim recovered in full with costs transactions ×2, GBP → ×2
transactions out of date order are refused transactions ×2, GBP → error: transactions must be in date order: 2026-01-01 comes after 2026-02-01
a negative reserve is refused transactions ×1, GBP → error: a reserve must not be negative
a zero payment is refused transactions ×1, GBP → error: a payment must be more than zero
Show the other 4 tests
CaseArgumentsExpected
a negative recovery is refused transactions ×1, GBP → error: a recovery must be more than zero
an unknown kind is refused transactions ×1, GBP → error: unknown transaction kind
a transaction in another currency is refused transactions ×1, GBP → error: currency mismatch
a malformed date is refused transactions ×1, GBP → error: is not a real calendar date

More from the author

## Transactions

- **reserve**: the claims handler's new estimate of what is still to be paid. It sets `outstanding` outright (0 closes the claim or releases the reserve). - **payment**: money paid out. It adds to `paid` and erodes `outstanding` by the same amount, never below zero. A payment within the reserve therefore leaves incurred unchanged; one larger than the reserve raises incurred by the difference, which is how an under-reserved claim shows up. - **recovery**: salvage, subrogation or third-party recoveries received. They reduce net incurred. A claim recovered in full with costs can show negative incurred.

`movement` is the change in incurred made by each transaction: the figure that goes through the claims account for the period, and what a development triangle is built from. Summing the movements in a period gives that period's incurred movement.

Transactions must be in date order; same-day transactions keep the order given, so a payment and the reserve change that follows it on one day come out as two positions. Dates are checked as real calendar dates.

Not covered: IBNR and other reserves not held against a specific claim, reserve currency conversion, and claims handling expenses (pass them as payments if your reporting counts them in incurred).

Files

PathBytes
README.md1,554
impl/python.py2,622
impl/rust.rs3,848
impl/typescript.ts2,514
vectors.json11,487