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

1"""Build the pure ``qualify()`` inputs from a loan's 1003 records. 

2 

3Shared by ``GET /{guid}/qualification`` and the AUS DU/LPA runs so both report the same DTI. 

4""" 

5 

6from __future__ import annotations 

7 

8from apps.core_los.models import Loan, LoanTransaction 

9from domain.calc import monthly_pi 

10from domain.qualification import HousingItem, IncomeItem, LiabilityItem, RentalItem 

11 

12#: REO disposition meaning the property is already sold, so it no longer counts. 

13SOLD_DISPOSITION = "Sell" 

14 

15 

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 ] 

23 

24 

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 }