Functional Weave
Code in TypeScript

property.mortgage-affordability@1.0.1

vectors.json

8,336 bytes · view raw

[
 {
  "name": "£200k on £54,000 a year: 3.70× income and 39.52% stressed, passes both",
  "args": [
   {
    "minor": 5400000,
    "currency": "GBP"
   },
   {
    "minor": 30000,
    "currency": "GBP"
   },
   {
    "minor": 20000000,
    "currency": "GBP"
   },
   450,
   750,
   300,
   45000,
   4000
  ],
  "expect": {
   "incomeMultipleBasisPoints": 37038,
   "maxLoanByMultiple": {
    "minor": 24300000,
    "currency": "GBP"
   },
   "withinMultiple": true,
   "monthlyIncome": {
    "minor": 450000,
    "currency": "GBP"
   },
   "repayment": {
    "payment": {
     "minor": 111167,
     "currency": "GBP"
    },
    "stressedPayment": {
     "minor": 147799,
     "currency": "GBP"
    },
    "debtToIncomeBasisPoints": 3138,
    "stressedDebtToIncomeBasisPoints": 3952,
    "affordable": true,
    "headroom": {
     "minor": 2201,
     "currency": "GBP"
    }
   },
   "affordable": true
  }
 },
 {
  "name": "£200k on £40,000 a year: 5× income and 53.34% stressed, fails both",
  "args": [
   {
    "minor": 4000000,
    "currency": "GBP"
   },
   {
    "minor": 30000,
    "currency": "GBP"
   },
   {
    "minor": 20000000,
    "currency": "GBP"
   },
   450,
   750,
   300,
   45000,
   4000
  ],
  "expect": {
   "incomeMultipleBasisPoints": 50000,
   "maxLoanByMultiple": {
    "minor": 18000000,
    "currency": "GBP"
   },
   "withinMultiple": false,
   "monthlyIncome": {
    "minor": 333333,
    "currency": "GBP"
   },
   "repayment": {
    "payment": {
     "minor": 111167,
     "currency": "GBP"
    },
    "stressedPayment": {
     "minor": 147799,
     "currency": "GBP"
    },
    "debtToIncomeBasisPoints": 4236,
    "stressedDebtToIncomeBasisPoints": 5334,
    "affordable": false,
    "headroom": {
     "minor": -44466,
     "currency": "GBP"
    }
   },
   "affordable": false
  }
 },
 {
  "name": "exactly 4.5× income is within a 4.5× multiple",
  "args": [
   {
    "minor": 4000000,
    "currency": "GBP"
   },
   {
    "minor": 0,
    "currency": "GBP"
   },
   {
    "minor": 18000000,
    "currency": "GBP"
   },
   425,
   725,
   300,
   45000,
   4000
  ],
  "expect": {
   "incomeMultipleBasisPoints": 45000,
   "maxLoanByMultiple": {
    "minor": 18000000,
    "currency": "GBP"
   },
   "withinMultiple": true,
   "monthlyIncome": {
    "minor": 333333,
    "currency": "GBP"
   },
   "repayment": {
    "payment": {
     "minor": 97513,
     "currency": "GBP"
    },
    "stressedPayment": {
     "minor": 130106,
     "currency": "GBP"
    },
    "debtToIncomeBasisPoints": 2926,
    "stressedDebtToIncomeBasisPoints": 3904,
    "affordable": true,
    "headroom": {
     "minor": 3227,
     "currency": "GBP"
    }
   },
   "affordable": true
  }
 },
 {
  "name": "a penny over 4.5× fails the multiple though the repayment fits",
  "args": [
   {
    "minor": 4000000,
    "currency": "GBP"
   },
   {
    "minor": 0,
    "currency": "GBP"
   },
   {
    "minor": 18000001,
    "currency": "GBP"
   },
   425,
   725,
   300,
   45000,
   4000
  ],
  "expect": {
   "incomeMultipleBasisPoints": 45001,
   "maxLoanByMultiple": {
    "minor": 18000000,
    "currency": "GBP"
   },
   "withinMultiple": false,
   "monthlyIncome": {
    "minor": 333333,
    "currency": "GBP"
   },
   "repayment": {
    "payment": {
     "minor": 97513,
     "currency": "GBP"
    },
    "stressedPayment": {
     "minor": 130106,
     "currency": "GBP"
    },
    "debtToIncomeBasisPoints": 2926,
    "stressedDebtToIncomeBasisPoints": 3904,
    "affordable": true,
    "headroom": {
     "minor": 3227,
     "currency": "GBP"
    }
   },
   "affordable": false
  }
 },
 {
  "name": "within the multiple but the stressed repayment does not fit",
  "args": [
   {
    "minor": 8000000,
    "currency": "GBP"
   },
   {
    "minor": 50000,
    "currency": "GBP"
   },
   {
    "minor": 30000000,
    "currency": "GBP"
   },
   500,
   800,
   360,
   45000,
   4000
  ],
  "expect": {
   "incomeMultipleBasisPoints": 37500,
   "maxLoanByMultiple": {
    "minor": 36000000,
    "currency": "GBP"
   },
   "withinMultiple": true,
   "monthlyIncome": {
    "minor": 666666,
    "currency": "GBP"
   },
   "repayment": {
    "payment": {
     "minor": 161047,
     "currency": "GBP"
    },
    "stressedPayment": {
     "minor": 220130,
     "currency": "GBP"
    },
    "debtToIncomeBasisPoints": 3166,
    "stressedDebtToIncomeBasisPoints": 4052,
    "affordable": false,
    "headroom": {
     "minor": -3464,
     "currency": "GBP"
    }
   },
   "affordable": false
  }
 },
 {
  "name": "a comfortable 2× income loan",
  "args": [
   {
    "minor": 10000000,
    "currency": "GBP"
   },
   {
    "minor": 0,
    "currency": "GBP"
   },
   {
    "minor": 20000000,
    "currency": "GBP"
   },
   450,
   750,
   300,
   45000,
   4000
  ],
  "expect": {
   "incomeMultipleBasisPoints": 20000,
   "maxLoanByMultiple": {
    "minor": 45000000,
    "currency": "GBP"
   },
   "withinMultiple": true,
   "monthlyIncome": {
    "minor": 833333,
    "currency": "GBP"
   },
   "repayment": {
    "payment": {
     "minor": 111167,
     "currency": "GBP"
    },
    "stressedPayment": {
     "minor": 147799,
     "currency": "GBP"
    },
    "debtToIncomeBasisPoints": 1335,
    "stressedDebtToIncomeBasisPoints": 1774,
    "affordable": true,
    "headroom": {
     "minor": 185534,
     "currency": "GBP"
    }
   },
   "affordable": true
  }
 },
 {
  "name": "an interest-free loan still gets a stressed repayment; odd income rounds the multiple up and the maximum down",
  "args": [
   {
    "minor": 6000001,
    "currency": "GBP"
   },
   {
    "minor": 0,
    "currency": "GBP"
   },
   {
    "minor": 12000000,
    "currency": "GBP"
   },
   0,
   300,
   240,
   45000,
   3500
  ],
  "expect": {
   "incomeMultipleBasisPoints": 20000,
   "maxLoanByMultiple": {
    "minor": 27000004,
    "currency": "GBP"
   },
   "withinMultiple": true,
   "monthlyIncome": {
    "minor": 500000,
    "currency": "GBP"
   },
   "repayment": {
    "payment": {
     "minor": 50000,
     "currency": "GBP"
    },
    "stressedPayment": {
     "minor": 66552,
     "currency": "GBP"
    },
    "debtToIncomeBasisPoints": 1000,
    "stressedDebtToIncomeBasisPoints": 1332,
    "affordable": true,
    "headroom": {
     "minor": 108448,
     "currency": "GBP"
    }
   },
   "affordable": true
  }
 },
 {
  "name": "mixed currencies are refused",
  "args": [
   {
    "minor": 5400000,
    "currency": "GBP"
   },
   {
    "minor": 30000,
    "currency": "GBP"
   },
   {
    "minor": 20000000,
    "currency": "EUR"
   },
   450,
   750,
   300,
   45000,
   4000
  ],
  "expectError": "currency mismatch"
 },
 {
  "name": "a zero income is refused",
  "args": [
   {
    "minor": 0,
    "currency": "GBP"
   },
   {
    "minor": 0,
    "currency": "GBP"
   },
   {
    "minor": 20000000,
    "currency": "GBP"
   },
   450,
   750,
   300,
   45000,
   4000
  ],
  "expectError": "annualIncome must be between 12 and"
 },
 {
  "name": "a zero loan is refused",
  "args": [
   {
    "minor": 5400000,
    "currency": "GBP"
   },
   {
    "minor": 0,
    "currency": "GBP"
   },
   {
    "minor": 0,
    "currency": "GBP"
   },
   450,
   750,
   300,
   45000,
   4000
  ],
  "expectError": "loan must be between 1 and"
 },
 {
  "name": "a zero multiple is refused",
  "args": [
   {
    "minor": 5400000,
    "currency": "GBP"
   },
   {
    "minor": 0,
    "currency": "GBP"
   },
   {
    "minor": 20000000,
    "currency": "GBP"
   },
   450,
   750,
   300,
   0,
   4000
  ],
  "expectError": "maxIncomeMultipleBasisPoints must be between 1 and 200000"
 },
 {
  "name": "a stress rate below the actual rate is refused by lending.affordability",
  "args": [
   {
    "minor": 5400000,
    "currency": "GBP"
   },
   {
    "minor": 0,
    "currency": "GBP"
   },
   {
    "minor": 20000000,
    "currency": "GBP"
   },
   450,
   400,
   300,
   45000,
   4000
  ],
  "expectError": "stressRateBasisPoints must not be below the actual rate"
 },
 {
  "name": "negative commitments are refused by lending.affordability",
  "args": [
   {
    "minor": 5400000,
    "currency": "GBP"
   },
   {
    "minor": -1,
    "currency": "GBP"
   },
   {
    "minor": 20000000,
    "currency": "GBP"
   },
   450,
   750,
   300,
   45000,
   4000
  ],
  "expectError": "monthlyCommitments must not be negative"
 }
]