This includes the worksheet to calculate the deduction limitation.
{
"name": "ustaxlib",
- "version": "2.0.0",
+ "version": "2.1.0",
"description": "A library for modeling individual US tax returns.",
"repository": "https://github.com/rsesek/ustaxlib",
"scripts": {
--- /dev/null
+// Copyright 2021 Blue Static <https://www.bluestatic.org>
+// This program is free software licensed under the GNU General Public License,
+// version 3.0. The full text of the license can be found in LICENSE.txt.
+// SPDX-License-Identifier: GPL-3.0-only
+
+import { Person } from '../core';
+
+import Form1040, { FilingStatus } from './Form1040';
+import { Form1098, MortgageInterestDeductionWorksheet } from './Form1098';
+import TaxReturn from './TaxReturn';
+
+test('grandfathered debt', () => {
+ const p = Person.self('A');
+ const tr = new TaxReturn();
+ tr.addPerson(p);
+ tr.addForm(new Form1040({
+ filingStatus: FilingStatus.MarriedFilingJoint,
+ }));
+ tr.addForm(new Form1098({
+ recipient: 'Bank',
+ payee: p,
+ mortgageInterestReceived: 20_000,
+ outstandingMortgagePrincipal: 2_000_000,
+ mortgageOriginationDate: new Date('1980-01-02'),
+ }));
+ const ws = new MortgageInterestDeductionWorksheet();
+ tr.addForm(ws);
+
+ expect(ws.getValue(tr, '1')).toBe(2_000_000);
+ expect(ws.getValue(tr, '2')).toBe(0);
+ expect(ws.getValue(tr, '5')).toBe(2_000_000);
+ expect(ws.getValue(tr, '7')).toBe(0);
+ expect(ws.getValue(tr, '11')).toBe(2_000_000);
+ expect(ws.deductibleMortgateInterest(tr)).toBe(20_000);
+});
+
+test('pre-limitation debt', () => {
+ const p = Person.self('A');
+ const tr = new TaxReturn();
+ tr.addPerson(p);
+ tr.addForm(new Form1040({
+ filingStatus: FilingStatus.MarriedFilingJoint,
+ }));
+ tr.addForm(new Form1098({
+ recipient: 'Bank',
+ payee: p,
+ mortgageInterestReceived: 20_000,
+ outstandingMortgagePrincipal: 2_000_000,
+ mortgageOriginationDate: new Date('2010-01-02'),
+ }));
+ const ws = new MortgageInterestDeductionWorksheet();
+ tr.addForm(ws);
+
+ expect(ws.getValue(tr, '1')).toBe(0);
+ expect(ws.getValue(tr, '2')).toBe(2_000_000);
+ expect(ws.getValue(tr, '5')).toBe(2_000_000);
+ expect(ws.getValue(tr, '7')).toBe(0);
+ expect(ws.getValue(tr, '11')).toBe(1_000_000);
+ expect(ws.getValue(tr, '14')).toBe(0.5);
+ expect(ws.getValue(tr, '15')).toBe(10_000);
+ expect(ws.deductibleMortgateInterest(tr)).toBe(10_000);
+});
+
+test('limited debt', () => {
+ const p = Person.self('A');
+ const tr = new TaxReturn();
+ tr.addPerson(p);
+ tr.addForm(new Form1040({
+ filingStatus: FilingStatus.MarriedFilingJoint,
+ }));
+ tr.addForm(new Form1098({
+ recipient: 'Bank',
+ payee: p,
+ mortgageInterestReceived: 20_000,
+ outstandingMortgagePrincipal: 2_000_000,
+ mortgageOriginationDate: new Date('2020-01-02'),
+ }));
+ const ws = new MortgageInterestDeductionWorksheet();
+ tr.addForm(ws);
+
+ expect(ws.getValue(tr, '1')).toBe(0);
+ expect(ws.getValue(tr, '2')).toBe(0);
+ expect(ws.getValue(tr, '5')).toBe(0);
+ expect(ws.getValue(tr, '7')).toBe(2_000_000);
+ expect(ws.getValue(tr, '11')).toBe(750_000);
+ expect(ws.getValue(tr, '14')).toBe(0.375);
+ expect(ws.getValue(tr, '15')).toBe(7_500);
+ expect(ws.deductibleMortgateInterest(tr)).toBe(7_500);
+});
--- /dev/null
+// Copyright 2021 Blue Static <https://www.bluestatic.org>
+// This program is free software licensed under the GNU General Public License,
+// version 3.0. The full text of the license can be found in LICENSE.txt.
+// SPDX-License-Identifier: GPL-3.0-only
+
+import { TaxReturn, Form, Person } from '../core';
+import { InputLine, ComputedLine, FormatType, sumFormLines, sumLineOfForms } from '../core/Line';
+
+import Form1040, { FilingStatus } from './Form1040';
+
+export interface Form1098Input {
+ recipient: string;
+ payee: Person;
+ mortgageInterestReceived: number;
+ // This is used for computing the "average mortgage balance." Consult Pub 936 and enter
+ // a different value than the one on Form 1098 to use that instead for the average
+ // balance calculations.
+ outstandingMortgagePrincipal: number;
+ mortgageOriginationDate: Date;
+ refundOfOverpaidInterest?: number;
+ mortgageInsurancePremiums?: number;
+ pointsPaidOnPurchaseOfPrincipalResidence?: number;
+};
+
+class Input<T extends keyof Form1098Input> extends InputLine<Form1098Input, T> {};
+
+export class Form1098 extends Form<Form1098Input> {
+ readonly name = '1098';
+
+ readonly supportsMultipleCopies = true;
+
+ readonly includeJointPersonForms = true;
+
+ person() { return this.getInput('payee'); }
+
+ readonly lines = {
+ 'recipient': new Input('recipient'),
+ 'payee': new Input('payee'),
+ '1': new Input('mortgageInterestReceived'),
+ '2': new Input('outstandingMortgagePrincipal'),
+ '3': new Input('mortgageOriginationDate'),
+ '4': new Input('refundOfOverpaidInterest'),
+ '5': new Input('mortgageInsurancePremiums'),
+ '6': new Input('pointsPaidOnPurchaseOfPrincipalResidence'),
+ };
+};
+
+const kGrandfatheredDate = new Date('1987-10-13');
+const kLimitationStartDate = new Date('2017-12-15');
+
+// Pub. 936 Worksheet
+export class MortgageInterestDeductionWorksheet extends Form {
+ readonly name = 'Mortgage Interest Deduction Worksheet';
+
+ private get1098sMatchingPredicate(tr: TaxReturn, pred: (Form1098) => boolean): Form1098[] {
+ return tr.findForms(Form1098).filter(pred);
+ }
+
+ deductibleMortgateInterest(tr: TaxReturn): number {
+ const l11 = this.getValue(tr, '11');
+ const l12 = this.getValue(tr, '12');
+ if (l11 < l12) {
+ return this.getValue(tr, '15');
+ }
+ return sumLineOfForms(tr, tr.findForms(Form1098), '1');
+ }
+
+ readonly lines = {
+ '1': new ComputedLine((tr): number => {
+ const f1098s = this.get1098sMatchingPredicate(tr, f => f.getValue(tr, '3') <= kGrandfatheredDate);
+ if (f1098s.length == 0)
+ return 0;
+ return sumLineOfForms(tr, f1098s, '2') / f1098s.length;
+ }, 'Average balance of grandfathered debt'),
+ '2': new ComputedLine((tr): number => {
+ const f1098s = this.get1098sMatchingPredicate(tr, f => {
+ const date = f.getValue(tr, '3');
+ return date >= kGrandfatheredDate && date <= kLimitationStartDate;
+ });
+ if (f1098s.length == 0)
+ return 0;
+ return sumLineOfForms(tr, f1098s, '2') / f1098s.length;
+ }, 'Average balance of pre-limitation debt'),
+ '3': new ComputedLine((tr): number => {
+ if (tr.getForm(Form1040).filingStatus == FilingStatus.MarriedFilingSeparate)
+ return 500_000;
+ return 1_000_000;
+ }),
+ '4': new ComputedLine((tr): number => Math.max(this.getValue(tr, '1'), this.getValue(tr, '3'))),
+ '5': new ComputedLine((tr): number => sumFormLines(tr, this, ['1', '2'])),
+ '6': new ComputedLine((tr): number => Math.min(this.getValue(tr, '4'), this.getValue(tr, '5'))),
+ '7': new ComputedLine((tr): number => {
+ const f1098s = this.get1098sMatchingPredicate(tr, f => f.getValue(tr, '3') > kLimitationStartDate);
+ if (f1098s.length == 0)
+ return 0;
+ return sumLineOfForms(tr, f1098s, '2') / f1098s.length;
+ }, 'Average balance of post-limitation debt'),
+ '8': new ComputedLine((tr): number => {
+ const fs = tr.getForm(Form1040).filingStatus;
+ return tr.constants.mortgatgeInterestDeduction.limit[fs];
+ }),
+ '9': new ComputedLine((tr): number => Math.max(this.getValue(tr, '6'), this.getValue(tr, '8'))),
+ '10': new ComputedLine((tr): number => sumFormLines(tr, this, ['6', '7'])),
+ '11': new ComputedLine((tr): number => Math.min(this.getValue(tr, '9'), this.getValue(tr, '10')), 'Qualified loan limit'),
+
+ '12': new ComputedLine((tr): number => sumFormLines(tr, this, ['1', '2', '7']), 'Total of all average balanaces'),
+ '13': new ComputedLine((tr): number => sumLineOfForms(tr, tr.findForms(Form1098), '1'), 'Total interest paid'),
+ '14': new ComputedLine((tr): number => this.getValue(tr, '11') / this.getValue(tr, '12'), undefined, { formatType: FormatType.Decimal }),
+ '15': new ComputedLine((tr): number => this.getValue(tr, '13') * this.getValue(tr, '14'), 'Deductible home mortgage interest'),
+ '16': new ComputedLine((tr): number => this.getValue(tr, '13') - this.getValue(tr, '15'), 'Non-deductible interest'),
+ };
+};
1040._
- **Schedule A:** Itemized deductions
- **Schedule D:** Capital gains and losses
+- **Form 1098:** Mortgage interest deduction
- **Form 1099-B:** Proceeds from broker transactions
- **Form 1099-DIV:** Dividend income
- **Form 1099-INT:** Interest income
import { clampToZero } from '../core/Math';
import Form1040, { FilingStatus } from './Form1040';
+import { Form1098, MortgageInterestDeductionWorksheet } from './Form1098';
export interface ScheduleAInput {
medicalAndDentalExpenses?: number;
'7': new ComputedLine((tr): number => sumFormLines(tr, this, ['5e', '6'])),
// Interest you paid
- // TODO - Form 1098
- '8a': new UnsupportedLine('Home mortgage interest and points'),
+ '8a': new ComputedLine((tr): number => {
+ if (tr.findForms(Form1098).length == 0)
+ return 0;
+ return tr.getForm(MortgageInterestDeductionWorksheet).deductibleMortgateInterest(tr);
+ }, 'Home mortgage interest and points'),
'8b': new Input('unreportedMortgageInterest', 'Home mortgage interest not reported on Form 1098', 0),
'8c': new Input('unreportedMortagePoints', 'Points not reported on Form 1098', 0),
'8d': new Input('mortgageInsurancePremiums', 'Mortgage insurance premiums', 0),
[FilingStatus.MarriedFilingSeparate]: 97400,
},
},
+
+ mortgatgeInterestDeduction: {
+ limit: {
+ [FilingStatus.MarriedFilingJoint]: 750_000,
+ [FilingStatus.Single]: 750_000,
+ [FilingStatus.MarriedFilingSeparate]: 375_000,
+ },
+ },
};
export default class TaxReturn extends BaseTaxReturn {
export { default as W2 } from './W2';
export * from './Form1040';
+export * from './Form1098';
export * from './Form1099B';
export * from './Form1099R';
export * from './Form1116';
export { default as W2 } from '../fed2019/W2';
export { FilingStatus, Form1040Input, computeTax } from '../fed2019/Form1040';
+export * from '../fed2019/Form1098';
export * from '../fed2019/Form1099B';
export * from '../fed2019/Form1099R';
export * from '../fed2019/Form1116';