use super::funejson::Value; use super::math_round_div::round_div; use super::money_amount::{assert_same_currency, money, money_from_value, money_to_value, Money}; /// 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(), )) }