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::{money_from_value, Money}; ← from money.amount ^1.0.0 · built alongside by fune
/// Above this, loan × 10000 is no longer exact as a JavaScript number.
const MAX_LOAN: i64 = 900_719_925_474;
/// Loan-to-value, and the band from the lender's table. The band is chosen on
/// the exact ratio, loan × 10000 <= ceiling × value, never on a rounded
/// percentage, so 75.004% is not "75%". The reported basis points are rounded
/// up, which agrees: a whole ceiling is met exactly when the rounded-up ratio
/// is within it.
///
/// # Panics
/// Panics on mismatched currencies, a value of zero or less, a negative or
/// oversized loan, or an empty or unordered band table.
pub fn loan_to_value(loan: &Money, property_value: &Money, bands: &[LtvBand]) -> LtvResult {
if loan.currency != property_value.currency {
panic!("currency mismatch: {} and {}", loan.currency, property_value.currency);
}
if property_value.minor <= 0 {
panic!("propertyValue must be greater than zero, received {}", property_value.minor);
}
if loan.minor < 0 {
panic!("loan must not be negative, received {}", loan.minor);
}
if loan.minor > MAX_LOAN {
panic!("loan must be at most {} minor units", MAX_LOAN);
}
if bands.is_empty() {
panic!("bands must not be empty");
}
for i in 0..bands.len() {
let ceiling = bands[i].max_basis_points;
if ceiling < 1 {
panic!("band ceilings must be whole basis points of 1 or more, received {}", ceiling);
}
if i > 0 && ceiling <= bands[i - 1].max_basis_points {
panic!("band ceilings must be in strictly ascending order");
}
}
let basis_points = round_div(loan.minor * 10000, property_value.minor, "up");
let band = bands
.iter()
.find(|band| basis_points <= band.max_basis_points)
.map(|band| band.label.clone());
LtvResult { basis_points, band }
}
pub fn ltv_band_from_value(v: &Value) -> LtvBand {
LtvBand {
max_basis_points: v.get("maxBasisPoints").as_i64(),
label: v.get("label").as_str().to_string(),
}
}
pub fn ltv_result_to_value(result: &LtvResult) -> Value {
Value::obj(vec![
("basisPoints", Value::Int(result.basis_points)),
(
"band",
match &result.band {
Some(label) => Value::str(label),
None => Value::Null,
},
),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
let bands: Vec<LtvBand> = args[2].as_arr().iter().map(ltv_band_from_value).collect();
ltv_result_to_value(&loan_to_value(
&money_from_value(&args[0]),
&money_from_value(&args[1]),
&bands,
))
}