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
fn whole_at_least_zero(value: i64, name: &str) {
if value < 0 {
panic!("{} must be a whole number of 0 or more, received {}", name, value);
}
}
fn level_for(scale: &[NcdLevel], years: i64) -> &NcdLevel {
let mut found = &scale[0];
for level in scale {
if level.years <= years {
found = level;
}
}
found
}
/// The no-claims discount held at renewal.
///
/// A claim-free year earns a year. A year with claims steps back to what the
/// scale says for the level held and the number of claims, unless protected
/// NCD covers them, in which case the years stay where they were (a year with
/// a claim earns nothing). Years keep counting past the top of the scale; the
/// discount is the highest level not above them.
///
/// # Panics
/// Panics on a malformed scale, negative counts, or a protection window that
/// leaves out this year's claims.
pub fn next_no_claims_discount(
scale: &[NcdLevel],
current_years: i64,
claims: i64,
protection: Option<&NcdProtection>,
) -> NcdOutcome {
if scale.is_empty() {
panic!("the NCD scale must not be empty");
}
if scale[0].years != 0 {
panic!("the NCD scale must start at 0 years");
}
for (i, level) in scale.iter().enumerate() {
whole_at_least_zero(level.years, "years");
if i > 0 && level.years <= scale[i - 1].years {
panic!("the NCD scale must be in ascending order of years");
}
if !(0..=10000).contains(&level.discount_basis_points) {
panic!("discountBasisPoints must be from 0 to 10000, received {}", level.discount_basis_points);
}
let mut previous = level.years;
for back in &level.step_back {
whole_at_least_zero(*back, "stepBack");
if *back > previous {
panic!(
"stepBack from {} years must not rise above the level or the step before it",
level.years
);
}
previous = *back;
}
}
whole_at_least_zero(current_years, "currentYears");
whole_at_least_zero(claims, "claims");
let mut protection_applied = false;
let years = if claims == 0 {
current_years + 1
} else {
let mut covered = false;
if let Some(p) = protection {
whole_at_least_zero(p.claim_limit, "claimLimit");
whole_at_least_zero(p.claims_in_window, "claimsInWindow");
if p.claims_in_window < claims {
panic!("claimsInWindow must include this year's claims");
}
covered = p.claims_in_window <= p.claim_limit;
}
if covered {
protection_applied = true;
current_years
} else {
let steps = &level_for(scale, current_years).step_back;
if steps.is_empty() {
0
} else {
steps[(claims as usize).min(steps.len()) - 1]
}
}
};
NcdOutcome {
years,
discount_basis_points: level_for(scale, years).discount_basis_points,
protection_applied,
}
}
fn int_arg(v: &Value, name: &str) -> i64 {
if let Value::Float(f) = v {
panic!("{} must be a whole number of 0 or more, received {}", name, f);
}
v.as_i64()
}
pub fn ncd_level_from_value(v: &Value) -> NcdLevel {
NcdLevel {
years: int_arg(v.get("years"), "years"),
discount_basis_points: v.get("discountBasisPoints").as_i64(),
step_back: v.get("stepBack").as_arr().iter().map(|s| int_arg(s, "stepBack")).collect(),
}
}
pub fn ncd_outcome_to_value(o: &NcdOutcome) -> Value {
Value::obj(vec![
("years", Value::Int(o.years)),
("discountBasisPoints", Value::Int(o.discount_basis_points)),
("protectionApplied", Value::Bool(o.protection_applied)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
let scale: Vec<NcdLevel> = args[0].as_arr().iter().map(ncd_level_from_value).collect();
let protection = if args[3].is_null() {
None
} else {
Some(NcdProtection {
claim_limit: int_arg(args[3].get("claimLimit"), "claimLimit"),
claims_in_window: int_arg(args[3].get("claimsInWindow"), "claimsInWindow"),
})
};
ncd_outcome_to_value(&next_no_claims_discount(
&scale,
int_arg(&args[1], "currentYears"),
int_arg(&args[2], "claims"),
protection.as_ref(),
))
}