diff --git a/public/forms/Y2023/irs/f1040.pdf b/public/forms/Y2023/irs/f1040.pdf new file mode 100644 index 000000000..a4888bc2c Binary files /dev/null and b/public/forms/Y2023/irs/f1040.pdf differ diff --git a/public/forms/Y2023/irs/f1040s1.pdf b/public/forms/Y2023/irs/f1040s1.pdf new file mode 100644 index 000000000..47185742a Binary files /dev/null and b/public/forms/Y2023/irs/f1040s1.pdf differ diff --git a/public/forms/Y2023/irs/f1040s2.pdf b/public/forms/Y2023/irs/f1040s2.pdf new file mode 100644 index 000000000..a8cd60980 Binary files /dev/null and b/public/forms/Y2023/irs/f1040s2.pdf differ diff --git a/public/forms/Y2023/irs/f1040s3.pdf b/public/forms/Y2023/irs/f1040s3.pdf new file mode 100644 index 000000000..05ce61ee5 Binary files /dev/null and b/public/forms/Y2023/irs/f1040s3.pdf differ diff --git a/public/forms/Y2023/irs/f1040s8.pdf b/public/forms/Y2023/irs/f1040s8.pdf new file mode 100644 index 000000000..3637af294 Binary files /dev/null and b/public/forms/Y2023/irs/f1040s8.pdf differ diff --git a/public/forms/Y2023/irs/f1040sa.pdf b/public/forms/Y2023/irs/f1040sa.pdf new file mode 100644 index 000000000..81046351f Binary files /dev/null and b/public/forms/Y2023/irs/f1040sa.pdf differ diff --git a/public/forms/Y2023/irs/f1040sb.pdf b/public/forms/Y2023/irs/f1040sb.pdf new file mode 100644 index 000000000..a06815de2 Binary files /dev/null and b/public/forms/Y2023/irs/f1040sb.pdf differ diff --git a/public/forms/Y2023/irs/f1040sd.pdf b/public/forms/Y2023/irs/f1040sd.pdf new file mode 100644 index 000000000..73fc0a3ca Binary files /dev/null and b/public/forms/Y2023/irs/f1040sd.pdf differ diff --git a/public/forms/Y2023/irs/f1040se.pdf b/public/forms/Y2023/irs/f1040se.pdf new file mode 100644 index 000000000..0abcd3931 Binary files /dev/null and b/public/forms/Y2023/irs/f1040se.pdf differ diff --git a/public/forms/Y2023/irs/f1040sei.pdf b/public/forms/Y2023/irs/f1040sei.pdf new file mode 100644 index 000000000..012fd96c5 Binary files /dev/null and b/public/forms/Y2023/irs/f1040sei.pdf differ diff --git a/public/forms/Y2023/irs/f1040sse.pdf b/public/forms/Y2023/irs/f1040sse.pdf new file mode 100644 index 000000000..e2d900552 Binary files /dev/null and b/public/forms/Y2023/irs/f1040sse.pdf differ diff --git a/public/forms/Y2023/irs/f1040v.pdf b/public/forms/Y2023/irs/f1040v.pdf new file mode 100644 index 000000000..02e4f7545 Binary files /dev/null and b/public/forms/Y2023/irs/f1040v.pdf differ diff --git a/public/forms/Y2023/irs/f6251.pdf b/public/forms/Y2023/irs/f6251.pdf new file mode 100644 index 000000000..10ed53558 Binary files /dev/null and b/public/forms/Y2023/irs/f6251.pdf differ diff --git a/public/forms/Y2023/irs/f8889.pdf b/public/forms/Y2023/irs/f8889.pdf new file mode 100644 index 000000000..125cd71e4 Binary files /dev/null and b/public/forms/Y2023/irs/f8889.pdf differ diff --git a/public/forms/Y2023/irs/f8949.pdf b/public/forms/Y2023/irs/f8949.pdf new file mode 100644 index 000000000..99d1d71c3 Binary files /dev/null and b/public/forms/Y2023/irs/f8949.pdf differ diff --git a/public/forms/Y2023/irs/f8959.pdf b/public/forms/Y2023/irs/f8959.pdf new file mode 100644 index 000000000..fc35bc40c Binary files /dev/null and b/public/forms/Y2023/irs/f8959.pdf differ diff --git a/public/forms/Y2023/irs/f8960.pdf b/public/forms/Y2023/irs/f8960.pdf new file mode 100644 index 000000000..409331e80 Binary files /dev/null and b/public/forms/Y2023/irs/f8960.pdf differ diff --git a/public/forms/Y2023/irs/f8995.pdf b/public/forms/Y2023/irs/f8995.pdf new file mode 100644 index 000000000..20864aa61 Binary files /dev/null and b/public/forms/Y2023/irs/f8995.pdf differ diff --git a/public/forms/Y2023/irs/f8995a.pdf b/public/forms/Y2023/irs/f8995a.pdf new file mode 100644 index 000000000..815117059 Binary files /dev/null and b/public/forms/Y2023/irs/f8995a.pdf differ diff --git a/src/core/data/index.ts b/src/core/data/index.ts index 111b1ccf6..e01477149 100644 --- a/src/core/data/index.ts +++ b/src/core/data/index.ts @@ -4,7 +4,8 @@ export enum TaxYears { Y2019 = 2019, Y2020 = 2020, Y2021 = 2021, - Y2022 = 2022 + Y2022 = 2022, + Y2023 = 2023 } export type TaxYear = keyof typeof TaxYears diff --git a/src/forms/Y2023/data/federal.ts b/src/forms/Y2023/data/federal.ts new file mode 100644 index 000000000..83620f1a5 --- /dev/null +++ b/src/forms/Y2023/data/federal.ts @@ -0,0 +1,403 @@ +import { FilingStatus } from 'ustaxes/core/data' +import { linear, Piecewise } from 'ustaxes/core/util' + +export const CURRENT_YEAR = 2023 + +interface TaggedAmount { + name: string + amount: number +} + +interface Brackets { + brackets: number[] +} + +interface Deductions { + deductions: TaggedAmount[] + exemptions: TaggedAmount[] +} + +interface Rates { + rates: number[] +} + +interface FederalBrackets { + ordinary: Rates & { status: { [key in FilingStatus]: Brackets & Deductions } } + longTermCapGains: Rates & { status: { [key in FilingStatus]: Brackets } } +} + +// Tax brackets can be most easily found via google +// The standard deduction amounts with the allowances can be most +// easily found at the end of 1040-SR +const federalBrackets: FederalBrackets = { + ordinary: { + rates: [10, 12, 22, 24, 32, 35, 37], + status: { + [FilingStatus.S]: { + brackets: [11000, 44725, 95375, 182100, 231250, 578125], + deductions: [ + { + name: 'Standard Deduction (Single)', + amount: 13850 + }, + { + name: 'Standard Deduction (Single) with 1 age or blindness allowance', + amount: 15700 + }, + { + name: 'Standard Deduction (Single) with 2 age or blindness allowances', + amount: 17750 + } + ], + exemptions: [ + { + name: 'Standard Exemption (Single)', + amount: 0 + } + ] + }, + [FilingStatus.MFJ]: { + brackets: [22000, 89450, 190750, 364200, 462500, 693750], + deductions: [ + { + name: 'Standard Deduction (Married)', + amount: 27700 + }, + { + name: 'Standard Deduction (Married) with 1 age or blindness allowance', + amount: 29200 + }, + { + name: 'Standard Deduction (Married) with 2 age or blindness allowances', + amount: 30700 + }, + { + name: 'Standard Deduction (Married) with 3 age or blindness allowances', + amount: 32200 + }, + { + name: 'Standard Deduction (Married) with 4 age or blindness allowances', + amount: 33700 + } + ], + exemptions: [ + { + name: 'Standard Exemption (Single)', + amount: 0 + } + ] + }, + [FilingStatus.W]: { + brackets: [22000, 89450, 190750, 364200, 462500, 693750], + deductions: [ + { + name: 'Standard Deduction (Widowed)', + amount: 27700 + }, + { + name: 'Standard Deduction (Widowed) with 1 age or blindness allowance', + amount: 29200 + }, + { + name: 'Standard Deduction (Widowed) with 2 age or blindness allowances', + amount: 30700 + } + ], + exemptions: [ + { + name: 'Standard Exemption (Widowed)', + amount: 0 + } + ] + }, + [FilingStatus.MFS]: { + brackets: [11000, 44725, 95375, 182100, 231250, 346875], + deductions: [ + { + name: 'Standard Deduction (Married Filing Separately)', + amount: 13850 + }, + { + name: 'Standard Deduction (Married Filing Separately) with 1 age or blindness allowance', + amount: 15350 + }, + { + name: 'Standard Deduction (Married Filing Separately) with 2 age or blindness allowances', + amount: 16850 + }, + { + name: 'Standard Deduction (Married Filing Separately) with 3 age or blindness allowances', + amount: 18350 + }, + { + name: 'Standard Deduction (Married Filing Separately) with 4 age or blindness allowances', + amount: 19850 + } + ], + exemptions: [ + { + name: 'Standard Exemption (Single)', + amount: 0 + } + ] + }, + [FilingStatus.HOH]: { + brackets: [15700, 59850, 95350, 182100, 231250, 578100], + deductions: [ + { + name: 'Standard Deduction (Head of Household)', + amount: 20800 + }, + { + name: 'Standard Deduction (Head of Household) with 1 age or blindness allowance', + amount: 22650 + }, + { + name: 'Standard Deduction (Head of Household) with 2 age or blindness allowances', + amount: 24500 + } + ], + exemptions: [ + { + name: 'Standard Exemption (Single)', + amount: 0 + } + ] + } + } + }, + longTermCapGains: { + rates: [0, 15, 20], + status: { + [FilingStatus.S]: { + brackets: [44625, 492300] + }, + [FilingStatus.MFJ]: { + brackets: [89250, 553850] + }, + [FilingStatus.W]: { + brackets: [89250, 553850] + }, + [FilingStatus.MFS]: { + brackets: [44625, 276900] + }, + [FilingStatus.HOH]: { + brackets: [59750, 523050] + } + } + } +} + +export const fica = { + maxSSTax: 9932.4, + maxIncomeSSTaxApplies: 160200, + + regularMedicareTaxRate: 1.45 / 100, + additionalMedicareTaxRate: 0.9 / 100, + additionalMedicareTaxThreshold: (filingStatus: FilingStatus): number => { + switch (filingStatus) { + case FilingStatus.MFJ: { + return 250000 + } + case FilingStatus.MFS: { + return 125000 + } + default: { + return 200000 // Single, Head of Household, Windower + } + } + } +} + +// Net Investment Income Tax calculated on form 8960 +export const netInvestmentIncomeTax = { + taxRate: 0.038, // 3.8% + taxThreshold: (filingStatus: FilingStatus): number => { + switch (filingStatus) { + case FilingStatus.MFJ: { + return 250000 + } + case FilingStatus.W: { + return 250000 + } + case FilingStatus.MFS: { + return 125000 + } + default: { + return 200000 // Single, Head of Household + } + } + } +} + +export const healthSavingsAccounts = { + contributionLimit: { + 'self-only': 3850, + family: 7750 + } +} + +export const amt = { + excemption: ( + filingStatus: FilingStatus, + income: number + ): number | undefined => { + switch (filingStatus) { + case FilingStatus.S: + if (income <= 578150) { + return 81300 + } + break + case FilingStatus.MFJ: + if (income <= 1156300) { + return 126500 + } + break + case FilingStatus.MFS: + if (income <= 578150) { + return 63250 + } + } + // TODO: Handle "Exemption Worksheet" + return undefined + }, + + cap: (filingStatus: FilingStatus): number => { + if (filingStatus === FilingStatus.MFS) { + return 110350 + } + return 220700 + } +} + +// https://www.irs.gov/credits-deductions/individuals/earned-income-tax-credit/earned-income-and-earned-income-tax-credit-eitc-tables#EITC%20Tables +// line 11 caps based on step one in instructions +const line11Caps = [17640, 46450, 52918, 56838] +const line11MfjCaps = [24210, 53120, 59478, 63398] + +type Point = [number, number] + +// Provided a list of points, create a piecewise function +// that makes linear segments through the list of points. +const toPieceWise = (points: Point[]): Piecewise => + points + .slice(0, points.length - 1) + .map((point, idx) => [point, points[idx + 1]]) + .map(([[x1, y1], [x2, y2]]) => ({ + // starting point slope intercept + lowerBound: x1, + f: linear((y2 - y1) / (x2 - x1), y1 - (x1 * (y2 - y1)) / (x2 - x1)) + })) + +// These points are taken directly from IRS publication +// IRS Rev. Proc. 2022-38 for tax year 2023 +// https://www.irs.gov/pub/irs-drop/rp-22-38.pdf +const unmarriedFormulas: Piecewise[] = (() => { + const points: Point[][] = [ + [ + [0, 0], + [7840, 600], + [9800, 600], + [17640, 0] + ], // 0 + [ + [0, 0], + [11750, 3995], + [21560, 3995], + [46560, 0] + ], // 1 + [ + [0, 0], + [16510, 6604], + [21560, 6604], + [52918, 0] + ], // 2 + [ + [0, 0], + [16510, 7430], + [21560, 7430], + [56838, 0] + ] // 3 or more + ] + return points.map((ps: Point[]) => toPieceWise(ps)) +})() + +const marriedFormulas: Piecewise[] = (() => { + const points: Point[][] = [ + [ + [0, 0], + [7840, 600], + [16370, 3995], + [24210, 0] + ], // 0 + [ + [0, 0], + [11750, 3995], + [28120, 3995], + [53120, 0] + ], // 1 + [ + [0, 0], + [16510, 6604], + [28120, 6604], + [59478, 0] + ], // 2 + [ + [0, 0], + [16510, 7430], + [28120, 7430], + [63398, 0] + ] // 3 or more + ] + return points.map((ps) => toPieceWise(ps)) +})() + +interface EICDef { + caps: { [k in FilingStatus]: number[] | undefined } + maxInvestmentIncome: number + formulas: { [k in FilingStatus]: Piecewise[] | undefined } +} + +export const QualifyingDependents = { + childMaxAge: 17, + qualifyingDependentMaxAge: 19, + qualifyingStudentMaxAge: 24 +} + +export const EIC: EICDef = { + // credit caps for number of children (0, 1, 2, 3 or more): + // Step 1 + caps: { + [FilingStatus.S]: line11Caps, + [FilingStatus.W]: line11Caps, + [FilingStatus.HOH]: line11Caps, + [FilingStatus.MFS]: undefined, + [FilingStatus.MFJ]: line11MfjCaps + }, + maxInvestmentIncome: 11000, + formulas: { + [FilingStatus.S]: unmarriedFormulas, + [FilingStatus.W]: unmarriedFormulas, + [FilingStatus.HOH]: unmarriedFormulas, + [FilingStatus.MFS]: undefined, + [FilingStatus.MFJ]: marriedFormulas + } +} + +export default federalBrackets + +// Constants used in the social security benefits worksheet +interface SocialSecurityBenefitsDef { + caps: { [k in FilingStatus]: { l8: number; l10: number } } +} + +// TODO: update for Y2023 +export const SSBenefits: SocialSecurityBenefitsDef = { + caps: { + [FilingStatus.S]: { l8: 25000, l10: 9000 }, + [FilingStatus.W]: { l8: 25000, l10: 9000 }, + [FilingStatus.HOH]: { l8: 25000, l10: 9000 }, + [FilingStatus.MFS]: { l8: 25000, l10: 9000 }, + [FilingStatus.MFJ]: { l8: 32000, l10: 12000 } + } +} diff --git a/src/forms/Y2023/irsForms/F1040.ts b/src/forms/Y2023/irsForms/F1040.ts new file mode 100644 index 000000000..ce20b1c87 --- /dev/null +++ b/src/forms/Y2023/irsForms/F1040.ts @@ -0,0 +1,649 @@ +import { + AccountType, + Dependent, + FilingStatus, + IncomeW2, + PersonRole, + PlanType1099, + Asset +} from 'ustaxes/core/data' +import federalBrackets, { CURRENT_YEAR } from '../data/federal' +import F4972 from './F4972' +import F5695 from './F5695' +import F8814 from './F8814' +import F8888 from './F8888' +import F8889 from './F8889' +import F8910 from './F8910' +import F8936 from './F8936' +import F8959 from './F8959' +import F8995, { getF8995PhaseOutIncome } from './F8995' +import F8995A from './F8995A' +import Schedule1 from './Schedule1' +import Schedule2 from './Schedule2' +import Schedule3 from './Schedule3' +import Schedule8812 from './Schedule8812' +import ScheduleA from './ScheduleA' +import ScheduleD from './ScheduleD' +import ScheduleE from './ScheduleE' +import ScheduleSE from './ScheduleSE' +import ScheduleEIC from './ScheduleEIC' +import ScheduleR from './ScheduleR' +import Form, { FormTag } from 'ustaxes/core/irsForms/Form' +import { sumFields } from 'ustaxes/core/irsForms/util' +import ScheduleB from './ScheduleB' +import { computeOrdinaryTax } from './TaxTable' +import SDQualifiedAndCapGains from './worksheets/SDQualifiedAndCapGains' +import QualifyingDependents from './worksheets/QualifyingDependents' +import SocialSecurityBenefitsWorksheet from './worksheets/SocialSecurityBenefits' +import F4797 from './F4797' +import StudentLoanInterestWorksheet from './worksheets/StudentLoanInterestWorksheet' +import F1040V from './F1040v' +import _ from 'lodash' +import F8960 from './F8960' +import F4952 from './F4952' +import F2555 from './F2555' +import F4563 from './F4563' +import F8863 from './F8863' +import F8962 from './F8962' +import F4136 from './F4136' +import F2439 from './F2439' +import F2441 from './F2441' +import ScheduleC from './ScheduleC' +import F8949 from './F8949' +import F6251 from './F6251' +import F4137 from './F4137' +import F8919 from './F8919' +import F8853 from './F8853' +import F8582 from './F8582' +import { Field } from 'ustaxes/core/pdfFiller' +import F1040Base, { ValidatedInformation } from 'ustaxes/forms/F1040Base' +import F1040Attachment from './F1040Attachment' + +export default class F1040 extends F1040Base { + tag: FormTag = 'f1040' + sequenceIndex = 0 + + assets: Asset[] + + schedule1: Schedule1 + schedule2: Schedule2 + schedule3: Schedule3 + scheduleA: ScheduleA + scheduleB: ScheduleB + scheduleC?: ScheduleC + scheduleD: ScheduleD + scheduleE: ScheduleE + scheduleSE: ScheduleSE + scheduleEIC: ScheduleEIC + scheduleR?: ScheduleR + schedule8812: Schedule8812 + f2439?: F2439 + f2441?: F2441 + f2555?: F2555 + f4136?: F4136 + f4137?: F4137 + f4563?: F4563 + f4797?: F4797 + f4952?: F4952 + f4972?: F4972 + f5695?: F5695 + f6251: F6251 + f8814?: F8814 + f8582?: F8582 + f8853?: F8853 + f8863?: F8863 + f8888?: F8888 + f8889: F8889 + f8889Spouse?: F8889 + f8910?: F8910 + f8919?: F8919 + f8936?: F8936 + f8949: F8949 + _f8949s?: F8949[] + f8959: F8959 + f8960: F8960 + f8962?: F8962 + f8995?: F8995 | F8995A + qualifiedAndCapGainsWorksheet?: SDQualifiedAndCapGains + studentLoanInterestWorksheet?: StudentLoanInterestWorksheet + socialSecurityBenefitsWorksheet?: SocialSecurityBenefitsWorksheet + + qualifyingDependents: QualifyingDependents + + constructor(info: ValidatedInformation, assets: Asset[]) { + super(info) + this.assets = assets + this.qualifyingDependents = new QualifyingDependents(this) + + this.scheduleA = new ScheduleA(this) + this.scheduleB = new ScheduleB(this) + this.scheduleD = new ScheduleD(this) + this.scheduleE = new ScheduleE(this) + this.scheduleEIC = new ScheduleEIC(this) + this.scheduleSE = new ScheduleSE(this) + + this.schedule1 = new Schedule1(this) + this.schedule2 = new Schedule2(this) + this.schedule3 = new Schedule3(this) + this.schedule8812 = new Schedule8812(this) + + this.f6251 = new F6251(this) + this.f8949 = new F8949(this) + this.f8889 = new F8889(this, this.info.taxPayer.primaryPerson) + + // add in separate form 8889 for the spouse + if (this.info.taxPayer.spouse) { + this.f8889Spouse = new F8889(this, this.info.taxPayer.spouse) + } + + this.f8959 = new F8959(this) + this.f8960 = new F8960(this) + + if (this.f1099ssas().length > 0) { + const ssws = new SocialSecurityBenefitsWorksheet(this) + this.socialSecurityBenefitsWorksheet = ssws + } + + if (this.info.f1098es.length > 0) { + this.studentLoanInterestWorksheet = new StudentLoanInterestWorksheet( + this, + this.info.f1098es + ) + } + + if (this.totalQbi() > 0) { + const formAMinAmount = getF8995PhaseOutIncome( + this.info.taxPayer.filingStatus + ) + if (this.l11() - this.l12() >= formAMinAmount) { + this.f8995 = new F8995A(this) + } else { + this.f8995 = new F8995(this) + } + } + } + + get f8949s(): F8949[] { + if (this._f8949s === undefined) { + this._f8949s = [this.f8949, ...this.f8949.copies()] + } + return this._f8949s + } + + totalQbi = () => + this.info.scheduleK1Form1065s + .map((k1) => k1.section199AQBI) + .reduce((c, a) => c + a, 0) + + toString = (): string => ` + Form 1040 generated from information: + Information: + ${JSON.stringify(this.info)} + ` + + // TODO - get from W2 box 12, code Q + nonTaxableCombatPay = (): number | undefined => undefined + + schedules = (): Form[] => { + const res1: (F1040Attachment | undefined)[] = [ + this.scheduleA, + this.scheduleB, + this.scheduleD, + this.scheduleE, + this.scheduleSE, + this.scheduleR, + this.scheduleEIC, + this.schedule8812, + this.f4797, + this.f4952, + this.f4972, + this.f5695, + this.f6251, + this.f8814, + this.f8888, + this.f8889, + this.f8889Spouse, + this.f8910, + this.f8936, + this.f8949, + this.f8959, + this.f8960, + this.f8995, + this.schedule1, + this.schedule2, + this.schedule3 + ] + const res = _.compact(res1) + .filter((f) => f.isNeeded()) + .flatMap((f) => [f, ...f.copies()]) + + // Attach payment voucher to front if there is a payment due + if (this.l37() > 0) { + res.push(new F1040V(this)) + } + + return [this, ...res].sort((a, b) => a.sequenceIndex - b.sequenceIndex) + } + + // born before 1959/01/02 + bornBeforeDate = (): boolean => + this.info.taxPayer.primaryPerson.dateOfBirth < + new Date(CURRENT_YEAR - 64, 0, 2) + + blind = (): boolean => this.info.taxPayer.primaryPerson.isBlind + + spouseBeforeDate = (): boolean => + (this.info.taxPayer.spouse?.dateOfBirth ?? new Date()) < + new Date(CURRENT_YEAR - 64, 0, 2) + + spouseBlind = (): boolean => this.info.taxPayer.spouse?.isBlind ?? false + + validW2s = (): IncomeW2[] => { + if (this.info.taxPayer.filingStatus === FilingStatus.MFS) { + return this.info.w2s.filter((w2) => w2.personRole === PersonRole.PRIMARY) + } + return this.info.w2s + } + + wages = (): number => this.validW2s().reduce((res, w2) => res + w2.income, 0) + medicareWages = (): number => + this.validW2s().reduce((res, w2) => res + w2.medicareIncome, 0) + + occupation = (r: PersonRole): string | undefined => + this.info.w2s.find((w2) => w2.personRole === r && w2.occupation !== '') + ?.occupation + + standardDeduction = (): number | undefined => { + const filingStatus = this.info.taxPayer.filingStatus + + const allowances = [ + this.bornBeforeDate(), + this.blind(), + this.spouseBeforeDate(), + this.spouseBlind() + ].reduce((res, e) => res + +!!e, 0) + + if ( + this.info.taxPayer.primaryPerson.isTaxpayerDependent || + (this.info.taxPayer.spouse?.isTaxpayerDependent ?? false) + ) { + const l4a = Math.min( + federalBrackets.ordinary.status[filingStatus].deductions[0].amount, + this.wages() > 750 ? this.wages() + 350 : 1100 + ) + if (allowances > 0) { + if ( + filingStatus === FilingStatus.HOH || + filingStatus === FilingStatus.S + ) { + return l4a + allowances * 1700 + } else { + return l4a + allowances * 1350 + } + } else { + return l4a + } + } + + return federalBrackets.ordinary.status[filingStatus].deductions[allowances] + .amount + } + + totalQualifiedDividends = (): number => + this.f1099Divs() + .map((f) => f.form.qualifiedDividends) + .reduce((l, r) => l + r, 0) + + totalGrossDistributionsFromIra = (): number => + this.info.individualRetirementArrangements.reduce( + (res, i) => res + i.grossDistribution, + 0 + ) + + totalTaxableFromIra = (): number => + this.info.individualRetirementArrangements.reduce( + (r, i) => r + i.taxableAmount, + 0 + ) + + totalGrossDistributionsFrom1099R = (planType: PlanType1099): number => + this.f1099rs() + .filter((element) => element.form.planType === planType) + .reduce((res, f1099) => res + f1099.form.grossDistribution, 0) + + totalTaxableFrom1099R = (planType: PlanType1099): number => + this.f1099rs() + .filter((element) => element.form.planType === planType) + .reduce((res, f1099) => res + f1099.form.taxableAmount, 0) + + l1a = (): number => this.wages() + l1b = (): number | undefined => undefined + l1c = (): number | undefined => undefined + l1d = (): number | undefined => undefined + l1e = (): number | undefined => undefined + l1f = (): number | undefined => undefined + l1g = (): number | undefined => undefined + l1h = (): number | undefined => undefined + l1i = (): number | undefined => undefined + l1z = (): number => + sumFields([ + this.l1a(), + this.l1b(), + this.l1c(), + this.l1d(), + this.l1e(), + this.l1f(), + this.l1g(), + this.l1h() + ]) + l2a = (): number | undefined => this.scheduleB.l3() + l2b = (): number | undefined => this.scheduleB.to1040l2b() + l3a = (): number | undefined => this.totalQualifiedDividends() + l3b = (): number | undefined => this.scheduleB.to1040l3b() + // This is the value of box 1 in 1099-R forms coming from IRAs + l4a = (): number | undefined => this.totalGrossDistributionsFromIra() + // This should be the value of box 2a in 1099-R coming from IRAs + l4b = (): number | undefined => this.totalTaxableFromIra() + // This is the value of box 1 in 1099-R forms coming from pensions/annuities + l5a = (): number | undefined => + this.totalGrossDistributionsFrom1099R(PlanType1099.Pension) + // this is the value of box 2a in 1099-R forms coming from pensions/annuities + l5b = (): number | undefined => + this.totalTaxableFrom1099R(PlanType1099.Pension) + // The sum of box 5 from SSA-1099 + l6a = (): number | undefined => this.socialSecurityBenefitsWorksheet?.l1() + // calculation of the taxable amount of line 6a based on other income + l6b = (): number | undefined => + this.socialSecurityBenefitsWorksheet?.taxableAmount() + // TODO: change this so that it is not hard coded + l6c = (): boolean => false + l7Box = (): boolean => !this.scheduleD.isNeeded() + l7 = (): number | undefined => this.scheduleD.to1040() + l8 = (): number | undefined => this.schedule1.l10() + l9 = (): number => + sumFields([ + this.l1z(), + this.l2b(), + this.l3b(), + this.l4b(), + this.l5b(), + this.l6b(), + this.l7(), + this.l8() + ]) + + l10 = (): number | undefined => this.schedule1.to1040Line10() + + l11 = (): number => Math.max(0, this.l9() - (this.l10() ?? 0)) + + l12 = (): number => { + if (this.scheduleA.isNeeded()) { + return this.scheduleA.deductions() + } + return this.standardDeduction() ?? 0 + } + + l13 = (): number | undefined => this.f8995?.deductions() + l14 = (): number => sumFields([this.l12(), this.l13()]) + + l15 = (): number => Math.max(0, this.l11() - this.l14()) + + f8814Box = (): boolean | undefined => this.f8814 !== undefined + f4972Box = (): boolean | undefined => this.f4972 !== undefined + // TODO: tax from other form? + otherFormBox = (): boolean => false + otherFormName = (): string | undefined => undefined + + computeTax = (): number | undefined => { + if ( + this.scheduleD.computeTaxOnQDWorksheet() || + this.totalQualifiedDividends() > 0 + ) { + this.qualifiedAndCapGainsWorksheet = new SDQualifiedAndCapGains(this) + return this.qualifiedAndCapGainsWorksheet.tax() + } + + return computeOrdinaryTax(this.info.taxPayer.filingStatus, this.l15()) + } + + l16 = (): number | undefined => + sumFields([this.f8814?.tax(), this.f4972?.tax(), this.computeTax()]) + + l17 = (): number | undefined => this.schedule2.l3() + l18 = (): number => sumFields([this.l16(), this.l17()]) + + l19 = (): number | undefined => this.schedule8812.to1040Line19() + l20 = (): number | undefined => + this.schedule3.isNeeded() ? this.schedule3.l8() : undefined + + l21 = (): number => sumFields([this.l19(), this.l20()]) + + l22 = (): number => Math.max(0, this.l18() - this.l21()) + + l23 = (): number | undefined => this.schedule2.l21() + + l24 = (): number => sumFields([this.l22(), this.l23()]) + + l25a = (): number => + this.validW2s().reduce((res, w2) => res + w2.fedWithholding, 0) + + // tax withheld from 1099s + l25b = (): number => + this.f1099rs().reduce( + (res, f1099) => res + f1099.form.federalIncomeTaxWithheld, + 0 + ) + + this.f1099ssas().reduce( + (res, f1099) => res + f1099.form.federalIncomeTaxWithheld, + 0 + ) + + // TODO: form(s) W-2G box 4, schedule K-1, form 1042-S, form 8805, form 8288-A + l25c = (): number | undefined => this.f8959.l24() + + l25d = (): number => sumFields([this.l25a(), this.l25b(), this.l25c()]) + + l26 = (): number => + this.info.estimatedTaxes.reduce((res, et) => res + et.payment, 0) + + l27 = (): number => + this.scheduleEIC.isNeeded() ? this.scheduleEIC.credit() : 0 + + // TODO: handle taxpayers between 1998 and 2004 that + // can claim themselves for eic. + //l27acheckBox = (): boolean => false + + // TODO: nontaxable combat pay + //l27b = (): number | undefined => undefined + + // TODO: prior year earned income + //l27c = (): number | undefined => undefined + + l28 = (): number | undefined => this.schedule8812.to1040Line28() + + l29 = (): number | undefined => this.f8863?.l8() + + // TODO: recovery rebate credit? + l30 = (): number | undefined => undefined + + l31 = (): number | undefined => + this.schedule3.isNeeded() ? this.schedule3.l15() : undefined + + l32 = (): number => + sumFields([this.l27(), this.l28(), this.l29(), this.l30(), this.l31()]) + + l33 = (): number => sumFields([this.l25d(), this.l26(), this.l32()]) + + l34 = (): number => Math.max(0, this.l33() - this.l24()) + + // TODO: assuming user wants amount refunded + // rather than applied to estimated tax + l35a = (): number => this.l34() + l36 = (): number => Math.max(0, this.l34() - this.l35a()) + + l37 = (): number => Math.max(0, this.l24() - this.l33()) + + // TODO - estimated tax penalty + l38 = (): number | undefined => undefined + + _depField = (idx: number): string | boolean => { + const deps: Dependent[] = this.info.taxPayer.dependents + + // Based on the PDF row we are on, select correct dependent + const depIdx = Math.floor(idx / 5) + const depFieldIdx = idx % 5 + + let fieldArr = ['', '', '', false, false] + + if (depIdx < deps.length) { + const dep = deps[depIdx] + // Based on the PDF column, select the correct field + fieldArr = [ + `${dep.firstName} ${dep.lastName}`, + dep.ssid, + dep.relationship, + this.qualifyingDependents.qualifiesChild(dep), + this.qualifyingDependents.qualifiesOther(dep) + ] + } + + return fieldArr[depFieldIdx] + } + + // 1040 allows 4 dependents listed without a supplemental schedule, + // so create field mappings for 4x5 grid of fields + _depFieldMappings = (): Array => + Array.from(Array(20)).map((u, n: number) => this._depField(n)) + + fields = (): Field[] => + [ + '', + '', + '', + this.info.taxPayer.primaryPerson.firstName, + this.info.taxPayer.primaryPerson.lastName, + this.info.taxPayer.primaryPerson.ssid, + this.info.taxPayer.filingStatus === FilingStatus.MFJ + ? this.info.taxPayer.spouse?.firstName + : '', + this.info.taxPayer.filingStatus === FilingStatus.MFJ + ? this.info.taxPayer.spouse?.lastName ?? '' + : '', + this.info.taxPayer.spouse?.ssid, + this.info.taxPayer.primaryPerson.address.address, + this.info.taxPayer.primaryPerson.address.aptNo, + this.info.taxPayer.primaryPerson.address.city, + this.info.taxPayer.primaryPerson.address.state, + this.info.taxPayer.primaryPerson.address.zip, + this.info.taxPayer.primaryPerson.address.foreignCountry, + this.info.taxPayer.primaryPerson.address.province, + this.info.taxPayer.primaryPerson.address.postalCode, + false, // election campaign boxes + false, + this.info.taxPayer.filingStatus === FilingStatus.S, + this.info.taxPayer.filingStatus === FilingStatus.HOH, + this.info.taxPayer.filingStatus === FilingStatus.MFJ, + this.info.taxPayer.filingStatus === FilingStatus.MFS, + this.info.taxPayer.filingStatus === FilingStatus.W, + // TODO: implement non dependent child for HOH and QW + this.info.taxPayer.filingStatus === 'MFS' ? this.spouseFullName() : '', + this.info.questions.CRYPTO ?? false, + !(this.info.questions.CRYPTO ?? false), + this.info.taxPayer.primaryPerson.isTaxpayerDependent, + this.info.taxPayer.spouse?.isTaxpayerDependent ?? false, + false, // TODO: spouse itemizes separately, + this.bornBeforeDate(), + this.blind(), + this.spouseBeforeDate(), + this.spouseBlind(), + this.info.taxPayer.dependents.length > 4, + ...this._depFieldMappings(), + this.l1a(), + this.l1b(), + this.l1c(), + this.l1d(), + this.l1e(), + this.l1f(), + this.l1g(), + this.l1h(), + this.l1i(), + this.l1z(), + this.l2a(), + this.l2b(), + this.l3a(), + this.l3b(), + this.l4a(), + this.l4b(), + this.l5a(), + this.l5b(), + this.l6a(), + this.l6b(), + this.l6c(), + this.l7Box(), + this.l7(), + this.l8(), + this.l9(), + this.l10(), + this.l11(), + this.l12(), + this.l13(), + this.l14(), + this.l15(), + this.f8814Box(), + this.f4972Box(), + this.otherFormBox(), + this.otherFormName(), + this.l16(), + this.l17(), + this.l18(), + this.l19(), + this.l20(), + this.l21(), + this.l22(), + this.l23(), + this.l24(), + this.l25a(), + this.l25b(), + this.l25c(), + this.l25d(), + this.l26(), + this.l27(), + this.l28(), + this.l29(), + undefined, //this.l30(), + this.l31(), + this.l32(), + this.l33(), + this.l34(), + this.f8888 !== undefined, + this.l35a(), + this.info.refund?.routingNumber, + this.info.refund?.accountType === AccountType.checking, + this.info.refund?.accountType === AccountType.savings, + this.info.refund?.accountNumber, + this.l36(), + this.l37(), + this.l38(), + // TODO: 3rd party + false, + false, + '', + '', + '', + this.occupation(PersonRole.PRIMARY), + // TODO: pin numbers + '', + this.occupation(PersonRole.SPOUSE), + '', + this.info.taxPayer.contactPhoneNumber, + this.info.taxPayer.contactEmail, + // Paid preparer fields: + '', + '', + false, + '', + '', + '', + '' + ].map((x) => (x === undefined ? '' : x)) +} diff --git a/src/forms/Y2023/irsForms/F1040Attachment.ts b/src/forms/Y2023/irsForms/F1040Attachment.ts new file mode 100644 index 000000000..aa3678dbe --- /dev/null +++ b/src/forms/Y2023/irsForms/F1040Attachment.ts @@ -0,0 +1,24 @@ +import Form from 'ustaxes/core/irsForms/Form' +import F1040 from './F1040' + +abstract class F1040Attachment extends Form { + f1040: F1040 + + constructor(f1040: F1040) { + super() + this.f1040 = f1040 + } + + isNeeded = (): boolean => true + copies = (): F1040Attachment[] => [] +} + +export abstract class Worksheet { + f1040: F1040 + + constructor(f1040: F1040) { + this.f1040 = f1040 + } +} + +export default F1040Attachment diff --git a/src/forms/Y2023/irsForms/F1040v.ts b/src/forms/Y2023/irsForms/F1040v.ts new file mode 100644 index 000000000..a4fbbf053 --- /dev/null +++ b/src/forms/Y2023/irsForms/F1040v.ts @@ -0,0 +1,34 @@ +import F1040Attachment from './F1040Attachment' +import { FormTag } from 'ustaxes/core/irsForms/Form' +import { Field } from 'ustaxes/core/pdfFiller' + +export default class F1040V extends F1040Attachment { + tag: FormTag = 'f1040v' + sequenceIndex = -1 + + fields = (): Field[] => { + const tp = this.f1040.info.taxPayer + + const taxOwed = this.f1040.l37() + + const result = [ + tp.primaryPerson.ssid, + tp.spouse?.ssid, + taxOwed.toFixed(2), // dollars + tp.primaryPerson.firstName, + tp.primaryPerson.lastName, + tp.spouse?.firstName, + tp.spouse?.lastName, + tp.primaryPerson.address.address, + tp.primaryPerson.address.aptNo, + tp.primaryPerson.address.city, + tp.primaryPerson.address.state, + tp.primaryPerson.address.zip, + tp.primaryPerson.address.foreignCountry, + tp.primaryPerson.address.province, + tp.primaryPerson.address.postalCode + ] + + return result + } +} diff --git a/src/forms/Y2023/irsForms/F2439.ts b/src/forms/Y2023/irsForms/F2439.ts new file mode 100644 index 000000000..cdf6ebb15 --- /dev/null +++ b/src/forms/Y2023/irsForms/F2439.ts @@ -0,0 +1,16 @@ +import F1040Attachment from './F1040Attachment' +import { Field } from 'ustaxes/core/pdfFiller' +import { FormTag } from 'ustaxes/core/irsForms/Form' + +/** + * TODO: not implemented + * Referenced Schedule 3 line 13a + */ +export default class F2439 extends F1040Attachment { + tag: FormTag = 'f2439' + sequenceIndex = 999 + + credit = (): number | undefined => undefined + + fields = (): Field[] => [] +} diff --git a/src/forms/Y2023/irsForms/F2441.ts b/src/forms/Y2023/irsForms/F2441.ts new file mode 100644 index 000000000..f369bd8c0 --- /dev/null +++ b/src/forms/Y2023/irsForms/F2441.ts @@ -0,0 +1,15 @@ +import F1040Attachment from './F1040Attachment' +import { Field } from 'ustaxes/core/pdfFiller' +import { FormTag } from 'ustaxes/core/irsForms/Form' + +/** + * TODO: Credit for child and dependent care expenses + */ +export default class F2441 extends F1040Attachment { + tag: FormTag = 'f2441' + sequenceIndex = 999 + + credit = (): number | undefined => undefined + + fields = (): Field[] => [] +} diff --git a/src/forms/Y2023/irsForms/F2555.ts b/src/forms/Y2023/irsForms/F2555.ts new file mode 100644 index 000000000..95cae61f5 --- /dev/null +++ b/src/forms/Y2023/irsForms/F2555.ts @@ -0,0 +1,28 @@ +import F1040Attachment from './F1040Attachment' +import { Field } from 'ustaxes/core/pdfFiller' +import { FormTag } from 'ustaxes/core/irsForms/Form' + +/** + * Impacts EIC, 1040 instructions L27 step 1 squestion 4 + */ +export default class F2555 extends F1040Attachment { + tag: FormTag = 'f2555' + sequenceIndex = 34 + + // TODO - Required from SDCapitalGainWorksheet + l3 = (): number | undefined => undefined + + // TODO - required from 6251 + l36 = (): number => 0 + + // TODO - required from 6251 + l42 = (): number => 0 + + // TODO - required from 8812 + l45 = (): number => 0 + + // TODO - required from 6251 & 8812 + l50 = (): number => 0 + + fields = (): Field[] => [] +} diff --git a/src/forms/Y2023/irsForms/F4136.ts b/src/forms/Y2023/irsForms/F4136.ts new file mode 100644 index 000000000..86f8c44ef --- /dev/null +++ b/src/forms/Y2023/irsForms/F4136.ts @@ -0,0 +1,16 @@ +import F1040Attachment from './F1040Attachment' +import { Field } from 'ustaxes/core/pdfFiller' +import { FormTag } from 'ustaxes/core/irsForms/Form' + +/** + * TODO: not implemented + * Credit for federal tax on fuels + */ +export default class F4136 extends F1040Attachment { + tag: FormTag = 'f4136' + sequenceIndex = 999 + + credit = (): number | undefined => undefined + + fields = (): Field[] => [] +} diff --git a/src/forms/Y2023/irsForms/F4137.ts b/src/forms/Y2023/irsForms/F4137.ts new file mode 100644 index 000000000..ebc8e9813 --- /dev/null +++ b/src/forms/Y2023/irsForms/F4137.ts @@ -0,0 +1,12 @@ +import F1040Attachment from './F1040Attachment' +import { Field } from 'ustaxes/core/pdfFiller' + +// TODO +export default class F4137 extends F1040Attachment { + tag = 'f4137' + sequenceIndex = 999 + + l6 = (): number | undefined => undefined + + fields = (): Field[] => [] +} diff --git a/src/forms/Y2023/irsForms/F4563.ts b/src/forms/Y2023/irsForms/F4563.ts new file mode 100644 index 000000000..598107bc2 --- /dev/null +++ b/src/forms/Y2023/irsForms/F4563.ts @@ -0,0 +1,17 @@ +import F1040Attachment from './F1040Attachment' +import { Field } from 'ustaxes/core/pdfFiller' +import { FormTag } from 'ustaxes/core/irsForms/Form' + +/** + * Exclusion of income for residents of American Somoa + * Impacts 8812, + */ +export default class F4563 extends F1040Attachment { + sequenceIndex = 563 + tag: FormTag = 'f4563' + + // TODO - required from 8812 + l15 = (): number => 0 + + fields = (): Field[] => [] +} diff --git a/src/forms/Y2023/irsForms/F4797.ts b/src/forms/Y2023/irsForms/F4797.ts new file mode 100644 index 000000000..791aa10d1 --- /dev/null +++ b/src/forms/Y2023/irsForms/F4797.ts @@ -0,0 +1,19 @@ +import F1040Attachment from './F1040Attachment' +import { Field } from 'ustaxes/core/pdfFiller' +import { FormTag } from 'ustaxes/core/irsForms/Form' + +/** + * Impacts EIC, 1040 instructions L27 step 2 question 3 + * Not implemented yet + */ +export default class F4797 extends F1040Attachment { + tag: FormTag = 'f4797' + sequenceIndex = 999 + + // TODO, required from schedule EIC, PUB 596, worksheet 1 + l7 = (): number | undefined => undefined + l8 = (): number | undefined => undefined + l9 = (): number | undefined => undefined + + fields = (): Field[] => [] +} diff --git a/src/forms/Y2023/irsForms/F4952.ts b/src/forms/Y2023/irsForms/F4952.ts new file mode 100644 index 000000000..a180c135f --- /dev/null +++ b/src/forms/Y2023/irsForms/F4952.ts @@ -0,0 +1,17 @@ +import F1040Attachment from './F1040Attachment' +import { Field } from 'ustaxes/core/pdfFiller' +import { FormTag } from 'ustaxes/core/irsForms/Form' + +/** + * Impacts Schedule D, capital gains and taxes worksheet, + * TODO: Not implemented yet + */ +export default class F4952 extends F1040Attachment { + tag: FormTag = 'f4952' + sequenceIndex = 999 + + l4e = (): number | undefined => undefined + l4g = (): number | undefined => undefined + + fields = (): Field[] => [] +} diff --git a/src/forms/Y2023/irsForms/F4972.ts b/src/forms/Y2023/irsForms/F4972.ts new file mode 100644 index 000000000..6328e4fbf --- /dev/null +++ b/src/forms/Y2023/irsForms/F4972.ts @@ -0,0 +1,15 @@ +import F1040Attachment from './F1040Attachment' +import { Field } from 'ustaxes/core/pdfFiller' +import { FormTag } from 'ustaxes/core/irsForms/Form' + +/** + * TODO: Not implemented yet + */ +export default class F4972 extends F1040Attachment { + tag: FormTag = 'f4972' + sequenceIndex = 999 + + tax = (): number => 0 + + fields = (): Field[] => [] +} diff --git a/src/forms/Y2023/irsForms/F5695.ts b/src/forms/Y2023/irsForms/F5695.ts new file mode 100644 index 000000000..e9404ee04 --- /dev/null +++ b/src/forms/Y2023/irsForms/F5695.ts @@ -0,0 +1,15 @@ +import F1040Attachment from './F1040Attachment' +import { Field } from 'ustaxes/core/pdfFiller' +import { FormTag } from 'ustaxes/core/irsForms/Form' + +/** + * Not implemented yet + */ +export default class F5695 extends F1040Attachment { + tag: FormTag = 'f5695' + sequenceIndex = 999 + + l30 = (): number | undefined => undefined + + fields = (): Field[] => [] +} diff --git a/src/forms/Y2023/irsForms/F6168.ts b/src/forms/Y2023/irsForms/F6168.ts new file mode 100644 index 000000000..6fcce711c --- /dev/null +++ b/src/forms/Y2023/irsForms/F6168.ts @@ -0,0 +1,13 @@ +import { Field } from 'ustaxes/core/pdfFiller' +import F1040Attachment from './F1040Attachment' + +/** + * Referenced from line 21 of Schedule E + * TODO: Not implemented + */ +export default class F6168 extends F1040Attachment { + sequenceIndex = 999 + tag = 'f6168' + + fields = (): Field[] => [] +} diff --git a/src/forms/Y2023/irsForms/F6251.ts b/src/forms/Y2023/irsForms/F6251.ts new file mode 100644 index 000000000..686375975 --- /dev/null +++ b/src/forms/Y2023/irsForms/F6251.ts @@ -0,0 +1,508 @@ +import F1040Attachment from './F1040Attachment' +import { FilingStatus, PersonRole } from 'ustaxes/core/data' +import { FormTag } from 'ustaxes/core/irsForms/Form' +import { Field } from 'ustaxes/core/pdfFiller' +import { amt } from '../data/federal' + +type Part3 = Partial<{ + l12: number + l13: number + l14: number + l15: number + l16: number + l17: number + l18: number + l19: number + l20: number + l21: number + l22: number + l23: number + l24: number + l25: number + l26: number + l27: number + l28: number + l29: number + l30: number + l31: number + l32: number + l33: number + l34: number + l35: number + l36: number + l37: number + l38: number + l39: number + l40: number +}> + +export default class F6251 extends F1040Attachment { + tag: FormTag = 'f6251' + sequenceIndex = 32 + + isNeeded = (): boolean => { + // See https://www.irs.gov/instructions/i6251 + + // 1. Form 6251, line 7, is greater than line 10. + if ((this.l7() ?? 0) > this.l10()) { + return true + } + + // TODO: 2. You claim any general business credit, and either line 6 (in Part I) of Form 3800 or line 25 of Form 3800 is more than zero. + + // TODO: 3. You claim the qualified electric vehicle credit (Form 8834), the personal use part of the alternative fuel vehicle refueling property credit (Form 8911), or the credit for prior year minimum tax (Form 8801). + + // 4. The total of Form 6251, lines 2c through 3, is negative and line 7 would be greater than line 10 if you didn’t take into account lines 2c through 3. + const l2cTo3Total = + (this.l2c() ?? 0) + + (this.l2d() ?? 0) + + (this.l2e() ?? 0) + + (this.l2f() ?? 0) + + (this.l2g() ?? 0) + + (this.l2h() ?? 0) + + (this.l2i() ?? 0) + + (this.l2j() ?? 0) + + (this.l2k() ?? 0) + + (this.l2l() ?? 0) + + (this.l2m() ?? 0) + + (this.l2n() ?? 0) + + (this.l2o() ?? 0) + + (this.l2p() ?? 0) + + (this.l2q() ?? 0) + + (this.l2r() ?? 0) + + (this.l2s() ?? 0) + + (this.l2t() ?? 0) + + (this.l3() ?? 0) + if (l2cTo3Total < 0 && (this.l7(-l2cTo3Total) ?? 0) > this.l10()) + return true + + return false + } + + l1 = (): number | undefined => { + const l15 = this.f1040.l15() + if (l15 !== 0) { + return l15 + } + return this.f1040.l11() - this.f1040.l14() + } + + l2a = (): number | undefined => { + if (this.f1040.scheduleA.isNeeded()) { + return this.f1040.scheduleA.l7() + } + return this.f1040.l12() + } + + l2b = (): number | undefined => { + return (this.f1040.schedule1.l1() ?? 0) + this.f1040.schedule1.l8z() + } + + // TODO: Investment interest expense (difference between regular tax and AMT) + l2c = (): number | undefined => undefined + + // TODO: Depletion (difference between regular tax and AMT) + l2d = (): number | undefined => undefined + + l2e = (): number | undefined => { + return Math.abs(this.f1040.schedule1.l8a() ?? 0) + } + + // TODO: Alternative tax net operating loss deduction + l2f = (): number | undefined => undefined + // TODO: Interest from specified private activity bonds exempt from the regular tax + l2g = (): number | undefined => undefined + // TODO: Qualified small business stock, see instructions + l2h = (): number | undefined => undefined + + // Exercise of incentive stock options (excess of AMT income over regular tax income) + l2i = (): number | undefined => { + let f3921s = this.f1040.info.f3921s + if (this.f1040.info.taxPayer.filingStatus === FilingStatus.MFS) { + f3921s = f3921s.filter((w2) => w2.personRole === PersonRole.PRIMARY) + } + return f3921s.reduce( + (amount, f) => (f.fmv - f.exercisePricePerShare) * f.numShares + amount, + 0 + ) + } + + // TODO: Estates and trusts (amount from Schedule K-1 (Form 1041), box 12, code A) + l2j = (): number | undefined => undefined + // TODO: Disposition of property (difference between AMT and regular tax gain or loss) + l2k = (): number | undefined => undefined + // TODO: Depreciation on assets placed in service after 1986 (difference between regular tax and AMT) + l2l = (): number | undefined => undefined + // TODO: Passive activities (difference between AMT and regular tax income or loss) + l2m = (): number | undefined => undefined + // TODO: Loss limitations (difference between AMT and regular tax income or loss) + l2n = (): number | undefined => undefined + // TODO: Circulation costs (difference between regular tax and AMT) + l2o = (): number | undefined => undefined + // TODO: Long-term contracts (difference between AMT and regular tax income) + l2p = (): number | undefined => undefined + // TODO: Mining costs (difference between regular tax and AMT) + l2q = (): number | undefined => undefined + // TODO: Research and experimental costs (difference between regular tax and AMT) + l2r = (): number | undefined => undefined + // TODO: Income from certain installment sales before January 1, 1987 + l2s = (): number | undefined => undefined + // TODO: Intangible drilling costs preference + l2t = (): number | undefined => undefined + + // TODO: Other adjustments, including income-based related adjustments + l3 = (): number | undefined => undefined + + l4 = (additionalAmount = 0): number | undefined => + additionalAmount + + (this.l1() ?? 0) + + (this.l2a() ?? 0) - + (this.l2b() ?? 0) + + (this.l2c() ?? 0) + + (this.l2d() ?? 0) + + (this.l2e() ?? 0) - + (this.l2f() ?? 0) + + (this.l2g() ?? 0) + + (this.l2h() ?? 0) + + (this.l2i() ?? 0) + + (this.l2j() ?? 0) + + (this.l2k() ?? 0) + + (this.l2l() ?? 0) + + (this.l2m() ?? 0) + + (this.l2n() ?? 0) + + (this.l2o() ?? 0) + + (this.l2p() ?? 0) + + (this.l2q() ?? 0) + + (this.l2r() ?? 0) - + (this.l2s() ?? 0) + + (this.l2t() ?? 0) + + (this.l3() ?? 0) + + l5 = (additionalAmount = 0): number | undefined => { + const l4 = this.l4(additionalAmount) ?? 0 + return amt.excemption(this.f1040.info.taxPayer.filingStatus, l4) + } + + l6 = (additionalAmount = 0): number => + Math.max( + 0, + (this.l4(additionalAmount) ?? 0) - (this.l5(additionalAmount) ?? 0) + ) + + requiresPartIII = (): boolean => { + // If you reported capital gain distributions directly on Form 1040 or 1040-SR, line 7; + // you reported qualified dividends on Form 1040 or 1040-SR, line 3a; + // or you had a gain on both lines 15 and 16 of Schedule D (Form 1040) (as refigured for the AMT, if necessary), + // complete Part III on the back and enter the amount from line 40 here. + return ( + this.f1040.l7() !== undefined || + this.f1040.l3a() !== undefined || + (this.f1040.scheduleD.l15() > 0 && this.f1040.scheduleD.l16() > 0) + ) + } + + l7 = (additionalAmount = 0): number | undefined => { + const l6 = this.l6(additionalAmount) + if (l6 === 0) { + return 0 + } + + // TODO: Handle Form 2555 + const f2555 = this.f1040.f2555 + if ( + f2555 !== undefined && + (f2555.l36() > 0 || f2555.l42() > 0 || f2555.l50() > 0) + ) { + // TODO: Foreign Earned Income Tax Worksheet—Line 7 + } + + // Use line 40 if Part III is required + if (this.requiresPartIII()) { + return this.part3().l40 + } + + const cap = amt.cap(this.f1040.info.taxPayer.filingStatus) + + if (l6 <= cap) { + return l6 * 0.26 + } + return ( + l6 * 0.28 - + (this.f1040.info.taxPayer.filingStatus === FilingStatus.MFS ? 2207 : 4414) + ) + } + + // TODO: Alternative minimum tax foreign tax credit + l8 = (): number | undefined => undefined + + l9 = (additionalAmount = 0): number => { + const l6 = this.l6(additionalAmount) + if (l6 === 0) { + return 0 + } + return (this.l7(additionalAmount) ?? 0) - (this.l8() ?? 0) + } + + // Add Form 1040 or 1040-SR, line 16 (minus any tax from Form 4972), + // and Schedule 2 (Form 1040), line 2. + // Subtract from the result Schedule 3 (Form 1040), line 1 + // and any negative amount reported on Form 8978, line 14 (treated as a positive number). + // If zero or less, enter -0-. + // TODO: If you used Schedule J to figure your tax on Form 1040 or 1040-SR, line 16, refigure that tax without using Schedule J before completing this line. See instructions + l10 = (): number => { + const f1040L16 = this.f1040.l16() ?? 0 + const f4972 = this.f1040.f4972?.tax() ?? 0 + const sch2L2 = this.f1040.schedule2.l2() ?? 0 + const sch3L1 = this.f1040.schedule3.l1() ?? 0 + const f8978L14 = Math.abs(0) // TODO: Form 8978 + return Math.max(0, f1040L16 - f4972 + sch2L2 - sch3L1 - f8978L14) + } + + l11 = (): number => { + const l6 = this.l6() + if (l6 === 0) { + return 0 + } + return Math.max(0, this.l9() - this.l10()) + } + + part3 = (): Part3 => { + if (!this.requiresPartIII()) { + return {} + } + const fs = this.f1040.info.taxPayer.filingStatus + const qdivWorksheet = this.f1040.qualifiedAndCapGainsWorksheet + const schDWksht = this.f1040.scheduleD.taxWorksheet + const usingTaxWorksheet = schDWksht.isNeeded() + + const l18Consts: [number, number] = (() => { + if (this.f1040.info.taxPayer.filingStatus === FilingStatus.MFS) { + return [110350, 2207] + } + return [220700, 4414] + })() + + const l19Value: { [k in FilingStatus]: number } = { + [FilingStatus.MFJ]: 89950, + [FilingStatus.W]: 89950, + [FilingStatus.S]: 446275, + [FilingStatus.MFS]: 44625, + [FilingStatus.HOH]: 59750 + } + + const l25Value: { [k in FilingStatus]: number } = { + [FilingStatus.MFJ]: 553850, + [FilingStatus.W]: 553850, + [FilingStatus.S]: 492300, + [FilingStatus.MFS]: 276900, + [FilingStatus.HOH]: 523050 + } + + const l12 = this.l6() + + // TODO - for F2555, see the instructions for amount + const l13: number = (() => { + if (usingTaxWorksheet) { + return schDWksht.l13() ?? 0 + } + + return qdivWorksheet?.l4() ?? 0 + })() + + const l14 = this.f1040.scheduleD.l19() ?? 0 + + const l15 = (() => { + if (!usingTaxWorksheet) { + return l13 + } + return Math.min(l13 + l14, schDWksht.l10() ?? 0) + })() + + const l16 = Math.min(l12, l15) + + const l17 = l12 - l16 + + const l18 = (() => { + const [c1, c2] = l18Consts + + if (l17 <= c1) { + return l17 * 0.26 + } + return l17 * 0.28 - c2 + })() + + const l19 = l19Value[fs] + + const l20 = (() => { + if (usingTaxWorksheet) { + return schDWksht.l14() ?? 0 + } + + if (qdivWorksheet !== undefined) { + return qdivWorksheet.l5() + } + + return Math.max(0, this.f1040.l15()) + })() + + const l21 = Math.max(0, l19 - l20) + + const l22 = Math.min(l12, l13) + + const l23 = Math.min(l21, l22) + + const l24 = Math.max(0, l22 - l23) + + const l25 = l25Value[fs] + + const l26 = l21 + + // TODO - see instructions for F2555 + const l27 = (() => { + if (usingTaxWorksheet) { + return schDWksht.l21() ?? 0 + } + + if (qdivWorksheet !== undefined) { + return qdivWorksheet.l5() + } + + return Math.max(0, this.f1040.l15()) + })() + + const l28 = l26 + l27 + + const l29 = Math.max(0, l25 - l28) + + const l30 = Math.min(l24, l29) + + const l31 = l30 * 0.15 + + const l32 = l23 + l30 + + const l33 = l22 - l32 + + const l34 = l33 * 0.2 + + const l35 = l17 + l32 + l33 + + const l36 = l12 - l35 + + const l37 = l36 * 0.25 + + const l38 = l18 + l31 + l34 + l37 + + const l39 = (() => { + // numbers referenced here are the same as l18. + const [c1, c2] = l18Consts + if (l12 <= c1) { + return l12 * 0.26 + } + return l12 * 0.28 - c2 + })() + + const l40 = Math.min(l38, l39) + + return { + l12, + l13, + l14, + l15, + l16, + l17, + l18, + l19, + l20, + l21, + l22, + l23, + l24, + l25, + l26, + l27, + l28, + l29, + l30, + l31, + l32, + l33, + l34, + l35, + l36, + l37, + l38, + l39, + l40 + } + } + + fields = (): Field[] => { + const p3 = this.part3() + return [ + this.f1040.namesString(), + this.f1040.info.taxPayer.primaryPerson.ssid, + // Part I + this.l1(), + this.l2a(), + this.l2b(), + this.l2c(), + this.l2d(), + this.l2e(), + this.l2f(), + this.l2g(), + this.l2h(), + this.l2i(), + this.l2j(), + this.l2k(), + this.l2l(), + this.l2m(), + this.l2n(), + this.l2o(), + this.l2p(), + this.l2q(), + this.l2r(), + this.l2s(), + this.l2t(), + this.l3(), + this.l4(), + // Part II + this.l5(), + this.l6(), + this.l7(), + this.l8(), + this.l9(), + this.l10(), + this.l11(), + // Part III + p3.l12, + p3.l13, + p3.l14, + p3.l15, + p3.l16, + p3.l17, + p3.l18, + p3.l19, + p3.l20, + p3.l21, + p3.l22, + p3.l23, + p3.l24, + p3.l25, + p3.l26, + p3.l27, + p3.l28, + p3.l29, + p3.l30, + p3.l31, + p3.l32, + p3.l33, + p3.l34, + p3.l35, + p3.l36, + p3.l37, + p3.l38, + p3.l39, + p3.l40 + ] + } +} diff --git a/src/forms/Y2023/irsForms/F8582.ts b/src/forms/Y2023/irsForms/F8582.ts new file mode 100644 index 000000000..becdf079c --- /dev/null +++ b/src/forms/Y2023/irsForms/F8582.ts @@ -0,0 +1,23 @@ +import { MatrixRow } from './ScheduleE' +import F1040Attachment from './F1040Attachment' +import { Field } from 'ustaxes/core/pdfFiller' + +/** + * Referenced from line 22 of Schedule E + * TODO: Not implemented + */ +export default class F8582 extends F1040Attachment { + tag = 'f8562' + sequenceIndex = 999 + + // TODO: 'Deducible rental estate loss after limitation, assuming all allowed' + deductibleRealEstateLossAfterLimitation = (): MatrixRow => + this.f1040.scheduleE.rentalNet().map((v) => { + if (v === undefined || v >= 0) { + return undefined + } + return v + }) as MatrixRow + + fields = (): Field[] => [] +} diff --git a/src/forms/Y2023/irsForms/F8814.ts b/src/forms/Y2023/irsForms/F8814.ts new file mode 100644 index 000000000..937b78c7c --- /dev/null +++ b/src/forms/Y2023/irsForms/F8814.ts @@ -0,0 +1,15 @@ +import F1040Attachment from './F1040Attachment' +import { FormTag } from 'ustaxes/core/irsForms/Form' +import { Field } from 'ustaxes/core/pdfFiller' + +export default class F8814 extends F1040Attachment { + tag: FormTag = 'f8814' + sequenceIndex = 999 + + // TODO: required from schedule EIC, pub596, worksheet 1 + l1b = (): number | undefined => undefined + + tax = (): number => 0 + + fields = (): Field[] => [] +} diff --git a/src/forms/Y2023/irsForms/F8853.ts b/src/forms/Y2023/irsForms/F8853.ts new file mode 100644 index 000000000..cf181b874 --- /dev/null +++ b/src/forms/Y2023/irsForms/F8853.ts @@ -0,0 +1,14 @@ +import F1040Attachment from './F1040Attachment' +import { Field } from 'ustaxes/core/pdfFiller' +import { FormTag } from 'ustaxes/core/irsForms/Form' + +// Not yet implemented +export default class F8853 extends F1040Attachment { + tag: FormTag = 'f8853' + sequenceIndex = 999 + + l1 = (): number | undefined => undefined + l2 = (): number | undefined => undefined + + fields = (): Field[] => [] +} diff --git a/src/forms/Y2023/irsForms/F8863.ts b/src/forms/Y2023/irsForms/F8863.ts new file mode 100644 index 000000000..90f3ece7d --- /dev/null +++ b/src/forms/Y2023/irsForms/F8863.ts @@ -0,0 +1,14 @@ +import F1040Attachment from './F1040Attachment' +import { Field } from 'ustaxes/core/pdfFiller' +import { FormTag } from 'ustaxes/core/irsForms/Form' + +// Not yet implemented +export default class F8863 extends F1040Attachment { + tag: FormTag = 'f8863' + sequenceIndex = 999 + + l8 = (): number | undefined => undefined + l19 = (): number | undefined => undefined + + fields = (): Field[] => [] +} diff --git a/src/forms/Y2023/irsForms/F8888.ts b/src/forms/Y2023/irsForms/F8888.ts new file mode 100644 index 000000000..0115bd7b3 --- /dev/null +++ b/src/forms/Y2023/irsForms/F8888.ts @@ -0,0 +1,13 @@ +import F1040Attachment from './F1040Attachment' +import { Field } from 'ustaxes/core/pdfFiller' +import { FormTag } from 'ustaxes/core/irsForms/Form' + +/** + * Not implemented yet + */ +export default class F8888 extends F1040Attachment { + tag: FormTag = 'f8888' + sequenceIndex = 999 + + fields = (): Field[] => [] +} diff --git a/src/forms/Y2023/irsForms/F8889.ts b/src/forms/Y2023/irsForms/F8889.ts new file mode 100644 index 000000000..ab60d951a --- /dev/null +++ b/src/forms/Y2023/irsForms/F8889.ts @@ -0,0 +1,289 @@ +import { Information, Person, HealthSavingsAccount } from 'ustaxes/core/data' +import { sumFields } from 'ustaxes/core/irsForms/util' +import { FormTag } from 'ustaxes/core/irsForms/Form' +import F8853 from './F8853' +import { CURRENT_YEAR, healthSavingsAccounts } from '../data/federal' +import F1040Attachment from './F1040Attachment' +import F1040 from './F1040' +import { Field } from 'ustaxes/core/pdfFiller' + +type ContributionType = 'self-only' | 'family' +type PerMonthContributionType = { + amount: number[] + type: ContributionType[] +} + +export default class F8889 extends F1040Attachment { + tag: FormTag = 'f8889' + sequenceIndex = 52 + // these should only be the HSAs that belong to this person + // the person can be either the primary person or the spouse + hsas: HealthSavingsAccount[] + f8853?: F8853 + person: Person + state: Information + calculatedCoverageType: 'self-only' | 'family' + perMonthContributions: PerMonthContributionType + readonly firstDayOfLastMonth: Date + + isNeeded = (): boolean => { + return this.f1040.info.healthSavingsAccounts.some( + (h) => h.personRole === this.person.role || h.coverageType === 'family' + ) + } + + constructor(f1040: F1040, person: Person) { + super(f1040) + this.f8853 = f1040.f8853 + this.person = person + this.state = f1040.info + // The relevant HSAs are the ones either for this person or any that + // have family coverage. + this.hsas = this.state.healthSavingsAccounts + .filter((h) => { + if (h.personRole == person.role || h.coverageType == 'family') { + return true + } + return false + }) + .map((h) => { + return { + ...h, + startDate: new Date(h.startDate), + endDate: new Date(h.endDate) + } + }) + this.calculatedCoverageType = 'self-only' + this.firstDayOfLastMonth = new Date(CURRENT_YEAR, 11, 1) + this.perMonthContributions = { + amount: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + type: new Array(12) + } + } + + calculatePerMonthLimits = (): void => { + for ( + let index = 0; + index < this.perMonthContributions.amount.length; + index++ + ) { + // for each month check each HSA to see if we are covered. + this.hsas.forEach((h) => { + const firstDayOfThisMonth = new Date(CURRENT_YEAR, index, 1) + if ( + h.startDate <= firstDayOfThisMonth && + h.endDate >= firstDayOfThisMonth + ) { + // the coverage limit for that month is based on the type of coverage of the + // HSA. If you have both types of HSA coverage for that month, then the family + // coverage limit wins out. Since family coverage limit is higher we can just + // take the max of the coverage limit for this month. + if ( + this.perMonthContributions.amount[index] < + healthSavingsAccounts.contributionLimit[h.coverageType] + ) { + this.perMonthContributions.amount[index] = + healthSavingsAccounts.contributionLimit[h.coverageType] + this.perMonthContributions.type[index] = h.coverageType + } + } + }) + } + // The calculated coverage type is whichever one was in effect for longer + let familyMonthCount = 0 + let singleMonthCount = 0 + this.perMonthContributions.amount.forEach((m) => { + if (m == healthSavingsAccounts.contributionLimit.family) { + familyMonthCount += 1 + } else if (m == healthSavingsAccounts.contributionLimit['self-only']) { + singleMonthCount += 1 + } + }) + if (familyMonthCount >= singleMonthCount) { + this.calculatedCoverageType = 'family' + } else { + this.calculatedCoverageType = 'self-only' + } + } + + /* If you are an eligible individual on the first day of the last month of your tax year + (December 1 for most taxpayers), you are considered to be an eligible individual + for the entire year. + */ + lastMonthRule = (): boolean => { + return this.hsas.some((hsa) => hsa.endDate >= this.firstDayOfLastMonth) + } + + /*If, on the first day of the last month of your tax year (December 1 for most taxpayers), + you had family coverage, check the "family" box. + */ + lastMonthCoverage = (): string | undefined => { + let coverage = undefined + for (const hsa of this.hsas) { + if (hsa.endDate >= this.firstDayOfLastMonth) { + if (hsa.coverageType == 'family') { + coverage = 'family' + break + } + coverage = 'self-only' + } + } + return coverage + } + + fullYearHsa = (): boolean => { + return this.hsas.some( + (hsa) => + hsa.startDate <= new Date(CURRENT_YEAR, 0, 1) && + hsa.endDate >= this.firstDayOfLastMonth + ) + } + + contributionLimit = (): number => { + /*If you were under age 55 at the end of 2020 and, on the first day of every month during 2020, + you were, or were considered, an eligible individual with the same coverage, enter $3,550 + ($7,100 for family coverage). All others, see the instructions for the amount to enter. + */ + /*If the last-month rule (see Last-month rule, earlier) applies, you are considered an eligible individual + for the entire year. You are treated as having the same HDHP coverage for the entire year as you had on + the first day of the last month of your tax year. + */ + if (this.lastMonthRule()) { + // If, on the first day of the last month of your tax year (December 1 for most taxpayers), + // you had family coverage, check the "family" box. + const lastMonthCoverage = this.lastMonthCoverage() + if (lastMonthCoverage !== undefined) { + if (lastMonthCoverage === 'family') { + this.calculatedCoverageType = 'family' + return healthSavingsAccounts.contributionLimit.family + } else if (lastMonthCoverage === 'self-only') { + this.calculatedCoverageType = 'self-only' + return healthSavingsAccounts.contributionLimit['self-only'] + } + } + } + /* If you don't have coverage in the last month, then you need to figure out + your contribution limit. If you don't have coverage for that month then + your contribution limit is 0. So let's initialize our per-month contribution + limit based on that. + */ + this.calculatePerMonthLimits() + return Math.round( + this.perMonthContributions.amount.reduce((a, b) => a + b) / 12 + ) + } + + splitFamilyContributionLimit = (): number | undefined => { + /* if you and your spouse each have separate HSAs and had family coverage under an HDHP at any time during 2020*/ + /* If you are treated as having family coverage for each month, divide the amount on line 5 equally between you + and your spouse, unless you both agree on a different allocation (such as allocating nothing to one spouse). + Enter your allocable share on line 6.*/ + /* Example. In 2020, you are an eligible individual and have self-only HDHP coverage. In March you marry and as + of April 1 you have family HDHP coverage. Neither you nor your spouse qualify for the additional contribution + amount. Your spouse has a separate HSA and is an eligible individual from April 1 to December 31, 2020. + Because you and your spouse are considered to have family coverage on December 1, your contribution limit is + $7,100 (the family coverage maximum). You and your spouse can divide this amount in any allocation to which + you agree (such as allocating nothing to one spouse).*/ + if (!this.hsas.some((h) => h.coverageType === 'family')) { + return this.l5() + } + + if (this.lastMonthCoverage() === 'family') { + // TODO: This hard codes the allocation at 50% for each spouse but the + // rules say any contribution allowcation is allowed + return Math.round(this.l5() / 2) + } else { + // get the number of months of family coverage + const familyMonths: number = this.perMonthContributions.type.filter( + (t) => t === 'family' + ).length + + // TODO: This hard codes the allocation at 50% for each spouse but the + // rules say any contribution allowcation is allowed + const familyContribution: number = + (familyMonths * healthSavingsAccounts.contributionLimit['family']) / + 12 / + 2 + + // Add this to the contributions of the self-only portion of the year + const selfMonths: number = 12 - familyMonths + + const selfContribution: number = + (selfMonths * healthSavingsAccounts.contributionLimit['self-only']) / 12 + + return familyContribution + selfContribution + } + } + + /*Include on line 2 only those amounts you, or others on your behalf, contributed to your HSA in 2020. + Also, include those contributions made from January 1, 2021, through April 15, 2021, that were for 2020. + Do not include employer contributions (see line 9) or amounts rolled over from another HSA or Archer MSA. + See Rollovers, earlier. Also, do not include any qualified HSA funding distributions (see line 10). + Contributions to an employee's account through a cafeteria plan are treated as employer contributions + and are not included on line 2. + */ + l2 = (): number => + this.hsas.reduce((total, hsa) => hsa.contributions + total, 0) + + l3 = (): number => this.contributionLimit() + l4 = (): number => sumFields([this.f8853?.l1(), this.f8853?.l2()]) + l5 = (): number => Math.max(0, this.l3() - this.l4()) + l6 = (): number | undefined => this.splitFamilyContributionLimit() + // TODO: Additional contirbution amount. Need to know the age of the user + l7 = (): number | undefined => undefined + l8 = (): number => sumFields([this.l6(), this.l7()]) + // Employer contributions are listed in W2 box 12 with code W + l9 = (): number => + this.state.w2s + .filter((w2) => w2.personRole == this.person.role) + .reduce((res, w2) => res + (w2.box12?.W ?? 0), 0) + l10 = (): number | undefined => undefined + l11 = (): number => sumFields([this.l9(), this.l10()]) + l12 = (): number => Math.max(0, this.l8() - this.l11()) + l13 = (): number => Math.min(this.l2(), this.l12()) + l14a = (): number => + this.hsas.reduce((total, hsa) => hsa.totalDistributions + total, 0) + l14b = (): number | undefined => undefined + l14c = (): number => Math.max(0, this.l14a() - (this.l14b() ?? 0)) + l15 = (): number => + this.hsas.reduce((total, hsa) => hsa.qualifiedDistributions + total, 0) + l16 = (): number => Math.max(0, this.l14c() - this.l15()) + l17a = (): boolean => false + // TODO: add in logic for when line 17a is true + l17b = (): number | undefined => Math.round(this.l16() * 0.2) + + l18 = (): number | undefined => undefined + l19 = (): number | undefined => undefined + l20 = (): number => sumFields([this.l18(), this.l19()]) + l21 = (): number => Math.round(this.l20() * 0.1) + + fields = (): Field[] => [ + `${this.person.firstName} ${this.person.lastName}`, + this.person.ssid, + this.calculatedCoverageType === 'self-only', // line 1: self-only check box + this.calculatedCoverageType === 'family', // line 1: family checkbox + this.l2(), + this.l3(), + this.l4(), + this.l5(), + this.l6(), + this.l7(), + this.l8(), + this.l9(), + this.l10(), + this.l11(), + this.l12(), + this.l13(), + this.l14a(), + this.l14b(), + this.l14c(), + this.l15(), + this.l16(), + this.l17a(), + this.l17b(), + this.l18(), + this.l19(), + this.l20(), + this.l21() + ] +} diff --git a/src/forms/Y2023/irsForms/F8910.ts b/src/forms/Y2023/irsForms/F8910.ts new file mode 100644 index 000000000..9b62e3963 --- /dev/null +++ b/src/forms/Y2023/irsForms/F8910.ts @@ -0,0 +1,15 @@ +import F1040Attachment from './F1040Attachment' +import { Field } from 'ustaxes/core/pdfFiller' +import { FormTag } from 'ustaxes/core/irsForms/Form' + +/** + * Not implemented + */ +export default class F8910 extends F1040Attachment { + sequenceIndex = 999 + tag: FormTag = 'f8910' + + l15 = (): number | undefined => undefined + + fields = (): Field[] => [] +} diff --git a/src/forms/Y2023/irsForms/F8919.ts b/src/forms/Y2023/irsForms/F8919.ts new file mode 100644 index 000000000..b4466734f --- /dev/null +++ b/src/forms/Y2023/irsForms/F8919.ts @@ -0,0 +1,12 @@ +import F1040Attachment from './F1040Attachment' +import { Field } from 'ustaxes/core/pdfFiller' + +// TODO: Not implemented yet +export default class F8919 extends F1040Attachment { + tag = 'f8919' + sequenceIndex = 999 + + l6 = (): number | undefined => undefined + + fields = (): Field[] => [] +} diff --git a/src/forms/Y2023/irsForms/F8936.ts b/src/forms/Y2023/irsForms/F8936.ts new file mode 100644 index 000000000..28e097dcd --- /dev/null +++ b/src/forms/Y2023/irsForms/F8936.ts @@ -0,0 +1,13 @@ +import F1040Attachment from './F1040Attachment' +import { Field } from 'ustaxes/core/pdfFiller' +import { FormTag } from 'ustaxes/core/irsForms/Form' + +export default class F8936 extends F1040Attachment { + tag: FormTag = 'f8936' + sequenceIndex = 999 + + l15 = (): number | undefined => undefined + l23 = (): number | undefined => undefined + + fields = (): Field[] => [] +} diff --git a/src/forms/Y2023/irsForms/F8949.ts b/src/forms/Y2023/irsForms/F8949.ts new file mode 100644 index 000000000..4c1dc6b82 --- /dev/null +++ b/src/forms/Y2023/irsForms/F8949.ts @@ -0,0 +1,204 @@ +import { FormTag } from 'ustaxes/core/irsForms/Form' +import { Asset, isSold, SoldAsset } from 'ustaxes/core/data' +import F1040Attachment from './F1040Attachment' +import F1040 from './F1040' +import { CURRENT_YEAR } from '../data/federal' +import { Field } from 'ustaxes/core/pdfFiller' + +type EmptyLine = [ + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined +] + +type Line = + | [string, string, string, number, number, undefined, undefined, number] + | EmptyLine +const emptyLine: EmptyLine = [ + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined +] + +const showDate = (date: Date): string => + `${date.getMonth() + 1}/${date.getDate()}/${date.getFullYear()}` + +const toLine = (position: SoldAsset): Line => [ + position.name, + showDate(position.openDate), + showDate(position.closeDate), + position.closePrice * position.quantity, + position.openPrice * position.quantity, + undefined, + undefined, + (position.closePrice - position.openPrice) * position.quantity +] + +const NUM_SHORT_LINES = 14 +const NUM_LONG_LINES = 14 + +const padUntil = (xs: A[], v: B, n: number): (A | B)[] => { + if (xs.length >= n) { + return xs + } + return [...xs, ...Array.from(Array(n - xs.length)).map(() => v)] +} + +export default class F8949 extends F1040Attachment { + tag: FormTag = 'f8949' + sequenceIndex = 12.1 + + index = 0 + + constructor(f1040: F1040, index = 0) { + super(f1040) + this.index = index + } + + isNeeded = (): boolean => this.thisYearSales().length > 0 + + copies = (): F8949[] => { + if (this.index === 0) { + const extraCopiesNeeded = Math.round( + Math.max( + this.thisYearShortTermSales().length / NUM_SHORT_LINES, + this.thisYearLongTermSales().length / NUM_LONG_LINES + ) + ) + return Array.from(Array(extraCopiesNeeded)).map( + (_, i) => new F8949(this.f1040, i + 1) + ) + } + return [] + } + + // Assuming we're only handling non-reported transactions + part1BoxA = (): boolean => false + part1BoxB = (): boolean => false + part1BoxC = (): boolean => true + part2BoxD = (): boolean => false + part2BoxE = (): boolean => false + part2BoxF = (): boolean => true + + thisYearSales = (): SoldAsset[] => + this.f1040.assets.filter( + (p) => isSold(p) && p.closeDate.getFullYear() === CURRENT_YEAR + ) as SoldAsset[] + + thisYearLongTermSales = (): SoldAsset[] => + this.thisYearSales().filter((p) => this.isLongTerm(p)) + + thisYearShortTermSales = (): SoldAsset[] => + this.thisYearSales().filter((p) => !this.isLongTerm(p)) + + // in milliseconds + oneDay = 1000 * 60 * 60 * 24 + + isLongTerm = (p: Asset): boolean => { + if (p.closeDate === undefined || p.closePrice === undefined) return false + const milliInterval = p.closeDate.getTime() - p.openDate.getTime() + return milliInterval / this.oneDay > 366 + } + + /** + * Take the short term transactions that fit on this copy of the 8949 + */ + shortTermSales = (): SoldAsset[] => + this.thisYearShortTermSales().slice( + this.index * NUM_SHORT_LINES, + (this.index + 1) * NUM_SHORT_LINES + ) + + /** + * Take the long term transactions that fit on this copy of the 8949 + */ + longTermSales = (): SoldAsset[] => + this.thisYearLongTermSales().slice( + this.index * NUM_LONG_LINES, + (this.index + 1) * NUM_LONG_LINES + ) + + shortTermLines = (): Line[] => + padUntil( + this.shortTermSales().map((p) => toLine(p)), + emptyLine, + NUM_SHORT_LINES + ) + longTermLines = (): Line[] => + padUntil( + this.longTermSales().map((p) => toLine(p)), + emptyLine, + NUM_LONG_LINES + ) + + shortTermTotalProceeds = (): number => + this.shortTermSales().reduce( + (acc, p) => acc + p.closePrice * p.quantity - (p.closeFee ?? 0), + 0 + ) + + shortTermTotalCost = (): number => + this.shortTermSales().reduce( + (acc, p) => acc + p.openPrice * p.quantity + p.openFee, + 0 + ) + + shortTermTotalGain = (): number => + this.shortTermTotalProceeds() - this.shortTermTotalCost() + + // TODO: handle adjustments column. + shortTermTotalAdjustments = (): number | undefined => undefined + + longTermTotalProceeds = (): number => + this.longTermSales().reduce( + (acc, p) => acc + p.closePrice * p.quantity - (p.closeFee ?? 0), + 0 + ) + + longTermTotalCost = (): number => + this.longTermSales().reduce( + (acc, p) => acc + p.openPrice * p.quantity + p.openFee, + 0 + ) + + longTermTotalGain = (): number => + this.longTermTotalProceeds() - this.longTermTotalCost() + + // TODO: handle adjustments column. + longTermTotalAdjustments = (): number | undefined => undefined + + fields = (): Field[] => [ + this.f1040.namesString(), + this.f1040.info.taxPayer.primaryPerson.ssid, + this.part1BoxA(), + this.part1BoxB(), + this.part1BoxC(), + ...this.shortTermLines().flat(), + this.shortTermTotalProceeds(), + this.shortTermTotalCost(), + undefined, // greyed out field + this.shortTermTotalAdjustments(), + this.shortTermTotalGain(), + this.f1040.namesString(), + this.f1040.info.taxPayer.primaryPerson.ssid, + this.part2BoxD(), + this.part2BoxE(), + this.part2BoxF(), + ...this.longTermLines().flat(), + this.longTermTotalProceeds(), + this.longTermTotalCost(), + undefined, // greyed out field + this.longTermTotalAdjustments(), + this.longTermTotalGain() + ] +} diff --git a/src/forms/Y2023/irsForms/F8959.ts b/src/forms/Y2023/irsForms/F8959.ts new file mode 100644 index 000000000..c64ab4e8a --- /dev/null +++ b/src/forms/Y2023/irsForms/F8959.ts @@ -0,0 +1,111 @@ +import F1040Attachment from './F1040Attachment' +import { sumFields } from 'ustaxes/core/irsForms/util' +import { FormTag } from 'ustaxes/core/irsForms/Form' +import { fica } from '../data/federal' +import { Field } from 'ustaxes/core/pdfFiller' + +export default class F8959 extends F1040Attachment { + tag: FormTag = 'f8959' + sequenceIndex = 71 + + isNeeded = (): boolean => { + const filingStatus = this.f1040.info.taxPayer.filingStatus + const totalW2Income = this.f1040.info.w2s + .map((w2) => w2.medicareIncome) + .reduce((l, r) => l + r, 0) + return ( + fica.additionalMedicareTaxThreshold(filingStatus) < + totalW2Income + (this.f1040.scheduleSE.l6() ?? 0) + ) + } + + thresholdFromFilingStatus = (): number => + fica.additionalMedicareTaxThreshold(this.f1040.info.taxPayer.filingStatus) + + computeAdditionalMedicareTax = (compensation: number): number => + fica.additionalMedicareTaxRate * compensation + + // Part I: Additional Medicare Tax on Medicare Wages + l1 = (): number => this.f1040.medicareWages() + + l2 = (): number | undefined => this.f1040.f4137?.l6() + l3 = (): number | undefined => this.f1040.f8919?.l6() + l4 = (): number => sumFields([this.l1(), this.l2(), this.l3()]) + + l5 = (): number => this.thresholdFromFilingStatus() + l6 = (): number => Math.max(0, this.l4() - this.l5()) + + l7 = (): number | undefined => this.computeAdditionalMedicareTax(this.l6()) + + // Part II: Additional Medicare Tax on Self-Employment Income + l8 = (): number | undefined => this.f1040.scheduleSE.l6() + l9 = (): number => this.thresholdFromFilingStatus() + l10 = (): number => this.l4() + l11 = (): number => Math.max(0, this.l9() - this.l10()) + + l12 = (): number => Math.max(0, (this.l8() ?? 0) - this.l11()) + + l13 = (): number | undefined => this.computeAdditionalMedicareTax(this.l12()) + + // Part III: Additional Medicare Tax on Railroad Retirement Tax Act + // (RRTA) Compensation + l14 = (): number | undefined => undefined // TODO: RRTA in W2 + l15 = (): number => this.thresholdFromFilingStatus() + l16 = (): number => Math.max(0, (this.l14() ?? 0) - this.l15()) + + l17 = (): number => this.computeAdditionalMedicareTax(this.l16()) + + // Part IV: Total Medicare Tax + l18 = (): number => sumFields([this.l7(), this.l13(), this.l17()]) + + // Part V: Withholding Reconciliation + l19 = (): number => + this.f1040 + .validW2s() + .map((w2) => w2.medicareWithholding) + .reduce((l, r) => l + r, 0) + + l20 = (): number => this.l1() + l21 = (): number => fica.regularMedicareTaxRate * this.l20() + + l22 = (): number => Math.max(0, this.l19() - this.l21()) + + l23 = (): number | undefined => 0 // TODO: RRTA + l24 = (): number => sumFields([this.l22(), this.l23()]) + + toSchedule2l11 = (): number => this.l18() + to1040l25c = (): number => this.l24() + + fields = (): Field[] => [ + this.f1040.namesString(), + this.f1040.info.taxPayer.primaryPerson.ssid, + this.l1(), + this.l2(), + this.l3(), + this.l4(), + this.l5(), + this.l6(), + this.l7(), + + this.l8(), + this.l9(), + this.l10(), + this.l11(), + this.l12(), + this.l13(), + this.l14(), + + this.l15(), + this.l16(), + this.l17(), + + this.l18(), + + this.l19(), + this.l20(), + this.l21(), + this.l22(), + this.l23(), + this.l24() + ] +} diff --git a/src/forms/Y2023/irsForms/F8960.ts b/src/forms/Y2023/irsForms/F8960.ts new file mode 100644 index 000000000..9d27b5080 --- /dev/null +++ b/src/forms/Y2023/irsForms/F8960.ts @@ -0,0 +1,176 @@ +import F1040Attachment from './F1040Attachment' +import { sumFields } from 'ustaxes/core/irsForms/util' +import { FormTag } from 'ustaxes/core/irsForms/Form' +import { netInvestmentIncomeTax } from '../data/federal' +import { Field } from 'ustaxes/core/pdfFiller' + +export default class F8960 extends F1040Attachment { + tag: FormTag = 'f8960' + sequenceIndex = 72 + + isNeeded = (): boolean => { + const filingStatus = this.f1040.info.taxPayer.filingStatus + const magi = this.f1040.l11() + return netInvestmentIncomeTax.taxThreshold(filingStatus) < magi + } + + //Taxable Interest + l1 = (): number | undefined => this.f1040.l2b() + + // Ordinary Dividends + l2 = (): number | undefined => this.f1040.l3b() + /* Enter the gross income from all annuities, except annuities paid from the following. + + - Section 401—Qualified pension, profit-sharing, and stock bonus plans. + - Section 403(a)—Qualified annuity plans purchased by an employer for an employee. + - Section 403(b)—Annuities purchased by public schools or section 501(c)(3) tax-exempt organizations. + - Section 408—Individual Retirement Accounts (IRAs) or Annuities. + - Section 408A—Roth IRAs. + - Section 457(b)—Deferred compensation plans of a state and local government and tax-exempt organization. + - Amounts paid in consideration for services (for example, distributions from a foreign retirement plan that + are paid in the form of an annuity and include investment income that was earned by the retirement plan). + + How your annuities are reported to you. Net investment income from annuities is reported to a recipient on + Form 1099-R, Distributions From Pensions, Annuities, Retirement or Profit-Sharing Plans, IRAs, Insurance + Contracts, etc. However, the amount reported on Form 1099-R may also include annuity payments from + retirement plans that are exempt from NIIT. Amounts subject to NIIT should be identified with code "D" in box 7. + If code "D" is shown in box 7 of Form 1099-R, include on Form 8960, line 3, the taxable amount reported + on Form 1099-R, box 2a. However, if the payor checks box 2b indicating the taxable amount can’t be determined, + you may need to calculate the taxable portion of your distribution. See Pub. 939, General Rule for Pensions + and Annuities, and Pub. 575, Pension and Annuity Income, for details. + */ + l3 = (): number | undefined => undefined + /* Rental Real Estate, Royalties, Partnerships, S Corporations, and Trusts + Enter the following amount from your properly completed return. + + - Schedule 1 (Form 1040), line 5. + - Form 1041, line 5. + - Form 1041-QFT, the portion of line 4 that’s income and loss that properly + would be reported by a trust filing Form 1041 on Form 1041, line 5. + - Form 1040-NR, the amount properly reported on the attachment to your Form 1040-NR + representing the amount that you would properly include on Schedule 1 (Form 1040), line 5, + if you were filing Form 1040 or 1040‐SR and including income and loss only for your period of U.S. residency. + */ + l4a = (): number | undefined => this.f1040.schedule1.l5() + + l4b = (): number | undefined => undefined + + l4c = (): number => sumFields([this.l4a(), this.l4b()]) + + // Line 5a-5d: Gains and Losses on the Disassets of Property + l5a = (): number => sumFields([this.f1040.l7(), this.f1040.schedule1.l4()]) + // TODO: implement line 5b and 5c from worksheet. + l5b = (): number | undefined => undefined + l5c = (): number | undefined => undefined + l5d = (): number => sumFields([this.l5a(), this.l5b(), this.l5c()]) + + // TODO: Line 6: Adjustments to Investment Income for Certain CFCs and PFICs + l6 = (): number | undefined => undefined + + // TODO: Line 7: Other Modifications to Investment Income + l7 = (): number | undefined => undefined + + l8 = (): number => + sumFields([ + this.l1(), + this.l2(), + this.l3(), + this.l4c(), + this.l5d(), + this.l6(), + this.l7() + ]) + + l9a = (): number | undefined => + this.f1040.scheduleA.isNeeded() ? this.f1040.scheduleA.l9() : undefined + // Line 9b Reasonable method: Deductible state tax from Schedule A * Form 8960 Line 8 / 1040 Line 11, Adjusted gross income + l9b = (): number | undefined => { + if (this.f1040.scheduleA.isNeeded()) { + // Sales taxes aren’t deductible in computing net investment income + if (this.f1040.scheduleA.l5aSalesTax()) { + return 0 + } + const deducibleStateTaxes = this.f1040.scheduleA.l5e() + const f1040L11 = this.f1040.l11() + if (f1040L11 === 0) { + return 0 + } + return (deducibleStateTaxes * this.l8()) / f1040L11 + } + return undefined + } + l9c = (): number | undefined => undefined + l9d = (): number => sumFields([this.l9a(), this.l9b(), this.l9c()]) + l10 = (): number | undefined => undefined + l11 = (): number => sumFields([this.l9d(), this.l10()]) + + l12 = (): number => Math.max(0, this.l8() - this.l11()) + + // TODO: This should also take into account values on form 2555 and adjustments for Certain CFCs and Certain PFICs + l13 = (): number => this.f1040.l11() + + l14 = (): number => + netInvestmentIncomeTax.taxThreshold(this.f1040.info.taxPayer.filingStatus) + + l15 = (): number => Math.max(0, this.l13() - this.l14()) + l16 = (): number => (this.l12() < this.l15() ? this.l12() : this.l15()) + + l17 = (): number => Math.round(this.l16() * netInvestmentIncomeTax.taxRate) + + // TODO: Estates and Trusts + // leave all of the following undefined until we support estates and trusts + // these lines are to be left blank for individuals + l18a = (): number | undefined => undefined + l18b = (): number | undefined => undefined + l18c = (): number | undefined => undefined + + l19a = (): number | undefined => undefined + l19b = (): number | undefined => undefined + l19c = (): number | undefined => undefined + + l20 = (): number | undefined => undefined // this.l19c() < this.l18c()? this.l19c() : this.l18c() + l21 = (): number | undefined => undefined // Math.round(this.l20() * netInvestmentIncomeTax.taxRate) + + toSchedule2l12 = (): number | undefined => this.l17() + + fields = (): Field[] => [ + this.f1040.namesString(), + this.f1040.info.taxPayer.primaryPerson.ssid, + undefined, // Section 6013(g) election checkbox + undefined, // Section 6013(h) election checkbox + undefined, // Regulations section 1.1411-10(g) election checkbox + this.l1(), + this.l2(), + this.l3(), + this.l4a(), + this.l4b(), + this.l4c(), + this.l5a(), + this.l5b(), + this.l5c(), + this.l5d(), + this.l6(), + this.l7(), + this.l8(), + this.l9a(), + this.l9b(), + this.l9c(), + this.l9d(), + this.l10(), + this.l11(), + this.l12(), + this.l13(), + this.l14(), + this.l15(), + this.l16(), + this.l17(), + this.l18a(), + this.l18b(), + this.l18c(), + this.l19a(), + this.l19b(), + this.l19c(), + this.l20(), + this.l21() + ] +} diff --git a/src/forms/Y2023/irsForms/F8962.ts b/src/forms/Y2023/irsForms/F8962.ts new file mode 100644 index 000000000..2fb17a029 --- /dev/null +++ b/src/forms/Y2023/irsForms/F8962.ts @@ -0,0 +1,16 @@ +import F1040Attachment from './F1040Attachment' +import { Field } from 'ustaxes/core/pdfFiller' +import { FormTag } from 'ustaxes/core/irsForms/Form' + +/** + * TODO: Not yet implemented + * Net premium tax credit + */ +export default class F8962 extends F1040Attachment { + tag: FormTag = 'f8962' + sequenceIndex = 999 + + credit = (): number | undefined => undefined + + fields = (): Field[] => [] +} diff --git a/src/forms/Y2023/irsForms/F8995.ts b/src/forms/Y2023/irsForms/F8995.ts new file mode 100644 index 000000000..3fb8a9f78 --- /dev/null +++ b/src/forms/Y2023/irsForms/F8995.ts @@ -0,0 +1,103 @@ +import F1040Attachment from './F1040Attachment' +import { FormTag } from 'ustaxes/core/irsForms/Form' +import { FilingStatus } from 'ustaxes/core/data' +import { Field } from 'ustaxes/core/pdfFiller' + +export function getF8995PhaseOutIncome(filingStatus: FilingStatus): number { + let formAMinAmount = 170050 + if (filingStatus === FilingStatus.MFJ) { + formAMinAmount = 340100 + } + return formAMinAmount +} + +function ifNumber( + num: number | undefined, + f: (num: number) => number | undefined +) { + return num !== undefined ? f(num) : undefined +} + +export default class F8995 extends F1040Attachment { + tag: FormTag = 'f8995' + sequenceIndex = 55 + + applicableK1s = () => + this.f1040.info.scheduleK1Form1065s.filter((k1) => k1.section199AQBI > 0) + + netCapitalGains = (): number => { + let rtn = this.f1040.l3a() ?? 0 + if (this.f1040.scheduleD.isNeeded()) { + const l15 = this.f1040.scheduleD.l15() + const l16 = this.f1040.scheduleD.l16() + const min = Math.min(l15, l16) + if (min > 0) rtn += min + } else { + rtn += this.f1040.l7() ?? 0 + } + return rtn + } + + l2 = (): number | undefined => + this.applicableK1s() + .map((k1) => k1.section199AQBI) + .reduce((c, a) => c + a, 0) + l3 = (): number | undefined => undefined + l4 = (): number | undefined => + ifNumber(this.l2(), (num) => num + (this.l3() ?? 0)) + l5 = (): number | undefined => ifNumber(this.l4(), (num) => num * 0.2) + + // TODO: REIT + l6 = (): number => 0 + l7 = (): number => 0 + l8 = (): number | undefined => ifNumber(this.l6(), (num) => num + this.l7()) + l9 = (): number | undefined => ifNumber(this.l8(), (num) => num * 0.2) + + l10 = (): number | undefined => + ifNumber(this.l5(), (num) => num + (this.l9() ?? 0)) + l11 = (): number => this.f1040.l11() - this.f1040.l12() + l12 = (): number => this.netCapitalGains() + l13 = (): number => Math.max(0, this.l11() - this.l12()) + l14 = (): number => this.l13() * 0.2 + l15 = (): number => Math.min(this.l10() ?? 0, this.l14()) + l16 = (): number => Math.min(0, (this.l2() ?? 0) + (this.l3() ?? 0)) + l17 = (): number => Math.min(0, this.l6() + this.l7()) + + deductions = (): number => this.l15() + + fields = (): Field[] => [ + this.f1040.namesString(), + this.f1040.info.taxPayer.primaryPerson.ssid, + this.applicableK1s()[0]?.partnershipName, + this.applicableK1s()[0]?.partnershipEin, + this.applicableK1s()[0]?.section199AQBI, + this.applicableK1s()[1]?.partnershipName, + this.applicableK1s()[1]?.partnershipEin, + this.applicableK1s()[1]?.section199AQBI, + this.applicableK1s()[2]?.partnershipName, + this.applicableK1s()[2]?.partnershipEin, + this.applicableK1s()[2]?.section199AQBI, + this.applicableK1s()[3]?.partnershipName, + this.applicableK1s()[3]?.partnershipEin, + this.applicableK1s()[3]?.section199AQBI, + this.applicableK1s()[4]?.partnershipName, + this.applicableK1s()[4]?.partnershipEin, + this.applicableK1s()[4]?.section199AQBI, + this.l2(), + this.l3(), + this.l4(), + this.l5(), + this.l6(), + this.l7(), + this.l8(), + this.l9(), + this.l10(), + this.l11(), + this.l12(), + this.l13(), + this.l14(), + this.l15(), + this.l16(), + this.l17() + ] +} diff --git a/src/forms/Y2023/irsForms/F8995A.ts b/src/forms/Y2023/irsForms/F8995A.ts new file mode 100644 index 000000000..2beb2eed5 --- /dev/null +++ b/src/forms/Y2023/irsForms/F8995A.ts @@ -0,0 +1,268 @@ +import F8995, { getF8995PhaseOutIncome } from './F8995' + +import { FormTag } from 'ustaxes/core/irsForms/Form' +import { FilingStatus } from 'ustaxes/core/data' +import { sumFields } from 'ustaxes/core/irsForms/util' +import { Field } from 'ustaxes/core/pdfFiller' + +function ifNumber( + num: number | undefined, + f: (num: number) => number | undefined +) { + return num !== undefined ? f(num) : undefined +} + +export default class F8995A extends F8995 { + tag: FormTag = 'f8995a' + sequenceIndex = 55.5 + + l2a = (): number | undefined => this.applicableK1s()[0]?.section199AQBI + l2b = (): number | undefined => this.applicableK1s()[1]?.section199AQBI + l2c = (): number | undefined => this.applicableK1s()[2]?.section199AQBI + + l3a = (): number | undefined => ifNumber(this.l2a(), (num) => num * 0.2) + l3b = (): number | undefined => ifNumber(this.l2b(), (num) => num * 0.2) + l3c = (): number | undefined => ifNumber(this.l2c(), (num) => num * 0.2) + + // TODO: Allow W2 income with QBI + l4a = (): number | undefined => ifNumber(this.l2a(), () => 0) + l4b = (): number | undefined => ifNumber(this.l2a(), () => 0) + l4c = (): number | undefined => ifNumber(this.l2a(), () => 0) + + l5a = (): number | undefined => ifNumber(this.l4a(), (num) => num * 0.5) + l5b = (): number | undefined => ifNumber(this.l4a(), (num) => num * 0.5) + l5c = (): number | undefined => ifNumber(this.l4a(), (num) => num * 0.5) + + l6a = (): number | undefined => ifNumber(this.l4a(), (num) => num * 0.25) + l6b = (): number | undefined => ifNumber(this.l4a(), (num) => num * 0.25) + l6c = (): number | undefined => ifNumber(this.l4a(), (num) => num * 0.25) + + // TODO: Allow UBIA + l7a = (): number | undefined => ifNumber(this.l2a(), () => 0) + l7b = (): number | undefined => ifNumber(this.l2a(), () => 0) + l7c = (): number | undefined => ifNumber(this.l2a(), () => 0) + + l8a = (): number | undefined => ifNumber(this.l7a(), (num) => num * 0.25) + l8b = (): number | undefined => ifNumber(this.l7a(), (num) => num * 0.25) + l8c = (): number | undefined => ifNumber(this.l7a(), (num) => num * 0.25) + + l9a = (): number | undefined => + ifNumber(this.l6a(), (num) => num + (this.l8a() ?? 0)) + l9b = (): number | undefined => + ifNumber(this.l6a(), (num) => num + (this.l8b() ?? 0)) + l9c = (): number | undefined => + ifNumber(this.l6a(), (num) => num + (this.l8c() ?? 0)) + + l10a = (): number | undefined => + ifNumber(this.l5a(), (num) => Math.max(num, this.l9a() ?? 0)) + l10b = (): number | undefined => + ifNumber(this.l5b(), (num) => Math.max(num, this.l9b() ?? 0)) + l10c = (): number | undefined => + ifNumber(this.l5c(), (num) => Math.max(num, this.l9c() ?? 0)) + + l11a = (): number | undefined => + ifNumber(this.l3a(), (num) => Math.min(num, this.l10a() ?? 0)) + l11b = (): number | undefined => + ifNumber(this.l3b(), (num) => Math.min(num, this.l10b() ?? 0)) + l11c = (): number | undefined => + ifNumber(this.l3c(), (num) => Math.min(num, this.l10c() ?? 0)) + + l12a = (): number | undefined => ifNumber(this.l26a(), (num) => num) + l12b = (): number | undefined => ifNumber(this.l26b(), (num) => num) + l12c = (): number | undefined => ifNumber(this.l26c(), (num) => num) + + l13a = (): number | undefined => + ifNumber(this.l12a(), (num) => Math.max(num, this.l11a() ?? 0)) + l13b = (): number | undefined => + ifNumber(this.l12b(), (num) => Math.max(num, this.l11b() ?? 0)) + l13c = (): number | undefined => + ifNumber(this.l12c(), (num) => Math.max(num, this.l11c() ?? 0)) + + // TODO: Patron reduction + l14a = (): number | undefined => ifNumber(this.l2a(), () => 0) + l14b = (): number | undefined => ifNumber(this.l2a(), () => 0) + l14c = (): number | undefined => ifNumber(this.l2a(), () => 0) + + l15a = (): number | undefined => + ifNumber(this.l13a(), (num) => num - (this.l14a() ?? 0)) + l15b = (): number | undefined => + ifNumber(this.l13b(), (num) => num - (this.l14b() ?? 0)) + l15c = (): number | undefined => + ifNumber(this.l13c(), (num) => num - (this.l14c() ?? 0)) + + l16 = (): number => sumFields([this.l15a(), this.l15b(), this.l15c()]) + + l17a = (): number | undefined => ifNumber(this.l3a(), (num) => num) + l17b = (): number | undefined => ifNumber(this.l3b(), (num) => num) + l17c = (): number | undefined => ifNumber(this.l3c(), (num) => num) + + l18a = (): number | undefined => ifNumber(this.l10a(), (num) => num) + l18b = (): number | undefined => ifNumber(this.l10b(), (num) => num) + l18c = (): number | undefined => ifNumber(this.l10c(), (num) => num) + + l19a = (): number | undefined => + ifNumber(this.l17a(), (num) => num - (this.l18a() ?? 0)) + l19b = (): number | undefined => + ifNumber(this.l17b(), (num) => num - (this.l18b() ?? 0)) + l19c = (): number | undefined => + ifNumber(this.l17c(), (num) => num - (this.l18c() ?? 0)) + + l20 = (): number => this.f1040.l11() - this.f1040.l12() + l21 = (): number => + getF8995PhaseOutIncome(this.f1040.info.taxPayer.filingStatus) + l22 = (): number => this.l20() - this.l21() + l23 = (): number => + this.f1040.info.taxPayer.filingStatus === FilingStatus.MFJ ? 100000 : 50000 + l24 = (): number => Math.round((this.l22() / this.l23()) * 10000) / 10000 // We want xx.xx% + + l25a = (): number | undefined => + ifNumber(this.l19a(), (num) => num * this.l24()) + l25b = (): number | undefined => + ifNumber(this.l19b(), (num) => num * this.l24()) + l25c = (): number | undefined => + ifNumber(this.l19c(), (num) => num * this.l24()) + + l26a = (): number | undefined => + ifNumber(this.l17a(), (num) => num - (this.l25a() ?? 0)) + l26b = (): number | undefined => + ifNumber(this.l17b(), (num) => num - (this.l25b() ?? 0)) + l26c = (): number | undefined => + ifNumber(this.l17c(), (num) => num - (this.l25c() ?? 0)) + + l27 = (): number => this.l16() + + // TODO: REIT + l28 = (): number => 0 + l29 = (): number => 0 + + l30 = (): number => Math.max(0, this.l28() + this.l29()) + l31 = (): number => this.l30() * 0.2 + + l32 = (): number => this.l27() + this.l31() + l33 = (): number => this.l20() + l34 = (): number => this.netCapitalGains() + l35 = (): number => this.l33() - this.l34() + l36 = (): number => this.l35() * 0.2 + l37 = (): number => Math.min(this.l32(), this.l36()) + + // TODO: DPAD + l38 = (): number => 0 + + l39 = (): number => this.l37() - this.l38() + deductions = (): number => this.l39() + l40 = (): number => Math.min(0, this.l28() + this.l29()) + + fields = (): Field[] => [ + this.f1040.namesString(), + this.f1040.info.taxPayer.primaryPerson.ssid, + this.applicableK1s()[0]?.partnershipName, + false, // 1Ab + false, // 1Ac + this.applicableK1s()[0]?.partnershipEin, + false, // 1Ae + this.applicableK1s()[1]?.partnershipName, + false, // 1Bb + false, // 1Bc + this.applicableK1s()[1]?.partnershipEin, + false, // 1Be + this.applicableK1s()[2]?.partnershipName, + false, // 1Cb + false, // 1Cc + this.applicableK1s()[2]?.partnershipEin, + false, // 1Ce + this.l2a(), + this.l2b(), + this.l2c(), + this.l3a(), + this.l3b(), + this.l3c(), + this.l4a(), + this.l4b(), + this.l4c(), + this.l5a(), + this.l5b(), + this.l5c(), + this.l6a(), + this.l6b(), + this.l6c(), + this.l7a(), + this.l7b(), + this.l7c(), + this.l8a(), + this.l8b(), + this.l8c(), + this.l9a(), + this.l9b(), + this.l9c(), + this.l10a(), + this.l10b(), + this.l10c(), + this.l11a(), + this.l11b(), + this.l11c(), + this.l12a(), + this.l12b(), + this.l12c(), + this.l13a(), + this.l13b(), + this.l13c(), + this.l14a(), + this.l14b(), + this.l14c(), + this.l15a(), + this.l15b(), + this.l15c(), + this.l16(), + undefined, // Gray + undefined, // Gray + this.l17a(), + this.l17b(), + this.l17c(), + this.l18a(), + this.l18b(), + this.l18c(), + this.l19a(), + this.l19b(), + this.l19c(), + this.l20(), + undefined, // Gray + undefined, // Gray + undefined, // Gray + this.l21(), + undefined, // Gray + undefined, // Gray + undefined, // Gray + this.l22(), + undefined, // Gray + undefined, // Gray + undefined, // Gray + this.l23(), + undefined, // Gray + undefined, // Gray + undefined, // Gray + (this.l24() * 100).toFixed(2) + '%', // TODO: Percent sign is duplicated, but it prevents Fill.ts from rounding this + undefined, // Gray + undefined, // Gray + undefined, // Gray + this.l25a(), + this.l25b(), + this.l25c(), + this.l26a(), + this.l26b(), + this.l26c(), + this.l27(), + this.l28(), + this.l29(), + this.l30(), + this.l31(), + this.l32(), + this.l33(), + this.l34(), + this.l35(), + this.l36(), + this.l37(), + this.l38(), + this.l39(), + this.l40() + ] +} diff --git a/src/forms/Y2023/irsForms/Main.ts b/src/forms/Y2023/irsForms/Main.ts new file mode 100644 index 000000000..271bb3e2c --- /dev/null +++ b/src/forms/Y2023/irsForms/Main.ts @@ -0,0 +1,17 @@ +import { Asset, Information } from 'ustaxes/core/data' +import { Either, run } from 'ustaxes/core/util' +import F1040 from './F1040' +import Form from 'ustaxes/core/irsForms/Form' +import { F1040Error } from 'ustaxes/forms/errors' +import { validate } from 'ustaxes/forms/F1040Base' + +export const create1040 = ( + info: Information, + assets: Asset[] +): Either => + run(validate(info)) + .map<[F1040, Form[]]>((info) => { + const f1040 = new F1040(info, assets) + return [f1040, f1040.schedules()] + }) + .value() diff --git a/src/forms/Y2023/irsForms/Schedule1.ts b/src/forms/Y2023/irsForms/Schedule1.ts new file mode 100644 index 000000000..f387e6e74 --- /dev/null +++ b/src/forms/Y2023/irsForms/Schedule1.ts @@ -0,0 +1,242 @@ +import F1040Attachment from './F1040Attachment' +import { FormTag } from 'ustaxes/core/irsForms/Form' +import { sumFields } from 'ustaxes/core/irsForms/util' +import F1040 from './F1040' +import { Field } from 'ustaxes/core/pdfFiller' + +export default class Schedule1 extends F1040Attachment { + tag: FormTag = 'f1040s1' + sequenceIndex = 1 + otherIncomeStrings: Set + + constructor(f1040: F1040) { + super(f1040) + this.otherIncomeStrings = new Set() + } + + isNeeded = (): boolean => + this.f1040.scheduleE.isNeeded() || + (this.f1040.studentLoanInterestWorksheet !== undefined && + this.f1040.studentLoanInterestWorksheet.notMFS() && + this.f1040.studentLoanInterestWorksheet.isNotDependent()) || + this.f1040.f8889.isNeeded() || + (this.f1040.f8889Spouse?.isNeeded() ?? false) + + l1 = (): number | undefined => undefined + l2a = (): number | undefined => undefined + l2b = (): number | undefined => undefined + l3 = (): number | undefined => undefined + l4 = (): number | undefined => undefined + l5 = (): number | undefined => this.f1040.scheduleE.l41() + l6 = (): number | undefined => undefined + l7 = (): number | undefined => undefined + l8a = (): number | undefined => undefined + l8b = (): number | undefined => undefined + l8c = (): number | undefined => undefined + l8d = (): number | undefined => undefined + l8e = (): number | undefined => undefined + l8f = (): number | undefined => + sumFields([this.f1040.f8889.l16(), this.f1040.f8889Spouse?.l16()]) + l8g = (): number | undefined => undefined + l8h = (): number | undefined => undefined + l8i = (): number | undefined => undefined + l8j = (): number | undefined => undefined + l8k = (): number | undefined => undefined + l8l = (): number | undefined => undefined + l8m = (): number | undefined => undefined + l8n = (): number | undefined => undefined + l8o = (): number | undefined => undefined + l8p = (): number | undefined => undefined + l8q = (): number | undefined => undefined + l8r = (): number | undefined => undefined + l8s = (): number | undefined => undefined + l8t = (): number | undefined => undefined + l8u = (): number | undefined => undefined + l8z = (): number => { + if ( + (this.f1040.f8889.isNeeded() && this.f1040.f8889.l20() > 0) || + ((this.f1040.f8889Spouse?.isNeeded() ?? false) && + this.f1040.f8889Spouse?.l20() !== undefined && + this.f1040.f8889Spouse.l20() > 0) + ) { + this.otherIncomeStrings.add('HSA') + } + + return sumFields([this.f1040.f8889.l20(), this.f1040.f8889Spouse?.l20()]) + } + + l9 = (): number => + sumFields([ + this.l8a(), + this.l8b(), + this.l8c(), + this.l8d(), + this.l8e(), + this.l8f(), + this.l8g(), + this.l8h(), + this.l8i(), + this.l8j(), + this.l8k(), + this.l8l(), + this.l8m(), + this.l8n(), + this.l8o(), + this.l8p(), + this.l8q(), + this.l8r(), + this.l8s(), + this.l8t(), + this.l8u(), + this.l8z() + ]) + + l10 = (): number => + sumFields([ + this.l1(), + this.l2a(), + this.l3(), + this.l4(), + this.l5(), + this.l6(), + this.l7(), + this.l9() + ]) + + to1040Line8 = (): number => this.l10() + + l11 = (): number | undefined => undefined + l12 = (): number | undefined => undefined + l13 = (): number | undefined => + sumFields([this.f1040.f8889.l13(), this.f1040.f8889Spouse?.l13()]) + l14 = (): number | undefined => undefined + l15 = (): number | undefined => this.f1040.scheduleSE.l13() + l16 = (): number | undefined => undefined + l17 = (): number | undefined => undefined + l18 = (): number | undefined => undefined + l19a = (): number | undefined => undefined + l19b = (): string | undefined => undefined + l19c = (): string | undefined => undefined + l20 = (): number | undefined => undefined + l21 = (): number | undefined => this.f1040.studentLoanInterestWorksheet?.l9() + l23 = (): number | undefined => undefined + l24a = (): number | undefined => undefined + l24b = (): number | undefined => undefined + l24c = (): number | undefined => undefined + l24d = (): number | undefined => undefined + l24e = (): number | undefined => undefined + l24f = (): number | undefined => undefined + l24g = (): number | undefined => undefined + l24h = (): number | undefined => undefined + l24i = (): number | undefined => undefined + l24j = (): number | undefined => undefined + l24k = (): number | undefined => undefined + l24zDesc = (): string | undefined => undefined + l24zDesc2 = (): string | undefined => undefined + l24z = (): number | undefined => undefined + + l25 = (): number => + sumFields([ + this.l24a(), + this.l24b(), + this.l24c(), + this.l24d(), + this.l24e(), + this.l24f(), + this.l24g(), + this.l24h(), + this.l24i(), + this.l24j(), + this.l24k(), + this.l24z() + ]) + + l26 = (): number => + sumFields([ + this.l11(), + this.l12(), + this.l13(), + this.l14(), + this.l15(), + this.l16(), + this.l17(), + this.l18(), + this.l19a(), + this.l20(), + this.l21(), + this.l23(), + this.l25() + ]) + + to1040Line10 = (): number => this.l26() + + fields = (): Field[] => [ + this.f1040.namesString(), + this.f1040.info.taxPayer.primaryPerson.ssid, + this.l1(), + this.l2a(), + this.l2b(), + this.l3(), + this.l4(), + this.l5(), + this.l6(), + this.l7(), + this.l8a(), + this.l8b(), + this.l8c(), + this.l8d(), + this.l8e(), + this.l8f(), + this.l8g(), + this.l8h(), + this.l8i(), + this.l8j(), + this.l8k(), + this.l8l(), + this.l8m(), + this.l8n(), + this.l8o(), + this.l8p(), + this.l8q(), + this.l8r(), + this.l8s(), + this.l8t(), + this.l8u(), + Array.from(this.otherIncomeStrings).join(' '), + undefined, + this.l8z(), + this.l9(), + this.l10(), + this.l11(), + this.l12(), + this.l13(), + this.l14(), + this.l15(), + this.l16(), + this.l17(), + this.l18(), + this.l19a(), + this.l19b(), + this.l19c(), + this.l20(), + this.l21(), + // Reserved for future use + this.l23(), + this.l24a(), + this.l24b(), + this.l24c(), + this.l24d(), + this.l24e(), + this.l24f(), + this.l24g(), + this.l24h(), + this.l24i(), + this.l24j(), + this.l24k(), + this.l24zDesc(), + this.l24zDesc2(), + this.l24z(), + this.l25(), + this.l26() + ] +} diff --git a/src/forms/Y2023/irsForms/Schedule2.ts b/src/forms/Y2023/irsForms/Schedule2.ts new file mode 100644 index 000000000..d8b158506 --- /dev/null +++ b/src/forms/Y2023/irsForms/Schedule2.ts @@ -0,0 +1,176 @@ +import F1040Attachment from './F1040Attachment' +import { FormTag } from 'ustaxes/core/irsForms/Form' +import { sumFields } from 'ustaxes/core/irsForms/util' +import { Field } from 'ustaxes/core/pdfFiller' + +export default class Schedule2 extends F1040Attachment { + tag: FormTag = 'f1040s2' + sequenceIndex = 2 + + // Part I: Tax + l1 = (): number | undefined => this.f1040.f6251.l11() + l2 = (): number | undefined => undefined // TODO: excess advance premium tax credit repayment (form 8962) + l3 = (): number => sumFields([this.l1(), this.l2()]) + + // Part II: Other Tax + l4 = (): number | undefined => this.f1040.scheduleSE.l12() // self-employment tax (schedule SE) + l5 = (): number | undefined => undefined // TODO: unreported FICA tax + l6 = (): number | undefined => undefined // TODO: additional tax on retirement accounts + l7 = (): number | undefined => sumFields([this.l5(), this.l6()]) + l8box = (): boolean => false // TODO: implement this after l8 is implemented. + l8 = (): number | undefined => undefined // TODO: additional tax on IRAs or other tax favored accoutns, form 5329 + l9 = (): number | undefined => undefined // TODO: household employment taxes, schedule H + l10 = (): number | undefined => undefined // repayment of firsttime homebuyer credit, form 5405 + l11 = (): number | undefined => this.f1040.f8959.toSchedule2l11() + l12 = (): number | undefined => this.f1040.f8960.toSchedule2l12() + l13 = (): number | undefined => undefined // TODO: uncollected ss and medicare or rrta tax on tips or group-term life insurance, w-2, box 12 + l14 = (): number | undefined => undefined // TODO - interest on tax due on installment income from the sale of residential lots and timeshares + l15 = (): number | undefined => undefined //interest on the deferred tax on gain from certain installment sales with a sales price over 150000. + l16 = (): number | undefined => undefined // recapture of low-income housing credit, form 8611 + + // Other additional taxes: + // TODO: Recapture of other credits. List type, form number, and + // amount ▶ + l17aDesc = (): string | undefined => undefined + l17a = (): number | undefined => undefined + // TODO: Recapture of federal mortgage subsidy. If you sold your home in + // 2021, see instructions + l17b = (): number | undefined => undefined + + l17c = (): number | undefined => + sumFields([this.f1040.f8889.l17b(), this.f1040.f8889Spouse?.l17b()]) + + l17d = (): number | undefined => + sumFields([this.f1040.f8889.l21(), this.f1040.f8889Spouse?.l21()]) + // TODO: Additional tax on Archer MSA distributions. Attach Form 8853 + l17e = (): number | undefined => undefined + // TODO: Additional tax on Medicare Advantage MSA distributions. Attach + // Form 8853 + l17f = (): number | undefined => undefined + // TODO: Recapture of a charitable contribution deduction related to a + // fractional interest in tangible personal property...17g + l17g = (): number | undefined => undefined + // TODO: Income you received from a nonqualified deferred compensation + // plan that fails to meet the requirements of section 409A.17h + l17h = (): number | undefined => undefined + // TODO Compensation you received from a nonqualified deferred + // compensation plan described in section 457A + l17i = (): number | undefined => undefined + // Section 72(m)(5) excess benefits tax + l17j = (): number | undefined => undefined + // TODO: Golden parachute payments + l17k = (): number | undefined => undefined + // Tax on accumulation distribution of trusts + l17l = (): number | undefined => undefined + // m Excise tax on insider stock compensation from an expatriated + // corporation + l17m = (): number | undefined => undefined + // n Look-back interest under section 167(g) or 460(b) from Form + // 8697 or 8866 + l17n = (): number | undefined => undefined + // o Tax on non-effectively connected income for any part of the + // year you were a nonresident alien from Form 1040-NR + l17o = (): number | undefined => undefined + // p Any interest from Form 8621, line 16f, relating to distributions + // from, and disassets of, stock of a section 1291 fund.. 17p + l17p = (): number | undefined => undefined + // q Any interest from Form 8621, line 24 + l17q = (): number | undefined => undefined + // z Any other taxes. List type and amount ▶ + l17zDesc = (): string | undefined => undefined + l17z = (): number | undefined => undefined + // 18Total additional taxes. Add lines 17a through 17z.......18 + l18 = (): number => + sumFields([ + this.l17a(), + this.l17b(), + this.l17c(), + this.l17d(), + this.l17e(), + this.l17f(), + this.l17g(), + this.l17h(), + this.l17i(), + this.l17j(), + this.l17k(), + this.l17l(), + this.l17m(), + this.l17n(), + this.l17o(), + this.l17p(), + this.l17q(), + this.l17z() + ]) + + // TODO: Section 965 net tax liability installment from Form 965-A. . + l20 = (): number | undefined => undefined + + // Add lines 4, 7 through 16, 18, and 19. These are your total other taxes. Enter here + l21 = (): number => + sumFields([ + this.l4(), + this.l7(), + this.l8(), + this.l9(), + this.l10(), + this.l11(), + this.l12(), + this.l13(), + this.l14(), + this.l15(), + this.l16(), + this.l18() + ]) + + to1040l23 = (): number => this.l21() + // and on Form 1040 or 1040-SR, line 23, or Form 1040-NR, line 23b + + fields = (): Field[] => [ + this.f1040.namesString(), + this.f1040.info.taxPayer.primaryPerson.ssid, + + this.l1(), + this.l2(), + this.l3(), + + this.l4(), + this.l5(), + this.l6(), + this.l7(), + this.l8box(), + this.l8(), + this.l9(), + this.l10(), + this.l11(), + this.l12(), + this.l13(), + this.l14(), + this.l15(), + this.l16(), + this.l17aDesc(), + this.l17a(), + this.l17b(), + this.l17c(), + this.l17d(), + this.l17e(), + this.l17f(), + this.l17g(), + this.l17h(), + this.l17i(), + this.l17j(), + this.l17k(), + this.l17l(), + this.l17m(), + this.l17n(), + this.l17o(), + this.l17p(), + this.l17q(), + this.l17zDesc(), + undefined, + this.l17z(), + this.l18(), + undefined, //this.l19(), + this.l20(), + this.l21() + ] +} diff --git a/src/forms/Y2023/irsForms/Schedule3.ts b/src/forms/Y2023/irsForms/Schedule3.ts new file mode 100644 index 000000000..750e572d9 --- /dev/null +++ b/src/forms/Y2023/irsForms/Schedule3.ts @@ -0,0 +1,205 @@ +import F1040Attachment from './F1040Attachment' +import { PersonRole } from 'ustaxes/core/data' +import { sumFields } from 'ustaxes/core/irsForms/util' +import { FormTag } from 'ustaxes/core/irsForms/Form' +import { fica } from '../data/federal' +import { Field } from 'ustaxes/core/pdfFiller' + +export default class Schedule3 extends F1040Attachment { + tag: FormTag = 'f1040s3' + sequenceIndex = 3 + + claimableExcessSSTaxWithholding = (): number => { + const w2s = this.f1040.validW2s() + + // Excess FICA taxes are calculated per person. If an individual person + // has greater than the applicable amount then they are entitled to a refund + // of that amount + + let claimableExcessFica = 0 + const primaryFica = w2s + .filter((w2) => w2.personRole == PersonRole.PRIMARY) + .map((w2) => w2.ssWithholding) + .reduce((l, r) => l + r, 0) + const spouseFica = w2s + .filter((w2) => w2.personRole == PersonRole.SPOUSE) + .map((w2) => w2.ssWithholding) + .reduce((l, r) => l + r, 0) + + if ( + primaryFica > fica.maxSSTax && + w2s + .filter((w2) => w2.personRole == PersonRole.PRIMARY) + .every((w2) => w2.ssWithholding <= fica.maxSSTax) + ) { + claimableExcessFica += primaryFica - fica.maxSSTax + } + + if ( + spouseFica > fica.maxSSTax && + w2s + .filter((w2) => w2.personRole == PersonRole.SPOUSE) + .every((w2) => w2.ssWithholding <= fica.maxSSTax) + ) { + claimableExcessFica += spouseFica - fica.maxSSTax + } + + return claimableExcessFica + } + + isNeeded = (): boolean => this.claimableExcessSSTaxWithholding() > 0 + + deductions = (): number => 0 + // Part I: Nonrefundable credits + l1 = (): number | undefined => undefined + l2 = (): number | undefined => undefined + l3 = (): number | undefined => this.f1040.f8863?.l19() + l4 = (): number | undefined => undefined + l5 = (): number | undefined => undefined + l6a = (): number | undefined => undefined // TODO: other credits + l6b = (): number | undefined => undefined // TODO: other credits + l6c = (): number | undefined => undefined // TODO: other credits + l6d = (): number | undefined => undefined // TODO: other credits + l6e = (): number | undefined => undefined // TODO: other credits + l6f = (): number | undefined => undefined // TODO: other credits + l6g = (): number | undefined => undefined // TODO: other credits + l6h = (): number | undefined => undefined // TODO: other credits + l6i = (): number | undefined => undefined // TODO: other credits + l6j = (): number | undefined => undefined // TODO: other credits + l6k = (): number | undefined => undefined // TODO: other credits + l6l = (): number | undefined => undefined // TODO: other credits + l6zDesc1 = (): string | undefined => undefined + l6zDesc2 = (): string | undefined => undefined + l6z = (): number | undefined => undefined // TODO: other credits + + l7 = (): number => + sumFields([ + this.l6a(), + this.l6b(), + this.l6c(), + this.l6d(), + this.l6e(), + this.l6f(), + this.l6g(), + this.l6h(), + this.l6i(), + this.l6j(), + this.l6k(), + this.l6l(), + this.l6z() + ]) + + l8 = (): number => + sumFields([ + this.l1(), + this.l2(), + this.l3(), + this.l4(), + this.l5(), + this.l7() + ]) + + // Part II: Other payments and refundable credits + l9 = (): number | undefined => this.f1040.f8962?.credit() + + // TODO: Amount paid with extension for time to file + l10 = (): number | undefined => undefined + + l11 = (): number => + // TODO: also applies to RRTA tax + this.claimableExcessSSTaxWithholding() + + l12 = (): number | undefined => this.f1040.f4136?.credit() + + l13a = (): number | undefined => this.f1040.f2439?.credit() + // TODO: qualified sick and family leave credits + // Schedule H and form 7202 pre 4/1/21 + l13b = (): number | undefined => undefined + + // reserved! + l13c = (): number | undefined => undefined + + // TODO: Credit for repayment of amounts included in income from earlier years + l13d = (): number | undefined => undefined // TODO: 'other' box + + // reserved! + l13e = (): number | undefined => undefined + + // deferred amount of net 965 tax liability + l13f = (): number | undefined => undefined + + // reserved! + l13g = (): number | undefined => undefined + + // TODO: qualified sick and family leave credits + // Schedule H and form 7202 post 3/31/21 + l13h = (): number | undefined => undefined + + l13zDesc1 = (): string | undefined => undefined + l13zDesc2 = (): string | undefined => undefined + l13z = (): number | undefined => undefined + + l14 = (): number => + sumFields([ + this.l13a(), + this.l13b(), + this.l13c(), + this.l13d(), + this.l13e(), + this.l13f(), + this.l13g(), + this.l13h(), + this.l13z() + ]) + + l15 = (): number => + sumFields([this.l9(), this.l10(), this.l11(), this.l12(), this.l14()]) + + // Credit for child and dependent care expenses form 2441, line 10 + + fields = (): Field[] => [ + this.f1040.namesString(), + this.f1040.info.taxPayer.primaryPerson.ssid, + this.l1(), + this.l2(), + this.l3(), + this.l4(), + this.l5(), + this.l6a(), + this.l6b(), + this.l6c(), + this.l6d(), + this.l6e(), + this.l6f(), + this.l6g(), + this.l6h(), + this.l6i(), + this.l6j(), + this.l6k(), + this.l6l(), + this.l6zDesc1(), + this.l6zDesc2(), + this.l6z(), + this.l7(), + this.l8(), + + this.l9(), + this.l10(), + this.l11(), + this.l12(), + + this.l13a(), + this.l13b(), + //this.l13c(), // this field is left for future use and is not fillable + this.l13d(), + //this.l13e(), // this field is left for future use and is not fillable + this.l13f(), + //this.l13g(), // this field is left for future use and is not fillable + this.l13h(), + this.l13zDesc1(), + this.l13zDesc2(), + this.l13z(), + this.l14(), + this.l15() + ] +} diff --git a/src/forms/Y2023/irsForms/Schedule8812.ts b/src/forms/Y2023/irsForms/Schedule8812.ts new file mode 100644 index 000000000..58201296a --- /dev/null +++ b/src/forms/Y2023/irsForms/Schedule8812.ts @@ -0,0 +1,316 @@ +import F1040Attachment from './F1040Attachment' +import { CreditType, Dependent, FilingStatus } from 'ustaxes/core/data' +import { sumFields } from 'ustaxes/core/irsForms/util' +import { FormTag } from 'ustaxes/core/irsForms/Form' +import { Field } from 'ustaxes/core/pdfFiller' +import { nextMultipleOf1000 } from 'ustaxes/core/util' + +type Part2a = { allowed: boolean } & Partial<{ + l16a: number + l16bdeps: number + l16b: number + l17: number + l18a: number + l18b: number + l19No: boolean + l19Yes: boolean + l19: number + l20: number + l20No: boolean + l20Yes: boolean + toLine27: number +}> + +type Part2b = { allowed: boolean } & Partial<{ + l21: number + l22: number + l23: number + l24: number + l25: number + l26: number + toLine27: number +}> + +export default class Schedule8812 extends F1040Attachment { + tag: FormTag = 'f1040s8' + sequenceIndex = 47 + + isNeeded = (): boolean => + this.f1040.info.taxPayer.dependents.some( + (dep) => + this.f1040.qualifyingDependents.qualifiesChild(dep) || + this.f1040.qualifyingDependents.qualifiesOther(dep) + ) + + l1 = (): number => this.f1040.l11() + + // TODO: Puerto Rico income + l2a = (): number => 0 + + l2b = (): number => + sumFields([this.f1040.f2555?.l45(), this.f1040.f2555?.l50()]) + + l2c = (): number => this.f1040.f4563?.l15() ?? 0 + + l2d = (): number => sumFields([this.l2a(), this.l2b(), this.l2c()]) + + l3 = (): number => sumFields([this.l1(), this.l2d()]) + + creditDependents = (): Dependent[] => + this.f1040.qualifyingDependents.qualifyingChildren() + + l4 = (): number => this.creditDependents().length + + l5 = (): number => this.l4() * 2000 + + // TODO: Verify: + // Number of other dependents, including any qualifying children, who are not under age 18 or who do not have the required SSN. Do not include yourself, your spouse, + // or anyone who is not a US citizen/national/resident alien, + // or do not have the required SSN. + l6 = (): number => this.f1040.info.taxPayer.dependents.length - this.l4() + + l7 = (): number => this.l6() * 500 + + l8 = (): number => sumFields([this.l5(), this.l7()]) + + l9 = (): number => + this.f1040.info.taxPayer.filingStatus === FilingStatus.MFJ ? 400000 : 200000 + + l10 = (): number => nextMultipleOf1000(Math.max(0, this.l3() - this.l9())) + + l11 = (): number => this.l10() * 0.05 + + l12 = (): number => Math.max(0, this.l8() - this.l11()) + l12yes = (): boolean => this.l8() > this.l11() + l12no = (): boolean => !this.l12yes() + // you and spouse have residence in US for more than half of year. + // TODO: Assuming true + l13 = (): number => this.creditLimitWorksheetA() + + l14 = (): number => (this.l12no() ? 0 : Math.min(this.l12(), this.l13())) + + // Check this box if you do not want to file the additional tax credit + // TODO: Assuming that people do right now + l15 = (): boolean => true + + creditLimitWorksheetB = (): number | undefined => undefined + + creditLimitWorksheetA = (): number => { + const wsl1 = this.f1040.l18() + const schedule3Fields = this.f1040.schedule3.isNeeded() + ? [ + this.f1040.schedule3.l1(), + this.f1040.schedule3.l2(), + this.f1040.schedule3.l3(), + this.f1040.schedule3.l4(), + this.f1040.schedule3.l6l() + ] + : [] + + const wsl2 = sumFields([ + ...schedule3Fields, + this.f1040.f5695?.l30(), + this.f1040.f8936?.l15(), + this.f1040.f8936?.l23(), + this.f1040.scheduleR?.l22() + ]) + const wsl3 = Math.max(0, wsl1 - wsl2) + const wsl4 = this.creditLimitWorksheetB() ?? 0 + const wsl5 = Math.max(0, wsl3 - wsl4) + return wsl5 + } + + // TODO: Letter 6419 advance child tax credit payments + letter6419Payments = (): number | undefined => + this.f1040.info.credits + .filter((c) => c.type === CreditType.AdvanceChildTaxCredit) + .reduce((sum, c) => sum + c.amount, 0) + + to1040Line19 = (): number => this.l14() + + to1040Line28 = (): number | undefined => this.l27() + + earnedIncomeWorksheet = (): number => { + const l1a = this.f1040.l1z() + const l1b = this.f1040.nonTaxableCombatPay() + const l2a = this.f1040.scheduleC?.l1() ?? 0 + // Todo: 1065 Schedule K-1, box 14, code A, and other + // data also belong here. + const l2b = this.f1040.scheduleC?.l31() ?? 0 + // TODO: Net farm profit... + // const l2c = undefined + // TODO: Farm optional method for self-employment net earnings + const l2d = 0 + + // TODO: min(l2c, l2d) + // Allowed to be a loss: + const l2e = Math.min(0, l2d) + + const l3 = sumFields([l1a, l1b, l2a, l2b, l2e]) + + const allowed = l3 > 0 + + // TODO: Scholarship or grant not reported on w-2 + const l4a = !allowed ? undefined : 0 + + // TODO: Penal income + const l4b = !allowed ? undefined : 0 + + // TODO: nonqualified deferred comp plan or 457 plan + const l4c = !allowed ? undefined : 0 + + // TODO: Amount included on 1040 that is a medicaid + // waiver payment excluded from income, schedule 1, line 8z + // or choose to include in earned income, then enter 0. + const l4d = !allowed ? undefined : 0 + + const l5 = this.f1040.schedule1.l15() ?? 0 + + const l6 = sumFields([l4a, l4b, l4c, l4d, l5]) + + const l7 = Math.max(0, l3 - l6) + + return l7 + } + + part2a = (): Part2a => { + const l16a = Math.max(0, this.l12() - this.l14()) + const l16bdeps = this.l4() + + const l16b = l16bdeps * 1600 + + const l17 = Math.min(l16a, l16b) + const l18a = this.earnedIncomeWorksheet() + const l18b = this.f1040.nonTaxableCombatPay() ?? 0 + const l19No = l18a > 2500 + const l19Yes = l18a <= 2500 + const l19 = Math.max(0, l18a - 2500) + const l20 = l19 * 0.15 + const l20No = l16b >= 4800 + const l20Yes = l16b < 4800 + + // TODO: check for Puerto Rico residency + const toLine27 = (() => { + if (l20No && l20 > 0) { + return Math.min(l17, l20) + } else if (l20Yes && l20 >= l17) { + return l17 + } + })() + + return { + allowed: true, + l16a, + l16bdeps, + l16b, + l17, + l18a, + l18b, + l19No, + l19Yes, + l19, + l20, + l20No, + l20Yes, + toLine27 + } + } + + part2b = (): Part2b => { + const part2a = this.part2a() + // three or more qualifying children. + const allowed = part2a.allowed + + if (!allowed) return { allowed: false } + + const ssWithholding = this.f1040 + .validW2s() + .reduce((res, w2) => res + w2.ssWithholding, 0) + + const medicareWithholding = this.f1040 + .validW2s() + .reduce((res, w2) => res + w2.medicareWithholding, 0) + + const l21 = ssWithholding + medicareWithholding + + const l22 = sumFields([ + this.f1040.schedule1.l15(), + this.f1040.schedule2.l5(), + this.f1040.schedule2.l6(), + this.f1040.schedule2.l13() + ]) + + const l23 = sumFields([l21, l22]) + + const l24 = sumFields([this.f1040.l27(), this.f1040.schedule3.l11()]) + + const l25 = Math.max(0, l23 - l24) + + const l26 = Math.max(part2a.l20 ?? 0, l25) + + const toLine27 = Math.min(part2a.l17 ?? 0, l26) + + return { + allowed: true, + l21, + l22, + l23, + l24, + l25, + l26, + toLine27 + } + } + + l27 = (): number | undefined => + this.l12no() ? 0 : this.part2a().toLine27 ?? this.part2b().toLine27 + + fields = (): Field[] => { + const part2a = this.part2a() + const part2b = this.part2b() + + return [ + this.f1040.namesString(), + this.f1040.info.taxPayer.primaryPerson.ssid, + this.l1(), + this.l2a(), + this.l2b(), + this.l2c(), + this.l2d(), + this.l3(), + this.l4(), + this.l5(), + this.l6(), + this.l7(), + this.l8(), + this.l9(), + this.l10(), + this.l11(), + this.l12(), + this.l12no(), + this.l12yes(), + this.l13(), + this.l14(), + this.l15(), + part2a.l16a, + part2a.l16bdeps, + part2a.l16b, + part2a.l17, + part2a.l18a, + part2a.l18b, + part2a.l19No, + part2a.l19Yes, + part2a.l19, + part2a.l20, + part2a.l20No, + part2a.l20Yes, + part2b.l21, + part2b.l22, + part2b.l23, + part2b.l24, + part2b.l25, + part2b.l26, + this.l27() + ] + } +} diff --git a/src/forms/Y2023/irsForms/ScheduleA.ts b/src/forms/Y2023/irsForms/ScheduleA.ts new file mode 100644 index 000000000..a65498fca --- /dev/null +++ b/src/forms/Y2023/irsForms/ScheduleA.ts @@ -0,0 +1,160 @@ +import F1040Attachment from './F1040Attachment' +import { FilingStatus, ItemizedDeductions } from 'ustaxes/core/data' +import { FormTag } from 'ustaxes/core/irsForms/Form' +import { Field } from 'ustaxes/core/pdfFiller' +import F1040 from './F1040' + +const blankItemizedDeductions = { + medicalAndDental: 0, + stateAndLocalTaxes: 0, + isSalesTax: false, + stateAndLocalRealEstateTaxes: 0, + stateAndLocalPropertyTaxes: 0, + interest8a: 0, + interest8b: 0, + interest8c: 0, + interest8d: 0, + investmentInterest: 0, + charityCashCheck: 0, + charityOther: 0 +} + +export default class ScheduleA extends F1040Attachment { + tag: FormTag = 'f1040sa' + itemizedDeductions: ItemizedDeductions + sequenceIndex = 7 + + constructor(f1040: F1040) { + super(f1040) + this.itemizedDeductions = { + ...blankItemizedDeductions, + ...(f1040.info.itemizedDeductions ?? {}) + } + } + + isNeeded = (): boolean => { + if (this.f1040.info.itemizedDeductions !== undefined) { + const standardDeduction = this.f1040.standardDeduction() + const itemizedAmount = this.deductions() + return ( + standardDeduction === undefined || itemizedAmount > standardDeduction + ) + } + return false + } + + deductions(): number { + return ( + this.l4() + this.l7() + this.l10() + this.l14() + this.l15() + this.l16() + ) + } + + l1 = (): number => Number(this.itemizedDeductions.medicalAndDental) + + l2 = (): number => this.f1040.l11() + + l3 = (): number => this.l2() * 0.075 + + l4 = (): number => Math.max(0, this.l1() - this.l3()) + + l5aSalesTax = (): boolean => this.itemizedDeductions.isSalesTax + + l5a = (): number => Number(this.itemizedDeductions.stateAndLocalTaxes) + l5b = (): number => + Number(this.itemizedDeductions.stateAndLocalRealEstateTaxes) + l5c = (): number => Number(this.itemizedDeductions.stateAndLocalPropertyTaxes) + l5d = (): number => this.l5a() + this.l5b() + this.l5c() + l5e = (): number => { + const max = + this.f1040.info.taxPayer.filingStatus === FilingStatus.MFS ? 5000 : 10000 + return Math.min(max, this.l5d()) + } + + // TODO + l6OtherTaxesTypeAndAmount1 = (): string | undefined => undefined + l6OtherTaxesTypeAndAmount2 = (): string | undefined => undefined + + // TODO + l6 = (): number | undefined => undefined + + l7 = (): number => this.l5e() + (this.l6() ?? 0) + + // TODO + l8AllMortgageLoan = (): boolean => false + l8a = (): number => Number(this.itemizedDeductions.interest8a) + + // TODO + l8bUnreportedInterest1 = (): string | undefined => undefined + // TODO + l8bUnreportedInterest2 = (): string | undefined => undefined + l8b = (): number => Number(this.itemizedDeductions.interest8b) + l8c = (): number => Number(this.itemizedDeductions.interest8c) + l8d = (): number | undefined => undefined // Reserved for future use + l8e = (): number => this.l8a() + this.l8b() + this.l8c() + + // Used in Form 8960 + l9 = (): number | undefined => + Number(this.itemizedDeductions.investmentInterest) + + l10 = (): number => this.l8e() + (this.l9() ?? 0) + + l11 = (): number => Number(this.itemizedDeductions.charityCashCheck) + + // TODO: form 8283 + l12 = (): number => Number(this.itemizedDeductions.charityOther) + l13 = (): number => 0 + l14 = (): number => this.l11() + this.l12() + this.l13() + + l15 = (): number => 0 + + // TODO + l16Other1 = (): string | undefined => undefined + l16Other2 = (): string | undefined => undefined + l16Other3 = (): string | undefined => undefined + l16 = (): number => 0 + + l17 = (): number => + this.l4() + this.l7() + this.l10() + this.l14() + this.l15() + this.l16() + + l18 = (): boolean => false + + fields = (): Field[] => [ + this.f1040.namesString(), + this.f1040.info.taxPayer.primaryPerson.ssid, + this.l1(), + this.l2(), + this.l3(), + this.l4(), + this.l5aSalesTax(), + this.l5a(), + this.l5b(), + this.l5c(), + this.l5d(), + this.l5e(), + this.l6OtherTaxesTypeAndAmount1(), + this.l6OtherTaxesTypeAndAmount2(), + this.l6(), + this.l7(), + this.l8AllMortgageLoan(), + this.l8a(), + this.l8bUnreportedInterest1(), + this.l8bUnreportedInterest2(), + this.l8b(), + this.l8c(), + this.l8d(), // Reserved for future use + this.l8e(), + this.l9(), + this.l10(), + this.l11(), + this.l12(), + this.l13(), + this.l14(), + this.l15(), + this.l16Other1(), + this.l16Other2(), + this.l16Other3(), + this.l16(), + this.l17(), + this.l18() + ] +} diff --git a/src/forms/Y2023/irsForms/ScheduleB.ts b/src/forms/Y2023/irsForms/ScheduleB.ts new file mode 100644 index 000000000..f5f5c6ea3 --- /dev/null +++ b/src/forms/Y2023/irsForms/ScheduleB.ts @@ -0,0 +1,148 @@ +import F1040Attachment from './F1040Attachment' +import { FormTag } from 'ustaxes/core/irsForms/Form' +import { sumFields } from 'ustaxes/core/irsForms/util' +import { Field } from 'ustaxes/core/pdfFiller' +import F1040 from './F1040' + +interface PayerAmount { + payer?: string + amount?: number +} + +export default class ScheduleB extends F1040Attachment { + tag: FormTag = 'f1040sb' + sequenceIndex = 8 + readonly interestPayersLimit = 14 + readonly dividendPayersLimit = 15 + + index = 0 + + constructor(f1040: F1040, index = 0) { + super(f1040) + this.index = index + } + + copies = (): ScheduleB[] => { + if (this.index === 0) { + const numInterestPayers = this.l1Fields().length + const numDivPayers = this.l5Fields().length + + const extraCopiesNeeded = Math.floor( + Math.max( + numInterestPayers / this.interestPayersLimit, + numDivPayers / this.dividendPayersLimit + ) + ) + + return Array.from(Array(extraCopiesNeeded)).map( + (_, i) => new ScheduleB(this.f1040, i + 1) + ) + } else { + return [] + } + } + + isNeeded = (): boolean => + this.l1Fields().length > 0 || this.l5Fields().length > 0 + + l1Fields = (): PayerAmount[] => + this.f1040 + .f1099Ints() + .map((v) => ({ + payer: v.payer, + amount: v.form.income + })) + .concat( + this.f1040.k1sWithInterest().map((v) => ({ + payer: v.partnershipName, + amount: v.interestIncome + })) + ) + + l1 = (): Array => { + const payerValues = this.l1Fields().slice( + this.index * this.interestPayersLimit, + (this.index + 1) * this.interestPayersLimit + ) + const rightPad = 2 * (this.interestPayersLimit - payerValues.length) + // ensure we return an array of length interestPayersLimit * 2. + // This form may have multiple copies, only display the copies for this form + return payerValues + .flatMap(({ payer, amount }) => [payer, amount?.toString()]) + .concat(Array(rightPad).fill(undefined)) + } + + l2 = (): number => sumFields(this.f1040.f1099Ints().map((f) => f.form.income)) + + // TODO: Interest from tax exempt savings bonds + l3 = (): number | undefined => undefined + + l4 = (): number => this.l2() - (this.l3() ?? 0) + + /** + * Total interest on all schedule Bs. + */ + to1040l2b = (): number => + [this, ...this.copies()].reduce((acc, f) => acc + f.l4(), 0) + + l5Fields = (): PayerAmount[] => + this.f1040.f1099Divs().map((v) => ({ + payer: v.payer, + amount: v.form.dividends + })) + + l5 = (): Array => { + const payerValues = this.l5Fields().slice( + this.index * this.dividendPayersLimit, + (this.index + 1) * this.dividendPayersLimit + ) + + const rightPad = 2 * (this.dividendPayersLimit - payerValues.length) + return payerValues + .flatMap(({ payer, amount }) => [payer, amount]) + .concat(Array(rightPad).fill(undefined)) + } + + l6 = (): number => sumFields(this.l5Fields().map(({ amount }) => amount)) + + /** + * Total dividends on all schedule Bs. + */ + to1040l3b = (): number => + [this, ...this.copies()].reduce((acc, f) => acc + f.l6(), 0) + + foreignAccount = (): boolean => + this.f1040.info.questions.FOREIGN_ACCOUNT_EXISTS ?? false + fincenForm = (): boolean => this.f1040.info.questions.FINCEN_114 ?? false + fincenCountry = (): string | undefined => + this.f1040.info.questions.FINCEN_114_ACCOUNT_COUNTRY + foreignTrust = (): boolean => + this.f1040.info.questions.FOREIGN_TRUST_RELATIONSHIP ?? false + + l7a = (): [boolean, boolean] => [ + this.foreignAccount(), + !this.foreignAccount() + ] + + l7a2 = (): [boolean, boolean] => [this.fincenForm(), !this.fincenForm()] + + l7b = (): string | undefined => this.fincenCountry() + + l8 = (): [boolean, boolean] => [this.foreignTrust(), !this.foreignTrust()] + + fields = (): Field[] => [ + this.f1040.namesString(), + this.f1040.info.taxPayer.primaryPerson.ssid, + ...this.l1(), + this.l2(), + this.l3(), + this.l4(), + ...this.l5(), + this.l6(), + ...this.l7a(), + ...this.l7a2(), + this.l7b(), + undefined, // there are two separate fields for line 7b. + ...this.l8() + ] +} diff --git a/src/forms/Y2023/irsForms/ScheduleC.ts b/src/forms/Y2023/irsForms/ScheduleC.ts new file mode 100644 index 000000000..d659565de --- /dev/null +++ b/src/forms/Y2023/irsForms/ScheduleC.ts @@ -0,0 +1,21 @@ +import F1040Attachment from './F1040Attachment' +import { Field } from 'ustaxes/core/pdfFiller' +import { FormTag } from 'ustaxes/core/irsForms/Form' + +/** + * Not implemented + */ +export default class ScheduleC extends F1040Attachment { + tag: FormTag = 'f1040sc' + sequenceIndex = 9 + + // TODO: statutory employee income + // shown on Schedule 8812, earned income + l1 = (): number | undefined => undefined + + // TODO: net profit or loss + // shown on Schedule 8812, earned income + l31 = (): number | undefined => undefined + + fields = (): Field[] => [] +} diff --git a/src/forms/Y2023/irsForms/ScheduleD.ts b/src/forms/Y2023/irsForms/ScheduleD.ts new file mode 100644 index 000000000..6b72c5f10 --- /dev/null +++ b/src/forms/Y2023/irsForms/ScheduleD.ts @@ -0,0 +1,314 @@ +import { F1099BData, FilingStatus } from 'ustaxes/core/data' +import { FormTag } from 'ustaxes/core/irsForms/Form' +import { sumFields } from 'ustaxes/core/irsForms/util' +import SDRateGainWorksheet from './worksheets/SDRateGainWorksheet' +import SDUnrecaptured1250 from './worksheets/SDUnrecaptured1250' +import F8949 from './F8949' +import F1040Attachment from './F1040Attachment' +import F1040 from './F1040' +import { Field } from 'ustaxes/core/pdfFiller' +import SDTaxWorksheet from './worksheets/SDTaxWorksheet' +import QualDivAndCGWorksheet from './worksheets/SDQualifiedAndCapGains' +export default class ScheduleD extends F1040Attachment { + tag: FormTag = 'f1040sd' + sequenceIndex = 12 + _aggregated?: F1099BData + qualifiedDivAndCGWorksheet: QualDivAndCGWorksheet + taxWorksheet: SDTaxWorksheet + rateGainWorksheet: SDRateGainWorksheet + unrecaptured1250: SDUnrecaptured1250 + _f8949s?: F8949[] + + readonly l21MinMFS = 1500 + readonly l21MinDefault = 3000 + + constructor(f1040: F1040) { + super(f1040) + + this.rateGainWorksheet = new SDRateGainWorksheet() + this.taxWorksheet = new SDTaxWorksheet(f1040) + this.qualifiedDivAndCGWorksheet = new QualDivAndCGWorksheet(f1040) + this.unrecaptured1250 = new SDUnrecaptured1250() + } + + get aggregated(): F1099BData { + if (this._aggregated === undefined) { + const bs: F1099BData[] = this.f1040.f1099Bs().map((f) => f.form) + + this._aggregated = { + shortTermProceeds: bs.reduce((l, r) => l + r.shortTermProceeds, 0), + shortTermCostBasis: bs.reduce((l, r) => l + r.shortTermCostBasis, 0), + longTermProceeds: bs.reduce((l, r) => l + r.longTermProceeds, 0), + longTermCostBasis: bs.reduce((l, r) => l + r.longTermCostBasis, 0) + } + } + + return this._aggregated + } + + isNeeded = (): boolean => + this.f1040.f1099Bs().length > 0 || this.f1040.f8949.isNeeded() + + l21Min = (): number => { + if (this.f1040.info.taxPayer.filingStatus === FilingStatus.MFS) { + return this.l21MinMFS + } + return this.l21MinDefault + } + + l1ad = (): number | undefined => this.aggregated.shortTermProceeds + l1ae = (): number | undefined => this.aggregated.shortTermCostBasis + // This field is greyed out, but fillable + l1ag = (): number | undefined => undefined + l1ah = (): number => sumFields([this.l1ad(), 0 - (this.l1ae() ?? 0)]) + + l1f8949s = (): F8949[] => this.f1040.f8949s.filter((f) => f.part1BoxA()) + + l1bd = (): number => + sumFields(this.l1f8949s().map((f) => f.shortTermTotalProceeds())) + l1be = (): number => + sumFields(this.l1f8949s().map((f) => f.shortTermTotalCost())) + + l1bg = (): number => + sumFields(this.l1f8949s().map((f) => f.shortTermTotalAdjustments())) + l1bh = (): number => + sumFields(this.l1f8949s().map((f) => f.shortTermTotalGain())) + + l2f8949s = (): F8949[] => this.f1040.f8949s.filter((f) => f.part1BoxB()) + + l2d = (): number => + sumFields(this.l2f8949s().map((f) => f.shortTermTotalProceeds())) + + l2e = (): number => + sumFields(this.l2f8949s().map((f) => f.shortTermTotalCost())) + + l2g = (): number => + sumFields(this.l2f8949s().map((f) => f.shortTermTotalAdjustments())) + + l2h = (): number => + sumFields(this.l2f8949s().map((f) => f.shortTermTotalGain())) + + l3f8949s = (): F8949[] => this.f1040.f8949s.filter((f) => f.part1BoxC()) + + l3d = (): number => + sumFields(this.l3f8949s().map((f) => f.shortTermTotalProceeds())) + + l3e = (): number => + sumFields(this.l3f8949s().map((f) => f.shortTermTotalCost())) + + l3g = (): number => + sumFields(this.l3f8949s().map((f) => f.shortTermTotalAdjustments())) + + l3h = (): number => + sumFields(this.l3f8949s().map((f) => f.shortTermTotalGain())) + + l4 = (): number | undefined => undefined + + l5 = (): number | undefined => undefined + + l6 = (): number | undefined => undefined + + l7 = (): number => + sumFields([ + this.l1ah(), + this.l1bh(), + this.l2h(), + this.l3h(), + this.l4(), + this.l5(), + this.l6() + ]) + + l8ad = (): number | undefined => this.aggregated.longTermProceeds + l8ae = (): number | undefined => this.aggregated.longTermCostBasis + // This field is greyed out, but fillable + l8ag = (): number | undefined => undefined + l8ah = (): number | undefined => + sumFields([this.l8ad(), 0 - (this.l8ae() ?? 0)]) + + l8f8949s = (): F8949[] => this.f1040.f8949s.filter((f) => f.part2BoxD()) + + l8bd = (): number => + sumFields(this.l8f8949s().map((f) => f.longTermTotalProceeds())) + + l8be = (): number => + sumFields(this.l8f8949s().map((f) => f.longTermTotalCost())) + + l8bg = (): number => + sumFields(this.l8f8949s().map((f) => f.longTermTotalAdjustments())) + + l8bh = (): number => + sumFields(this.l8f8949s().map((f) => f.longTermTotalGain())) + + l9f8949s = (): F8949[] => this.f1040.f8949s.filter((f) => f.part2BoxE()) + + l9d = (): number => + sumFields(this.l9f8949s().map((f) => f.longTermTotalProceeds())) + + l9e = (): number => + sumFields(this.l9f8949s().map((f) => f.longTermTotalCost())) + + l9g = (): number => + sumFields(this.l9f8949s().map((f) => f.longTermTotalAdjustments())) + + l9h = (): number => + sumFields(this.l9f8949s().map((f) => f.longTermTotalGain())) + + l10f8949s = (): F8949[] => this.f1040.f8949s.filter((f) => f.part2BoxF()) + + l10d = (): number => + sumFields(this.l10f8949s().map((f) => f.longTermTotalProceeds())) + + l10e = (): number => + sumFields(this.l10f8949s().map((f) => f.longTermTotalCost())) + + l10g = (): number => + sumFields(this.l10f8949s().map((f) => f.longTermTotalAdjustments())) + + l10h = (): number => + sumFields(this.l10f8949s().map((f) => f.longTermTotalGain())) + + l11 = (): number | undefined => undefined + + l12 = (): number | undefined => undefined + + l13 = (): number | undefined => + this.f1040 + .f1099Divs() + .reduce((s, f) => s + f.form.totalCapitalGainsDistributions, 0) + + l14 = (): number | undefined => undefined + + l15 = (): number => + sumFields([ + this.l8ah(), + this.l8bh(), + this.l9h(), + this.l10h(), + this.l11(), + this.l12(), + this.l13(), + this.l14() + ]) + + // L7 + L15 + // If +, enter on L16 of F1040 + // If -, go to L21 + // If 0, go to L22 + l16 = (): number => sumFields([this.l7(), this.l15()]) + + // Are L15 and L16 both gains? + l17 = (): boolean => this.l15() > 0 && this.l16() > 0 + + l18 = (): number | undefined => { + if (!this.l17()) { + return undefined + } + return this.rateGainWorksheet.l7() + } + + l19 = (): number | undefined => { + if (!this.l17()) { + return undefined + } + return this.unrecaptured1250.l18() + } + + l20 = (): boolean | undefined => { + if (!this.l17()) { + return undefined + } + return (this.l18() ?? 0) === 0 && (this.l19() ?? 0) === 0 + } + + fillL21 = (): boolean => + !this.l20() && ((this.l16() > 0 && this.l17()) || this.l16() < 0) + + l21 = (): number | undefined => { + if (this.fillL21()) { + return Math.max(-this.l21Min(), this.l16()) + } + } + + haveQualifiedDividends = (): boolean => + this.f1040.f1099Divs().some((f) => f.form.qualifiedDividends > 0) + + // TODO: Schedule D tax worksheet + // neither box should be checked if this question was not required to be answered by l20. + l22 = (): boolean | undefined => { + if (this.l20() !== false) { + return this.haveQualifiedDividends() + } + } + + lossCarryForward = (): number => { + const amount = this.l16() + this.l21Min() + if (amount < 0) { + return -amount + } + return 0 + } + + to1040 = (): number => this.l21() ?? this.l16() + + computeTaxOnQDWorksheet = (): boolean => + (this.l20() ?? false) || (this.l22() ?? false) + + fields = (): Field[] => [ + this.f1040.namesString(), + this.f1040.info.taxPayer.primaryPerson.ssid, + false, + false, + this.l1ad(), + this.l1ae(), + this.l1ag(), + this.l1ah(), + this.l1bd(), + this.l1be(), + this.l1bg(), + this.l1bh(), + this.l2d(), + this.l2e(), + this.l2g(), + this.l2h(), + this.l3d(), + this.l3e(), + this.l3g(), + this.l3h(), + this.l4(), + this.l5(), + this.l6(), + this.l7(), + this.l8ad(), + this.l8ae(), + this.l8ag(), + this.l8ah(), + this.l8bd(), + this.l8be(), + this.l8bg(), + this.l8bh(), + this.l9d(), + this.l9e(), + this.l9g(), + this.l9h(), + this.l10d(), + this.l10e(), + this.l10g(), + this.l10h(), + this.l11(), + this.l12(), + this.l13(), + this.l14(), + this.l15(), + this.l16(), + this.l17(), + !this.l17(), + this.l18(), + this.l19(), + this.l20() === true, + this.l20() === false, + this.l21(), + this.l22() === true, + this.l22() === false + ] +} diff --git a/src/forms/Y2023/irsForms/ScheduleE.ts b/src/forms/Y2023/irsForms/ScheduleE.ts new file mode 100644 index 000000000..2ef923739 --- /dev/null +++ b/src/forms/Y2023/irsForms/ScheduleE.ts @@ -0,0 +1,336 @@ +import { + Address, + Property, + PropertyType, + PropertyExpenseTypeName +} from 'ustaxes/core/data' +import { displayNegPos, sumFields } from 'ustaxes/core/irsForms/util' +import _ from 'lodash' +import F1040Attachment from './F1040Attachment' +import { FormTag } from 'ustaxes/core/irsForms/Form' +import { Field } from 'ustaxes/core/pdfFiller' + +type Cell = number | undefined +export type MatrixRow = [Cell, Cell, Cell] + +const fill = (values: number[]): MatrixRow => { + const realValues = (values as Cell[]).slice(0, 3).map((v) => { + if (v === 0) { + return undefined + } + return v + }) + return [ + ...realValues, + ...Array.from(Array(3 - realValues.length)).map(() => undefined) + ] as MatrixRow +} + +const propTypeIndex = { + [PropertyType.singleFamily]: 1, + [PropertyType.multiFamily]: 2, + [PropertyType.vacation]: 3, + [PropertyType.commercial]: 4, + [PropertyType.land]: 5, + [PropertyType.selfRental]: 7, + [PropertyType.other]: 8 +} + +export default class ScheduleE extends F1040Attachment { + tag: FormTag = 'f1040se' + sequenceIndex = 13 + + isNeeded = (): boolean => + this.f1040.info.realEstate.length > 0 || + this.f1040.info.scheduleK1Form1065s.length > 0 + + addressString = (address: Address): string => + [ + address.address, + address.city, + address.state ?? address.province ?? '', + address.zip ?? address.postalCode ?? '' + ].join(', ') + + propForRow = (row: number): Property | undefined => { + if (row < this.f1040.info.realEstate.length) { + return this.f1040.info.realEstate[row] + } + } + + /** + * Whether or not you can deduct expenses for the unit depends on whether or not you used + * the unit as a home in 2023. You used the unit as a home if your personal use of the unit + * was more than the greater of: + * 14 days, or + * 10% of the total days it was rented to others at a fair rental price. + * @param p + */ + propertyUseTest = (p: Property): boolean => + p.personalUseDays <= Math.max(14, 0.1 * p.rentalDays) + + l3 = (): MatrixRow => { + const properties = this.f1040.info.realEstate + return fill(properties.map((a) => a.rentReceived)) + } + + // TODO: not implemented + l4 = (): MatrixRow => { + return [undefined, undefined, undefined] + } + + getExpensesRow = (expType: PropertyExpenseTypeName): MatrixRow => + fill( + this.f1040.info.realEstate.map((p) => { + if (this.propertyUseTest(p)) { + return p.expenses[expType] ?? 0 + } + return 0 + }) + ) + + // Matching order of expenses in rows of form + expenses: PropertyExpenseTypeName[] = [ + 'advertising', + 'auto', + 'cleaning', + 'commissions', + 'insurance', + 'legal', + 'management', + 'mortgage', + 'otherInterest', + 'repairs', + 'supplies', + 'taxes', + 'utilities', + 'depreciation' + ] + + l19 = (): [string | undefined, MatrixRow] => { + const expenseRow = this.getExpensesRow('other') + const otherText = this.f1040.info.realEstate + .flatMap((p) => + p.otherExpenseType !== undefined ? [p.otherExpenseType] : [] + ) + .join(',') + return [otherText, expenseRow] + } + + allExpenses = (): MatrixRow[] => + this.expenses.map((e) => this.getExpensesRow(e)) + + l12 = (): MatrixRow => this.getExpensesRow('mortgage') + l18 = (): MatrixRow => this.getExpensesRow('depreciation') + + // TODO - required from pub 596 worksheet 1 + royaltyExpenses = (): number | undefined => undefined + + l20 = (): MatrixRow => + fill(_.unzip(this.allExpenses()).map((column) => sumFields(column))) + + l21 = (): MatrixRow => + _.zipWith( + this.l3(), + this.l4(), + this.l20(), + (x, y, z) => (x ?? 0) + (y ?? 0) - (z ?? 0) + ) as MatrixRow + + // Deductible real estate loss from 8582, as positive number + l22 = (): MatrixRow => + this.f1040.f8582?.deductibleRealEstateLossAfterLimitation() ?? [ + undefined, + undefined, + undefined + ] + + l23a = (): number => sumFields(this.l3()) + l23b = (): number => sumFields(this.l4()) + l23c = (): number => sumFields(this.l12()) + l23d = (): number => sumFields(this.l18()) + l23e = (): number => sumFields(this.l20()) + + rentalNet = (): MatrixRow => + _.zipWith(this.l3(), this.l20(), (x, y) => (x ?? 0) - (y ?? 0)) as MatrixRow + + l24 = (): number => + sumFields(this.l21().filter((x) => x !== undefined && x > 0)) + + // TODO: Royalty losses + l25 = (): number => sumFields(this.l22()) + + l26 = (): number => sumFields([this.l24(), this.l25()]) + + // TODO: required from Pub 596 + l29ah = (): number | undefined => + this.f1040.info.scheduleK1Form1065s.reduce( + (t, k1) => t + Math.max(0, k1.isPassive ? k1.ordinaryBusinessIncome : 0), + 0 + ) + l29ak = (): number | undefined => + this.f1040.info.scheduleK1Form1065s.reduce( + (t, k1) => t + Math.max(0, k1.isPassive ? 0 : k1.ordinaryBusinessIncome), + 0 + ) + + l29bg = (): number | undefined => + this.f1040.info.scheduleK1Form1065s.reduce( + (t, k1) => t + Math.min(0, k1.isPassive ? k1.ordinaryBusinessIncome : 0), + 0 + ) + l29bi = (): number | undefined => + this.f1040.info.scheduleK1Form1065s.reduce( + (t, k1) => t + Math.min(0, k1.isPassive ? 0 : k1.ordinaryBusinessIncome), + 0 + ) + l29bj = (): number | undefined => undefined + + l30 = (): number | undefined => sumFields([this.l29ah(), this.l29ak()]) + l31 = (): number | undefined => + sumFields([this.l29bg(), this.l29bi(), this.l29bj()]) + l32 = (): number | undefined => sumFields([this.l30(), this.l31()]) + + l34ad = (): number | undefined => undefined + l34af = (): number | undefined => undefined + l34bc = (): number | undefined => undefined + l34be = (): number | undefined => undefined + + // TODO: Real estate trust income or loss + l37 = (): number | undefined => undefined + + // TODO: REMICS income or loss + l39 = (): number | undefined => undefined + + // TODO: Farm rental income or loss + l40 = (): number | undefined => undefined + + l41 = (): number => + sumFields([this.l26(), this.l32(), this.l37(), this.l39(), this.l40()]) + + fields = (): Field[] => { + const [p0, p1, p2] = [0, 1, 2].map((i) => this.propForRow(i)) + + // TODO: Support more than 4 K1s + const k1s = this.f1040.info.scheduleK1Form1065s + const l28Fields: Field[] = [] + l28Fields.push( + ...k1s + .slice(0, 4) + .flatMap((k1) => [ + k1.partnershipName, + k1.partnerOrSCorp, + k1.isForeign, + k1.partnershipEin, + false, + false + ]) + ) + l28Fields.push( + ...Array(6 * Math.max(0, 4 - k1s.length)).fill(undefined) + ) + l28Fields.push( + ...k1s.slice(0, 4).flatMap((k1) => { + if (k1.isPassive) { + if (k1.ordinaryBusinessIncome < 0) { + return [k1.ordinaryBusinessIncome, 0, 0, 0, 0] + } else { + return [0, k1.ordinaryBusinessIncome, 0, 0, 0] + } + } else { + if (k1.ordinaryBusinessIncome < 0) { + return [0, 0, k1.ordinaryBusinessIncome, 0, 0] + } else { + return [0, 0, 0, 0, k1.ordinaryBusinessIncome] + } + } + }) + ) + l28Fields.push( + ...Array(5 * Math.max(0, 4 - k1s.length)).fill(undefined) + ) + + return [ + this.f1040.namesString(), + this.f1040.info.taxPayer.primaryPerson.ssid, + false, + false, + false, + false, + ...[p0, p1, p2].map((p) => + p === undefined ? undefined : this.addressString(p.address) + ), + p0 === undefined + ? undefined + : propTypeIndex[PropertyType[p0.propertyType]], + p1 === undefined + ? undefined + : propTypeIndex[PropertyType[p1.propertyType]], + p2 === undefined + ? undefined + : propTypeIndex[PropertyType[p2.propertyType]], + p0?.rentalDays, + p0?.personalUseDays, + p0?.qualifiedJointVenture, + p1?.rentalDays, + p1?.personalUseDays, + p1?.qualifiedJointVenture, + p2?.rentalDays, + p2?.personalUseDays, + p2?.qualifiedJointVenture, + [p0, p1, p2].find((p) => p?.propertyType === 'other') + ?.otherPropertyType ?? undefined, + ...this.l3(), + ...this.l4(), + ...this.allExpenses().flat(), + ...this.l19().flat(), + ...this.l20(), + ...this.l21(), + ...this.l22(), + this.l23a(), + this.l23b(), + this.l23c(), + this.l23d(), + this.l23e(), + this.l24(), + Math.abs(this.l25()), + displayNegPos(this.l26()), + // Page 2 - TODO: Only part II implemented + this.f1040.namesString(), + this.f1040.info.taxPayer.primaryPerson.ssid, + ...[false, false], // l27 + ...l28Fields, + undefined, // grey + this.l29ah(), + undefined, // grey + undefined, // grey + this.l29ak(), + this.l29bg(), + undefined, // grey + this.l29bi(), + this.l29bj(), + undefined, // grey + this.l30(), + this.l31(), + this.l32(), // l32 + ...Array(2 * 6).fill(undefined), // l33 + undefined, + this.l34ad(), + undefined, + this.l34af(), + this.l34bc(), + undefined, // grey + this.l34be(), + undefined, // grey + undefined, // l35 + undefined, // l36 + this.l37(), // l37 + ...Array(5).fill(undefined), // l38 + this.l39(), // l39 + this.l40(), // l40 + this.l41(), // l41 + undefined, + undefined + ] + } +} diff --git a/src/forms/Y2023/irsForms/ScheduleEIC.ts b/src/forms/Y2023/irsForms/ScheduleEIC.ts new file mode 100644 index 000000000..a54c16be6 --- /dev/null +++ b/src/forms/Y2023/irsForms/ScheduleEIC.ts @@ -0,0 +1,388 @@ +import F1040Attachment from './F1040Attachment' +import { Dependent, FilingStatus } from 'ustaxes/core/data' +import F1040 from './F1040' +import { sumFields } from 'ustaxes/core/irsForms/util' +import * as federal from '../data/federal' +import F2555 from './F2555' +import F4797 from './F4797' +import F8814 from './F8814' +import Pub596Worksheet1 from './worksheets/Pub596Worksheet1' +import { FormTag } from 'ustaxes/core/irsForms/Form' +import { evaluatePiecewise, Piecewise } from 'ustaxes/core/util' +import _ from 'lodash' +import { Field } from 'ustaxes/core/pdfFiller' + +type PrecludesEIC = (f: F) => boolean + +// TODO: check F2555 +const checks2555: PrecludesEIC = (): boolean => false + +// TODO: check F4797 +const checks4797: PrecludesEIC = (): boolean => false + +// TODO: check F8814 +const checks8814: PrecludesEIC = (): boolean => false + +const checksPub596: PrecludesEIC = (f): boolean => + f.precludesEIC() + +const precludesEIC = + (p: PrecludesEIC) => + (f: F | undefined): boolean => { + if (f === undefined) { + return false + } + return p(f) + } + +export default class ScheduleEIC extends F1040Attachment { + tag: FormTag = 'f1040sei' + sequenceIndex = 43 + pub596Worksheet1: Pub596Worksheet1 + qualifyingStudentCutoffYear = 1999 + qualifyingCutoffYear = 2004 + investmentIncomeLimit = 3650 + + constructor(f1040: F1040) { + super(f1040) + this.pub596Worksheet1 = new Pub596Worksheet1(f1040) + } + + isNeeded = (): boolean => this.allowed() + + // instructions step 1.1 + passIncomeLimit = (): boolean => { + const filingStatus = this.f1040.info.taxPayer.filingStatus + const incomeLimits = federal.EIC.caps[filingStatus] + if (incomeLimits !== undefined) { + const limit = + incomeLimits[ + Math.min(this.qualifyingDependents().length, incomeLimits.length - 1) + ] + return this.f1040.l11() < limit + } + return false + } + + // Step 1.2, todo, both spouses must have a SSN issued before 2020 due date + // + // TODO: ('Step 1.2 (valid SSNs) unchecked') and without work restriction and valid for eic purpos + validSSNs = (): boolean => { + return true + } + + // Step 1.3 + allowedFilingStatus = (): boolean => + this.f1040.info.taxPayer.filingStatus !== FilingStatus.MFS + + // Step 1.4 + allowedFilling2555 = (): boolean => + !precludesEIC(checks2555)(this.f1040.f2555) + + // + // TODO: ('Step 1.5, Not checking non-resident alien') Step 1.5 nonResidentAli + allowedNonresidentAlien = (): boolean => { + return true + } + + // step 2, question 1 + investmentIncome = (): number => + sumFields([ + this.f1040.l2a(), + this.f1040.l2b(), + this.f1040.l3b(), + Math.max(this.f1040.l7() ?? 0, 0) + ]) + + passInvestmentIncomeLimit = (): boolean => + this.investmentIncome() < federal.EIC.maxInvestmentIncome + + // Todo, step 2, question 3 + f4797AllowsEIC = (): boolean => !precludesEIC(checks4797)(this.f1040.f4797) + + // Todo, instruction 2.4.1 + filingScheduleE = (): boolean => this.f1040.scheduleE.isNeeded() + + // + // TODO: ('Not checking personal property income') 2.4 + passIncomeFromPersonalProperty = (): boolean => { + return true + } + + // 2.4.3 + passForm8814 = (): boolean => !precludesEIC(checks8814)(this.f1040.f8814) + + // + // TODO: ('Not checking passive activity') 2.4 + incomeOrLossFromPassiveActivity = (): boolean => { + return false + } + + // 2.4.5 + passPub596 = (): boolean => !precludesEIC(checksPub596)(this.pub596Worksheet1) + + // 3.1 + atLeastOneChild = (): boolean => this.qualifyingDependents().length > 0 + + // 3.2, 4.4 + jointReturn = (): boolean => + this.f1040.info.taxPayer.filingStatus === FilingStatus.MFJ + + // + // TODO: ('3.3: Not checking qualifying child of another') 3.3, 4 + qualifyingChildOfAnother = (): boolean => { + return false + } + + // 4.1 - covered by income limit check + // + // TODO: ('4.2: Not checking taxpayer age') 4 + over25Under65 = (): boolean => { + return true + } + + // + // TODO: ('4.3: Not checking residency') 4 + mainHomeInsideUsBothPeople = (): boolean => { + return true + } + + // 4.4 covered above + // 4.5 covered above + // 4.6 dependent of another + dependentOfAnother = (): boolean => + this.f1040.info.taxPayer.primaryPerson.isTaxpayerDependent || + (this.f1040.info.taxPayer.spouse?.isTaxpayerDependent ?? false) + + // + // TODO: ('5.1: Not checking church self-employment income') 5.1 - Filing schedule SE for chur + filingSEChurchIncome = (): boolean => { + return false + } + + // + // TODO: ('5.1.2: Not checking scholarship, grants') 5.1 + taxableScholarshipIncome = (): number => { + return 0 + } + + // + // TODO: ('5.1.3: Not checking prison income') 5.1 + prisonIncome = (): number => { + return 0 + } + + // + // TODO: ('5.1.4: Not checking pension income') 5.1 + pensionPlanIncome = (): number => { + return 0 + } + + // + // TODO: ('5.1.5: Not checking medicaid waiver') 5.1 + medicaidWaiverPayment = (): number => { + return 0 + } + + // + // TODO: ('5.1.8: Not checking nontaxable combat pay') 5.1 + nontaxableCombatPay = (): number => { + return 0 + } + + // 5.1 - Earned income + earnedIncome = (): number => { + const l1 = this.f1040.l1z() + const l2 = this.taxableScholarshipIncome() + const l3 = this.prisonIncome() + const l4 = this.pensionPlanIncome() + const l5 = this.medicaidWaiverPayment() + const l6 = l2 + l3 + l4 + l5 + const l7 = l1 - l6 + const l8 = this.nontaxableCombatPay() + const l9 = l7 + l8 + return l9 + } + + /** + * The credit table in Publication 596 provides an + * amount for each interval of $50, calculated from the + * midpoint of the interval. + * + * @param income The earned income + * @returns the earned income rounded to the nearest 25 + */ + roundIncome = (income: number): number => { + if (income < 1) { + return 0 + } + return Math.round(Math.round(income) / 50) * 50 + 25 + } + + /** + * Based on the earned income and filing status, calculate the + * allowed EITC. + * + * For tax year 2020, IRS Rev. Proc. 2019-44 outlines the required + * calculation for the EITC based on number of qualifying children + * and filing status. + * + * https://www.irs.gov/pub/irs-drop/rp-19-44.pdf + * + * IRS publication 596 provides a table that can be used + * to figure the EITC, and is the basis of online calculators published + * by IRS. This table uses the formulas outlined in Rev Proc 2019-44 + * but applies them to incomes lying in $50 intervals, with the midpoint + * of those intervals used to calculate the credit for the entire window. + * For example, if the taxpayer has an earned income of $5000, the amount + * that is found in the table is calculated based on an income of $5025 and + * comes out ahead. Conversely, someone with an earned income of $5049 finds + * a credit in the table calculated off the same $5,025 and loses out. + * + * https://www.irs.gov/pub/irs-pdf/p596.pdf + * + * @param income The earned income + * @returns + */ + calculateEICForIncome = (income: number): number => { + const filingStatus = this.f1040.info.taxPayer.filingStatus + const f: Piecewise[] | undefined = federal.EIC.formulas[filingStatus] + if (f === undefined) { + return 0 + } + + return Math.max( + 0, + evaluatePiecewise( + f[this.qualifyingDependents().length], + this.roundIncome(income) + ) + ) + } + + // + // TODO: ('5.2: Not checking selfemployment') 5 + selfEmployed = (): boolean => { + return false + } + + // 5.3 - covered by income check + + // 6.1 - We will figure the credit. + + // EIC worksheet A calculation + credit = (): number => + Math.min( + this.calculateEICForIncome(this.earnedIncome()), + this.calculateEICForIncome(this.f1040.l11()) + ) + + allowed = (): boolean => { + return ( + // Step 1 + this.passIncomeLimit() && + this.validSSNs() && + this.allowedFilingStatus() && + this.allowedFilling2555() && + this.allowedNonresidentAlien() && + // Step 2 + (this.passInvestmentIncomeLimit() || this.f4797AllowsEIC()) && + (!( + // Step 3 + ( + this.filingScheduleE() || + !this.passIncomeFromPersonalProperty() || + !this.passForm8814() || + this.incomeOrLossFromPassiveActivity() + ) + ) || + this.passPub596()) && + !( + // Step 4 + ( + this.f1040.info.taxPayer.filingStatus !== FilingStatus.MFJ && + this.dependentOfAnother() + ) + ) && + this.credit() > 0 + ) + } + + qualifyingDependents = (): Dependent[] => + this.f1040.info.taxPayer.dependents + .filter( + (d) => + d.dateOfBirth.getFullYear() >= this.qualifyingCutoffYear || + ((d.qualifyingInfo?.isStudent ?? false) && + d.dateOfBirth.getFullYear() >= this.qualifyingStudentCutoffYear) + ) + .sort((d) => d.dateOfBirth.getFullYear()) + .slice(0, 3) + + qualifyingDependentsFilled = (): Array => { + const res = this.qualifyingDependents() + return _.fill([...res], undefined, res.length, 3) + } + + // EIC line 1 + nameFields = (): Array => + this.qualifyingDependentsFilled().map( + (d) => `${d?.firstName ?? ''} ${d?.lastName ?? ''}` + ) + + // EIC line 2 + ssnFields = (): Array => + this.qualifyingDependentsFilled().map((d) => d?.ssid) + + years = (): Array => + this.qualifyingDependentsFilled().map((d) => d?.dateOfBirth.getFullYear()) + + // EIC line 3 + birthYearFields = (): Array => + this.years().flatMap((year) => { + if (year !== undefined) { + return String(year).split('') + } + return [undefined, undefined, undefined, undefined] + }) + + // EIC line 4a: Not handling case of child older than taxpayer + ageFields = (): Array => + this.years().flatMap((year) => { + if (year !== undefined) { + const qualifies = year > this.qualifyingStudentCutoffYear + return [qualifies, !qualifies] + } + return [undefined, undefined] + }) + + // TODO: disability + disabledFields = (): Array => + this.years().flatMap((year) => { + if (year === undefined || year < this.qualifyingCutoffYear) { + return [undefined, undefined] + } + return [undefined, undefined] + }) + + // Line 5 + // TODO: Address eic relationships + relationships = (): Array => + this.qualifyingDependentsFilled().map((d) => d?.relationship) + + // Line 6 + numberMonths = (): Array => + this.qualifyingDependents().map((d) => d.qualifyingInfo?.numberOfMonths) + + fields = (): Field[] => [ + this.f1040.namesString(), + this.f1040.info.taxPayer.primaryPerson.ssid, + ...this.nameFields(), // 6 + ...this.ssnFields(), // 3 + ...this.birthYearFields(), // 12 + ...this.ageFields(), // 6 + ...this.disabledFields(), // 6 + ...this.relationships(), + ...this.numberMonths() + ] +} diff --git a/src/forms/Y2023/irsForms/ScheduleR.ts b/src/forms/Y2023/irsForms/ScheduleR.ts new file mode 100644 index 000000000..74eadcc3e --- /dev/null +++ b/src/forms/Y2023/irsForms/ScheduleR.ts @@ -0,0 +1,12 @@ +import F1040Attachment from './F1040Attachment' +import { Field } from 'ustaxes/core/pdfFiller' +import { FormTag } from 'ustaxes/core/irsForms/Form' + +export default class ScheduleR extends F1040Attachment { + tag: FormTag = 'f1040sr' + sequenceIndex = 999 + + l22 = (): number | undefined => undefined + + fields = (): Field[] => [] +} diff --git a/src/forms/Y2023/irsForms/ScheduleSE.ts b/src/forms/Y2023/irsForms/ScheduleSE.ts new file mode 100644 index 000000000..a306a5208 --- /dev/null +++ b/src/forms/Y2023/irsForms/ScheduleSE.ts @@ -0,0 +1,142 @@ +import F1040Attachment from './F1040Attachment' +import { FormTag } from 'ustaxes/core/irsForms/Form' +import { sumFields } from 'ustaxes/core/irsForms/util' +import { Field } from 'ustaxes/core/pdfFiller' +import { fica } from '../data/federal' + +export default class ScheduleSE extends F1040Attachment { + tag: FormTag = 'f1040sse' + sequenceIndex = 14 + + isNeeded = (): boolean => + this.f1040.info.scheduleK1Form1065s + .map( + (k1) => + k1.selfEmploymentEarningsA + + k1.selfEmploymentEarningsB + + k1.selfEmploymentEarningsC + ) + .reduce((a, b) => a + b, 0) > 0 + + postL4Field = (f: () => number | undefined): number | undefined => { + if (this.l4c() < 400) { + return undefined + } + return f() + } + + l8aRelatedField = (f: () => number | undefined): number | undefined => { + return this.postL4Field(() => { + if ((this.l8a() ?? 0) >= fica.maxIncomeSSTaxApplies) { + return undefined + } + return f() + }) + } + + l1a = (): number => { + const schFL34 = 0 // TODO: Net farm profit or (loss) from Schedule F, line 34 + const k1B14 = 0 // TODO: If a farm partnership + return schFL34 + k1B14 + } + + l1b = (): number => 0 + + l2 = (): number => { + const schCL31 = 0 // TODO: Net profit or (loss) from Schedule C, line 31 + const k1B14 = this.f1040.info.scheduleK1Form1065s.reduce( + (c, k1) => c + k1.selfEmploymentEarningsA, + 0 + ) + return schCL31 + k1B14 + } + + l3 = (): number => sumFields([this.l1a(), this.l1b(), this.l2()]) + + // TODO: Handle line 4A being less than 400 due to the Conservation Reserve Program + l4a = (): number => { + const l3 = this.l3() + if (l3 > 0) { + return l3 * 0.9235 + } + return l3 + } + + l4b = (): number | undefined => undefined + + l4c = (): number => sumFields([this.l4a(), this.l4b()]) + + l5a = (): number | undefined => this.postL4Field(() => 0) + l5b = (): number | undefined => + this.postL4Field(() => { + const l5a = this.l5a() + if (l5a === undefined) { + return undefined + } + return l5a * 0.9235 + }) + + l6 = (): number | undefined => + this.postL4Field((): number => sumFields([this.l4c(), this.l5b()])) + + l7 = (): number => fica.maxIncomeSSTaxApplies + + l8a = (): number | undefined => + this.postL4Field((): number => + this.f1040.validW2s().reduce((c, w2) => c + w2.ssWages, 0) + ) + + l8b = (): number | undefined => + this.l8aRelatedField((): number | undefined => undefined) + l8c = (): number | undefined => + this.l8aRelatedField((): number | undefined => undefined) + l8d = (): number | undefined => + this.l8aRelatedField((): number => + sumFields([this.l8a(), this.l8b(), this.l8c()]) + ) + + l9 = (): number | undefined => + this.l8aRelatedField((): number => + Math.max(0, this.l7() - (this.l8d() ?? 0)) + ) + + l10 = (): number | undefined => + this.l8aRelatedField( + (): number => Math.min(this.l6() ?? 0, this.l9() ?? 0) * 0.124 + ) + + l11 = (): number | undefined => + this.postL4Field((): number => (this.l6() ?? 0) * 0.029) + + l12 = (): number | undefined => + this.postL4Field((): number => sumFields([this.l10(), this.l11()])) + + l13 = (): number | undefined => + this.postL4Field((): number => (this.l12() ?? 0) * 0.5) + + fields = (): Field[] => [ + this.f1040.namesString(), + this.f1040.info.taxPayer.primaryPerson.ssid, + false, // Minister + this.l1a(), + this.l1b(), + this.l2(), + this.l3(), + this.l4a(), + this.l4b(), + this.l4c(), + this.l5a(), + this.l5b(), + this.l6(), + this.l7(), + this.l8a(), + this.l8b(), + this.l8c(), + this.l8d(), + this.l9(), + this.l10(), + this.l11(), + this.l12(), + this.l13() + ] +} diff --git a/src/forms/Y2023/irsForms/TaxTable.ts b/src/forms/Y2023/irsForms/TaxTable.ts new file mode 100644 index 000000000..30532d22a --- /dev/null +++ b/src/forms/Y2023/irsForms/TaxTable.ts @@ -0,0 +1,62 @@ +import federalBrackets from '../data/federal' +import { FilingStatus } from 'ustaxes/core/data' +import _ from 'lodash' + +const computeTax = + (brackets: (status: FilingStatus) => number[], rates: number[]) => + (filingStatus: FilingStatus, income: number): number => + _.chain([0, ...brackets(filingStatus)]) // Low end of each bracket + .zipWith( + [...brackets(filingStatus), undefined], // top end of each bracket + rates.map((r) => r / 100), // rate for each bracket + (low, high, rate) => { + if (income < low) { + // this bracket is above income, no tax here + return 0 + } else if (high === undefined) { + // This is the top bracket + return Math.max(0, income - low) * rate + } else if (income >= high) { + // Taxable income is above the top of this bracket + // so add the max tax for this bracket + return (high - low) * rate + } + // Otherwise max income is inside this bracket, + // add the tax on the amount falling in this bracket + + if (income < 5) { + return 0 + } + // If income is between $5 and $25, tax table computes rate at midpoint of $5 ranges + if (income >= 5 && income < 25) { + income = Math.floor(income) + const over5 = income % 5 + income += 2.5 - over5 + } + // If income is between $25 and $3,000, tax table computes rate at midpoint of $25 ranges + else if (income >= 25 && income < 3000) { + income = Math.floor(income) + const over25 = income % 25 + income += 12.5 - over25 + } + // If income is between $3,000 and $100,000, tax table computes rate at midpoint of $50 ranges + else if (income >= 3000 && income < 100000) { + income = Math.floor(income) + const over50 = income % 50 + income += 25 - over50 + } + return (income - low) * rate + } + ) + .sum() + .value() + +export const computeOrdinaryTax = computeTax( + (status) => federalBrackets.ordinary.status[status].brackets, + federalBrackets.ordinary.rates +) + +export const computeLongTermCapGainsTax = computeTax( + (status) => federalBrackets.longTermCapGains.status[status].brackets, + federalBrackets.longTermCapGains.rates +) diff --git a/src/forms/Y2023/irsForms/index.ts b/src/forms/Y2023/irsForms/index.ts new file mode 100644 index 000000000..d026b679d --- /dev/null +++ b/src/forms/Y2023/irsForms/index.ts @@ -0,0 +1,46 @@ +import { PDFDocument } from 'pdf-lib' +import { create1040 } from '../irsForms/Main' +import { Either, isLeft, isRight, right } from 'ustaxes/core/util' +import log from 'ustaxes/core/log' +import { combinePdfs, PDFDownloader } from 'ustaxes/core/pdfFiller/pdfHandler' +import { Information, Asset } from 'ustaxes/core/data' +import { insertFormDataToPdfs } from 'ustaxes/core/irsForms' +import { F1040Error } from 'ustaxes/forms/errors' + +export { create1040 } + +export const create1040PDFs = + (state: Information, assets: Asset[]) => + async ( + downloader: PDFDownloader + ): Promise> => { + if (state.taxPayer.primaryPerson !== undefined) { + const f1040Result = create1040(state, assets) + // Get data and pdf links applicable to the model state + if (isLeft(f1040Result)) { + return Promise.reject(f1040Result) + } + + const [, forms] = f1040Result.right + + return right(await insertFormDataToPdfs(forms, downloader)) + } + + log.error('Attempt to create pdf with no data, will be empty') + return right([]) + } + +export const create1040PDF = + (state: Information, assets: Asset[]) => + async ( + downloader: PDFDownloader + ): Promise> => { + const pdfResult = await create1040PDFs(state, assets)(downloader) + if (isRight(pdfResult)) { + const pdf = await combinePdfs(pdfResult.right) + const bytes = await pdf.save() + return right(bytes) + } else { + return Promise.resolve(pdfResult) + } + } diff --git a/src/forms/Y2023/irsForms/worksheets/Pub596Worksheet1.ts b/src/forms/Y2023/irsForms/worksheets/Pub596Worksheet1.ts new file mode 100644 index 000000000..e1f66f692 --- /dev/null +++ b/src/forms/Y2023/irsForms/worksheets/Pub596Worksheet1.ts @@ -0,0 +1,86 @@ +import { EIC } from '../../data/federal' +import { ifNegative, ifPositive } from 'ustaxes/core/util' +import F1040 from '../../irsForms/F1040' +import { sumFields } from 'ustaxes/core/irsForms/util' + +export default class Pub596Worksheet1 { + f1040: F1040 + + constructor(f1040: F1040) { + this.f1040 = f1040 + } + + l1 = (): number | undefined => this.f1040.l2b() + l2 = (): number | undefined => + sumFields([this.f1040.l2a(), this.f1040.f8814?.l1b()]) + l3 = (): number | undefined => this.f1040.l3b() + l4 = (): number | undefined => { + return this.f1040.schedule1.l8f() + } + + l5 = (): number => ((this.f1040.l7() ?? 0) < 0 ? 0 : this.f1040.l7() ?? 0) + l6 = (): number => { + const l7 = this.f1040.f4797?.l7() + const l9 = this.f1040.f4797?.l9() + + if (l7 !== undefined && l7 < 0) { + return l9 ?? 0 + } + + return l7 ?? 0 + } + + l7 = (): number => { + const diff = this.l5() - this.l6() + return diff < 0 ? 0 : diff + } + + l8 = (): number | undefined => + sumFields([this.f1040.scheduleE.l23b(), this.f1040.schedule1.l8k()]) + + l9 = (): number | undefined => + sumFields([ + this.f1040.scheduleE.royaltyExpenses(), + this.f1040.schedule1.l24b() + ]) + + l10 = (): number => Math.max(0, (this.l9() ?? 0) - (this.l8() ?? 0)) + + l11 = (): number | undefined => + sumFields([ + ifPositive(this.f1040.scheduleE.l26()), + this.f1040.scheduleE.l29ah(), + this.f1040.scheduleE.l34ad(), + this.f1040.scheduleE.l40() + // todo: FPA form 4797 line 10 + ]) + + l12 = (): number | undefined => + sumFields( + [ + this.f1040.scheduleE.l26(), + this.f1040.scheduleE.l29bg() ?? 0, + this.f1040.scheduleE.l34bc() ?? 0, + this.f1040.scheduleE.l40() ?? 0 + // TODO: PAL Loss form 4797 + ].map((x) => ifNegative(x)) + ) + + l13 = (): number | undefined => + ifPositive(sumFields([this.l11(), this.l12()])) + + l14 = (): number | undefined => + sumFields([ + this.l1(), + this.l2(), + this.l3(), + this.l4(), + this.l7(), + this.l10(), + this.l13() + ]) + + l15 = (): boolean => (this.l14() ?? 0) > EIC.maxInvestmentIncome + + precludesEIC = (): boolean => this.l15() +} diff --git a/src/forms/Y2023/irsForms/worksheets/QualifyingDependents.ts b/src/forms/Y2023/irsForms/worksheets/QualifyingDependents.ts new file mode 100644 index 000000000..5e7f4081a --- /dev/null +++ b/src/forms/Y2023/irsForms/worksheets/QualifyingDependents.ts @@ -0,0 +1,34 @@ +import F1040 from '../F1040' +import { Dependent } from 'ustaxes/core/data' +import * as federal from '../../data/federal' + +/** + * As of TY2021, the Child Tax Credit worksheet + * is no longer published. This just implements + * the qualifying dependent logic. + */ +export default class QualifyingDependents { + f1040: F1040 + year = 2023 + + constructor(f1040: F1040) { + this.f1040 = f1040 + } + + qualifiesChild = (d: Dependent): boolean => + this.year - d.dateOfBirth.getFullYear() < + federal.QualifyingDependents.childMaxAge + + qualifiesOther = (d: Dependent): boolean => + d.qualifyingInfo !== undefined && + !this.qualifiesChild(d) && + this.year - d.dateOfBirth.getFullYear() < + (d.qualifyingInfo.isStudent + ? federal.QualifyingDependents.qualifyingDependentMaxAge + : federal.QualifyingDependents.qualifyingStudentMaxAge) + + qualifyingChildren = (): Dependent[] => + this.f1040.info.taxPayer.dependents.filter((dep) => + this.qualifiesChild(dep) + ) +} diff --git a/src/forms/Y2023/irsForms/worksheets/SDQualifiedAndCapGains.ts b/src/forms/Y2023/irsForms/worksheets/SDQualifiedAndCapGains.ts new file mode 100644 index 000000000..ae683762c --- /dev/null +++ b/src/forms/Y2023/irsForms/worksheets/SDQualifiedAndCapGains.ts @@ -0,0 +1,205 @@ +// Reference implementation for ltcg and cap gains worksheet +import { WorksheetData } from 'ustaxes/components/SummaryData' +import { FilingStatus } from 'ustaxes/core/data' +import federalBrackets from '../../data/federal' +import { computeOrdinaryTax } from '../../irsForms/TaxTable' +import { Worksheet } from '../F1040Attachment' + +type Bracket = [number, number] +type Cutoffs = { [key in FilingStatus]: Bracket } +const cutoffAmounts: Cutoffs = { + [FilingStatus.S]: [40400, 445850], + [FilingStatus.MFJ]: [80800, 501600], + [FilingStatus.MFS]: [40400, 250800], + [FilingStatus.W]: [80800, 501600], + [FilingStatus.HOH]: [54100, 473750] +} + +export default class QualDivAndCGWorksheet extends Worksheet { + // 1. Enter the amount from Form 1040 or 1040-SR, line 15. + // However, if you are filing Form 2555(relating to foreign earned income), + // enter the amount from line 3 of the Foreign Earned Income Tax Worksheet + l1 = (): number => { + if (this.f1040.f2555 !== undefined) { + return this.f1040.f2555.l3() ?? 0 + } + return this.f1040.l15() + } + // 2. Enter the amount from Form 1040 or 1040-SR, line 3a* + l2 = (): number => this.f1040.l3a() ?? 0 + // 3. Are you filing Schedule D?* + // Yes. Enter the smaller of line 15 or 16 of Schedule D. + // If either line 15 or 16 is blank or a loss, enter - 0 -. 3. + // No. Enter the amount from Form 1040 or 1040-SR, line 7. + l3 = (): number => { + if (this.f1040.scheduleD.isNeeded()) { + return Math.min( + Math.max(this.f1040.scheduleD.l15(), 0), + Math.max(this.f1040.scheduleD.l16(), 0) + ) + } + return this.f1040.l7() ?? 0 + } + // 4. Add lines 2 and 3: LTCG + QDIV + l4 = (): number => this.l2() + this.l3() + // 5. Subtract line 4 from line 1. If zero or less, enter -0- + l5 = (): number => Math.max(this.l1() - this.l4(), 0) + // 6. Enter: + // $40,400 if single or married filing separately, + // $80,800 if married filing jointly or qualifying widow(er), $54,100 if head of household. + l6 = (): number => cutoffAmounts[this.f1040.info.taxPayer.filingStatus][0] + // 7. Enter the smaller of line 1 or line 6 + l7 = (): number => Math.min(this.l1(), this.l6()) + // 8. Enter the smaller of line 5 or line 7 + l8 = (): number => Math.min(this.l5(), this.l7()) + // 9. Subtract line 8 from line 7. This amount is taxed at 0% + l9 = (): number => this.l7() - this.l8() + // 10. Enter the smaller of line 1 or line 4 + l10 = (): number => Math.min(this.l1(), this.l4()) + // 11. Enter the amount from line 9 + l11 = (): number => this.l9() + // 12. Subtract line 11 from line 10 + l12 = (): number => this.l10() - this.l11() + // 13. Enter: + // $445,850 if single, $250,800 if married filing separately, $501,600 if married filing jointly or qualifying widow(er), $473,750 if head of household. + // + l13 = (): number => cutoffAmounts[this.f1040.info.taxPayer.filingStatus][1] + // 14. Enter the smaller of line 1 or line 13 + l14 = (): number => Math.min(this.l1(), this.l13()) + // 15. Add lines 5 and 9 + l15 = (): number => this.l5() + this.l9() + // 16. Subtract line 15 from line 14. If zero or less, enter -0- + l16 = (): number => Math.max(this.l14() - this.l15(), 0) + // 17. Enter the smaller of line 12 or line 16 + l17 = (): number => Math.min(this.l12(), this.l16()) + // 18. Multiply line 17 by 15% (0.15) + l18 = (): number => + (this.l17() * federalBrackets.longTermCapGains.rates[1]) / 100 + // 19. Add lines 9 and 17 + l19 = (): number => this.l9() + this.l17() + // 20. Subtract line 19 from line 10 + l20 = (): number => this.l10() - this.l19() + // 21. Multiply line 20 by 20% (0.20) + l21 = (): number => + (this.l20() * federalBrackets.longTermCapGains.rates[2]) / 100 + // 22. Figure the tax on the amount on line 5. If the amount on line 5 is less than $100,000, use the Tax Table to figure the tax. If the amount on line 5 is $100,000 or more, use the Tax Computation Worksheet + l22 = (): number => + computeOrdinaryTax(this.f1040.info.taxPayer.filingStatus, this.l5()) + // 23. Add lines 18, 21, and 22 + l23 = (): number => this.l18() + this.l21() + this.l22() + // 24. Figure the tax on the amount on line 1. If the amount on line 1 is less than $100,000, use the Tax Table to figure the tax. If the amount on line 1 is $100,000 or more, use the Tax Computation Worksheet + l24 = (): number => + computeOrdinaryTax(this.f1040.info.taxPayer.filingStatus, this.l1()) + // 25. Tax on all taxable income. Enter the smaller of line 23 or 24. Also include this amount on the entry space on Form 1040 or 1040-SR, line 16. If you are filing Form 2555, don’t enter this amount on the entry space on Form 1040 or 1040-SR, line 16. Instead, enter it on line 4 of the Foreign Earned Income Tax Worksheet + l25 = (): number => Math.min(this.l23(), this.l24()) + + tax = (): number => this.l25() + + getSummaryData = (): WorksheetData => { + return { + name: 'Qualified Dividends and Capital Gains Worksheet — Line 16', + lines: [ + { + line: 1, + value: this.l1() + }, + { + line: 2, + value: this.l2() + }, + { + line: 3, + value: this.l3() + }, + { + line: 4, + value: this.l4() + }, + { + line: 5, + value: this.l5() + }, + { + line: 6, + value: this.l6() + }, + { + line: 7, + value: this.l7() + }, + { + line: 8, + value: this.l8() + }, + { + line: 9, + value: this.l9() + }, + { + line: 10, + value: this.l10() + }, + { + line: 11, + value: this.l11() + }, + { + line: 12, + value: this.l12() + }, + { + line: 13, + value: this.l13() + }, + { + line: 14, + value: this.l14() + }, + { + line: 15, + value: this.l15() + }, + { + line: 16, + value: this.l16() + }, + { + line: 17, + value: this.l17() + }, + { + line: 18, + value: this.l18() + }, + { + line: 19, + value: this.l19() + }, + { + line: 20, + value: this.l20() + }, + { + line: 21, + value: this.l21() + }, + { + line: 22, + value: this.l22() + }, + { + line: 23, + value: this.l23() + }, + { + line: 24, + value: this.l24() + }, + { + line: 25, + value: this.l25() + } + ] + } + } +} diff --git a/src/forms/Y2023/irsForms/worksheets/SDRateGainWorksheet.ts b/src/forms/Y2023/irsForms/worksheets/SDRateGainWorksheet.ts new file mode 100644 index 000000000..07728f305 --- /dev/null +++ b/src/forms/Y2023/irsForms/worksheets/SDRateGainWorksheet.ts @@ -0,0 +1,7 @@ +export default class SDRateGainWorksheet { + l7 = (): number | undefined => undefined + l10 = (): number | undefined => undefined + l13 = (): number | undefined => undefined + l14 = (): number | undefined => undefined + l21 = (): number | undefined => undefined +} diff --git a/src/forms/Y2023/irsForms/worksheets/SDTaxWorksheet.ts b/src/forms/Y2023/irsForms/worksheets/SDTaxWorksheet.ts new file mode 100644 index 000000000..54f44cf9f --- /dev/null +++ b/src/forms/Y2023/irsForms/worksheets/SDTaxWorksheet.ts @@ -0,0 +1,40 @@ +import { Worksheet } from '../F1040Attachment' + +export default class SDTaxWorksheet extends Worksheet { + /** + * Complete this worksheet only if line 18 or line 19 of Schedule D is more than zero + * and lines 15 and 16 of Schedule D are gains or if you file Form 4952 and you have + * an amount on line 4g, even if you don’t need to file Schedule D. Otherwise, + * complete the Qualified Dividends and Capital Gain Tax Worksheet in the instructions + * for Forms 1040 and 1040-SR, line 16 (or in the instructions for Form 1040-NR, line 16) + * to figure your tax. Before completing this worksheet, complete Form 1040, 1040-SR, or + * 1040-NR through line 15. + */ + isNeeded = (): boolean => { + const sd = this.f1040.scheduleD + const f4952 = this.f1040.f4952 + + const sdCondition = + sd.isNeeded() && + ((sd.l18() ?? 0) > 0 || (sd.l19() ?? 0) > 0) && + sd.l15() > 0 && + sd.l16() > 0 + + const f4952Condition = f4952 !== undefined && (f4952.l4g() ?? 0) > 0 + + return sdCondition || f4952Condition + } + + // TODO - Required by 6251, + // Might be refigured for AMT + l10 = (): number | undefined => undefined + + // TODO - Required by 6251, + l13 = (): number | undefined => undefined + + // TODO - Required by 6251, + l14 = (): number | undefined => undefined + + // TODO - Required by 6251, + l21 = (): number | undefined => undefined +} diff --git a/src/forms/Y2023/irsForms/worksheets/SDUnrecaptured1250.ts b/src/forms/Y2023/irsForms/worksheets/SDUnrecaptured1250.ts new file mode 100644 index 000000000..328c18aa5 --- /dev/null +++ b/src/forms/Y2023/irsForms/worksheets/SDUnrecaptured1250.ts @@ -0,0 +1,3 @@ +export default class SDUnrecaptured1250 { + l18 = (): number | undefined => undefined +} diff --git a/src/forms/Y2023/irsForms/worksheets/ScheduleDTaxWorksheet.ts b/src/forms/Y2023/irsForms/worksheets/ScheduleDTaxWorksheet.ts new file mode 100644 index 000000000..cf99a2e71 --- /dev/null +++ b/src/forms/Y2023/irsForms/worksheets/ScheduleDTaxWorksheet.ts @@ -0,0 +1,147 @@ +// Reference implementation for Schedule D Tax Worksheet +import { FilingStatus } from 'ustaxes/core/data' +import { computeOrdinaryTax } from '../../irsForms/TaxTable' + +export interface TestData { + qualDiv: number + taxableIncome: number + f4952l4g: number + f4952l4e: number + sdl15: number + sdl16: number + sdl18: number + sdl19: number + filingStatus: FilingStatus +} + +type Bracket = [number, number, number] +type Cutoffs = { [key in FilingStatus]: Bracket } +const cutoffAmounts: Cutoffs = { + [FilingStatus.S]: [40000, 163300, 441450], + [FilingStatus.MFJ]: [80000, 326600, 496600], + [FilingStatus.MFS]: [40000, 163300, 441450], + [FilingStatus.W]: [80000, 326600, 496600], + [FilingStatus.HOH]: [53600, 163300, 469050] +} + +export default class LTCGQualDivReference { + [k: string]: TestData | (() => number) + data: TestData + + constructor(data: TestData) { + this.data = data + } + + // 1. Enter your taxable income from Form 1040, 1040-SR, or 1040-NR, line 15. (However, if you are filing Form 2555 (relating to foreign earned income), enter instead the amount from line 3 of the Foreign Earned Income Tax Worksheet in the instructions for Forms 1040 and 1040-SR, line 16) + l1 = (): number => this.data.taxableIncome + // 2. Enter your qualified dividends from Form 1040, 1040-SR, or 1040-NR, line 3a + l2 = (): number => this.data.qualDiv + // 3. Enter the amount from Form 4952 (used to figure investment interest expense deduction), line 4g + l3 = (): number => this.data.f4952l4g + // 4. Enter the amount from Form 4952, line 4e* + l4 = (): number => this.data.f4952l4e + // 5. Subtract line 4 from line 3. If zero or less, enter -0- + l5 = (): number => Math.max(0, this.l3() - this.l4()) + // 6. Subtract line 5 from line 2. If zero or less, enter -0-** + l6 = (): number => Math.max(0, this.l2() - this.l5()) + // 7. Enter the smaller of line 15 or line 16 of Schedule D + l7 = (): number => Math.min(this.data.sdl15, this.data.sdl16) + // 8. Enter the smaller of line 3 or line 4 + l8 = (): number => Math.min(this.l3(), this.l4()) + // 9. Subtract line 8 from line 7. If zero or less, enter -0-** + l9 = (): number => Math.max(0, this.l7() - this.l8()) + // 10. Add lines 6 and 9 + l10 = (): number => this.l6() + this.l9() + // 11. Add lines 18 and 19 of Schedule D** + l11 = (): number => this.data.sdl18 + this.data.sdl19 + // 12. Enter the smaller of line 9 or line 11 + l12 = (): number => Math.min(this.l9(), this.l11()) + // 13. Subtract line 12 from line 10 + l13 = (): number => this.l10() - this.l12() + // 14. Subtract line 13 from line 1. If zero or less, enter -0- + l14 = (): number => Math.max(0, this.l1() - this.l13()) + // 15. Enter: + l15 = (): number => cutoffAmounts[this.data.filingStatus][0] + // 16. Enter the smaller of line 1 or line 15 + l16 = (): number => Math.min(this.l1(), this.l15()) + // 17. Enter the smaller of line 14 or line 16 + l17 = (): number => Math.min(this.l14(), this.l16()) + // 18. Subtract line 10 from line 1. If zero or less, enter -0- + l18 = (): number => Math.max(0, this.l1() - this.l10()) + // 19. Enter the smaller of line 1 or [ltcg bracket 2] + l19 = (): number => + Math.min(this.l1(), cutoffAmounts[this.data.filingStatus][1]) + // 20. Enter the smaller of line 14 or line 19 + l20 = (): number => Math.min(this.l14(), this.l19()) + // 21. Enter the larger of line 18 or line 20 + l21 = (): number => Math.max(this.l18(), this.l20()) + // 22. Subtract line 17 from line 16. This amount is taxed at 0%. + l22 = (): number => Math.max(0, this.l16() - this.l17()) + // If lines 1 and 16 are the same, skip lines 23 through 43 and go to line 44. Otherwise, go to line 23. + // 23. Enter the smaller of line 1 or line 13 + l23 = (): number => Math.min(this.l1(), this.l13()) + // 24. Enter the amount from line 22. (If line 22 is blank, enter -0-.) + l24 = (): number => this.l22() + // 25. Subtract line 24 from line 23. If zero or less, enter -0- + l25 = (): number => Math.max(0, this.l23() - this.l24()) + // 26. Enter top bracket amount + l26 = (): number => cutoffAmounts[this.data.filingStatus][1] + // 27. Enter the smaller of line 1 or line 26 + l27 = (): number => Math.min(this.l1(), this.l26()) + // 28. Add lines 21 and 22 + l28 = (): number => this.l21() + this.l22() + // 29. Subtract line 28 from line 27. If zero or less, enter -0- + l29 = (): number => Math.max(0, this.l27() - this.l28()) + // 30. Enter the smaller of line 25 or line 29 + l30 = (): number => Math.min(this.l25(), this.l29()) + // 31. Multiply line 30 by 15% (0.15) + l31 = (): number => this.l30() * 0.15 + // 32. Add lines 24 and 30 + l32 = (): number => this.l24() + this.l30() + // If lines 1 and 32 are the same, skip lines 33 through 43 and go to line 44. Otherwise, go to line 33. + // 33. Subtract line 32 from line 23 + l33 = (): number => Math.max(0, this.l23() - this.l32()) + // 34. Multiply line 33 by 20% (0.20) + l34 = (): number => this.l33() * 0.2 + // If Schedule D, line 19, is zero or blank, skip lines 35 through 40 and go to line 41. Otherwise, go to line 35. + // 35. Enter the smaller of line 9 above or Schedule D, line 19 + l35 = (): number => Math.min(this.l9(), this.data.sdl19) + // 36. Add lines 10 and 21 + l36 = (): number => this.l10() + this.l21() + // 37. Enter the amount from line 1 above + l37 = (): number => this.l1() + // 38. Subtract line 37 from line 36. If zero or less, enter -0- + l38 = (): number => Math.max(0, this.l36() - this.l37()) + // 39. Subtract line 38 from line 35. If zero or less, enter -0- + l39 = (): number => Math.max(0, this.l35() - this.l38()) + // 40. Multiply line 39 by 25% (0.25) + l40 = (): number => this.l39() * 0.25 + // If Schedule D, line 18, is zero or blank, skip lines 41 through 43 and go to line 44. Otherwise, go to line 41. + // 41. Add lines 21, 22, 30, 33, and 39 + l41 = (): number => + this.l21() + this.l22() + this.l30() + this.l33() + this.l39() + // 42. Subtract line 41 from line 1 + l42 = (): number => Math.max(0, this.l1() - this.l41()) + // 43. Multiply line 42 by 28% (0.28) + l43 = (): number => this.l42() * 0.28 + // 44. Figure the tax on the amount on line 21. If the amount on line 21 is less than $100,000, use the Tax Table to + l44 = (): number => computeOrdinaryTax(this.data.filingStatus, this.l21()) + // 45. Add lines 31, 34, 40, 43, and 44 + l45 = (): number => + this.l31() + this.l34() + this.l40() + this.l43() + this.l44() + // 46. Figure the tax on the amount on line 1. If the amount on line 1 is less than $100,000, use the Tax Table to + l46 = (): number => computeOrdinaryTax(this.data.filingStatus, this.l1()) + // figure the tax. If the amount on line 1 is $100,000 or more, use the Tax Computation Worksheet + // 47. Tax on all taxable income (including capital gains and qualified dividends). Enter the smaller of line 45 + // or line 46. Also, include this amount on Form 1040, 1040-SR, or 1040-NR, line 16. (If you are filing Form + // 2555, don't enter this amount on Form 1040 or 1040-SR, line 16. Instead, enter it on line 4 of the Foreign + // Earned Income Tax Worksheet in the Instructions for Forms 1040 and 1040-SR) + l47 = (): number => Math.round(Math.min(this.l45(), this.l46())) +} + +export const showReference = (r: LTCGQualDivReference): string => + Array.from(Array(47)) + .map((_, i) => `l${i + 1}`) + .map((x) => [x, (r[x] as () => number)()]) + .map(([l, v]) => `${l}: ${v}`) + .join('\n') diff --git a/src/forms/Y2023/irsForms/worksheets/SocialSecurityBenefits.ts b/src/forms/Y2023/irsForms/worksheets/SocialSecurityBenefits.ts new file mode 100644 index 000000000..5c8852004 --- /dev/null +++ b/src/forms/Y2023/irsForms/worksheets/SocialSecurityBenefits.ts @@ -0,0 +1,190 @@ +import { FilingStatus } from 'ustaxes/core/data' +import { sumFields } from 'ustaxes/core/irsForms/util' +import { SSBenefits } from '../../data/federal' +import { Worksheet } from '../F1040Attachment' + +export default class SocialSecurityBenefitsWorksheet extends Worksheet { + totalNetBenefits = (): number => + this.f1040 + .f1099ssas() + .map((f) => f.form.netBenefits) + .reduce((l, r) => l + r, 0) + + /* Enter the total amount from box 5 of all your Forms SSA-1099 and RRB-1099. + Also enter this amount on Form 1040 or 1040-SR, line 6a + */ + l1 = (): number => this.totalNetBenefits() + // Multiply line 1 by 50% (0.50) + l2 = (): number => this.l1() / 2 + /* If you are not excluding unemployment compensation from income, + combine the amounts from Form 1040 or 1040-SR, lines 1, 2b, 3b, 4b, 5b, 7, and 8. + If you are excluding unemployment compensation from income, + combine the amounts from Form 1040 or 1040-SR , lines 1, 2b, 3b, 4b, 5b, 7, + Schedule 1, lines 1 through 7, and line 3 of the Unemployment Compensation Exclusion Worksheet + */ + l3 = (): number => + sumFields([ + this.f1040.l1z(), + this.f1040.l2b(), + this.f1040.l3b(), + this.f1040.l4b(), + this.f1040.l5b(), + this.f1040.l7(), + this.f1040.l8() + ]) + // Enter the amount, if any, from Form 1040 or 1040-SR, line 2a + l4 = (): number | undefined => this.f1040.l2a() + // Combine lines 2, 3, and 4 + l5 = (): number => sumFields([this.l2(), this.l3(), this.l4()]) + /* Enter the total of the amounts from Form 1040 or 1040-SR, line 10b, + Schedule 1, lines 10 through 19, + plus any write-in adjustments you entered on the dotted line next to Schedule 1, line 22 + */ + l6 = (): number => + sumFields([ + // TODO: figure out the Y2022 line + //this.f1040.l12b(), + this.f1040.schedule1.l10(), + this.f1040.schedule1.l11(), + this.f1040.schedule1.l12(), + this.f1040.schedule1.l13(), + this.f1040.schedule1.l14(), + this.f1040.schedule1.l15(), + this.f1040.schedule1.l16(), + this.f1040.schedule1.l17(), + this.f1040.schedule1.l18(), + this.f1040.schedule1.l19a(), + this.f1040.schedule1.l20() + ]) + + /* Line 7: Is the amount on line 6 less than the amount on line 5? + If No, None of your social security benefits are taxable. Enter -0- on Form 1040 or 1040-SR, line 6b. + If Yes, Subtract line 6 from line 5 + */ + + l7 = (): number => { + if (this.l6() < this.l5()) { + return this.l5() - this.l6() + } else { + return 0 + } + } + /* + If you are: + Married filing jointly, enter $32,000 + Single, head of household, qualifying widow(er), or married filing + separately and you lived apart from your spouse for all of the year, + enter $25,000 + Married filing separately and you lived with your spouse at any time + in the year, skip lines 8 through 15; multiply line 7 by 85% (0.85) and + enter the result on line 16. Then, go to line 17 + */ + l8 = (): number => { + if (this.f1040.info.taxPayer.filingStatus == FilingStatus.MFS) { + // treat Married filing separately specially due to the extra question below + // and resulting logic in the worksheet + if (this.f1040.info.questions.LIVE_APART_FROM_SPOUSE) { + return SSBenefits.caps[this.f1040.info.taxPayer.filingStatus].l8 + } else { + // Note that this value won't be taken into account. Instead, + // the line 16 function will also check for this and perform + // the right math. + return 0 + } + } else { + return SSBenefits.caps[this.f1040.info.taxPayer.filingStatus].l8 + } + } + /* + Is the amount on line 8 less than the amount on line 7? + + If No, None of your social security benefits are taxable. + Enter -0- on Form 1040 or 1040-SR, line 6b. + If you are married filing separately and you lived apart from your spouse for all of 2020, + be sure you entered "D" to the right of the word "benefits" on line 6a. + + If Yes, Subtract line 8 from line 7. + */ + l9 = (): number => { + if (this.l8() < this.l7()) { + return this.l7() - this.l8() + } else { + return 0 + } + } + + /* + Enter: $12,000 if married filing jointly; + $9,000 if single, head of household, qualifying widow(er), or married filing separately + and you lived apart from your spouse for all of 2020 + */ + l10 = (): number => SSBenefits.caps[this.f1040.info.taxPayer.filingStatus].l10 + + // Subtract line 10 from line 9. If zero or less, enter -0- + l11 = (): number => { + const tmp = this.l9() - this.l10() + if (tmp < 0) { + return 0 + } else { + return tmp + } + } + + // Enter the smaller of line 9 or line 10 + l12 = (): number => Math.min(this.l9(), this.l10()) + + // Enter one-half of line 12 + l13 = (): number => this.l12() / 2 + + // Enter the smaller of line 2 or line 13 + l14 = (): number => Math.min(this.l13(), this.l2()) + + // Multiply line 11 by 85% (0.85). If line 11 is zero, enter -0- + l15 = (): number => { + if (this.l11() == 0) { + return 0 + } else { + return this.l11() * 0.85 + } + } + + // Add lines 14 and 15 + l16 = (): number => { + // From line 7 instructions: + // Married filing separately and you lived with your spouse at any time + // in 2020, skip lines 8 through 15; multiply line 7 by 85% (0.85) and + // enter the result on line 16. Then, go to line 17 + if ( + this.f1040.info.taxPayer.filingStatus == FilingStatus.MFS && + !this.f1040.info.questions.LIVE_APART_FROM_SPOUSE + ) { + return this.l7() * 0.85 + } else { + return sumFields([this.l14(), this.l15()]) + } + } + + // Multiply line 1 by 85% (0.85) + l17 = (): number => this.l1() * 0.85 + + // Taxable social security benefits. Enter the smaller of line 16 or line 17. + // Also enter this amount on Form 1040 or 1040-SR, line 6b + l18 = (): number => Math.min(this.l16(), this.l17()) + + // This is the function used to return the taxable amount of the social security + // benefits to be entered in line 6b of 1040. It takes into account the various + // stopping points in the worksheet. + taxableAmount = (): number => { + const line7 = this.l7() + if (line7 == 0) { + return line7 + } + + const line9 = this.l9() + if (line9 == 0) { + return line9 + } + + return this.l18() + } +} diff --git a/src/forms/Y2023/irsForms/worksheets/StudentLoanInterestWorksheet.ts b/src/forms/Y2023/irsForms/worksheets/StudentLoanInterestWorksheet.ts new file mode 100644 index 000000000..d03ce79f7 --- /dev/null +++ b/src/forms/Y2023/irsForms/worksheets/StudentLoanInterestWorksheet.ts @@ -0,0 +1,75 @@ +import { F1098e, FilingStatus } from 'ustaxes/core/data' +import F1040 from '../../irsForms/F1040' +import { sumFields } from 'ustaxes/core/irsForms/util' + +export default class StudentLoanInterestWorksheet { + f1040: F1040 + f1098es: F1098e[] + + constructor(f1040: F1040, f1098es: F1098e[]) { + this.f1040 = f1040 + this.f1098es = f1098es + } + + // Can't take deduction if filling Married Filling Seperate + notMFS = (): boolean => + this.f1040.info.taxPayer.filingStatus !== FilingStatus.MFS + + // Can't take deduction if MFJ and spouse is a dependent + isNotDependentSpouse = (): boolean => + this.f1040.info.taxPayer.filingStatus !== FilingStatus.MFJ || + this.f1040.info.taxPayer.spouse === undefined || + !this.f1040.info.taxPayer.spouse.isTaxpayerDependent + + // Can't take deduction if someone else claims you as a dependent + isNotDependentSelf = (): boolean => + !this.f1040.info.taxPayer.primaryPerson.isTaxpayerDependent + + isNotDependent = (): boolean => + this.isNotDependentSpouse() && this.isNotDependentSelf() + + // Sum interest, but maximum of 2500 can be deducted + l1 = (): number => + Math.min( + this.f1098es.map((f1098e) => f1098e.interest).reduce((l, r) => l + r, 0), + 2500 + ) + + // Currently do not support unemployment compensation exclusion + // TO DO: add unemployment compensation exclusion + l2 = (): number => this.f1040.l9() + + // Schedule 1 deductions + l3 = (): number => + sumFields([ + // TODO: figure out Y2022 line number + //this.f1040.l12b(), + this.f1040.schedule1.l11(), + this.f1040.schedule1.l12(), + this.f1040.schedule1.l13(), + this.f1040.schedule1.l14(), + this.f1040.schedule1.l15(), + this.f1040.schedule1.l16(), + this.f1040.schedule1.l17(), + this.f1040.schedule1.l18(), + this.f1040.schedule1.l19a(), + this.f1040.schedule1.l20() + // TODO: missing write-in deduction ? + ]) + + l4 = (): number => Math.max(0, this.l2() - this.l3()) + + l5 = (): number => + this.f1040.info.taxPayer.filingStatus === FilingStatus.MFJ ? 140000 : 70000 + + l6 = (): number => Math.max(0, this.l4() - this.l5()) + + l7 = (): number => Math.min(this.l6() / 15000, 1) + + l8 = (): number => this.l1() * this.l7() + + l9 = (): number | undefined => + this.notMFS() && this.isNotDependent() + ? Math.max(0, this.l1() - this.l8()) + : undefined +} diff --git a/src/forms/Y2023/stateForms/AK/Form.ts b/src/forms/Y2023/stateForms/AK/Form.ts new file mode 100644 index 000000000..58a8db644 --- /dev/null +++ b/src/forms/Y2023/stateForms/AK/Form.ts @@ -0,0 +1 @@ +export default class AKForm {} diff --git a/src/forms/Y2023/stateForms/AL/Form.ts b/src/forms/Y2023/stateForms/AL/Form.ts new file mode 100644 index 000000000..893ac3946 --- /dev/null +++ b/src/forms/Y2023/stateForms/AL/Form.ts @@ -0,0 +1 @@ +export default class ALForm {} diff --git a/src/forms/Y2023/stateForms/AR/Form.ts b/src/forms/Y2023/stateForms/AR/Form.ts new file mode 100644 index 000000000..19ef06e71 --- /dev/null +++ b/src/forms/Y2023/stateForms/AR/Form.ts @@ -0,0 +1 @@ +export default class ARForm {} diff --git a/src/forms/Y2023/stateForms/AZ/Form.ts b/src/forms/Y2023/stateForms/AZ/Form.ts new file mode 100644 index 000000000..4d61149ec --- /dev/null +++ b/src/forms/Y2023/stateForms/AZ/Form.ts @@ -0,0 +1 @@ +export default class AZForm {} diff --git a/src/forms/Y2023/stateForms/CA/Form.ts b/src/forms/Y2023/stateForms/CA/Form.ts new file mode 100644 index 000000000..7b577f104 --- /dev/null +++ b/src/forms/Y2023/stateForms/CA/Form.ts @@ -0,0 +1 @@ +export default class CAForm {} diff --git a/src/forms/Y2023/stateForms/CO/Form.ts b/src/forms/Y2023/stateForms/CO/Form.ts new file mode 100644 index 000000000..d599e8319 --- /dev/null +++ b/src/forms/Y2023/stateForms/CO/Form.ts @@ -0,0 +1 @@ +export default class COForm {} diff --git a/src/forms/Y2023/stateForms/CT/Form.ts b/src/forms/Y2023/stateForms/CT/Form.ts new file mode 100644 index 000000000..448b21fd7 --- /dev/null +++ b/src/forms/Y2023/stateForms/CT/Form.ts @@ -0,0 +1 @@ +export default class CTForm {} diff --git a/src/forms/Y2023/stateForms/DC/Form.ts b/src/forms/Y2023/stateForms/DC/Form.ts new file mode 100644 index 000000000..20f7e2dc5 --- /dev/null +++ b/src/forms/Y2023/stateForms/DC/Form.ts @@ -0,0 +1 @@ +export default class DCForm {} diff --git a/src/forms/Y2023/stateForms/DE/Form.ts b/src/forms/Y2023/stateForms/DE/Form.ts new file mode 100644 index 000000000..967fcf4b3 --- /dev/null +++ b/src/forms/Y2023/stateForms/DE/Form.ts @@ -0,0 +1 @@ +export default class DEForm {} diff --git a/src/forms/Y2023/stateForms/FL/Form.ts b/src/forms/Y2023/stateForms/FL/Form.ts new file mode 100644 index 000000000..6cca04bc8 --- /dev/null +++ b/src/forms/Y2023/stateForms/FL/Form.ts @@ -0,0 +1 @@ +export default class FLForm {} diff --git a/src/forms/Y2023/stateForms/GA/Form.ts b/src/forms/Y2023/stateForms/GA/Form.ts new file mode 100644 index 000000000..6a638a5aa --- /dev/null +++ b/src/forms/Y2023/stateForms/GA/Form.ts @@ -0,0 +1 @@ +export default class GAForm {} diff --git a/src/forms/Y2023/stateForms/HI/Form.ts b/src/forms/Y2023/stateForms/HI/Form.ts new file mode 100644 index 000000000..4fcb1aff0 --- /dev/null +++ b/src/forms/Y2023/stateForms/HI/Form.ts @@ -0,0 +1 @@ +export default class HIForm {} diff --git a/src/forms/Y2023/stateForms/IA/Form.ts b/src/forms/Y2023/stateForms/IA/Form.ts new file mode 100644 index 000000000..7d2952b30 --- /dev/null +++ b/src/forms/Y2023/stateForms/IA/Form.ts @@ -0,0 +1 @@ +export default class IAForm {} diff --git a/src/forms/Y2023/stateForms/ID/Form.ts b/src/forms/Y2023/stateForms/ID/Form.ts new file mode 100644 index 000000000..e15ef1257 --- /dev/null +++ b/src/forms/Y2023/stateForms/ID/Form.ts @@ -0,0 +1 @@ +export default class IDForm {} diff --git a/src/forms/Y2023/stateForms/IL/Form.ts b/src/forms/Y2023/stateForms/IL/Form.ts new file mode 100644 index 000000000..18ff67c1c --- /dev/null +++ b/src/forms/Y2023/stateForms/IL/Form.ts @@ -0,0 +1 @@ +export default class ILForm {} diff --git a/src/forms/Y2023/stateForms/IN/Form.ts b/src/forms/Y2023/stateForms/IN/Form.ts new file mode 100644 index 000000000..db1118d38 --- /dev/null +++ b/src/forms/Y2023/stateForms/IN/Form.ts @@ -0,0 +1 @@ +export default class INForm {} diff --git a/src/forms/Y2023/stateForms/KS/Form.ts b/src/forms/Y2023/stateForms/KS/Form.ts new file mode 100644 index 000000000..f197f46c1 --- /dev/null +++ b/src/forms/Y2023/stateForms/KS/Form.ts @@ -0,0 +1 @@ +export default class KSForm {} diff --git a/src/forms/Y2023/stateForms/KY/Form.ts b/src/forms/Y2023/stateForms/KY/Form.ts new file mode 100644 index 000000000..f06e92268 --- /dev/null +++ b/src/forms/Y2023/stateForms/KY/Form.ts @@ -0,0 +1 @@ +export default class KYForm {} diff --git a/src/forms/Y2023/stateForms/LA/Form.ts b/src/forms/Y2023/stateForms/LA/Form.ts new file mode 100644 index 000000000..d795be49b --- /dev/null +++ b/src/forms/Y2023/stateForms/LA/Form.ts @@ -0,0 +1 @@ +export default class LAForm {} diff --git a/src/forms/Y2023/stateForms/MA/Form.ts b/src/forms/Y2023/stateForms/MA/Form.ts new file mode 100644 index 000000000..df0a34b8e --- /dev/null +++ b/src/forms/Y2023/stateForms/MA/Form.ts @@ -0,0 +1 @@ +export default class MAForm {} diff --git a/src/forms/Y2023/stateForms/MD/Form.ts b/src/forms/Y2023/stateForms/MD/Form.ts new file mode 100644 index 000000000..f76b42edf --- /dev/null +++ b/src/forms/Y2023/stateForms/MD/Form.ts @@ -0,0 +1 @@ +export default class MDForm {} diff --git a/src/forms/Y2023/stateForms/ME/Form.ts b/src/forms/Y2023/stateForms/ME/Form.ts new file mode 100644 index 000000000..6981b1c8b --- /dev/null +++ b/src/forms/Y2023/stateForms/ME/Form.ts @@ -0,0 +1 @@ +export default class MEForm {} diff --git a/src/forms/Y2023/stateForms/MI/Form.ts b/src/forms/Y2023/stateForms/MI/Form.ts new file mode 100644 index 000000000..28f509e7a --- /dev/null +++ b/src/forms/Y2023/stateForms/MI/Form.ts @@ -0,0 +1 @@ +export default class MIForm {} diff --git a/src/forms/Y2023/stateForms/MN/Form.ts b/src/forms/Y2023/stateForms/MN/Form.ts new file mode 100644 index 000000000..1a788a214 --- /dev/null +++ b/src/forms/Y2023/stateForms/MN/Form.ts @@ -0,0 +1 @@ +export default class MNForm {} diff --git a/src/forms/Y2023/stateForms/MO/Form.ts b/src/forms/Y2023/stateForms/MO/Form.ts new file mode 100644 index 000000000..2a14cea47 --- /dev/null +++ b/src/forms/Y2023/stateForms/MO/Form.ts @@ -0,0 +1 @@ +export default class MOForm {} diff --git a/src/forms/Y2023/stateForms/MS/Form.ts b/src/forms/Y2023/stateForms/MS/Form.ts new file mode 100644 index 000000000..bc3076b7f --- /dev/null +++ b/src/forms/Y2023/stateForms/MS/Form.ts @@ -0,0 +1 @@ +export default class MSForm {} diff --git a/src/forms/Y2023/stateForms/MT/Form.ts b/src/forms/Y2023/stateForms/MT/Form.ts new file mode 100644 index 000000000..ef4d1a1b0 --- /dev/null +++ b/src/forms/Y2023/stateForms/MT/Form.ts @@ -0,0 +1 @@ +export default class MTForm {} diff --git a/src/forms/Y2023/stateForms/NC/Form.ts b/src/forms/Y2023/stateForms/NC/Form.ts new file mode 100644 index 000000000..b047e2ce8 --- /dev/null +++ b/src/forms/Y2023/stateForms/NC/Form.ts @@ -0,0 +1 @@ +export default class NCForm {} diff --git a/src/forms/Y2023/stateForms/ND/Form.ts b/src/forms/Y2023/stateForms/ND/Form.ts new file mode 100644 index 000000000..344214c27 --- /dev/null +++ b/src/forms/Y2023/stateForms/ND/Form.ts @@ -0,0 +1 @@ +export default class NDForm {} diff --git a/src/forms/Y2023/stateForms/NE/Form.ts b/src/forms/Y2023/stateForms/NE/Form.ts new file mode 100644 index 000000000..c34a3e28b --- /dev/null +++ b/src/forms/Y2023/stateForms/NE/Form.ts @@ -0,0 +1 @@ +export default class NEForm {} diff --git a/src/forms/Y2023/stateForms/NH/Form.ts b/src/forms/Y2023/stateForms/NH/Form.ts new file mode 100644 index 000000000..e42d2eb2f --- /dev/null +++ b/src/forms/Y2023/stateForms/NH/Form.ts @@ -0,0 +1 @@ +export default class NHForm {} diff --git a/src/forms/Y2023/stateForms/NJ/Form.ts b/src/forms/Y2023/stateForms/NJ/Form.ts new file mode 100644 index 000000000..2383b7955 --- /dev/null +++ b/src/forms/Y2023/stateForms/NJ/Form.ts @@ -0,0 +1 @@ +export default class NJForm {} diff --git a/src/forms/Y2023/stateForms/NM/Form.ts b/src/forms/Y2023/stateForms/NM/Form.ts new file mode 100644 index 000000000..77ee2d48d --- /dev/null +++ b/src/forms/Y2023/stateForms/NM/Form.ts @@ -0,0 +1 @@ +export default class NMForm {} diff --git a/src/forms/Y2023/stateForms/NV/Form.ts b/src/forms/Y2023/stateForms/NV/Form.ts new file mode 100644 index 000000000..f9e8d498c --- /dev/null +++ b/src/forms/Y2023/stateForms/NV/Form.ts @@ -0,0 +1 @@ +export default class NVForm {} diff --git a/src/forms/Y2023/stateForms/NY/Form.ts b/src/forms/Y2023/stateForms/NY/Form.ts new file mode 100644 index 000000000..075ccedb7 --- /dev/null +++ b/src/forms/Y2023/stateForms/NY/Form.ts @@ -0,0 +1 @@ +export default class NYForm {} diff --git a/src/forms/Y2023/stateForms/OH/Form.ts b/src/forms/Y2023/stateForms/OH/Form.ts new file mode 100644 index 000000000..d46bf56a3 --- /dev/null +++ b/src/forms/Y2023/stateForms/OH/Form.ts @@ -0,0 +1 @@ +export default class OHForm {} diff --git a/src/forms/Y2023/stateForms/OK/Form.ts b/src/forms/Y2023/stateForms/OK/Form.ts new file mode 100644 index 000000000..4920e4eac --- /dev/null +++ b/src/forms/Y2023/stateForms/OK/Form.ts @@ -0,0 +1 @@ +export default class OKForm {} diff --git a/src/forms/Y2023/stateForms/OR/Form.ts b/src/forms/Y2023/stateForms/OR/Form.ts new file mode 100644 index 000000000..f3cc928fb --- /dev/null +++ b/src/forms/Y2023/stateForms/OR/Form.ts @@ -0,0 +1 @@ +export default class ORForm {} diff --git a/src/forms/Y2023/stateForms/PA/Form.ts b/src/forms/Y2023/stateForms/PA/Form.ts new file mode 100644 index 000000000..9af75e83b --- /dev/null +++ b/src/forms/Y2023/stateForms/PA/Form.ts @@ -0,0 +1 @@ +export default class PAForm {} diff --git a/src/forms/Y2023/stateForms/RI/Form.ts b/src/forms/Y2023/stateForms/RI/Form.ts new file mode 100644 index 000000000..b89753f9c --- /dev/null +++ b/src/forms/Y2023/stateForms/RI/Form.ts @@ -0,0 +1 @@ +export default class RIForm {} diff --git a/src/forms/Y2023/stateForms/SC/Form.ts b/src/forms/Y2023/stateForms/SC/Form.ts new file mode 100644 index 000000000..078ccafdf --- /dev/null +++ b/src/forms/Y2023/stateForms/SC/Form.ts @@ -0,0 +1 @@ +export default class SCForm {} diff --git a/src/forms/Y2023/stateForms/SD/Form.ts b/src/forms/Y2023/stateForms/SD/Form.ts new file mode 100644 index 000000000..ac1de6c8d --- /dev/null +++ b/src/forms/Y2023/stateForms/SD/Form.ts @@ -0,0 +1 @@ +export default class SDForm {} diff --git a/src/forms/Y2023/stateForms/TN/Form.ts b/src/forms/Y2023/stateForms/TN/Form.ts new file mode 100644 index 000000000..5736a8ef3 --- /dev/null +++ b/src/forms/Y2023/stateForms/TN/Form.ts @@ -0,0 +1 @@ +export default class TNForm {} diff --git a/src/forms/Y2023/stateForms/TX/Form.ts b/src/forms/Y2023/stateForms/TX/Form.ts new file mode 100644 index 000000000..75c4a72a5 --- /dev/null +++ b/src/forms/Y2023/stateForms/TX/Form.ts @@ -0,0 +1 @@ +export default class TXForm {} diff --git a/src/forms/Y2023/stateForms/UT/Form.ts b/src/forms/Y2023/stateForms/UT/Form.ts new file mode 100644 index 000000000..9079b7b7d --- /dev/null +++ b/src/forms/Y2023/stateForms/UT/Form.ts @@ -0,0 +1 @@ +export default class UTForm {} diff --git a/src/forms/Y2023/stateForms/VA/Form.ts b/src/forms/Y2023/stateForms/VA/Form.ts new file mode 100644 index 000000000..dd44425f3 --- /dev/null +++ b/src/forms/Y2023/stateForms/VA/Form.ts @@ -0,0 +1 @@ +export default class VAForm {} diff --git a/src/forms/Y2023/stateForms/VT/Form.ts b/src/forms/Y2023/stateForms/VT/Form.ts new file mode 100644 index 000000000..75491e366 --- /dev/null +++ b/src/forms/Y2023/stateForms/VT/Form.ts @@ -0,0 +1 @@ +export default class VTForm {} diff --git a/src/forms/Y2023/stateForms/WA/Form.ts b/src/forms/Y2023/stateForms/WA/Form.ts new file mode 100644 index 000000000..ad1d00135 --- /dev/null +++ b/src/forms/Y2023/stateForms/WA/Form.ts @@ -0,0 +1 @@ +export default class WAForm {} diff --git a/src/forms/Y2023/stateForms/WI/Form.ts b/src/forms/Y2023/stateForms/WI/Form.ts new file mode 100644 index 000000000..617b8ac29 --- /dev/null +++ b/src/forms/Y2023/stateForms/WI/Form.ts @@ -0,0 +1 @@ +export default class WIForm {} diff --git a/src/forms/Y2023/stateForms/WV/Form.ts b/src/forms/Y2023/stateForms/WV/Form.ts new file mode 100644 index 000000000..f95a73691 --- /dev/null +++ b/src/forms/Y2023/stateForms/WV/Form.ts @@ -0,0 +1 @@ +export default class WVForm {} diff --git a/src/forms/Y2023/stateForms/WY/Form.ts b/src/forms/Y2023/stateForms/WY/Form.ts new file mode 100644 index 000000000..4e9e71128 --- /dev/null +++ b/src/forms/Y2023/stateForms/WY/Form.ts @@ -0,0 +1 @@ +export default class WYForm {} diff --git a/src/forms/Y2023/stateForms/index.ts b/src/forms/Y2023/stateForms/index.ts new file mode 100644 index 000000000..ccb4ac95a --- /dev/null +++ b/src/forms/Y2023/stateForms/index.ts @@ -0,0 +1,27 @@ +import F1040 from '../irsForms/F1040' +import { State } from 'ustaxes/core/data' +import StateForm from 'ustaxes/core/stateForms/Form' +import { Either } from 'ustaxes/core/util' +import { createStateReturn as createStateReturnF } from '../../StateForms' +import { StateFormError } from '../../StateForms' + +export const noFilingRequirementStates: State[] = [ + 'AK', + 'TN', + 'WY', + 'FL', + 'NH', + 'SD', + 'TX', + 'WA', + 'NV' +] + +export const stateForms: { + [K in State]?: (f1040: F1040) => StateForm +} = {} + +export const createStateReturn = ( + f1040: F1040 +): Either => + createStateReturnF(noFilingRequirementStates, stateForms)(f1040) diff --git a/src/forms/Y2023/tests/Schedule8812.test.ts b/src/forms/Y2023/tests/Schedule8812.test.ts new file mode 100644 index 000000000..af27c32ff --- /dev/null +++ b/src/forms/Y2023/tests/Schedule8812.test.ts @@ -0,0 +1,43 @@ +import { commonTests } from '.' +import Schedule8812 from '../irsForms/Schedule8812' +import F1040 from '../irsForms/F1040' + +const withSchedule8812 = async ( + f: (f1040: F1040, s8812: Schedule8812) => void +): Promise => + await commonTests.withValid1040( + (f1040: F1040): void => { + if (f1040.schedule8812.isNeeded()) { + f(f1040, f1040.schedule8812) + } + }, + // Add filter to info property so we're only testing in the domain + // we care about. + (info) => info.taxPayer.dependents.length > 0 + ) + +describe('Schedule 8812', () => { + it('should be attached with qualifiying dependents', async () => { + await commonTests.withValid1040((f1040) => { + // If there are qualifying dependents, we must have a schedule 8812 + if (f1040.qualifyingDependents.qualifyingChildren().length > 0) { + expect(f1040.schedule8812).not.toBe(undefined) + } + }) + }) + + it('should not produce line 5 with no dependents', async () => { + await withSchedule8812((f1040, s8812) => { + // If Schedule A is attached, the deduction should be greater than the standard deduction + if (s8812.l4() === 0) { + expect(s8812.l5()).toEqual(0) + } + }) + }) + + it('should show a multiple of 1000 at l10', async () => { + await withSchedule8812((f1040, s8812) => { + expect(s8812.l10() % 1000).toEqual(0) + }) + }) +}) diff --git a/src/forms/Y2023/tests/ScheduleA.test.ts b/src/forms/Y2023/tests/ScheduleA.test.ts new file mode 100644 index 000000000..dedd30564 --- /dev/null +++ b/src/forms/Y2023/tests/ScheduleA.test.ts @@ -0,0 +1,26 @@ +import { commonTests } from '.' +import * as fc from 'fast-check' + +describe('ScheduleA', () => { + it('should make deduction > standard deduction if Schedule A is attached', async () => { + await commonTests.withValid1040((f1040) => { + // If Schedule A is attached, the deduction should be greater than the standard deduction + // Cancel test if Schedule A is not attached + fc.pre(f1040.scheduleA.isNeeded()) + + expect(f1040.l12()).toBeGreaterThan(f1040.standardDeduction() ?? 0) + }) + }) + + it('should be attached if deduction is more than standard', async () => { + await commonTests.withValid1040((f1040) => { + const standardDeduction = f1040.standardDeduction() ?? 0 + + // If the deduction is more than standard, we must have a schedule A + // Note dependents of other taxpayers may still itemize. + if (f1040.l12() > standardDeduction) { + expect(f1040.scheduleA).not.toBe(undefined) + } + }) + }) +}) diff --git a/src/forms/Y2023/tests/ScheduleD.test.ts b/src/forms/Y2023/tests/ScheduleD.test.ts new file mode 100644 index 000000000..451540db1 --- /dev/null +++ b/src/forms/Y2023/tests/ScheduleD.test.ts @@ -0,0 +1,18 @@ +import * as fc from 'fast-check' +import { testKit, commonTests } from '.' + +describe('ScheduleD', () => { + it('should never pass through more than allowed losses', async () => { + await fc.assert( + testKit.with1040Property((forms): Promise => { + const f1040 = commonTests.findF1040OrFail(forms) + if (f1040.scheduleD.isNeeded()) { + expect(Math.round(f1040.l7() ?? 0)).toBeGreaterThanOrEqual( + -f1040.scheduleD.l21Min() + ) + } + return Promise.resolve() + }) + ) + }) +}) diff --git a/src/forms/Y2023/tests/ScheduleEIC.test.ts b/src/forms/Y2023/tests/ScheduleEIC.test.ts new file mode 100644 index 000000000..f64c890b0 --- /dev/null +++ b/src/forms/Y2023/tests/ScheduleEIC.test.ts @@ -0,0 +1,20 @@ +/* eslint @typescript-eslint/no-empty-function: "off" */ + +import * as federal from '../data/federal' +import { testKit, commonTests } from '.' + +beforeAll(() => jest.spyOn(console, 'warn').mockImplementation(() => {})) + +describe('ScheduleEIC', () => { + it('should disallow EIC for income below threshold', async () => { + await testKit.with1040Assert((forms): Promise => { + const f1040 = commonTests.findF1040OrFail(forms) + const formula = federal.EIC.formulas[f1040.info.taxPayer.filingStatus] + if (formula !== undefined && f1040.wages() < formula[0][1].lowerBound) { + expect(f1040.scheduleEIC.allowed()).toBe(false) + expect(f1040.scheduleEIC.credit()).toBe(0) + } + return Promise.resolve() + }) + }) +}) diff --git a/src/forms/Y2023/tests/f1040.test.ts b/src/forms/Y2023/tests/f1040.test.ts new file mode 100644 index 000000000..0c9207484 --- /dev/null +++ b/src/forms/Y2023/tests/f1040.test.ts @@ -0,0 +1,60 @@ +import { displayRound } from 'ustaxes/core/irsForms/util' +import { commonTests, testKit } from '.' + +jest.setTimeout(40000) + +beforeAll(() => { + jest.spyOn(console, 'warn').mockImplementation((x: string) => { + if (!x.includes('Removing XFA form data as pdf-lib')) { + console.warn(x) + } + }) +}) + +describe('f1040', () => { + commonTests.run() + + it('should never have higher AGI than total income', async () => { + await testKit.with1040Assert((forms): Promise => { + const f1040 = commonTests.findF1040(forms) + expect(f1040).not.toBeUndefined() + if (f1040 !== undefined) { + expect(displayRound(f1040.l11()) ?? 0).toBeLessThanOrEqual( + // It is possible for losses to create negative income. + displayRound(Math.max(0, f1040.l9())) ?? 0 + ) + } + return Promise.resolve() + }) + }) + + it('should never produce higher tax than total income', async () => { + await testKit.with1040Assert((forms): Promise => { + const f1040 = commonTests.findF1040(forms) + expect(f1040).not.toBeUndefined() + if (f1040 !== undefined) { + // Remove line 7 for AMT + expect( + displayRound(f1040.l24() - (f1040.l17() ?? 0)) ?? 0 + ).toBeLessThanOrEqual(displayRound(Math.max(0, f1040.l9())) ?? 0) + } + return Promise.resolve() + }) + }) + + it('should never produce tax on taxable income higher than income', async () => { + await testKit.with1040Assert((forms): Promise => { + const f1040 = commonTests.findF1040(forms) + expect(f1040).not.toBeUndefined() + if (f1040 !== undefined) { + // tax on taxable income should be less than taxable income + if (f1040.l15() > 0) { + expect(f1040.l16() ?? 0).toBeLessThan(f1040.l15()) + } else { + expect(f1040.l16() ?? 0).toEqual(0) + } + } + return Promise.resolve() + }) + }) +}) diff --git a/src/forms/Y2023/tests/f6251.test.ts b/src/forms/Y2023/tests/f6251.test.ts new file mode 100644 index 000000000..b5dbb8b4d --- /dev/null +++ b/src/forms/Y2023/tests/f6251.test.ts @@ -0,0 +1,102 @@ +/* eslint @typescript-eslint/no-empty-function: "off" */ + +import { FilingStatus, PersonRole } from 'ustaxes/core/data' +import F1040 from '../irsForms/F1040' +import F6251 from '../irsForms/F6251' +import { cloneDeep } from 'lodash' +import { ValidatedInformation } from 'ustaxes/forms/F1040Base' +import federalBrackets, { amt } from '../data/federal' + +const baseInformation: ValidatedInformation = { + f1099s: [], + f3921s: [ + { + name: 'Stock Option', + personRole: PersonRole.PRIMARY, + exercisePricePerShare: 1, + fmv: 101, + numShares: 1000 + } + ], + credits: [], + scheduleK1Form1065s: [], + itemizedDeductions: undefined, + w2s: [ + { + employer: { EIN: '111111111', employerName: 'w2s employer name' }, + personRole: PersonRole.PRIMARY, + occupation: 'w2s-occupation', + state: 'AL', + income: 100000, + medicareIncome: 0, + fedWithholding: 0, + ssWages: 100000, + ssWithholding: 0, + medicareWithholding: 0, + stateWages: 100000, + stateWithholding: 0 + } + ], + estimatedTaxes: [], + realEstate: [], + taxPayer: { + primaryPerson: { + address: { + address: '0001', + aptNo: '', + city: 'AR city', + state: 'AR', + zip: '1234567' + }, + firstName: 'payer-first-name', + lastName: 'payer-last-name', + isTaxpayerDependent: false, + role: PersonRole.PRIMARY, + ssid: '111111111', + dateOfBirth: new Date('01/01/1970'), + isBlind: false + }, + spouse: undefined, + dependents: [], + filingStatus: FilingStatus.S + }, + questions: {}, + f1098es: [], + stateResidencies: [{ state: 'AL' }], + healthSavingsAccounts: [], + individualRetirementArrangements: [] +} + +describe('AMT', () => { + it('stock options should trigger AMT', () => { + const information = cloneDeep(baseInformation) + const income = baseInformation.w2s[0].income + const f1040 = new F1040(information, []) + const f6251 = new F6251(f1040) + expect(f6251.isNeeded()).toEqual(true) + expect(Math.round(f6251.l1() ?? 0)).toEqual( + income - + federalBrackets.ordinary.status[FilingStatus.S].deductions[0].amount + ) + expect(Math.round(f6251.l7() ?? 0)).toEqual( + (income + + 100000 - + (amt.excemption(FilingStatus.S, income + 100000) ?? 0)) * + 0.26 + ) + expect(Math.round(f6251.l10())).toEqual(Math.round(f1040.l16() ?? 0)) + expect(Math.round(f6251.l11())).toEqual( + Math.round(f6251.l9() - f6251.l10()) + ) + }) + + it('small stock options should NOT trigger AMT', () => { + const information = cloneDeep(baseInformation) + information.f3921s[0].exercisePricePerShare = 100 + + const f1040 = new F1040(information, []) + const f6251 = new F6251(f1040) + expect(f6251.isNeeded()).toEqual(false) + expect(Math.round(f6251.l11())).toEqual(0) + }) +}) diff --git a/src/forms/Y2023/tests/fica.test.ts b/src/forms/Y2023/tests/fica.test.ts new file mode 100644 index 000000000..3c93353f4 --- /dev/null +++ b/src/forms/Y2023/tests/fica.test.ts @@ -0,0 +1,268 @@ +import { fica } from '../data/federal' +import F1040 from '../irsForms/F1040' +import F8959 from '../irsForms/F8959' +import Form from 'ustaxes/core/irsForms/Form' +import Schedule2 from '../irsForms/Schedule2' +import Schedule3 from '../irsForms/Schedule3' +import { displayRound } from 'ustaxes/core/irsForms/util' +import { testKit, commonTests } from '.' +import { FilingStatus, IncomeW2, PersonRole } from 'ustaxes/core/data' +import { run } from 'ustaxes/core/util' +import { blankState } from 'ustaxes/redux/reducer' +import { ValidatedInformation } from 'ustaxes/forms/F1040Base' +import * as fc from 'fast-check' + +jest.setTimeout(10000) + +beforeAll(() => { + jest.spyOn(console, 'warn').mockImplementation(() => { + // do nothing + }) +}) + +const sampleW2: IncomeW2 = { + employer: { EIN: '111111111', employerName: 'w2s employer name' }, + personRole: PersonRole.PRIMARY, + occupation: 'w2s-occupation', + state: 'AL', + income: 111, + medicareIncome: 222, + fedWithholding: 333, + ssWages: 111, + ssWithholding: fica.maxSSTax, + medicareWithholding: 555, + stateWages: 666, + stateWithholding: 777 +} + +const sampleInfo: ValidatedInformation = { + ...blankState, + taxPayer: { + dependents: [], + filingStatus: FilingStatus.MFJ, + primaryPerson: { + address: { + address: '', + city: '' + }, + firstName: '', + isTaxpayerDependent: false, + lastName: '', + role: PersonRole.PRIMARY, + ssid: '', + isBlind: false, + dateOfBirth: new Date('2000-01-01') + }, + spouse: { + firstName: '', + isTaxpayerDependent: false, + lastName: '', + role: PersonRole.SPOUSE, + ssid: '', + isBlind: false, + dateOfBirth: new Date('2000-01-01') + } + } +} + +const hasSSRefund = (f1040: F1040): boolean => f1040.schedule3.l11() > 0 + +function hasAdditionalMedicareTax(f1040: F1040): boolean { + const medicareTax = f1040.f8959.l18() + return medicareTax > 0 +} + +/* eslint-disable @typescript-eslint/no-explicit-any */ +type Constructor = new (...args: any[]) => T +function hasAttachment( + attachments: Form[], + formType: Constructor +): boolean { + return ( + attachments.find((f) => { + return f instanceof formType + }) !== undefined + ) +} + +describe('fica', () => { + it('should give refund SS tax overpayment only in some conditions', async () => { + await testKit.with1040Assert((forms) => { + const f1040 = commonTests.findF1040OrFail(forms) + if (f1040.validW2s().length <= 1) { + // Should never give SS refund with 1 or fewer W2s + expect(hasSSRefund(f1040)).toEqual(false) + } else { + const ssWithheld = f1040 + .validW2s() + .map((w2) => w2.ssWithholding) + .reduce((l, r) => l + r, 0) + if ( + f1040.wages() <= fica.maxIncomeSSTaxApplies || + f1040.validW2s().some((w2) => w2.ssWithholding > fica.maxSSTax) || + ssWithheld < fica.maxSSTax + ) { + // Should never give SS refund if W2 income below max threshold, some W2 has + // withheld over the max, or there is no SS withholding to refund. + expect(hasSSRefund(f1040)).toEqual(false) + } else { + // Otherwise, should always give SS refund, and attach schedule 3 + expect(hasSSRefund(f1040)).toEqual(true) + expect(hasAttachment(forms, Schedule3)).toEqual(true) + } + } + return Promise.resolve() + }) + }) + + it('should give SS refund based on filing status', async () => { + await testKit.with1040Assert((forms) => { + const f1040 = commonTests.findF1040OrFail(forms) + if (hasSSRefund(f1040)) { + const ssRefund = f1040.schedule3.l11() + expect(displayRound(ssRefund)).not.toBeUndefined() + expect(ssRefund).toBeGreaterThan(0) + + const ssWithheld = + f1040 + .validW2s() + .filter((w2) => w2.personRole == PersonRole.PRIMARY) + .map((w2) => w2.ssWithholding) + .reduce((l, r) => l + r, 0) + + f1040 + .validW2s() + .filter((w2) => w2.personRole == PersonRole.SPOUSE) + .map((w2) => w2.ssWithholding) + .reduce((l, r) => l + r, 0) + + expect(ssRefund).toEqual(ssWithheld - fica.maxSSTax) + } else { + fc.pre(false) + } + + return Promise.resolve() + }) + }) + + it('should not give a refund if each person has less than the max', () => { + const testInfo: ValidatedInformation = { + ...sampleInfo, + w2s: [ + { + ...sampleW2, + personRole: PersonRole.SPOUSE, + ssWithholding: fica.maxSSTax + }, + { + ...sampleW2, + personRole: PersonRole.PRIMARY, + ssWithholding: fica.maxSSTax + } + ] + } + + const f1040 = run(testKit.builder.build(testInfo, []).f1040()) + .map(commonTests.findF1040OrFail) + .orThrow() + + expect(f1040.schedule3.claimableExcessSSTaxWithholding()).toEqual(0) + }) + + it('should give a refund if a person has more than the max if they have two w2s', () => { + const testInfo: ValidatedInformation = { + ...sampleInfo, + w2s: [ + { + ...sampleW2, + personRole: PersonRole.SPOUSE, + ssWithholding: fica.maxSSTax + }, + { + ...sampleW2, + personRole: PersonRole.SPOUSE, + // This person has already contributed to the max for their other w2 so the refund should equal this amount + ssWithholding: 1000 + }, + { + ...sampleW2, + personRole: PersonRole.PRIMARY, + ssWithholding: fica.maxSSTax + } + ] + } + + const f1040 = run(testKit.builder.build(testInfo, []).f1040()) + .map(commonTests.findF1040OrFail) + .orThrow() + expect(f1040.schedule3.claimableExcessSSTaxWithholding()).toEqual(1000) + }) + + it('should add Additional Medicare Tax form 8959', async () => { + await testKit.with1040Assert((forms): Promise => { + const f1040 = commonTests.findF1040OrFail(forms) + const filingStatus = f1040.info.taxPayer.filingStatus + // Should add Additional Medicare Tax if medicare wages over threshold + if ( + f1040.medicareWages() > + fica.additionalMedicareTaxThreshold(filingStatus) + ) { + expect(hasAdditionalMedicareTax(f1040)).toEqual(true) + + // Should attach both S2 and F8959 to return + expect(hasAttachment(forms, Schedule2)).toEqual(true) + expect(hasAttachment(forms, F8959)).toEqual(true) + } else { + const selfEmploymentWages = f1040.scheduleSE.l6() ?? 0 + const hasTax = + f1040.medicareWages() + selfEmploymentWages > + fica.additionalMedicareTaxThreshold(filingStatus) + expect(hasAdditionalMedicareTax(f1040)).toEqual(hasTax) + expect(hasAttachment(forms, F8959)).toEqual(hasTax) + } + return Promise.resolve() + }) + }) + + it('should add Additional Medicare Tax based on filing status', async () => { + await testKit.with1040Assert(async (forms): Promise => { + const f1040 = commonTests.findF1040OrFail(forms) + if (hasAdditionalMedicareTax(f1040)) { + const filingStatus = f1040.info.taxPayer.filingStatus + const selfEmploymentWages = f1040.scheduleSE.l6() ?? 0 + const incomeOverThreshold = + f1040.medicareWages() + + selfEmploymentWages - + fica.additionalMedicareTaxThreshold(filingStatus) + expect(incomeOverThreshold).toBeGreaterThan(0) + + // Adds the right amount of additional tax + const s2l8 = f1040.f8959.l18() + expect(s2l8).not.toBeUndefined() + expect(Math.round(s2l8)).toEqual( + Math.round(incomeOverThreshold * fica.additionalMedicareTaxRate) + ) + + // Also adds in the extra Medicare tax withheld to 1040 taxes already paid + const medicareWithheld = f1040 + .validW2s() + .map((w2) => w2.medicareWithholding) + .reduce((l, r) => l + r, 0) + + const regularWithholding = + fica.regularMedicareTaxRate * f1040.medicareWages() + + if (medicareWithheld > regularWithholding) { + const f1040l25c = f1040.l25c() + expect(f1040l25c).not.toBeUndefined() + const additionalWithheld = medicareWithheld - regularWithholding + expect(displayRound(f1040l25c ?? 0)).toEqual( + displayRound(additionalWithheld) + ) + } else { + expect(displayRound(f1040.l25c() ?? 0) ?? 0).toEqual(0) + } + } + return Promise.resolve() + }) + }) +}) diff --git a/src/forms/Y2023/tests/index.ts b/src/forms/Y2023/tests/index.ts new file mode 100644 index 000000000..944992ab0 --- /dev/null +++ b/src/forms/Y2023/tests/index.ts @@ -0,0 +1,15 @@ +import CommonTests, { FormTestInfo } from 'ustaxes/forms/tests/CommonTests' +import TestKit from 'ustaxes/forms/tests/TestKit' +import F1040 from '../irsForms/F1040' + +export const testKit = new TestKit('Y2023') + +class FormTestInfo2023 extends FormTestInfo { + getAssets = (f1040: F1040) => f1040.assets + getInfo = (f1040: F1040) => f1040.info +} + +export const commonTests = new CommonTests( + testKit, + new FormTestInfo2023() +) diff --git a/src/forms/Y2023/tests/taxRates.test.ts b/src/forms/Y2023/tests/taxRates.test.ts new file mode 100644 index 000000000..21b1596d5 --- /dev/null +++ b/src/forms/Y2023/tests/taxRates.test.ts @@ -0,0 +1,126 @@ +import { FilingStatus } from 'ustaxes/core/data' +import { CURRENT_YEAR } from '../data/federal' +import { computeOrdinaryTax } from '../irsForms/TaxTable' +import fs from 'fs/promises' +import { parseCsvOrThrow } from 'ustaxes/data/csvImport' + +const getTaxTable = async (): Promise => { + const path = './src/forms/Y2023/tests/taxTable.csv' + const taxTableCsv = (await fs.readFile(path)).toString('utf-8') + return parseCsvOrThrow(taxTableCsv, (r: string[], rowNum) => + // ignore heading row. + rowNum > 0 ? [r.map((s) => Number(s))] : [] + ) +} + +const expectTax = (status: FilingStatus, amount: number, tax: number) => { + const computedTax = Math.round(computeOrdinaryTax(status, amount)) + expect(computedTax).toEqual(tax) +} + +const expectTaxUnder100KRange = ( + status: FilingStatus, + min: number, + max: number, + tax: number +) => { + const diff = max - min + const quarter = Math.round((diff / 4) * 100) / 100 + expectTax(status, min, tax) + expectTax(status, min + quarter, tax) + expectTax(status, min + 2 * quarter, tax) + expectTax(status, min + 3 * quarter, tax) + expectTax(status, max, tax) +} + +describe('Tax rates', () => { + it('test should be updated for new year', () => { + // WARNING! Do not just change the year. Also update the CSV and expected tax amounts below! + expect(CURRENT_YEAR).toEqual(2023) + }) + it('ordinary taxes for single status should be correct', async () => { + const rows = await getTaxTable() + rows.forEach(([min, lessThan, tax]) => { + expectTaxUnder100KRange(FilingStatus.S, min, lessThan - 0.01, tax) + }) + + // Over $100,000 + const amounts = [ + [100000, 17400], + [182100, 37104], + [182101, 37104], + [231250, 52832], + [231251, 52832], + [578125, 174238], + [578126, 174239] + ] + amounts.forEach(([amount, tax]) => { + expectTax(FilingStatus.S, amount, tax) + }) + }) + + it('ordinary taxes for married filing jointly status should be correct', async () => { + const rows = await getTaxTable() + rows.forEach(([min, lessThan, , tax]) => { + expectTaxUnder100KRange(FilingStatus.MFJ, min, lessThan - 0.01, tax) + }) + + // Over $100,000 + const amounts = [ + [100000, 12615], + [190750, 32580], + [190751, 32580], + [364200, 74208], + [364201, 74208], + [462500, 105664], + [462501, 105664], + [693750, 186602], + [693751, 186602] + ] + amounts.forEach(([amount, tax]) => { + expectTax(FilingStatus.MFJ, amount, tax) + }) + }) + + it('ordinary taxes for married filing separately status should be correct', async () => { + const rows = await getTaxTable() + rows.forEach(([min, lessThan, , , tax]) => { + expectTaxUnder100KRange(FilingStatus.MFS, min, lessThan - 0.01, tax) + }) + + // Over $100,000 + const amounts = [ + [100000, 17400], + [182100, 37104], + [182101, 37104], + [231250, 52832], + [231251, 52832], + [346875, 93301], + [346876, 93301] + ] + amounts.forEach(([amount, tax]) => { + expectTax(FilingStatus.MFS, amount, tax) + }) + }) + + it('ordinary taxes for head of household status should be correct', async () => { + const rows = await getTaxTable() + rows.forEach(([min, lessThan, , , , tax]) => { + expectTaxUnder100KRange(FilingStatus.HOH, min, lessThan - 0.01, tax) + }) + + // Over $100,000 + const amounts = [ + [100000, 15794], + [182100, 35498], + [182101, 35498], + [231250, 51226], + [231251, 51226], + [578100, 172624], + [578101, 172624] + ] + amounts.forEach(([amount, tax]) => { + expectTax(FilingStatus.HOH, amount, tax) + }) + }) +}) diff --git a/src/forms/Y2023/tests/taxTable.csv b/src/forms/Y2023/tests/taxTable.csv new file mode 100644 index 000000000..41c55010d --- /dev/null +++ b/src/forms/Y2023/tests/taxTable.csv @@ -0,0 +1,2063 @@ +At Least,Less than,Single,Married Filing Jointly,Married Filing Separately,Head of Household +0,5,0,0,0,0 +5,15,1,1,1,1 +15,25,2,2,2,2 +25,50,4,4,4,4 +50,75,6,6,6,6 +75,100,9,9,9,9 +100,125,11,11,11,11 +125,150,14,14,14,14 +150,175,16,16,16,16 +175,200,19,19,19,19 +200,225,21,21,21,21 +225,250,24,24,24,24 +250,275,26,26,26,26 +275,300,29,29,29,29 +300,325,31,31,31,31 +325,350,34,34,34,34 +350,375,36,36,36,36 +375,400,39,39,39,39 +400,425,41,41,41,41 +425,450,44,44,44,44 +450,475,46,46,46,46 +475,500,49,49,49,49 +500,525,51,51,51,51 +525,550,54,54,54,54 +550,575,56,56,56,56 +575,600,59,59,59,59 +600,625,61,61,61,61 +625,650,64,64,64,64 +650,675,66,66,66,66 +675,700,69,69,69,69 +700,725,71,71,71,71 +725,750,74,74,74,74 +750,775,76,76,76,76 +775,800,79,79,79,79 +800,825,81,81,81,81 +825,850,84,84,84,84 +850,875,86,86,86,86 +875,900,89,89,89,89 +900,925,91,91,91,91 +925,950,94,94,94,94 +950,975,96,96,96,96 +975,1000,99,99,99,99 +1000,1025,101,101,101,101 +1025,1050,104,104,104,104 +1050,1075,106,106,106,106 +1075,1100,109,109,109,109 +1100,1125,111,111,111,111 +1125,1150,114,114,114,114 +1150,1175,116,116,116,116 +1175,1200,119,119,119,119 +1200,1225,121,121,121,121 +1225,1250,124,124,124,124 +1250,1275,126,126,126,126 +1275,1300,129,129,129,129 +1300,1325,131,131,131,131 +1325,1350,134,134,134,134 +1350,1375,136,136,136,136 +1375,1400,139,139,139,139 +1400,1425,141,141,141,141 +1425,1450,144,144,144,144 +1450,1475,146,146,146,146 +1475,1500,149,149,149,149 +1500,1525,151,151,151,151 +1525,1550,154,154,154,154 +1550,1575,156,156,156,156 +1575,1600,159,159,159,159 +1600,1625,161,161,161,161 +1625,1650,164,164,164,164 +1650,1675,166,166,166,166 +1675,1700,169,169,169,169 +1700,1725,171,171,171,171 +1725,1750,174,174,174,174 +1750,1775,176,176,176,176 +1775,1800,179,179,179,179 +1800,1825,181,181,181,181 +1825,1850,184,184,184,184 +1850,1875,186,186,186,186 +1875,1900,189,189,189,189 +1900,1925,191,191,191,191 +1925,1950,194,194,194,194 +1950,1975,196,196,196,196 +1975,2000,199,199,199,199 +2000,2025,201,201,201,201 +2025,2050,204,204,204,204 +2050,2075,206,206,206,206 +2075,2100,209,209,209,209 +2100,2125,211,211,211,211 +2125,2150,214,214,214,214 +2150,2175,216,216,216,216 +2175,2200,219,219,219,219 +2200,2225,221,221,221,221 +2225,2250,224,224,224,224 +2250,2275,226,226,226,226 +2275,2300,229,229,229,229 +2300,2325,231,231,231,231 +2325,2350,234,234,234,234 +2350,2375,236,236,236,236 +2375,2400,239,239,239,239 +2400,2425,241,241,241,241 +2425,2450,244,244,244,244 +2450,2475,246,246,246,246 +2475,2500,249,249,249,249 +2500,2525,251,251,251,251 +2525,2550,254,254,254,254 +2550,2575,256,256,256,256 +2575,2600,259,259,259,259 +2600,2625,261,261,261,261 +2625,2650,264,264,264,264 +2650,2675,266,266,266,266 +2675,2700,269,269,269,269 +2700,2725,271,271,271,271 +2725,2750,274,274,274,274 +2750,2775,276,276,276,276 +2775,2800,279,279,279,279 +2800,2825,281,281,281,281 +2825,2850,284,284,284,284 +2850,2875,286,286,286,286 +2875,2900,289,289,289,289 +2900,2925,291,291,291,291 +2925,2950,294,294,294,294 +2950,2975,296,296,296,296 +2975,3000,299,299,299,299 +3000,3050,303,303,303,303 +3050,3100,308,308,308,308 +3100,3150,313,313,313,313 +3150,3200,318,318,318,318 +3200,3250,323,323,323,323 +3250,3300,328,328,328,328 +3300,3350,333,333,333,333 +3350,3400,338,338,338,338 +3400,3450,343,343,343,343 +3450,3500,348,348,348,348 +3500,3550,353,353,353,353 +3550,3600,358,358,358,358 +3600,3650,363,363,363,363 +3650,3700,368,368,368,368 +3700,3750,373,373,373,373 +3750,3800,378,378,378,378 +3800,3850,383,383,383,383 +3850,3900,388,388,388,388 +3900,3950,393,393,393,393 +3950,4000,398,398,398,398 +4000,4050,403,403,403,403 +4050,4100,408,408,408,408 +4100,4150,413,413,413,413 +4150,4200,418,418,418,418 +4200,4250,423,423,423,423 +4250,4300,428,428,428,428 +4300,4350,433,433,433,433 +4350,4400,438,438,438,438 +4400,4450,443,443,443,443 +4450,4500,448,448,448,448 +4500,4550,453,453,453,453 +4550,4600,458,458,458,458 +4600,4650,463,463,463,463 +4650,4700,468,468,468,468 +4700,4750,473,473,473,473 +4750,4800,478,478,478,478 +4800,4850,483,483,483,483 +4850,4900,488,488,488,488 +4900,4950,493,493,493,493 +4950,5000,498,498,498,498 +5000,5050,503,503,503,503 +5050,5100,508,508,508,508 +5100,5150,513,513,513,513 +5150,5200,518,518,518,518 +5200,5250,523,523,523,523 +5250,5300,528,528,528,528 +5300,5350,533,533,533,533 +5350,5400,538,538,538,538 +5400,5450,543,543,543,543 +5450,5500,548,548,548,548 +5500,5550,553,553,553,553 +5550,5600,558,558,558,558 +5600,5650,563,563,563,563 +5650,5700,568,568,568,568 +5700,5750,573,573,573,573 +5750,5800,578,578,578,578 +5800,5850,583,583,583,583 +5850,5900,588,588,588,588 +5900,5950,593,593,593,593 +5950,6000,598,598,598,598 +6000,6050,603,603,603,603 +6050,6100,608,608,608,608 +6100,6150,613,613,613,613 +6150,6200,618,618,618,618 +6200,6250,623,623,623,623 +6250,6300,628,628,628,628 +6300,6350,633,633,633,633 +6350,6400,638,638,638,638 +6400,6450,643,643,643,643 +6450,6500,648,648,648,648 +6500,6550,653,653,653,653 +6550,6600,658,658,658,658 +6600,6650,663,663,663,663 +6650,6700,668,668,668,668 +6700,6750,673,673,673,673 +6750,6800,678,678,678,678 +6800,6850,683,683,683,683 +6850,6900,688,688,688,688 +6900,6950,693,693,693,693 +6950,7000,698,698,698,698 +7000,7050,703,703,703,703 +7050,7100,708,708,708,708 +7100,7150,713,713,713,713 +7150,7200,718,718,718,718 +7200,7250,723,723,723,723 +7250,7300,728,728,728,728 +7300,7350,733,733,733,733 +7350,7400,738,738,738,738 +7400,7450,743,743,743,743 +7450,7500,748,748,748,748 +7500,7550,753,753,753,753 +7550,7600,758,758,758,758 +7600,7650,763,763,763,763 +7650,7700,768,768,768,768 +7700,7750,773,773,773,773 +7750,7800,778,778,778,778 +7800,7850,783,783,783,783 +7850,7900,788,788,788,788 +7900,7950,793,793,793,793 +7950,8000,798,798,798,798 +8000,8050,803,803,803,803 +8050,8100,808,808,808,808 +8100,8150,813,813,813,813 +8150,8200,818,818,818,818 +8200,8250,823,823,823,823 +8250,8300,828,828,828,828 +8300,8350,833,833,833,833 +8350,8400,838,838,838,838 +8400,8450,843,843,843,843 +8450,8500,848,848,848,848 +8500,8550,853,853,853,853 +8550,8600,858,858,858,858 +8600,8650,863,863,863,863 +8650,8700,868,868,868,868 +8700,8750,873,873,873,873 +8750,8800,878,878,878,878 +8800,8850,883,883,883,883 +8850,8900,888,888,888,888 +8900,8950,893,893,893,893 +8950,9000,898,898,898,898 +9000,9050,903,903,903,903 +9050,9100,908,908,908,908 +9100,9150,913,913,913,913 +9150,9200,918,918,918,918 +9200,9250,923,923,923,923 +9250,9300,928,928,928,928 +9300,9350,933,933,933,933 +9350,9400,938,938,938,938 +9400,9450,943,943,943,943 +9450,9500,948,948,948,948 +9500,9550,953,953,953,953 +9550,9600,958,958,958,958 +9600,9650,963,963,963,963 +9650,9700,968,968,968,968 +9700,9750,973,973,973,973 +9750,9800,978,978,978,978 +9800,9850,983,983,983,983 +9850,9900,988,988,988,988 +9900,9950,993,993,993,993 +9950,10000,998,998,998,998 +10000,10050,1003,1003,1003,1003 +10050,10100,1008,1008,1008,1008 +10100,10150,1013,1013,1013,1013 +10150,10200,1018,1018,1018,1018 +10200,10250,1023,1023,1023,1023 +10250,10300,1028,1028,1028,1028 +10300,10350,1033,1033,1033,1033 +10350,10400,1038,1038,1038,1038 +10400,10450,1043,1043,1043,1043 +10450,10500,1048,1048,1048,1048 +10500,10550,1053,1053,1053,1053 +10550,10600,1058,1058,1058,1058 +10600,10650,1063,1063,1063,1063 +10650,10700,1068,1068,1068,1068 +10700,10750,1073,1073,1073,1073 +10750,10800,1078,1078,1078,1078 +10800,10850,1083,1083,1083,1083 +10850,10900,1088,1088,1088,1088 +10900,10950,1093,1093,1093,1093 +10950,11000,1098,1098,1098,1098 +11000,11050,1103,1103,1103,1103 +11050,11100,1109,1108,1109,1108 +11100,11150,1115,1113,1115,1113 +11150,11200,1121,1118,1121,1118 +11200,11250,1127,1123,1127,1123 +11250,11300,1133,1128,1133,1128 +11300,11350,1139,1133,1139,1133 +11350,11400,1145,1138,1145,1138 +11400,11450,1151,1143,1151,1143 +11450,11500,1157,1148,1157,1148 +11500,11550,1163,1153,1163,1153 +11550,11600,1169,1158,1169,1158 +11600,11650,1175,1163,1175,1163 +11650,11700,1181,1168,1181,1168 +11700,11750,1187,1173,1187,1173 +11750,11800,1193,1178,1193,1178 +11800,11850,1199,1183,1199,1183 +11850,11900,1205,1188,1205,1188 +11900,11950,1211,1193,1211,1193 +11950,12000,1217,1198,1217,1198 +12000,12050,1223,1203,1223,1203 +12050,12100,1229,1208,1229,1208 +12100,12150,1235,1213,1235,1213 +12150,12200,1241,1218,1241,1218 +12200,12250,1247,1223,1247,1223 +12250,12300,1253,1228,1253,1228 +12300,12350,1259,1233,1259,1233 +12350,12400,1265,1238,1265,1238 +12400,12450,1271,1243,1271,1243 +12450,12500,1277,1248,1277,1248 +12500,12550,1283,1253,1283,1253 +12550,12600,1289,1258,1289,1258 +12600,12650,1295,1263,1295,1263 +12650,12700,1301,1268,1301,1268 +12700,12750,1307,1273,1307,1273 +12750,12800,1313,1278,1313,1278 +12800,12850,1319,1283,1319,1283 +12850,12900,1325,1288,1325,1288 +12900,12950,1331,1293,1331,1293 +12950,13000,1337,1298,1337,1298 +13000,13050,1343,1303,1343,1303 +13050,13100,1349,1308,1349,1308 +13100,13150,1355,1313,1355,1313 +13150,13200,1361,1318,1361,1318 +13200,13250,1367,1323,1367,1323 +13250,13300,1373,1328,1373,1328 +13300,13350,1379,1333,1379,1333 +13350,13400,1385,1338,1385,1338 +13400,13450,1391,1343,1391,1343 +13450,13500,1397,1348,1397,1348 +13500,13550,1403,1353,1403,1353 +13550,13600,1409,1358,1409,1358 +13600,13650,1415,1363,1415,1363 +13650,13700,1421,1368,1421,1368 +13700,13750,1427,1373,1427,1373 +13750,13800,1433,1378,1433,1378 +13800,13850,1439,1383,1439,1383 +13850,13900,1445,1388,1445,1388 +13900,13950,1451,1393,1451,1393 +13950,14000,1457,1398,1457,1398 +14000,14050,1463,1403,1463,1403 +14050,14100,1469,1408,1469,1408 +14100,14150,1475,1413,1475,1413 +14150,14200,1481,1418,1481,1418 +14200,14250,1487,1423,1487,1423 +14250,14300,1493,1428,1493,1428 +14300,14350,1499,1433,1499,1433 +14350,14400,1505,1438,1505,1438 +14400,14450,1511,1443,1511,1443 +14450,14500,1517,1448,1517,1448 +14500,14550,1523,1453,1523,1453 +14550,14600,1529,1458,1529,1458 +14600,14650,1535,1463,1535,1463 +14650,14700,1541,1468,1541,1468 +14700,14750,1547,1473,1547,1473 +14750,14800,1553,1478,1553,1478 +14800,14850,1559,1483,1559,1483 +14850,14900,1565,1488,1565,1488 +14900,14950,1571,1493,1571,1493 +14950,15000,1577,1498,1577,1498 +15000,15050,1583,1503,1583,1503 +15050,15100,1589,1508,1589,1508 +15100,15150,1595,1513,1595,1513 +15150,15200,1601,1518,1601,1518 +15200,15250,1607,1523,1607,1523 +15250,15300,1613,1528,1613,1528 +15300,15350,1619,1533,1619,1533 +15350,15400,1625,1538,1625,1538 +15400,15450,1631,1543,1631,1543 +15450,15500,1637,1548,1637,1548 +15500,15550,1643,1553,1643,1553 +15550,15600,1649,1558,1649,1558 +15600,15650,1655,1563,1655,1563 +15650,15700,1661,1568,1661,1568 +15700,15750,1667,1573,1667,1573 +15750,15800,1673,1578,1673,1579 +15800,15850,1679,1583,1679,1585 +15850,15900,1685,1588,1685,1591 +15900,15950,1691,1593,1691,1597 +15950,16000,1697,1598,1697,1603 +16000,16050,1703,1603,1703,1609 +16050,16100,1709,1608,1709,1615 +16100,16150,1715,1613,1715,1621 +16150,16200,1721,1618,1721,1627 +16200,16250,1727,1623,1727,1633 +16250,16300,1733,1628,1733,1639 +16300,16350,1739,1633,1739,1645 +16350,16400,1745,1638,1745,1651 +16400,16450,1751,1643,1751,1657 +16450,16500,1757,1648,1757,1663 +16500,16550,1763,1653,1763,1669 +16550,16600,1769,1658,1769,1675 +16600,16650,1775,1663,1775,1681 +16650,16700,1781,1668,1781,1687 +16700,16750,1787,1673,1787,1693 +16750,16800,1793,1678,1793,1699 +16800,16850,1799,1683,1799,1705 +16850,16900,1805,1688,1805,1711 +16900,16950,1811,1693,1811,1717 +16950,17000,1817,1698,1817,1723 +17000,17050,1823,1703,1823,1729 +17050,17100,1829,1708,1829,1735 +17100,17150,1835,1713,1835,1741 +17150,17200,1841,1718,1841,1747 +17200,17250,1847,1723,1847,1753 +17250,17300,1853,1728,1853,1759 +17300,17350,1859,1733,1859,1765 +17350,17400,1865,1738,1865,1771 +17400,17450,1871,1743,1871,1777 +17450,17500,1877,1748,1877,1783 +17500,17550,1883,1753,1883,1789 +17550,17600,1889,1758,1889,1795 +17600,17650,1895,1763,1895,1801 +17650,17700,1901,1768,1901,1807 +17700,17750,1907,1773,1907,1813 +17750,17800,1913,1778,1913,1819 +17800,17850,1919,1783,1919,1825 +17850,17900,1925,1788,1925,1831 +17900,17950,1931,1793,1931,1837 +17950,18000,1937,1798,1937,1843 +18000,18050,1943,1803,1943,1849 +18050,18100,1949,1808,1949,1855 +18100,18150,1955,1813,1955,1861 +18150,18200,1961,1818,1961,1867 +18200,18250,1967,1823,1967,1873 +18250,18300,1973,1828,1973,1879 +18300,18350,1979,1833,1979,1885 +18350,18400,1985,1838,1985,1891 +18400,18450,1991,1843,1991,1897 +18450,18500,1997,1848,1997,1903 +18500,18550,2003,1853,2003,1909 +18550,18600,2009,1858,2009,1915 +18600,18650,2015,1863,2015,1921 +18650,18700,2021,1868,2021,1927 +18700,18750,2027,1873,2027,1933 +18750,18800,2033,1878,2033,1939 +18800,18850,2039,1883,2039,1945 +18850,18900,2045,1888,2045,1951 +18900,18950,2051,1893,2051,1957 +18950,19000,2057,1898,2057,1963 +19000,19050,2063,1903,2063,1969 +19050,19100,2069,1908,2069,1975 +19100,19150,2075,1913,2075,1981 +19150,19200,2081,1918,2081,1987 +19200,19250,2087,1923,2087,1993 +19250,19300,2093,1928,2093,1999 +19300,19350,2099,1933,2099,2005 +19350,19400,2105,1938,2105,2011 +19400,19450,2111,1943,2111,2017 +19450,19500,2117,1948,2117,2023 +19500,19550,2123,1953,2123,2029 +19550,19600,2129,1958,2129,2035 +19600,19650,2135,1963,2135,2041 +19650,19700,2141,1968,2141,2047 +19700,19750,2147,1973,2147,2053 +19750,19800,2153,1978,2153,2059 +19800,19850,2159,1983,2159,2065 +19850,19900,2165,1988,2165,2071 +19900,19950,2171,1993,2171,2077 +19950,20000,2177,1998,2177,2083 +20000,20050,2183,2003,2183,2089 +20050,20100,2189,2008,2189,2095 +20100,20150,2195,2013,2195,2101 +20150,20200,2201,2018,2201,2107 +20200,20250,2207,2023,2207,2113 +20250,20300,2213,2028,2213,2119 +20300,20350,2219,2033,2219,2125 +20350,20400,2225,2038,2225,2131 +20400,20450,2231,2043,2231,2137 +20450,20500,2237,2048,2237,2143 +20500,20550,2243,2053,2243,2149 +20550,20600,2249,2058,2249,2155 +20600,20650,2255,2063,2255,2161 +20650,20700,2261,2068,2261,2167 +20700,20750,2267,2073,2267,2173 +20750,20800,2273,2078,2273,2179 +20800,20850,2279,2083,2279,2185 +20850,20900,2285,2088,2285,2191 +20900,20950,2291,2093,2291,2197 +20950,21000,2297,2098,2297,2203 +21000,21050,2303,2103,2303,2209 +21050,21100,2309,2108,2309,2215 +21100,21150,2315,2113,2315,2221 +21150,21200,2321,2118,2321,2227 +21200,21250,2327,2123,2327,2233 +21250,21300,2333,2128,2333,2239 +21300,21350,2339,2133,2339,2245 +21350,21400,2345,2138,2345,2251 +21400,21450,2351,2143,2351,2257 +21450,21500,2357,2148,2357,2263 +21500,21550,2363,2153,2363,2269 +21550,21600,2369,2158,2369,2275 +21600,21650,2375,2163,2375,2281 +21650,21700,2381,2168,2381,2287 +21700,21750,2387,2173,2387,2293 +21750,21800,2393,2178,2393,2299 +21800,21850,2399,2183,2399,2305 +21850,21900,2405,2188,2405,2311 +21900,21950,2411,2193,2411,2317 +21950,22000,2417,2198,2417,2323 +22000,22050,2423,2203,2423,2329 +22050,22100,2429,2209,2429,2335 +22100,22150,2435,2215,2435,2341 +22150,22200,2441,2221,2441,2347 +22200,22250,2447,2227,2447,2353 +22250,22300,2453,2233,2453,2359 +22300,22350,2459,2239,2459,2365 +22350,22400,2465,2245,2465,2371 +22400,22450,2471,2251,2471,2377 +22450,22500,2477,2257,2477,2383 +22500,22550,2483,2263,2483,2389 +22550,22600,2489,2269,2489,2395 +22600,22650,2495,2275,2495,2401 +22650,22700,2501,2281,2501,2407 +22700,22750,2507,2287,2507,2413 +22750,22800,2513,2293,2513,2419 +22800,22850,2519,2299,2519,2425 +22850,22900,2525,2305,2525,2431 +22900,22950,2531,2311,2531,2437 +22950,23000,2537,2317,2537,2443 +23000,23050,2543,2323,2543,2449 +23050,23100,2549,2329,2549,2455 +23100,23150,2555,2335,2555,2461 +23150,23200,2561,2341,2561,2467 +23200,23250,2567,2347,2567,2473 +23250,23300,2573,2353,2573,2479 +23300,23350,2579,2359,2579,2485 +23350,23400,2585,2365,2585,2491 +23400,23450,2591,2371,2591,2497 +23450,23500,2597,2377,2597,2503 +23500,23550,2603,2383,2603,2509 +23550,23600,2609,2389,2609,2515 +23600,23650,2615,2395,2615,2521 +23650,23700,2621,2401,2621,2527 +23700,23750,2627,2407,2627,2533 +23750,23800,2633,2413,2633,2539 +23800,23850,2639,2419,2639,2545 +23850,23900,2645,2425,2645,2551 +23900,23950,2651,2431,2651,2557 +23950,24000,2657,2437,2657,2563 +24000,24050,2663,2443,2663,2569 +24050,24100,2669,2449,2669,2575 +24100,24150,2675,2455,2675,2581 +24150,24200,2681,2461,2681,2587 +24200,24250,2687,2467,2687,2593 +24250,24300,2693,2473,2693,2599 +24300,24350,2699,2479,2699,2605 +24350,24400,2705,2485,2705,2611 +24400,24450,2711,2491,2711,2617 +24450,24500,2717,2497,2717,2623 +24500,24550,2723,2503,2723,2629 +24550,24600,2729,2509,2729,2635 +24600,24650,2735,2515,2735,2641 +24650,24700,2741,2521,2741,2647 +24700,24750,2747,2527,2747,2653 +24750,24800,2753,2533,2753,2659 +24800,24850,2759,2539,2759,2665 +24850,24900,2765,2545,2765,2671 +24900,24950,2771,2551,2771,2677 +24950,25000,2777,2557,2777,2683 +25000,25050,2783,2563,2783,2689 +25050,25100,2789,2569,2789,2695 +25100,25150,2795,2575,2795,2701 +25150,25200,2801,2581,2801,2707 +25200,25250,2807,2587,2807,2713 +25250,25300,2813,2593,2813,2719 +25300,25350,2819,2599,2819,2725 +25350,25400,2825,2605,2825,2731 +25400,25450,2831,2611,2831,2737 +25450,25500,2837,2617,2837,2743 +25500,25550,2843,2623,2843,2749 +25550,25600,2849,2629,2849,2755 +25600,25650,2855,2635,2855,2761 +25650,25700,2861,2641,2861,2767 +25700,25750,2867,2647,2867,2773 +25750,25800,2873,2653,2873,2779 +25800,25850,2879,2659,2879,2785 +25850,25900,2885,2665,2885,2791 +25900,25950,2891,2671,2891,2797 +25950,26000,2897,2677,2897,2803 +26000,26050,2903,2683,2903,2809 +26050,26100,2909,2689,2909,2815 +26100,26150,2915,2695,2915,2821 +26150,26200,2921,2701,2921,2827 +26200,26250,2927,2707,2927,2833 +26250,26300,2933,2713,2933,2839 +26300,26350,2939,2719,2939,2845 +26350,26400,2945,2725,2945,2851 +26400,26450,2951,2731,2951,2857 +26450,26500,2957,2737,2957,2863 +26500,26550,2963,2743,2963,2869 +26550,26600,2969,2749,2969,2875 +26600,26650,2975,2755,2975,2881 +26650,26700,2981,2761,2981,2887 +26700,26750,2987,2767,2987,2893 +26750,26800,2993,2773,2993,2899 +26800,26850,2999,2779,2999,2905 +26850,26900,3005,2785,3005,2911 +26900,26950,3011,2791,3011,2917 +26950,27000,3017,2797,3017,2923 +27000,27050,3023,2803,3023,2929 +27050,27100,3029,2809,3029,2935 +27100,27150,3035,2815,3035,2941 +27150,27200,3041,2821,3041,2947 +27200,27250,3047,2827,3047,2953 +27250,27300,3053,2833,3053,2959 +27300,27350,3059,2839,3059,2965 +27350,27400,3065,2845,3065,2971 +27400,27450,3071,2851,3071,2977 +27450,27500,3077,2857,3077,2983 +27500,27550,3083,2863,3083,2989 +27550,27600,3089,2869,3089,2995 +27600,27650,3095,2875,3095,3001 +27650,27700,3101,2881,3101,3007 +27700,27750,3107,2887,3107,3013 +27750,27800,3113,2893,3113,3019 +27800,27850,3119,2899,3119,3025 +27850,27900,3125,2905,3125,3031 +27900,27950,3131,2911,3131,3037 +27950,28000,3137,2917,3137,3043 +28000,28050,3143,2923,3143,3049 +28050,28100,3149,2929,3149,3055 +28100,28150,3155,2935,3155,3061 +28150,28200,3161,2941,3161,3067 +28200,28250,3167,2947,3167,3073 +28250,28300,3173,2953,3173,3079 +28300,28350,3179,2959,3179,3085 +28350,28400,3185,2965,3185,3091 +28400,28450,3191,2971,3191,3097 +28450,28500,3197,2977,3197,3103 +28500,28550,3203,2983,3203,3109 +28550,28600,3209,2989,3209,3115 +28600,28650,3215,2995,3215,3121 +28650,28700,3221,3001,3221,3127 +28700,28750,3227,3007,3227,3133 +28750,28800,3233,3013,3233,3139 +28800,28850,3239,3019,3239,3145 +28850,28900,3245,3025,3245,3151 +28900,28950,3251,3031,3251,3157 +28950,29000,3257,3037,3257,3163 +29000,29050,3263,3043,3263,3169 +29050,29100,3269,3049,3269,3175 +29100,29150,3275,3055,3275,3181 +29150,29200,3281,3061,3281,3187 +29200,29250,3287,3067,3287,3193 +29250,29300,3293,3073,3293,3199 +29300,29350,3299,3079,3299,3205 +29350,29400,3305,3085,3305,3211 +29400,29450,3311,3091,3311,3217 +29450,29500,3317,3097,3317,3223 +29500,29550,3323,3103,3323,3229 +29550,29600,3329,3109,3329,3235 +29600,29650,3335,3115,3335,3241 +29650,29700,3341,3121,3341,3247 +29700,29750,3347,3127,3347,3253 +29750,29800,3353,3133,3353,3259 +29800,29850,3359,3139,3359,3265 +29850,29900,3365,3145,3365,3271 +29900,29950,3371,3151,3371,3277 +29950,30000,3377,3157,3377,3283 +30000,30050,3383,3163,3383,3289 +30050,30100,3389,3169,3389,3295 +30100,30150,3395,3175,3395,3301 +30150,30200,3401,3181,3401,3307 +30200,30250,3407,3187,3407,3313 +30250,30300,3413,3193,3413,3319 +30300,30350,3419,3199,3419,3325 +30350,30400,3425,3205,3425,3331 +30400,30450,3431,3211,3431,3337 +30450,30500,3437,3217,3437,3343 +30500,30550,3443,3223,3443,3349 +30550,30600,3449,3229,3449,3355 +30600,30650,3455,3235,3455,3361 +30650,30700,3461,3241,3461,3367 +30700,30750,3467,3247,3467,3373 +30750,30800,3473,3253,3473,3379 +30800,30850,3479,3259,3479,3385 +30850,30900,3485,3265,3485,3391 +30900,30950,3491,3271,3491,3397 +30950,31000,3497,3277,3497,3403 +31000,31050,3503,3283,3503,3409 +31050,31100,3509,3289,3509,3415 +31100,31150,3515,3295,3515,3421 +31150,31200,3521,3301,3521,3427 +31200,31250,3527,3307,3527,3433 +31250,31300,3533,3313,3533,3439 +31300,31350,3539,3319,3539,3445 +31350,31400,3545,3325,3545,3451 +31400,31450,3551,3331,3551,3457 +31450,31500,3557,3337,3557,3463 +31500,31550,3563,3343,3563,3469 +31550,31600,3569,3349,3569,3475 +31600,31650,3575,3355,3575,3481 +31650,31700,3581,3361,3581,3487 +31700,31750,3587,3367,3587,3493 +31750,31800,3593,3373,3593,3499 +31800,31850,3599,3379,3599,3505 +31850,31900,3605,3385,3605,3511 +31900,31950,3611,3391,3611,3517 +31950,32000,3617,3397,3617,3523 +32000,32050,3623,3403,3623,3529 +32050,32100,3629,3409,3629,3535 +32100,32150,3635,3415,3635,3541 +32150,32200,3641,3421,3641,3547 +32200,32250,3647,3427,3647,3553 +32250,32300,3653,3433,3653,3559 +32300,32350,3659,3439,3659,3565 +32350,32400,3665,3445,3665,3571 +32400,32450,3671,3451,3671,3577 +32450,32500,3677,3457,3677,3583 +32500,32550,3683,3463,3683,3589 +32550,32600,3689,3469,3689,3595 +32600,32650,3695,3475,3695,3601 +32650,32700,3701,3481,3701,3607 +32700,32750,3707,3487,3707,3613 +32750,32800,3713,3493,3713,3619 +32800,32850,3719,3499,3719,3625 +32850,32900,3725,3505,3725,3631 +32900,32950,3731,3511,3731,3637 +32950,33000,3737,3517,3737,3643 +33000,33050,3743,3523,3743,3649 +33050,33100,3749,3529,3749,3655 +33100,33150,3755,3535,3755,3661 +33150,33200,3761,3541,3761,3667 +33200,33250,3767,3547,3767,3673 +33250,33300,3773,3553,3773,3679 +33300,33350,3779,3559,3779,3685 +33350,33400,3785,3565,3785,3691 +33400,33450,3791,3571,3791,3697 +33450,33500,3797,3577,3797,3703 +33500,33550,3803,3583,3803,3709 +33550,33600,3809,3589,3809,3715 +33600,33650,3815,3595,3815,3721 +33650,33700,3821,3601,3821,3727 +33700,33750,3827,3607,3827,3733 +33750,33800,3833,3613,3833,3739 +33800,33850,3839,3619,3839,3745 +33850,33900,3845,3625,3845,3751 +33900,33950,3851,3631,3851,3757 +33950,34000,3857,3637,3857,3763 +34000,34050,3863,3643,3863,3769 +34050,34100,3869,3649,3869,3775 +34100,34150,3875,3655,3875,3781 +34150,34200,3881,3661,3881,3787 +34200,34250,3887,3667,3887,3793 +34250,34300,3893,3673,3893,3799 +34300,34350,3899,3679,3899,3805 +34350,34400,3905,3685,3905,3811 +34400,34450,3911,3691,3911,3817 +34450,34500,3917,3697,3917,3823 +34500,34550,3923,3703,3923,3829 +34550,34600,3929,3709,3929,3835 +34600,34650,3935,3715,3935,3841 +34650,34700,3941,3721,3941,3847 +34700,34750,3947,3727,3947,3853 +34750,34800,3953,3733,3953,3859 +34800,34850,3959,3739,3959,3865 +34850,34900,3965,3745,3965,3871 +34900,34950,3971,3751,3971,3877 +34950,35000,3977,3757,3977,3883 +35000,35050,3983,3763,3983,3889 +35050,35100,3989,3769,3989,3895 +35100,35150,3995,3775,3995,3901 +35150,35200,4001,3781,4001,3907 +35200,35250,4007,3787,4007,3913 +35250,35300,4013,3793,4013,3919 +35300,35350,4019,3799,4019,3925 +35350,35400,4025,3805,4025,3931 +35400,35450,4031,3811,4031,3937 +35450,35500,4037,3817,4037,3943 +35500,35550,4043,3823,4043,3949 +35550,35600,4049,3829,4049,3955 +35600,35650,4055,3835,4055,3961 +35650,35700,4061,3841,4061,3967 +35700,35750,4067,3847,4067,3973 +35750,35800,4073,3853,4073,3979 +35800,35850,4079,3859,4079,3985 +35850,35900,4085,3865,4085,3991 +35900,35950,4091,3871,4091,3997 +35950,36000,4097,3877,4097,4003 +36000,36050,4103,3883,4103,4009 +36050,36100,4109,3889,4109,4015 +36100,36150,4115,3895,4115,4021 +36150,36200,4121,3901,4121,4027 +36200,36250,4127,3907,4127,4033 +36250,36300,4133,3913,4133,4039 +36300,36350,4139,3919,4139,4045 +36350,36400,4145,3925,4145,4051 +36400,36450,4151,3931,4151,4057 +36450,36500,4157,3937,4157,4063 +36500,36550,4163,3943,4163,4069 +36550,36600,4169,3949,4169,4075 +36600,36650,4175,3955,4175,4081 +36650,36700,4181,3961,4181,4087 +36700,36750,4187,3967,4187,4093 +36750,36800,4193,3973,4193,4099 +36800,36850,4199,3979,4199,4105 +36850,36900,4205,3985,4205,4111 +36900,36950,4211,3991,4211,4117 +36950,37000,4217,3997,4217,4123 +37000,37050,4223,4003,4223,4129 +37050,37100,4229,4009,4229,4135 +37100,37150,4235,4015,4235,4141 +37150,37200,4241,4021,4241,4147 +37200,37250,4247,4027,4247,4153 +37250,37300,4253,4033,4253,4159 +37300,37350,4259,4039,4259,4165 +37350,37400,4265,4045,4265,4171 +37400,37450,4271,4051,4271,4177 +37450,37500,4277,4057,4277,4183 +37500,37550,4283,4063,4283,4189 +37550,37600,4289,4069,4289,4195 +37600,37650,4295,4075,4295,4201 +37650,37700,4301,4081,4301,4207 +37700,37750,4307,4087,4307,4213 +37750,37800,4313,4093,4313,4219 +37800,37850,4319,4099,4319,4225 +37850,37900,4325,4105,4325,4231 +37900,37950,4331,4111,4331,4237 +37950,38000,4337,4117,4337,4243 +38000,38050,4343,4123,4343,4249 +38050,38100,4349,4129,4349,4255 +38100,38150,4355,4135,4355,4261 +38150,38200,4361,4141,4361,4267 +38200,38250,4367,4147,4367,4273 +38250,38300,4373,4153,4373,4279 +38300,38350,4379,4159,4379,4285 +38350,38400,4385,4165,4385,4291 +38400,38450,4391,4171,4391,4297 +38450,38500,4397,4177,4397,4303 +38500,38550,4403,4183,4403,4309 +38550,38600,4409,4189,4409,4315 +38600,38650,4415,4195,4415,4321 +38650,38700,4421,4201,4421,4327 +38700,38750,4427,4207,4427,4333 +38750,38800,4433,4213,4433,4339 +38800,38850,4439,4219,4439,4345 +38850,38900,4445,4225,4445,4351 +38900,38950,4451,4231,4451,4357 +38950,39000,4457,4237,4457,4363 +39000,39050,4463,4243,4463,4369 +39050,39100,4469,4249,4469,4375 +39100,39150,4475,4255,4475,4381 +39150,39200,4481,4261,4481,4387 +39200,39250,4487,4267,4487,4393 +39250,39300,4493,4273,4493,4399 +39300,39350,4499,4279,4499,4405 +39350,39400,4505,4285,4505,4411 +39400,39450,4511,4291,4511,4417 +39450,39500,4517,4297,4517,4423 +39500,39550,4523,4303,4523,4429 +39550,39600,4529,4309,4529,4435 +39600,39650,4535,4315,4535,4441 +39650,39700,4541,4321,4541,4447 +39700,39750,4547,4327,4547,4453 +39750,39800,4553,4333,4553,4459 +39800,39850,4559,4339,4559,4465 +39850,39900,4565,4345,4565,4471 +39900,39950,4571,4351,4571,4477 +39950,40000,4577,4357,4577,4483 +40000,40050,4583,4363,4583,4489 +40050,40100,4589,4369,4589,4495 +40100,40150,4595,4375,4595,4501 +40150,40200,4601,4381,4601,4507 +40200,40250,4607,4387,4607,4513 +40250,40300,4613,4393,4613,4519 +40300,40350,4619,4399,4619,4525 +40350,40400,4625,4405,4625,4531 +40400,40450,4631,4411,4631,4537 +40450,40500,4637,4417,4637,4543 +40500,40550,4643,4423,4643,4549 +40550,40600,4649,4429,4649,4555 +40600,40650,4655,4435,4655,4561 +40650,40700,4661,4441,4661,4567 +40700,40750,4667,4447,4667,4573 +40750,40800,4673,4453,4673,4579 +40800,40850,4679,4459,4679,4585 +40850,40900,4685,4465,4685,4591 +40900,40950,4691,4471,4691,4597 +40950,41000,4697,4477,4697,4603 +41000,41050,4703,4483,4703,4609 +41050,41100,4709,4489,4709,4615 +41100,41150,4715,4495,4715,4621 +41150,41200,4721,4501,4721,4627 +41200,41250,4727,4507,4727,4633 +41250,41300,4733,4513,4733,4639 +41300,41350,4739,4519,4739,4645 +41350,41400,4745,4525,4745,4651 +41400,41450,4751,4531,4751,4657 +41450,41500,4757,4537,4757,4663 +41500,41550,4763,4543,4763,4669 +41550,41600,4769,4549,4769,4675 +41600,41650,4775,4555,4775,4681 +41650,41700,4781,4561,4781,4687 +41700,41750,4787,4567,4787,4693 +41750,41800,4793,4573,4793,4699 +41800,41850,4799,4579,4799,4705 +41850,41900,4805,4585,4805,4711 +41900,41950,4811,4591,4811,4717 +41950,42000,4817,4597,4817,4723 +42000,42050,4823,4603,4823,4729 +42050,42100,4829,4609,4829,4735 +42100,42150,4835,4615,4835,4741 +42150,42200,4841,4621,4841,4747 +42200,42250,4847,4627,4847,4753 +42250,42300,4853,4633,4853,4759 +42300,42350,4859,4639,4859,4765 +42350,42400,4865,4645,4865,4771 +42400,42450,4871,4651,4871,4777 +42450,42500,4877,4657,4877,4783 +42500,42550,4883,4663,4883,4789 +42550,42600,4889,4669,4889,4795 +42600,42650,4895,4675,4895,4801 +42650,42700,4901,4681,4901,4807 +42700,42750,4907,4687,4907,4813 +42750,42800,4913,4693,4913,4819 +42800,42850,4919,4699,4919,4825 +42850,42900,4925,4705,4925,4831 +42900,42950,4931,4711,4931,4837 +42950,43000,4937,4717,4937,4843 +43000,43050,4943,4723,4943,4849 +43050,43100,4949,4729,4949,4855 +43100,43150,4955,4735,4955,4861 +43150,43200,4961,4741,4961,4867 +43200,43250,4967,4747,4967,4873 +43250,43300,4973,4753,4973,4879 +43300,43350,4979,4759,4979,4885 +43350,43400,4985,4765,4985,4891 +43400,43450,4991,4771,4991,4897 +43450,43500,4997,4777,4997,4903 +43500,43550,5003,4783,5003,4909 +43550,43600,5009,4789,5009,4915 +43600,43650,5015,4795,5015,4921 +43650,43700,5021,4801,5021,4927 +43700,43750,5027,4807,5027,4933 +43750,43800,5033,4813,5033,4939 +43800,43850,5039,4819,5039,4945 +43850,43900,5045,4825,5045,4951 +43900,43950,5051,4831,5051,4957 +43950,44000,5057,4837,5057,4963 +44000,44050,5063,4843,5063,4969 +44050,44100,5069,4849,5069,4975 +44100,44150,5075,4855,5075,4981 +44150,44200,5081,4861,5081,4987 +44200,44250,5087,4867,5087,4993 +44250,44300,5093,4873,5093,4999 +44300,44350,5099,4879,5099,5005 +44350,44400,5105,4885,5105,5011 +44400,44450,5111,4891,5111,5017 +44450,44500,5117,4897,5117,5023 +44500,44550,5123,4903,5123,5029 +44550,44600,5129,4909,5129,5035 +44600,44650,5135,4915,5135,5041 +44650,44700,5141,4921,5141,5047 +44700,44750,5147,4927,5147,5053 +44750,44800,5158,4933,5158,5059 +44800,44850,5169,4939,5169,5065 +44850,44900,5180,4945,5180,5071 +44900,44950,5191,4951,5191,5077 +44950,45000,5202,4957,5202,5083 +45000,45050,5213,4963,5213,5089 +45050,45100,5224,4969,5224,5095 +45100,45150,5235,4975,5235,5101 +45150,45200,5246,4981,5246,5107 +45200,45250,5257,4987,5257,5113 +45250,45300,5268,4993,5268,5119 +45300,45350,5279,4999,5279,5125 +45350,45400,5290,5005,5290,5131 +45400,45450,5301,5011,5301,5137 +45450,45500,5312,5017,5312,5143 +45500,45550,5323,5023,5323,5149 +45550,45600,5334,5029,5334,5155 +45600,45650,5345,5035,5345,5161 +45650,45700,5356,5041,5356,5167 +45700,45750,5367,5047,5367,5173 +45750,45800,5378,5053,5378,5179 +45800,45850,5389,5059,5389,5185 +45850,45900,5400,5065,5400,5191 +45900,45950,5411,5071,5411,5197 +45950,46000,5422,5077,5422,5203 +46000,46050,5433,5083,5433,5209 +46050,46100,5444,5089,5444,5215 +46100,46150,5455,5095,5455,5221 +46150,46200,5466,5101,5466,5227 +46200,46250,5477,5107,5477,5233 +46250,46300,5488,5113,5488,5239 +46300,46350,5499,5119,5499,5245 +46350,46400,5510,5125,5510,5251 +46400,46450,5521,5131,5521,5257 +46450,46500,5532,5137,5532,5263 +46500,46550,5543,5143,5543,5269 +46550,46600,5554,5149,5554,5275 +46600,46650,5565,5155,5565,5281 +46650,46700,5576,5161,5576,5287 +46700,46750,5587,5167,5587,5293 +46750,46800,5598,5173,5598,5299 +46800,46850,5609,5179,5609,5305 +46850,46900,5620,5185,5620,5311 +46900,46950,5631,5191,5631,5317 +46950,47000,5642,5197,5642,5323 +47000,47050,5653,5203,5653,5329 +47050,47100,5664,5209,5664,5335 +47100,47150,5675,5215,5675,5341 +47150,47200,5686,5221,5686,5347 +47200,47250,5697,5227,5697,5353 +47250,47300,5708,5233,5708,5359 +47300,47350,5719,5239,5719,5365 +47350,47400,5730,5245,5730,5371 +47400,47450,5741,5251,5741,5377 +47450,47500,5752,5257,5752,5383 +47500,47550,5763,5263,5763,5389 +47550,47600,5774,5269,5774,5395 +47600,47650,5785,5275,5785,5401 +47650,47700,5796,5281,5796,5407 +47700,47750,5807,5287,5807,5413 +47750,47800,5818,5293,5818,5419 +47800,47850,5829,5299,5829,5425 +47850,47900,5840,5305,5840,5431 +47900,47950,5851,5311,5851,5437 +47950,48000,5862,5317,5862,5443 +48000,48050,5873,5323,5873,5449 +48050,48100,5884,5329,5884,5455 +48100,48150,5895,5335,5895,5461 +48150,48200,5906,5341,5906,5467 +48200,48250,5917,5347,5917,5473 +48250,48300,5928,5353,5928,5479 +48300,48350,5939,5359,5939,5485 +48350,48400,5950,5365,5950,5491 +48400,48450,5961,5371,5961,5497 +48450,48500,5972,5377,5972,5503 +48500,48550,5983,5383,5983,5509 +48550,48600,5994,5389,5994,5515 +48600,48650,6005,5395,6005,5521 +48650,48700,6016,5401,6016,5527 +48700,48750,6027,5407,6027,5533 +48750,48800,6038,5413,6038,5539 +48800,48850,6049,5419,6049,5545 +48850,48900,6060,5425,6060,5551 +48900,48950,6071,5431,6071,5557 +48950,49000,6082,5437,6082,5563 +49000,49050,6093,5443,6093,5569 +49050,49100,6104,5449,6104,5575 +49100,49150,6115,5455,6115,5581 +49150,49200,6126,5461,6126,5587 +49200,49250,6137,5467,6137,5593 +49250,49300,6148,5473,6148,5599 +49300,49350,6159,5479,6159,5605 +49350,49400,6170,5485,6170,5611 +49400,49450,6181,5491,6181,5617 +49450,49500,6192,5497,6192,5623 +49500,49550,6203,5503,6203,5629 +49550,49600,6214,5509,6214,5635 +49600,49650,6225,5515,6225,5641 +49650,49700,6236,5521,6236,5647 +49700,49750,6247,5527,6247,5653 +49750,49800,6258,5533,6258,5659 +49800,49850,6269,5539,6269,5665 +49850,49900,6280,5545,6280,5671 +49900,49950,6291,5551,6291,5677 +49950,50000,6302,5557,6302,5683 +50000,50050,6313,5563,6313,5689 +50050,50100,6324,5569,6324,5695 +50100,50150,6335,5575,6335,5701 +50150,50200,6346,5581,6346,5707 +50200,50250,6357,5587,6357,5713 +50250,50300,6368,5593,6368,5719 +50300,50350,6379,5599,6379,5725 +50350,50400,6390,5605,6390,5731 +50400,50450,6401,5611,6401,5737 +50450,50500,6412,5617,6412,5743 +50500,50550,6423,5623,6423,5749 +50550,50600,6434,5629,6434,5755 +50600,50650,6445,5635,6445,5761 +50650,50700,6456,5641,6456,5767 +50700,50750,6467,5647,6467,5773 +50750,50800,6478,5653,6478,5779 +50800,50850,6489,5659,6489,5785 +50850,50900,6500,5665,6500,5791 +50900,50950,6511,5671,6511,5797 +50950,51000,6522,5677,6522,5803 +51000,51050,6533,5683,6533,5809 +51050,51100,6544,5689,6544,5815 +51100,51150,6555,5695,6555,5821 +51150,51200,6566,5701,6566,5827 +51200,51250,6577,5707,6577,5833 +51250,51300,6588,5713,6588,5839 +51300,51350,6599,5719,6599,5845 +51350,51400,6610,5725,6610,5851 +51400,51450,6621,5731,6621,5857 +51450,51500,6632,5737,6632,5863 +51500,51550,6643,5743,6643,5869 +51550,51600,6654,5749,6654,5875 +51600,51650,6665,5755,6665,5881 +51650,51700,6676,5761,6676,5887 +51700,51750,6687,5767,6687,5893 +51750,51800,6698,5773,6698,5899 +51800,51850,6709,5779,6709,5905 +51850,51900,6720,5785,6720,5911 +51900,51950,6731,5791,6731,5917 +51950,52000,6742,5797,6742,5923 +52000,52050,6753,5803,6753,5929 +52050,52100,6764,5809,6764,5935 +52100,52150,6775,5815,6775,5941 +52150,52200,6786,5821,6786,5947 +52200,52250,6797,5827,6797,5953 +52250,52300,6808,5833,6808,5959 +52300,52350,6819,5839,6819,5965 +52350,52400,6830,5845,6830,5971 +52400,52450,6841,5851,6841,5977 +52450,52500,6852,5857,6852,5983 +52500,52550,6863,5863,6863,5989 +52550,52600,6874,5869,6874,5995 +52600,52650,6885,5875,6885,6001 +52650,52700,6896,5881,6896,6007 +52700,52750,6907,5887,6907,6013 +52750,52800,6918,5893,6918,6019 +52800,52850,6929,5899,6929,6025 +52850,52900,6940,5905,6940,6031 +52900,52950,6951,5911,6951,6037 +52950,53000,6962,5917,6962,6043 +53000,53050,6973,5923,6973,6049 +53050,53100,6984,5929,6984,6055 +53100,53150,6995,5935,6995,6061 +53150,53200,7006,5941,7006,6067 +53200,53250,7017,5947,7017,6073 +53250,53300,7028,5953,7028,6079 +53300,53350,7039,5959,7039,6085 +53350,53400,7050,5965,7050,6091 +53400,53450,7061,5971,7061,6097 +53450,53500,7072,5977,7072,6103 +53500,53550,7083,5983,7083,6109 +53550,53600,7094,5989,7094,6115 +53600,53650,7105,5995,7105,6121 +53650,53700,7116,6001,7116,6127 +53700,53750,7127,6007,7127,6133 +53750,53800,7138,6013,7138,6139 +53800,53850,7149,6019,7149,6145 +53850,53900,7160,6025,7160,6151 +53900,53950,7171,6031,7171,6157 +53950,54000,7182,6037,7182,6163 +54000,54050,7193,6043,7193,6169 +54050,54100,7204,6049,7204,6175 +54100,54150,7215,6055,7215,6181 +54150,54200,7226,6061,7226,6187 +54200,54250,7237,6067,7237,6193 +54250,54300,7248,6073,7248,6199 +54300,54350,7259,6079,7259,6205 +54350,54400,7270,6085,7270,6211 +54400,54450,7281,6091,7281,6217 +54450,54500,7292,6097,7292,6223 +54500,54550,7303,6103,7303,6229 +54550,54600,7314,6109,7314,6235 +54600,54650,7325,6115,7325,6241 +54650,54700,7336,6121,7336,6247 +54700,54750,7347,6127,7347,6253 +54750,54800,7358,6133,7358,6259 +54800,54850,7369,6139,7369,6265 +54850,54900,7380,6145,7380,6271 +54900,54950,7391,6151,7391,6277 +54950,55000,7402,6157,7402,6283 +55000,55050,7413,6163,7413,6289 +55050,55100,7424,6169,7424,6295 +55100,55150,7435,6175,7435,6301 +55150,55200,7446,6181,7446,6307 +55200,55250,7457,6187,7457,6313 +55250,55300,7468,6193,7468,6319 +55300,55350,7479,6199,7479,6325 +55350,55400,7490,6205,7490,6331 +55400,55450,7501,6211,7501,6337 +55450,55500,7512,6217,7512,6343 +55500,55550,7523,6223,7523,6349 +55550,55600,7534,6229,7534,6355 +55600,55650,7545,6235,7545,6361 +55650,55700,7556,6241,7556,6367 +55700,55750,7567,6247,7567,6373 +55750,55800,7578,6253,7578,6379 +55800,55850,7589,6259,7589,6385 +55850,55900,7600,6265,7600,6391 +55900,55950,7611,6271,7611,6397 +55950,56000,7622,6277,7622,6403 +56000,56050,7633,6283,7633,6409 +56050,56100,7644,6289,7644,6415 +56100,56150,7655,6295,7655,6421 +56150,56200,7666,6301,7666,6427 +56200,56250,7677,6307,7677,6433 +56250,56300,7688,6313,7688,6439 +56300,56350,7699,6319,7699,6445 +56350,56400,7710,6325,7710,6451 +56400,56450,7721,6331,7721,6457 +56450,56500,7732,6337,7732,6463 +56500,56550,7743,6343,7743,6469 +56550,56600,7754,6349,7754,6475 +56600,56650,7765,6355,7765,6481 +56650,56700,7776,6361,7776,6487 +56700,56750,7787,6367,7787,6493 +56750,56800,7798,6373,7798,6499 +56800,56850,7809,6379,7809,6505 +56850,56900,7820,6385,7820,6511 +56900,56950,7831,6391,7831,6517 +56950,57000,7842,6397,7842,6523 +57000,57050,7853,6403,7853,6529 +57050,57100,7864,6409,7864,6535 +57100,57150,7875,6415,7875,6541 +57150,57200,7886,6421,7886,6547 +57200,57250,7897,6427,7897,6553 +57250,57300,7908,6433,7908,6559 +57300,57350,7919,6439,7919,6565 +57350,57400,7930,6445,7930,6571 +57400,57450,7941,6451,7941,6577 +57450,57500,7952,6457,7952,6583 +57500,57550,7963,6463,7963,6589 +57550,57600,7974,6469,7974,6595 +57600,57650,7985,6475,7985,6601 +57650,57700,7996,6481,7996,6607 +57700,57750,8007,6487,8007,6613 +57750,57800,8018,6493,8018,6619 +57800,57850,8029,6499,8029,6625 +57850,57900,8040,6505,8040,6631 +57900,57950,8051,6511,8051,6637 +57950,58000,8062,6517,8062,6643 +58000,58050,8073,6523,8073,6649 +58050,58100,8084,6529,8084,6655 +58100,58150,8095,6535,8095,6661 +58150,58200,8106,6541,8106,6667 +58200,58250,8117,6547,8117,6673 +58250,58300,8128,6553,8128,6679 +58300,58350,8139,6559,8139,6685 +58350,58400,8150,6565,8150,6691 +58400,58450,8161,6571,8161,6697 +58450,58500,8172,6577,8172,6703 +58500,58550,8183,6583,8183,6709 +58550,58600,8194,6589,8194,6715 +58600,58650,8205,6595,8205,6721 +58650,58700,8216,6601,8216,6727 +58700,58750,8227,6607,8227,6733 +58750,58800,8238,6613,8238,6739 +58800,58850,8249,6619,8249,6745 +58850,58900,8260,6625,8260,6751 +58900,58950,8271,6631,8271,6757 +58950,59000,8282,6637,8282,6763 +59000,59050,8293,6643,8293,6769 +59050,59100,8304,6649,8304,6775 +59100,59150,8315,6655,8315,6781 +59150,59200,8326,6661,8326,6787 +59200,59250,8337,6667,8337,6793 +59250,59300,8348,6673,8348,6799 +59300,59350,8359,6679,8359,6805 +59350,59400,8370,6685,8370,6811 +59400,59450,8381,6691,8381,6817 +59450,59500,8392,6697,8392,6823 +59500,59550,8403,6703,8403,6829 +59550,59600,8414,6709,8414,6835 +59600,59650,8425,6715,8425,6841 +59650,59700,8436,6721,8436,6847 +59700,59750,8447,6727,8447,6853 +59750,59800,8458,6733,8458,6859 +59800,59850,8469,6739,8469,6865 +59850,59900,8480,6745,8480,6874 +59900,59950,8491,6751,8491,6885 +59950,60000,8502,6757,8502,6896 +60000,60050,8513,6763,8513,6907 +60050,60100,8524,6769,8524,6918 +60100,60150,8535,6775,8535,6929 +60150,60200,8546,6781,8546,6940 +60200,60250,8557,6787,8557,6951 +60250,60300,8568,6793,8568,6962 +60300,60350,8579,6799,8579,6973 +60350,60400,8590,6805,8590,6984 +60400,60450,8601,6811,8601,6995 +60450,60500,8612,6817,8612,7006 +60500,60550,8623,6823,8623,7017 +60550,60600,8634,6829,8634,7028 +60600,60650,8645,6835,8645,7039 +60650,60700,8656,6841,8656,7050 +60700,60750,8667,6847,8667,7061 +60750,60800,8678,6853,8678,7072 +60800,60850,8689,6859,8689,7083 +60850,60900,8700,6865,8700,7094 +60900,60950,8711,6871,8711,7105 +60950,61000,8722,6877,8722,7116 +61000,61050,8733,6883,8733,7127 +61050,61100,8744,6889,8744,7138 +61100,61150,8755,6895,8755,7149 +61150,61200,8766,6901,8766,7160 +61200,61250,8777,6907,8777,7171 +61250,61300,8788,6913,8788,7182 +61300,61350,8799,6919,8799,7193 +61350,61400,8810,6925,8810,7204 +61400,61450,8821,6931,8821,7215 +61450,61500,8832,6937,8832,7226 +61500,61550,8843,6943,8843,7237 +61550,61600,8854,6949,8854,7248 +61600,61650,8865,6955,8865,7259 +61650,61700,8876,6961,8876,7270 +61700,61750,8887,6967,8887,7281 +61750,61800,8898,6973,8898,7292 +61800,61850,8909,6979,8909,7303 +61850,61900,8920,6985,8920,7314 +61900,61950,8931,6991,8931,7325 +61950,62000,8942,6997,8942,7336 +62000,62050,8953,7003,8953,7347 +62050,62100,8964,7009,8964,7358 +62100,62150,8975,7015,8975,7369 +62150,62200,8986,7021,8986,7380 +62200,62250,8997,7027,8997,7391 +62250,62300,9008,7033,9008,7402 +62300,62350,9019,7039,9019,7413 +62350,62400,9030,7045,9030,7424 +62400,62450,9041,7051,9041,7435 +62450,62500,9052,7057,9052,7446 +62500,62550,9063,7063,9063,7457 +62550,62600,9074,7069,9074,7468 +62600,62650,9085,7075,9085,7479 +62650,62700,9096,7081,9096,7490 +62700,62750,9107,7087,9107,7501 +62750,62800,9118,7093,9118,7512 +62800,62850,9129,7099,9129,7523 +62850,62900,9140,7105,9140,7534 +62900,62950,9151,7111,9151,7545 +62950,63000,9162,7117,9162,7556 +63000,63050,9173,7123,9173,7567 +63050,63100,9184,7129,9184,7578 +63100,63150,9195,7135,9195,7589 +63150,63200,9206,7141,9206,7600 +63200,63250,9217,7147,9217,7611 +63250,63300,9228,7153,9228,7622 +63300,63350,9239,7159,9239,7633 +63350,63400,9250,7165,9250,7644 +63400,63450,9261,7171,9261,7655 +63450,63500,9272,7177,9272,7666 +63500,63550,9283,7183,9283,7677 +63550,63600,9294,7189,9294,7688 +63600,63650,9305,7195,9305,7699 +63650,63700,9316,7201,9316,7710 +63700,63750,9327,7207,9327,7721 +63750,63800,9338,7213,9338,7732 +63800,63850,9349,7219,9349,7743 +63850,63900,9360,7225,9360,7754 +63900,63950,9371,7231,9371,7765 +63950,64000,9382,7237,9382,7776 +64000,64050,9393,7243,9393,7787 +64050,64100,9404,7249,9404,7798 +64100,64150,9415,7255,9415,7809 +64150,64200,9426,7261,9426,7820 +64200,64250,9437,7267,9437,7831 +64250,64300,9448,7273,9448,7842 +64300,64350,9459,7279,9459,7853 +64350,64400,9470,7285,9470,7864 +64400,64450,9481,7291,9481,7875 +64450,64500,9492,7297,9492,7886 +64500,64550,9503,7303,9503,7897 +64550,64600,9514,7309,9514,7908 +64600,64650,9525,7315,9525,7919 +64650,64700,9536,7321,9536,7930 +64700,64750,9547,7327,9547,7941 +64750,64800,9558,7333,9558,7952 +64800,64850,9569,7339,9569,7963 +64850,64900,9580,7345,9580,7974 +64900,64950,9591,7351,9591,7985 +64950,65000,9602,7357,9602,7996 +65000,65050,9613,7363,9613,8007 +65050,65100,9624,7369,9624,8018 +65100,65150,9635,7375,9635,8029 +65150,65200,9646,7381,9646,8040 +65200,65250,9657,7387,9657,8051 +65250,65300,9668,7393,9668,8062 +65300,65350,9679,7399,9679,8073 +65350,65400,9690,7405,9690,8084 +65400,65450,9701,7411,9701,8095 +65450,65500,9712,7417,9712,8106 +65500,65550,9723,7423,9723,8117 +65550,65600,9734,7429,9734,8128 +65600,65650,9745,7435,9745,8139 +65650,65700,9756,7441,9756,8150 +65700,65750,9767,7447,9767,8161 +65750,65800,9778,7453,9778,8172 +65800,65850,9789,7459,9789,8183 +65850,65900,9800,7465,9800,8194 +65900,65950,9811,7471,9811,8205 +65950,66000,9822,7477,9822,8216 +66000,66050,9833,7483,9833,8227 +66050,66100,9844,7489,9844,8238 +66100,66150,9855,7495,9855,8249 +66150,66200,9866,7501,9866,8260 +66200,66250,9877,7507,9877,8271 +66250,66300,9888,7513,9888,8282 +66300,66350,9899,7519,9899,8293 +66350,66400,9910,7525,9910,8304 +66400,66450,9921,7531,9921,8315 +66450,66500,9932,7537,9932,8326 +66500,66550,9943,7543,9943,8337 +66550,66600,9954,7549,9954,8348 +66600,66650,9965,7555,9965,8359 +66650,66700,9976,7561,9976,8370 +66700,66750,9987,7567,9987,8381 +66750,66800,9998,7573,9998,8392 +66800,66850,10009,7579,10009,8403 +66850,66900,10020,7585,10020,8414 +66900,66950,10031,7591,10031,8425 +66950,67000,10042,7597,10042,8436 +67000,67050,10053,7603,10053,8447 +67050,67100,10064,7609,10064,8458 +67100,67150,10075,7615,10075,8469 +67150,67200,10086,7621,10086,8480 +67200,67250,10097,7627,10097,8491 +67250,67300,10108,7633,10108,8502 +67300,67350,10119,7639,10119,8513 +67350,67400,10130,7645,10130,8524 +67400,67450,10141,7651,10141,8535 +67450,67500,10152,7657,10152,8546 +67500,67550,10163,7663,10163,8557 +67550,67600,10174,7669,10174,8568 +67600,67650,10185,7675,10185,8579 +67650,67700,10196,7681,10196,8590 +67700,67750,10207,7687,10207,8601 +67750,67800,10218,7693,10218,8612 +67800,67850,10229,7699,10229,8623 +67850,67900,10240,7705,10240,8634 +67900,67950,10251,7711,10251,8645 +67950,68000,10262,7717,10262,8656 +68000,68050,10273,7723,10273,8667 +68050,68100,10284,7729,10284,8678 +68100,68150,10295,7735,10295,8689 +68150,68200,10306,7741,10306,8700 +68200,68250,10317,7747,10317,8711 +68250,68300,10328,7753,10328,8722 +68300,68350,10339,7759,10339,8733 +68350,68400,10350,7765,10350,8744 +68400,68450,10361,7771,10361,8755 +68450,68500,10372,7777,10372,8766 +68500,68550,10383,7783,10383,8777 +68550,68600,10394,7789,10394,8788 +68600,68650,10405,7795,10405,8799 +68650,68700,10416,7801,10416,8810 +68700,68750,10427,7807,10427,8821 +68750,68800,10438,7813,10438,8832 +68800,68850,10449,7819,10449,8843 +68850,68900,10460,7825,10460,8854 +68900,68950,10471,7831,10471,8865 +68950,69000,10482,7837,10482,8876 +69000,69050,10493,7843,10493,8887 +69050,69100,10504,7849,10504,8898 +69100,69150,10515,7855,10515,8909 +69150,69200,10526,7861,10526,8920 +69200,69250,10537,7867,10537,8931 +69250,69300,10548,7873,10548,8942 +69300,69350,10559,7879,10559,8953 +69350,69400,10570,7885,10570,8964 +69400,69450,10581,7891,10581,8975 +69450,69500,10592,7897,10592,8986 +69500,69550,10603,7903,10603,8997 +69550,69600,10614,7909,10614,9008 +69600,69650,10625,7915,10625,9019 +69650,69700,10636,7921,10636,9030 +69700,69750,10647,7927,10647,9041 +69750,69800,10658,7933,10658,9052 +69800,69850,10669,7939,10669,9063 +69850,69900,10680,7945,10680,9074 +69900,69950,10691,7951,10691,9085 +69950,70000,10702,7957,10702,9096 +70000,70050,10713,7963,10713,9107 +70050,70100,10724,7969,10724,9118 +70100,70150,10735,7975,10735,9129 +70150,70200,10746,7981,10746,9140 +70200,70250,10757,7987,10757,9151 +70250,70300,10768,7993,10768,9162 +70300,70350,10779,7999,10779,9173 +70350,70400,10790,8005,10790,9184 +70400,70450,10801,8011,10801,9195 +70450,70500,10812,8017,10812,9206 +70500,70550,10823,8023,10823,9217 +70550,70600,10834,8029,10834,9228 +70600,70650,10845,8035,10845,9239 +70650,70700,10856,8041,10856,9250 +70700,70750,10867,8047,10867,9261 +70750,70800,10878,8053,10878,9272 +70800,70850,10889,8059,10889,9283 +70850,70900,10900,8065,10900,9294 +70900,70950,10911,8071,10911,9305 +70950,71000,10922,8077,10922,9316 +71000,71050,10933,8083,10933,9327 +71050,71100,10944,8089,10944,9338 +71100,71150,10955,8095,10955,9349 +71150,71200,10966,8101,10966,9360 +71200,71250,10977,8107,10977,9371 +71250,71300,10988,8113,10988,9382 +71300,71350,10999,8119,10999,9393 +71350,71400,11010,8125,11010,9404 +71400,71450,11021,8131,11021,9415 +71450,71500,11032,8137,11032,9426 +71500,71550,11043,8143,11043,9437 +71550,71600,11054,8149,11054,9448 +71600,71650,11065,8155,11065,9459 +71650,71700,11076,8161,11076,9470 +71700,71750,11087,8167,11087,9481 +71750,71800,11098,8173,11098,9492 +71800,71850,11109,8179,11109,9503 +71850,71900,11120,8185,11120,9514 +71900,71950,11131,8191,11131,9525 +71950,72000,11142,8197,11142,9536 +72000,72050,11153,8203,11153,9547 +72050,72100,11164,8209,11164,9558 +72100,72150,11175,8215,11175,9569 +72150,72200,11186,8221,11186,9580 +72200,72250,11197,8227,11197,9591 +72250,72300,11208,8233,11208,9602 +72300,72350,11219,8239,11219,9613 +72350,72400,11230,8245,11230,9624 +72400,72450,11241,8251,11241,9635 +72450,72500,11252,8257,11252,9646 +72500,72550,11263,8263,11263,9657 +72550,72600,11274,8269,11274,9668 +72600,72650,11285,8275,11285,9679 +72650,72700,11296,8281,11296,9690 +72700,72750,11307,8287,11307,9701 +72750,72800,11318,8293,11318,9712 +72800,72850,11329,8299,11329,9723 +72850,72900,11340,8305,11340,9734 +72900,72950,11351,8311,11351,9745 +72950,73000,11362,8317,11362,9756 +73000,73050,11373,8323,11373,9767 +73050,73100,11384,8329,11384,9778 +73100,73150,11395,8335,11395,9789 +73150,73200,11406,8341,11406,9800 +73200,73250,11417,8347,11417,9811 +73250,73300,11428,8353,11428,9822 +73300,73350,11439,8359,11439,9833 +73350,73400,11450,8365,11450,9844 +73400,73450,11461,8371,11461,9855 +73450,73500,11472,8377,11472,9866 +73500,73550,11483,8383,11483,9877 +73550,73600,11494,8389,11494,9888 +73600,73650,11505,8395,11505,9899 +73650,73700,11516,8401,11516,9910 +73700,73750,11527,8407,11527,9921 +73750,73800,11538,8413,11538,9932 +73800,73850,11549,8419,11549,9943 +73850,73900,11560,8425,11560,9954 +73900,73950,11571,8431,11571,9965 +73950,74000,11582,8437,11582,9976 +74000,74050,11593,8443,11593,9987 +74050,74100,11604,8449,11604,9998 +74100,74150,11615,8455,11615,10009 +74150,74200,11626,8461,11626,10020 +74200,74250,11637,8467,11637,10031 +74250,74300,11648,8473,11648,10042 +74300,74350,11659,8479,11659,10053 +74350,74400,11670,8485,11670,10064 +74400,74450,11681,8491,11681,10075 +74450,74500,11692,8497,11692,10086 +74500,74550,11703,8503,11703,10097 +74550,74600,11714,8509,11714,10108 +74600,74650,11725,8515,11725,10119 +74650,74700,11736,8521,11736,10130 +74700,74750,11747,8527,11747,10141 +74750,74800,11758,8533,11758,10152 +74800,74850,11769,8539,11769,10163 +74850,74900,11780,8545,11780,10174 +74900,74950,11791,8551,11791,10185 +74950,75000,11802,8557,11802,10196 +75000,75050,11813,8563,11813,10207 +75050,75100,11824,8569,11824,10218 +75100,75150,11835,8575,11835,10229 +75150,75200,11846,8581,11846,10240 +75200,75250,11857,8587,11857,10251 +75250,75300,11868,8593,11868,10262 +75300,75350,11879,8599,11879,10273 +75350,75400,11890,8605,11890,10284 +75400,75450,11901,8611,11901,10295 +75450,75500,11912,8617,11912,10306 +75500,75550,11923,8623,11923,10317 +75550,75600,11934,8629,11934,10328 +75600,75650,11945,8635,11945,10339 +75650,75700,11956,8641,11956,10350 +75700,75750,11967,8647,11967,10361 +75750,75800,11978,8653,11978,10372 +75800,75850,11989,8659,11989,10383 +75850,75900,12000,8665,12000,10394 +75900,75950,12011,8671,12011,10405 +75950,76000,12022,8677,12022,10416 +76000,76050,12033,8683,12033,10427 +76050,76100,12044,8689,12044,10438 +76100,76150,12055,8695,12055,10449 +76150,76200,12066,8701,12066,10460 +76200,76250,12077,8707,12077,10471 +76250,76300,12088,8713,12088,10482 +76300,76350,12099,8719,12099,10493 +76350,76400,12110,8725,12110,10504 +76400,76450,12121,8731,12121,10515 +76450,76500,12132,8737,12132,10526 +76500,76550,12143,8743,12143,10537 +76550,76600,12154,8749,12154,10548 +76600,76650,12165,8755,12165,10559 +76650,76700,12176,8761,12176,10570 +76700,76750,12187,8767,12187,10581 +76750,76800,12198,8773,12198,10592 +76800,76850,12209,8779,12209,10603 +76850,76900,12220,8785,12220,10614 +76900,76950,12231,8791,12231,10625 +76950,77000,12242,8797,12242,10636 +77000,77050,12253,8803,12253,10647 +77050,77100,12264,8809,12264,10658 +77100,77150,12275,8815,12275,10669 +77150,77200,12286,8821,12286,10680 +77200,77250,12297,8827,12297,10691 +77250,77300,12308,8833,12308,10702 +77300,77350,12319,8839,12319,10713 +77350,77400,12330,8845,12330,10724 +77400,77450,12341,8851,12341,10735 +77450,77500,12352,8857,12352,10746 +77500,77550,12363,8863,12363,10757 +77550,77600,12374,8869,12374,10768 +77600,77650,12385,8875,12385,10779 +77650,77700,12396,8881,12396,10790 +77700,77750,12407,8887,12407,10801 +77750,77800,12418,8893,12418,10812 +77800,77850,12429,8899,12429,10823 +77850,77900,12440,8905,12440,10834 +77900,77950,12451,8911,12451,10845 +77950,78000,12462,8917,12462,10856 +78000,78050,12473,8923,12473,10867 +78050,78100,12484,8929,12484,10878 +78100,78150,12495,8935,12495,10889 +78150,78200,12506,8941,12506,10900 +78200,78250,12517,8947,12517,10911 +78250,78300,12528,8953,12528,10922 +78300,78350,12539,8959,12539,10933 +78350,78400,12550,8965,12550,10944 +78400,78450,12561,8971,12561,10955 +78450,78500,12572,8977,12572,10966 +78500,78550,12583,8983,12583,10977 +78550,78600,12594,8989,12594,10988 +78600,78650,12605,8995,12605,10999 +78650,78700,12616,9001,12616,11010 +78700,78750,12627,9007,12627,11021 +78750,78800,12638,9013,12638,11032 +78800,78850,12649,9019,12649,11043 +78850,78900,12660,9025,12660,11054 +78900,78950,12671,9031,12671,11065 +78950,79000,12682,9037,12682,11076 +79000,79050,12693,9043,12693,11087 +79050,79100,12704,9049,12704,11098 +79100,79150,12715,9055,12715,11109 +79150,79200,12726,9061,12726,11120 +79200,79250,12737,9067,12737,11131 +79250,79300,12748,9073,12748,11142 +79300,79350,12759,9079,12759,11153 +79350,79400,12770,9085,12770,11164 +79400,79450,12781,9091,12781,11175 +79450,79500,12792,9097,12792,11186 +79500,79550,12803,9103,12803,11197 +79550,79600,12814,9109,12814,11208 +79600,79650,12825,9115,12825,11219 +79650,79700,12836,9121,12836,11230 +79700,79750,12847,9127,12847,11241 +79750,79800,12858,9133,12858,11252 +79800,79850,12869,9139,12869,11263 +79850,79900,12880,9145,12880,11274 +79900,79950,12891,9151,12891,11285 +79950,80000,12902,9157,12902,11296 +80000,80050,12913,9163,12913,11307 +80050,80100,12924,9169,12924,11318 +80100,80150,12935,9175,12935,11329 +80150,80200,12946,9181,12946,11340 +80200,80250,12957,9187,12957,11351 +80250,80300,12968,9193,12968,11362 +80300,80350,12979,9199,12979,11373 +80350,80400,12990,9205,12990,11384 +80400,80450,13001,9211,13001,11395 +80450,80500,13012,9217,13012,11406 +80500,80550,13023,9223,13023,11417 +80550,80600,13034,9229,13034,11428 +80600,80650,13045,9235,13045,11439 +80650,80700,13056,9241,13056,11450 +80700,80750,13067,9247,13067,11461 +80750,80800,13078,9253,13078,11472 +80800,80850,13089,9259,13089,11483 +80850,80900,13100,9265,13100,11494 +80900,80950,13111,9271,13111,11505 +80950,81000,13122,9277,13122,11516 +81000,81050,13133,9283,13133,11527 +81050,81100,13144,9289,13144,11538 +81100,81150,13155,9295,13155,11549 +81150,81200,13166,9301,13166,11560 +81200,81250,13177,9307,13177,11571 +81250,81300,13188,9313,13188,11582 +81300,81350,13199,9319,13199,11593 +81350,81400,13210,9325,13210,11604 +81400,81450,13221,9331,13221,11615 +81450,81500,13232,9337,13232,11626 +81500,81550,13243,9343,13243,11637 +81550,81600,13254,9349,13254,11648 +81600,81650,13265,9355,13265,11659 +81650,81700,13276,9361,13276,11670 +81700,81750,13287,9367,13287,11681 +81750,81800,13298,9373,13298,11692 +81800,81850,13309,9379,13309,11703 +81850,81900,13320,9385,13320,11714 +81900,81950,13331,9391,13331,11725 +81950,82000,13342,9397,13342,11736 +82000,82050,13353,9403,13353,11747 +82050,82100,13364,9409,13364,11758 +82100,82150,13375,9415,13375,11769 +82150,82200,13386,9421,13386,11780 +82200,82250,13397,9427,13397,11791 +82250,82300,13408,9433,13408,11802 +82300,82350,13419,9439,13419,11813 +82350,82400,13430,9445,13430,11824 +82400,82450,13441,9451,13441,11835 +82450,82500,13452,9457,13452,11846 +82500,82550,13463,9463,13463,11857 +82550,82600,13474,9469,13474,11868 +82600,82650,13485,9475,13485,11879 +82650,82700,13496,9481,13496,11890 +82700,82750,13507,9487,13507,11901 +82750,82800,13518,9493,13518,11912 +82800,82850,13529,9499,13529,11923 +82850,82900,13540,9505,13540,11934 +82900,82950,13551,9511,13551,11945 +82950,83000,13562,9517,13562,11956 +83000,83050,13573,9523,13573,11967 +83050,83100,13584,9529,13584,11978 +83100,83150,13595,9535,13595,11989 +83150,83200,13606,9541,13606,12000 +83200,83250,13617,9547,13617,12011 +83250,83300,13628,9553,13628,12022 +83300,83350,13639,9559,13639,12033 +83350,83400,13650,9565,13650,12044 +83400,83450,13661,9571,13661,12055 +83450,83500,13672,9577,13672,12066 +83500,83550,13683,9583,13683,12077 +83550,83600,13694,9589,13694,12088 +83600,83650,13705,9595,13705,12099 +83650,83700,13716,9601,13716,12110 +83700,83750,13727,9607,13727,12121 +83750,83800,13738,9613,13738,12132 +83800,83850,13749,9619,13749,12143 +83850,83900,13760,9625,13760,12154 +83900,83950,13771,9631,13771,12165 +83950,84000,13782,9637,13782,12176 +84000,84050,13793,9643,13793,12187 +84050,84100,13804,9649,13804,12198 +84100,84150,13815,9655,13815,12209 +84150,84200,13826,9661,13826,12220 +84200,84250,13837,9667,13837,12231 +84250,84300,13848,9673,13848,12242 +84300,84350,13859,9679,13859,12253 +84350,84400,13870,9685,13870,12264 +84400,84450,13881,9691,13881,12275 +84450,84500,13892,9697,13892,12286 +84500,84550,13903,9703,13903,12297 +84550,84600,13914,9709,13914,12308 +84600,84650,13925,9715,13925,12319 +84650,84700,13936,9721,13936,12330 +84700,84750,13947,9727,13947,12341 +84750,84800,13958,9733,13958,12352 +84800,84850,13969,9739,13969,12363 +84850,84900,13980,9745,13980,12374 +84900,84950,13991,9751,13991,12385 +84950,85000,14002,9757,14002,12396 +85000,85050,14013,9763,14013,12407 +85050,85100,14024,9769,14024,12418 +85100,85150,14035,9775,14035,12429 +85150,85200,14046,9781,14046,12440 +85200,85250,14057,9787,14057,12451 +85250,85300,14068,9793,14068,12462 +85300,85350,14079,9799,14079,12473 +85350,85400,14090,9805,14090,12484 +85400,85450,14101,9811,14101,12495 +85450,85500,14112,9817,14112,12506 +85500,85550,14123,9823,14123,12517 +85550,85600,14134,9829,14134,12528 +85600,85650,14145,9835,14145,12539 +85650,85700,14156,9841,14156,12550 +85700,85750,14167,9847,14167,12561 +85750,85800,14178,9853,14178,12572 +85800,85850,14189,9859,14189,12583 +85850,85900,14200,9865,14200,12594 +85900,85950,14211,9871,14211,12605 +85950,86000,14222,9877,14222,12616 +86000,86050,14233,9883,14233,12627 +86050,86100,14244,9889,14244,12638 +86100,86150,14255,9895,14255,12649 +86150,86200,14266,9901,14266,12660 +86200,86250,14277,9907,14277,12671 +86250,86300,14288,9913,14288,12682 +86300,86350,14299,9919,14299,12693 +86350,86400,14310,9925,14310,12704 +86400,86450,14321,9931,14321,12715 +86450,86500,14332,9937,14332,12726 +86500,86550,14343,9943,14343,12737 +86550,86600,14354,9949,14354,12748 +86600,86650,14365,9955,14365,12759 +86650,86700,14376,9961,14376,12770 +86700,86750,14387,9967,14387,12781 +86750,86800,14398,9973,14398,12792 +86800,86850,14409,9979,14409,12803 +86850,86900,14420,9985,14420,12814 +86900,86950,14431,9991,14431,12825 +86950,87000,14442,9997,14442,12836 +87000,87050,14453,10003,14453,12847 +87050,87100,14464,10009,14464,12858 +87100,87150,14475,10015,14475,12869 +87150,87200,14486,10021,14486,12880 +87200,87250,14497,10027,14497,12891 +87250,87300,14508,10033,14508,12902 +87300,87350,14519,10039,14519,12913 +87350,87400,14530,10045,14530,12924 +87400,87450,14541,10051,14541,12935 +87450,87500,14552,10057,14552,12946 +87500,87550,14563,10063,14563,12957 +87550,87600,14574,10069,14574,12968 +87600,87650,14585,10075,14585,12979 +87650,87700,14596,10081,14596,12990 +87700,87750,14607,10087,14607,13001 +87750,87800,14618,10093,14618,13012 +87800,87850,14629,10099,14629,13023 +87850,87900,14640,10105,14640,13034 +87900,87950,14651,10111,14651,13045 +87950,88000,14662,10117,14662,13056 +88000,88050,14673,10123,14673,13067 +88050,88100,14684,10129,14684,13078 +88100,88150,14695,10135,14695,13089 +88150,88200,14706,10141,14706,13100 +88200,88250,14717,10147,14717,13111 +88250,88300,14728,10153,14728,13122 +88300,88350,14739,10159,14739,13133 +88350,88400,14750,10165,14750,13144 +88400,88450,14761,10171,14761,13155 +88450,88500,14772,10177,14772,13166 +88500,88550,14783,10183,14783,13177 +88550,88600,14794,10189,14794,13188 +88600,88650,14805,10195,14805,13199 +88650,88700,14816,10201,14816,13210 +88700,88750,14827,10207,14827,13221 +88750,88800,14838,10213,14838,13232 +88800,88850,14849,10219,14849,13243 +88850,88900,14860,10225,14860,13254 +88900,88950,14871,10231,14871,13265 +88950,89000,14882,10237,14882,13276 +89000,89050,14893,10243,14893,13287 +89050,89100,14904,10249,14904,13298 +89100,89150,14915,10255,14915,13309 +89150,89200,14926,10261,14926,13320 +89200,89250,14937,10267,14937,13331 +89250,89300,14948,10273,14948,13342 +89300,89350,14959,10279,14959,13353 +89350,89400,14970,10285,14970,13364 +89400,89450,14981,10291,14981,13375 +89450,89500,14992,10300,14992,13386 +89500,89550,15003,10311,15003,13397 +89550,89600,15014,10322,15014,13408 +89600,89650,15025,10333,15025,13419 +89650,89700,15036,10344,15036,13430 +89700,89750,15047,10355,15047,13441 +89750,89800,15058,10366,15058,13452 +89800,89850,15069,10377,15069,13463 +89850,89900,15080,10388,15080,13474 +89900,89950,15091,10399,15091,13485 +89950,90000,15102,10410,15102,13496 +90000,90050,15113,10421,15113,13507 +90050,90100,15124,10432,15124,13518 +90100,90150,15135,10443,15135,13529 +90150,90200,15146,10454,15146,13540 +90200,90250,15157,10465,15157,13551 +90250,90300,15168,10476,15168,13562 +90300,90350,15179,10487,15179,13573 +90350,90400,15190,10498,15190,13584 +90400,90450,15201,10509,15201,13595 +90450,90500,15212,10520,15212,13606 +90500,90550,15223,10531,15223,13617 +90550,90600,15234,10542,15234,13628 +90600,90650,15245,10553,15245,13639 +90650,90700,15256,10564,15256,13650 +90700,90750,15267,10575,15267,13661 +90750,90800,15278,10586,15278,13672 +90800,90850,15289,10597,15289,13683 +90850,90900,15300,10608,15300,13694 +90900,90950,15311,10619,15311,13705 +90950,91000,15322,10630,15322,13716 +91000,91050,15333,10641,15333,13727 +91050,91100,15344,10652,15344,13738 +91100,91150,15355,10663,15355,13749 +91150,91200,15366,10674,15366,13760 +91200,91250,15377,10685,15377,13771 +91250,91300,15388,10696,15388,13782 +91300,91350,15399,10707,15399,13793 +91350,91400,15410,10718,15410,13804 +91400,91450,15421,10729,15421,13815 +91450,91500,15432,10740,15432,13826 +91500,91550,15443,10751,15443,13837 +91550,91600,15454,10762,15454,13848 +91600,91650,15465,10773,15465,13859 +91650,91700,15476,10784,15476,13870 +91700,91750,15487,10795,15487,13881 +91750,91800,15498,10806,15498,13892 +91800,91850,15509,10817,15509,13903 +91850,91900,15520,10828,15520,13914 +91900,91950,15531,10839,15531,13925 +91950,92000,15542,10850,15542,13936 +92000,92050,15553,10861,15553,13947 +92050,92100,15564,10872,15564,13958 +92100,92150,15575,10883,15575,13969 +92150,92200,15586,10894,15586,13980 +92200,92250,15597,10905,15597,13991 +92250,92300,15608,10916,15608,14002 +92300,92350,15619,10927,15619,14013 +92350,92400,15630,10938,15630,14024 +92400,92450,15641,10949,15641,14035 +92450,92500,15652,10960,15652,14046 +92500,92550,15663,10971,15663,14057 +92550,92600,15674,10982,15674,14068 +92600,92650,15685,10993,15685,14079 +92650,92700,15696,11004,15696,14090 +92700,92750,15707,11015,15707,14101 +92750,92800,15718,11026,15718,14112 +92800,92850,15729,11037,15729,14123 +92850,92900,15740,11048,15740,14134 +92900,92950,15751,11059,15751,14145 +92950,93000,15762,11070,15762,14156 +93000,93050,15773,11081,15773,14167 +93050,93100,15784,11092,15784,14178 +93100,93150,15795,11103,15795,14189 +93150,93200,15806,11114,15806,14200 +93200,93250,15817,11125,15817,14211 +93250,93300,15828,11136,15828,14222 +93300,93350,15839,11147,15839,14233 +93350,93400,15850,11158,15850,14244 +93400,93450,15861,11169,15861,14255 +93450,93500,15872,11180,15872,14266 +93500,93550,15883,11191,15883,14277 +93550,93600,15894,11202,15894,14288 +93600,93650,15905,11213,15905,14299 +93650,93700,15916,11224,15916,14310 +93700,93750,15927,11235,15927,14321 +93750,93800,15938,11246,15938,14332 +93800,93850,15949,11257,15949,14343 +93850,93900,15960,11268,15960,14354 +93900,93950,15971,11279,15971,14365 +93950,94000,15982,11290,15982,14376 +94000,94050,15993,11301,15993,14387 +94050,94100,16004,11312,16004,14398 +94100,94150,16015,11323,16015,14409 +94150,94200,16026,11334,16026,14420 +94200,94250,16037,11345,16037,14431 +94250,94300,16048,11356,16048,14442 +94300,94350,16059,11367,16059,14453 +94350,94400,16070,11378,16070,14464 +94400,94450,16081,11389,16081,14475 +94450,94500,16092,11400,16092,14486 +94500,94550,16103,11411,16103,14497 +94550,94600,16114,11422,16114,14508 +94600,94650,16125,11433,16125,14519 +94650,94700,16136,11444,16136,14530 +94700,94750,16147,11455,16147,14541 +94750,94800,16158,11466,16158,14552 +94800,94850,16169,11477,16169,14563 +94850,94900,16180,11488,16180,14574 +94900,94950,16191,11499,16191,14585 +94950,95000,16202,11510,16202,14596 +95000,95050,16213,11521,16213,14607 +95050,95100,16224,11532,16224,14618 +95100,95150,16235,11543,16235,14629 +95150,95200,16246,11554,16246,14640 +95200,95250,16257,11565,16257,14651 +95250,95300,16268,11576,16268,14662 +95300,95350,16279,11587,16279,14673 +95350,95400,16290,11598,16290,14684 +95400,95450,16302,11609,16302,14696 +95450,95500,16314,11620,16314,14708 +95500,95550,16326,11631,16326,14720 +95550,95600,16338,11642,16338,14732 +95600,95650,16350,11653,16350,14744 +95650,95700,16362,11664,16362,14756 +95700,95750,16374,11675,16374,14768 +95750,95800,16386,11686,16386,14780 +95800,95850,16398,11697,16398,14792 +95850,95900,16410,11708,16410,14804 +95900,95950,16422,11719,16422,14816 +95950,96000,16434,11730,16434,14828 +96000,96050,16446,11741,16446,14840 +96050,96100,16458,11752,16458,14852 +96100,96150,16470,11763,16470,14864 +96150,96200,16482,11774,16482,14876 +96200,96250,16494,11785,16494,14888 +96250,96300,16506,11796,16506,14900 +96300,96350,16518,11807,16518,14912 +96350,96400,16530,11818,16530,14924 +96400,96450,16542,11829,16542,14936 +96450,96500,16554,11840,16554,14948 +96500,96550,16566,11851,16566,14960 +96550,96600,16578,11862,16578,14972 +96600,96650,16590,11873,16590,14984 +96650,96700,16602,11884,16602,14996 +96700,96750,16614,11895,16614,15008 +96750,96800,16626,11906,16626,15020 +96800,96850,16638,11917,16638,15032 +96850,96900,16650,11928,16650,15044 +96900,96950,16662,11939,16662,15056 +96950,97000,16674,11950,16674,15068 +97000,97050,16686,11961,16686,15080 +97050,97100,16698,11972,16698,15092 +97100,97150,16710,11983,16710,15104 +97150,97200,16722,11994,16722,15116 +97200,97250,16734,12005,16734,15128 +97250,97300,16746,12016,16746,15140 +97300,97350,16758,12027,16758,15152 +97350,97400,16770,12038,16770,15164 +97400,97450,16782,12049,16782,15176 +97450,97500,16794,12060,16794,15188 +97500,97550,16806,12071,16806,15200 +97550,97600,16818,12082,16818,15212 +97600,97650,16830,12093,16830,15224 +97650,97700,16842,12104,16842,15236 +97700,97750,16854,12115,16854,15248 +97750,97800,16866,12126,16866,15260 +97800,97850,16878,12137,16878,15272 +97850,97900,16890,12148,16890,15284 +97900,97950,16902,12159,16902,15296 +97950,98000,16914,12170,16914,15308 +98000,98050,16926,12181,16926,15320 +98050,98100,16938,12192,16938,15332 +98100,98150,16950,12203,16950,15344 +98150,98200,16962,12214,16962,15356 +98200,98250,16974,12225,16974,15368 +98250,98300,16986,12236,16986,15380 +98300,98350,16998,12247,16998,15392 +98350,98400,17010,12258,17010,15404 +98400,98450,17022,12269,17022,15416 +98450,98500,17034,12280,17034,15428 +98500,98550,17046,12291,17046,15440 +98550,98600,17058,12302,17058,15452 +98600,98650,17070,12313,17070,15464 +98650,98700,17082,12324,17082,15476 +98700,98750,17094,12335,17094,15488 +98750,98800,17106,12346,17106,15500 +98800,98850,17118,12357,17118,15512 +98850,98900,17130,12368,17130,15524 +98900,98950,17142,12379,17142,15536 +98950,99000,17154,12390,17154,15548 +99000,99050,17166,12401,17166,15560 +99050,99100,17178,12412,17178,15572 +99100,99150,17190,12423,17190,15584 +99150,99200,17202,12434,17202,15596 +99200,99250,17214,12445,17214,15608 +99250,99300,17226,12456,17226,15620 +99300,99350,17238,12467,17238,15632 +99350,99400,17250,12478,17250,15644 +99400,99450,17262,12489,17262,15656 +99450,99500,17274,12500,17274,15668 +99500,99550,17286,12511,17286,15680 +99550,99600,17298,12522,17298,15692 +99600,99650,17310,12533,17310,15704 +99650,99700,17322,12544,17322,15716 +99700,99750,17334,12555,17334,15728 +99750,99800,17346,12566,17346,15740 +99800,99850,17358,12577,17358,15752 +99850,99900,17370,12588,17370,15764 +99900,99950,17382,12599,17382,15776 +99950,100000,17394,12610,17394,15788 diff --git a/src/forms/YearForms.ts b/src/forms/YearForms.ts index 926e461f2..e422488bc 100644 --- a/src/forms/YearForms.ts +++ b/src/forms/YearForms.ts @@ -4,10 +4,12 @@ import { TaxYear } from 'ustaxes/core/data' import { create1040 as create1040For2020 } from 'ustaxes/forms/Y2020/irsForms/Main' import { create1040 as create1040For2021 } from 'ustaxes/forms/Y2021/irsForms/Main' import { create1040 as create1040For2022 } from 'ustaxes/forms/Y2022/irsForms/Main' +import { create1040 as create1040For2023 } from 'ustaxes/forms/Y2023/irsForms/Main' import F1040For2020 from 'ustaxes/forms/Y2020/irsForms/F1040' import F1040For2021 from 'ustaxes/forms/Y2021/irsForms/F1040' import F1040For2022 from 'ustaxes/forms/Y2022/irsForms/F1040' +import F1040For2023 from 'ustaxes/forms/Y2023/irsForms/F1040' import Form from 'ustaxes/core/irsForms/Form' import StateForm from 'ustaxes/core/stateForms/Form' @@ -15,6 +17,7 @@ import StateForm from 'ustaxes/core/stateForms/Form' import { createStateReturn as createStateReturn2020 } from 'ustaxes/forms/Y2020/stateForms' import { createStateReturn as createStateReturn2021 } from 'ustaxes/forms/Y2021/stateForms' import { createStateReturn as createStateReturn2022 } from 'ustaxes/forms/Y2022/stateForms' +import { createStateReturn as createStateReturn2023 } from 'ustaxes/forms/Y2023/stateForms' import { PDFDocument } from 'pdf-lib' import { fillPDF } from 'ustaxes/core/pdfFiller/fillPdf' import { @@ -194,6 +197,11 @@ export class CreateForms { ...baseConfig, createF1040: takeSecond(create1040For2022), createStateReturn: (f: Form) => createStateReturn2022(f as F1040For2022) + }, + Y2023: { + ...baseConfig, + createF1040: takeSecond(create1040For2023), + createStateReturn: (f: Form) => createStateReturn2023(f as F1040For2023) } } diff --git a/src/redux/data.ts b/src/redux/data.ts index a14838ddb..f28b83bcc 100644 --- a/src/redux/data.ts +++ b/src/redux/data.ts @@ -19,6 +19,7 @@ export const blankYearTaxesState: YearsTaxesState = { Y2020: blankState, Y2021: blankState, Y2022: blankState, + Y2023: blankState, activeYear: 'Y2020' } diff --git a/src/redux/reducer.ts b/src/redux/reducer.ts index 9f3e6bbb8..4705d39be 100644 --- a/src/redux/reducer.ts +++ b/src/redux/reducer.ts @@ -5,7 +5,7 @@ import { YearsTaxesState } from '.' import { ActionName, Actions } from './actions' import { stringToDateInfo } from './data' -const DEFAULT_TAX_YEAR: TaxYear = 'Y2022' +const DEFAULT_TAX_YEAR: TaxYear = 'Y2023' export const blankState: Information = { f1099s: [], @@ -485,6 +485,7 @@ const rootReducer: Reducer< Y2020: guardByYear('Y2020'), Y2021: guardByYear('Y2021'), Y2022: guardByYear('Y2022'), + Y2023: guardByYear('Y2023'), activeYear }) as Reducer, Actions> diff --git a/src/tests/arbitraries.ts b/src/tests/arbitraries.ts index bca218530..2927bbe40 100644 --- a/src/tests/arbitraries.ts +++ b/src/tests/arbitraries.ts @@ -84,14 +84,16 @@ export const taxesState: fc.Arbitrary = taxYear.chain( information, information, information, + information, information ) - .map(([assets, Y2019, Y2020, Y2021, Y2022]) => ({ + .map(([assets, Y2019, Y2020, Y2021, Y2022, Y2023]) => ({ assets, Y2019, Y2020, Y2021, Y2022, + Y2023, activeYear })) }