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

1"""Ninja schemas for credit ordering + scores.""" 

2 

3from ninja import ModelSchema, Schema 

4from pydantic import field_validator 

5 

6from apps.underwriting.models import AusRun, Condition, CreditOrder, CreditScore 

7 

8 

9class CreditScoreOut(ModelSchema): 

10 borrower_id: int | None = None 

11 borrower_name: str | None = None 

12 

13 class Meta: 

14 model = CreditScore 

15 exclude = ["order"] 

16 

17 @staticmethod 

18 def resolve_borrower_id(obj: CreditScore) -> int | None: 

19 return obj.borrower_id 

20 

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 

27 

28 

29class CreditOrderOut(ModelSchema): 

30 scores: list[CreditScoreOut] = [] 

31 provider_status: str = "live" 

32 

33 class Meta: 

34 model = CreditOrder 

35 exclude = ["loan"] 

36 

37 

38class CreditOrderCreate(Schema): 

39 request_type: str = "New" 

40 credit_company_id: str = "" 

41 

42 

43class ImportLiabilitiesIn(Schema): 

44 credit_order_id: int | None = None 

45 

46 

47class ImportLiabilitiesOut(Schema): 

48 imported: int 

49 

50 

51class AusIssueOut(Schema): 

52 category: str 

53 code: str 

54 message: str 

55 engines: list[str] = [] 

56 severity: str = "block" 

57 

58 

59class AusEligibilityOut(Schema): 

60 du_eligible: bool 

61 lpa_eligible: bool 

62 issues: list[AusIssueOut] 

63 

64 

65class AusRunOut(ModelSchema): 

66 class Meta: 

67 model = AusRun 

68 exclude = ["loan"] 

69 

70 

71class AusComparisonOut(Schema): 

72 du: AusRunOut | None = None 

73 lpa: AusRunOut | None = None 

74 runs: list[AusRunOut] = [] 

75 

76 

77class ConditionOut(ModelSchema): 

78 document_ids: list[int] = [] 

79 

80 class Meta: 

81 model = Condition 

82 exclude = ["loan", "documents"] 

83 

84 @staticmethod 

85 def resolve_document_ids(obj: Condition) -> list[int]: 

86 return list(obj.documents.values_list("id", flat=True)) 

87 

88 

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 

96 

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 

106 

107 

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 

115 

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 

127 

128 

129class ConditionStatusPatch(Schema): 

130 status: str