Coverage for apps/fees_trid/schemas.py: 95%

78 statements  

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

1"""Ninja schemas for the TRID fees / tolerance / QM API. 

2 

3``FeeOut`` is a ``ModelSchema`` over the ORM row; summary / tolerance / QM outputs are 

4hand-written ``Schema``s over the pure-domain results. Money is plain ``Decimal`` (rendered 

5as exact strings by pydantic). 

6""" 

7 

8from __future__ import annotations 

9 

10from decimal import Decimal 

11 

12from ninja import ModelSchema, Schema 

13from pydantic import field_validator 

14 

15from apps.fees_trid.models import Fee 

16 

17VALID_FEE_SECTIONS = frozenset(["A", "B", "C", "E", "F", "G", "H"]) 

18 

19 

20class FeeOut(ModelSchema): 

21 class Meta: 

22 model = Fee 

23 exclude = ["loan"] 

24 

25 

26class FeeIn(Schema): 

27 section: str 

28 name: str 

29 total_amt: Decimal 

30 tolerance_bucket: str | None = None 

31 paid_by: str = "Borrower" 

32 paid_to: str = "ThirdParty" 

33 poc_ind: bool = False 

34 

35 @field_validator("name") 

36 @classmethod 

37 def validate_name(cls, v: str) -> str: 

38 clean = v.strip() if v else "" 

39 if not clean: 

40 raise ValueError("Fee name cannot be blank") 

41 if len(clean) > 255: 41 ↛ 42line 41 didn't jump to line 42 because the condition on line 41 was never true

42 raise ValueError("Fee name cannot exceed 255 characters") 

43 return clean 

44 

45 @field_validator("total_amt") 

46 @classmethod 

47 def validate_total_amt(cls, v: Decimal) -> Decimal: 

48 if v < Decimal("0.00"): 

49 raise ValueError("Fee total amount cannot be negative") 

50 if v > Decimal("999999999.99"): 50 ↛ 51line 50 didn't jump to line 51 because the condition on line 50 was never true

51 raise ValueError("Fee total amount cannot exceed $999,999,999.99") 

52 return v 

53 

54 @field_validator("section") 

55 @classmethod 

56 def validate_section(cls, v: str) -> str: 

57 s = v.strip().upper() if v else "" 

58 if s not in VALID_FEE_SECTIONS: 

59 raise ValueError( 

60 f"Invalid TRID fee section: '{s}'. Expected one of {sorted(VALID_FEE_SECTIONS)}" 

61 ) 

62 return s 

63 

64 

65class FeeSetIn(Schema): 

66 fees: list[FeeIn] = [] 

67 

68 

69class BucketTotalOut(Schema): 

70 bucket: str 

71 total: Decimal 

72 

73 

74class SectionTotalOut(Schema): 

75 section: str 

76 total: Decimal 

77 

78 

79class FeeSummaryOut(Schema): 

80 by_bucket: list[BucketTotalOut] 

81 by_section: list[SectionTotalOut] 

82 grand_total: Decimal 

83 poc_total: Decimal 

84 

85 

86class BucketCureOut(Schema): 

87 bucket: str 

88 baseline: Decimal 

89 revised: Decimal 

90 variance: Decimal 

91 cure: Decimal 

92 

93 

94class ToleranceOut(Schema): 

95 by_bucket: list[BucketCureOut] 

96 total_cure: Decimal 

97 

98 

99class QmFindingOut(Schema): 

100 code: str 

101 severity: str 

102 message: str 

103 

104 

105class QmResultOut(Schema): 

106 loan_amount: Decimal 

107 points_and_fees: Decimal 

108 threshold: Decimal 

109 pct: Decimal 

110 passed: bool 

111 qm_type: str 

112 apr_spread: Decimal 

113 findings: list[QmFindingOut] = [] 

114 points_and_fees_source: str