monitoring(uk-payslip): simplify yearly receipt to earned-and-kept view
Replace the 7-stack "where total comp went" decomposition with a 3-stack "what I actually earned" view: salary (gross), bonus (gross), and RSU vest after band-aware tax (PAYE+NI withheld via sell-to-cover). Skips income tax / NI / student loan / pension / RSU offset. Bar height = real income kept across all components. RSU is net of tax because it's withheld at source and never hits the bank account; salary and bonus are gross because they're paid in full and taxes are deducted elsewhere. This is the income-side view where tax is implicit, not the deduction waterfall. Per-year RSU after tax: 2020/21 £18k · 2021/22 £39k · 2022/23 £50k · 2023/24 £26k · 2024/25 £71k · 2025/26 £73k.
This commit is contained in:
parent
a17304f735
commit
b2a25775aa
1 changed files with 11 additions and 87 deletions
|
|
@ -2328,8 +2328,8 @@
|
|||
},
|
||||
{
|
||||
"id": 16,
|
||||
"title": "Yearly receipt — where total comp went per tax year",
|
||||
"description": "One stacked bar per tax year. Bar height ≈ total comp (gross_pay + pension salary-sacrifice). Stacks: net pay (bank deposit), cash income tax, RSU tax (band-aware marginal: PAYE+NI), cash NI, student loan, pension via salary-sacrifice, RSU offset (Variant A only). Always shows all years — ignores the time picker.",
|
||||
"title": "Yearly receipt — what I actually earned per tax year",
|
||||
"description": "One stacked bar per tax year, showing pay components I keep: salary (gross), bonus (gross), and RSU vests AFTER band-aware tax (PAYE+NI withheld via sell-to-cover). Excludes deductions and taxes — this is the take-home view of earnings, not the gross-deductions waterfall. tax_year axis (text), unit GBP. Always shows all years — ignores the time picker.",
|
||||
"type": "barchart",
|
||||
"datasource": {
|
||||
"type": "grafana-postgresql-datasource",
|
||||
|
|
@ -2368,7 +2368,7 @@
|
|||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "net_pay"
|
||||
"options": "salary"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
|
|
@ -2380,121 +2380,45 @@
|
|||
},
|
||||
{
|
||||
"id": "displayName",
|
||||
"value": "Net pay (bank deposit)"
|
||||
"value": "Salary (gross)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "cash_income_tax"
|
||||
"options": "bonus"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "color",
|
||||
"value": {
|
||||
"mode": "fixed",
|
||||
"fixedColor": "#C4162A"
|
||||
"fixedColor": "#FADE2A"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "displayName",
|
||||
"value": "Income Tax (cash)"
|
||||
"value": "Bonus (gross)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "rsu_tax_marginal"
|
||||
"options": "rsu_after_tax"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "color",
|
||||
"value": {
|
||||
"mode": "fixed",
|
||||
"fixedColor": "#E0652E"
|
||||
"fixedColor": "#3274D9"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "displayName",
|
||||
"value": "Tax on RSU vest (band-aware marginal)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "cash_ni"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "color",
|
||||
"value": {
|
||||
"mode": "fixed",
|
||||
"fixedColor": "orange"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "displayName",
|
||||
"value": "National Insurance (cash)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "student_loan"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "color",
|
||||
"value": {
|
||||
"mode": "fixed",
|
||||
"fixedColor": "#8B4513"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "displayName",
|
||||
"value": "Student Loan"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "pension_sacrifice"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "color",
|
||||
"value": {
|
||||
"mode": "fixed",
|
||||
"fixedColor": "#CE96D8"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "displayName",
|
||||
"value": "Pension (salary sacrifice)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "rsu_offset"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "color",
|
||||
"value": {
|
||||
"mode": "fixed",
|
||||
"fixedColor": "#888888"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "displayName",
|
||||
"value": "RSU Offset (Variant A)"
|
||||
"value": "RSU vest (after band-aware tax)"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -2532,7 +2456,7 @@
|
|||
"rawQuery": true,
|
||||
"editorMode": "code",
|
||||
"format": "table",
|
||||
"rawSql": "WITH r AS (SELECT * FROM payslip_ingest.payslip), ani AS (SELECT *, COALESCE(SUM(gross_pay - COALESCE(pension_sacrifice, 0)) OVER (PARTITION BY tax_year ORDER BY pay_date ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING), 0) AS ani_prior FROM r), slice AS (SELECT *, ani_prior + gross_pay - COALESCE(rsu_vest, 0) - COALESCE(pension_sacrifice, 0) AS ani_pre, ani_prior + gross_pay - COALESCE(pension_sacrifice, 0) AS ani_post FROM ani), m AS (SELECT *, GREATEST(0, LEAST(ani_post, 12570) - GREATEST(ani_pre, 0)) * 0.00 + GREATEST(0, LEAST(ani_post, 50270) - GREATEST(ani_pre, 12570)) * 0.20 + GREATEST(0, LEAST(ani_post, 100000) - GREATEST(ani_pre, 50270)) * 0.40 + GREATEST(0, LEAST(ani_post, 125140) - GREATEST(ani_pre, 100000)) * 0.60 + GREATEST(0, ani_post - GREATEST(ani_pre, 125140)) * 0.45 AS rsu_paye_marginal, GREATEST(0, LEAST(ani_post, 12570) - GREATEST(ani_pre, 0)) * 0.00 + GREATEST(0, LEAST(ani_post, 50270) - GREATEST(ani_pre, 12570)) * 0.08 + GREATEST(0, ani_post - GREATEST(ani_pre, 50270)) * 0.02 AS rsu_ni_marginal FROM slice) SELECT tax_year, SUM(net_pay) AS net_pay, SUM(GREATEST(0, income_tax - rsu_paye_marginal)) AS cash_income_tax, SUM(rsu_paye_marginal + rsu_ni_marginal) AS rsu_tax_marginal, SUM(GREATEST(0, national_insurance - rsu_ni_marginal)) AS cash_ni, SUM(student_loan) AS student_loan, SUM(COALESCE(pension_sacrifice, 0)) AS pension_sacrifice, SUM(rsu_offset) AS rsu_offset FROM m GROUP BY tax_year ORDER BY tax_year"
|
||||
"rawSql": "WITH r AS (SELECT * FROM payslip_ingest.payslip), ani AS (SELECT *, COALESCE(SUM(gross_pay - COALESCE(pension_sacrifice, 0)) OVER (PARTITION BY tax_year ORDER BY pay_date ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING), 0) AS ani_prior FROM r), slice AS (SELECT *, ani_prior + gross_pay - COALESCE(rsu_vest, 0) - COALESCE(pension_sacrifice, 0) AS ani_pre, ani_prior + gross_pay - COALESCE(pension_sacrifice, 0) AS ani_post FROM ani), m AS (SELECT *, GREATEST(0, LEAST(ani_post, 12570) - GREATEST(ani_pre, 0)) * 0.00 + GREATEST(0, LEAST(ani_post, 50270) - GREATEST(ani_pre, 12570)) * 0.20 + GREATEST(0, LEAST(ani_post, 100000) - GREATEST(ani_pre, 50270)) * 0.40 + GREATEST(0, LEAST(ani_post, 125140) - GREATEST(ani_pre, 100000)) * 0.60 + GREATEST(0, ani_post - GREATEST(ani_pre, 125140)) * 0.45 AS rsu_paye_marginal, GREATEST(0, LEAST(ani_post, 12570) - GREATEST(ani_pre, 0)) * 0.00 + GREATEST(0, LEAST(ani_post, 50270) - GREATEST(ani_pre, 12570)) * 0.08 + GREATEST(0, ani_post - GREATEST(ani_pre, 50270)) * 0.02 AS rsu_ni_marginal FROM slice) SELECT tax_year, SUM(salary) AS salary, SUM(bonus) AS bonus, SUM(rsu_vest - rsu_paye_marginal - rsu_ni_marginal) AS rsu_after_tax FROM m GROUP BY tax_year ORDER BY tax_year"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue