Created
June 25, 2024 00:41
-
-
Save danielabar/e24e8322798f2bd3b1d2973a829871ef to your computer and use it in GitHub Desktop.
Old Age Security (OAS) Calculator Prototype
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Should I Delay Old Age Security</title> | |
<!-- TailwindCSS CDN link --> | |
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css" rel="stylesheet"> | |
<!-- Chart.js CDN links (core library and chart types that you'll use) --> | |
<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script> | |
<!-- Chart.js Annotation plugin CDN link --> | |
<!-- https://www.chartjs.org/chartjs-plugin-annotation/latest/guide/ --> | |
<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script> | |
</head> | |
<!-- Ref: Life expectancy stats can: https://www150.statcan.gc.ca/n1/en/catalogue/84-537-X --> | |
<body class="bg-gray-100 p-4"> | |
<div class="max-w-screen-md mx-auto"> | |
<h1 class="text-3xl font-semibold mb-4">Should I Delay Old Age Security?</h1> | |
<div class="bg-white p-4 rounded shadow mb-6"> | |
<p class="text-lg leading-relaxed mb-4"> | |
Eligible Canadians can apply for Old Age Security (OAS) starting at age 65. It's also possible to delay | |
applying up to age 70. Each month of delay will increase the benefit by 0.6%. But it may not be worth it, | |
especially for low income Canadians that are also eligible for GIS. Use this calculator to determine how | |
delaying could impact you. | |
</p> | |
</div> | |
<div class="bg-white p-4 rounded shadow mb-6"> | |
<form id="oas-form"> | |
<!-- Years Lived in Canada as an Adult --> | |
<div class="mb-4"> | |
<label for="years-in-canada" class="block text-xl font-bold mb-2">Years Lived in Canada as an Adult</label> | |
<p class="text-gray-600 text-sm mb-2">(between ages 18 and 65)</p> | |
<input type="number" id="years-in-canada" name="years_in_canada" | |
class="w-full p-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" | |
min="0" max="40" required> | |
</div> | |
<!-- Age taking OAS: 66, 67, 68, 69, 70 --> | |
<div class="mb-4"> | |
<label for="age-taking-oas" class="block text-xl font-bold mb-2">I Want to Delay OAS to Age</label> | |
<select id="age-taking-oas" name="age_taking_oas" | |
class="w-full p-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" | |
required> | |
<option value="66">66</option> | |
<option value="67">67</option> | |
<option value="68">68</option> | |
<option value="69">69</option> | |
<option value="70">70</option> | |
</select> | |
</div> | |
<!-- Submit Button --> | |
<div class="text-center"> | |
<button type="submit" | |
class="w-full bg-blue-500 text-white font-bold py-3 px-4 rounded-lg hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50"> | |
Calculate | |
</button> | |
</div> | |
</form> | |
</div> | |
<div id="resultsPanel" hidden class="bg-white p-4 rounded shadow mb-6"> | |
<div id="explanation"> | |
<h2 class="text-2xl font-semibold mb-4">Your Results</h2> | |
<p class="mb-4 text-lg"> | |
With your <span class="text-blue-500 font-semibold" id="resultsYearsInCanada"></span> years in Canada after | |
age 18, you're eligible for a monthly OAS benefit of | |
<span class="text-blue-500 font-semibold" id="resultsOasAt65"></span>, which is | |
<span class="text-blue-500 font-semibold" id="resultsCanadaMultiplier"></span> | |
of the full amount <span class="text-blue-500 font-semibold" id="resultsOasFullAmount"></span>, | |
starting at age <span class="text-blue-500 font-semibold" id="resultsDefaultAge"></span>. | |
</p> | |
<p class="mb-4 text-lg"> | |
Delaying OAS to age <span class="text-blue-500 font-semibold" id="resultsDelayAge"></span> will increase | |
your payment by <span class="text-blue-500 font-semibold" id="resultsPercentageIncrease"></span> | |
to <span class="text-blue-500 font-semibold" id="resultsOasDelayed"></span>. | |
</p> | |
<p class="mb-4 text-lg"> | |
While delaying does result in a larger monthly payment, it also means forgoing <span | |
class="text-blue-500 font-semibold" id="resultsYearsMissed"></span> years of payments | |
from age <span class="text-blue-500 font-semibold" id="resultsDefaultAge"></span> to your delayed starting | |
age of <span class="text-blue-500 font-semibold" id="resultsDelayAge"></span>. | |
</p> | |
<p class="mb-4 text-lg"> | |
In order to break even (have the same amount of money) from delaying OAS to <span | |
class="text-blue-500 font-semibold" id="resultsDelayAge"></span>, you would have to | |
live until at least <span class="text-blue-500 font-semibold" id="resultsBreakevenAge"></span> years old. If | |
you live beyond this, then you will end up with more OAS | |
overall than if you had started at <span class="text-blue-500 font-semibold" id="resultsDefaultAge"></span>. | |
</p> | |
<p class="mb-4 text-lg"> | |
The chart below shows how much total OAS you will have accumulated at each age whether you started at | |
<span class="text-blue-500 font-semibold" id="resultsDefaultAge"></span> or <span | |
class="text-blue-500 font-semibold" id="resultsDelayAge"></span>. | |
Where the two lines cross over is the break even point. | |
</p> | |
<canvas id="myChart"></canvas> | |
</div> | |
</div> | |
</div> | |
<script> | |
// TODO: Get all source data from a single json | |
// as of 2024 | |
const lifeExpectancy = 83 | |
const fullOasAmount = 713.34 | |
const requiredYearsInCanadaAsAdult = 40 | |
const minAge = 65 | |
const finalAge = 95 // we have to pick somewhere to end | |
let myChart = null | |
// FIRST we calculate how many 40ths of the full amount user is eligible for based on years in Canada. | |
// SECOND apply the months delay multiplier if applicable. | |
function determineOasAmount(yearsInCanada, ageTakingOas) { | |
yearsInCanadaFraction = yearsInCanada / requiredYearsInCanadaAsAdult | |
interimAmount = fullOasAmount * yearsInCanadaFraction | |
if (ageTakingOas == minAge) { | |
return interimAmount | |
} else { | |
const delayedMonths = (ageTakingOas - minAge) * 12 | |
const multiplier = 1 + (delayedMonths * 0.006); // 0.6% increase per month | |
return interimAmount * multiplier; | |
} | |
} | |
// Ref: https://www.canada.ca/en/services/benefits/publicpensions/cpp/old-age-security/benefit-amount.html | |
// Function to generate data for starting at age 65 | |
const generateChartData = (yearsInCanada) => { | |
const monthlyAmount = determineOasAmount(yearsInCanada, minAge) | |
let dataPoints = []; | |
dataPoints.push({ x: minAge, y: 0 }) | |
for (let age = minAge + 1; age <= finalAge; age++) { | |
let totalBenefit = (age - minAge) * 12 * monthlyAmount; | |
dataPoints.push({ x: age, y: totalBenefit }); | |
} | |
return dataPoints; | |
}; | |
const generateChartDataDelayed = (yearsInCanada, ageTakingOas) => { | |
const monthlyAmount = determineOasAmount(yearsInCanada, ageTakingOas) | |
let dataPoints = []; | |
dataPoints.push({ x: ageTakingOas, y: 0 }) | |
for (let age = ageTakingOas + 1; age <= finalAge; age++) { | |
let totalBenefit = (age - ageTakingOas) * 12 * monthlyAmount; | |
dataPoints.push({ x: age, y: totalBenefit }); | |
} | |
return dataPoints; | |
}; | |
// Find the breakeven age (where the two lines intersect approximately) | |
function findBreakevenAge(dataDefault, dataDelayed, ageTakingOas) { | |
let breakevenAge = null; | |
for (let age = ageTakingOas; age <= finalAge; age++) { | |
const benefitAt65 = dataDefault.find(item => item.x === age)?.y || 0; | |
const benefitAt70 = dataDelayed.find(item => item.x === age)?.y || 0; | |
if (Math.abs(benefitAt70 - benefitAt65) <= 2000) { | |
breakevenAge = age; | |
break; | |
} | |
} | |
return breakevenAge; | |
}; | |
function generateChart(dataDefault, dataDelayed, breakEvenAge, ageTakingOas) { | |
const ctx = document.getElementById('myChart').getContext('2d'); | |
if (myChart) { // If a chart instance already exists, destroy it | |
myChart.destroy(); | |
} | |
const data = { | |
datasets: [ | |
{ | |
label: `Starting at Age ${minAge}`, | |
data: dataDefault, | |
borderColor: 'rgb(75, 192, 192)', | |
tension: 0.1, | |
fill: false | |
}, | |
{ | |
label: `Starting at Age ${ageTakingOas} (Delayed)`, | |
data: dataDelayed, | |
borderColor: 'rgb(255, 99, 132)', | |
tension: 0.1, | |
fill: false | |
} | |
] | |
}; | |
const options = { | |
responsive: true, | |
plugins: { | |
tooltip: { | |
callbacks: { | |
label: function (tooltipItem) { | |
return `Age ${tooltipItem.raw.x}: $${tooltipItem.raw.y.toLocaleString()}`; | |
} | |
} | |
}, | |
annotation: { | |
annotations: [ | |
{ | |
type: 'line', | |
mode: 'vertical', | |
scaleID: 'x', | |
value: breakEvenAge, | |
borderColor: 'gray', | |
borderWidth: 2, | |
label: { | |
content: `Breakeven Age: ${breakEvenAge}`, | |
enabled: true, | |
position: 'start' | |
} | |
} | |
] | |
} | |
}, | |
scales: { | |
x: { | |
type: 'linear', | |
position: 'bottom', | |
title: { | |
display: true, | |
text: 'Age' | |
}, | |
ticks: { | |
stepSize: 5 | |
} | |
}, | |
y: { | |
title: { | |
display: true, | |
text: 'Total Benefits Received' | |
}, | |
ticks: { | |
callback: function (value, index, values) { | |
return '$' + value.toLocaleString(); // Add thousands separators | |
} | |
} | |
} | |
} | |
}; | |
myChart = new Chart(ctx, { | |
type: 'line', | |
data: data, | |
options: options | |
}); | |
} | |
document.getElementById('oas-form').addEventListener('submit', calculate); | |
function updateContent(selector, value) { | |
const elements = document.querySelectorAll(`#${selector}`) | |
elements.forEach(element => { | |
element.textContent = value | |
}); | |
} | |
// function updateResults(results) { | |
// document.getElementById('resultsYearsInCanada').textContent = results.yearsInCanada; | |
// document.getElementById('resultsOasAt65').textContent = `${results.oasAt65.toFixed(2)}`; | |
// document.getElementById('resultsCanadaMultiplier').textContent = `${results.canadaMultiplier.toFixed(3)}`; | |
// document.getElementById('resultsOasFullAmount').textContent = `${fullOasAmount.toFixed(2)}`; | |
// document.getElementById('resultsDefaultAge').textContent = minAge; | |
// document.getElementById('resultsDelayAge').textContent = results.delayAge; | |
// document.getElementById('resultsPercentageIncrease').textContent = `${(results.percentageIncrease * 100).toFixed(1)}%`; | |
// document.getElementById('resultsOasDelayed').textContent = `${results.oasDelayed.toFixed(2)}`; | |
// document.getElementById('resultsYearsMissed').textContent = results.yearsMissed; | |
// document.getElementById('resultsBreakevenAge').textContent = results.breakevenAge; | |
// // Show the results panel | |
// document.getElementById('resultsPanel').removeAttribute('hidden') | |
// } | |
function updateResults(results) { | |
updateContent('resultsYearsInCanada', results.yearsInCanada); | |
updateContent('resultsOasAt65', `${results.oasAt65.toFixed(2)}`); | |
updateContent('resultsCanadaMultiplier', `${results.canadaMultiplier.toFixed(3)}`); | |
updateContent('resultsOasFullAmount', `${fullOasAmount.toFixed(2)}`); | |
updateContent('resultsDefaultAge', minAge); | |
updateContent('resultsDelayAge', results.delayAge); | |
updateContent('resultsPercentageIncrease', `${(results.percentageIncrease * 100).toFixed(1)}%`); | |
updateContent('resultsOasDelayed', `${results.oasDelayed.toFixed(2)}`); | |
updateContent('resultsYearsMissed', results.yearsMissed); | |
updateContent('resultsBreakevenAge', results.breakEvenAge); | |
// Show the results panel | |
document.getElementById('resultsPanel').removeAttribute('hidden'); | |
} | |
function calculate(evt) { | |
evt.preventDefault(); | |
const yearsInCanada = Number(document.getElementById('years-in-canada').value); | |
const ageTakingOas = Number(document.getElementById('age-taking-oas').value); | |
const dataDefault = generateChartData(yearsInCanada); | |
const dataDelayed = generateChartDataDelayed(yearsInCanada, ageTakingOas); | |
const breakEvenAge = findBreakevenAge(dataDefault, dataDelayed, ageTakingOas) | |
// Calculate the required values | |
// function determineOasAmount(yearsInCanada, ageTakingOas) { | |
// const interimAmount = fullOasAmount * (yearsInCanada / requiredYearsInCanadaAsAdult); | |
const oasAt65 = determineOasAmount(yearsInCanada, minAge) | |
const oasDelayed = determineOasAmount(yearsInCanada, ageTakingOas) | |
const percentageIncrease = (oasDelayed - oasAt65) / oasAt65; | |
const yearsMissed = ageTakingOas - minAge; | |
// Construct results object | |
const results = { | |
yearsInCanada, | |
oasAt65, | |
canadaMultiplier: yearsInCanada / requiredYearsInCanadaAsAdult, | |
oasFullAmount: fullOasAmount, | |
defaultAge: minAge, | |
delayAge: ageTakingOas, | |
percentageIncrease, | |
oasDelayed, | |
yearsMissed, | |
breakEvenAge | |
}; | |
// Update the UI with results | |
updateResults(results); | |
generateChart(dataDefault, dataDelayed, breakEvenAge, ageTakingOas) | |
} | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment