diff options
Diffstat (limited to 'ledger-tax-report')
| -rwxr-xr-x | ledger-tax-report | 276 | 
1 files changed, 276 insertions, 0 deletions
| diff --git a/ledger-tax-report b/ledger-tax-report new file mode 100755 index 0000000..de91f29 --- /dev/null +++ b/ledger-tax-report @@ -0,0 +1,276 @@ +#!/usr/bin/env python3 + +import re +import subprocess +import sys + +if len(sys.argv) == 2: +    year = sys.argv[1] +else: +    year = '2023' + +def ledger_amount(query): +    p = subprocess.run("ledger -p '%s' -n -J --yearly reg %s" % (year, query,), +            shell=True, stdout=subprocess.PIPE, text=True) +    parts = p.stdout.split(' ') +    if len(parts) == 2: +        return float(parts[1]) +    else: +        return 0.0 + +def parse_tax_table(input): +    filing_status = 2 # Married/Jointly +    table = [] +    delta = 0 +    prev_max_tax = 0 +    for line in input.split("\n"): +        if not line: +            continue +        cols = line.split("\t") +        rate = float(cols[0].strip(' %')) / 100.0 +        brk = re.sub('[$,]', '', cols[filing_status]).split(' ') +        lower = brk[0] +        upper = brk[2] +        delta +        if upper == 'more': +            table.append((float(lower), None, rate, delta)) +        else: +            delta += rate * float(lower) - prev_max_tax +            prev_max_tax = float(upper) * rate +            table.append((float(lower), float(upper), rate, delta)) +    return table + +     +income_tax_table = {} + +# c&p from https://taxfoundation.org/2022-tax-brackets/ +income_tax_table['2022'] = parse_tax_table(''' +10% 	$0 to $10,275 	$0 to $20,550 	$0 to $14,650 +12% 	$10,275 to $41,775 	$20,550 to $83,550 	$14,650 to $55,900 +22% 	$41,775 to $89,075 	$83,550 to $178,150 	$55,900 to $89,050 +24% 	$89,075 to $170,050 	$178,150 to $340,100 	$89,050 to $170,050 +32% 	$170,050 to $215,950 	$340,100 to $431,900 	$170,050 to $215,950 +35% 	$215,950 to $539,900 	$431,900 to $647,850 	$215,950 to $539,900 +37% 	$539,900 or more 	$647,850 or more 	$539,900 or more +''') + +# c&p from https://taxfoundation.org/2023-tax-brackets/ +income_tax_table['2023'] = parse_tax_table(''' +10% 	$0 to $11,000 	$0 to $22,000 	$0 to $15,700 +12% 	$11,000 to $44,725 	$22,000 to $89,450 	$15,700 to $59,850 +22% 	$44,725 to $95,375 	$89,450 to $190,750 	$59,850 to $95,350 +24% 	$95,375 to $182,100 	$190,750 to $364,200 	$95,350 to $182,100 +32% 	$182,100 to $231,250 	$364,200 to $462,500 	$182,100 to $231,250 +35% 	$231,250 to $578,125 	$462,500 to $693,750 	$231,250 to $578,100 +37% 	$578,125 or more 	$693,750 or more 	$578,100 or more +''') + +def compute_income_tax(income): +    for (lower, upper, rate, sub) in income_tax_table[year]: +        if income > lower and (upper is None or income <= upper): +            return round(income * rate - sub) +    raise Exception("Failed to compute tax") + +def compute_tax_worksheet(taxable_income, qualified_dividends, schedule_d): +    line       = {} +    line['1']  = taxable_income +    line['2']  = qualified_dividends +    line['3']  = max(0, min(schedule_d['15'], schedule_d['16'])) +    line['4']  = line['2'] + line['3'] +    line['5']  = max(0, taxable_income - line['4']) +    line['6']  = 83350.00 # TODO Tax table (this number from 2022) +    line['7']  = min(taxable_income, line['6']) +    line['8']  = min(line['5'], line['7']) +    line['9']  = line['7'] - line['8'] # 0% tax amount +    line['10'] = min(taxable_income, line['4']) +    line['11'] = line['9'] +    line['12'] = line['10'] - line['11'] +    line['13'] = 517200.00 # TODO Tax table (this number from 2022) +    line['14'] = min(taxable_income, line['13']) +    line['15'] = line['5'] + line['9'] +    line['16'] = max(0, line['14'] - line['15']) +    line['17'] = min(line['12'], line['16']) +    line['18'] = line['17'] * 0.15 +    line['19'] = line['9'] + line['17'] +    line['20'] = line['10'] - line['19'] +    line['21'] = line['20'] * 0.20 +    line['22'] = compute_income_tax(line['5']) +    line['23'] = line['18'] + line['21'] + line['22'] +    line['24'] = compute_income_tax(taxable_income) +    line['25'] = min(line['23'], line['24']) +    return line + +def compute_schedule_d(net_st, net_lt): +    line       = {} +    line['7']  = net_st +    line['15'] = net_lt +    line['16'] = line['7'] + line['15'] + +    # N.B. this deviates from the instructions  +    # For simplicity, Form 1040 will always get line 7 from schedule D line 21. +    line['21'] = line['16'] + +    if line['16'] > 0: +        if line['15'] > 0: +            line['17'] = 'Yes' +            line['18'] = 0.00 # TODO: 28% Rate Gain Worksheet +            line['19'] = 0.00 # TODO: Section 1250 Worksheet +            if line['18'] == 0.00 and line['19'] == 0.00: +                line['20'] = 'Yes' +                return line +            else: +                line['20'] = 'No' +                raise Exception("TODO: Implement Schedule D worksheet") +        else: +            line['17'] = 'No' +            # Goto line 22 +    elif line['16'] < 0: +        # Goto Line 21 (and 22) +        line['21'] = max(-3000.00, line['16']) + +    # Line 22 - Do you have qualified dividends? + +    return line + +def compute_deduction(deductions, limited): +    itemized  = deductions  +    itemized += min(10000, limited) +    standard = 25900  # TODO Tax table +    return max(itemized, standard) + +def credit_limit_worksheet_a(line18): +    line       = {} +    line['1']  = line18 +    line['2']  = 0.00                   # TODO: Schedule 3 +    line['3']  = line['1'] - line['2'] +    line['4']  = 0                      # TODO: Credit Limit Worksheet B +    line['5']  = line['3'] - line['4'] +    return line + +# Schedule 8812 +def compute_child_tax_credit(agi, dependants, line18): +    line       = {} +    line['1']  = agi +    line['3']  = line['1'] +    line['4']  = dependants +    line['5']  = line['4'] * 2000.00 +    line['6']  = 0 +    line['7']  = line['6'] * 500.00 +    line['8']  = line['5'] + line['7'] +    line['9']  = 400000.00 # Married, filing jointly +    line['10'] = max(0, round((line['3'] - line['9'])/1000) * 1000) +    line['11'] = line['10'] * 0.05 +    if line['8'] > line['11']: +        line['12'] = line['8'] - line['11'] +    else: +        line['14'] = 0 +        line['27'] = 0 +        return line +    line['13'] = credit_limit_worksheet_a(line18)['5'] +    line['14'] = min(line['12'], line['13']) +    return line + + +     + +print("Tax report for %s" % (year)) +print("-------------------") + +################################# +# Gather ledger values + +# Income +income              = 0 - ledger_amount('tag EarnedIncome') +taxable_interest    = 0 - ledger_amount('"Interest and not HSA"') +qualified_dividends = 0 - ledger_amount('tag QualifiedDividends') +ordinary_dividends  = 0 - ledger_amount('tag OrdinaryDividends') +net_st              = 0 - ledger_amount('tag ShortTermGains') +net_lt              = 0 - ledger_amount('tag LongTermGains') + +# Expenses +total_payments      = ledger_amount('tag TaxesPaid') +deductions          = ledger_amount('tag ^TaxDeductible$') +deductions_limited  = ledger_amount('tag ^TaxDeductibleLimited$') + +# Other +dependants          = 2 + + +############################ +# Compute taxes + + +schedule_d = compute_schedule_d(net_st, net_lt) + +line       = {} + +# Income +line['1z'] = income +line['2b'] = taxable_interest +line['3a'] = qualified_dividends +line['3b'] = ordinary_dividends +line['4b'] = 0.00                # TODO: IRA Distribution +line['5b'] = 0.00                # TODO: Pension +line['6b'] = 0.00                # TODO: Social security +line['7']  = schedule_d['21'] +line['8']  = 0.00 # TODO: Schedule 1 +line['9']  = line['1z'] + line['2b'] + line['3b'] + line['4b'] + \ +             line['5b'] + line['6b'] + line['7'] + line['8'] + +line['10'] = 0.00                # TODO Schedule 1 (line 26) +line['11'] = line['9'] - line['10'] + +line['12'] = compute_deduction(deductions, deductions_limited) +line['13'] = 0.00                # TODO: Form 8995 +line['14'] = line['12'] + line['13'] +line['15'] = max(0, line['11'] - line['14']) + +# Tax and Credits +tax_worksheet = compute_tax_worksheet(line['15'], line['3a'], schedule_d) +line['16'] = tax_worksheet['25'] +line['17'] = 0.00                # TODO: Schedule 2 (line 3) +line['18'] = line['16'] + line['17'] +line['19'] = compute_child_tax_credit(line['11'], dependants, line['18'])['14'] +line['20'] = 0.00                # TODO: Schedule 3 (line 8) +line['21'] = line['19'] + line['20'] +line['22'] = max(0, line['18'] - line['21']) +line['23'] = 0.00                # TODO Schedule 2 (line 21) +line['24'] = line['22'] + line['23'] + +# Payments +line['33'] = total_payments + +# Refund +line['34'] = max(0, line['33'] - line['24']) + +# Amount You Owe +line['37'] = max(0, line['24'] - line['33']) +# TODO Penalty + + +#################### + +print(" 1z. Total income         ${:>12,.0f}".format(line['1z'])) +print(" 2b. Taxable interest     ${:>12,.0f}".format(line['2b'])) +print(" 3b. Ordinary dividends   ${:>12,.0f}".format(line['3b'])) +print("  7. Capital gain (loss)  ${:>12,.0f}".format(line['7'])) +print("  8. Other income         ${:>12,.0f}".format(line['8'])) +print("  9. Total income         ${:>12,.0f}".format(line['9'])) +print(" 10. Adjustments          ${:>12,.0f}".format(line['10'])) +print(" 11. AGI                  ${:>12,.0f}".format(line['11'])) +print()  + +print(" 12. Deductions           ${:>12,.0f}".format(line['12'])) +print(" 13. Business deduction   ${:>12,.0f}".format(line['13'])) +print(" 15. Taxable income       ${:>12,.0f}".format(line['15'])) +print() + +print(" 16. Tax                  ${:>12,.0f}".format(line['16'])) +print(" 16. Total tax            ${:>12,.0f}".format(line['24'])) +print(" 33. Total payments       ${:>12,.0f}".format(line['33'])) + +if line['34'] > 0: +    print(" 34. Tax refund           ${:>12,.0f}".format(line['34'])) +else: +    print(" 37. Taxes owed           ${:>12,.0f}".format(line['37'])) + | 
