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()