import { InconsistencyError, NotFoundError } from './Errors';
class TestTaxReturn extends TaxReturn {
+ readonly constants = undefined;
get year() { return 2019; }
get includeJointPersonForms() { return true; }
};
import { NotFoundError } from './Errors';
class TestTaxReturn extends TaxReturn {
+ readonly constants = undefined;
get year() { return 2019; }
get includeJointPersonForms() { return false; }
};
export const undefinedToZero = (value?: number): number => value === undefined ? 0 : value;
export const reduceBySum = (list: number[]) => list.reduce((acc, curr) => acc + curr, 0);
+
+// An identity function to convery the semantic meaning of a constant, but
+// without needing the variability of TaxReturn.constants.
+export const Literal = x => x;
class TestTaxReturn extends TaxReturn {
get year() { return 2019; }
+ readonly constants = {};
+
includeJointPersonForms = false;
};
private _people: Person[] = [];
private _forms: Form[] = [];
+ abstract readonly constants;
+
abstract get year(): number;
abstract get includeJointPersonForms(): boolean;
import { Edge, getLastTraceList } from './Trace';
class TestTaxReturn extends TaxReturn {
+ readonly constants = undefined;
+
get year() { return 2019; }
get includeJointPersonForms() { return false; }
}
}
- switch (this.filingStatus) {
- case FilingStatus.Single:
- case FilingStatus.MarriedFilingSeparate:
- return Math.max(deduction, 12200);
- case FilingStatus.MarriedFilingJoint:
- return Math.max(deduction, 24400);
- }
+ return Math.max(deduction, tr.constants.standardDeduction[this.filingStatus]);
}, 'Deduction'),
'10': new ComputedLine((tr): number => {
}
// Otherwise, compute just on taxable income.
- return computeTax(this.getValue(tr, '11b'), this.filingStatus);
+ return computeTax(this.getValue(tr, '11b'), tr);
}, 'Tax'),
'12b': new ComputedLine((tr): number => {
}
};
-export function computeTax(income: number, filingStatus: FilingStatus): number {
- // From https://www.irs.gov/pub/irs-drop/rp-18-57.pdf, Section 3.01 and
- // https://www.irs.gov/pub/irs-pdf/p17.pdf, 2019 Tax Rate Schedules (p254).
- const taxBrackets = {
- // Format is:
- // [ limit-of-taxable-income, marginal-rate, base-tax ]
- // If Income is over Row[0], pay Row[2] + (Row[1] * (Income - PreviousRow[0]))
- [FilingStatus.MarriedFilingJoint]: [
- [ 19400, 0.10, 0 ],
- [ 78950, 0.12, 1940 ],
- [ 168400, 0.22, 9086 ],
- [ 321450, 0.24, 28765 ],
- [ 408200, 0.32, 65497 ],
- [ 612350, 0.35, 93257 ],
- [ Infinity, 0.37, 164709.50 ]
- ],
- [FilingStatus.Single]: [
- [ 9700, 0.10, 0 ],
- [ 39475, 0.12, 970 ],
- [ 84200, 0.22, 4543 ],
- [ 160725, 0.24, 14382.50 ],
- [ 204100, 0.32, 32748.50 ],
- [ 510300, 0.35, 46628.50 ],
- [ Infinity, 0.37, 153798.50 ]
- ],
- [FilingStatus.MarriedFilingSeparate]: [
- [ 9700, 0.10, 0 ],
- [ 39475, 0.12, 970 ],
- [ 84200, 0.22, 4543 ],
- [ 160725, 0.24, 14382.50 ],
- [ 204100, 0.32, 32748.50 ],
- [ 306175, 0.35, 46628.50 ],
- [ Infinity, 0.37, 82354.75 ]
- ]
- }[filingStatus];
+export function computeTax(income: number, tr: TaxReturn): number {
+ const f1040 = tr.getForm(Form1040);
+ const taxBrackets = tr.constants.taxBrackets[f1040.filingStatus];
let i = 0;
while (taxBrackets[i][0] < income)
'6': new ComputedLine((tr): number => clampToZero(this.getValue(tr, '4') - this.getValue(tr, '5'))),
'7': new ComputedLine((tr): number => clampToZero(this.getValue(tr, '1') - this.getValue(tr, '6'))),
'8': new ComputedLine((tr): number => {
- switch (tr.getForm(Form1040).filingStatus) {
- case FilingStatus.Single:
- case FilingStatus.MarriedFilingSeparate:
- return 39375;
- case FilingStatus.MarriedFilingJoint:
- return 78750;
- };
+ const fs = tr.getForm(Form1040).filingStatus;
+ return tr.constants.capitalGains.rate0MaxIncome[fs];
}),
'9': new ComputedLine((tr): number => Math.min(this.getValue(tr, '1'), this.getValue(tr, '8'))),
'10': new ComputedLine((tr): number => Math.min(this.getValue(tr, '7'), this.getValue(tr, '9'))),
'13': new ReferenceLine(QDCGTaxWorksheet as any, '11'),
'14': new ComputedLine((tr): number => this.getValue(tr, '12') - this.getValue(tr, '13')),
'15': new ComputedLine((tr): number => {
- switch (tr.getForm(Form1040).filingStatus) {
- case FilingStatus.Single: return 434550;
- case FilingStatus.MarriedFilingSeparate: return 244425;
- case FilingStatus.MarriedFilingJoint: return 488850;
- };
+ const fs = tr.getForm(Form1040).filingStatus;
+ return tr.constants.capitalGains.rate15MaxIncome[fs];
}),
'16': new ComputedLine((tr): number => Math.min(this.getValue(tr, '1'), this.getValue(tr, '15'))),
'17': new ComputedLine((tr): number => this.getValue(tr, '7') + this.getValue(tr, '11')),
return this.getValue(tr, '22') * 0.20;
}, 'Amount taxed at 20%'),
'24': new ComputedLine((tr): number => {
- return computeTax(this.getValue(tr, '7'), tr.getForm(Form1040).filingStatus);
+ return computeTax(this.getValue(tr, '7'), tr);
}, 'Tax on line 7'),
'25': new ComputedLine((tr): number => {
return this.getValue(tr, '20') +
this.getValue(tr, '24');
}),
'26': new ComputedLine((tr): number => {
- return computeTax(this.getValue(tr, '1'), tr.getForm(Form1040).filingStatus);
+ return computeTax(this.getValue(tr, '1'), tr);
}, 'Tax on line 1'),
'27': new ComputedLine((tr): number => {
return Math.min(this.getValue(tr, '25'), this.getValue(tr, '26'));
import { Form, TaxReturn } from '../core';
import { AccumulatorLine, ComputedLine, ReferenceLine, UnsupportedLine, sumFormLines } from '../core/Line';
-import { clampToZero } from '../core/Math';
+import { Literal, clampToZero } from '../core/Math';
import Form1040, { QDCGTaxWorksheet, FilingStatus } from './Form1040';
import Form1099INT from './Form1099INT';
// Part II
'5': new ComputedLine((tr): number => {
- // [ threshold, exemption ]
- const exemptions = {
- [FilingStatus.Single]: [ 510300, 71700 ],
- [FilingStatus.MarriedFilingJoint]: [ 1020600, 111700 ],
- [FilingStatus.MarriedFilingSeparate]: [ 510300, 55850 ],
- };
- const exemption = exemptions[tr.getForm(Form1040).filingStatus];
+ const fs = tr.getForm(Form1040).filingStatus;
+ const exemption = tr.constants.amt.exemption[fs];
+ const phaseout = tr.constants.amt.phaseout[fs];
const l4 = this.getValue(tr, '4');
- if (l4 < exemption[0])
- return exemption[1];
+ if (l4 < phaseout)
+ return exemption;
// Exemption worksheet:
- const wl1 = exemption[1];
+ const wl1 = exemption;
const wl2 = l4;
- const wl3 = exemption[0];
+ const wl3 = phaseout;
const wl4 = clampToZero(wl2 - wl3);
- const wl5 = wl4 * 0.25;
+ const wl5 = wl4 * Literal(0.25);
const wl6 = clampToZero(wl1 - wl5);
return wl6;
}),
if (part3)
return this.getValue(tr, '40');
- return computeAmtTax(f1040.filingStatus, this.getValue(tr, '6'));
+ return computeAmtTax(tr, this.getValue(tr, '6'));
}),
'8': new ReferenceLine(Schedule3, '1', 'Alternative minimum tax foreign tax credit'), // Not supported - AMT FTC recalculation
'9': new ComputedLine((tr): number => {
}),
'16': new ComputedLine((tr): number => Math.min(this.getValue(tr, '12'), this.getValue(tr, '15'))),
'17': new ComputedLine((tr): number => this.getValue(tr, '12') - this.getValue(tr, '16')),
- '18': new ComputedLine((tr): number => {
- const fs = tr.getForm(Form1040).filingStatus;
- return computeAmtTax(fs, this.getValue(tr, '17'));
- }),
+ '18': new ComputedLine((tr): number => computeAmtTax(tr, this.getValue(tr, '17'))),
'19': new ComputedLine((tr): number => {
- switch (tr.getForm(Form1040).filingStatus) {
- case FilingStatus.Single:
- case FilingStatus.MarriedFilingSeparate:
- return 39375;
- case FilingStatus.MarriedFilingJoint:
- return 78750;
- }
+ const fs = tr.getForm(Form1040).filingStatus;
+ return tr.constants.capitalGains.rate0MaxIncome[fs];
}),
'20': new ComputedLine((tr): number => {
const schedDTW = tr.findForm(ScheduleDTaxWorksheet);
'23': new ComputedLine((tr): number => Math.min(this.getValue(tr, '21'), this.getValue(tr, '22'))),
'24': new ComputedLine((tr): number => this.getValue(tr, '22') - this.getValue(tr, '23')),
'25': new ComputedLine((tr): number => {
- switch (tr.getForm(Form1040).filingStatus) {
- case FilingStatus.Single: return 434550;
- case FilingStatus.MarriedFilingSeparate: return 244425;
- case FilingStatus.MarriedFilingJoint: return 488850;
- }
+ const fs = tr.getForm(Form1040).filingStatus;
+ return tr.constants.capitalGains.rate15MaxIncome[fs];
}),
'26': new ReferenceLine(Form6251 as any, '21'),
'27': new ComputedLine((tr): number => {
'36': new ComputedLine((tr): number => clampToZero(this.getValue(tr, '12') - this.getValue(tr, '35'))),
'37': new ComputedLine((tr): number => this.getValue(tr, '36') * 0.25),
'38': new ComputedLine((tr): number => sumFormLines(tr, this, ['18', '31', '34', '37'])),
- '39': new ComputedLine((tr): number => {
- const fs = tr.getForm(Form1040).filingStatus;
- return computeAmtTax(fs, this.getValue(tr, '12'));
- }),
+ '39': new ComputedLine((tr): number => computeAmtTax(tr, this.getValue(tr, '12'))),
'40': new ComputedLine((tr): number => Math.min(this.getValue(tr, '38'), this.getValue(tr, '39'))),
};
};
-function computeAmtTax(filingStatus, amount) {
- const mfs = filingStatus = FilingStatus.MarriedFilingSeparate;
- const limit = mfs ? 97400 : 194800;
- const sub = mfs ? 1948 : 3896;
+function computeAmtTax(tr: TaxReturn, amount) {
+ const fs = tr.getForm(Form1040).filingStatus;
+ const limit = tr.constants.amt.limitForRate28Percent[fs];
+ const sub = limit * 0.02; // Difference between the two rates.
if (amount < limit)
- return amount * 0.26;
- return (amount * 0.28) - sub;
+ return amount * Literal(0.26);
+ return (amount * Literal(0.28)) - sub;
}
tr.addForm(new Schedule2());
expect(form.getValue(tr, '4')).toBe(300000);
- expect(form.getValue(tr, '5')).toBe(Form8959.filingStatusLimit(filingStatus));
+ expect(form.getValue(tr, '5')).toBe(Form8959.filingStatusLimit(tr));
expect(form.getValue(tr, '6')).toBe(filingStatusToResults[filingStatus]['6']);
expect(form.getValue(tr, '18')).toBeCloseTo(form.getValue(tr, '6') * 0.009);
tr.addForm(new Schedule2());
expect(form.getValue(tr, '4')).toBe(110000);
- expect(form.getValue(tr, '5')).toBe(Form8959.filingStatusLimit(filingStatus));
+ expect(form.getValue(tr, '5')).toBe(Form8959.filingStatusLimit(tr));
expect(form.getValue(tr, '6')).toBe(0);
expect(form.getValue(tr, '18')).toBe(0);
return sumFormLines(tr, this, ['1', '2', '3']);
}),
'5': new ComputedLine((tr): number => {
- return Form8959.filingStatusLimit(tr.getForm(Form1040).filingStatus);
+ return tr.constants.medicare.additionalWithholdingLimit[tr.getForm(Form1040).filingStatus];
}),
'6': new ComputedLine((tr): number => {
return clampToZero(this.getValue(tr, '4') - this.getValue(tr, '5'));
}),
'7': new ComputedLine((tr): number => {
- return this.getValue(tr, '6') * 0.009;
+ return this.getValue(tr, '6') * tr.constants.medicare.additionalWithholdingRate;
}, 'Additional Medicare tax on Medicare wages'),
// All of Section 2 and 3 skipped.
'19': new AccumulatorLine(W2, '6', 'Medicare tax withheld'),
'20': new ReferenceLine(Form8959 as any, '1'),
'21': new ComputedLine((tr): number => {
- return this.getValue(tr, '20') * 0.0145;
+ return this.getValue(tr, '20') * tr.constants.medicare.withholdingRate;
}, 'Regular Medicare withholding on Medicare wages'),
'22': new ComputedLine((tr): number => {
return clampToZero(this.getValue(tr, '19') - this.getValue(tr, '21'));
}),
};
- static filingStatusLimit(filingStatus: FilingStatus): number {
- switch (filingStatus) {
- case FilingStatus.Single: return 200000;
- case FilingStatus.MarriedFilingJoint: return 250000;
- case FilingStatus.MarriedFilingSeparate: return 125000;
- }
+ static filingStatusLimit(tr: TaxReturn): number {
+ const filingStatus = tr.getForm(Form1040).filingStatus;
+ return tr.constants.medicare.additionalWithholdingLimit[filingStatus];
}
};
expect(f.getValue(tr, '11')).toBe(0);
expect(f.getValue(tr, '12')).toBe(5555);
expect(f.getValue(tr, '13')).toBe(305555);
- expect(f.getValue(tr, '14')).toBe(Form8960.filingStatusLimit(filingStatus));
+ expect(f.getValue(tr, '14')).toBe(Form8960.filingStatusLimit(tr));
expect(f.getValue(tr, '15')).toBe(filingStatusToResult[filingStatus]);
expect(f.getValue(tr, '16')).toBe(5555);
expect(f.getValue(tr, '17')).toBe(5555 * 0.038);
import { ComputedLine, ReferenceLine, UnsupportedLine, sumFormLines } from '../core/Line';
import { clampToZero, undefinedToZero } from '../core/Math';
+import { Constants } from './TaxReturn';
import Form1040, { FilingStatus } from './Form1040';
import Schedule1 from './Schedule1';
'12': new ComputedLine((tr): number => this.getValue(tr, '8') - this.getValue(tr, '11'), 'Net investment income'),
'13': new ReferenceLine(Form1040, '8b', 'Modified adjusted gross income'),
'14': new ComputedLine((tr): number => {
- return Form8960.filingStatusLimit(tr.getForm(Form1040).filingStatus);
+ return Form8960.filingStatusLimit(tr);
}, 'Threshold'),
'15': new ComputedLine((tr): number => clampToZero(this.getValue(tr, '13') - this.getValue(tr, '14'))),
'16': new ComputedLine((tr): number => Math.min(this.getValue(tr, '12'), this.getValue(tr, '15'))),
- '17': new ComputedLine((tr): number => this.getValue(tr, '16') * 0.038, 'Net investment income tax'),
+ '17': new ComputedLine((tr): number => this.getValue(tr, '16') * tr.constants.niit.rate, 'Net investment income tax'),
// 18 - 21 not supported (Estates and Trusts)
};
- static filingStatusLimit(filingStatus: FilingStatus): number {
- switch (filingStatus) {
- case FilingStatus.MarriedFilingJoint: return 250000;
- case FilingStatus.MarriedFilingSeparate: return 125000;
- case FilingStatus.Single: return 200000;
- }
+ static filingStatusLimit(tr: TaxReturn): number {
+ const filingStatus = tr.getForm(Form1040).filingStatus;
+ return tr.constants.niit.limit[filingStatus];
}
};
import { Form, Person, TaxReturn } from '../core';
import { AccumulatorLine, ComputedLine, InputLine, UnsupportedLine } from '../core/Line';
-import { clampToZero } from '../core/Math';
+import { Literal, clampToZero } from '../core/Math';
import Form1040 from './Form1040';
import Form1099DIV from './Form1099DIV';
'28': new AccumulatorLine(Form1099DIV, '5', 'Qualified REIT dividends'),
'29': new InputLine<Form8995REITInput>('qualifiedReitDividendCarryforward', undefined, 0),
'30': new ComputedLine((tr): number => clampToZero(this.getValue(tr, '28') + this.getValue(tr, '29'))),
- '31': new ComputedLine((tr): number => this.getValue(tr, '30') * 0.20, 'REIT and PTP component'),
+ '31': new ComputedLine((tr): number => this.getValue(tr, '30') * Literal(0.20), 'REIT and PTP component'),
'32': new ComputedLine((tr): number => this.getValue(tr, '27') + this.getValue(tr, '31'), 'QBI deduction before limitation'),
'33': new ComputedLine((tr): number => {
const f1040 = tr.getForm(Form1040);
return value;
}, 'Net capital gain'),
'35': new ComputedLine((tr): number => clampToZero(this.getValue(tr, '33') - this.getValue(tr, '34'))),
- '36': new ComputedLine((tr): number => this.getValue(tr, '35') * 0.20, 'Income limitation'),
+ '36': new ComputedLine((tr): number => this.getValue(tr, '35') * Literal(0.20), 'Income limitation'),
'37': new ComputedLine((tr): number => Math.min(this.getValue(tr, '32'), this.getValue(tr, '36'))),
'38': new UnsupportedLine('DPAD under section 199A(g) allocated from an agricultural or horticultural cooperative'),
'39': new ComputedLine((tr): number => this.getValue(tr, '37') + this.getValue(tr, '38')),
}),
'4': new InputLine<SALTWorksheetInput>('prevYearItemizedDeductions'),
'5': new ComputedLine((tr): number => {
- switch (this.getInput('prevYearFilingStatus')) {
- case FilingStatus.Single:
- case FilingStatus.MarriedFilingSeparate:
- return 12000;
- case FilingStatus.MarriedFilingJoint:
- return 24000;
- }
+ const fs = this.getInput('prevYearFilingStatus');
+ return tr.constants.prevYearStandardDeduction[fs];
}, 'Previous year standard deduction'),
'6': new ComputedLine((tr): number => 0, 'Special situations'), // Not supported
'7': new ComputedLine((tr): number => this.getValue(tr, '5') + this.getValue(tr, '6')),
let value = 0;
// Additional medicare tax.
- if (wages > Form8959.filingStatusLimit(filingStatus)) {
+ if (wages > Form8959.filingStatusLimit(tr)) {
value += tr.getForm(Form8959).getValue(tr, '18');
}
// Net investment income tax.
- if (wages > Form8960.filingStatusLimit(filingStatus) &&
+ if (wages > Form8960.filingStatusLimit(tr) &&
(tr.findForms(Form1099DIV).length || tr.findForms(Form1099INT).length)) {
value += tr.getForm(Form8960).getValue(tr, '17');
}
const totalForeignTax = (new AccumulatorLine(Form1099DIV, '7')).value(tr) +
(new AccumulatorLine(Form1099INT, '6')).value(tr);
- const limit = f1040.filingStatus == FilingStatus.MarriedFilingJoint ? 600 : 300;
+ const limit = tr.constants.foreignTaxCreditWithoutForm1116Limit[f1040.filingStatus];
if (totalForeignTax < limit) {
const sched2l2 = new ReferenceLine(Schedule2, '2', undefined, 0);
// Medical and dental expenses
'1': new Input('medicalAndDentalExpenses', 'Medical and dental expenses', 0),
'2': new ReferenceLine(Form1040, '8b'),
- '3': new ComputedLine((tr): number => this.getValue(tr, '2') * 0.075),
+ '3': new ComputedLine((tr): number => this.getValue(tr, '2') * tr.constants.medicalDeductionLimitationPercent),
'4': new ComputedLine((tr): number => clampToZero(this.getValue(tr, '1') - this.getValue(tr, '3'))),
// Taxes you paid
'5d': new ComputedLine((tr): number => sumFormLines(tr, this, ['5a', '5b', '5c'])),
'5e': new ComputedLine((tr): number => {
const fs = tr.getForm(Form1040).filingStatus;
- const limit = fs == FilingStatus.MarriedFilingSeparate ? 5000 : 10000;
+ const limit = tr.constants.saltLimit[fs];
return Math.min(this.getValue(tr, '5d'), limit);
}),
'6': new Input('otherTaxes', 'Other taxes', 0),
import { Form, Person, TaxReturn } from '../core';
import { Line, AccumulatorLine, ComputedLine, ReferenceLine, UnsupportedLine, sumFormLines, sumLineOfForms } from '../core/Line';
-import { clampToZero } from '../core/Math';
+import { Literal, clampToZero } from '../core/Math';
import { NotFoundError, UnsupportedFeatureError } from '../core/Errors';
import Form8949, { Form8949Box } from './Form8949';
readonly name = 'Schedule D Tax Worksheet';
readonly lines = {
- '1': new ReferenceLine(Form1040, '11b'),
- '2': new ReferenceLine(Form1040, '3a'),
+ '1': new ReferenceLine(Form1040, '11b', 'Taxable income'),
+ '2': new ReferenceLine(Form1040, '3a', 'Qualified dividends'),
'3': new UnsupportedLine('Form 4952@4g'),
'4': new UnsupportedLine('Form 4952@4e'),
'5': new ComputedLine((tr): number => 0),
'7': new ComputedLine((tr): number => {
const schedD = tr.getForm(ScheduleD);
return Math.min(schedD.getValue(tr, '15'), schedD.getValue(tr, '16'));
- }),
+ }, 'Capital loss'),
'8': new ComputedLine((tr): number => {
return Math.min(this.getValue(tr, '3'), this.getValue(tr, '4'));
}),
'11': new ComputedLine((tr): number => {
const schedD = tr.getForm(ScheduleD);
return schedD.getValue(tr, '18') + schedD.getValue(tr, '19');
- }),
+ }, '28% gains and unrecaptured gains'),
'12': new ComputedLine((tr): number => Math.min(this.getValue(tr, '9'), this.getValue(tr, '11'))),
'13': new ComputedLine((tr): number => this.getValue(tr, '10') - this.getValue(tr, '12')),
'14': new ComputedLine((tr): number => clampToZero(this.getValue(tr, '1') - this.getValue(tr, '13'))),
'15': new ComputedLine((tr): number => {
- switch (tr.getForm(Form1040).filingStatus) {
- case FilingStatus.Single:
- case FilingStatus.MarriedFilingSeparate:
- return 39375;
- case FilingStatus.MarriedFilingJoint:
- return 78750;
- }
+ const fs = tr.getForm(Form1040).filingStatus;
+ return tr.constants.capitalGains.rate0MaxIncome[fs];
}),
'16': new ComputedLine((tr): number => Math.min(this.getValue(tr, '1'), this.getValue(tr, '15'))),
'17': new ComputedLine((tr): number => Math.min(this.getValue(tr, '14'), this.getValue(tr, '16'))),
'18': new ComputedLine((tr): number => clampToZero(this.getValue(tr, '1') - this.getValue(tr, '10'))),
'19': new ComputedLine((tr): number => {
- let threshold: number;
- switch (tr.getForm(Form1040).filingStatus) {
- case FilingStatus.Single:
- case FilingStatus.MarriedFilingSeparate:
- threshold = 160725;
- break;
- case FilingStatus.MarriedFilingJoint:
- threshold = 321450;
- break;
- }
+ const fs = tr.getForm(Form1040).filingStatus;
+ const threshold = tr.constants.qualifiedBusinessIncomeDeductionThreshold[fs];
return Math.min(this.getValue(tr, '1'), threshold);
}),
'20': new ComputedLine((tr): number => Math.min(this.getValue(tr, '14'), this.getValue(tr, '19'))),
'21': new ComputedLine((tr): number => Math.max(this.getValue(tr, '18'), this.getValue(tr, '20'))),
- '22': new ComputedLine((tr): number => this.getValue(tr, '16') - this.getValue(tr, '17')),
+ '22': new ComputedLine((tr): number => this.getValue(tr, '16') - this.getValue(tr, '17'), 'Amount taxed at 0%'),
'23': new ComputedLine((tr): number => Math.min(this.getValue(tr, '1'), this.getValue(tr, '13'))),
'24': new ReferenceLine(ScheduleDTaxWorksheet as any, '22'),
'25': new ComputedLine((tr): number => clampToZero(this.getValue(tr, '23') - this.getValue(tr, '24'))),
'26': new ComputedLine((tr): number => {
- switch (tr.getForm(Form1040).filingStatus) {
- case FilingStatus.Single:
- return 434550;
- case FilingStatus.MarriedFilingSeparate:
- return 244425;
- case FilingStatus.MarriedFilingJoint:
- return 488850;
- }
+ const fs = tr.getForm(Form1040).filingStatus;
+ return tr.constants.capitalGains.rate15MaxIncome[fs];
}),
'27': new ComputedLine((tr): number => Math.min(this.getValue(tr, '1'), this.getValue(tr, '26'))),
'28': new ComputedLine((tr): number => this.getValue(tr, '21') + this.getValue(tr, '22')),
'29': new ComputedLine((tr): number => clampToZero(this.getValue(tr, '27') - this.getValue(tr, '28'))),
- '30': new ComputedLine((tr): number => Math.min(this.getValue(tr, '25'), this.getValue(tr, '29'))),
- '31': new ComputedLine((tr): number => this.getValue(tr, '30') * 0.15),
+ '30': new ComputedLine((tr): number => Math.min(this.getValue(tr, '25'), this.getValue(tr, '29')), 'Amount taxed at 15%'),
+ '31': new ComputedLine((tr): number => this.getValue(tr, '30') * Literal(0.15), '15% Tax'),
'32': new ComputedLine((tr): number => this.getValue(tr, '24') + this.getValue(tr, '30')),
- '33': new ComputedLine((tr): number => this.getValue(tr, '23') - this.getValue(tr, '32')),
- '34': new ComputedLine((tr): number => this.getValue(tr, '33') * 0.20),
+ '33': new ComputedLine((tr): number => this.getValue(tr, '23') - this.getValue(tr, '32'), 'Amount taxed at 20%'),
+ '34': new ComputedLine((tr): number => this.getValue(tr, '33') * Literal(0.20), '20% Tax'),
'35': new ComputedLine((tr): number => {
const schedD = tr.getForm(ScheduleD);
return Math.min(this.getValue(tr, '9'), schedD.getValue(tr, '19'));
'37': new ReferenceLine(ScheduleDTaxWorksheet as any, '1'),
'38': new ComputedLine((tr): number => clampToZero(this.getValue(tr, '36') - this.getValue(tr, '37'))),
'39': new ComputedLine((tr): number => clampToZero(this.getValue(tr, '35') - this.getValue(tr, '38'))),
- '40': new ComputedLine((tr): number => this.getValue(tr, '39') * 0.25),
+ '40': new ComputedLine((tr): number => this.getValue(tr, '39') * Literal(0.25), 'Tax on unrecaptured gains'),
'41': new ComputedLine((tr): number => {
const schedD = tr.getForm(ScheduleD);
if (schedD.getValue(tr, '18'))
'43': new ComputedLine((tr): number => {
if (!tr.getForm(ScheduleD).getValue(tr, '18'))
return 0;
- return this.getValue(tr, '42') * 0.28;
- }),
+ return this.getValue(tr, '42') * Literal(0.28);
+ }, '28% gain tax'),
'44': new ComputedLine((tr): number => {
const income = this.getValue(tr, '21');
- return computeTax(income, tr.getForm(Form1040).filingStatus);
- }),
+ return computeTax(income, tr);
+ }, 'Nominal rate tax'),
'45': new ComputedLine((tr): number => {
return sumFormLines(tr, this, ['31', '34', '40', '43', '44']);
- }),
+ }, 'Schedule D tax'),
'46': new ComputedLine((tr): number => {
const income = this.getValue(tr, '1');
- return computeTax(income, tr.getForm(Form1040).filingStatus);
- }),
- '47': new ComputedLine((tr): number => Math.min(this.getValue(tr, '45'), this.getValue(tr, '46'))),
+ return computeTax(income, tr);
+ }, 'Income tax'),
+ '47': new ComputedLine((tr): number => Math.min(this.getValue(tr, '45'), this.getValue(tr, '46')), 'Tax on all taxable income'),
};
};
import Form1040, { FilingStatus } from './Form1040';
+export const Constants = {
+ taxBrackets: {
+ // From https://www.irs.gov/pub/irs-drop/rp-18-57.pdf, Section 3.01 and
+ // https://www.irs.gov/pub/irs-pdf/p17.pdf, 2019 Tax Rate Schedules (p254).
+ // Format is: [ limit-of-taxable-income, marginal-rate, base-tax ]
+ // If Income is over Row[0], pay Row[2] + (Row[1] * (Income - PreviousRow[0]))
+ [FilingStatus.MarriedFilingJoint]: [
+ [ 19400, 0.10, 0 ],
+ [ 78950, 0.12, 1940 ],
+ [ 168400, 0.22, 9086 ],
+ [ 321450, 0.24, 28765 ],
+ [ 408200, 0.32, 65497 ],
+ [ 612350, 0.35, 93257 ],
+ [ Infinity, 0.37, 164709.50 ]
+ ],
+ [FilingStatus.Single]: [
+ [ 9700, 0.10, 0 ],
+ [ 39475, 0.12, 970 ],
+ [ 84200, 0.22, 4543 ],
+ [ 160725, 0.24, 14382.50 ],
+ [ 204100, 0.32, 32748.50 ],
+ [ 510300, 0.35, 46628.50 ],
+ [ Infinity, 0.37, 153798.50 ]
+ ],
+ [FilingStatus.MarriedFilingSeparate]: [
+ [ 9700, 0.10, 0 ],
+ [ 39475, 0.12, 970 ],
+ [ 84200, 0.22, 4543 ],
+ [ 160725, 0.24, 14382.50 ],
+ [ 204100, 0.32, 32748.50 ],
+ [ 306175, 0.35, 46628.50 ],
+ [ Infinity, 0.37, 82354.75 ]
+ ]
+ },
+
+ standardDeduction: {
+ [FilingStatus.MarriedFilingJoint]: 24400,
+ [FilingStatus.Single]: 12200,
+ [FilingStatus.MarriedFilingSeparate]: 12200,
+ },
+
+ niit: {
+ rate: 0.038,
+ limit: {
+ [FilingStatus.MarriedFilingJoint]: 250000,
+ [FilingStatus.MarriedFilingSeparate]: 125000,
+ [FilingStatus.Single]: 200000,
+ },
+ },
+
+ medicare: {
+ withholdingRate: 0.0145,
+ additionalWithholdingRate: 0.009,
+ additionalWithholdingLimit: {
+ [FilingStatus.Single]: 200000,
+ [FilingStatus.MarriedFilingJoint]: 250000,
+ [FilingStatus.MarriedFilingSeparate]: 125000,
+ },
+ },
+
+ capitalGains: {
+ rate0MaxIncome: {
+ [FilingStatus.MarriedFilingJoint]: 78750,
+ [FilingStatus.Single]: 39375,
+ [FilingStatus.MarriedFilingSeparate]: 39375,
+ },
+ rate15MaxIncome: {
+ [FilingStatus.MarriedFilingJoint]: 488850,
+ [FilingStatus.MarriedFilingSeparate]: 244425,
+ [FilingStatus.Single]: 434550,
+ },
+ },
+
+ qualifiedBusinessIncomeDeductionThreshold: {
+ [FilingStatus.MarriedFilingJoint]: 321450, // RP-18-57, Section 3.27 indicates this should be 321400, but it does not match the 24% tax bracket nor Sched D Tax Worksheet line 19.
+ [FilingStatus.MarriedFilingSeparate]: 160725,
+ [FilingStatus.Single]: 160725,
+ },
+
+ foreignTaxCreditWithoutForm1116Limit: {
+ [FilingStatus.MarriedFilingJoint]: 600,
+ [FilingStatus.MarriedFilingSeparate]: 300,
+ [FilingStatus.Single]: 300,
+ },
+
+ saltLimit: {
+ [FilingStatus.MarriedFilingJoint]: 10000,
+ [FilingStatus.Single]: 10000,
+ [FilingStatus.MarriedFilingSeparate]: 5000,
+ },
+
+ medicalDeductionLimitationPercent: 0.075,
+
+ prevYearStandardDeduction: {
+ [FilingStatus.MarriedFilingJoint]: 24000,
+ [FilingStatus.Single]: 12000,
+ [FilingStatus.MarriedFilingSeparate]: 12000,
+ },
+
+ amt: {
+ exemption: {
+ [FilingStatus.MarriedFilingJoint]: 111700,
+ [FilingStatus.Single]: 71700,
+ [FilingStatus.MarriedFilingSeparate]: 55850,
+ },
+ phaseout: {
+ [FilingStatus.MarriedFilingJoint]: 1020600,
+ [FilingStatus.Single]: 510300,
+ [FilingStatus.MarriedFilingSeparate]: 510300,
+ },
+ limitForRate28Percent: {
+ [FilingStatus.MarriedFilingJoint]: 194800,
+ [FilingStatus.Single]: 194800,
+ [FilingStatus.MarriedFilingSeparate]: 97400,
+ },
+ },
+};
+
export default class TaxReturn extends BaseTaxReturn {
+ readonly constants = Constants;
+
get year() { return 2019; }
get includeJointPersonForms() {