From 4f4c5b7c219869307f52c53785159ed0b3bb1153 Mon Sep 17 00:00:00 2001 From: Marcus Lima Date: Wed, 8 Apr 2026 17:23:41 -0600 Subject: [PATCH] Add UK Self Assessment tax return skill Claude Code skill for preparing UK Self Assessment tax returns. Reads tax documents, extracts income data, calculates tax liability, and produces SA return summaries with HMRC box references. Co-Authored-By: Claude Opus 4.6 --- skills/uk-self-assessment/LICENSE.txt | 190 +++++ skills/uk-self-assessment/SKILL.md | 329 +++++++++ .../references/forms-guide.md | 165 +++++ .../references/tax-rates-2024-25.md | 141 ++++ .../references/tax-rates-2025-26.md | 139 ++++ .../scripts/tax-calculator.py | 678 ++++++++++++++++++ 6 files changed, 1642 insertions(+) create mode 100644 skills/uk-self-assessment/LICENSE.txt create mode 100644 skills/uk-self-assessment/SKILL.md create mode 100644 skills/uk-self-assessment/references/forms-guide.md create mode 100644 skills/uk-self-assessment/references/tax-rates-2024-25.md create mode 100644 skills/uk-self-assessment/references/tax-rates-2025-26.md create mode 100644 skills/uk-self-assessment/scripts/tax-calculator.py diff --git a/skills/uk-self-assessment/LICENSE.txt b/skills/uk-self-assessment/LICENSE.txt new file mode 100644 index 00000000..89376b8f --- /dev/null +++ b/skills/uk-self-assessment/LICENSE.txt @@ -0,0 +1,190 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to the Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by the Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding any notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2025 Marcus Lima + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/skills/uk-self-assessment/SKILL.md b/skills/uk-self-assessment/SKILL.md new file mode 100644 index 00000000..bddc7864 --- /dev/null +++ b/skills/uk-self-assessment/SKILL.md @@ -0,0 +1,329 @@ +--- +name: uk-self-assessment +description: | + UK Self Assessment tax return preparation. Reads tax documents from your + repository (P60s, bank statements, dividend vouchers, rental accounts, etc.), + extracts income data, asks clarifying questions about personal circumstances, + calculates income tax / NIC / student loans / CGT, and produces a complete + return summary with SA form box references ready for HMRC filing. + Use when asked to "file UK taxes", "self assessment", "UK tax return", + "calculate UK tax", or "prepare SA100". +allowed-tools: + - Read + - Glob + - Grep + - Bash + - Write + - Edit + - AskUserQuestion + - Agent +--- + +# /uk-self-assessment — UK Self Assessment Tax Return + +You are a UK tax preparation assistant. Your job is to read the user's tax +documents from their repository, extract all relevant income and deduction +data, ask clarifying questions where needed, calculate their tax liability, +and produce a complete Self Assessment return summary. + +**IMPORTANT DISCLAIMER:** Always include this at the start of your interaction: +> This skill provides tax calculations for informational purposes only. +> It is not a substitute for professional tax advice. You should verify +> all figures before submitting your return to HMRC. Tax legislation is +> complex and individual circumstances vary. + +## Setup + +First, read the reference files to load the current tax rates and forms guide: + +```bash +SKILL_DIR="${CLAUDE_SKILL_DIR:-.}" +echo "Skill directory: $SKILL_DIR" +ls "$SKILL_DIR/references/" 2>/dev/null || echo "References not found at SKILL_DIR, checking alternatives..." +``` + +Read these files at the start of every session: +- `references/tax-rates-2024-25.md` — All rates for 2024/25 +- `references/tax-rates-2025-26.md` — All rates for 2025/26 +- `references/forms-guide.md` — SA form box references + +## Phase 1: Tax Year Selection + +Use **AskUserQuestion** to determine which tax year: + +``` +Which tax year are you preparing your Self Assessment for? +- 2024/25 (6 April 2024 – 5 April 2025) — Online deadline: 31 January 2026 +- 2025/26 (6 April 2025 – 5 April 2026) — Online deadline: 31 January 2027 +``` + +Load the corresponding tax rates reference file. + +## Phase 2: Document Discovery + +Scan the repository for tax-relevant documents. Use Glob to search for: + +``` +**/*.pdf +**/*.png **/*.jpg **/*.jpeg **/*.heic +**/*.csv **/*.xlsx **/*.xls +**/*.txt **/*.md +``` + +Classify found documents by looking at filenames and content for keywords: +- **P60 / P45**: employment, gross pay, tax deducted, PAYE reference +- **P11D**: benefits in kind, company car, medical insurance +- **Bank statements**: interest earned, annual summary +- **Dividend vouchers**: dividend, shares, per share +- **Rental records**: rent received, tenant, property expenses, mortgage interest +- **Capital gains**: disposal, acquisition, shares sold, CGT, crypto +- **Pension**: contribution statement, pension provider +- **Student loan**: SLC, student loan, repayment +- **HMRC letters**: tax code, SA302, coding notice +- **Self-employment**: invoice, turnover, business expenses, profit and loss + +Present a summary of all found documents grouped by type to the user via +**AskUserQuestion**: + +``` +I found the following tax documents in your repository: + +📋 Employment: + - documents/p60-2024-25.pdf + - documents/p11d.pdf + +🏦 Banking: + - statements/hsbc-annual-summary.pdf + - statements/savings-interest.csv + +💰 Investments: + - trading/dividend-vouchers.pdf + - trading/share-sales-2024.csv + +Are there any missing documents or additional income sources I should know about? +``` + +Options should include: "These are all my documents", "I have additional documents to add", +"I also have income not covered by documents (tell me about it)". + +## Phase 3: Document Reading & Data Extraction + +Read each document using the **Read** tool. The Read tool supports: +- **PDFs**: Use `Read` with the file path. For large PDFs, use the `pages` parameter. +- **Images**: Read tool displays images visually — extract numbers from P60 photos, screenshots, etc. +- **CSVs/spreadsheets**: Read directly and parse the tabular data. +- **Text/Markdown**: Read and extract relevant figures. + +For each document, extract: +1. **Amounts** — gross pay, tax deducted, interest earned, dividends, etc. +2. **Dates** — to confirm they fall within the tax year +3. **References** — employer PAYE reference, UTR, bank account details +4. **Tax already paid** — PAYE deducted, tax on savings (if any) + +**Cross-reference** documents where possible: +- P60 gross pay should be consistent with payslips +- Bank interest should match annual summaries +- Share sale proceeds should match trading platform reports + +Use **AskUserQuestion** for any ambiguities: +- Unclear or partially visible figures +- Multiple possible interpretations +- Figures that seem inconsistent across documents +- Whether an amount is gross or net + +## Phase 4: Personal Circumstances + +Ask questions about things that CANNOT be determined from documents. Batch related +questions together using **AskUserQuestion** with multiple questions where possible. + +**Essential questions (always ask):** +1. Marital status / civil partnership — for Marriage Allowance eligibility +2. Student loan plan type(s) — Plan 1, 2, 4, 5, or Postgraduate (or none) + +**Conditional questions (ask if relevant based on income level or documents found):** +3. If income > £60,000: Do you receive Child Benefit? How many children? +4. If income > £100,000: Confirm adjusted net income for PA taper +5. If married/civil partnership and one partner is a non-taxpayer: Transfer Marriage Allowance? +6. Blind Person's Allowance? +7. Any Gift Aid donations made during the tax year? +8. Previous year's Self Assessment liability? (needed for Payment on Account calculation) +9. If pension documents found: Confirm personal contribution amounts for higher rate relief + +**Skip questions that are clearly answered by the documents.** For example, if the P60 +shows student loan deductions under Plan 2, don't ask about the plan type — just confirm. + +## Phase 5: Income Categorisation + +Organise all extracted data into Self Assessment categories. Map each piece of data +to the correct SA form and box number using `references/forms-guide.md`. + +Present a categorised summary to the user via **AskUserQuestion**: + +``` +Here's what I've extracted from your documents. Please confirm these figures: + +📋 EMPLOYMENT (SA102) + Employer: Acme Corp (PAYE: 123/A456) + Gross pay: £65,000.00 + Tax deducted: £13,432.00 + Benefits (P11D): £2,500.00 + +🏦 SAVINGS (SA100 Box 1) + Bank interest: £850.00 + +💰 DIVIDENDS (SA100 Box 4) + UK dividends: £3,200.00 + +🏠 RENTAL INCOME (SA105) + Gross rents: £12,000.00 + Allowable expenses: £3,500.00 + Mortgage interest: £4,000.00 (20% tax reducer) + Rental profit: £8,500.00 + +Are these figures correct? +``` + +Options: "All correct", "I need to correct some figures (tell me which)". + +## Phase 6: Allowances & Reliefs + +Based on the categorised income, calculate all applicable allowances. Read the +relevant tax rates reference file and apply: + +1. **Personal Allowance** — £12,570 (with taper if income > £100,000) +2. **Dividend Allowance** — £500 at 0% +3. **Personal Savings Allowance** — £1,000 (basic) / £500 (higher) / £0 (additional) +4. **Starting Rate for Savings** — 0% on up to £5,000 if applicable +5. **CGT Annual Exempt Amount** — £3,000 +6. **Marriage Allowance** — £1,260 transferred +7. **Pension Tax Relief** — Higher/additional rate relief on personal contributions +8. **Gift Aid** — Extends basic rate band by gross amount +9. **Trading Allowance** — £1,000 if claiming for small self-employment +10. **Property Allowance** — £1,000 if claiming instead of actual expenses + +## Phase 7: Tax Calculation + +Build a JSON input object with all the gathered data and run the tax calculator: + +```bash +cat << 'TAXINPUT' | python3 "${CLAUDE_SKILL_DIR}/scripts/tax-calculator.py" +{ + "tax_year": "2024-25", + "employment": [ + {"gross_pay": 65000, "tax_deducted": 13432, "benefits": 2500} + ], + "savings_interest": 850, + "dividends": 3200, + "rental": {"profit": 8500, "finance_costs": 4000}, + "pension": {"personal_gross": 0, "employer": 0}, + "student_loan_plans": ["plan2"], + "child_benefit": {"num_children": 0}, + "gift_aid": 0, + "blind_persons_allowance": false, + "marriage_allowance_received": false, + "marriage_allowance_given": false, + "capital_gains": [], + "other_income": 0, + "other_tax_paid": 0, + "previous_year_sa_liability": 0 +} +TAXINPUT +``` + +**Replace the example values with the actual extracted data.** + +Parse the JSON output and present the calculation to the user. Use **AskUserQuestion** +to check if anything looks wrong: + +``` +Here's your tax calculation. Does anything look incorrect? +- All looks correct, proceed +- Something doesn't look right (tell me what) +``` + +## Phase 8: Payment on Account + +From the calculator output, check the `payment_on_account` section. If POA is required, +explain to the user: + +- How much each payment is +- When payments are due (31 January and 31 July) +- That they can apply to reduce POA if they expect lower income next year + +## Phase 9: Return Output + +Write two files to the repository: + +### `tax-return-summary.md` +A complete return summary with: +- Tax year and relevant deadlines +- SA forms needed (SA100, SA102, SA105, etc.) +- Box-by-box values for each form +- Payment schedule + +### `tax-calculation-breakdown.md` +A detailed calculation showing: +- Income breakdown by category +- Personal allowance calculation (including taper if applicable) +- Income tax on each income type (non-savings, savings, dividends) +- NIC calculation +- Student loan repayments +- CGT calculation +- Child Benefit Charge +- Tax reducers (finance costs, marriage allowance) +- Total liability +- Tax already paid +- Amount owed or refund due +- Payment on account for next year + +## Phase 10: Headline Summary + +After writing the files, print the headline numbers directly to the user: + +``` +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + UK SELF ASSESSMENT {TAX_YEAR} — SUMMARY +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + Total income: £XX,XXX.XX + Personal allowance: £XX,XXX.XX + Total income tax: £XX,XXX.XX + National Insurance: £XX,XXX.XX + Student loan repayments: £XX,XXX.XX + Capital gains tax: £XX,XXX.XX + Child benefit charge: £XX,XXX.XX + ───────────────────────────────────── + TOTAL TAX DUE: £XX,XXX.XX + Tax already paid (PAYE): £XX,XXX.XX + ═════════════════════════════════════ + AMOUNT OWED: £XX,XXX.XX [or REFUND DUE] + + Payment on account (next year): £X,XXX.XX each + ───────────────────────────────────── + TOTAL DUE 31 JANUARY: £XX,XXX.XX + (includes 1st payment on account) + + SA forms needed: SA100, SA102, SA105, SA108 + Files saved: tax-return-summary.md, tax-calculation-breakdown.md + + ⚠ DEADLINE: File online by {DEADLINE_DATE} + ⚠ PAY by {PAYMENT_DATE} +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +``` + +Include any warnings: +- Items flagged for manual verification +- Potential reliefs not claimed (e.g., marriage allowance, pension relief) +- Approaching thresholds (e.g., income near £100k PA taper) +- Missing documents that could affect the calculation + +**REMINDER:** End with the disclaimer: +> These calculations are for guidance only. Please verify all figures before +> submitting to HMRC. Consider seeking professional advice for complex tax situations. + +## Error Handling + +- If no documents are found: Ask the user to describe their income sources and enter figures manually +- If a PDF can't be read: Ask the user to provide the key figures from that document +- If the calculator script fails: Fall back to manual calculation using the tax rates reference files +- If figures seem unreasonable (e.g., negative income, tax > income): Flag and ask for confirmation diff --git a/skills/uk-self-assessment/references/forms-guide.md b/skills/uk-self-assessment/references/forms-guide.md new file mode 100644 index 00000000..6d55a669 --- /dev/null +++ b/skills/uk-self-assessment/references/forms-guide.md @@ -0,0 +1,165 @@ +# UK Self Assessment Forms Guide + +## Main Return: SA100 + +Everyone must complete the SA100. Key sections and box numbers: + +### Income (SA100 boxes) +| Box | Description | +|-----|-------------| +| 1 | Untaxed UK interest (up to £10,000) | +| 2 | Taxed UK interest | +| 3 | Untaxed foreign interest (up to £2,000) | +| 4 | UK dividends | +| 5 | Foreign dividends (up to £300) | +| 6 | UK life insurance policy gains | +| 7 | Total of any other income | + +### Tax Reliefs (SA100 boxes) +| Box | Description | +|-----|-------------| +| 1 | Pension contributions (gross amount for relief at source schemes) | +| 2 | Gift Aid donations | +| 3 | Blind Person's Allowance | + +### Student Loan +| Box | Description | +|-----|-------------| +| 1 | Student loan plan type (1, 2, 4, or 5) | +| 2 | Postgraduate loan | + +### Marriage Allowance +| Box | Description | +|-----|-------------| +| 1 | Transfer £1,260 to spouse/civil partner | +| 2 | Receive £1,260 from spouse/civil partner | + +--- + +## Supplementary Pages + +### SA102 — Employment + +**When needed:** You had employment income during the tax year. +**One form per employer/directorship.** + +| Box | Description | Source Document | +|-----|-------------|----------------| +| 1 | Pay from this employment | P60 box 1 / P45 | +| 2 | Tax deducted | P60 box 2 / P45 | +| 3 | Tips and other payments not on P60 | Payslips/records | +| 5 | Employer's name | P60 | +| 6 | PAYE reference | P60 | +| 9 | Benefits from P11D - total cash equivalent | P11D total | +| 11 | Expenses | Receipts/records | + +### SA103S — Self-Employment (Short) + +**When needed:** Self-employed with turnover below £85,000 and simple accounts. + +| Box | Description | +|-----|-------------| +| 9 | Business name | +| 10 | Business description | +| 14 | Date started (if new) | +| 15 | Date ended (if ceased) | +| 17 | Turnover | +| 20 | Trading allowance claimed (£1,000 max) | +| 24 | Total allowable expenses | +| 27 | Net profit | +| 29 | Adjusted profit for tax | + +### SA103F — Self-Employment (Full) + +**When needed:** Self-employed with turnover of £85,000 or above, or complex accounts. + +Same structure as SA103S but with detailed expense categories (cost of goods sold, premises costs, admin, travel, advertising, legal, bad debts, etc.). + +### SA105 — UK Property + +**When needed:** You receive rental income from UK land or property. + +| Box | Description | +|-----|-------------| +| 1 | Number of properties rented out | +| 5 | Total rents received | +| 7 | Property allowance claimed (£1,000 max) | +| 9-19 | Expenses breakdown (rent/rates, repairs, insurance, management, legal, etc.) | +| 20 | Total allowable expenses | +| 21 | Private use adjustment | +| 24 | Taxable profit | +| 25 | Adjusted profit for tax | +| 38 | Residential finance costs (mortgage interest — 20% tax reducer) | +| 39 | Unused finance costs from earlier years | + +**Note:** From April 2020, mortgage interest is no longer deductible as an expense. Instead, a 20% tax reducer is given on finance costs (box 38). + +### SA106 — Foreign + +**When needed:** You have foreign income or gains, or claim Foreign Tax Credit Relief. + +Key sections: +- Foreign employment income +- Foreign self-employment +- Foreign property income +- Foreign dividends (if over £300) +- Foreign interest (if over £2,000) +- Foreign Tax Credit Relief claims + +### SA107 — Trusts etc. + +**When needed:** You receive income from trusts, settlements, or the estate of a deceased person. + +### SA108 — Capital Gains Summary + +**When needed:** You dispose of assets and your total gains exceed the Annual Exempt Amount (£3,000), OR your total disposal proceeds exceed 4x the annual exempt amount (£12,000), OR you want to claim a loss. + +| Box | Description | +|-----|-------------| +| 3 | Total gains (before losses and annual exempt amount) | +| 4 | Total losses | +| 6 | Gains qualifying for Business Asset Disposal Relief | +| 7 | Gains from residential property | +| 8 | Other gains | +| 9 | Losses brought forward | +| 15 | Annual exempt amount used | +| 20 | CGT due | + +### SA109 — Residence, Remittance Basis etc. + +**When needed:** You were not resident in the UK, or you were a dual resident, or you claim the remittance basis. + +### SA101 — Additional Information + +**When needed:** Covers less common situations: +- Share schemes (Enterprise Management Incentives, Company Share Option Plans, etc.) +- Post-cessation receipts +- Pension charges and unauthorised payments +- Tax avoidance schemes (DOTAS) +- Disguised remuneration +- Other income not covered by other supplementary pages + +### SA110 — Tax Calculation Summary + +Auto-generated when filing online. Shows the complete tax computation. + +--- + +## Document → Form Mapping + +| Document | SA Form | Key Data to Extract | +|----------|---------|-------------------| +| P60 | SA102 | Gross pay, tax deducted, NIC, employer PAYE ref | +| P45 | SA102 | Pay to date, tax to date, leaving date | +| P11D | SA102 | Benefits in kind (car, medical, etc.) | +| Bank interest summary | SA100 | Total interest received | +| Dividend vouchers | SA100 | Gross dividend amounts | +| Share dealing statements | SA108 | Disposal proceeds, acquisition costs, dates | +| Property rental accounts | SA105 | Rents, expenses, mortgage interest | +| Self-employment accounts | SA103S/F | Turnover, expenses, profit | +| Pension statement | SA100 | Contributions paid (for higher rate relief) | +| Student loan statement | SA100 | Plan type, balance (for repayment calc) | +| HMRC coding notice | Reference | Tax code, estimated income, deductions | +| SA302 (prior year) | Reference | Previous year's liability (for payment on account) | +| Gift Aid receipts | SA100 | Total qualifying donations | +| Crypto exchange reports | SA108 | Disposal proceeds, costs per trade | diff --git a/skills/uk-self-assessment/references/tax-rates-2024-25.md b/skills/uk-self-assessment/references/tax-rates-2024-25.md new file mode 100644 index 00000000..e305da6b --- /dev/null +++ b/skills/uk-self-assessment/references/tax-rates-2024-25.md @@ -0,0 +1,141 @@ +# UK Tax Rates and Allowances — 2024/25 + +Tax year: 6 April 2024 to 5 April 2025 + +## Income Tax Bands + +| Band | Taxable Income | Rate | +|------|---------------|------| +| Personal Allowance | £0 – £12,570 | 0% | +| Basic Rate | £12,571 – £50,270 | 20% | +| Higher Rate | £50,271 – £125,140 | 40% | +| Additional Rate | Over £125,140 | 45% | + +**Personal Allowance taper:** Reduced by £1 for every £2 of income above £100,000. Reaches zero at £125,140. This creates an effective 60% marginal rate between £100,000 and £125,140. + +## Dividend Tax Rates + +| Band | Rate | +|------|------| +| Dividend Allowance | £500 at 0% | +| Basic Rate | 8.75% | +| Higher Rate | 33.75% | +| Additional Rate | 39.35% | + +Dividends use up the basic/higher/additional rate bands but are taxed at dividend-specific rates. Dividends are treated as the top slice of income. + +## Savings Income + +| Band | Rate | +|------|------| +| Starting Rate for Savings | 0% on up to £5,000 (reduced by non-savings income above personal allowance) | +| Personal Savings Allowance (basic rate taxpayer) | £1,000 at 0% | +| Personal Savings Allowance (higher rate taxpayer) | £500 at 0% | +| Personal Savings Allowance (additional rate taxpayer) | £0 | + +Savings income is taxed after non-savings income but before dividends. + +## National Insurance Contributions + +### Employee (Class 1) +| Threshold | Amount | +|-----------|--------| +| Primary Threshold | £12,570/year (£242/week) | +| Upper Earnings Limit | £50,270/year (£967/week) | +| Main Rate (PT to UEL) | 8% | +| Additional Rate (above UEL) | 2% | + +### Self-Employed (Class 4) +| Threshold | Amount | +|-----------|--------| +| Lower Profits Limit | £12,570 | +| Upper Profits Limit | £50,270 | +| Main Rate | 6% | +| Additional Rate | 2% | + +### Class 2 NIC +Abolished from 2024/25 (voluntary contributions: £3.45/week for state pension qualification). + +## Student Loan Repayment Thresholds + +| Plan | Annual Threshold | Rate | +|------|-----------------|------| +| Plan 1 | £22,015 | 9% | +| Plan 2 | £27,295 | 9% | +| Plan 4 | £27,660 | 9% | +| Plan 5 | £25,000 | 9% | +| Postgraduate Loan | £21,000 | 6% | + +## Capital Gains Tax + +### Annual Exempt Amount +£3,000 + +### CGT Rates + +**Disposals before 30 October 2024:** +| Asset Type | Basic Rate | Higher Rate | +|-----------|-----------|------------| +| Most assets | 10% | 20% | +| Residential property | 18% | 28% | + +**Disposals from 30 October 2024:** +| Asset Type | Basic Rate | Higher Rate | +|-----------|-----------|------------| +| All assets | 18% | 24% | + +Business Asset Disposal Relief: 10% (lifetime limit £1 million) + +## Key Allowances + +| Allowance | Amount | +|-----------|--------| +| Personal Allowance | £12,570 | +| Marriage Allowance (transferable) | £1,260 | +| Blind Person's Allowance | £2,870 | +| Trading Allowance | £1,000 | +| Property Allowance | £1,000 | +| Dividend Allowance | £500 | +| Personal Savings Allowance (basic) | £1,000 | +| Personal Savings Allowance (higher) | £500 | +| CGT Annual Exempt Amount | £3,000 | +| Pension Annual Allowance | £60,000 | +| ISA Annual Allowance | £20,000 | + +## High Income Child Benefit Charge + +Applies when adjusted net income exceeds £60,000 (2024/25). Charge = 1% of child benefit for every £200 of income over £60,000. Full charge at £80,000. + +**Child Benefit rates 2024/25:** +- Eldest/only child: £25.60/week (£1,331.20/year) +- Each additional child: £16.95/week (£881.40/year) + +## Pension Tax Relief + +| Parameter | Amount | +|-----------|--------| +| Annual Allowance | £60,000 | +| Money Purchase Annual Allowance | £10,000 | +| Tapered Annual Allowance starts | Adjusted income > £260,000 | +| Minimum Tapered Allowance | £10,000 | +| Carry Forward | Previous 3 tax years | + +## Key Deadlines + +| Event | Date | +|-------|------| +| Tax year ends | 5 April 2025 | +| Register for SA | 5 October 2025 | +| Paper return deadline | 31 October 2025 | +| Online return deadline | 31 January 2026 | +| Tax payment due | 31 January 2026 | +| 2nd payment on account | 31 July 2026 | + +## Late Filing Penalties + +| Delay | Penalty | +|-------|---------| +| 1 day late | £100 fixed | +| 3 months late | £10/day for up to 90 days (max £900) | +| 6 months late | 5% of tax due or £300 (whichever greater) | +| 12 months late | Further 5% of tax due or £300 (whichever greater) | diff --git a/skills/uk-self-assessment/references/tax-rates-2025-26.md b/skills/uk-self-assessment/references/tax-rates-2025-26.md new file mode 100644 index 00000000..d6f1dfda --- /dev/null +++ b/skills/uk-self-assessment/references/tax-rates-2025-26.md @@ -0,0 +1,139 @@ +# UK Tax Rates and Allowances — 2025/26 + +Tax year: 6 April 2025 to 5 April 2026 + +## Income Tax Bands + +| Band | Taxable Income | Rate | +|------|---------------|------| +| Personal Allowance | £0 – £12,570 | 0% | +| Basic Rate | £12,571 – £50,270 | 20% | +| Higher Rate | £50,271 – £125,140 | 40% | +| Additional Rate | Over £125,140 | 45% | + +**Personal Allowance taper:** Reduced by £1 for every £2 of income above £100,000. Reaches zero at £125,140. Effective 60% marginal rate between £100,000 and £125,140. + +**Frozen until April 2028** (extended to April 2031 in Budget 2025). + +## Dividend Tax Rates + +| Band | Rate | +|------|------| +| Dividend Allowance | £500 at 0% | +| Basic Rate | 8.75% | +| Higher Rate | 33.75% | +| Additional Rate | 39.35% | + +## Savings Income + +| Band | Rate | +|------|------| +| Starting Rate for Savings | 0% on up to £5,000 (reduced by non-savings income above personal allowance) | +| Personal Savings Allowance (basic rate taxpayer) | £1,000 at 0% | +| Personal Savings Allowance (higher rate taxpayer) | £500 at 0% | +| Personal Savings Allowance (additional rate taxpayer) | £0 | + +## National Insurance Contributions + +### Employee (Class 1) +| Threshold | Amount | +|-----------|--------| +| Primary Threshold | £12,570/year (£242/week) | +| Upper Earnings Limit | £50,270/year (£967/week) | +| Main Rate (PT to UEL) | 8% | +| Additional Rate (above UEL) | 2% | + +### Self-Employed (Class 4) +| Threshold | Amount | +|-----------|--------| +| Lower Profits Limit | £12,570 | +| Upper Profits Limit | £50,270 | +| Main Rate | 6% | +| Additional Rate | 2% | + +### Class 2 NIC +Abolished (voluntary contributions: £3.45/week for state pension qualification). + +### Employer NIC (for reference) +| Parameter | Amount | +|-----------|--------| +| Rate | 15% (up from 13.8%) | +| Secondary Threshold | £5,000/year (down from £9,100) | +| Employment Allowance | £10,500 (up from £5,000) | + +## Student Loan Repayment Thresholds + +| Plan | Annual Threshold | Rate | +|------|-----------------|------| +| Plan 1 | £26,065 | 9% | +| Plan 2 | £28,470 | 9% | +| Plan 4 | £32,745 | 9% | +| Plan 5 | £25,000 | 9% | +| Postgraduate Loan | £21,000 | 6% | + +## Capital Gains Tax + +### Annual Exempt Amount +£3,000 + +### CGT Rates (full year — unified from Budget October 2024) +| Asset Type | Basic Rate | Higher Rate | +|-----------|-----------|------------| +| All assets | 18% | 24% | + +Business Asset Disposal Relief: 14% (lifetime limit £1 million) — rising to 18% from April 2026. + +## Key Allowances + +| Allowance | Amount | +|-----------|--------| +| Personal Allowance | £12,570 | +| Marriage Allowance (transferable) | £1,260 | +| Blind Person's Allowance | £3,070 | +| Trading Allowance | £1,000 | +| Property Allowance | £1,000 | +| Dividend Allowance | £500 | +| Personal Savings Allowance (basic) | £1,000 | +| Personal Savings Allowance (higher) | £500 | +| CGT Annual Exempt Amount | £3,000 | +| Pension Annual Allowance | £60,000 | +| ISA Annual Allowance | £20,000 | + +## High Income Child Benefit Charge + +Applies when adjusted net income exceeds £60,000. Charge = 1% of child benefit for every £200 of income over £60,000. Full charge at £80,000. + +**Child Benefit rates 2025/26:** +- Eldest/only child: £26.05/week (£1,354.60/year) +- Each additional child: £17.25/week (£897.00/year) + +## Pension Tax Relief + +| Parameter | Amount | +|-----------|--------| +| Annual Allowance | £60,000 | +| Money Purchase Annual Allowance | £10,000 | +| Tapered Annual Allowance starts | Adjusted income > £260,000 | +| Minimum Tapered Allowance | £10,000 | +| Carry Forward | Previous 3 tax years | + +## Key Changes from 2024/25 + +1. **Employer NIC:** Rate up from 13.8% to 15%; secondary threshold down from £9,100 to £5,000 +2. **Employment Allowance:** Up from £5,000 to £10,500 (£100k cap removed) +3. **CGT rates unified:** 18%/24% applies for full year (no split-year calculation) +4. **Business Asset Disposal Relief:** Rate increases to 14% (was 10%) +5. **Furnished Holiday Lettings:** Special tax treatment abolished +6. **Non-dom regime:** Abolished, replaced with residence-based system +7. **MTD for Income Tax:** Launches April 2026 for self-employed/landlords with income > £50,000 + +## Key Deadlines + +| Event | Date | +|-------|------| +| Tax year ends | 5 April 2026 | +| Register for SA | 5 October 2026 | +| Paper return deadline | 31 October 2026 | +| Online return deadline | 31 January 2027 | +| Tax payment due | 31 January 2027 | +| 2nd payment on account | 31 July 2027 | diff --git a/skills/uk-self-assessment/scripts/tax-calculator.py b/skills/uk-self-assessment/scripts/tax-calculator.py new file mode 100644 index 00000000..5cefc18e --- /dev/null +++ b/skills/uk-self-assessment/scripts/tax-calculator.py @@ -0,0 +1,678 @@ +#!/usr/bin/env python3 +""" +UK Self Assessment Tax Calculator + +Accepts JSON input with income/deduction data via stdin or file argument. +Calculates income tax, NIC, student loans, CGT, and outputs JSON breakdown. + +Usage: + echo '{"tax_year": "2024-25", "employment": [{"gross_pay": 45000, "tax_deducted": 6486}]}' | python3 tax-calculator.py + python3 tax-calculator.py input.json +""" + +import json +import sys +from dataclasses import dataclass, field +from typing import Optional + +# ─── Tax rates by year ──────────────────────────────────────────────────────── + +TAX_RATES = { + "2024-25": { + "personal_allowance": 12570, + "pa_taper_threshold": 100000, + "basic_rate_limit": 37700, # 12571 to 50270 + "higher_rate_limit": 87440, # 50271 to 125140 (125140 - 37700 = 87440 taxable) + "basic_rate": 0.20, + "higher_rate": 0.40, + "additional_rate": 0.45, + "basic_rate_ceiling": 50270, # PA + basic_rate_limit + "additional_rate_floor": 125140, + "dividend_allowance": 500, + "dividend_basic": 0.0875, + "dividend_higher": 0.3375, + "dividend_additional": 0.3935, + "savings_starting_rate_limit": 5000, + "psa_basic": 1000, + "psa_higher": 500, + "psa_additional": 0, + "marriage_allowance": 1260, + "blind_persons_allowance": 2870, + "trading_allowance": 1000, + "property_allowance": 1000, + # NIC Class 1 (employee) + "nic1_primary_threshold": 12570, + "nic1_upper_earnings_limit": 50270, + "nic1_main_rate": 0.08, + "nic1_additional_rate": 0.02, + # NIC Class 4 (self-employed) + "nic4_lower_profits": 12570, + "nic4_upper_profits": 50270, + "nic4_main_rate": 0.06, + "nic4_additional_rate": 0.02, + # Student loans + "student_loan_plan1_threshold": 22015, + "student_loan_plan2_threshold": 27295, + "student_loan_plan4_threshold": 27660, + "student_loan_plan5_threshold": 25000, + "student_loan_postgrad_threshold": 21000, + "student_loan_rate": 0.09, + "postgrad_loan_rate": 0.06, + # CGT + "cgt_annual_exempt": 3000, + "cgt_basic_rate": 0.18, # post-Oct 2024 unified rate + "cgt_higher_rate": 0.24, + "cgt_property_basic_rate": 0.18, + "cgt_property_higher_rate": 0.24, + "cgt_badr_rate": 0.10, + # Child benefit + "child_benefit_threshold": 60000, + "child_benefit_taper_end": 80000, + "child_benefit_eldest": 1331.20, + "child_benefit_other": 881.40, + # Pension + "pension_annual_allowance": 60000, + }, + "2025-26": { + "personal_allowance": 12570, + "pa_taper_threshold": 100000, + "basic_rate_limit": 37700, + "higher_rate_limit": 87440, + "basic_rate": 0.20, + "higher_rate": 0.40, + "additional_rate": 0.45, + "basic_rate_ceiling": 50270, + "additional_rate_floor": 125140, + "dividend_allowance": 500, + "dividend_basic": 0.0875, + "dividend_higher": 0.3375, + "dividend_additional": 0.3935, + "savings_starting_rate_limit": 5000, + "psa_basic": 1000, + "psa_higher": 500, + "psa_additional": 0, + "marriage_allowance": 1260, + "blind_persons_allowance": 3070, + "trading_allowance": 1000, + "property_allowance": 1000, + # NIC Class 1 + "nic1_primary_threshold": 12570, + "nic1_upper_earnings_limit": 50270, + "nic1_main_rate": 0.08, + "nic1_additional_rate": 0.02, + # NIC Class 4 + "nic4_lower_profits": 12570, + "nic4_upper_profits": 50270, + "nic4_main_rate": 0.06, + "nic4_additional_rate": 0.02, + # Student loans + "student_loan_plan1_threshold": 26065, + "student_loan_plan2_threshold": 28470, + "student_loan_plan4_threshold": 32745, + "student_loan_plan5_threshold": 25000, + "student_loan_postgrad_threshold": 21000, + "student_loan_rate": 0.09, + "postgrad_loan_rate": 0.06, + # CGT + "cgt_annual_exempt": 3000, + "cgt_basic_rate": 0.18, + "cgt_higher_rate": 0.24, + "cgt_property_basic_rate": 0.18, + "cgt_property_higher_rate": 0.24, + "cgt_badr_rate": 0.14, + # Child benefit + "child_benefit_threshold": 60000, + "child_benefit_taper_end": 80000, + "child_benefit_eldest": 1354.60, + "child_benefit_other": 897.00, + # Pension + "pension_annual_allowance": 60000, + }, +} + + +def round_tax(amount: float) -> float: + """Round to 2 decimal places.""" + return round(amount, 2) + + +def calculate_personal_allowance(total_income: float, rates: dict, blind: bool = False, marriage_allowance_received: bool = False, marriage_allowance_given: bool = False) -> float: + """Calculate personal allowance after taper and adjustments.""" + pa = rates["personal_allowance"] + + # Blind Person's Allowance + if blind: + pa += rates["blind_persons_allowance"] + + # Marriage Allowance + if marriage_allowance_received: + pa += rates["marriage_allowance"] + if marriage_allowance_given: + pa -= rates["marriage_allowance"] + + # Taper for income over £100,000 + if total_income > rates["pa_taper_threshold"]: + excess = total_income - rates["pa_taper_threshold"] + reduction = excess / 2 + pa = max(0, pa - reduction) + + return pa + + +def calculate_income_tax(non_savings: float, savings: float, dividends: float, rates: dict, personal_allowance: float, pension_relief_gross: float = 0) -> dict: + """ + Calculate income tax using the correct stacking order: + non-savings income → savings income → dividend income. + + Pension contributions extend the basic rate band. + """ + # Extend basic rate band by gross pension contributions + extended_basic_limit = rates["basic_rate_limit"] + pension_relief_gross + + # Total income for band allocation + total_income = non_savings + savings + dividends + + # Apply personal allowance — allocated against non-savings first, then savings, then dividends + remaining_pa = personal_allowance + + taxable_non_savings = max(0, non_savings - remaining_pa) + remaining_pa = max(0, remaining_pa - non_savings) + + taxable_savings = max(0, savings - remaining_pa) + remaining_pa = max(0, remaining_pa - savings) + + taxable_dividends = max(0, dividends - remaining_pa) + + # ─── Non-savings income tax ─── + ns_tax = 0 + ns_basic = 0 + ns_higher = 0 + ns_additional = 0 + band_used = 0 # how much of the basic rate band has been used + + if taxable_non_savings > 0: + # Basic rate + basic_amount = min(taxable_non_savings, extended_basic_limit) + ns_basic = basic_amount + ns_tax += basic_amount * rates["basic_rate"] + band_used += basic_amount + + # Higher rate + remaining = taxable_non_savings - basic_amount + if remaining > 0: + higher_limit = rates["additional_rate_floor"] - rates["personal_allowance"] - extended_basic_limit + higher_amount = min(remaining, max(0, higher_limit)) + ns_higher = higher_amount + ns_tax += higher_amount * rates["higher_rate"] + + # Additional rate + additional_amount = remaining - higher_amount + if additional_amount > 0: + ns_additional = additional_amount + ns_tax += additional_amount * rates["additional_rate"] + + # ─── Savings income tax ─── + sav_tax = 0 + sav_basic = 0 + sav_higher = 0 + sav_additional = 0 + + # Starting rate for savings (0% on up to £5,000 if non-savings income < PA + £5,000) + savings_starting_rate_available = max(0, rates["savings_starting_rate_limit"] - max(0, taxable_non_savings)) + + # Personal Savings Allowance + # Determine taxpayer band based on total taxable income for PSA + total_taxable = taxable_non_savings + taxable_savings + taxable_dividends + if total_taxable <= extended_basic_limit: + psa = rates["psa_basic"] + elif total_taxable <= (rates["additional_rate_floor"] - rates["personal_allowance"]): + psa = rates["psa_higher"] + else: + psa = rates["psa_additional"] + + if taxable_savings > 0: + remaining_savings = taxable_savings + + # Starting rate for savings (0%) + starting_rate_amount = min(remaining_savings, savings_starting_rate_available) + remaining_savings -= starting_rate_amount + band_used_for_savings = band_used + starting_rate_amount + + # PSA (0%) + psa_amount = min(remaining_savings, psa) + remaining_savings -= psa_amount + band_used_for_savings += psa_amount + + # Basic rate + basic_remaining = max(0, extended_basic_limit - band_used_for_savings) + sav_basic_amount = min(remaining_savings, basic_remaining) + sav_basic = sav_basic_amount + sav_tax += sav_basic_amount * rates["basic_rate"] + remaining_savings -= sav_basic_amount + band_used_for_savings += sav_basic_amount + + # Higher rate + if remaining_savings > 0: + higher_limit = rates["additional_rate_floor"] - rates["personal_allowance"] - extended_basic_limit + # Adjust for non-savings that already used higher band + higher_used_by_ns = max(0, taxable_non_savings - extended_basic_limit) + higher_remaining = max(0, higher_limit - higher_used_by_ns) + sav_higher_amount = min(remaining_savings, higher_remaining) + sav_higher = sav_higher_amount + sav_tax += sav_higher_amount * rates["higher_rate"] + remaining_savings -= sav_higher_amount + + # Additional rate + if remaining_savings > 0: + sav_additional = remaining_savings + sav_tax += remaining_savings * rates["additional_rate"] + + # ─── Dividend income tax ─── + div_tax = 0 + div_basic = 0 + div_higher = 0 + div_additional = 0 + + if taxable_dividends > 0: + remaining_divs = taxable_dividends + + # Dividend allowance (£500 at 0%) + div_allowance_used = min(remaining_divs, rates["dividend_allowance"]) + remaining_divs -= div_allowance_used + + # Determine which band dividends fall into + income_before_divs = taxable_non_savings + taxable_savings + total_band_used = income_before_divs + div_allowance_used + + # Basic rate band remaining + basic_remaining = max(0, extended_basic_limit - total_band_used) + div_basic_amount = min(remaining_divs, basic_remaining) + div_basic = div_basic_amount + div_tax += div_basic_amount * rates["dividend_basic"] + remaining_divs -= div_basic_amount + total_band_used += div_basic_amount + + # Higher rate + if remaining_divs > 0: + higher_ceiling = rates["additional_rate_floor"] - rates["personal_allowance"] + higher_remaining = max(0, higher_ceiling - total_band_used) + div_higher_amount = min(remaining_divs, higher_remaining) + div_higher = div_higher_amount + div_tax += div_higher_amount * rates["dividend_higher"] + remaining_divs -= div_higher_amount + + # Additional rate + if remaining_divs > 0: + div_additional = remaining_divs + div_tax += remaining_divs * rates["dividend_additional"] + + total_tax = round_tax(ns_tax + sav_tax + div_tax) + + return { + "non_savings_income": round_tax(non_savings), + "taxable_non_savings": round_tax(taxable_non_savings), + "savings_income": round_tax(savings), + "taxable_savings": round_tax(taxable_savings), + "dividend_income": round_tax(dividends), + "taxable_dividends": round_tax(taxable_dividends), + "personal_allowance_used": round_tax(personal_allowance), + "non_savings_tax": round_tax(ns_tax), + "non_savings_breakdown": { + "basic_rate": round_tax(ns_basic), + "higher_rate": round_tax(ns_higher), + "additional_rate": round_tax(ns_additional), + }, + "savings_tax": round_tax(sav_tax), + "savings_breakdown": { + "starting_rate_0pc": round_tax(min(taxable_savings, savings_starting_rate_available) if taxable_savings > 0 else 0), + "psa_0pc": round_tax(min(max(0, taxable_savings - savings_starting_rate_available), psa) if taxable_savings > 0 else 0), + "basic_rate": round_tax(sav_basic), + "higher_rate": round_tax(sav_higher), + "additional_rate": round_tax(sav_additional), + }, + "dividend_tax": round_tax(div_tax), + "dividend_breakdown": { + "allowance_0pc": round_tax(min(taxable_dividends, rates["dividend_allowance"]) if taxable_dividends > 0 else 0), + "basic_rate": round_tax(div_basic), + "higher_rate": round_tax(div_higher), + "additional_rate": round_tax(div_additional), + }, + "total_income_tax": total_tax, + } + + +def calculate_nic_class4(self_employment_profit: float, rates: dict) -> dict: + """Calculate Class 4 NIC on self-employment profits.""" + if self_employment_profit <= rates["nic4_lower_profits"]: + return {"class4_nic": 0, "main_rate_amount": 0, "additional_rate_amount": 0} + + main_rate_amount = min( + max(0, self_employment_profit - rates["nic4_lower_profits"]), + rates["nic4_upper_profits"] - rates["nic4_lower_profits"], + ) + additional_rate_amount = max(0, self_employment_profit - rates["nic4_upper_profits"]) + + class4 = (main_rate_amount * rates["nic4_main_rate"]) + (additional_rate_amount * rates["nic4_additional_rate"]) + + return { + "class4_nic": round_tax(class4), + "main_rate_amount": round_tax(main_rate_amount), + "additional_rate_amount": round_tax(additional_rate_amount), + } + + +def calculate_student_loan(gross_income: float, plans: list, rates: dict) -> dict: + """Calculate student loan repayments.""" + result = {} + total = 0 + + for plan in plans: + plan_lower = plan.lower().replace(" ", "") + if plan_lower in ("plan1", "1"): + threshold = rates["student_loan_plan1_threshold"] + rate = rates["student_loan_rate"] + key = "plan1" + elif plan_lower in ("plan2", "2"): + threshold = rates["student_loan_plan2_threshold"] + rate = rates["student_loan_rate"] + key = "plan2" + elif plan_lower in ("plan4", "4"): + threshold = rates["student_loan_plan4_threshold"] + rate = rates["student_loan_rate"] + key = "plan4" + elif plan_lower in ("plan5", "5"): + threshold = rates["student_loan_plan5_threshold"] + rate = rates["student_loan_rate"] + key = "plan5" + elif plan_lower in ("postgraduate", "postgrad", "pg"): + threshold = rates["student_loan_postgrad_threshold"] + rate = rates["postgrad_loan_rate"] + key = "postgraduate" + else: + continue + + repayment = max(0, (gross_income - threshold) * rate) + result[key] = { + "threshold": threshold, + "rate": rate, + "repayment": round_tax(repayment), + } + total += repayment + + result["total_student_loan"] = round_tax(total) + return result + + +def calculate_cgt(gains: list, rates: dict, remaining_basic_band: float) -> dict: + """ + Calculate Capital Gains Tax. + + gains: list of {"proceeds": float, "cost": float, "type": "shares"|"property"|"other", "badr": bool} + remaining_basic_band: how much of the basic rate band is unused after income tax + """ + total_gains = 0 + total_losses = 0 + gains_by_type = {"shares": 0, "property": 0, "other": 0, "badr": 0} + + for g in gains: + gain = g.get("proceeds", 0) - g.get("cost", 0) + if gain > 0: + if g.get("badr"): + gains_by_type["badr"] += gain + else: + gains_by_type[g.get("type", "other")] += gain + total_gains += gain + else: + total_losses += abs(gain) + + # Apply losses + net_gains = max(0, total_gains - total_losses) + + # Apply annual exempt amount + exempt_used = min(net_gains, rates["cgt_annual_exempt"]) + taxable_gains = max(0, net_gains - exempt_used) + + # Calculate CGT + cgt = 0 + remaining_band = remaining_basic_band + + # BADR gains first (flat rate) + badr_gains = min(gains_by_type["badr"], taxable_gains) + if badr_gains > 0: + cgt += badr_gains * rates["cgt_badr_rate"] + taxable_gains -= badr_gains + + # Remaining gains: basic rate then higher rate + if taxable_gains > 0: + basic_amount = min(taxable_gains, max(0, remaining_band)) + higher_amount = taxable_gains - basic_amount + + # Use property rates for property gains, standard rates for others + # Simplified: use the unified rates (post Oct 2024 they're the same) + cgt += basic_amount * rates["cgt_basic_rate"] + cgt += higher_amount * rates["cgt_higher_rate"] + + return { + "total_gains": round_tax(total_gains), + "total_losses": round_tax(total_losses), + "net_gains": round_tax(net_gains), + "annual_exempt_used": round_tax(exempt_used), + "taxable_gains": round_tax(max(0, net_gains - exempt_used)), + "badr_gains": round_tax(gains_by_type["badr"]), + "cgt_due": round_tax(cgt), + } + + +def calculate_child_benefit_charge(adjusted_net_income: float, num_children: int, rates: dict) -> dict: + """Calculate High Income Child Benefit Charge.""" + if num_children <= 0 or adjusted_net_income <= rates["child_benefit_threshold"]: + return {"charge": 0, "total_benefit": 0, "percentage_clawed_back": 0} + + # Total benefit + benefit = rates["child_benefit_eldest"] + if num_children > 1: + benefit += (num_children - 1) * rates["child_benefit_other"] + + # Charge: 1% per £200 over threshold + excess = adjusted_net_income - rates["child_benefit_threshold"] + percentage = min(100, (excess / 200)) # 1% per £200 + charge = benefit * (percentage / 100) + + return { + "total_benefit": round_tax(benefit), + "percentage_clawed_back": round(percentage, 1), + "charge": round_tax(charge), + } + + +def calculate_payment_on_account(sa_liability: float, tax_deducted_at_source: float) -> dict: + """Calculate payments on account for next year.""" + # POA required if SA liability > £1,000 AND less than 80% was deducted at source + total_liability = sa_liability + if total_liability <= 1000: + return {"poa_required": False, "each_payment": 0, "reason": "Liability is £1,000 or less"} + + source_percentage = (tax_deducted_at_source / total_liability * 100) if total_liability > 0 else 100 + if source_percentage >= 80: + return {"poa_required": False, "each_payment": 0, "reason": "80% or more was deducted at source"} + + each_payment = round_tax(total_liability / 2) + return { + "poa_required": True, + "each_payment": each_payment, + "total_poa": round_tax(total_liability), + "reason": f"SA liability of £{total_liability:,.2f} with {source_percentage:.1f}% deducted at source", + } + + +def calculate_tax(data: dict) -> dict: + """Main calculation entry point.""" + tax_year = data.get("tax_year", "2024-25") + if tax_year not in TAX_RATES: + return {"error": f"Unsupported tax year: {tax_year}. Supported: {list(TAX_RATES.keys())}"} + + rates = TAX_RATES[tax_year] + + # ─── Gather income ─── + # Employment + employments = data.get("employment", []) + total_employment_gross = sum(e.get("gross_pay", 0) for e in employments) + total_employment_tax = sum(e.get("tax_deducted", 0) for e in employments) + total_benefits = sum(e.get("benefits", 0) for e in employments) + + # Self-employment + self_employments = data.get("self_employment", []) + total_se_profit = sum(se.get("profit", 0) for se in self_employments) + + # Savings + savings_interest = data.get("savings_interest", 0) + + # Dividends + dividends = data.get("dividends", 0) + + # Rental + rental = data.get("rental", {}) + rental_profit = rental.get("profit", 0) + rental_finance_costs = rental.get("finance_costs", 0) # mortgage interest for tax reducer + + # Pension contributions + pension = data.get("pension", {}) + personal_pension_gross = pension.get("personal_gross", 0) # gross amount (already includes basic rate relief) + employer_pension = pension.get("employer", 0) + + # Other + other_income = data.get("other_income", 0) + + # ─── Personal circumstances ─── + blind = data.get("blind_persons_allowance", False) + marriage_allowance_received = data.get("marriage_allowance_received", False) + marriage_allowance_given = data.get("marriage_allowance_given", False) + student_loan_plans = data.get("student_loan_plans", []) + child_benefit = data.get("child_benefit", {}) + gift_aid = data.get("gift_aid", 0) + previous_year_liability = data.get("previous_year_sa_liability", 0) + + # ─── Calculate totals ─── + non_savings_income = total_employment_gross + total_benefits + total_se_profit + rental_profit + other_income + total_income = non_savings_income + savings_interest + dividends + + # Adjusted net income (for PA taper and child benefit): total income minus pension and gift aid + # Pension contributions extend the basic rate band and reduce adjusted net income + # Gift Aid extends the basic rate band too + adjusted_net_income = total_income - personal_pension_gross - (gift_aid * 1.25) # gross up gift aid + + # Personal allowance + pa = calculate_personal_allowance( + adjusted_net_income, rates, blind, marriage_allowance_received, marriage_allowance_given + ) + + # Pension relief extends basic rate band + pension_relief_gross = personal_pension_gross + (gift_aid * 1.25) + + # ─── Income tax ─── + income_tax = calculate_income_tax( + non_savings_income, savings_interest, dividends, rates, pa, pension_relief_gross + ) + + # ─── Rental finance costs tax reducer (20%) ─── + finance_cost_reducer = round_tax(rental_finance_costs * 0.20) + + # ─── Marriage Allowance tax reducer ─── + marriage_allowance_reducer = 0 + if marriage_allowance_received: + marriage_allowance_reducer = round_tax(rates["marriage_allowance"] * 0.20) + + # Total income tax after reducers + income_tax_after_reducers = round_tax( + max(0, income_tax["total_income_tax"] - finance_cost_reducer - marriage_allowance_reducer) + ) + + # ─── NIC ─── + nic4 = calculate_nic_class4(total_se_profit, rates) if total_se_profit > 0 else {"class4_nic": 0} + + # ─── Student loans ─── + # Student loan is calculated on total relevant income + student_loan_income = total_employment_gross + total_se_profit + student_loans = calculate_student_loan(student_loan_income, student_loan_plans, rates) if student_loan_plans else {"total_student_loan": 0} + + # ─── CGT ─── + capital_gains = data.get("capital_gains", []) + remaining_basic_band = max(0, rates["basic_rate_limit"] + pension_relief_gross - max(0, non_savings_income + savings_interest + dividends - pa)) + cgt = calculate_cgt(capital_gains, rates, remaining_basic_band) if capital_gains else {"cgt_due": 0, "total_gains": 0, "taxable_gains": 0} + + # ─── Child benefit charge ─── + num_children = child_benefit.get("num_children", 0) + child_benefit_charge = calculate_child_benefit_charge(adjusted_net_income, num_children, rates) + + # ─── Total liability ─── + total_tax_due = round_tax( + income_tax_after_reducers + + nic4.get("class4_nic", 0) + + student_loans.get("total_student_loan", 0) + + cgt.get("cgt_due", 0) + + child_benefit_charge.get("charge", 0) + ) + + # Tax already paid + tax_already_paid = round_tax(total_employment_tax) + # Could also include tax deducted from savings, CGT already paid, etc. + tax_already_paid += data.get("other_tax_paid", 0) + + amount_owed = round_tax(total_tax_due - tax_already_paid) + + # ─── Payment on account ─── + sa_liability_for_poa = round_tax(total_tax_due - total_employment_tax) # SA liability excluding PAYE + poa = calculate_payment_on_account(sa_liability_for_poa, 0) # POA based on SA-only liability + + # ─── Build result ─── + result = { + "tax_year": tax_year, + "income_summary": { + "employment_gross": round_tax(total_employment_gross), + "employment_benefits": round_tax(total_benefits), + "self_employment_profit": round_tax(total_se_profit), + "savings_interest": round_tax(savings_interest), + "dividends": round_tax(dividends), + "rental_profit": round_tax(rental_profit), + "other_income": round_tax(other_income), + "total_income": round_tax(total_income), + "adjusted_net_income": round_tax(adjusted_net_income), + }, + "personal_allowance": round_tax(pa), + "income_tax": income_tax, + "tax_reducers": { + "finance_cost_reducer": finance_cost_reducer, + "marriage_allowance_reducer": marriage_allowance_reducer, + }, + "income_tax_after_reducers": income_tax_after_reducers, + "national_insurance": nic4, + "student_loans": student_loans, + "capital_gains_tax": cgt, + "child_benefit_charge": child_benefit_charge, + "headline": { + "total_income": round_tax(total_income), + "total_tax_due": total_tax_due, + "tax_already_paid": tax_already_paid, + "amount_owed": amount_owed, + "amount_refund": round_tax(abs(amount_owed)) if amount_owed < 0 else 0, + }, + "payment_on_account": poa, + } + + return result + + +def main(): + # Read input + if len(sys.argv) > 1: + with open(sys.argv[1], "r") as f: + data = json.load(f) + else: + data = json.load(sys.stdin) + + result = calculate_tax(data) + print(json.dumps(result, indent=2)) + + +if __name__ == "__main__": + main()