Add support for Form 1098 and the mortgage interest deduction to Schedule A.
authorRobert Sesek <rsesek@bluestatic.org>
Fri, 6 Aug 2021 17:04:05 +0000 (13:04 -0400)
committerRobert Sesek <rsesek@bluestatic.org>
Fri, 6 Aug 2021 17:41:28 +0000 (13:41 -0400)
This includes the worksheet to calculate the deduction limitation.

package.json
src/fed2019/Form1098.test.ts [new file with mode: 0644]
src/fed2019/Form1098.ts [new file with mode: 0644]
src/fed2019/README.md
src/fed2019/ScheduleA.ts
src/fed2019/TaxReturn.ts
src/fed2019/index.ts
src/fed2020/index.ts

index 5d0897e2d52f67d97f19571a2b29be0ac775bd1e..7e25e0afbb2ff7fd231e0d8a3d6d8b6fe006d73e 100644 (file)
@@ -1,6 +1,6 @@
 {
   "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": {
diff --git a/src/fed2019/Form1098.test.ts b/src/fed2019/Form1098.test.ts
new file mode 100644 (file)
index 0000000..b7edffc
--- /dev/null
@@ -0,0 +1,89 @@
+// 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);
+});
diff --git a/src/fed2019/Form1098.ts b/src/fed2019/Form1098.ts
new file mode 100644 (file)
index 0000000..55cf078
--- /dev/null
@@ -0,0 +1,112 @@
+// 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'),
+  };
+};
index 01c186c04f094fcccc677ea7ed7d01915fbd1fdb..93fd75f4133f3d1ea88a9e8a2833f5073f9c5000 100644 (file)
@@ -22,6 +22,7 @@ The following forms are at least partially supported:
     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
index 5b238ad381768ab564976253e57d74bd5181cfc7..f0d717e1e1c41bff341d587ce33b85db9038e78e 100644 (file)
@@ -8,6 +8,7 @@ import { Line, ComputedLine, InputLine, ReferenceLine, SymbolicLine, Unsupported
 import { clampToZero } from '../core/Math';
 
 import Form1040, { FilingStatus } from './Form1040';
+import { Form1098, MortgageInterestDeductionWorksheet } from './Form1098';
 
 export interface ScheduleAInput {
   medicalAndDentalExpenses?: number;
@@ -58,8 +59,11 @@ export default class ScheduleA extends Form<ScheduleAInput> {
     '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),
index 808281e19327115eb7db6608244bed764b473e42..803f967f9f3dd94df2a929e647fafb9819d68686 100644 (file)
@@ -129,6 +129,14 @@ export const Constants = {
       [FilingStatus.MarriedFilingSeparate]: 97400,
     },
   },
+
+  mortgatgeInterestDeduction: {
+    limit: {
+      [FilingStatus.MarriedFilingJoint]: 750_000,
+      [FilingStatus.Single]: 750_000,
+      [FilingStatus.MarriedFilingSeparate]: 375_000,
+    },
+  },
 };
 
 export default class TaxReturn extends BaseTaxReturn {
index 78e3b757c8bdc31c96c5965f13efa18e2e14618f..b72c17f85dbd9d7a2f279f4a61c1dc055790a619 100644 (file)
@@ -24,6 +24,7 @@ export { default as TaxReturn } from './TaxReturn';
 export { default as W2 } from './W2';
 
 export * from './Form1040';
+export * from './Form1098';
 export * from './Form1099B';
 export * from './Form1099R';
 export * from './Form1116';
index de94206de4597e3a86ab3360745bef4eb8c00240..7e5fb29693cdc3096d4ef255f0256240aebc5360 100644 (file)
@@ -28,6 +28,7 @@ export { default as ScheduleD } from '../fed2019/ScheduleD';
 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';