Coverage for apps/underwriting/schemas.py: 81%
92 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"""Ninja schemas for credit ordering + scores."""
3from ninja import ModelSchema, Schema
4from pydantic import field_validator
6from apps.underwriting.models import AusRun, Condition, CreditOrder, CreditScore
9class CreditScoreOut(ModelSchema):
10 borrower_id: int | None = None
11 borrower_name: str | None = None
13 class Meta:
14 model = CreditScore
15 exclude = ["order"]
17 @staticmethod
18 def resolve_borrower_id(obj: CreditScore) -> int | None:
19 return obj.borrower_id
21 @staticmethod
22 def resolve_borrower_name(obj: CreditScore) -> str | None:
23 if hasattr(obj, "borrower") and obj.borrower: 23 ↛ 26line 23 didn't jump to line 26 because the condition on line 23 was always true
24 name = f"{obj.borrower.first_name} {obj.borrower.last_name}".strip()
25 return name or None
26 return None
29class CreditOrderOut(ModelSchema):
30 scores: list[CreditScoreOut] = []
31 provider_status: str = "live"
33 class Meta:
34 model = CreditOrder
35 exclude = ["loan"]
38class CreditOrderCreate(Schema):
39 request_type: str = "New"
40 credit_company_id: str = ""
43class ImportLiabilitiesIn(Schema):
44 credit_order_id: int | None = None
47class ImportLiabilitiesOut(Schema):
48 imported: int
51class AusIssueOut(Schema):
52 category: str
53 code: str
54 message: str
55 engines: list[str] = []
56 severity: str = "block"
59class AusEligibilityOut(Schema):
60 du_eligible: bool
61 lpa_eligible: bool
62 issues: list[AusIssueOut]
65class AusRunOut(ModelSchema):
66 class Meta:
67 model = AusRun
68 exclude = ["loan"]
71class AusComparisonOut(Schema):
72 du: AusRunOut | None = None
73 lpa: AusRunOut | None = None
74 runs: list[AusRunOut] = []
77class ConditionOut(ModelSchema):
78 document_ids: list[int] = []
80 class Meta:
81 model = Condition
82 exclude = ["loan", "documents"]
84 @staticmethod
85 def resolve_document_ids(obj: Condition) -> list[int]:
86 return list(obj.documents.values_list("id", flat=True))
89class ConditionCreate(Schema):
90 name: str
91 description: str = ""
92 category: str
93 source: str = "Manual"
94 owner_id: int | None = None
95 due_date: str | None = None
97 @field_validator("name")
98 @classmethod
99 def validate_name(cls, v: str) -> str:
100 s = v.strip() if v else ""
101 if not s: 101 ↛ 102line 101 didn't jump to line 102 because the condition on line 101 was never true
102 raise ValueError("Condition name cannot be blank.")
103 if len(s) > 255: 103 ↛ 104line 103 didn't jump to line 104 because the condition on line 103 was never true
104 raise ValueError("Condition name cannot exceed 255 characters.")
105 return s
108class ConditionPatch(Schema):
109 name: str | None = None
110 description: str | None = None
111 owner_id: int | None = None
112 due_date: str | None = None
113 # Not a Condition column — replaces the condition's linked Document set (M2M).
114 document_ids: list[int] | None = None
116 @field_validator("name")
117 @classmethod
118 def validate_name(cls, v: str | None) -> str | None:
119 if v is None:
120 return None
121 s = v.strip()
122 if not s:
123 raise ValueError("Condition name cannot be blank.")
124 if len(s) > 255:
125 raise ValueError("Condition name cannot exceed 255 characters.")
126 return s
129class ConditionStatusPatch(Schema):
130 status: str