use super::funejson::Value; use super::lending_affordability::{affordability, affordability_result_to_value}; use super::math_round_div::round_div; use super::money_amount::{money, money_from_value, money_to_value, Money}; const MAX_INCOME: i64 = 10_000_000_000; const MAX_MULTIPLE: i64 = 200_000; /// Loan-to-income against the lender's multiple, and the stressed repayment /// against income via lending.affordability. Every rounding goes against the /// borrower, and the multiple comparison itself is exact. /// /// # Panics /// Panics on mismatched currencies, an income, loan or multiple out of range, /// or anything lending.affordability refuses. #[allow(clippy::too_many_arguments)] pub fn mortgage_affordability( annual_income: &Money, monthly_commitments: &Money, loan: &Money, annual_rate_basis_points: i64, stress_rate_basis_points: i64, term_months: i64, max_income_multiple_basis_points: i64, max_debt_to_income_basis_points: i64, ) -> MortgageAffordability { if loan.currency != annual_income.currency { panic!("currency mismatch: {} and {}", annual_income.currency, loan.currency); } if annual_income.minor < 12 || annual_income.minor > MAX_INCOME { panic!("annualIncome must be between 12 and {} minor units, received {}", MAX_INCOME, annual_income.minor); } if loan.minor <= 0 || loan.minor > MAX_INCOME * 20 { panic!("loan must be between 1 and {} minor units, received {}", MAX_INCOME * 20, loan.minor); } let m = max_income_multiple_basis_points; if m < 1 || m > MAX_MULTIPLE { panic!("maxIncomeMultipleBasisPoints must be between 1 and {}, received {}", MAX_MULTIPLE, m); } let currency = annual_income.currency.as_str(); let monthly_income = money(annual_income.minor / 12, currency); let repayment = affordability( &monthly_income, monthly_commitments, loan, annual_rate_basis_points, stress_rate_basis_points, term_months, max_debt_to_income_basis_points, ); let within = loan.minor * 10000 <= annual_income.minor * m; MortgageAffordability { income_multiple_basis_points: round_div(loan.minor * 10000, annual_income.minor, "up"), max_loan_by_multiple: money(round_div(annual_income.minor * m, 10000, "down"), currency), within_multiple: within, monthly_income, affordable: within && repayment.affordable, repayment, } } pub fn mortgage_affordability_to_value(result: &MortgageAffordability) -> Value { Value::obj(vec![ ("incomeMultipleBasisPoints", Value::Int(result.income_multiple_basis_points)), ("maxLoanByMultiple", money_to_value(&result.max_loan_by_multiple)), ("withinMultiple", Value::Bool(result.within_multiple)), ("monthlyIncome", money_to_value(&result.monthly_income)), ("repayment", affordability_result_to_value(&result.repayment)), ("affordable", Value::Bool(result.affordable)), ]) } pub fn fune_vector(args: &[Value]) -> Value { mortgage_affordability_to_value(&mortgage_affordability( &money_from_value(&args[0]), &money_from_value(&args[1]), &money_from_value(&args[2]), args[3].as_i64(), args[4].as_i64(), args[5].as_i64(), args[6].as_i64(), args[7].as_i64(), )) }