us-paycheck-tax 0.1.0

2025 US federal income tax (progressive brackets + standard deduction) and FICA take-home math.
Documentation
//! # us-paycheck-tax
//!
//! 2025 US federal income tax (progressive brackets + standard deduction) and FICA
//! (Social Security + Medicare), the core of a paycheck/take-home calculation. Same
//! figures behind the [StateWage](https://statewage.com/) paycheck calculator.
//!
//! ```
//! use us_paycheck_tax::{take_home, FilingStatus};
//! // $75,000 single: fed tax on (75000-15000) + FICA
//! let (net, fed, fica) = take_home(75_000.0, FilingStatus::Single);
//! assert!(net < 75_000.0 && fed > 0.0 && fica > 0.0);
//! ```

/// 2025 federal standard deductions.
pub const STD_DEDUCTION_SINGLE: f64 = 15_000.0;
pub const STD_DEDUCTION_MARRIED: f64 = 30_000.0;

/// 2025 FICA.
pub const SS_RATE: f64 = 0.062;
pub const SS_WAGE_BASE: f64 = 176_100.0;
pub const MEDICARE_RATE: f64 = 0.0145;
pub const ADDL_MEDICARE_RATE: f64 = 0.009; // above threshold

#[derive(Clone, Copy, PartialEq, Eq)]
pub enum FilingStatus { Single, Married }

/// Standard deduction for the filing status.
pub fn standard_deduction(s: FilingStatus) -> f64 {
    match s { FilingStatus::Single => STD_DEDUCTION_SINGLE, FilingStatus::Married => STD_DEDUCTION_MARRIED }
}

/// 2025 federal brackets as (ceiling, rate); ceiling = f64::INFINITY for the top bracket.
pub fn brackets(s: FilingStatus) -> &'static [(f64, f64)] {
    match s {
        FilingStatus::Single => &[
            (11_925.0, 0.10), (48_475.0, 0.12), (103_350.0, 0.22), (197_300.0, 0.24),
            (250_525.0, 0.32), (626_350.0, 0.35), (f64::INFINITY, 0.37),
        ],
        FilingStatus::Married => &[
            (23_850.0, 0.10), (96_950.0, 0.12), (206_700.0, 0.22), (394_600.0, 0.24),
            (501_050.0, 0.32), (751_600.0, 0.35), (f64::INFINITY, 0.37),
        ],
    }
}

/// Progressive federal income tax on a *taxable* income (after deductions).
pub fn federal_income_tax(taxable: f64, s: FilingStatus) -> f64 {
    if taxable <= 0.0 { return 0.0; }
    let mut tax = 0.0;
    let mut prev = 0.0;
    for &(ceil, rate) in brackets(s) {
        if taxable <= prev { break; }
        let top = taxable.min(ceil);
        tax += (top - prev) * rate;
        prev = ceil;
        if ceil.is_infinite() { break; }
    }
    tax
}

/// FICA: Social Security (capped) + Medicare (+ additional Medicare above threshold).
pub fn fica(gross: f64, s: FilingStatus) -> f64 {
    let ss = SS_RATE * gross.min(SS_WAGE_BASE);
    let medicare = MEDICARE_RATE * gross;
    let threshold = match s { FilingStatus::Single => 200_000.0, FilingStatus::Married => 250_000.0 };
    let addl = if gross > threshold { ADDL_MEDICARE_RATE * (gross - threshold) } else { 0.0 };
    ss + medicare + addl
}

/// Take-home = gross - federal income tax (on taxable) - FICA. Returns (net, federal, fica).
pub fn take_home(gross: f64, s: FilingStatus) -> (f64, f64, f64) {
    let taxable = (gross - standard_deduction(s)).max(0.0);
    let fed = federal_income_tax(taxable, s);
    let f = fica(gross, s);
    (gross - fed - f, fed, f)
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn bracket_progression_single() {
        // taxable 60,000 -> 10% on 11925 + 12% on (48475-11925) + 22% on (60000-48475)
        let t = federal_income_tax(60_000.0, FilingStatus::Single);
        let expect = 0.10*11_925.0 + 0.12*(48_475.0-11_925.0) + 0.22*(60_000.0-48_475.0);
        assert!((t - expect).abs() < 1e-6);
    }
    #[test]
    fn zero_taxable_zero_tax() {
        assert_eq!(federal_income_tax(0.0, FilingStatus::Single), 0.0);
    }
    #[test]
    fn fica_caps_ss() {
        // gross far above wage base: SS only on 176,100
        let f = fica(500_000.0, FilingStatus::Single);
        let expect = 0.062*176_100.0 + 0.0145*500_000.0 + 0.009*(500_000.0-200_000.0);
        assert!((f - expect).abs() < 1e-6);
    }
    #[test]
    fn take_home_positive() {
        let (net, fed, fica) = take_home(75_000.0, FilingStatus::Single);
        assert!(net > 0.0 && fed > 0.0 && fica > 0.0);
    }
}