Coverage for apps/underwriting/models.py: 94%

68 statements  

« prev     ^ index     » next       coverage.py v7.17.0, created at 2026-11-04 17:24 +0000

1"""Credit ordering + scores (Sprint 4 / S4-01). AUS models land with add-dual-aus.""" 

2 

3from django.conf import settings 

4from django.db import models 

5 

6from domain.enums import ConditionSource, ConditionStatus, PriorToCategory, enum_choices 

7 

8 

9class CreditOrder(models.Model): 

10 REQUEST_TYPES = [ 

11 ("New", "New"), 

12 ("Reissue", "Reissue"), 

13 ("Refresh", "Refresh"), 

14 ("Supplement", "Supplement"), 

15 ] 

16 STATUSES = [ 

17 ("Ordered", "Ordered"), 

18 ("InProgress", "In Progress"), 

19 ("Completed", "Completed"), 

20 ("Error", "Error"), 

21 ] 

22 

23 loan = models.ForeignKey( 

24 "core_los.Loan", on_delete=models.CASCADE, related_name="credit_orders" 

25 ) 

26 credit_company_id = models.CharField(max_length=100, blank=True) 

27 request_type = models.CharField(max_length=20, choices=REQUEST_TYPES, default="New") 

28 status = models.CharField(max_length=20, choices=STATUSES, default="Ordered") 

29 report_id = models.CharField(max_length=100, blank=True) 

30 created_at = models.DateTimeField(auto_now_add=True) 

31 

32 class Meta: 

33 db_table = "credit_orders" 

34 

35 def __str__(self) -> str: 

36 return f"CreditOrder {self.pk} ({self.status})" 

37 

38 

39class CreditScore(models.Model): 

40 order = models.ForeignKey(CreditOrder, on_delete=models.CASCADE, related_name="scores") 

41 borrower = models.ForeignKey( 

42 "application_1003.Borrower", on_delete=models.CASCADE, related_name="credit_scores" 

43 ) 

44 equifax = models.IntegerField(null=True, blank=True) 

45 experian = models.IntegerField(null=True, blank=True) 

46 transunion = models.IntegerField(null=True, blank=True) 

47 mid = models.IntegerField(null=True, blank=True) 

48 

49 class Meta: 

50 db_table = "credit_scores" 

51 

52 def __str__(self) -> str: 

53 return f"Scores for borrower {self.borrower_id} (mid {self.mid})" 

54 

55 

56class AusRun(models.Model): 

57 ENGINES = [("du", "Fannie DU"), ("lpa", "Freddie LPA")] 

58 

59 loan = models.ForeignKey("core_los.Loan", on_delete=models.CASCADE, related_name="aus_runs") 

60 engine = models.CharField(max_length=10, choices=ENGINES) 

61 recommendation = models.CharField(max_length=100, blank=True) 

62 risk_class = models.CharField(max_length=50, blank=True) 

63 housing_pct = models.DecimalField(max_digits=6, decimal_places=2, null=True, blank=True) 

64 dti_pct = models.DecimalField(max_digits=6, decimal_places=2, null=True, blank=True) 

65 ltv_pct = models.DecimalField(max_digits=6, decimal_places=2, null=True, blank=True) 

66 cltv_pct = models.DecimalField(max_digits=6, decimal_places=2, null=True, blank=True) 

67 messages = models.JSONField(default=list, blank=True) 

68 verifications = models.JSONField(default=list, blank=True) 

69 findings_conditions = models.JSONField(default=list, blank=True) 

70 casefile_id = models.CharField(max_length=100, blank=True) 

71 credit_report_id = models.CharField(max_length=100, blank=True) 

72 submitted_at = models.DateTimeField(auto_now_add=True) 

73 

74 class Meta: 

75 db_table = "aus_runs" 

76 

77 def __str__(self) -> str: 

78 return f"{self.engine.upper()} run {self.pk}: {self.recommendation}" 

79 

80 

81class Condition(models.Model): 

82 """An underwriting condition on a loan (ADR-001: core LOS, status gating included).""" 

83 

84 loan = models.ForeignKey( 

85 "core_los.Loan", on_delete=models.CASCADE, related_name="conditions" 

86 ) 

87 name = models.CharField(max_length=200) 

88 description = models.TextField(blank=True) 

89 category = models.CharField(max_length=30, choices=enum_choices(PriorToCategory)) 

90 status = models.CharField( 

91 max_length=20, choices=enum_choices(ConditionStatus), default=ConditionStatus.ADDED.value 

92 ) 

93 source = models.CharField( 

94 max_length=10, choices=enum_choices(ConditionSource), default=ConditionSource.MANUAL.value 

95 ) 

96 owner = models.ForeignKey( 

97 settings.AUTH_USER_MODEL, 

98 on_delete=models.SET_NULL, 

99 null=True, 

100 blank=True, 

101 related_name="owned_conditions", 

102 ) 

103 due_date = models.DateField(null=True, blank=True) 

104 documents = models.ManyToManyField( 

105 "client_needs.Document", blank=True, related_name="linked_conditions" 

106 ) 

107 # ADR-001 Tireless-sync hooks: nullable, write-once-by-nobody-yet (design.md D5). 

108 # Populated only by a future Tireless condition-sync integration; never required. 

109 external_condition_id = models.CharField(max_length=100, null=True, blank=True) 

110 fraud_score = models.PositiveSmallIntegerField(null=True, blank=True) 

111 fraud_warnings = models.JSONField(null=True, blank=True) 

112 extracted_data = models.JSONField(null=True, blank=True) 

113 created_at = models.DateTimeField(auto_now_add=True) 

114 updated_at = models.DateTimeField(auto_now=True) 

115 

116 class Meta: 

117 db_table = "underwriting_conditions" 

118 indexes = [ 

119 models.Index(fields=["loan", "status"]), 

120 models.Index(fields=["loan", "category"]), 

121 ] 

122 

123 def __str__(self) -> str: 

124 return f"Condition {self.pk} ({self.status})"