Skip to content

Instantly share code, notes, and snippets.

@danielabar
Created June 25, 2024 00:41
Show Gist options
  • Save danielabar/e24e8322798f2bd3b1d2973a829871ef to your computer and use it in GitHub Desktop.
Save danielabar/e24e8322798f2bd3b1d2973a829871ef to your computer and use it in GitHub Desktop.
Old Age Security (OAS) Calculator Prototype
<!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