Coverage for apps/application_1003/qualification_inputs.py: 100%
15 statements
« prev ^ index » next coverage.py v7.17.0, created at 2026-11-05 09:30 +0000
« prev ^ index » next coverage.py v7.17.0, created at 2026-11-05 09:30 +0000
1"""Build the pure ``qualify()`` inputs from a loan's 1003 records.
3Shared by ``GET /{guid}/qualification`` and the AUS DU/LPA runs so both report the same DTI.
4"""
6from __future__ import annotations
8from apps.core_los.models import Loan, LoanTransaction
9from domain.calc import monthly_pi
10from domain.qualification import HousingItem, IncomeItem, LiabilityItem, RentalItem
12#: REO disposition meaning the property is already sold, so it no longer counts.
13SOLD_DISPOSITION = "Sell"
16def counted_reo(loan: Loan) -> list:
17 """REO rows that feed rental cash flow: everything except the subject property and sold ones."""
18 return [
19 reo
20 for reo in loan.real_estate.all()
21 if not reo.subject_property_ind and reo.disposition_status_type != SOLD_DISPOSITION
22 ]
25def qualification_inputs(loan: Loan) -> dict:
26 """Keyword arguments for ``domain.qualification.qualify`` built from the loan."""
27 reos = counted_reo(loan)
28 counted_ids = {reo.id for reo in reos}
29 try:
30 txn = loan.transaction
31 except LoanTransaction.DoesNotExist:
32 txn = None
33 return {
34 "incomes": [IncomeItem(i.monthly_amt, i.current_ind) for i in loan.incomes.all()],
35 "liabilities": [
36 LiabilityItem(
37 liab.monthly_payment_amt,
38 liab.exclude_from_dti_ind,
39 liab.omit_ind,
40 linked_reo_counted=liab.real_estate_id in counted_ids,
41 )
42 for liab in loan.liabilities.all()
43 ],
44 "housing": [HousingItem(h.payment_amt, h.timing_type) for h in loan.housing_expenses.all()],
45 "rentals": [
46 RentalItem(
47 reo.gross_rental_income_amt,
48 reo.mortgage_monthly_payment_amt,
49 reo.monthly_taxes_ins_hoa_amt,
50 )
51 for reo in reos
52 ],
53 "fallback_pi": (
54 monthly_pi(txn.base_loan_amt, txn.note_rate_percent or 0, txn.term_months)
55 if txn is not None
56 else None
57 ),
58 }