infra/stacks/monitoring/modules/monitoring/dashboards/uk-payslip.json
Viktor Barzin a17304f735 monitoring(uk-payslip): fix empty YTD gross YoY chart
Two bugs:
1. Synthetic dates projected onto 1970/71 fell outside the dashboard's
   default time range (now-10y → now), so Grafana filtered out every
   point. Switched to a sliding 12-month window
   (CURRENT_DATE - INTERVAL '12 months') as the projection base, plus
   a per-panel timeFrom: "13M" override so the panel always shows the
   last 13 months regardless of the dashboard's time picker.
2. ORDER BY tax_year, pay_date violated Grafana's long→wide conversion
   requirement (data must be ascending by time). Wrapped in a CTE and
   re-ordered by the synthetic time column. Pivoted result is now a
   single wide frame with 7 series (2019/20…2025/26).
2026-04-25 23:36:16 +00:00

2611 lines
No EOL
82 KiB
JSON

{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "datasource",
"uid": "grafana"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"description": "UK payslip breakdown \u2014 tax-year YTD hero, monthly cash flow, effective-rate trend, data-integrity flags, and full payslip table.",
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 1,
"id": null,
"links": [],
"panels": [
{
"id": 1,
"title": "YTD sources \u2014 income composition",
"description": "Year-to-date cumulative breakdown of gross pay by source. Stacked \u2014 top of the stack equals gross_pay. Reset at each tax-year boundary.",
"type": "timeseries",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "payslips-pg"
},
"gridPos": {
"h": 10,
"w": 12,
"x": 0,
"y": 0
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"unit": "currencyGBP",
"custom": {
"axisPlacement": "auto",
"drawStyle": "line",
"fillOpacity": 70,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineWidth": 1,
"pointSize": 4,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "normal"
},
"thresholdsStyle": {
"mode": "off"
}
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "ytd_salary"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "green"
}
},
{
"id": "displayName",
"value": "Salary"
}
]
},
{
"matcher": {
"id": "byName",
"options": "ytd_bonus"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "yellow"
}
},
{
"id": "displayName",
"value": "Bonus"
}
]
},
{
"matcher": {
"id": "byName",
"options": "ytd_rsu"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "blue"
}
},
{
"id": "displayName",
"value": "RSU (notional)"
}
]
},
{
"matcher": {
"id": "byName",
"options": "ytd_other"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "text"
}
},
{
"id": "displayName",
"value": "Other / residual"
}
]
}
]
},
"options": {
"legend": {
"calcs": [
"last",
"max"
],
"displayMode": "table",
"placement": "bottom"
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"targets": [
{
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "payslips-pg"
},
"rawSql": "SELECT pay_date AS \"time\", SUM(salary) OVER w AS ytd_salary, SUM(bonus) OVER w AS ytd_bonus, SUM(rsu_vest) OVER w AS ytd_rsu, SUM(GREATEST(gross_pay - salary - bonus - rsu_vest, 0)) OVER w AS ytd_other FROM payslip_ingest.payslip WHERE $__timeFilter(pay_date) WINDOW w AS (PARTITION BY tax_year ORDER BY pay_date) ORDER BY pay_date",
"format": "time_series",
"refId": "A",
"rawQuery": true,
"editorMode": "code"
}
]
},
{
"id": 7,
"title": "YTD uses \u2014 where gross went",
"description": "Year-to-date cumulative breakdown of where the gross went. Stacked \u2014 top equals gross_pay minus student loan and RSU offset (both small; shown on Panel 8 Sankey). RSU vest tax broken out at the exact band-aware marginal (PA-taper aware: 60% in the \u00a3100k\u2013\u00a3125,140 zone, 47% additional-rate, etc.) \u2014 see SQL for full bands. Green = take-home; red = cash income tax; orange = tax on RSU vest; orange = cash NI; purple = pension.",
"type": "timeseries",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "payslips-pg"
},
"gridPos": {
"h": 10,
"w": 12,
"x": 12,
"y": 0
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"unit": "currencyGBP",
"custom": {
"axisPlacement": "auto",
"drawStyle": "line",
"fillOpacity": 70,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineWidth": 1,
"pointSize": 4,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "normal"
},
"thresholdsStyle": {
"mode": "off"
}
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "ytd_net"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "green"
}
},
{
"id": "displayName",
"value": "Net (take-home)"
}
]
},
{
"matcher": {
"id": "byName",
"options": "ytd_cash_income_tax"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "#C4162A"
}
},
{
"id": "displayName",
"value": "Income Tax (cash)"
}
]
},
{
"matcher": {
"id": "byName",
"options": "ytd_rsu_tax_marginal"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "#E0652E"
}
},
{
"id": "displayName",
"value": "Tax on RSU vest (band-aware marginal)"
}
]
},
{
"matcher": {
"id": "byName",
"options": "ytd_cash_ni"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "orange"
}
},
{
"id": "displayName",
"value": "National Insurance (cash)"
}
]
},
{
"matcher": {
"id": "byName",
"options": "ytd_pension_employee"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "purple"
}
},
{
"id": "displayName",
"value": "Pension (employee)"
}
]
}
]
},
"options": {
"legend": {
"calcs": [
"last",
"max"
],
"displayMode": "table",
"placement": "bottom"
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"targets": [
{
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "payslips-pg"
},
"rawSql": "WITH r AS (SELECT * FROM payslip_ingest.payslip WHERE $__timeFilter(pay_date)), 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 pay_date AS \"time\", SUM(net_pay) OVER w AS ytd_net, SUM(GREATEST(0, income_tax - rsu_paye_marginal)) OVER w AS ytd_cash_income_tax, SUM(rsu_paye_marginal + rsu_ni_marginal) OVER w AS ytd_rsu_tax_marginal, SUM(GREATEST(0, national_insurance - rsu_ni_marginal)) OVER w AS ytd_cash_ni, SUM(pension_employee) OVER w AS ytd_pension_employee FROM m WINDOW w AS (PARTITION BY tax_year ORDER BY pay_date) ORDER BY pay_date",
"format": "time_series",
"refId": "A",
"rawQuery": true,
"editorMode": "code"
}
]
},
{
"id": 2,
"title": "Monthly cash flow (RSU stripped)",
"description": "Cash-only view: gross pay minus the RSU vest (cash_gross) and the bank-deposited net_pay. Tax and NI are not shown here because UK cumulative PAYE genuinely takes a YTD true-up chunk in vest months on top of the marginal RSU PAYE — see Panel 11 for the full tax breakdown with the band-aware RSU split.",
"type": "timeseries",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "payslips-pg"
},
"gridPos": {
"h": 9,
"w": 12,
"x": 0,
"y": 10
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"unit": "currencyGBP",
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 10,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineWidth": 2,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
}
},
"overrides": []
},
"options": {
"legend": {
"calcs": [
"last",
"mean"
],
"displayMode": "table",
"placement": "bottom"
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"targets": [
{
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "payslips-pg"
},
"rawSql": "SELECT pay_date AS \"time\", (gross_pay - rsu_vest) AS cash_gross, net_pay FROM payslip_ingest.payslip WHERE $__timeFilter(pay_date) ORDER BY pay_date",
"format": "time_series",
"refId": "A",
"rawQuery": true,
"editorMode": "code"
}
]
},
{
"id": 3,
"title": "Effective rate & take-home % (YTD cumulative)",
"description": "YTD-cumulative rates \u2014 three angles on take-home. (1) PAYE rate = SUM(income_tax) / SUM(taxable_pay): the audit number HMRC uses, converges to ~marginal in the additional-rate band. (2) Cash take-home % = SUM(net_pay) / SUM(gross_pay - rsu_vest): what fraction of cash earnings becomes a bank deposit; useful for cash-flow planning. (3) Total keep % = (SUM(net_pay) + SUM(rsu_vest - rsu_paye_marginal - rsu_ni_marginal)) / SUM(gross_pay): true 'what I actually keep' including post-tax RSU shares with the exact band-aware marginal (PA-taper aware). Resets on 6-April tax year boundary.",
"type": "timeseries",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "payslips-pg"
},
"gridPos": {
"h": 9,
"w": 12,
"x": 12,
"y": 10
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"unit": "percent",
"min": 0,
"max": 100,
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 10,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineWidth": 2,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "ytd_paye_rate_pct"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "#C4162A"
}
},
{
"id": "displayName",
"value": "PAYE rate (HMRC, on taxable_pay)"
}
]
},
{
"matcher": {
"id": "byName",
"options": "ytd_cash_take_home_pct"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "green"
}
},
{
"id": "displayName",
"value": "Cash take-home % (net / cash_gross)"
}
]
},
{
"matcher": {
"id": "byName",
"options": "ytd_total_keep_pct"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "blue"
}
},
{
"id": "displayName",
"value": "Total keep % (cash + post-tax shares)"
}
]
}
]
},
"options": {
"legend": {
"calcs": [
"last",
"mean"
],
"displayMode": "table",
"placement": "bottom"
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"targets": [
{
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "payslips-pg"
},
"rawSql": "WITH r AS (SELECT * FROM payslip_ingest.payslip WHERE $__timeFilter(pay_date)), 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 pay_date AS \"time\", ROUND(((SUM(income_tax) OVER w)::numeric / NULLIF(SUM(COALESCE(taxable_pay, gross_pay)) OVER w, 0)) * 100, 2) AS \"ytd_paye_rate_pct\", ROUND(((SUM(net_pay) OVER w)::numeric / NULLIF(SUM(gross_pay - rsu_vest) OVER w, 0)) * 100, 2) AS \"ytd_cash_take_home_pct\", ROUND((((SUM(net_pay) OVER w) + (SUM(rsu_vest - rsu_paye_marginal - rsu_ni_marginal) OVER w))::numeric / NULLIF(SUM(gross_pay) OVER w, 0)) * 100, 2) AS \"ytd_total_keep_pct\" FROM m WINDOW w AS (PARTITION BY tax_year ORDER BY pay_date) ORDER BY pay_date",
"format": "time_series",
"refId": "A",
"rawQuery": true,
"editorMode": "code"
}
]
},
{
"id": 11,
"title": "Tax & pension \u2014 monthly",
"description": "Per-month RSU vest tax + recurring deductions. Cash-side PAYE/NI hidden because UK cumulative PAYE makes them inherently bumpy in vest months despite the marginal RSU strip \u2014 see Panel 12 (YTD cumulative) for the smoothed totals or Panel 3 for the effective rate. Orange = tax on RSU vest at the exact band-aware marginal (PA-taper aware: 60% in \u00a3100k\u2013\u00a3125,140 zone); brown = student loan; purple = employee pension; light purple = employer pension (paid on top of salary).",
"type": "timeseries",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "payslips-pg"
},
"gridPos": {
"h": 10,
"w": 12,
"x": 0,
"y": 19
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"unit": "currencyGBP",
"custom": {
"axisPlacement": "auto",
"drawStyle": "line",
"fillOpacity": 70,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineWidth": 1,
"pointSize": 4,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "normal"
},
"thresholdsStyle": {
"mode": "off"
}
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "rsu_tax_marginal"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "#E0652E"
}
},
{
"id": "displayName",
"value": "Tax on RSU vest (band-aware marginal)"
}
]
},
{
"matcher": {
"id": "byName",
"options": "student_loan"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "#8B4513"
}
},
{
"id": "displayName",
"value": "Student Loan"
}
]
},
{
"matcher": {
"id": "byName",
"options": "pension_employee"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "purple"
}
},
{
"id": "displayName",
"value": "Pension (employee)"
}
]
},
{
"matcher": {
"id": "byName",
"options": "pension_employer"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "#CE96D8"
}
},
{
"id": "displayName",
"value": "Pension (employer)"
}
]
}
]
},
"options": {
"legend": {
"calcs": [
"last",
"max"
],
"displayMode": "table",
"placement": "bottom"
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"targets": [
{
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "payslips-pg"
},
"rawSql": "WITH r AS (SELECT * FROM payslip_ingest.payslip WHERE $__timeFilter(pay_date)), 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 pay_date AS \"time\", (rsu_paye_marginal + rsu_ni_marginal) AS rsu_tax_marginal, student_loan, pension_employee, pension_employer FROM m ORDER BY pay_date",
"format": "time_series",
"refId": "A",
"rawQuery": true,
"editorMode": "code"
}
]
},
{
"id": 12,
"title": "Tax & pension \u2014 YTD cumulative",
"description": "Year-to-date cumulative tax and pension. Same series and colors as the monthly panel \u2014 RSU vest tax broken out at the exact band-aware marginal (PA-taper aware: 60% in \u00a3100k\u2013\u00a3125,140 zone, 47% additional-rate, etc.). Resets on 6-April tax year boundary.",
"type": "timeseries",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "payslips-pg"
},
"gridPos": {
"h": 10,
"w": 12,
"x": 12,
"y": 19
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"unit": "currencyGBP",
"custom": {
"axisPlacement": "auto",
"drawStyle": "line",
"fillOpacity": 70,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineWidth": 1,
"pointSize": 4,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "normal"
},
"thresholdsStyle": {
"mode": "off"
}
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "ytd_cash_income_tax"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "#C4162A"
}
},
{
"id": "displayName",
"value": "Income Tax (cash)"
}
]
},
{
"matcher": {
"id": "byName",
"options": "ytd_rsu_tax_marginal"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "#E0652E"
}
},
{
"id": "displayName",
"value": "Tax on RSU vest (band-aware marginal)"
}
]
},
{
"matcher": {
"id": "byName",
"options": "ytd_cash_ni"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "orange"
}
},
{
"id": "displayName",
"value": "National Insurance (cash)"
}
]
},
{
"matcher": {
"id": "byName",
"options": "ytd_student_loan"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "#8B4513"
}
},
{
"id": "displayName",
"value": "Student Loan"
}
]
},
{
"matcher": {
"id": "byName",
"options": "ytd_pension_employee"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "purple"
}
},
{
"id": "displayName",
"value": "Pension (employee)"
}
]
},
{
"matcher": {
"id": "byName",
"options": "ytd_pension_employer"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "#CE96D8"
}
},
{
"id": "displayName",
"value": "Pension (employer)"
}
]
}
]
},
"options": {
"legend": {
"calcs": [
"last",
"max"
],
"displayMode": "table",
"placement": "bottom"
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"targets": [
{
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "payslips-pg"
},
"rawSql": "WITH r AS (SELECT * FROM payslip_ingest.payslip WHERE $__timeFilter(pay_date)), 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 pay_date AS \"time\", SUM(GREATEST(0, income_tax - rsu_paye_marginal)) OVER w AS ytd_cash_income_tax, SUM(rsu_paye_marginal + rsu_ni_marginal) OVER w AS ytd_rsu_tax_marginal, SUM(GREATEST(0, national_insurance - rsu_ni_marginal)) OVER w AS ytd_cash_ni, SUM(student_loan) OVER w AS ytd_student_loan, SUM(pension_employee) OVER w AS ytd_pension_employee, SUM(pension_employer) OVER w AS ytd_pension_employer FROM m WINDOW w AS (PARTITION BY tax_year ORDER BY pay_date) ORDER BY pay_date",
"format": "time_series",
"refId": "A",
"rawQuery": true,
"editorMode": "code"
}
]
},
{
"id": 13,
"title": "Monthly cash flow \u2014 tax impact (RSU excluded)",
"description": "Cash-only deductions. RSU PAYE excluded \u2014 it's withheld at source via sell-to-cover and never hits the bank account. See Panel 11 for the full PAYE view including RSU-attributed tax.",
"type": "timeseries",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "payslips-pg"
},
"gridPos": {
"h": 9,
"w": 24,
"x": 0,
"y": 29
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"unit": "currencyGBP",
"custom": {
"axisPlacement": "auto",
"drawStyle": "bars",
"fillOpacity": 100,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineWidth": 0,
"pointSize": 4,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "normal"
},
"thresholdsStyle": {
"mode": "off"
}
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "cash_income_tax"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "#C4162A"
}
},
{
"id": "displayName",
"value": "Income Tax (cash pay)"
}
]
},
{
"matcher": {
"id": "byName",
"options": "ni"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "orange"
}
},
{
"id": "displayName",
"value": "National Insurance"
}
]
},
{
"matcher": {
"id": "byName",
"options": "student_loan"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "#8B4513"
}
},
{
"id": "displayName",
"value": "Student Loan"
}
]
},
{
"matcher": {
"id": "byName",
"options": "pension_employee"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "purple"
}
},
{
"id": "displayName",
"value": "Pension (employee)"
}
]
}
]
},
"options": {
"legend": {
"calcs": [
"last",
"max",
"sum"
],
"displayMode": "table",
"placement": "bottom"
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"targets": [
{
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "payslips-pg"
},
"rawSql": "SELECT pay_date AS \"time\", COALESCE(cash_income_tax, income_tax) AS cash_income_tax, national_insurance AS ni, student_loan, pension_employee FROM payslip_ingest.payslip WHERE $__timeFilter(pay_date) ORDER BY pay_date",
"format": "time_series",
"refId": "A",
"rawQuery": true,
"editorMode": "code"
}
]
},
{
"id": 4,
"title": "Data integrity \u2014 missing months & parser flags",
"type": "table",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "payslips-pg"
},
"gridPos": {
"h": 6,
"w": 24,
"x": 0,
"y": 38
},
"fieldConfig": {
"defaults": {
"custom": {
"align": "right",
"cellOptions": {
"type": "auto"
},
"inspect": false
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "month_start"
},
"properties": [
{
"id": "custom.width",
"value": 120
},
{
"id": "custom.align",
"value": "left"
}
]
},
{
"matcher": {
"id": "byName",
"options": "status"
},
"properties": [
{
"id": "custom.width",
"value": 140
},
{
"id": "custom.align",
"value": "center"
},
{
"id": "custom.cellOptions",
"value": {
"type": "color-background",
"mode": "basic"
}
},
{
"id": "mappings",
"value": [
{
"type": "value",
"options": {
"MISSING": {
"color": "red",
"index": 0,
"text": "MISSING"
},
"ZERO_SALARY": {
"color": "red",
"index": 1,
"text": "ZERO_SALARY"
},
"RSU_NO_SALARY": {
"color": "red",
"index": 2,
"text": "RSU_NO_SALARY"
},
"NULL_CASH_TAX": {
"color": "orange",
"index": 3,
"text": "NULL_CASH_TAX"
},
"ok": {
"color": "green",
"index": 4,
"text": "ok"
}
}
}
]
}
]
},
{
"matcher": {
"id": "byName",
"options": "pay_date"
},
"properties": [
{
"id": "custom.width",
"value": 120
},
{
"id": "custom.align",
"value": "left"
}
]
},
{
"matcher": {
"id": "byType",
"options": "number"
},
"properties": [
{
"id": "unit",
"value": "currencyGBP"
}
]
},
{
"matcher": {
"id": "byName",
"options": "paperless_doc_id"
},
"properties": [
{
"id": "unit",
"value": "none"
},
{
"id": "custom.width",
"value": 100
}
]
}
]
},
"options": {
"cellHeight": "sm",
"footer": {
"show": false
}
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "payslips-pg"
},
"rawQuery": true,
"editorMode": "code",
"format": "table",
"rawSql": "WITH expected AS (SELECT generate_series(DATE '2019-07-01', DATE_TRUNC('month', CURRENT_DATE), '1 month'::interval)::date AS month_start), actual AS (SELECT DATE_TRUNC('month', pay_date)::date AS month_start, pay_date, salary, gross_pay, rsu_vest, cash_income_tax, income_tax, paperless_doc_id FROM payslip_ingest.payslip) SELECT e.month_start, CASE WHEN a.pay_date IS NULL THEN 'MISSING' WHEN a.salary = 0 AND a.gross_pay > 5000 THEN 'ZERO_SALARY' WHEN a.rsu_vest > 0 AND a.salary = 0 THEN 'RSU_NO_SALARY' WHEN a.rsu_vest > 0 AND a.cash_income_tax IS NULL THEN 'NULL_CASH_TAX' ELSE 'ok' END AS status, a.pay_date, a.salary, a.gross_pay, a.rsu_vest, a.cash_income_tax, a.paperless_doc_id FROM expected e LEFT JOIN actual a ON a.month_start = e.month_start WHERE e.month_start >= DATE '2019-07-01' ORDER BY e.month_start DESC"
}
]
},
{
"id": 5,
"title": "All payslips \u2014 detailed breakdown",
"type": "table",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "payslips-pg"
},
"gridPos": {
"h": 14,
"w": 24,
"x": 0,
"y": 44
},
"fieldConfig": {
"defaults": {
"custom": {
"align": "right",
"cellOptions": {
"type": "auto"
},
"inspect": true
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "pay_date"
},
"properties": [
{
"id": "custom.width",
"value": 110
},
{
"id": "custom.align",
"value": "left"
}
]
},
{
"matcher": {
"id": "byName",
"options": "employer"
},
"properties": [
{
"id": "custom.width",
"value": 150
},
{
"id": "custom.align",
"value": "left"
}
]
},
{
"matcher": {
"id": "byName",
"options": "tax_year"
},
"properties": [
{
"id": "custom.width",
"value": 80
}
]
},
{
"matcher": {
"id": "byName",
"options": "validated"
},
"properties": [
{
"id": "custom.width",
"value": 80
},
{
"id": "custom.cellOptions",
"value": {
"type": "color-text"
}
},
{
"id": "mappings",
"value": [
{
"type": "value",
"options": {
"true": {
"color": "green",
"text": "\u2713"
},
"false": {
"color": "red",
"text": "\u26a0"
}
}
}
]
}
]
},
{
"matcher": {
"id": "byType",
"options": "number"
},
"properties": [
{
"id": "unit",
"value": "currencyGBP"
}
]
},
{
"matcher": {
"id": "byName",
"options": "paperless_doc_id"
},
"properties": [
{
"id": "unit",
"value": "none"
},
{
"id": "custom.width",
"value": 80
}
]
}
]
},
"options": {
"footer": {
"show": true,
"reducer": [
"sum"
],
"fields": [
"gross_pay",
"net_pay",
"income_tax",
"national_insurance",
"pension_employee",
"pension_employer",
"student_loan"
]
}
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "payslips-pg"
},
"rawQuery": true,
"editorMode": "code",
"format": "table",
"rawSql": "SELECT pay_date, employer, tax_year, gross_pay, (gross_pay - rsu_vest) AS cash_gross, salary, bonus, rsu_vest, rsu_offset, pension_sacrifice, taxable_pay, income_tax, national_insurance, pension_employee, pension_employer, student_loan, COALESCE(other_deductions, '{}'::jsonb) AS other_deductions, net_pay, validated, paperless_doc_id FROM payslip_ingest.payslip WHERE $__timeFilter(pay_date) ORDER BY pay_date DESC"
}
]
},
{
"id": 6,
"title": "YTD reconciliation \u2014 reported vs computed",
"description": "Reconciles each payslip's reported YTD summary block against the cumulative sum of extracted per-payslip values within the same tax year. Any \u0394 > \u00a30.02 is a parser regression, a missing slip, or a duplicate ingest. Variant A payslips (pre-mid-2022) have no YTD block and are excluded.",
"type": "table",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "payslips-pg"
},
"gridPos": {
"h": 12,
"w": 24,
"x": 0,
"y": 58
},
"fieldConfig": {
"defaults": {
"custom": {
"align": "right",
"cellOptions": {
"type": "auto"
},
"inspect": false
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "pay_date"
},
"properties": [
{
"id": "custom.width",
"value": 110
},
{
"id": "custom.align",
"value": "left"
}
]
},
{
"matcher": {
"id": "byName",
"options": "tax_year"
},
"properties": [
{
"id": "custom.width",
"value": 80
}
]
},
{
"matcher": {
"id": "byName",
"options": "gross_pay"
},
"properties": [
{
"id": "unit",
"value": "currencyGBP"
}
]
},
{
"matcher": {
"id": "byName",
"options": "ytd_gross_reported"
},
"properties": [
{
"id": "unit",
"value": "currencyGBP"
}
]
},
{
"matcher": {
"id": "byName",
"options": "ytd_gross_computed"
},
"properties": [
{
"id": "unit",
"value": "currencyGBP"
}
]
},
{
"matcher": {
"id": "byName",
"options": "delta_gross"
},
"properties": [
{
"id": "unit",
"value": "currencyGBP"
},
{
"id": "custom.cellOptions",
"value": {
"type": "color-background",
"mode": "basic"
}
},
{
"id": "thresholds",
"value": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": null
},
{
"color": "green",
"value": -0.02
},
{
"color": "red",
"value": 0.02
}
]
}
}
]
},
{
"matcher": {
"id": "byName",
"options": "taxable_pay"
},
"properties": [
{
"id": "unit",
"value": "currencyGBP"
}
]
},
{
"matcher": {
"id": "byName",
"options": "ytd_taxable_reported"
},
"properties": [
{
"id": "unit",
"value": "currencyGBP"
}
]
},
{
"matcher": {
"id": "byName",
"options": "ytd_taxable_computed"
},
"properties": [
{
"id": "unit",
"value": "currencyGBP"
}
]
},
{
"matcher": {
"id": "byName",
"options": "delta_taxable"
},
"properties": [
{
"id": "unit",
"value": "currencyGBP"
},
{
"id": "custom.cellOptions",
"value": {
"type": "color-background",
"mode": "basic"
}
},
{
"id": "thresholds",
"value": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": null
},
{
"color": "green",
"value": -0.02
},
{
"color": "red",
"value": 0.02
}
]
}
}
]
},
{
"matcher": {
"id": "byName",
"options": "income_tax"
},
"properties": [
{
"id": "unit",
"value": "currencyGBP"
}
]
},
{
"matcher": {
"id": "byName",
"options": "ytd_tax_reported"
},
"properties": [
{
"id": "unit",
"value": "currencyGBP"
}
]
},
{
"matcher": {
"id": "byName",
"options": "ytd_tax_computed"
},
"properties": [
{
"id": "unit",
"value": "currencyGBP"
}
]
},
{
"matcher": {
"id": "byName",
"options": "delta_tax"
},
"properties": [
{
"id": "unit",
"value": "currencyGBP"
},
{
"id": "custom.cellOptions",
"value": {
"type": "color-background",
"mode": "basic"
}
},
{
"id": "thresholds",
"value": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": null
},
{
"color": "green",
"value": -0.02
},
{
"color": "red",
"value": 0.02
}
]
}
}
]
}
]
},
"options": {
"cellHeight": "sm",
"footer": {
"show": false
}
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "payslips-pg"
},
"rawQuery": true,
"editorMode": "code",
"format": "table",
"rawSql": "SELECT pay_date, tax_year, gross_pay, ytd_gross AS ytd_gross_reported, SUM(gross_pay) OVER w AS ytd_gross_computed, (ytd_gross - SUM(gross_pay) OVER w) AS delta_gross, taxable_pay, ytd_taxable_pay AS ytd_taxable_reported, SUM(taxable_pay) OVER w AS ytd_taxable_computed, (ytd_taxable_pay - SUM(taxable_pay) OVER w) AS delta_taxable, income_tax, ytd_tax_paid AS ytd_tax_reported, SUM(income_tax) OVER w AS ytd_tax_computed, (ytd_tax_paid - SUM(income_tax) OVER w) AS delta_tax FROM payslip_ingest.payslip WHERE $__timeFilter(pay_date) AND ytd_gross IS NOT NULL WINDOW w AS (PARTITION BY tax_year ORDER BY pay_date) ORDER BY pay_date DESC"
}
]
},
{
"id": 8,
"title": "Sankey \u2014 where the money went",
"description": "Income sources flow into gross, then out to deductions and take-home. Aggregated over the selected time range (use the time picker to scope to a single tax year).",
"type": "netsage-sankey-panel",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "payslips-pg"
},
"gridPos": {
"h": 14,
"w": 24,
"x": 0,
"y": 70
},
"options": {
"monochrome": false,
"monochromeColor": "#7294d4",
"showHeader": true,
"displayValues": "show",
"valueFormat": "currencyGBP",
"nodeWidth": 20,
"nodePadding": 15
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "payslips-pg"
},
"rawQuery": true,
"editorMode": "code",
"format": "table",
"rawSql": "WITH r AS (SELECT * FROM payslip_ingest.payslip WHERE $__timeFilter(pay_date)), 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), agg AS (SELECT COALESCE(SUM(salary), 0) AS salary, COALESCE(SUM(bonus), 0) AS bonus, COALESCE(SUM(rsu_vest), 0) AS rsu_vest, COALESCE(SUM(GREATEST(gross_pay - salary - bonus - rsu_vest, 0)), 0) AS other_income, COALESCE(SUM(net_pay), 0) AS net_pay, COALESCE(SUM(GREATEST(0, income_tax - rsu_paye_marginal)), 0) AS cash_income_tax, COALESCE(SUM(rsu_paye_marginal + rsu_ni_marginal), 0) AS rsu_tax_marginal, COALESCE(SUM(GREATEST(0, national_insurance - rsu_ni_marginal)), 0) AS cash_ni, COALESCE(SUM(pension_employee), 0) AS pension, COALESCE(SUM(student_loan), 0) AS student_loan, COALESCE(SUM(rsu_offset), 0) AS rsu_offset FROM m) SELECT 'Salary' AS source, 'Gross' AS target, salary AS value FROM agg WHERE salary > 0 UNION ALL SELECT 'Bonus', 'Gross', bonus FROM agg WHERE bonus > 0 UNION ALL SELECT 'RSU', 'Gross', rsu_vest FROM agg WHERE rsu_vest > 0 UNION ALL SELECT 'Other income', 'Gross', other_income FROM agg WHERE other_income > 0 UNION ALL SELECT 'Gross', 'Net pay', net_pay FROM agg WHERE net_pay > 0 UNION ALL SELECT 'Gross', 'Income Tax (cash)', cash_income_tax FROM agg WHERE cash_income_tax > 0 UNION ALL SELECT 'Gross', 'Tax on RSU vest', rsu_tax_marginal FROM agg WHERE rsu_tax_marginal > 0 UNION ALL SELECT 'Gross', 'National Insurance (cash)', cash_ni FROM agg WHERE cash_ni > 0 UNION ALL SELECT 'Gross', 'Pension', pension FROM agg WHERE pension > 0 UNION ALL SELECT 'Gross', 'Student Loan', student_loan FROM agg WHERE student_loan > 0 UNION ALL SELECT 'Gross', 'RSU Offset', rsu_offset FROM agg WHERE rsu_offset > 0"
}
]
},
{
"id": 9,
"title": "P60 reconciliation \u2014 HMRC annual vs summed payslips",
"description": "Per-tax-year comparison of the figures HMRC printed on the P60 vs what we summed from individual payslips. Delta columns: |\u0394|<1 green (exact match), 1-50 yellow (rounding), >50 red (missing month or parser drift). Always shows all years \u2014 ignores the time picker.",
"type": "table",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "payslips-pg"
},
"gridPos": {
"h": 10,
"w": 24,
"x": 0,
"y": 84
},
"fieldConfig": {
"defaults": {
"unit": "currencyGBP",
"custom": {
"align": "right",
"displayMode": "auto"
}
},
"overrides": [
{
"matcher": {
"id": "byRegexp",
"options": "^delta_"
},
"properties": [
{
"id": "custom.displayMode",
"value": "color-background"
},
{
"id": "custom.cellOptions",
"value": {
"type": "color-background",
"mode": "gradient"
}
},
{
"id": "thresholds",
"value": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "green",
"value": -1
},
{
"color": "yellow",
"value": 1
},
{
"color": "red",
"value": 50
},
{
"color": "red",
"value": -50
}
]
}
}
]
},
{
"matcher": {
"id": "byName",
"options": "tax_year"
},
"properties": [
{
"id": "unit",
"value": "string"
},
{
"id": "custom.align",
"value": "left"
}
]
},
{
"matcher": {
"id": "byName",
"options": "employer"
},
"properties": [
{
"id": "unit",
"value": "string"
},
{
"id": "custom.align",
"value": "left"
}
]
},
{
"matcher": {
"id": "byName",
"options": "tax_code"
},
"properties": [
{
"id": "unit",
"value": "string"
},
{
"id": "custom.align",
"value": "left"
}
]
},
{
"matcher": {
"id": "byName",
"options": "paperless_doc_id"
},
"properties": [
{
"id": "unit",
"value": "none"
},
{
"id": "custom.align",
"value": "left"
}
]
}
]
},
"options": {
"showHeader": true,
"cellHeight": "sm",
"footer": {
"show": false
}
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "payslips-pg"
},
"rawQuery": true,
"editorMode": "code",
"format": "table",
"rawSql": "SELECT p.tax_year, p.employer, p.gross_pay AS p60_gross, c.sum_gross AS computed_gross, (p.gross_pay - c.sum_gross) AS delta_gross, p.income_tax AS p60_tax, c.sum_tax AS computed_tax, (p.income_tax - c.sum_tax) AS delta_tax, p.national_insurance AS p60_ni, c.sum_ni AS computed_ni, (p.national_insurance - c.sum_ni) AS delta_ni, p.tax_code, p.paperless_doc_id FROM payslip_ingest.p60_reference p LEFT JOIN LATERAL (SELECT COALESCE(SUM(gross_pay), 0) AS sum_gross, COALESCE(SUM(income_tax), 0) AS sum_tax, COALESCE(SUM(national_insurance), 0) AS sum_ni FROM payslip_ingest.payslip WHERE tax_year = p.tax_year) c ON true ORDER BY p.tax_year DESC"
}
]
},
{
"id": 10,
"title": "HMRC Tax Year Reconciliation \u2014 Individual Tax API",
"description": "Latest snapshot from HMRC Individual Tax API v1.1 vs SUM(payslip.income_tax) per tax year. Delta > \u00a310 turns red \u2014 that's parser drift vs HMRC's held figures, the authoritative ground truth. Shown only for years where hmrc-sync has pulled a snapshot.",
"type": "table",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "payslips-pg"
},
"gridPos": {
"h": 10,
"w": 24,
"x": 0,
"y": 94
},
"fieldConfig": {
"defaults": {
"unit": "currencyGBP",
"custom": {
"align": "right",
"displayMode": "auto"
}
},
"overrides": [
{
"matcher": {
"id": "byRegexp",
"options": "^delta_"
},
"properties": [
{
"id": "custom.displayMode",
"value": "color-background"
},
{
"id": "thresholds",
"value": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "green",
"value": -10
},
{
"color": "red",
"value": 10
},
{
"color": "red",
"value": -10
}
]
}
}
]
},
{
"matcher": {
"id": "byName",
"options": "tax_year"
},
"properties": [
{
"id": "unit",
"value": "string"
},
{
"id": "custom.align",
"value": "left"
}
]
},
{
"matcher": {
"id": "byName",
"options": "employer_paye_ref"
},
"properties": [
{
"id": "unit",
"value": "string"
},
{
"id": "custom.align",
"value": "left"
}
]
},
{
"matcher": {
"id": "byName",
"options": "snapshot_date"
},
"properties": [
{
"id": "unit",
"value": "dateTimeAsIso"
}
]
}
]
},
"options": {
"showHeader": true,
"cellHeight": "sm",
"footer": {
"show": false
}
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "payslips-pg"
},
"rawQuery": true,
"editorMode": "code",
"format": "table",
"rawSql": "WITH latest AS (SELECT DISTINCT ON (tax_year, employer_paye_ref) tax_year, employer_paye_ref, snapshot_date, gross_pay, income_tax, ni_contributions FROM hmrc_sync.tax_year_snapshot ORDER BY tax_year, employer_paye_ref, snapshot_date DESC), summed AS (SELECT tax_year, COALESCE(SUM(gross_pay), 0) AS sum_gross, COALESCE(SUM(income_tax), 0) AS sum_tax, COALESCE(SUM(national_insurance), 0) AS sum_ni FROM payslip_ingest.payslip GROUP BY tax_year) SELECT l.tax_year, l.employer_paye_ref, l.snapshot_date, l.gross_pay AS hmrc_gross, s.sum_gross AS computed_gross, (l.gross_pay - s.sum_gross) AS delta_gross, l.income_tax AS hmrc_tax, s.sum_tax AS computed_tax, (l.income_tax - s.sum_tax) AS delta_tax, l.ni_contributions AS hmrc_ni, s.sum_ni AS computed_ni, (l.ni_contributions - s.sum_ni) AS delta_ni FROM latest l LEFT JOIN summed s ON s.tax_year = l.tax_year ORDER BY l.tax_year DESC"
}
]
},
{
"id": 14,
"title": "Meta payroll: bank deposit vs payslip net pay",
"description": "Cross-check between ActualBudget bank deposits (META/FACEBOOK payee) and payslip net_pay. |delta| > \u00a350 flags likely parser or bank-sync drift. Synced daily 02:00 UTC.",
"type": "timeseries",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "payslips-pg"
},
"gridPos": {
"h": 9,
"w": 24,
"x": 0,
"y": 160
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"unit": "currencyGBP",
"custom": {
"axisPlacement": "auto",
"drawStyle": "line",
"fillOpacity": 0,
"lineWidth": 2,
"pointSize": 6,
"showPoints": "always",
"spanNulls": false,
"thresholdsStyle": {
"mode": "off"
}
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "deposit_sum"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "green"
}
},
{
"id": "displayName",
"value": "Bank deposit (ActualBudget)"
}
]
},
{
"matcher": {
"id": "byName",
"options": "payslip_net_pay"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "blue"
}
},
{
"id": "displayName",
"value": "Payslip net pay"
}
]
},
{
"matcher": {
"id": "byName",
"options": "delta"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "red"
}
},
{
"id": "displayName",
"value": "Delta (deposit \u2212 payslip)"
},
{
"id": "custom.drawStyle",
"value": "bars"
}
]
}
]
},
"options": {
"legend": {
"calcs": [
"last"
],
"displayMode": "table",
"placement": "bottom"
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"targets": [
{
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "payslips-pg"
},
"rawSql": "WITH deposits AS (SELECT DATE_TRUNC('month', deposit_date)::date AS month_start, SUM(amount) AS deposit_sum FROM payslip_ingest.external_meta_deposits GROUP BY 1), payslip_net AS (SELECT DATE_TRUNC('month', pay_date)::date AS month_start, SUM(net_pay) AS payslip_net_pay FROM payslip_ingest.payslip GROUP BY 1) SELECT COALESCE(p.month_start, d.month_start) AS \"time\", d.deposit_sum, p.payslip_net_pay, COALESCE(d.deposit_sum, 0) - COALESCE(p.payslip_net_pay, 0) AS delta FROM deposits d FULL OUTER JOIN payslip_net p ON p.month_start = d.month_start WHERE $__timeFilter(COALESCE(p.month_start, d.month_start)) ORDER BY \"time\"",
"format": "time_series",
"refId": "A",
"rawQuery": true,
"editorMode": "code"
}
]
},
{
"id": 15,
"title": "RSU vest reconciliation \u2014 payslip vs Schwab",
"description": "Per-vest-month join between payslip.rsu_vest (what HMRC reporting shows) and Schwab's vest-confirmation email data (what actually happened at the broker). Empty rows until the broker-sync IMAP ingest runs and VestEvents are persisted.",
"type": "table",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "payslips-pg"
},
"gridPos": {
"h": 10,
"w": 24,
"x": 0,
"y": 169
},
"fieldConfig": {
"defaults": {
"custom": {
"align": "right",
"cellOptions": {
"type": "auto"
},
"inspect": false
},
"unit": "currencyGBP",
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "vest_month"
},
"properties": [
{
"id": "custom.width",
"value": 140
},
{
"id": "custom.align",
"value": "left"
},
{
"id": "unit",
"value": "none"
}
]
},
{
"matcher": {
"id": "byName",
"options": "ticker"
},
"properties": [
{
"id": "custom.width",
"value": 80
},
{
"id": "unit",
"value": "none"
}
]
},
{
"matcher": {
"id": "byName",
"options": "shares_vested"
},
"properties": [
{
"id": "unit",
"value": "none"
}
]
},
{
"matcher": {
"id": "byName",
"options": "tax_delta_pct"
},
"properties": [
{
"id": "unit",
"value": "percent"
},
{
"id": "custom.cellOptions",
"value": {
"type": "color-background",
"mode": "basic"
}
},
{
"id": "thresholds",
"value": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "orange",
"value": 2
},
{
"color": "red",
"value": 5
}
]
}
}
]
}
]
},
"options": {
"cellHeight": "sm",
"footer": {
"show": false
}
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "payslips-pg"
},
"rawQuery": true,
"editorMode": "code",
"format": "table",
"rawSql": "WITH vest_by_month AS (SELECT DATE_TRUNC('month', vest_date)::date AS vest_month, ticker, SUM(shares_vested) AS shares_vested, SUM(gross_value_gbp) AS broker_gross_gbp, SUM(tax_withheld_gbp) AS broker_tax_gbp FROM payslip_ingest.rsu_vest_events GROUP BY 1, 2), payslip_by_month AS (SELECT DATE_TRUNC('month', pay_date)::date AS vest_month, SUM(rsu_vest) AS payslip_rsu_gbp, SUM(income_tax - COALESCE(cash_income_tax, income_tax)) AS payslip_rsu_tax_gbp FROM payslip_ingest.payslip WHERE rsu_vest > 0 GROUP BY 1) SELECT COALESCE(v.vest_month, p.vest_month) AS vest_month, v.ticker, v.shares_vested, v.broker_gross_gbp, p.payslip_rsu_gbp, (p.payslip_rsu_gbp - v.broker_gross_gbp) AS gross_delta_gbp, v.broker_tax_gbp, p.payslip_rsu_tax_gbp, (p.payslip_rsu_tax_gbp - v.broker_tax_gbp) AS tax_delta_gbp, CASE WHEN v.broker_tax_gbp IS NULL OR v.broker_tax_gbp = 0 THEN NULL ELSE ABS(p.payslip_rsu_tax_gbp - v.broker_tax_gbp) * 100.0 / v.broker_tax_gbp END AS tax_delta_pct FROM vest_by_month v FULL OUTER JOIN payslip_by_month p ON p.vest_month = v.vest_month ORDER BY vest_month DESC"
}
]
},
{
"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.",
"type": "barchart",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "payslips-pg"
},
"gridPos": {
"h": 11,
"w": 24,
"x": 0,
"y": 179
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"unit": "currencyGBP",
"custom": {
"axisPlacement": "auto",
"axisLabel": "",
"axisCenteredZero": false,
"fillOpacity": 80,
"gradientMode": "none",
"lineWidth": 1,
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"thresholdsStyle": {
"mode": "off"
}
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "net_pay"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "green"
}
},
{
"id": "displayName",
"value": "Net pay (bank deposit)"
}
]
},
{
"matcher": {
"id": "byName",
"options": "cash_income_tax"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "#C4162A"
}
},
{
"id": "displayName",
"value": "Income Tax (cash)"
}
]
},
{
"matcher": {
"id": "byName",
"options": "rsu_tax_marginal"
},
"properties": [
{
"id": "color",
"value": {
"mode": "fixed",
"fixedColor": "#E0652E"
}
},
{
"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)"
}
]
}
]
},
"options": {
"barRadius": 0,
"barWidth": 0.6,
"groupWidth": 0.7,
"orientation": "auto",
"showValue": "auto",
"stacking": "normal",
"xField": "tax_year",
"xTickLabelRotation": 0,
"xTickLabelSpacing": 0,
"legend": {
"calcs": [
"sum"
],
"displayMode": "table",
"placement": "bottom"
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "payslips-pg"
},
"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"
}
]
},
{
"id": 17,
"title": "YTD gross salary — year-over-year comparison",
"description": "Cumulative gross pay built up month by month within each UK tax year (April → March). One line per tax year. Pay dates are projected onto a sliding 12-month window ending now, so years overlay cleanly without falling outside the dashboard's time range. X-axis shows month-of-tax-year (April first, March last).",
"type": "timeseries",
"timeFrom": "13M",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "payslips-pg"
},
"gridPos": {
"h": 11,
"w": 24,
"x": 0,
"y": 190
},
"fieldConfig": {
"defaults": {
"color": {"mode": "palette-classic"},
"unit": "currencyGBP",
"decimals": 0,
"custom": {
"drawStyle": "line",
"lineWidth": 2,
"fillOpacity": 0,
"pointSize": 5,
"showPoints": "auto",
"spanNulls": true,
"axisPlacement": "auto",
"stacking": {"group": "A", "mode": "none"}
}
},
"overrides": []
},
"options": {
"legend": {"calcs": ["last", "max"], "displayMode": "table", "placement": "bottom"},
"tooltip": {"mode": "multi", "sort": "desc"}
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "grafana-postgresql-datasource",
"uid": "payslips-pg"
},
"rawQuery": true,
"editorMode": "code",
"format": "time_series",
"rawSql": "WITH projected AS (SELECT ((CURRENT_DATE - INTERVAL '12 months')::date + (pay_date - MAKE_DATE(SUBSTRING(tax_year, 1, 4)::int, 4, 6)))::timestamp AS t, tax_year, SUM(gross_pay) OVER (PARTITION BY tax_year ORDER BY pay_date) AS ytd FROM payslip_ingest.payslip) SELECT t AS \"time\", tax_year AS metric, ytd AS ytd_gross FROM projected ORDER BY t, tax_year"
}
]
}
],
"refresh": "5m",
"schemaVersion": 39,
"tags": [
"finance",
"personal",
"uk-tax"
],
"templating": {
"list": []
},
"time": {
"from": "now-10y",
"to": "now"
},
"timepicker": {},
"timezone": "browser",
"title": "UK Payslip",
"uid": "uk-payslip",
"version": 1
}