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
« 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."""
3from django.conf import settings
4from django.db import models
6from domain.enums import ConditionSource, ConditionStatus, PriorToCategory, enum_choices
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 ]
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)
32 class Meta:
33 db_table = "credit_orders"
35 def __str__(self) -> str:
36 return f"CreditOrder {self.pk} ({self.status})"
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)
49 class Meta:
50 db_table = "credit_scores"
52 def __str__(self) -> str:
53 return f"Scores for borrower {self.borrower_id} (mid {self.mid})"
56class AusRun(models.Model):
57 ENGINES = [("du", "Fannie DU"), ("lpa", "Freddie LPA")]
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)
74 class Meta:
75 db_table = "aus_runs"
77 def __str__(self) -> str:
78 return f"{self.engine.upper()} run {self.pk}: {self.recommendation}"
81class Condition(models.Model):
82 """An underwriting condition on a loan (ADR-001: core LOS, status gating included)."""
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)
116 class Meta:
117 db_table = "underwriting_conditions"
118 indexes = [
119 models.Index(fields=["loan", "status"]),
120 models.Index(fields=["loan", "category"]),
121 ]
123 def __str__(self) -> str:
124 return f"Condition {self.pk} ({self.status})"