PK!taxbill/__init__.pyPK!V++taxbill/__main__.pyfrom taxbill.cli import taxbill taxbill() PK!U>7 7 taxbill/calculator.pyfrom collections import defaultdict def single_band_tax_amount(taxable_amount, rate, threshold=0): """Compute the taxable amount for a single band of tax. e.g. Income tax is split into basic and higher bands. This will compute the amount for either one of those if passed the threshold and rate for that band. """ if taxable_amount < threshold: return 0 return rate * (taxable_amount - threshold) def tax_amount(taxable_amount, bands): """Compute the total amount for a tax. This will compute the amount for each band a tax and return the sum of those amounts. e.g For income tax, it will return the sum of both the basic and higher band amounts. """ amounts = [ single_band_tax_amount( min([taxable_amount, band["limit"]]), band["rate"], band["threshold"], ) for band in bands ] return round(sum(amounts), 2) def salary_taxes(salary, rates): """NI and income tax on earnings are banded taxes based on the salary amount. We can use a comprehension to loop over those taxes and compute relevant amounts. """ return { tax: tax_amount(salary, rates[tax]) for tax in ["employees_ni", "employers_ni", "income_tax_earnings"] } def dividend_tax(salary, dividend, rates): """Dividend tax uses the income tax earnings bands to determine the appropriate rate. """ # Work out which income tax band applies to the sum of salary and # dividend amounts. idx, _ = next( (idx, band) for idx, band in enumerate(rates["income_tax_earnings"]) if band["limit"] > salary + dividend > band["threshold"] ) # Use that band to apply the relevant rate to the dividend amount rate = rates["income_tax_dividend"]["rates"][idx] threshold = rates["income_tax_dividend"]["threshold"] return single_band_tax_amount(dividend, rate, threshold) def all_taxes(salary, dividend, personal_pension, employer_pension, rates): result = defaultdict(int) if salary: result.update(salary_taxes(salary, rates)) if dividend: result["income_tax_dividend"] = dividend_tax(salary, dividend, rates) if personal_pension: result["income_tax_pension_relief"] = round( personal_pension * rates["income_tax_earnings"][1]["rate"], 2 ) corporation_tax = single_band_tax_amount( salary + result["employers_ni"] + employer_pension, rates["corporation_tax"], ) result["corporation_tax"] = round(corporation_tax, 2) return result PK!z⋚taxbill/cli.py# pylint: disable=missing-docstring, bad-continuation from datetime import datetime from pkgutil import get_data import click from yaml import load from taxbill.calculator import all_taxes from taxbill.optimiser import run RATES = load(get_data("taxbill", "rates.yml").decode("utf8")) def display( salary, dividend, employer_pension, personal_pension, employers_ni=0, employees_ni=0, income_tax_earnings=0, income_tax_dividend=0, income_tax_pension_relief=0, corporation_tax=0, ): total_gross_drawings = salary + dividend + employer_pension corporate_tax = round(employers_ni - corporation_tax, 2) personal_tax = round( income_tax_earnings + income_tax_dividend + employees_ni - income_tax_pension_relief, 2, ) total_tax = round(corporate_tax + personal_tax, 2) effective_tax_pct = round(total_tax * 100 / total_gross_drawings, 2) print(f"Salary: £{salary}") print(f"Dividend: £{dividend}") print(f"Employer Pension Contribution: £{employer_pension}") print(f"Personal Pension Contribution: £{personal_pension}") print("------") print(f"Employer NI contribution: £{employers_ni}") print(f"Employee NI contribution: £{employees_ni}") print(f"Income Tax on Salary: £{income_tax_earnings}") print(f"Income Tax on Dividend: £{income_tax_dividend}") print(f"Tax Relief on Pension Contribution: £{income_tax_pension_relief}") print(f"Corporation Tax Saving: £{corporation_tax}") print("---") print(f"Total Withdrawn Gross: £{total_gross_drawings}") print(f"Total Personal Taxes: £{personal_tax}") print(f"Total Withdrawn Net: £{total_gross_drawings - personal_tax}\n") print(f"Total Corporate Taxes: £{corporate_tax}") print(f"Total Tax: £{total_tax}\n") print(f"Total Tax / Gross Withdrawn: {effective_tax_pct}%") @click.version_option(prog_name="taxbill") @click.group() def taxbill(): pass @taxbill.command() @click.option( "-s", "--salary", default=0, prompt="Salary", help="Annual gross salary" ) @click.option( "-d", "--dividend", default=0, prompt="Dividend", help="Total dividend payments", ) @click.option( "-e", "--employer_pension", default=0, prompt="Employer Pension Contribution", help="Total employer pension contributions", ) @click.option( "-p", "--personal_pension", default=0, prompt="Personal Pension Contribution", help="Total personal pension contributions", ) @click.option( "-y", "--year", type=int, default=None, help="Calendar year in which tax year ends", ) def calculate(salary, dividend, employer_pension, personal_pension, year): if year is None: year = datetime.now().year taxes = all_taxes( salary, dividend, personal_pension, employer_pension, RATES[year] ) display( salary=salary, dividend=dividend, employer_pension=employer_pension, personal_pension=personal_pension, **taxes, ) @taxbill.command() @click.option( "-r", "--requirement", type=int, prompt="Required amount", help="Total required withdrawals", ) @click.option( "-p", "--pension", type=int, prompt="Pension Contribution", help="Total required pension contribution", ) @click.option( "-y", "--year", type=int, default=None, help="Calendar year in which tax year ends", ) def optimise(requirement, pension, year): if year is None: year = datetime.now().year optimal = run(requirement=requirement, pension=pension, rates=RATES[year]) taxes = all_taxes(rates=RATES[year], **optimal) display(**optimal, **taxes) PK!- taxbill/optimiser.pyfrom pulp import LpInteger, LpMinimize, LpProblem, LpStatus, LpVariable, lpSum def run(requirement, pension, rates, income_tax_band=1): tax_band = rates["income_tax_earnings"][income_tax_band] employees_ni_band = rates["employees_ni"][income_tax_band] employers_ni_band = rates["employers_ni"][income_tax_band] dividend_threshold = rates["income_tax_dividend"]["threshold"] dividend_rate = rates["income_tax_dividend"]["rates"][income_tax_band] problem = LpProblem(name="Optimise Tax", sense=LpMinimize) salary = LpVariable( name="salary", lowBound=tax_band["threshold"], upBound=tax_band["limit"], cat=LpInteger, ) dividend = LpVariable( name="dividend", lowBound=0, upBound=tax_band["limit"], cat=LpInteger ) personal_pension = LpVariable( name="personal_pension", lowBound=0, upBound=tax_band["limit"], cat=LpInteger, ) employer_pension = LpVariable( name="employer_pension", lowBound=0, upBound=tax_band["limit"], cat=LpInteger, ) problem += ( # Employees NI contribution employees_ni_band["rate"] * (salary - employees_ni_band["threshold"]) # Employers NI contribution + employers_ni_band["rate"] * (salary - employers_ni_band["threshold"]) # Corporation tax saving on employers NI contribution - rates["corporation_tax"] * employers_ni_band["rate"] * (salary - employers_ni_band["threshold"]) # Income tax on salary + tax_band["rate"] * (salary - tax_band["threshold"]) # Income tax on dividend + dividend_rate * (dividend - dividend_threshold) # Corporation tax saving on salary - rates["corporation_tax"] * salary # Income tax relief on personal pension contribution - tax_band["rate"] * personal_pension # Corporation Tax saving on employer pension contribution - rates["corporation_tax"] * employer_pension, "TotalTaxBill", ) problem += ( salary + dividend + employer_pension == requirement, "RequiredAmount", ) problem += personal_pension <= salary, "PensionLimitation" problem += ( employer_pension + personal_pension == pension, "PensionRequirement", ) problem.solve() if LpStatus[problem.status] != "Optimal": raise ValueError("No solution found") return {v.name: v.varValue for v in problem.variables()} PK!Iw:taxbill/rates.yml# The year refers to the calendar year in which the end of the tax year falls # e.g. 2019 refers to the tax year April 2018 - March 2019 2019: employees_ni: - threshold: 6032 limit: 8424 rate: 0 - threshold: 8424 limit: 46350 rate: 0.12 - threshold: 46350 limit: .inf rate: 0.02 employers_ni: - threshold: 6032 limit: 8424 rate: 0 - threshold: 8424 limit: .inf rate: 0.138 income_tax_earnings: - threshold: 0 limit: 11850 rate: 0 - threshold: 11850 limit: 46350 rate: 0.2 - threshold: 46350 limit: 150000 rate: 0.4 - threshold: 150000 limit: .inf rate: 0.45 income_tax_dividend: threshold: 2000 # These refer to the income tax earnings bands rates: - 0.075 - 0.075 - 0.325 - 0.381 corporation_tax: 0.19 PK!HcS&/(taxbill-0.1.0.dist-info/entry_points.txtN+I/N.,()*IHɱz9VP6PK!HڽTUtaxbill-0.1.0.dist-info/WHEEL A н#Z;/"d&F[xzw@Zpy3Fv]\fi4WZ^EgM_-]#0(q7PK!H@l$  taxbill-0.1.0.dist-info/METADATAMO@snZj4@B0AFD2ԍn߻Ux;̳=ո'kARTp!s+b1m#%6G5ܒ ַ}4aO,:ԷR/lKRdQ@g@ϿLdՕE)`j|;%lt7)>+f1knEE):$aq~PK!Hctaxbill-0.1.0.dist-info/RECORDuѶj@,(\[uRMHXh0̤xsUs }K;IA,IvhTlЖb|/WV&je㶫0v&w 8񋽠:E?wz@i.w-hP_bia7< ެZx,NY׎]yw 䙤j1WP:N܁zq0gTu &"eF7AȏT|lJm}jpãw}ȧȿ[u)TjʽU9&w=q&͙LUůyWMqixQQ8!xD7$AQ=ӯy9[xN%b޲tkIss|XmoHͲ?v6nScoWQqtm߰ x#"!lRH܀Ģa參d*ŨYms8PK!taxbill/__init__.pyPK!V++1taxbill/__main__.pyPK!U>7 7 taxbill/calculator.pyPK!z⋚ taxbill/cli.pyPK!- taxbill/optimiser.pyPK!Iw:#taxbill/rates.ymlPK!HcS&/('taxbill-0.1.0.dist-info/entry_points.txtPK!HڽTU'taxbill-0.1.0.dist-info/WHEELPK!H@l$  (taxbill-0.1.0.dist-info/METADATAPK!Hc)taxbill-0.1.0.dist-info/RECORDPK +