mortgage-utils 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mortgage_utils-1.0.0/PKG-INFO +171 -0
- mortgage_utils-1.0.0/README.md +147 -0
- mortgage_utils-1.0.0/mortgage_utils/__init__.py +30 -0
- mortgage_utils-1.0.0/mortgage_utils/affordability.py +100 -0
- mortgage_utils-1.0.0/mortgage_utils/amortization.py +99 -0
- mortgage_utils-1.0.0/mortgage_utils/dti.py +119 -0
- mortgage_utils-1.0.0/mortgage_utils.egg-info/PKG-INFO +171 -0
- mortgage_utils-1.0.0/mortgage_utils.egg-info/SOURCES.txt +11 -0
- mortgage_utils-1.0.0/mortgage_utils.egg-info/dependency_links.txt +1 -0
- mortgage_utils-1.0.0/mortgage_utils.egg-info/top_level.txt +1 -0
- mortgage_utils-1.0.0/pyproject.toml +46 -0
- mortgage_utils-1.0.0/setup.cfg +4 -0
- mortgage_utils-1.0.0/tests/test_mortgage_utils.py +85 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mortgage-utils
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Lightweight Python utility library for mortgage calculations — DTI, amortization, affordability, LTV.
|
|
5
|
+
Author-email: Sanjeev Kumar <contact@ournethelps.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://ournethelps.com
|
|
8
|
+
Project-URL: Repository, https://github.com/sanjeevkumardev/mortgage-utils-py
|
|
9
|
+
Project-URL: Bug Tracker, https://github.com/sanjeevkumardev/mortgage-utils-py/issues
|
|
10
|
+
Keywords: mortgage,calculator,dti,debt-to-income,amortization,affordability,ltv,fintech,real-estate,home-loan,mortgage-broker
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Office/Business :: Financial
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Requires-Python: >=3.8
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# mortgage-utils
|
|
26
|
+
|
|
27
|
+
> Lightweight Python utility library for mortgage calculations - DTI, amortization, affordability, and LTV.
|
|
28
|
+
|
|
29
|
+
Built by **[OurNetHelps](https://ournethelps.com)** - mortgage tools for US mortgage brokers.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install mortgage-utils
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Features
|
|
42
|
+
|
|
43
|
+
- DTI Calculator - front-end and back-end debt-to-income ratio
|
|
44
|
+
- Amortization - monthly payment and full amortization schedule
|
|
45
|
+
- Affordability - maximum loan amount based on income and debts
|
|
46
|
+
- LTV - loan-to-value ratio with PMI threshold detection
|
|
47
|
+
- Zero dependencies
|
|
48
|
+
- Python 3.8 and above
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Usage
|
|
53
|
+
|
|
54
|
+
### DTI (Debt-to-Income)
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from mortgage_utils import front_end_dti, back_end_dti, dti_report
|
|
58
|
+
|
|
59
|
+
# Front-End DTI
|
|
60
|
+
front = front_end_dti(monthly_housing_cost=1800, gross_monthly_income=7000)
|
|
61
|
+
print(front)
|
|
62
|
+
# {'ratio': 0.2571, 'percentage': 25.71, 'status': 'good', ...}
|
|
63
|
+
|
|
64
|
+
# Back-End DTI
|
|
65
|
+
back = back_end_dti(
|
|
66
|
+
monthly_housing_cost=1800,
|
|
67
|
+
monthly_debts=500,
|
|
68
|
+
gross_monthly_income=7000
|
|
69
|
+
)
|
|
70
|
+
print(back)
|
|
71
|
+
# {'ratio': 0.3286, 'percentage': 32.86, 'status': 'good', ...}
|
|
72
|
+
|
|
73
|
+
# Full DTI Report
|
|
74
|
+
report = dti_report(
|
|
75
|
+
monthly_housing_cost=1800,
|
|
76
|
+
monthly_debts=500,
|
|
77
|
+
gross_monthly_income=7000
|
|
78
|
+
)
|
|
79
|
+
print(report['qualified']) # True
|
|
80
|
+
print(report['summary']) # "Front-End: 25.71% | Back-End: 32.86%"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Amortization
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
from mortgage_utils import monthly_payment, amortization_schedule
|
|
87
|
+
|
|
88
|
+
# Monthly Payment
|
|
89
|
+
result = monthly_payment(
|
|
90
|
+
loan_amount=300000,
|
|
91
|
+
annual_interest_rate=6.5,
|
|
92
|
+
loan_term_years=30
|
|
93
|
+
)
|
|
94
|
+
print(result['monthly_payment']) # 1896.20
|
|
95
|
+
print(result['total_interest']) # 382632.0
|
|
96
|
+
|
|
97
|
+
# Full Amortization Schedule
|
|
98
|
+
schedule = amortization_schedule(300000, 6.5, 30)
|
|
99
|
+
print(schedule[0])
|
|
100
|
+
# {'month': 1, 'payment': 1896.20, 'principal': 271.2, 'interest': 1625.0, 'balance': 299728.8}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Affordability
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from mortgage_utils import max_affordable_loan, loan_to_value
|
|
107
|
+
|
|
108
|
+
# Max Affordable Loan
|
|
109
|
+
result = max_affordable_loan(
|
|
110
|
+
gross_monthly_income=8000,
|
|
111
|
+
monthly_debts=500,
|
|
112
|
+
annual_interest_rate=6.5,
|
|
113
|
+
loan_term_years=30
|
|
114
|
+
)
|
|
115
|
+
print(result['estimated_loan_amount']) # max loan amount
|
|
116
|
+
print(result['max_monthly_payment']) # max monthly payment
|
|
117
|
+
|
|
118
|
+
# LTV Ratio
|
|
119
|
+
ltv = loan_to_value(loan_amount=320000, property_value=400000)
|
|
120
|
+
print(ltv['percentage']) # 80.0
|
|
121
|
+
print(ltv['requires_pmi']) # False
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## API Reference
|
|
127
|
+
|
|
128
|
+
### `front_end_dti(monthly_housing_cost, gross_monthly_income)`
|
|
129
|
+
Returns front-end DTI with status (good / caution / high).
|
|
130
|
+
|
|
131
|
+
### `back_end_dti(monthly_housing_cost, monthly_debts, gross_monthly_income)`
|
|
132
|
+
Returns back-end DTI including all monthly debts.
|
|
133
|
+
|
|
134
|
+
### `dti_report(monthly_housing_cost, monthly_debts, gross_monthly_income)`
|
|
135
|
+
Returns full DTI report with qualified boolean and summary.
|
|
136
|
+
|
|
137
|
+
### `monthly_payment(loan_amount, annual_interest_rate, loan_term_years)`
|
|
138
|
+
Returns monthly P&I payment, total payment, and total interest.
|
|
139
|
+
|
|
140
|
+
### `amortization_schedule(loan_amount, annual_interest_rate, loan_term_years)`
|
|
141
|
+
Returns list of monthly payment breakdowns.
|
|
142
|
+
|
|
143
|
+
### `max_affordable_loan(gross_monthly_income, monthly_debts, annual_interest_rate, loan_term_years, max_dti=43.0)`
|
|
144
|
+
Returns max affordable loan amount based on income and DTI threshold.
|
|
145
|
+
|
|
146
|
+
### `loan_to_value(loan_amount, property_value)`
|
|
147
|
+
Returns LTV ratio, percentage, down payment, and PMI requirement flag.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## DTI Status Thresholds
|
|
152
|
+
|
|
153
|
+
| Status | Front-End DTI | Back-End DTI |
|
|
154
|
+
|--------|--------------|--------------|
|
|
155
|
+
| good | 28% or less | 36% or less |
|
|
156
|
+
| caution | 29% to 36% | 37% to 43% |
|
|
157
|
+
| high | Above 36% | Above 43% |
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Related
|
|
162
|
+
|
|
163
|
+
- [OurNetHelps](https://ournethelps.com) - mortgage tools for US mortgage brokers
|
|
164
|
+
- [mortgage-utils on NPM](https://www.npmjs.com/package/mortgage-utils) - JavaScript version
|
|
165
|
+
- [Live Mortgage Calculator](https://huggingface.co/spaces/sanjeevkumardev/mortgage-suite)
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## License
|
|
170
|
+
|
|
171
|
+
MIT © [Sanjeev Kumar](https://ournethelps.com)
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# mortgage-utils
|
|
2
|
+
|
|
3
|
+
> Lightweight Python utility library for mortgage calculations - DTI, amortization, affordability, and LTV.
|
|
4
|
+
|
|
5
|
+
Built by **[OurNetHelps](https://ournethelps.com)** - mortgage tools for US mortgage brokers.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install mortgage-utils
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Features
|
|
18
|
+
|
|
19
|
+
- DTI Calculator - front-end and back-end debt-to-income ratio
|
|
20
|
+
- Amortization - monthly payment and full amortization schedule
|
|
21
|
+
- Affordability - maximum loan amount based on income and debts
|
|
22
|
+
- LTV - loan-to-value ratio with PMI threshold detection
|
|
23
|
+
- Zero dependencies
|
|
24
|
+
- Python 3.8 and above
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
### DTI (Debt-to-Income)
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from mortgage_utils import front_end_dti, back_end_dti, dti_report
|
|
34
|
+
|
|
35
|
+
# Front-End DTI
|
|
36
|
+
front = front_end_dti(monthly_housing_cost=1800, gross_monthly_income=7000)
|
|
37
|
+
print(front)
|
|
38
|
+
# {'ratio': 0.2571, 'percentage': 25.71, 'status': 'good', ...}
|
|
39
|
+
|
|
40
|
+
# Back-End DTI
|
|
41
|
+
back = back_end_dti(
|
|
42
|
+
monthly_housing_cost=1800,
|
|
43
|
+
monthly_debts=500,
|
|
44
|
+
gross_monthly_income=7000
|
|
45
|
+
)
|
|
46
|
+
print(back)
|
|
47
|
+
# {'ratio': 0.3286, 'percentage': 32.86, 'status': 'good', ...}
|
|
48
|
+
|
|
49
|
+
# Full DTI Report
|
|
50
|
+
report = dti_report(
|
|
51
|
+
monthly_housing_cost=1800,
|
|
52
|
+
monthly_debts=500,
|
|
53
|
+
gross_monthly_income=7000
|
|
54
|
+
)
|
|
55
|
+
print(report['qualified']) # True
|
|
56
|
+
print(report['summary']) # "Front-End: 25.71% | Back-End: 32.86%"
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Amortization
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from mortgage_utils import monthly_payment, amortization_schedule
|
|
63
|
+
|
|
64
|
+
# Monthly Payment
|
|
65
|
+
result = monthly_payment(
|
|
66
|
+
loan_amount=300000,
|
|
67
|
+
annual_interest_rate=6.5,
|
|
68
|
+
loan_term_years=30
|
|
69
|
+
)
|
|
70
|
+
print(result['monthly_payment']) # 1896.20
|
|
71
|
+
print(result['total_interest']) # 382632.0
|
|
72
|
+
|
|
73
|
+
# Full Amortization Schedule
|
|
74
|
+
schedule = amortization_schedule(300000, 6.5, 30)
|
|
75
|
+
print(schedule[0])
|
|
76
|
+
# {'month': 1, 'payment': 1896.20, 'principal': 271.2, 'interest': 1625.0, 'balance': 299728.8}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Affordability
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from mortgage_utils import max_affordable_loan, loan_to_value
|
|
83
|
+
|
|
84
|
+
# Max Affordable Loan
|
|
85
|
+
result = max_affordable_loan(
|
|
86
|
+
gross_monthly_income=8000,
|
|
87
|
+
monthly_debts=500,
|
|
88
|
+
annual_interest_rate=6.5,
|
|
89
|
+
loan_term_years=30
|
|
90
|
+
)
|
|
91
|
+
print(result['estimated_loan_amount']) # max loan amount
|
|
92
|
+
print(result['max_monthly_payment']) # max monthly payment
|
|
93
|
+
|
|
94
|
+
# LTV Ratio
|
|
95
|
+
ltv = loan_to_value(loan_amount=320000, property_value=400000)
|
|
96
|
+
print(ltv['percentage']) # 80.0
|
|
97
|
+
print(ltv['requires_pmi']) # False
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## API Reference
|
|
103
|
+
|
|
104
|
+
### `front_end_dti(monthly_housing_cost, gross_monthly_income)`
|
|
105
|
+
Returns front-end DTI with status (good / caution / high).
|
|
106
|
+
|
|
107
|
+
### `back_end_dti(monthly_housing_cost, monthly_debts, gross_monthly_income)`
|
|
108
|
+
Returns back-end DTI including all monthly debts.
|
|
109
|
+
|
|
110
|
+
### `dti_report(monthly_housing_cost, monthly_debts, gross_monthly_income)`
|
|
111
|
+
Returns full DTI report with qualified boolean and summary.
|
|
112
|
+
|
|
113
|
+
### `monthly_payment(loan_amount, annual_interest_rate, loan_term_years)`
|
|
114
|
+
Returns monthly P&I payment, total payment, and total interest.
|
|
115
|
+
|
|
116
|
+
### `amortization_schedule(loan_amount, annual_interest_rate, loan_term_years)`
|
|
117
|
+
Returns list of monthly payment breakdowns.
|
|
118
|
+
|
|
119
|
+
### `max_affordable_loan(gross_monthly_income, monthly_debts, annual_interest_rate, loan_term_years, max_dti=43.0)`
|
|
120
|
+
Returns max affordable loan amount based on income and DTI threshold.
|
|
121
|
+
|
|
122
|
+
### `loan_to_value(loan_amount, property_value)`
|
|
123
|
+
Returns LTV ratio, percentage, down payment, and PMI requirement flag.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## DTI Status Thresholds
|
|
128
|
+
|
|
129
|
+
| Status | Front-End DTI | Back-End DTI |
|
|
130
|
+
|--------|--------------|--------------|
|
|
131
|
+
| good | 28% or less | 36% or less |
|
|
132
|
+
| caution | 29% to 36% | 37% to 43% |
|
|
133
|
+
| high | Above 36% | Above 43% |
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Related
|
|
138
|
+
|
|
139
|
+
- [OurNetHelps](https://ournethelps.com) - mortgage tools for US mortgage brokers
|
|
140
|
+
- [mortgage-utils on NPM](https://www.npmjs.com/package/mortgage-utils) - JavaScript version
|
|
141
|
+
- [Live Mortgage Calculator](https://huggingface.co/spaces/sanjeevkumardev/mortgage-suite)
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## License
|
|
146
|
+
|
|
147
|
+
MIT © [Sanjeev Kumar](https://ournethelps.com)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""
|
|
2
|
+
mortgage-utils
|
|
3
|
+
--------------
|
|
4
|
+
Lightweight Python utility library for mortgage calculations.
|
|
5
|
+
Built by OurNetHelps: https://ournethelps.com
|
|
6
|
+
|
|
7
|
+
Modules:
|
|
8
|
+
dti: front_end_dti, back_end_dti, dti_report
|
|
9
|
+
amortization: monthly_payment, amortization_schedule
|
|
10
|
+
affordability: max_affordable_loan, loan_to_value
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from .dti import front_end_dti, back_end_dti, dti_report
|
|
14
|
+
from .amortization import monthly_payment, amortization_schedule
|
|
15
|
+
from .affordability import max_affordable_loan, loan_to_value
|
|
16
|
+
|
|
17
|
+
__version__ = "1.0.0"
|
|
18
|
+
__author__ = "Sanjeev Kumar"
|
|
19
|
+
__email__ = "contact@ournethelps.com"
|
|
20
|
+
__url__ = "https://ournethelps.com"
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"front_end_dti",
|
|
24
|
+
"back_end_dti",
|
|
25
|
+
"dti_report",
|
|
26
|
+
"monthly_payment",
|
|
27
|
+
"amortization_schedule",
|
|
28
|
+
"max_affordable_loan",
|
|
29
|
+
"loan_to_value",
|
|
30
|
+
]
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Affordability and LTV Calculations
|
|
3
|
+
------------------------------------
|
|
4
|
+
Built by OurNetHelps: https://ournethelps.com
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def max_affordable_loan(
|
|
9
|
+
gross_monthly_income: float,
|
|
10
|
+
monthly_debts: float,
|
|
11
|
+
annual_interest_rate: float,
|
|
12
|
+
loan_term_years: int,
|
|
13
|
+
max_dti: float = 43.0,
|
|
14
|
+
) -> dict:
|
|
15
|
+
"""
|
|
16
|
+
Calculate maximum affordable loan based on income and debts.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
gross_monthly_income: Borrower's gross monthly income
|
|
20
|
+
monthly_debts: Existing monthly debt obligations
|
|
21
|
+
annual_interest_rate: Annual interest rate (e.g. 6.5 for 6.5%)
|
|
22
|
+
loan_term_years: Loan term in years
|
|
23
|
+
max_dti: Maximum back-end DTI allowed (default 43%)
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
dict with max_monthly_payment, estimated_loan_amount, max_dti_used
|
|
27
|
+
|
|
28
|
+
Raises:
|
|
29
|
+
ValueError: If gross_monthly_income is zero or negative
|
|
30
|
+
"""
|
|
31
|
+
if gross_monthly_income <= 0:
|
|
32
|
+
raise ValueError("Gross monthly income must be greater than zero.")
|
|
33
|
+
|
|
34
|
+
max_monthly_debt = (gross_monthly_income * max_dti) / 100
|
|
35
|
+
max_monthly_payment = round(max_monthly_debt - monthly_debts, 2)
|
|
36
|
+
|
|
37
|
+
if max_monthly_payment <= 0:
|
|
38
|
+
return {
|
|
39
|
+
"max_monthly_payment": 0,
|
|
40
|
+
"estimated_loan_amount": 0,
|
|
41
|
+
"max_dti_used": max_dti,
|
|
42
|
+
"message": "Existing debts exceed the maximum DTI threshold.",
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
monthly_rate = annual_interest_rate / 100 / 12
|
|
46
|
+
n = loan_term_years * 12
|
|
47
|
+
|
|
48
|
+
if monthly_rate == 0:
|
|
49
|
+
estimated_loan = max_monthly_payment * n
|
|
50
|
+
else:
|
|
51
|
+
estimated_loan = (
|
|
52
|
+
max_monthly_payment
|
|
53
|
+
* ((1 + monthly_rate) ** n - 1)
|
|
54
|
+
/ (monthly_rate * (1 + monthly_rate) ** n)
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
"max_monthly_payment": max_monthly_payment,
|
|
59
|
+
"estimated_loan_amount": round(estimated_loan, 2),
|
|
60
|
+
"max_dti_used": max_dti,
|
|
61
|
+
"gross_monthly_income": gross_monthly_income,
|
|
62
|
+
"monthly_debts": monthly_debts,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def loan_to_value(loan_amount: float, property_value: float) -> dict:
|
|
67
|
+
"""
|
|
68
|
+
Calculate LTV (Loan-to-Value Ratio).
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
loan_amount: Total loan amount in dollars
|
|
72
|
+
property_value: Appraised property value in dollars
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
dict with ltv, percentage, requires_pmi, down_payment, status, guideline
|
|
76
|
+
|
|
77
|
+
Raises:
|
|
78
|
+
ValueError: If property_value is zero or negative
|
|
79
|
+
"""
|
|
80
|
+
if property_value <= 0:
|
|
81
|
+
raise ValueError("Property value must be greater than zero.")
|
|
82
|
+
|
|
83
|
+
ltv = loan_amount / property_value
|
|
84
|
+
percentage = round(ltv * 100, 2)
|
|
85
|
+
|
|
86
|
+
if percentage <= 80:
|
|
87
|
+
status = "good"
|
|
88
|
+
elif percentage <= 95:
|
|
89
|
+
status = "caution"
|
|
90
|
+
else:
|
|
91
|
+
status = "high"
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
"ltv": round(ltv, 4),
|
|
95
|
+
"percentage": percentage,
|
|
96
|
+
"requires_pmi": percentage > 80,
|
|
97
|
+
"down_payment": round(property_value - loan_amount, 2),
|
|
98
|
+
"status": status,
|
|
99
|
+
"guideline": "LTV 80% or less avoids PMI requirement",
|
|
100
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Amortization Calculations
|
|
3
|
+
--------------------------
|
|
4
|
+
Monthly payment and full amortization schedule.
|
|
5
|
+
|
|
6
|
+
Built by OurNetHelps: https://ournethelps.com
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def monthly_payment(
|
|
11
|
+
loan_amount: float,
|
|
12
|
+
annual_interest_rate: float,
|
|
13
|
+
loan_term_years: int,
|
|
14
|
+
) -> dict:
|
|
15
|
+
"""
|
|
16
|
+
Calculate monthly mortgage payment (Principal + Interest only).
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
loan_amount: Total loan amount in dollars
|
|
20
|
+
annual_interest_rate: Annual interest rate (e.g. 6.5 for 6.5%)
|
|
21
|
+
loan_term_years: Loan term in years (e.g. 30)
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
dict with monthly_payment, total_payment, total_interest,
|
|
25
|
+
loan_amount, annual_interest_rate, loan_term_years
|
|
26
|
+
|
|
27
|
+
Raises:
|
|
28
|
+
ValueError: If loan_amount or loan_term_years is invalid
|
|
29
|
+
"""
|
|
30
|
+
if loan_amount <= 0:
|
|
31
|
+
raise ValueError("Loan amount must be greater than zero.")
|
|
32
|
+
if annual_interest_rate < 0:
|
|
33
|
+
raise ValueError("Interest rate cannot be negative.")
|
|
34
|
+
if loan_term_years <= 0:
|
|
35
|
+
raise ValueError("Loan term must be greater than zero.")
|
|
36
|
+
|
|
37
|
+
monthly_rate = annual_interest_rate / 100 / 12
|
|
38
|
+
n = loan_term_years * 12
|
|
39
|
+
|
|
40
|
+
if monthly_rate == 0:
|
|
41
|
+
payment = loan_amount / n
|
|
42
|
+
else:
|
|
43
|
+
payment = (
|
|
44
|
+
loan_amount
|
|
45
|
+
* (monthly_rate * (1 + monthly_rate) ** n)
|
|
46
|
+
/ ((1 + monthly_rate) ** n - 1)
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
total_payment = payment * n
|
|
50
|
+
total_interest = total_payment - loan_amount
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
"monthly_payment": round(payment, 2),
|
|
54
|
+
"total_payment": round(total_payment, 2),
|
|
55
|
+
"total_interest": round(total_interest, 2),
|
|
56
|
+
"loan_amount": loan_amount,
|
|
57
|
+
"annual_interest_rate": annual_interest_rate,
|
|
58
|
+
"loan_term_years": loan_term_years,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def amortization_schedule(
|
|
63
|
+
loan_amount: float,
|
|
64
|
+
annual_interest_rate: float,
|
|
65
|
+
loan_term_years: int,
|
|
66
|
+
) -> list:
|
|
67
|
+
"""
|
|
68
|
+
Generate full amortization schedule.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
loan_amount: Total loan amount in dollars
|
|
72
|
+
annual_interest_rate: Annual interest rate (e.g. 6.5 for 6.5%)
|
|
73
|
+
loan_term_years: Loan term in years
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
List of dicts with month, payment, principal, interest, balance
|
|
77
|
+
"""
|
|
78
|
+
result = monthly_payment(loan_amount, annual_interest_rate, loan_term_years)
|
|
79
|
+
payment = result["monthly_payment"]
|
|
80
|
+
monthly_rate = annual_interest_rate / 100 / 12
|
|
81
|
+
n = loan_term_years * 12
|
|
82
|
+
|
|
83
|
+
balance = loan_amount
|
|
84
|
+
schedule = []
|
|
85
|
+
|
|
86
|
+
for month in range(1, n + 1):
|
|
87
|
+
interest_payment = round(balance * monthly_rate, 2)
|
|
88
|
+
principal_payment = round(payment - interest_payment, 2)
|
|
89
|
+
balance = max(0, round(balance - principal_payment, 2))
|
|
90
|
+
|
|
91
|
+
schedule.append({
|
|
92
|
+
"month": month,
|
|
93
|
+
"payment": round(payment, 2),
|
|
94
|
+
"principal": principal_payment,
|
|
95
|
+
"interest": interest_payment,
|
|
96
|
+
"balance": balance,
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
return schedule
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""
|
|
2
|
+
DTI (Debt-to-Income) Calculations
|
|
3
|
+
----------------------------------
|
|
4
|
+
Used by mortgage brokers to assess borrower qualification.
|
|
5
|
+
Standard guidelines: Front-end DTI <= 28%, Back-end DTI <= 43%
|
|
6
|
+
|
|
7
|
+
Built by OurNetHelps: https://ournethelps.com
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _get_front_status(pct: float) -> str:
|
|
12
|
+
if pct <= 28:
|
|
13
|
+
return "good"
|
|
14
|
+
elif pct <= 36:
|
|
15
|
+
return "caution"
|
|
16
|
+
return "high"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _get_back_status(pct: float) -> str:
|
|
20
|
+
if pct <= 36:
|
|
21
|
+
return "good"
|
|
22
|
+
elif pct <= 43:
|
|
23
|
+
return "caution"
|
|
24
|
+
return "high"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def front_end_dti(monthly_housing_cost: float, gross_monthly_income: float) -> dict:
|
|
28
|
+
"""
|
|
29
|
+
Calculate Front-End DTI (Housing Ratio).
|
|
30
|
+
Only includes housing-related costs vs gross income.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
monthly_housing_cost: PITI (Principal + Interest + Tax + Insurance)
|
|
34
|
+
gross_monthly_income: Borrower's gross monthly income
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
dict with ratio, percentage, status, label, guideline
|
|
38
|
+
|
|
39
|
+
Raises:
|
|
40
|
+
ValueError: If gross_monthly_income is zero or negative
|
|
41
|
+
"""
|
|
42
|
+
if gross_monthly_income <= 0:
|
|
43
|
+
raise ValueError("Gross monthly income must be greater than zero.")
|
|
44
|
+
|
|
45
|
+
ratio = monthly_housing_cost / gross_monthly_income
|
|
46
|
+
percentage = round(ratio * 100, 2)
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
"ratio": round(ratio, 4),
|
|
50
|
+
"percentage": percentage,
|
|
51
|
+
"status": _get_front_status(percentage),
|
|
52
|
+
"label": "Front-End DTI",
|
|
53
|
+
"guideline": "Recommended: 28% or less",
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def back_end_dti(
|
|
58
|
+
monthly_housing_cost: float,
|
|
59
|
+
monthly_debts: float,
|
|
60
|
+
gross_monthly_income: float,
|
|
61
|
+
) -> dict:
|
|
62
|
+
"""
|
|
63
|
+
Calculate Back-End DTI (Total Debt Ratio).
|
|
64
|
+
Includes all monthly debt obligations vs gross income.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
monthly_housing_cost: PITI (Principal + Interest + Tax + Insurance)
|
|
68
|
+
monthly_debts: All other monthly debts (car, student loans, credit cards)
|
|
69
|
+
gross_monthly_income: Borrower's gross monthly income
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
dict with ratio, percentage, total_monthly_debt, status, label, guideline
|
|
73
|
+
|
|
74
|
+
Raises:
|
|
75
|
+
ValueError: If gross_monthly_income is zero or negative
|
|
76
|
+
"""
|
|
77
|
+
if gross_monthly_income <= 0:
|
|
78
|
+
raise ValueError("Gross monthly income must be greater than zero.")
|
|
79
|
+
|
|
80
|
+
total_debt = monthly_housing_cost + monthly_debts
|
|
81
|
+
ratio = total_debt / gross_monthly_income
|
|
82
|
+
percentage = round(ratio * 100, 2)
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
"ratio": round(ratio, 4),
|
|
86
|
+
"percentage": percentage,
|
|
87
|
+
"total_monthly_debt": round(total_debt, 2),
|
|
88
|
+
"status": _get_back_status(percentage),
|
|
89
|
+
"label": "Back-End DTI",
|
|
90
|
+
"guideline": "Recommended: 43% or less (FHA allows up to 50%)",
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def dti_report(
|
|
95
|
+
monthly_housing_cost: float,
|
|
96
|
+
monthly_debts: float,
|
|
97
|
+
gross_monthly_income: float,
|
|
98
|
+
) -> dict:
|
|
99
|
+
"""
|
|
100
|
+
Full DTI Report - returns both front-end and back-end DTI.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
monthly_housing_cost: PITI monthly housing cost
|
|
104
|
+
monthly_debts: All other monthly debt obligations
|
|
105
|
+
gross_monthly_income: Borrower's gross monthly income
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
dict with front_end, back_end, qualified, summary
|
|
109
|
+
"""
|
|
110
|
+
front = front_end_dti(monthly_housing_cost, gross_monthly_income)
|
|
111
|
+
back = back_end_dti(monthly_housing_cost, monthly_debts, gross_monthly_income)
|
|
112
|
+
qualified = front["status"] != "high" and back["status"] != "high"
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
"front_end": front,
|
|
116
|
+
"back_end": back,
|
|
117
|
+
"qualified": qualified,
|
|
118
|
+
"summary": f"Front-End: {front['percentage']}% | Back-End: {back['percentage']}%",
|
|
119
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mortgage-utils
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Lightweight Python utility library for mortgage calculations — DTI, amortization, affordability, LTV.
|
|
5
|
+
Author-email: Sanjeev Kumar <contact@ournethelps.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://ournethelps.com
|
|
8
|
+
Project-URL: Repository, https://github.com/sanjeevkumardev/mortgage-utils-py
|
|
9
|
+
Project-URL: Bug Tracker, https://github.com/sanjeevkumardev/mortgage-utils-py/issues
|
|
10
|
+
Keywords: mortgage,calculator,dti,debt-to-income,amortization,affordability,ltv,fintech,real-estate,home-loan,mortgage-broker
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Office/Business :: Financial
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Requires-Python: >=3.8
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# mortgage-utils
|
|
26
|
+
|
|
27
|
+
> Lightweight Python utility library for mortgage calculations - DTI, amortization, affordability, and LTV.
|
|
28
|
+
|
|
29
|
+
Built by **[OurNetHelps](https://ournethelps.com)** - mortgage tools for US mortgage brokers.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install mortgage-utils
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Features
|
|
42
|
+
|
|
43
|
+
- DTI Calculator - front-end and back-end debt-to-income ratio
|
|
44
|
+
- Amortization - monthly payment and full amortization schedule
|
|
45
|
+
- Affordability - maximum loan amount based on income and debts
|
|
46
|
+
- LTV - loan-to-value ratio with PMI threshold detection
|
|
47
|
+
- Zero dependencies
|
|
48
|
+
- Python 3.8 and above
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Usage
|
|
53
|
+
|
|
54
|
+
### DTI (Debt-to-Income)
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from mortgage_utils import front_end_dti, back_end_dti, dti_report
|
|
58
|
+
|
|
59
|
+
# Front-End DTI
|
|
60
|
+
front = front_end_dti(monthly_housing_cost=1800, gross_monthly_income=7000)
|
|
61
|
+
print(front)
|
|
62
|
+
# {'ratio': 0.2571, 'percentage': 25.71, 'status': 'good', ...}
|
|
63
|
+
|
|
64
|
+
# Back-End DTI
|
|
65
|
+
back = back_end_dti(
|
|
66
|
+
monthly_housing_cost=1800,
|
|
67
|
+
monthly_debts=500,
|
|
68
|
+
gross_monthly_income=7000
|
|
69
|
+
)
|
|
70
|
+
print(back)
|
|
71
|
+
# {'ratio': 0.3286, 'percentage': 32.86, 'status': 'good', ...}
|
|
72
|
+
|
|
73
|
+
# Full DTI Report
|
|
74
|
+
report = dti_report(
|
|
75
|
+
monthly_housing_cost=1800,
|
|
76
|
+
monthly_debts=500,
|
|
77
|
+
gross_monthly_income=7000
|
|
78
|
+
)
|
|
79
|
+
print(report['qualified']) # True
|
|
80
|
+
print(report['summary']) # "Front-End: 25.71% | Back-End: 32.86%"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Amortization
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
from mortgage_utils import monthly_payment, amortization_schedule
|
|
87
|
+
|
|
88
|
+
# Monthly Payment
|
|
89
|
+
result = monthly_payment(
|
|
90
|
+
loan_amount=300000,
|
|
91
|
+
annual_interest_rate=6.5,
|
|
92
|
+
loan_term_years=30
|
|
93
|
+
)
|
|
94
|
+
print(result['monthly_payment']) # 1896.20
|
|
95
|
+
print(result['total_interest']) # 382632.0
|
|
96
|
+
|
|
97
|
+
# Full Amortization Schedule
|
|
98
|
+
schedule = amortization_schedule(300000, 6.5, 30)
|
|
99
|
+
print(schedule[0])
|
|
100
|
+
# {'month': 1, 'payment': 1896.20, 'principal': 271.2, 'interest': 1625.0, 'balance': 299728.8}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Affordability
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from mortgage_utils import max_affordable_loan, loan_to_value
|
|
107
|
+
|
|
108
|
+
# Max Affordable Loan
|
|
109
|
+
result = max_affordable_loan(
|
|
110
|
+
gross_monthly_income=8000,
|
|
111
|
+
monthly_debts=500,
|
|
112
|
+
annual_interest_rate=6.5,
|
|
113
|
+
loan_term_years=30
|
|
114
|
+
)
|
|
115
|
+
print(result['estimated_loan_amount']) # max loan amount
|
|
116
|
+
print(result['max_monthly_payment']) # max monthly payment
|
|
117
|
+
|
|
118
|
+
# LTV Ratio
|
|
119
|
+
ltv = loan_to_value(loan_amount=320000, property_value=400000)
|
|
120
|
+
print(ltv['percentage']) # 80.0
|
|
121
|
+
print(ltv['requires_pmi']) # False
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## API Reference
|
|
127
|
+
|
|
128
|
+
### `front_end_dti(monthly_housing_cost, gross_monthly_income)`
|
|
129
|
+
Returns front-end DTI with status (good / caution / high).
|
|
130
|
+
|
|
131
|
+
### `back_end_dti(monthly_housing_cost, monthly_debts, gross_monthly_income)`
|
|
132
|
+
Returns back-end DTI including all monthly debts.
|
|
133
|
+
|
|
134
|
+
### `dti_report(monthly_housing_cost, monthly_debts, gross_monthly_income)`
|
|
135
|
+
Returns full DTI report with qualified boolean and summary.
|
|
136
|
+
|
|
137
|
+
### `monthly_payment(loan_amount, annual_interest_rate, loan_term_years)`
|
|
138
|
+
Returns monthly P&I payment, total payment, and total interest.
|
|
139
|
+
|
|
140
|
+
### `amortization_schedule(loan_amount, annual_interest_rate, loan_term_years)`
|
|
141
|
+
Returns list of monthly payment breakdowns.
|
|
142
|
+
|
|
143
|
+
### `max_affordable_loan(gross_monthly_income, monthly_debts, annual_interest_rate, loan_term_years, max_dti=43.0)`
|
|
144
|
+
Returns max affordable loan amount based on income and DTI threshold.
|
|
145
|
+
|
|
146
|
+
### `loan_to_value(loan_amount, property_value)`
|
|
147
|
+
Returns LTV ratio, percentage, down payment, and PMI requirement flag.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## DTI Status Thresholds
|
|
152
|
+
|
|
153
|
+
| Status | Front-End DTI | Back-End DTI |
|
|
154
|
+
|--------|--------------|--------------|
|
|
155
|
+
| good | 28% or less | 36% or less |
|
|
156
|
+
| caution | 29% to 36% | 37% to 43% |
|
|
157
|
+
| high | Above 36% | Above 43% |
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Related
|
|
162
|
+
|
|
163
|
+
- [OurNetHelps](https://ournethelps.com) - mortgage tools for US mortgage brokers
|
|
164
|
+
- [mortgage-utils on NPM](https://www.npmjs.com/package/mortgage-utils) - JavaScript version
|
|
165
|
+
- [Live Mortgage Calculator](https://huggingface.co/spaces/sanjeevkumardev/mortgage-suite)
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## License
|
|
170
|
+
|
|
171
|
+
MIT © [Sanjeev Kumar](https://ournethelps.com)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
mortgage_utils/__init__.py
|
|
4
|
+
mortgage_utils/affordability.py
|
|
5
|
+
mortgage_utils/amortization.py
|
|
6
|
+
mortgage_utils/dti.py
|
|
7
|
+
mortgage_utils.egg-info/PKG-INFO
|
|
8
|
+
mortgage_utils.egg-info/SOURCES.txt
|
|
9
|
+
mortgage_utils.egg-info/dependency_links.txt
|
|
10
|
+
mortgage_utils.egg-info/top_level.txt
|
|
11
|
+
tests/test_mortgage_utils.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
mortgage_utils
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "mortgage-utils"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Lightweight Python utility library for mortgage calculations — DTI, amortization, affordability, LTV."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
authors = [
|
|
12
|
+
{ name = "Sanjeev Kumar", email = "contact@ournethelps.com" }
|
|
13
|
+
]
|
|
14
|
+
keywords = [
|
|
15
|
+
"mortgage",
|
|
16
|
+
"calculator",
|
|
17
|
+
"dti",
|
|
18
|
+
"debt-to-income",
|
|
19
|
+
"amortization",
|
|
20
|
+
"affordability",
|
|
21
|
+
"ltv",
|
|
22
|
+
"fintech",
|
|
23
|
+
"real-estate",
|
|
24
|
+
"home-loan",
|
|
25
|
+
"mortgage-broker",
|
|
26
|
+
]
|
|
27
|
+
classifiers = [
|
|
28
|
+
"Development Status :: 5 - Production/Stable",
|
|
29
|
+
"Intended Audience :: Developers",
|
|
30
|
+
"License :: OSI Approved :: MIT License",
|
|
31
|
+
"Programming Language :: Python :: 3",
|
|
32
|
+
"Programming Language :: Python :: 3.8",
|
|
33
|
+
"Programming Language :: Python :: 3.9",
|
|
34
|
+
"Programming Language :: Python :: 3.10",
|
|
35
|
+
"Programming Language :: Python :: 3.11",
|
|
36
|
+
"Programming Language :: Python :: 3.12",
|
|
37
|
+
"Topic :: Office/Business :: Financial",
|
|
38
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
39
|
+
]
|
|
40
|
+
requires-python = ">=3.8"
|
|
41
|
+
dependencies = []
|
|
42
|
+
|
|
43
|
+
[project.urls]
|
|
44
|
+
Homepage = "https://ournethelps.com"
|
|
45
|
+
Repository = "https://github.com/sanjeevkumardev/mortgage-utils-py"
|
|
46
|
+
"Bug Tracker" = "https://github.com/sanjeevkumardev/mortgage-utils-py/issues"
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for mortgage-utils Python package
|
|
3
|
+
Built by OurNetHelps — https://ournethelps.com
|
|
4
|
+
"""
|
|
5
|
+
import sys
|
|
6
|
+
sys.path.insert(0, '..')
|
|
7
|
+
|
|
8
|
+
from mortgage_utils import (
|
|
9
|
+
front_end_dti, back_end_dti, dti_report,
|
|
10
|
+
monthly_payment, amortization_schedule,
|
|
11
|
+
max_affordable_loan, loan_to_value,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
passed = 0
|
|
15
|
+
failed = 0
|
|
16
|
+
|
|
17
|
+
def test(name, condition, expected=None, got=None):
|
|
18
|
+
global passed, failed
|
|
19
|
+
if condition:
|
|
20
|
+
print(f" ✅ {name}")
|
|
21
|
+
passed += 1
|
|
22
|
+
else:
|
|
23
|
+
print(f" ❌ {name} — expected {expected}, got {got}")
|
|
24
|
+
failed += 1
|
|
25
|
+
|
|
26
|
+
print("\n📦 mortgage-utils Python — Test Suite\n")
|
|
27
|
+
|
|
28
|
+
# DTI Tests
|
|
29
|
+
print("🔢 DTI Calculations")
|
|
30
|
+
|
|
31
|
+
r = front_end_dti(1800, 7000)
|
|
32
|
+
test("front_end_dti percentage", r['percentage'] == 25.71, 25.71, r['percentage'])
|
|
33
|
+
test("front_end_dti status good", r['status'] == 'good', 'good', r['status'])
|
|
34
|
+
|
|
35
|
+
r = front_end_dti(2500, 5000)
|
|
36
|
+
test("front_end_dti status high", r['status'] == 'high', 'high', r['status'])
|
|
37
|
+
|
|
38
|
+
r = back_end_dti(1800, 500, 7000)
|
|
39
|
+
test("back_end_dti percentage", r['percentage'] == 32.86, 32.86, r['percentage'])
|
|
40
|
+
test("back_end_dti status good", r['status'] == 'good', 'good', r['status'])
|
|
41
|
+
|
|
42
|
+
r = dti_report(1800, 500, 7000)
|
|
43
|
+
test("dti_report qualified", r['qualified'] == True, True, r['qualified'])
|
|
44
|
+
test("dti_report front_end", r['front_end']['percentage'] == 25.71, 25.71, r['front_end']['percentage'])
|
|
45
|
+
test("dti_report back_end", r['back_end']['percentage'] == 32.86, 32.86, r['back_end']['percentage'])
|
|
46
|
+
|
|
47
|
+
r = dti_report(3000, 1500, 6000)
|
|
48
|
+
test("dti_report not qualified", r['qualified'] == False, False, r['qualified'])
|
|
49
|
+
|
|
50
|
+
# Amortization Tests
|
|
51
|
+
print("\n🏠 Amortization Calculations")
|
|
52
|
+
|
|
53
|
+
r = monthly_payment(300000, 6.5, 30)
|
|
54
|
+
test("monthly_payment amount", r['monthly_payment'] == 1896.20, 1896.20, r['monthly_payment'])
|
|
55
|
+
test("monthly_payment total_interest positive", r['total_interest'] > 0)
|
|
56
|
+
|
|
57
|
+
r = monthly_payment(120000, 0, 10)
|
|
58
|
+
test("monthly_payment zero interest", r['monthly_payment'] == 1000.0, 1000.0, r['monthly_payment'])
|
|
59
|
+
|
|
60
|
+
schedule = amortization_schedule(300000, 6.5, 30)
|
|
61
|
+
test("amortization schedule length", len(schedule) == 360, 360, len(schedule))
|
|
62
|
+
test("amortization first month", schedule[0]['month'] == 1)
|
|
63
|
+
test("amortization balance reduces", schedule[0]['balance'] < 300000)
|
|
64
|
+
|
|
65
|
+
# Affordability Tests
|
|
66
|
+
print("\n💰 Affordability and LTV")
|
|
67
|
+
|
|
68
|
+
r = max_affordable_loan(8000, 500, 6.5, 30)
|
|
69
|
+
test("max_affordable_loan positive", r['estimated_loan_amount'] > 0)
|
|
70
|
+
test("max_affordable_loan payment", r['max_monthly_payment'] == 2940.0, 2940.0, r['max_monthly_payment'])
|
|
71
|
+
|
|
72
|
+
r = max_affordable_loan(5000, 3000, 6.5, 30)
|
|
73
|
+
test("max_affordable_loan debts exceed", r['estimated_loan_amount'] == 0, 0, r['estimated_loan_amount'])
|
|
74
|
+
|
|
75
|
+
r = loan_to_value(320000, 400000)
|
|
76
|
+
test("ltv percentage", r['percentage'] == 80.0, 80.0, r['percentage'])
|
|
77
|
+
test("ltv no pmi at 80", r['requires_pmi'] == False, False, r['requires_pmi'])
|
|
78
|
+
test("ltv down payment", r['down_payment'] == 80000.0, 80000.0, r['down_payment'])
|
|
79
|
+
|
|
80
|
+
r = loan_to_value(380000, 400000)
|
|
81
|
+
test("ltv pmi required above 80", r['requires_pmi'] == True, True, r['requires_pmi'])
|
|
82
|
+
|
|
83
|
+
print(f"\n📊 Results: {passed} passed, {failed} failed\n")
|
|
84
|
+
if failed > 0:
|
|
85
|
+
sys.exit(1)
|