@novha/calc-engines 1.3.1 → 1.3.2
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.
- package/dist/index.js +4 -2
- package/dist/mortgage/south-africa/SouthAfricaMortgageService.js +3 -0
- package/dist/mortgage/south-africa/SouthAfricaMortgageServiceImpl.js +54 -0
- package/dist/mortgage/south-africa/domain/types.js +3 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/mortgage/south-africa/SouthAfricaMortgageService.d.ts +4 -0
- package/dist/types/mortgage/south-africa/SouthAfricaMortgageServiceImpl.d.ts +9 -0
- package/dist/types/mortgage/south-africa/domain/types.d.ts +81 -0
- package/package.json +1 -1
- package/src/index.ts +6 -0
- package/src/mortgage/south-africa/SouthAfricaMortgageService.ts +8 -0
- package/src/mortgage/south-africa/SouthAfricaMortgageServiceImpl.ts +68 -0
- package/src/mortgage/south-africa/domain/types.ts +90 -0
package/dist/index.js
CHANGED
|
@@ -14,12 +14,14 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.FranceIncomeTaxService = exports.CanadaMortgageService = exports.CanadaIncomeTaxService = void 0;
|
|
17
|
+
exports.SouthAfricaMortgageService = exports.FranceIncomeTaxService = exports.CanadaMortgageService = exports.CanadaIncomeTaxService = void 0;
|
|
18
18
|
var CanadaIncomeTaxServiceImpl_1 = require("./income-tax/canada/CanadaIncomeTaxServiceImpl");
|
|
19
19
|
Object.defineProperty(exports, "CanadaIncomeTaxService", { enumerable: true, get: function () { return CanadaIncomeTaxServiceImpl_1.CanadaIncomeTaxServiceImpl; } });
|
|
20
20
|
var CanadaMortgageServiceImpl_1 = require("./mortgage/canada/CanadaMortgageServiceImpl");
|
|
21
21
|
Object.defineProperty(exports, "CanadaMortgageService", { enumerable: true, get: function () { return CanadaMortgageServiceImpl_1.CanadaMortgageServiceImpl; } });
|
|
22
22
|
var FranceIncomeTaxServiceImpl_1 = require("./income-tax/france/FranceIncomeTaxServiceImpl");
|
|
23
23
|
Object.defineProperty(exports, "FranceIncomeTaxService", { enumerable: true, get: function () { return FranceIncomeTaxServiceImpl_1.FranceIncomeTaxServiceImpl; } });
|
|
24
|
+
var SouthAfricaMortgageServiceImpl_1 = require("./mortgage/south-africa/SouthAfricaMortgageServiceImpl");
|
|
25
|
+
Object.defineProperty(exports, "SouthAfricaMortgageService", { enumerable: true, get: function () { return SouthAfricaMortgageServiceImpl_1.SouthAfricaMortgageServiceImpl; } });
|
|
24
26
|
__exportStar(require("./income-tax/domain/types"), exports);
|
|
25
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
27
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQSw2RkFBc0g7QUFBN0csb0lBQUEsMEJBQTBCLE9BQTBCO0FBQzdELHlGQUFpSDtBQUF4RyxrSUFBQSx5QkFBeUIsT0FBeUI7QUFDM0QsNkZBQXNIO0FBQTdHLG9JQUFBLDBCQUEwQixPQUEwQjtBQVU3RCx5R0FBc0k7QUFBN0gsNElBQUEsOEJBQThCLE9BQThCO0FBV3JFLDREQUEwQyIsInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCB7IENhbmFkYUluY29tZVRheFNlcnZpY2VJbXBsIGFzIENhbmFkYUluY29tZVRheFNlcnZpY2UgfSBmcm9tICcuL2luY29tZS10YXgvY2FuYWRhL0NhbmFkYUluY29tZVRheFNlcnZpY2VJbXBsJztcbmV4cG9ydCB7IENhbmFkYU1vcnRnYWdlU2VydmljZUltcGwgYXMgQ2FuYWRhTW9ydGdhZ2VTZXJ2aWNlIH0gZnJvbSAnLi9tb3J0Z2FnZS9jYW5hZGEvQ2FuYWRhTW9ydGdhZ2VTZXJ2aWNlSW1wbCc7XG5leHBvcnQgeyBGcmFuY2VJbmNvbWVUYXhTZXJ2aWNlSW1wbCBhcyBGcmFuY2VJbmNvbWVUYXhTZXJ2aWNlIH0gZnJvbSAnLi9pbmNvbWUtdGF4L2ZyYW5jZS9GcmFuY2VJbmNvbWVUYXhTZXJ2aWNlSW1wbCc7XG5leHBvcnQge1xuICAgIENvbXB1dGVkSW5jb21lVGF4VmFsdWVzIGFzIENhbmFkYUNvbXB1dGVkSW5jb21lVGF4VmFsdWVzLFxuICAgIEluY29tZVRheFJ1bGVzIGFzIENhbmFkYUluY29tZVRheFJ1bGVzLFxufSBmcm9tICcuL2luY29tZS10YXgvY2FuYWRhL2RvbWFpbi90eXBlcyc7XG5leHBvcnQge1xuICAgIE1vcnRnYWdlUnVsZXMgYXMgQ2FuYWRhTW9ydGdhZ2VSdWxlcyxcbiAgICBNb3J0Z2FnZUNhbGN1bGF0aW9uSW5wdXQgYXMgQ2FuYWRhTW9ydGdhZ2VDYWxjdWxhdGlvbklucHV0LFxuICAgIE1vcnRnYWdlQ2FsY3VsYXRpb25SZXN1bHQgYXMgQ2FuYWRhTW9ydGdhZ2VDYWxjdWxhdGlvblJlc3VsdCxcbn0gZnJvbSAnLi9tb3J0Z2FnZS9jYW5hZGEvZG9tYWluL3R5cGVzJztcbmV4cG9ydCB7IFNvdXRoQWZyaWNhTW9ydGdhZ2VTZXJ2aWNlSW1wbCBhcyBTb3V0aEFmcmljYU1vcnRnYWdlU2VydmljZSB9IGZyb20gJy4vbW9ydGdhZ2Uvc291dGgtYWZyaWNhL1NvdXRoQWZyaWNhTW9ydGdhZ2VTZXJ2aWNlSW1wbCc7XG5leHBvcnQge1xuICAgIE1vcnRnYWdlUnVsZXMgYXMgU291dGhBZnJpY2FNb3J0Z2FnZVJ1bGVzLFxuICAgIE1vcnRnYWdlSW5wdXQgYXMgU291dGhBZnJpY2FNb3J0Z2FnZUlucHV0LFxuICAgIE1vcnRnYWdlT3V0cHV0IGFzIFNvdXRoQWZyaWNhTW9ydGdhZ2VPdXRwdXQsXG59IGZyb20gJy4vbW9ydGdhZ2Uvc291dGgtYWZyaWNhL2RvbWFpbi90eXBlcyc7XG5leHBvcnQgeyBcbiAgICBDb21wdXRlZEluY29tZVRheFZhbHVlcyBhcyBGcmFuY2VDb21wdXRlZEluY29tZVRheFZhbHVlcyxcbiAgICBJbmNvbWVUYXhSdWxlcyBhcyBGcmFuY2VJbmNvbWVUYXhSdWxlcyxcbn0gZnJvbSAnLi9pbmNvbWUtdGF4L2ZyYW5jZS9kb21haW4vdHlwZXMnO1xuZXhwb3J0IHsgSW5jb21lVGF4Q2FsY3VsYXRvclNjaGVtYSB9IGZyb20gJy4vaW5jb21lLXRheC9kb21haW4vdHlwZXMnO1xuZXhwb3J0ICogZnJvbSAnLi9pbmNvbWUtdGF4L2RvbWFpbi90eXBlcyc7Il19
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiU291dGhBZnJpY2FNb3J0Z2FnZVNlcnZpY2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvbW9ydGdhZ2Uvc291dGgtYWZyaWNhL1NvdXRoQWZyaWNhTW9ydGdhZ2VTZXJ2aWNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiIiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBNb3J0Z2FnZVJ1bGVzLCBNb3J0Z2FnZUlucHV0LCBNb3J0Z2FnZU91dHB1dCB9IGZyb20gJy4vZG9tYWluL3R5cGVzJztcblxuZXhwb3J0IGludGVyZmFjZSBTb3V0aEFmcmljYU1vcnRnYWdlU2VydmljZSB7XG4gICAgY2FsY3VsYXRlKFxuICAgICAgICBpbnB1dDogTW9ydGdhZ2VJbnB1dCxcbiAgICAgICAgcnVsZXM6IE1vcnRnYWdlUnVsZXNcbiAgICApOiBNb3J0Z2FnZU91dHB1dDtcbn0gIl19
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SouthAfricaMortgageServiceImpl = void 0;
|
|
4
|
+
class SouthAfricaMortgageServiceImpl {
|
|
5
|
+
constructor(input, rules) {
|
|
6
|
+
this.input = input;
|
|
7
|
+
this.rules = rules;
|
|
8
|
+
}
|
|
9
|
+
calculate() {
|
|
10
|
+
const loanAmount = this.input.propertyPrice - this.input.downPayment;
|
|
11
|
+
// Monthly interest rate
|
|
12
|
+
const monthlyRate = this.input.annualInterestRate / 100 / 12;
|
|
13
|
+
const totalPayments = this.input.amortizationYears * 12;
|
|
14
|
+
// Standard amortized monthly payment formula
|
|
15
|
+
const monthlyPayment = (loanAmount * monthlyRate) /
|
|
16
|
+
(1 - Math.pow(1 + monthlyRate, -totalPayments));
|
|
17
|
+
const totalPaid = monthlyPayment * totalPayments;
|
|
18
|
+
const totalInterestPaid = totalPaid - loanAmount;
|
|
19
|
+
// Debt-to-income ratio
|
|
20
|
+
const debtToIncomeRatio = (monthlyPayment / this.input.grossMonthlyIncome) * 100;
|
|
21
|
+
const isAffordable = debtToIncomeRatio <= this.rules.loanConstraints.maxDebtToIncomePercent;
|
|
22
|
+
// Fees
|
|
23
|
+
const bondRegistrationFee = loanAmount * (this.rules.fees.bondRegistrationPercent / 100);
|
|
24
|
+
const transferDuty = this.calculateTransferDuty(this.input.propertyPrice);
|
|
25
|
+
return {
|
|
26
|
+
loanAmount,
|
|
27
|
+
monthlyPayment,
|
|
28
|
+
totalInterestPaid,
|
|
29
|
+
totalPaid,
|
|
30
|
+
debtToIncomeRatio,
|
|
31
|
+
isAffordable,
|
|
32
|
+
transferDuty,
|
|
33
|
+
bondRegistrationFee
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
calculateTransferDuty(propertyPrice) {
|
|
37
|
+
let duty = 0;
|
|
38
|
+
let previousLimit = 0;
|
|
39
|
+
for (const bracket of this.rules.fees.transferDuty.brackets) {
|
|
40
|
+
if (bracket.upTo && propertyPrice > previousLimit) {
|
|
41
|
+
const taxable = Math.min(propertyPrice, bracket.upTo) - previousLimit;
|
|
42
|
+
duty += taxable * bracket.rate;
|
|
43
|
+
previousLimit = bracket.upTo;
|
|
44
|
+
}
|
|
45
|
+
if (bracket.above && propertyPrice > bracket.above) {
|
|
46
|
+
duty += (propertyPrice - bracket.above) * bracket.rate;
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return duty;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
exports.SouthAfricaMortgageServiceImpl = SouthAfricaMortgageServiceImpl;
|
|
54
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiU291dGhBZnJpY2FNb3J0Z2FnZVNlcnZpY2VJbXBsLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL21vcnRnYWdlL3NvdXRoLWFmcmljYS9Tb3V0aEFmcmljYU1vcnRnYWdlU2VydmljZUltcGwudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBR0EsTUFBYSw4QkFBOEI7SUFDekMsWUFDbUIsS0FBb0IsRUFDcEIsS0FBb0I7UUFEcEIsVUFBSyxHQUFMLEtBQUssQ0FBZTtRQUNwQixVQUFLLEdBQUwsS0FBSyxDQUFlO0lBQ3RDLENBQUM7SUFFRixTQUFTO1FBQ1AsTUFBTSxVQUFVLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxhQUFhLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxXQUFXLENBQUM7UUFFckUsd0JBQXdCO1FBQ3hCLE1BQU0sV0FBVyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsa0JBQWtCLEdBQUcsR0FBRyxHQUFHLEVBQUUsQ0FBQztRQUM3RCxNQUFNLGFBQWEsR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLGlCQUFpQixHQUFHLEVBQUUsQ0FBQztRQUV4RCw2Q0FBNkM7UUFDN0MsTUFBTSxjQUFjLEdBQ2xCLENBQUMsVUFBVSxHQUFHLFdBQVcsQ0FBQztZQUMxQixDQUFDLENBQUMsR0FBRyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUMsR0FBRyxXQUFXLEVBQUUsQ0FBQyxhQUFhLENBQUMsQ0FBQyxDQUFDO1FBRWxELE1BQU0sU0FBUyxHQUFHLGNBQWMsR0FBRyxhQUFhLENBQUM7UUFDakQsTUFBTSxpQkFBaUIsR0FBRyxTQUFTLEdBQUcsVUFBVSxDQUFDO1FBRWpELHVCQUF1QjtRQUN2QixNQUFNLGlCQUFpQixHQUNyQixDQUFDLGNBQWMsR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLGtCQUFrQixDQUFDLEdBQUcsR0FBRyxDQUFDO1FBRXpELE1BQU0sWUFBWSxHQUNoQixpQkFBaUIsSUFBSSxJQUFJLENBQUMsS0FBSyxDQUFDLGVBQWUsQ0FBQyxzQkFBc0IsQ0FBQztRQUV6RSxPQUFPO1FBQ1AsTUFBTSxtQkFBbUIsR0FDdkIsVUFBVSxHQUFHLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsdUJBQXVCLEdBQUcsR0FBRyxDQUFDLENBQUM7UUFFL0QsTUFBTSxZQUFZLEdBQUcsSUFBSSxDQUFDLHFCQUFxQixDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsYUFBYSxDQUFDLENBQUM7UUFDMUUsT0FBTztZQUNMLFVBQVU7WUFDVixjQUFjO1lBQ2QsaUJBQWlCO1lBQ2pCLFNBQVM7WUFDVCxpQkFBaUI7WUFDakIsWUFBWTtZQUNaLFlBQVk7WUFDWixtQkFBbUI7U0FDcEIsQ0FBQztJQUNKLENBQUM7SUFFTyxxQkFBcUIsQ0FBQyxhQUFxQjtRQUNqRCxJQUFJLElBQUksR0FBRyxDQUFDLENBQUM7UUFDYixJQUFJLGFBQWEsR0FBRyxDQUFDLENBQUM7UUFFdEIsS0FBSyxNQUFNLE9BQU8sSUFBSSxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxZQUFZLENBQUMsUUFBUSxFQUFFLENBQUM7WUFDNUQsSUFBSSxPQUFPLENBQUMsSUFBSSxJQUFJLGFBQWEsR0FBRyxhQUFhLEVBQUUsQ0FBQztnQkFDbEQsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLEdBQUcsQ0FBQyxhQUFhLEVBQUUsT0FBTyxDQUFDLElBQUksQ0FBQyxHQUFHLGFBQWEsQ0FBQztnQkFDdEUsSUFBSSxJQUFJLE9BQU8sR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDO2dCQUMvQixhQUFhLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQztZQUMvQixDQUFDO1lBRUQsSUFBSSxPQUFPLENBQUMsS0FBSyxJQUFJLGFBQWEsR0FBRyxPQUFPLENBQUMsS0FBSyxFQUFFLENBQUM7Z0JBQ25ELElBQUksSUFBSSxDQUFDLGFBQWEsR0FBRyxPQUFPLENBQUMsS0FBSyxDQUFDLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQztnQkFDdkQsTUFBTTtZQUNSLENBQUM7UUFDSCxDQUFDO1FBRUQsT0FBTyxJQUFJLENBQUM7SUFDZCxDQUFDO0NBQ0Y7QUFoRUQsd0VBZ0VDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgTW9ydGdhZ2VSdWxlcywgTW9ydGdhZ2VJbnB1dCwgTW9ydGdhZ2VPdXRwdXQgfSBmcm9tICcuL2RvbWFpbi90eXBlcyc7XG5pbXBvcnQgeyBTb3V0aEFmcmljYU1vcnRnYWdlU2VydmljZSB9IGZyb20gJy4vU291dGhBZnJpY2FNb3J0Z2FnZVNlcnZpY2UnO1xuXG5leHBvcnQgY2xhc3MgU291dGhBZnJpY2FNb3J0Z2FnZVNlcnZpY2VJbXBsIGltcGxlbWVudHMgU291dGhBZnJpY2FNb3J0Z2FnZVNlcnZpY2Uge1xuICBjb25zdHJ1Y3RvcihcbiAgICBwcml2YXRlIHJlYWRvbmx5IGlucHV0OiBNb3J0Z2FnZUlucHV0LFxuICAgIHByaXZhdGUgcmVhZG9ubHkgcnVsZXM6IE1vcnRnYWdlUnVsZXNcbikge31cblxuICBjYWxjdWxhdGUoKTogTW9ydGdhZ2VPdXRwdXQge1xuICAgIGNvbnN0IGxvYW5BbW91bnQgPSB0aGlzLmlucHV0LnByb3BlcnR5UHJpY2UgLSB0aGlzLmlucHV0LmRvd25QYXltZW50O1xuXG4gICAgLy8gTW9udGhseSBpbnRlcmVzdCByYXRlXG4gICAgY29uc3QgbW9udGhseVJhdGUgPSB0aGlzLmlucHV0LmFubnVhbEludGVyZXN0UmF0ZSAvIDEwMCAvIDEyO1xuICAgIGNvbnN0IHRvdGFsUGF5bWVudHMgPSB0aGlzLmlucHV0LmFtb3J0aXphdGlvblllYXJzICogMTI7XG5cbiAgICAvLyBTdGFuZGFyZCBhbW9ydGl6ZWQgbW9udGhseSBwYXltZW50IGZvcm11bGFcbiAgICBjb25zdCBtb250aGx5UGF5bWVudCA9XG4gICAgICAobG9hbkFtb3VudCAqIG1vbnRobHlSYXRlKSAvXG4gICAgICAoMSAtIE1hdGgucG93KDEgKyBtb250aGx5UmF0ZSwgLXRvdGFsUGF5bWVudHMpKTtcblxuICAgIGNvbnN0IHRvdGFsUGFpZCA9IG1vbnRobHlQYXltZW50ICogdG90YWxQYXltZW50cztcbiAgICBjb25zdCB0b3RhbEludGVyZXN0UGFpZCA9IHRvdGFsUGFpZCAtIGxvYW5BbW91bnQ7XG5cbiAgICAvLyBEZWJ0LXRvLWluY29tZSByYXRpb1xuICAgIGNvbnN0IGRlYnRUb0luY29tZVJhdGlvID1cbiAgICAgIChtb250aGx5UGF5bWVudCAvIHRoaXMuaW5wdXQuZ3Jvc3NNb250aGx5SW5jb21lKSAqIDEwMDtcblxuICAgIGNvbnN0IGlzQWZmb3JkYWJsZSA9XG4gICAgICBkZWJ0VG9JbmNvbWVSYXRpbyA8PSB0aGlzLnJ1bGVzLmxvYW5Db25zdHJhaW50cy5tYXhEZWJ0VG9JbmNvbWVQZXJjZW50O1xuXG4gICAgLy8gRmVlc1xuICAgIGNvbnN0IGJvbmRSZWdpc3RyYXRpb25GZWUgPVxuICAgICAgbG9hbkFtb3VudCAqICh0aGlzLnJ1bGVzLmZlZXMuYm9uZFJlZ2lzdHJhdGlvblBlcmNlbnQgLyAxMDApO1xuXG4gICAgY29uc3QgdHJhbnNmZXJEdXR5ID0gdGhpcy5jYWxjdWxhdGVUcmFuc2ZlckR1dHkodGhpcy5pbnB1dC5wcm9wZXJ0eVByaWNlKTtcbiAgICByZXR1cm4ge1xuICAgICAgbG9hbkFtb3VudCxcbiAgICAgIG1vbnRobHlQYXltZW50LFxuICAgICAgdG90YWxJbnRlcmVzdFBhaWQsXG4gICAgICB0b3RhbFBhaWQsXG4gICAgICBkZWJ0VG9JbmNvbWVSYXRpbyxcbiAgICAgIGlzQWZmb3JkYWJsZSxcbiAgICAgIHRyYW5zZmVyRHV0eSxcbiAgICAgIGJvbmRSZWdpc3RyYXRpb25GZWVcbiAgICB9O1xuICB9XG5cbiAgcHJpdmF0ZSBjYWxjdWxhdGVUcmFuc2ZlckR1dHkocHJvcGVydHlQcmljZTogbnVtYmVyKTogbnVtYmVyIHtcbiAgICBsZXQgZHV0eSA9IDA7XG4gICAgbGV0IHByZXZpb3VzTGltaXQgPSAwO1xuXG4gICAgZm9yIChjb25zdCBicmFja2V0IG9mIHRoaXMucnVsZXMuZmVlcy50cmFuc2ZlckR1dHkuYnJhY2tldHMpIHtcbiAgICAgIGlmIChicmFja2V0LnVwVG8gJiYgcHJvcGVydHlQcmljZSA+IHByZXZpb3VzTGltaXQpIHtcbiAgICAgICAgY29uc3QgdGF4YWJsZSA9IE1hdGgubWluKHByb3BlcnR5UHJpY2UsIGJyYWNrZXQudXBUbykgLSBwcmV2aW91c0xpbWl0O1xuICAgICAgICBkdXR5ICs9IHRheGFibGUgKiBicmFja2V0LnJhdGU7XG4gICAgICAgIHByZXZpb3VzTGltaXQgPSBicmFja2V0LnVwVG87XG4gICAgICB9XG5cbiAgICAgIGlmIChicmFja2V0LmFib3ZlICYmIHByb3BlcnR5UHJpY2UgPiBicmFja2V0LmFib3ZlKSB7XG4gICAgICAgIGR1dHkgKz0gKHByb3BlcnR5UHJpY2UgLSBicmFja2V0LmFib3ZlKSAqIGJyYWNrZXQucmF0ZTtcbiAgICAgICAgYnJlYWs7XG4gICAgICB9XG4gICAgfVxuXG4gICAgcmV0dXJuIGR1dHk7XG4gIH1cbn1cbiJdfQ==
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHlwZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvbW9ydGdhZ2Uvc291dGgtYWZyaWNhL2RvbWFpbi90eXBlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgUnVsZU1ldGEgfSBmcm9tIFwiLi4vLi4vLi4vc2hhcmVkL2RvbWFpbi90eXBlc1wiO1xuXG5leHBvcnQgaW50ZXJmYWNlIFJ1bGVJbnB1dERlZmluaXRpb24ge1xuICB0eXBlOiBcIm51bWJlclwiIHwgXCJzdHJpbmdcIjtcbiAgbWluPzogbnVtYmVyO1xuICBtYXg/OiBudW1iZXI7XG4gIGVudW0/OiBzdHJpbmdbXTtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBSdWxlSW5wdXRzIHtcbiAgcHJvcGVydHlQcmljZTogUnVsZUlucHV0RGVmaW5pdGlvbjtcbiAgZG93blBheW1lbnQ6IFJ1bGVJbnB1dERlZmluaXRpb247XG4gIGludGVyZXN0UmF0ZTogUnVsZUlucHV0RGVmaW5pdGlvbjtcbiAgYW1vcnRpemF0aW9uWWVhcnM6IFJ1bGVJbnB1dERlZmluaXRpb247XG4gIGdyb3NzTW9udGhseUluY29tZTogUnVsZUlucHV0RGVmaW5pdGlvbjtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBMb2FuQ29uc3RyYWludHMge1xuICBtYXhMdHY6IG51bWJlcjtcbiAgbWluRG93blBheW1lbnRQZXJjZW50OiBudW1iZXI7XG4gIG1heEFtb3J0aXphdGlvblllYXJzOiBudW1iZXI7XG4gIG1heERlYnRUb0luY29tZVBlcmNlbnQ6IG51bWJlcjtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBJbnRlcmVzdFJ1bGVzIHtcbiAgdHlwZTogXCJmaXhlZFwiIHwgXCJ2YXJpYWJsZVwiO1xuICByYXRlUmFuZ2VQZXJjZW50OiB7IG1pbjogbnVtYmVyOyBtYXg6IG51bWJlciB9O1xuICBzdHJlc3NUZXN0QnVmZmVyUGVyY2VudDogbnVtYmVyO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIEluc3VyYW5jZVJ1bGVzIHtcbiAgcmVxdWlyZWQ6IGJvb2xlYW47XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgVHJhbnNmZXJEdXR5QnJhY2tldCB7XG4gIHVwVG8/OiBudW1iZXI7XG4gIGFib3ZlPzogbnVtYmVyO1xuICByYXRlOiBudW1iZXI7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgRmVlc1J1bGVzIHtcbiAgYm9uZFJlZ2lzdHJhdGlvblBlcmNlbnQ6IG51bWJlcjtcbiAgdHJhbnNmZXJEdXR5OiB7XG4gICAgYnJhY2tldHM6IFRyYW5zZmVyRHV0eUJyYWNrZXRbXTtcbiAgfTtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBNb3J0Z2FnZVJ1bGVzIHtcbiAgbG9hbkNvbnN0cmFpbnRzOiBMb2FuQ29uc3RyYWludHM7XG4gIGludGVyZXN0OiBJbnRlcmVzdFJ1bGVzO1xuICBpbnN1cmFuY2U6IEluc3VyYW5jZVJ1bGVzO1xuICBmZWVzOiBGZWVzUnVsZXM7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgUnVsZU91dHB1dHMge1xuICBsb2FuQW1vdW50OiBcIm51bWJlclwiO1xuICBtb250aGx5UGF5bWVudDogXCJudW1iZXJcIjtcbiAgdG90YWxJbnRlcmVzdFBhaWQ6IFwibnVtYmVyXCI7XG4gIHRvdGFsUGFpZDogXCJudW1iZXJcIjtcbiAgZGVidFRvSW5jb21lUmF0aW86IFwibnVtYmVyXCI7XG4gIGlzQWZmb3JkYWJsZTogXCJib29sZWFuXCI7XG4gIHRyYW5zZmVyRHV0eTogXCJudW1iZXJcIjtcbiAgYm9uZFJlZ2lzdHJhdGlvbkZlZTogXCJudW1iZXJcIjtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBNb3J0Z2FnZVJ1bGVTZXQge1xuICBtZXRhOiBSdWxlTWV0YTtcbiAgaW5wdXRzOiBSdWxlSW5wdXRzO1xuICBydWxlczogTW9ydGdhZ2VSdWxlcztcbiAgb3V0cHV0czogUnVsZU91dHB1dHM7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgTW9ydGdhZ2VJbnB1dCB7XG4gIHByb3BlcnR5UHJpY2U6IG51bWJlcjtcbiAgZG93blBheW1lbnQ6IG51bWJlcjtcbiAgYW5udWFsSW50ZXJlc3RSYXRlOiBudW1iZXI7IC8vIGUuZy4sIDExLjc1JVxuICBhbW9ydGl6YXRpb25ZZWFyczogbnVtYmVyO1xuICBncm9zc01vbnRobHlJbmNvbWU6IG51bWJlcjtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBNb3J0Z2FnZU91dHB1dCB7XG4gIGxvYW5BbW91bnQ6IG51bWJlcjtcbiAgbW9udGhseVBheW1lbnQ6IG51bWJlcjtcbiAgdG90YWxJbnRlcmVzdFBhaWQ6IG51bWJlcjtcbiAgdG90YWxQYWlkOiBudW1iZXI7XG4gIGRlYnRUb0luY29tZVJhdGlvOiBudW1iZXI7XG4gIGlzQWZmb3JkYWJsZTogYm9vbGVhbjtcbiAgdHJhbnNmZXJEdXR5OiBudW1iZXI7XG4gIGJvbmRSZWdpc3RyYXRpb25GZWU6IG51bWJlcjtcbn0iXX0=
|
package/dist/types/index.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ export { CanadaMortgageServiceImpl as CanadaMortgageService } from './mortgage/c
|
|
|
3
3
|
export { FranceIncomeTaxServiceImpl as FranceIncomeTaxService } from './income-tax/france/FranceIncomeTaxServiceImpl';
|
|
4
4
|
export { ComputedIncomeTaxValues as CanadaComputedIncomeTaxValues, IncomeTaxRules as CanadaIncomeTaxRules, } from './income-tax/canada/domain/types';
|
|
5
5
|
export { MortgageRules as CanadaMortgageRules, MortgageCalculationInput as CanadaMortgageCalculationInput, MortgageCalculationResult as CanadaMortgageCalculationResult, } from './mortgage/canada/domain/types';
|
|
6
|
+
export { SouthAfricaMortgageServiceImpl as SouthAfricaMortgageService } from './mortgage/south-africa/SouthAfricaMortgageServiceImpl';
|
|
7
|
+
export { MortgageRules as SouthAfricaMortgageRules, MortgageInput as SouthAfricaMortgageInput, MortgageOutput as SouthAfricaMortgageOutput, } from './mortgage/south-africa/domain/types';
|
|
6
8
|
export { ComputedIncomeTaxValues as FranceComputedIncomeTaxValues, IncomeTaxRules as FranceIncomeTaxRules, } from './income-tax/france/domain/types';
|
|
7
9
|
export { IncomeTaxCalculatorSchema } from './income-tax/domain/types';
|
|
8
10
|
export * from './income-tax/domain/types';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { MortgageRules, MortgageInput, MortgageOutput } from './domain/types';
|
|
2
|
+
import { SouthAfricaMortgageService } from './SouthAfricaMortgageService';
|
|
3
|
+
export declare class SouthAfricaMortgageServiceImpl implements SouthAfricaMortgageService {
|
|
4
|
+
private readonly input;
|
|
5
|
+
private readonly rules;
|
|
6
|
+
constructor(input: MortgageInput, rules: MortgageRules);
|
|
7
|
+
calculate(): MortgageOutput;
|
|
8
|
+
private calculateTransferDuty;
|
|
9
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { RuleMeta } from "../../../shared/domain/types";
|
|
2
|
+
export interface RuleInputDefinition {
|
|
3
|
+
type: "number" | "string";
|
|
4
|
+
min?: number;
|
|
5
|
+
max?: number;
|
|
6
|
+
enum?: string[];
|
|
7
|
+
}
|
|
8
|
+
export interface RuleInputs {
|
|
9
|
+
propertyPrice: RuleInputDefinition;
|
|
10
|
+
downPayment: RuleInputDefinition;
|
|
11
|
+
interestRate: RuleInputDefinition;
|
|
12
|
+
amortizationYears: RuleInputDefinition;
|
|
13
|
+
grossMonthlyIncome: RuleInputDefinition;
|
|
14
|
+
}
|
|
15
|
+
export interface LoanConstraints {
|
|
16
|
+
maxLtv: number;
|
|
17
|
+
minDownPaymentPercent: number;
|
|
18
|
+
maxAmortizationYears: number;
|
|
19
|
+
maxDebtToIncomePercent: number;
|
|
20
|
+
}
|
|
21
|
+
export interface InterestRules {
|
|
22
|
+
type: "fixed" | "variable";
|
|
23
|
+
rateRangePercent: {
|
|
24
|
+
min: number;
|
|
25
|
+
max: number;
|
|
26
|
+
};
|
|
27
|
+
stressTestBufferPercent: number;
|
|
28
|
+
}
|
|
29
|
+
export interface InsuranceRules {
|
|
30
|
+
required: boolean;
|
|
31
|
+
}
|
|
32
|
+
export interface TransferDutyBracket {
|
|
33
|
+
upTo?: number;
|
|
34
|
+
above?: number;
|
|
35
|
+
rate: number;
|
|
36
|
+
}
|
|
37
|
+
export interface FeesRules {
|
|
38
|
+
bondRegistrationPercent: number;
|
|
39
|
+
transferDuty: {
|
|
40
|
+
brackets: TransferDutyBracket[];
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export interface MortgageRules {
|
|
44
|
+
loanConstraints: LoanConstraints;
|
|
45
|
+
interest: InterestRules;
|
|
46
|
+
insurance: InsuranceRules;
|
|
47
|
+
fees: FeesRules;
|
|
48
|
+
}
|
|
49
|
+
export interface RuleOutputs {
|
|
50
|
+
loanAmount: "number";
|
|
51
|
+
monthlyPayment: "number";
|
|
52
|
+
totalInterestPaid: "number";
|
|
53
|
+
totalPaid: "number";
|
|
54
|
+
debtToIncomeRatio: "number";
|
|
55
|
+
isAffordable: "boolean";
|
|
56
|
+
transferDuty: "number";
|
|
57
|
+
bondRegistrationFee: "number";
|
|
58
|
+
}
|
|
59
|
+
export interface MortgageRuleSet {
|
|
60
|
+
meta: RuleMeta;
|
|
61
|
+
inputs: RuleInputs;
|
|
62
|
+
rules: MortgageRules;
|
|
63
|
+
outputs: RuleOutputs;
|
|
64
|
+
}
|
|
65
|
+
export interface MortgageInput {
|
|
66
|
+
propertyPrice: number;
|
|
67
|
+
downPayment: number;
|
|
68
|
+
annualInterestRate: number;
|
|
69
|
+
amortizationYears: number;
|
|
70
|
+
grossMonthlyIncome: number;
|
|
71
|
+
}
|
|
72
|
+
export interface MortgageOutput {
|
|
73
|
+
loanAmount: number;
|
|
74
|
+
monthlyPayment: number;
|
|
75
|
+
totalInterestPaid: number;
|
|
76
|
+
totalPaid: number;
|
|
77
|
+
debtToIncomeRatio: number;
|
|
78
|
+
isAffordable: boolean;
|
|
79
|
+
transferDuty: number;
|
|
80
|
+
bondRegistrationFee: number;
|
|
81
|
+
}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -10,6 +10,12 @@ export {
|
|
|
10
10
|
MortgageCalculationInput as CanadaMortgageCalculationInput,
|
|
11
11
|
MortgageCalculationResult as CanadaMortgageCalculationResult,
|
|
12
12
|
} from './mortgage/canada/domain/types';
|
|
13
|
+
export { SouthAfricaMortgageServiceImpl as SouthAfricaMortgageService } from './mortgage/south-africa/SouthAfricaMortgageServiceImpl';
|
|
14
|
+
export {
|
|
15
|
+
MortgageRules as SouthAfricaMortgageRules,
|
|
16
|
+
MortgageInput as SouthAfricaMortgageInput,
|
|
17
|
+
MortgageOutput as SouthAfricaMortgageOutput,
|
|
18
|
+
} from './mortgage/south-africa/domain/types';
|
|
13
19
|
export {
|
|
14
20
|
ComputedIncomeTaxValues as FranceComputedIncomeTaxValues,
|
|
15
21
|
IncomeTaxRules as FranceIncomeTaxRules,
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { MortgageRules, MortgageInput, MortgageOutput } from './domain/types';
|
|
2
|
+
import { SouthAfricaMortgageService } from './SouthAfricaMortgageService';
|
|
3
|
+
|
|
4
|
+
export class SouthAfricaMortgageServiceImpl implements SouthAfricaMortgageService {
|
|
5
|
+
constructor(
|
|
6
|
+
private readonly input: MortgageInput,
|
|
7
|
+
private readonly rules: MortgageRules
|
|
8
|
+
) {}
|
|
9
|
+
|
|
10
|
+
calculate(): MortgageOutput {
|
|
11
|
+
const loanAmount = this.input.propertyPrice - this.input.downPayment;
|
|
12
|
+
|
|
13
|
+
// Monthly interest rate
|
|
14
|
+
const monthlyRate = this.input.annualInterestRate / 100 / 12;
|
|
15
|
+
const totalPayments = this.input.amortizationYears * 12;
|
|
16
|
+
|
|
17
|
+
// Standard amortized monthly payment formula
|
|
18
|
+
const monthlyPayment =
|
|
19
|
+
(loanAmount * monthlyRate) /
|
|
20
|
+
(1 - Math.pow(1 + monthlyRate, -totalPayments));
|
|
21
|
+
|
|
22
|
+
const totalPaid = monthlyPayment * totalPayments;
|
|
23
|
+
const totalInterestPaid = totalPaid - loanAmount;
|
|
24
|
+
|
|
25
|
+
// Debt-to-income ratio
|
|
26
|
+
const debtToIncomeRatio =
|
|
27
|
+
(monthlyPayment / this.input.grossMonthlyIncome) * 100;
|
|
28
|
+
|
|
29
|
+
const isAffordable =
|
|
30
|
+
debtToIncomeRatio <= this.rules.loanConstraints.maxDebtToIncomePercent;
|
|
31
|
+
|
|
32
|
+
// Fees
|
|
33
|
+
const bondRegistrationFee =
|
|
34
|
+
loanAmount * (this.rules.fees.bondRegistrationPercent / 100);
|
|
35
|
+
|
|
36
|
+
const transferDuty = this.calculateTransferDuty(this.input.propertyPrice);
|
|
37
|
+
return {
|
|
38
|
+
loanAmount,
|
|
39
|
+
monthlyPayment,
|
|
40
|
+
totalInterestPaid,
|
|
41
|
+
totalPaid,
|
|
42
|
+
debtToIncomeRatio,
|
|
43
|
+
isAffordable,
|
|
44
|
+
transferDuty,
|
|
45
|
+
bondRegistrationFee
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private calculateTransferDuty(propertyPrice: number): number {
|
|
50
|
+
let duty = 0;
|
|
51
|
+
let previousLimit = 0;
|
|
52
|
+
|
|
53
|
+
for (const bracket of this.rules.fees.transferDuty.brackets) {
|
|
54
|
+
if (bracket.upTo && propertyPrice > previousLimit) {
|
|
55
|
+
const taxable = Math.min(propertyPrice, bracket.upTo) - previousLimit;
|
|
56
|
+
duty += taxable * bracket.rate;
|
|
57
|
+
previousLimit = bracket.upTo;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (bracket.above && propertyPrice > bracket.above) {
|
|
61
|
+
duty += (propertyPrice - bracket.above) * bracket.rate;
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return duty;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { RuleMeta } from "../../../shared/domain/types";
|
|
2
|
+
|
|
3
|
+
export interface RuleInputDefinition {
|
|
4
|
+
type: "number" | "string";
|
|
5
|
+
min?: number;
|
|
6
|
+
max?: number;
|
|
7
|
+
enum?: string[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface RuleInputs {
|
|
11
|
+
propertyPrice: RuleInputDefinition;
|
|
12
|
+
downPayment: RuleInputDefinition;
|
|
13
|
+
interestRate: RuleInputDefinition;
|
|
14
|
+
amortizationYears: RuleInputDefinition;
|
|
15
|
+
grossMonthlyIncome: RuleInputDefinition;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface LoanConstraints {
|
|
19
|
+
maxLtv: number;
|
|
20
|
+
minDownPaymentPercent: number;
|
|
21
|
+
maxAmortizationYears: number;
|
|
22
|
+
maxDebtToIncomePercent: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface InterestRules {
|
|
26
|
+
type: "fixed" | "variable";
|
|
27
|
+
rateRangePercent: { min: number; max: number };
|
|
28
|
+
stressTestBufferPercent: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface InsuranceRules {
|
|
32
|
+
required: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface TransferDutyBracket {
|
|
36
|
+
upTo?: number;
|
|
37
|
+
above?: number;
|
|
38
|
+
rate: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface FeesRules {
|
|
42
|
+
bondRegistrationPercent: number;
|
|
43
|
+
transferDuty: {
|
|
44
|
+
brackets: TransferDutyBracket[];
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface MortgageRules {
|
|
49
|
+
loanConstraints: LoanConstraints;
|
|
50
|
+
interest: InterestRules;
|
|
51
|
+
insurance: InsuranceRules;
|
|
52
|
+
fees: FeesRules;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface RuleOutputs {
|
|
56
|
+
loanAmount: "number";
|
|
57
|
+
monthlyPayment: "number";
|
|
58
|
+
totalInterestPaid: "number";
|
|
59
|
+
totalPaid: "number";
|
|
60
|
+
debtToIncomeRatio: "number";
|
|
61
|
+
isAffordable: "boolean";
|
|
62
|
+
transferDuty: "number";
|
|
63
|
+
bondRegistrationFee: "number";
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface MortgageRuleSet {
|
|
67
|
+
meta: RuleMeta;
|
|
68
|
+
inputs: RuleInputs;
|
|
69
|
+
rules: MortgageRules;
|
|
70
|
+
outputs: RuleOutputs;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface MortgageInput {
|
|
74
|
+
propertyPrice: number;
|
|
75
|
+
downPayment: number;
|
|
76
|
+
annualInterestRate: number; // e.g., 11.75%
|
|
77
|
+
amortizationYears: number;
|
|
78
|
+
grossMonthlyIncome: number;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface MortgageOutput {
|
|
82
|
+
loanAmount: number;
|
|
83
|
+
monthlyPayment: number;
|
|
84
|
+
totalInterestPaid: number;
|
|
85
|
+
totalPaid: number;
|
|
86
|
+
debtToIncomeRatio: number;
|
|
87
|
+
isAffordable: boolean;
|
|
88
|
+
transferDuty: number;
|
|
89
|
+
bondRegistrationFee: number;
|
|
90
|
+
}
|