use crate::conventions::Usd;
use crate::tax::tables::{AmtParams, LtcgBreakpoints};
use crate::tax::types::FilingStatus;
use rust_decimal::Decimal;
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[non_exhaustive]
pub struct Form6251 {
pub line1: Usd,
pub line2a: Usd,
pub line2b: Usd,
pub line3: Usd,
pub line4: Usd,
pub line5: Usd,
pub line6: Usd,
pub line7: Usd,
pub line8: Usd,
pub line9: Usd,
pub line10: Usd,
pub line11: Usd,
pub line12: Usd,
pub line13: Usd,
pub line14: Usd,
pub line15: Usd,
pub line16: Usd,
pub line17: Usd,
pub line18: Usd,
pub line19: Usd,
pub line20: Usd,
pub line21: Usd,
pub line22: Usd,
pub line23: Usd,
pub line24: Usd,
pub line25: Usd,
pub line26: Usd,
pub line27: Usd,
pub line28: Usd,
pub line29: Usd,
pub line30: Usd,
pub line31: Usd,
pub line32: Usd,
pub line33: Usd,
pub line34: Usd,
pub line35: Usd,
pub line36: Usd,
pub line37: Usd,
pub line38: Usd,
pub line39: Usd,
pub line40: Usd,
}
impl Form6251 {
pub fn must_attach(&self) -> bool {
self.line7 > self.line10
}
pub fn amt(&self) -> Usd {
self.line11
}
pub fn tentative_minimum_tax(&self) -> Usd {
self.line9
}
}
#[derive(Debug, Clone, Copy)]
pub struct Form6251Inputs {
pub status: FilingStatus,
pub taxable_income_l15: Usd,
pub agi_l11: Usd,
pub deduction_l12: Usd,
pub deduction_l14: Usd,
pub schedule_a_line7: Usd,
pub itemized: bool,
pub state_refund_sch1_l1: Usd,
pub net_capital_gain: Usd,
pub qualified_dividends: Usd,
pub qdcgt_line5_regular: Usd,
pub regular_tax_l16: Usd,
pub schedule_2_line1z: Usd,
pub schedule_3_line1: Usd,
}
pub fn compute_6251(i: Form6251Inputs, amt: &AmtParams, bp: &LtcgBreakpoints) -> Form6251 {
let z = Usd::ZERO;
let st = i.status;
let line1 = if i.taxable_income_l15 > z {
i.taxable_income_l15
} else {
i.agi_l11 - i.deduction_l14 };
let line2a = if i.itemized {
i.schedule_a_line7
} else {
i.deduction_l12 };
let line2b = -i.state_refund_sch1_l1;
let line3 = z;
let mut line4 = line1 + line2a + line2b + line3;
if st == FilingStatus::Mfs && line4 > amt.mfs_kicker_start {
let excess = line4 - amt.mfs_kicker_start;
line4 += (amt.phaseout_rate * excess).min(amt.mfs_kicker_max);
}
let exemption = amt.exemption(st);
let phase_start = amt.phaseout_start(st);
let line5 = (exemption - amt.phaseout_rate * (line4 - phase_start).max(z)).max(z);
let line6 = (line4 - line5).max(z);
let bp28 = amt.breakpoint_28pct(st);
let sub28 = if st == FilingStatus::Mfs {
amt.rate_28_subtrahend_mfs
} else {
amt.rate_28_subtrahend
};
let flat_26_28 = |x: Usd| -> Usd {
if x <= bp28 {
amt.rate_26 * x
} else {
amt.rate_28 * x - sub28
}
};
let mut f = Form6251 {
line1,
line2a,
line2b,
line3,
line4,
line5,
line6,
line7: z,
line8: z,
line9: z,
line10: z,
line11: z,
line12: z,
line13: z,
line14: z,
line15: z,
line16: z,
line17: z,
line18: z,
line19: z,
line20: z,
line21: z,
line22: z,
line23: z,
line24: z,
line25: z,
line26: z,
line27: z,
line28: z,
line29: z,
line30: z,
line31: z,
line32: z,
line33: z,
line34: z,
line35: z,
line36: z,
line37: z,
line38: z,
line39: z,
line40: z,
};
f.line10 = (i.regular_tax_l16 + i.schedule_2_line1z - i.schedule_3_line1).max(z);
if line6 <= z {
return f;
}
let preferential = (i.net_capital_gain + i.qualified_dividends).max(z);
if preferential > z {
f.line12 = line6;
f.line13 = preferential;
f.line14 = z; f.line15 = f.line13;
f.line16 = f.line12.min(f.line15);
f.line17 = f.line12 - f.line16;
f.line18 = flat_26_28(f.line17);
f.line19 = bp.max_zero;
f.line20 = i.qdcgt_line5_regular;
f.line21 = (f.line19 - f.line20).max(z);
f.line22 = f.line12.min(f.line13);
f.line23 = f.line21.min(f.line22);
f.line24 = f.line22 - f.line23;
f.line25 = bp.max_fifteen;
f.line26 = f.line21;
f.line27 = i.qdcgt_line5_regular;
f.line28 = f.line26 + f.line27;
f.line29 = (f.line25 - f.line28).max(z);
f.line30 = f.line24.min(f.line29);
f.line31 = Decimal::new(15, 2) * f.line30;
f.line32 = f.line23 + f.line30;
if f.line32 != f.line12 {
f.line33 = f.line22 - f.line32; f.line34 = Decimal::new(20, 2) * f.line33;
if f.line14 != z {
f.line35 = f.line17 + f.line32 + f.line33;
f.line36 = f.line12 - f.line35;
f.line37 = Decimal::new(25, 2) * f.line36;
}
}
f.line38 = f.line18 + f.line31 + f.line34 + f.line37;
f.line39 = flat_26_28(f.line12);
f.line40 = f.line38.min(f.line39);
f.line7 = f.line40;
} else {
f.line7 = flat_26_28(line6);
}
f.line8 = if f.line10 >= f.line7 {
z
} else {
i.schedule_3_line1
};
f.line9 = f.line7 - f.line8;
f.line11 = (f.line9 - f.line10).max(z);
f
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tax::tables::LtcgBreakpoints;
use rust_decimal_macros::dec;
use std::str::FromStr;
const VECTORS: &str = include_str!("fixtures/form6251_vectors.json");
fn params() -> AmtParams {
AmtParams {
exemption_single_hoh: dec!(85700),
exemption_mfj_qss: dec!(133300),
exemption_mfs: dec!(66650),
phaseout_start_single_hoh_mfs: dec!(609350),
phaseout_start_mfj_qss: dec!(1218700),
breakpoint_28pct: dec!(232600),
breakpoint_28pct_mfs: dec!(116300),
mfs_kicker_start: dec!(875950),
mfs_kicker_max: dec!(66650),
phaseout_rate: dec!(0.25),
rate_26: dec!(0.26),
rate_28: dec!(0.28),
rate_28_subtrahend: dec!(4652),
rate_28_subtrahend_mfs: dec!(2326),
}
}
fn bps(status: FilingStatus) -> LtcgBreakpoints {
match status {
FilingStatus::Mfs => LtcgBreakpoints {
max_zero: dec!(47025),
max_fifteen: dec!(291850),
},
_ => LtcgBreakpoints {
max_zero: dec!(94050),
max_fifteen: dec!(583750),
},
}
}
fn d(v: &serde_json::Value) -> Usd {
Usd::from_str(v.as_str().expect("fixture value is a decimal string")).expect("parses")
}
#[test]
fn every_vector_reproduces_the_form_line_by_line() {
let json: serde_json::Value = serde_json::from_str(VECTORS).expect("fixture parses");
let vectors = json["vectors"].as_array().expect("vectors array");
assert!(
vectors.len() >= 11,
"fixture shrank: {} vectors",
vectors.len()
);
let p = params();
for v in vectors {
let id = v["id"].as_str().unwrap();
let inp = &v["inputs"];
let der = &v["derived"];
let want = &v["form6251"];
let status = match inp["filing_status"].as_str().unwrap() {
"mfs" => FilingStatus::Mfs,
_ => FilingStatus::Mfj,
};
let ti = d(&der["taxable_income_1040_L15"]);
let pref = d(&inp["net_ltcg"]);
let qdcgt_l5 = (ti - pref.min(ti)).max(Usd::ZERO);
let got = compute_6251(
Form6251Inputs {
status,
taxable_income_l15: ti,
agi_l11: d(&der["agi_1040_L11"]),
deduction_l12: d(&der["deduction_1040_L14"]),
deduction_l14: d(&der["deduction_1040_L14"]),
schedule_a_line7: Usd::ZERO,
itemized: der["itemized"].as_bool().unwrap(),
state_refund_sch1_l1: d(&inp["state_refund"]),
net_capital_gain: pref,
qualified_dividends: Usd::ZERO,
qdcgt_line5_regular: qdcgt_l5,
regular_tax_l16: d(&der["regular_tax_1040_L16"]),
schedule_2_line1z: Usd::ZERO,
schedule_3_line1: d(&inp["sch3_line1_ftc"]),
},
&p,
&bps(status),
);
let check = |n: &str, actual: Usd| {
assert_eq!(actual, d(&want[n]), "{id}: Form 6251 {n}");
};
check("line1", got.line1);
check("line2a", got.line2a);
check("line2b", got.line2b);
check("line4", got.line4);
check("line5", got.line5);
check("line6", got.line6);
check("line7", got.line7);
check("line8", got.line8);
check("line9", got.line9);
check("line10", got.line10);
check("line11", got.line11);
check("line16", got.line16);
check("line17", got.line17);
check("line22", got.line22);
check("line23", got.line23);
check("line31", got.line31);
check("line32", got.line32);
check("line33", got.line33);
check("line38", got.line38);
check("line40", got.line40);
assert_eq!(
got.must_attach(),
v["attach_required_who_must_file_cond1"].as_bool().unwrap(),
"{id}: Who Must File condition 1 (line 7 > line 10)"
);
}
}
#[test]
fn mfs_kicker_reproduces_the_irs_worked_example() {
let p = params();
let f = compute_6251(
Form6251Inputs {
status: FilingStatus::Mfs,
taxable_income_l15: dec!(881350),
agi_l11: dec!(895950),
deduction_l12: dec!(14600),
deduction_l14: dec!(14600),
schedule_a_line7: Usd::ZERO,
itemized: false,
state_refund_sch1_l1: Usd::ZERO,
net_capital_gain: dec!(395950),
qualified_dividends: Usd::ZERO,
qdcgt_line5_regular: dec!(485400),
regular_tax_l16: dec!(1),
schedule_2_line1z: Usd::ZERO,
schedule_3_line1: Usd::ZERO,
},
&p,
&bps(FilingStatus::Mfs),
);
assert_eq!(f.line4, dec!(900950), "the IRS's own example");
let at = |l4_pre: Usd| {
let f = compute_6251(
Form6251Inputs {
status: FilingStatus::Mfs,
taxable_income_l15: l4_pre - dec!(14600),
agi_l11: l4_pre,
deduction_l12: dec!(14600),
deduction_l14: dec!(14600),
schedule_a_line7: Usd::ZERO,
itemized: false,
state_refund_sch1_l1: Usd::ZERO,
net_capital_gain: Usd::ZERO,
qualified_dividends: Usd::ZERO,
qdcgt_line5_regular: l4_pre - dec!(14600),
regular_tax_l16: dec!(1),
schedule_2_line1z: Usd::ZERO,
schedule_3_line1: Usd::ZERO,
},
&p,
&bps(FilingStatus::Mfs),
);
f.line4 - l4_pre
};
assert_eq!(
at(dec!(875950)),
Usd::ZERO,
"at the start, nothing is added"
);
assert_eq!(at(dec!(1142550)), dec!(66650), "at the cap, a flat $66,650");
assert_eq!(
at(dec!(2000000)),
dec!(66650),
"above the cap, still $66,650"
);
}
#[test]
fn v9_must_attach_while_the_amt_is_zero() {
let json: serde_json::Value = serde_json::from_str(VECTORS).unwrap();
let v9 = json["vectors"]
.as_array()
.unwrap()
.iter()
.find(|v| v["id"] == "V9")
.expect("V9 present");
let f = &v9["form6251"];
let (l7, l10, l11) = (d(&f["line7"]), d(&f["line10"]), d(&f["line11"]));
assert!(
l7 > l10,
"V9: line 7 must exceed line 10 (attachment required)"
);
assert_eq!(l11, Usd::ZERO, "V9: yet the AMT itself is zero");
assert!(
v9["attach_required_who_must_file_cond1"].as_bool().unwrap(),
"V9 exists precisely to prove `attach iff amt > 0` is WRONG"
);
}
#[test]
fn line40_min_is_a_proved_no_op_for_this_input_class() {
let p = params();
let bp28 = p.breakpoint_28pct;
let flat = |x: Usd| {
if x <= bp28 {
p.rate_26 * x
} else {
p.rate_28 * x - p.rate_28_subtrahend
}
};
let mut checked = 0u32;
let mut step = 0i64;
while step < 1_200_000 {
let l17 = Usd::from(step);
let mut g = 0i64;
while g < 1_200_000 {
let gain = Usd::from(g);
let line38 = flat(l17) + Decimal::new(20, 2) * gain;
let line39 = flat(l17 + gain);
assert!(
line38 <= line39,
"line 40's min WOULD bind at line17={l17}, gain={gain}: 38={line38} > 39={line39}. The input class has widened — re-examine the min, do not delete it."
);
checked += 1;
g += 9_973; }
step += 9_973;
}
assert!(
checked > 10_000,
"the sweep must actually cover ground: {checked}"
);
}
#[test]
fn line2a_reads_1040_line12_not_line14_which_qbi_distinguishes() {
let mk = |l12: Usd, l14: Usd| {
compute_6251(
Form6251Inputs {
status: FilingStatus::Mfj,
taxable_income_l15: dec!(400000),
agi_l11: dec!(450000),
deduction_l12: l12,
deduction_l14: l14,
schedule_a_line7: Usd::ZERO,
itemized: false,
state_refund_sch1_l1: Usd::ZERO,
net_capital_gain: Usd::ZERO,
qualified_dividends: Usd::ZERO,
qdcgt_line5_regular: dec!(400000),
regular_tax_l16: dec!(80000),
schedule_2_line1z: Usd::ZERO,
schedule_3_line1: Usd::ZERO,
},
¶ms(),
&bps(FilingStatus::Mfj),
)
};
let f = mk(dec!(29200), dec!(50000));
assert_eq!(f.line2a, dec!(29200), "line 2a is 1040 L12");
assert_eq!(f.line4, dec!(429200), "AMTI = 400,000 + 29,200");
let wrong = mk(dec!(50000), dec!(50000));
assert_eq!(wrong.line4, dec!(450000));
assert_ne!(
f.line4, wrong.line4,
"the two 1040 lines must not be interchangeable"
);
}
#[test]
fn line6_zero_or_less_zeroes_lines_7_9_and_11_but_still_fills_line_10() {
let f = compute_6251(
Form6251Inputs {
status: FilingStatus::Mfj,
taxable_income_l15: dec!(50000),
agi_l11: dec!(79200),
deduction_l12: dec!(29200),
deduction_l14: dec!(29200),
schedule_a_line7: Usd::ZERO,
itemized: false,
state_refund_sch1_l1: Usd::ZERO,
net_capital_gain: Usd::ZERO,
qualified_dividends: Usd::ZERO,
qdcgt_line5_regular: dec!(50000),
regular_tax_l16: dec!(5536),
schedule_2_line1z: Usd::ZERO,
schedule_3_line1: Usd::ZERO,
},
¶ms(),
&bps(FilingStatus::Mfj),
);
assert_eq!(f.line4, dec!(79200));
assert_eq!(f.line6, Usd::ZERO, "AMTI below the exemption");
assert_eq!(
(f.line7, f.line9, f.line11),
(Usd::ZERO, Usd::ZERO, Usd::ZERO)
);
assert_eq!(
f.line10,
dec!(5536),
"line 10 is still filled — the form says 'go to line 10'"
);
assert!(!f.must_attach());
}
}