calcuris-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/LICENSE +21 -0
- package/README.md +110 -0
- package/dist/index.js +303 -0
- package/dist/lib/au-income-tax-data.js +17 -0
- package/dist/lib/au-income-tax-math.js +110 -0
- package/dist/lib/au-stamp-duty-data.js +22 -0
- package/dist/lib/au-stamp-duty-math.js +129 -0
- package/dist/lib/ca-income-tax-data.js +49 -0
- package/dist/lib/ca-income-tax-math.js +95 -0
- package/dist/lib/income-tax-math.js +54 -0
- package/dist/lib/paycheck-math.js +52 -0
- package/dist/lib/property-tax-math.js +16 -0
- package/dist/lib/uk-dividend-tax-data.js +22 -0
- package/dist/lib/uk-dividend-tax-math.js +110 -0
- package/dist/lib/us-federal-tax-data.js +65 -0
- package/dist/lib/us-fica-data.js +23 -0
- package/dist/lib/us-state-income-brackets-2026.js +191 -0
- package/dist/lib/us-state-property-tax-2026.js +58 -0
- package/dist/lib/us-state-tax-data.js +101 -0
- package/dist/lib/us-tax-engine.js +44 -0
- package/package.json +52 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// Moteur stamp duty (droits de mutation) Australie — droit dû par État/Territoire, concession primo-accédant
|
|
2
|
+
// et surtaxe acheteur étranger. Logique PURE, testable. Réutilise le cœur progressif partagé (us-tax-engine)
|
|
3
|
+
// pour les barèmes marginaux ; gère les structures non-marginales réelles de chaque juridiction : formule
|
|
4
|
+
// quadratique NT, bande à taux plat VIC ($960k–$2M), taux plat ACT sur la valeur entière (> $1,455,000),
|
|
5
|
+
// minimum forfaitaire TAS, et barèmes distincts owner-occupier vs investisseur (QLD home concession, ACT).
|
|
6
|
+
// Couche "estimate" honnête : achat résidentiel, concessions standard ; les concessions à test de revenu
|
|
7
|
+
// (ACT) ou réservées au neuf (SA) ne sont PAS déduites automatiquement (signalées dans le contenu).
|
|
8
|
+
import { applyBrackets } from "./us-tax-engine.js";
|
|
9
|
+
import { AU_STAMP_DUTY_2026 } from "./au-stamp-duty-data.js";
|
|
10
|
+
// NT : formule quadratique D = a·V² + b·V (V = valeur ÷ vDivisor) jusqu'au cutoff, puis paliers à taux plat
|
|
11
|
+
// sur la valeur entière.
|
|
12
|
+
function ntDuty(value, f) {
|
|
13
|
+
if (value <= f.cutoff) {
|
|
14
|
+
const V = value / f.vDivisor;
|
|
15
|
+
return f.a * V * V + f.b * V;
|
|
16
|
+
}
|
|
17
|
+
for (const [upTo, rate] of f.flatTiers) {
|
|
18
|
+
if (upTo === null || value <= upTo)
|
|
19
|
+
return value * rate;
|
|
20
|
+
}
|
|
21
|
+
return value * f.flatTiers[f.flatTiers.length - 1][1];
|
|
22
|
+
}
|
|
23
|
+
function withFee(row, tax, slices, label) {
|
|
24
|
+
return { duty: tax + (row.baseFee || 0), slices, label };
|
|
25
|
+
}
|
|
26
|
+
// Calcule le droit "de base" (avant concession primo et surtaxe étrangère) sur le barème applicable selon
|
|
27
|
+
// l'usage (résidence principale vs investissement) et la valeur.
|
|
28
|
+
function baseDutyFor(row, value, ppr) {
|
|
29
|
+
// NT : formule (identique owner-occupier / investissement).
|
|
30
|
+
if (row.ntFormula)
|
|
31
|
+
return { duty: ntDuty(value, row.ntFormula), slices: [], label: "formula" };
|
|
32
|
+
// VIC : barème résidence principale concessionnel jusqu'à pprUpper.
|
|
33
|
+
if (ppr && row.pprBrackets && row.pprUpper && value <= row.pprUpper) {
|
|
34
|
+
const r = applyBrackets(value, row.pprBrackets);
|
|
35
|
+
return withFee(row, r.tax, r.slices, "owner-occupier");
|
|
36
|
+
}
|
|
37
|
+
// VIC : bande à taux plat sur la valeur entière, puis base + taux au-dessus.
|
|
38
|
+
if (row.flatBand) {
|
|
39
|
+
const fb = row.flatBand;
|
|
40
|
+
if (value > fb.lower && value <= fb.upper)
|
|
41
|
+
return withFee(row, value * fb.rate, [], "standard");
|
|
42
|
+
if (value > fb.upper)
|
|
43
|
+
return withFee(row, fb.upper * fb.rate + (value - fb.upper) * fb.aboveRate, [], "standard");
|
|
44
|
+
// value <= fb.lower → on continue vers le barème marginal.
|
|
45
|
+
}
|
|
46
|
+
// ACT : taux plat sur la valeur entière au-dessus du seuil.
|
|
47
|
+
if (row.flatAbove && value > row.flatAbove.threshold) {
|
|
48
|
+
return withFee(row, value * row.flatAbove.rate, [], ppr && row.investmentBrackets ? "owner-occupier" : "standard");
|
|
49
|
+
}
|
|
50
|
+
// Sélection du barème marginal selon l'usage.
|
|
51
|
+
let brackets = row.brackets;
|
|
52
|
+
let label = "standard";
|
|
53
|
+
if (ppr && row.homeConcessionBrackets) {
|
|
54
|
+
brackets = row.homeConcessionBrackets;
|
|
55
|
+
label = "owner-occupier";
|
|
56
|
+
} // QLD
|
|
57
|
+
else if (!ppr && row.investmentBrackets) {
|
|
58
|
+
brackets = row.investmentBrackets;
|
|
59
|
+
label = "investment";
|
|
60
|
+
} // ACT
|
|
61
|
+
else if (row.investmentBrackets) {
|
|
62
|
+
label = "owner-occupier";
|
|
63
|
+
} // ACT défaut (ppr)
|
|
64
|
+
const r = applyBrackets(value, brackets || []);
|
|
65
|
+
return withFee(row, r.tax, r.slices, label);
|
|
66
|
+
}
|
|
67
|
+
export function computeAuStampDuty(inp) {
|
|
68
|
+
const row = AU_STAMP_DUTY_2026[inp.stateCode] || AU_STAMP_DUTY_2026.NSW;
|
|
69
|
+
const value = Math.max(inp.propertyValue || 0, 0);
|
|
70
|
+
const ppr = inp.principalResidence !== false; // défaut owner-occupier
|
|
71
|
+
const base = baseDutyFor(row, value, ppr);
|
|
72
|
+
const baseDuty = base.duty;
|
|
73
|
+
// Concession primo-accédant.
|
|
74
|
+
let concession = 0;
|
|
75
|
+
let fhbApplies = false;
|
|
76
|
+
const fh = row.firstHome;
|
|
77
|
+
if (inp.firstHomeBuyer && fh && value > 0) {
|
|
78
|
+
if (fh.method === "linear") {
|
|
79
|
+
if (value <= fh.fullExemptThreshold) {
|
|
80
|
+
concession = baseDuty;
|
|
81
|
+
fhbApplies = true;
|
|
82
|
+
}
|
|
83
|
+
else if (value < fh.concessionUpper && fh.concessionUpper > fh.fullExemptThreshold) {
|
|
84
|
+
const frac = (fh.concessionUpper - value) / (fh.concessionUpper - fh.fullExemptThreshold);
|
|
85
|
+
concession = baseDuty * Math.min(Math.max(frac, 0), 1);
|
|
86
|
+
fhbApplies = true;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
else if (fh.method === "cliff") {
|
|
90
|
+
if (value <= fh.fullExemptThreshold) {
|
|
91
|
+
concession = baseDuty;
|
|
92
|
+
fhbApplies = true;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// newOnly (SA) / incomeTest (ACT) / none (NT) → pas de réduction automatique (dépend du neuf / du revenu).
|
|
96
|
+
}
|
|
97
|
+
const dutyAfterConcession = Math.max(baseDuty - concession, 0);
|
|
98
|
+
const foreignSurcharge = inp.foreignResident ? value * row.foreignSurchargeRate : 0;
|
|
99
|
+
const totalDuty = dutyAfterConcession + foreignSurcharge;
|
|
100
|
+
const effectiveRate = value > 0 ? (totalDuty / value) * 100 : 0;
|
|
101
|
+
return {
|
|
102
|
+
stateCode: row.code,
|
|
103
|
+
propertyValue: value,
|
|
104
|
+
baseDuty,
|
|
105
|
+
concession,
|
|
106
|
+
dutyAfterConcession,
|
|
107
|
+
foreignSurcharge,
|
|
108
|
+
totalDuty,
|
|
109
|
+
effectiveRate,
|
|
110
|
+
fhbApplies,
|
|
111
|
+
fhbNote: fh ? fh.note : "",
|
|
112
|
+
scheduleLabel: base.label,
|
|
113
|
+
slices: base.slices,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
// Compare le droit dû pour une même valeur dans plusieurs États (moat : "où acheter coûte le moins cher").
|
|
117
|
+
export function compareStates(propertyValue, opts, codes) {
|
|
118
|
+
const out = {};
|
|
119
|
+
for (const code of codes) {
|
|
120
|
+
out[code] = computeAuStampDuty({
|
|
121
|
+
propertyValue,
|
|
122
|
+
stateCode: code,
|
|
123
|
+
firstHomeBuyer: opts.firstHomeBuyer,
|
|
124
|
+
foreignResident: opts.foreignResident,
|
|
125
|
+
principalResidence: opts.principalResidence,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
return out;
|
|
129
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Barème fédéral 2026 + montant personnel de base fédéral (montant rehaussé avec phase-out
|
|
2
|
+
// entre 181,440 $ et 258,482 $ de revenu net).
|
|
3
|
+
export const CA_FEDERAL_2026 = {
|
|
4
|
+
brackets: [{ rate: 0.14, lower: 0, upper: 58523 }, { rate: 0.205, lower: 58523, upper: 117045 }, { rate: 0.26, lower: 117045, upper: 181440 }, { rate: 0.29, lower: 181440, upper: 258482 }, { rate: 0.33, lower: 258482, upper: null }],
|
|
5
|
+
bpaMax: 16452,
|
|
6
|
+
bpaMin: 14829,
|
|
7
|
+
bpaPhaseStart: 181440,
|
|
8
|
+
bpaPhaseEnd: 258482,
|
|
9
|
+
creditRate: 0.14,
|
|
10
|
+
qcAbatementPct: 0.165,
|
|
11
|
+
};
|
|
12
|
+
// Cotisations de paie 2026 (côté employé). CPP/QPP : portion "de base" = crédit non remboursable,
|
|
13
|
+
// portion "bonifiée" (1ʳᵉ additionnelle + CPP2/QPP2) = déduction du revenu imposable.
|
|
14
|
+
export const CA_PAYROLL_2026 = {
|
|
15
|
+
cpp: { rate: 0.0595, baseRate: 0.0495, ympe: 74600, exemption: 3500, maxContribution: 4230.45 },
|
|
16
|
+
cpp2: { rate: 0.04, lower: 74600, upper: 85000, maxContribution: 416 },
|
|
17
|
+
qpp: { rate: 0.063, baseRate: 0.053, ympe: 74600, exemption: 3500, maxContribution: 4479.3 },
|
|
18
|
+
qpp2: { rate: 0.04, lower: 74600, upper: 85000, maxContribution: 416 },
|
|
19
|
+
ei: { rate: 0.0163, mie: 68900, maxPremium: 1123.07 },
|
|
20
|
+
eiQc: { rate: 0.013, mie: 68900, maxPremium: 895.7 },
|
|
21
|
+
qpip: { rate: 0.0043, mie: 103000, maxPremium: 442.9 },
|
|
22
|
+
};
|
|
23
|
+
// 13 juridictions (10 provinces + 3 territoires).
|
|
24
|
+
export const CA_INCOME_TAX_2026 = {
|
|
25
|
+
AB: { code: "AB", name: "Alberta", payroll: "CPP", bpa: 22769, brackets: [{ rate: 0.08, lower: 0, upper: 61200 }, { rate: 0.1, lower: 61200, upper: 154259 }, { rate: 0.12, lower: 154259, upper: 185111 }, { rate: 0.13, lower: 185111, upper: 246813 }, { rate: 0.14, lower: 246813, upper: 370220 }, { rate: 0.15, lower: 370220, upper: null }] },
|
|
26
|
+
BC: { code: "BC", name: "British Columbia", payroll: "CPP", bpa: 13216, brackets: [{ rate: 0.056, lower: 0, upper: 50363 }, { rate: 0.077, lower: 50363, upper: 100728 }, { rate: 0.105, lower: 100728, upper: 115648 }, { rate: 0.1229, lower: 115648, upper: 140430 }, { rate: 0.147, lower: 140430, upper: 190405 }, { rate: 0.168, lower: 190405, upper: 265545 }, { rate: 0.205, lower: 265545, upper: null }] },
|
|
27
|
+
MB: { code: "MB", name: "Manitoba", payroll: "CPP", bpa: 15780, brackets: [{ rate: 0.108, lower: 0, upper: 47564 }, { rate: 0.1275, lower: 47564, upper: 101200 }, { rate: 0.174, lower: 101200, upper: null }] },
|
|
28
|
+
NB: { code: "NB", name: "New Brunswick", payroll: "CPP", bpa: 13664, brackets: [{ rate: 0.094, lower: 0, upper: 52333 }, { rate: 0.14, lower: 52333, upper: 104666 }, { rate: 0.16, lower: 104666, upper: 193861 }, { rate: 0.195, lower: 193861, upper: null }] },
|
|
29
|
+
NL: { code: "NL", name: "Newfoundland and Labrador", payroll: "CPP", bpa: 13094, brackets: [{ rate: 0.087, lower: 0, upper: 44678 }, { rate: 0.145, lower: 44678, upper: 89354 }, { rate: 0.158, lower: 89354, upper: 159528 }, { rate: 0.178, lower: 159528, upper: 223340 }, { rate: 0.198, lower: 223340, upper: 285319 }, { rate: 0.208, lower: 285319, upper: 570638 }, { rate: 0.213, lower: 570638, upper: 1141275 }, { rate: 0.218, lower: 1141275, upper: null }] },
|
|
30
|
+
NS: { code: "NS", name: "Nova Scotia", payroll: "CPP", bpa: 11932, brackets: [{ rate: 0.0879, lower: 0, upper: 30995 }, { rate: 0.1495, lower: 30995, upper: 61991 }, { rate: 0.1667, lower: 61991, upper: 97417 }, { rate: 0.175, lower: 97417, upper: 157124 }, { rate: 0.21, lower: 157124, upper: null }] },
|
|
31
|
+
NT: { code: "NT", name: "Northwest Territories", payroll: "CPP", bpa: 18198, brackets: [{ rate: 0.059, lower: 0, upper: 53003 }, { rate: 0.086, lower: 53003, upper: 106009 }, { rate: 0.122, lower: 106009, upper: 172346 }, { rate: 0.1405, lower: 172346, upper: null }] },
|
|
32
|
+
NU: { code: "NU", name: "Nunavut", payroll: "CPP", bpa: 19659, brackets: [{ rate: 0.04, lower: 0, upper: 55801 }, { rate: 0.07, lower: 55801, upper: 111602 }, { rate: 0.09, lower: 111602, upper: 181439 }, { rate: 0.115, lower: 181439, upper: null }] },
|
|
33
|
+
ON: { code: "ON", name: "Ontario", payroll: "CPP", bpa: 12989, brackets: [{ rate: 0.0505, lower: 0, upper: 53891 }, { rate: 0.0915, lower: 53891, upper: 107785 }, { rate: 0.1116, lower: 107785, upper: 150000 }, { rate: 0.1216, lower: 150000, upper: 220000 }, { rate: 0.1316, lower: 220000, upper: null }], surtax: { rate1: 0.2, threshold1: 5818, rate2: 0.36, threshold2: 7446 } },
|
|
34
|
+
PE: { code: "PE", name: "Prince Edward Island", payroll: "CPP", bpa: 15000, brackets: [{ rate: 0.095, lower: 0, upper: 33928 }, { rate: 0.1347, lower: 33928, upper: 65820 }, { rate: 0.166, lower: 65820, upper: 106890 }, { rate: 0.1762, lower: 106890, upper: 142520 }, { rate: 0.19, lower: 142520, upper: 200000 }, { rate: 0.2, lower: 200000, upper: null }] },
|
|
35
|
+
QC: { code: "QC", name: "Quebec", payroll: "QPP", bpa: 18952, brackets: [{ rate: 0.14, lower: 0, upper: 54345 }, { rate: 0.19, lower: 54345, upper: 108680 }, { rate: 0.24, lower: 108680, upper: 132245 }, { rate: 0.2575, lower: 132245, upper: null }], abatementPct: 0.165 },
|
|
36
|
+
SK: { code: "SK", name: "Saskatchewan", payroll: "CPP", bpa: 20381, brackets: [{ rate: 0.105, lower: 0, upper: 54532 }, { rate: 0.125, lower: 54532, upper: 155805 }, { rate: 0.145, lower: 155805, upper: null }] },
|
|
37
|
+
YT: { code: "YT", name: "Yukon", payroll: "CPP", bpa: 16452, brackets: [{ rate: 0.064, lower: 0, upper: 58523 }, { rate: 0.09, lower: 58523, upper: 117045 }, { rate: 0.109, lower: 117045, upper: 181440 }, { rate: 0.128, lower: 181440, upper: 500000 }, { rate: 0.15, lower: 500000, upper: null }], usesFederalBpa: true },
|
|
38
|
+
};
|
|
39
|
+
export const CA_INCOME_TAX_AS_OF = "2026";
|
|
40
|
+
export const CA_INCOME_TAX_SOURCES = [
|
|
41
|
+
{ label: "Canada Revenue Agency — Current year tax rates and income brackets (2026), federal + provincial/territorial", url: "https://www.canada.ca/en/revenue-agency/services/tax/individuals/tax-rates-brackets/current-year.html" },
|
|
42
|
+
{ label: "Revenu Québec — Income tax rates (2026)", url: "https://www.revenuquebec.ca/en/citizens/income-tax-return/completing-your-income-tax-return/income-tax-rates/" },
|
|
43
|
+
{ label: "CRA — CPP contribution rates, maximums and exemptions (2026)", url: "https://www.canada.ca/en/revenue-agency/services/tax/businesses/topics/payroll/payroll-deductions-contributions/canada-pension-plan-cpp/cpp-contribution-rates-maximums-exemptions.html" },
|
|
44
|
+
{ label: "CRA — Second additional CPP (CPP2) rates and maximums (2026)", url: "https://www.canada.ca/en/revenue-agency/services/tax/businesses/topics/payroll/calculating-deductions/making-deductions/second-additional-cpp-contribution-rates-maximums.html" },
|
|
45
|
+
{ label: "CRA — EI premium rates and maximums (2026, federal + Quebec)", url: "https://www.canada.ca/en/revenue-agency/services/tax/businesses/topics/payroll/payroll-deductions-contributions/employment-insurance-ei/ei-premium-rates-maximums.html" },
|
|
46
|
+
{ label: "Revenu Québec — QPIP maximum insurable earnings and premium rate (2026)", url: "https://www.revenuquebec.ca/en/businesses/source-deductions-and-employer-contributions/calculating-source-deductions-and-contributions/qpip-premiums/maximum-insurable-earnings-and-premium-rate/" },
|
|
47
|
+
{ label: "Retraite Québec — QPP contribution rate 2026 (6.30%)", url: "https://www.retraitequebec.gouv.qc.ca/en/professionals-employers/your-role-quebec-pension-plan/contributions-quebec-pension-plan-qpp" },
|
|
48
|
+
{ label: "TaxTips.ca — 2026 provincial/territorial basic personal amounts and Ontario surtax thresholds", url: "https://www.taxtips.ca/tax-rates.htm" },
|
|
49
|
+
];
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// Moteur income-tax / take-home Canada (2026) — impôt fédéral + provincial + CPP/QPP + CPP2/QPP2 + EI/QPIP,
|
|
2
|
+
// salaire net réel. Logique PURE, testable. Réutilise le cœur progressif partagé (us-tax-engine) — zéro
|
|
3
|
+
// duplication du calcul par tranche (le breakdown par tranche = le moat). Couche "estimate" honnête : revenu
|
|
4
|
+
// d'emploi côté employé, crédits standard (BPA + base CPP/QPP + EI/QPIP), pas de dividendes/gains, pas de
|
|
5
|
+
// réductions provinciales pour bas revenus. Québec géré spécifiquement (QPP, QPIP, EI réduit, abattement 16,5 %).
|
|
6
|
+
import { applyBrackets, effectiveRate } from "./us-tax-engine.js";
|
|
7
|
+
import { CA_FEDERAL_2026, CA_PAYROLL_2026, CA_INCOME_TAX_2026 } from "./ca-income-tax-data.js";
|
|
8
|
+
// BPA fédéral avec phase-out de la portion bonifiée entre les seuils des tranches 29 % et 33 %.
|
|
9
|
+
export function federalBpaFor(netIncome) {
|
|
10
|
+
const f = CA_FEDERAL_2026;
|
|
11
|
+
if (netIncome <= f.bpaPhaseStart)
|
|
12
|
+
return f.bpaMax;
|
|
13
|
+
if (netIncome >= f.bpaPhaseEnd)
|
|
14
|
+
return f.bpaMin;
|
|
15
|
+
const t = (netIncome - f.bpaPhaseStart) / (f.bpaPhaseEnd - f.bpaPhaseStart);
|
|
16
|
+
return f.bpaMax - (f.bpaMax - f.bpaMin) * t;
|
|
17
|
+
}
|
|
18
|
+
// Cotisations employé : CPP/QPP (base = crédit, bonifié = déduction), CPP2/QPP2, EI (réduit en QC) + QPIP.
|
|
19
|
+
export function payrollFor(gross, system) {
|
|
20
|
+
const P = CA_PAYROLL_2026;
|
|
21
|
+
const g = Math.max(gross, 0);
|
|
22
|
+
const main = system === "QPP" ? P.qpp : P.cpp;
|
|
23
|
+
const second = system === "QPP" ? P.qpp2 : P.cpp2;
|
|
24
|
+
const contributory = Math.min(Math.max(g - main.exemption, 0), main.ympe - main.exemption);
|
|
25
|
+
const cpp = Math.min(contributory * main.rate, main.maxContribution);
|
|
26
|
+
const baseCpp = contributory * main.baseRate; // portion de base → crédit non remboursable
|
|
27
|
+
const enhancedFirst = cpp - baseCpp; // 1ʳᵉ additionnelle → déduction
|
|
28
|
+
const cpp2Earnings = Math.min(Math.max(g - second.lower, 0), second.upper - second.lower);
|
|
29
|
+
const cpp2 = Math.min(cpp2Earnings * second.rate, second.maxContribution);
|
|
30
|
+
const enhanced = enhancedFirst + cpp2; // total déductible
|
|
31
|
+
const eiCfg = system === "QPP" ? P.eiQc : P.ei;
|
|
32
|
+
const ei = Math.min(Math.min(g, eiCfg.mie) * eiCfg.rate, eiCfg.maxPremium);
|
|
33
|
+
const qpip = system === "QPP" ? Math.min(Math.min(g, P.qpip.mie) * P.qpip.rate, P.qpip.maxPremium) : 0;
|
|
34
|
+
return { cpp, cpp2, baseCpp, enhanced, ei, qpip };
|
|
35
|
+
}
|
|
36
|
+
function ontarioSurtax(provTaxBeforeSurtax, s) {
|
|
37
|
+
return s.rate1 * Math.max(provTaxBeforeSurtax - s.threshold1, 0) + s.rate2 * Math.max(provTaxBeforeSurtax - s.threshold2, 0);
|
|
38
|
+
}
|
|
39
|
+
export function computeCaIncomeTax(inp) {
|
|
40
|
+
const row = CA_INCOME_TAX_2026[inp.provinceCode] || CA_INCOME_TAX_2026.ON;
|
|
41
|
+
const gross = Math.max(inp.grossIncome || 0, 0);
|
|
42
|
+
const rrsp = Math.max(inp.rrspContribution || 0, 0);
|
|
43
|
+
const pay = payrollFor(gross, row.payroll);
|
|
44
|
+
const taxableIncome = Math.max(gross - rrsp - pay.enhanced, 0);
|
|
45
|
+
// ---- Fédéral ----
|
|
46
|
+
const federalBpa = federalBpaFor(taxableIncome);
|
|
47
|
+
const fedProg = applyBrackets(taxableIncome, CA_FEDERAL_2026.brackets);
|
|
48
|
+
const federalCredits = (federalBpa + pay.baseCpp + pay.ei + pay.qpip) * CA_FEDERAL_2026.creditRate;
|
|
49
|
+
const fedBeforeAbatement = Math.max(fedProg.tax - federalCredits, 0);
|
|
50
|
+
const federalAbatement = row.abatementPct ? fedBeforeAbatement * row.abatementPct : 0;
|
|
51
|
+
const federalTax = fedBeforeAbatement - federalAbatement;
|
|
52
|
+
// ---- Provincial / territorial ----
|
|
53
|
+
const provLowest = row.brackets[0].rate;
|
|
54
|
+
const provincialBpa = row.usesFederalBpa ? federalBpa : row.bpa;
|
|
55
|
+
const provProg = applyBrackets(taxableIncome, row.brackets);
|
|
56
|
+
const provincialCredits = (provincialBpa + pay.baseCpp + pay.ei + pay.qpip) * provLowest;
|
|
57
|
+
const provBeforeSurtax = Math.max(provProg.tax - provincialCredits, 0);
|
|
58
|
+
const provincialSurtax = row.surtax ? ontarioSurtax(provBeforeSurtax, row.surtax) : 0;
|
|
59
|
+
const provincialTax = provBeforeSurtax + provincialSurtax;
|
|
60
|
+
// ---- Totaux ----
|
|
61
|
+
const cppTotal = pay.cpp + pay.cpp2;
|
|
62
|
+
const payrollTotal = cppTotal + pay.ei + pay.qpip;
|
|
63
|
+
const incomeTax = federalTax + provincialTax;
|
|
64
|
+
const totalTax = incomeTax + payrollTotal;
|
|
65
|
+
const netIncome = gross - totalTax;
|
|
66
|
+
// ---- Taux marginal d'impôt combiné (féd après abattement + prov avec multiplicateur de surtaxe) ----
|
|
67
|
+
const fedMarg = fedProg.marginalRate * (1 - (row.abatementPct || 0));
|
|
68
|
+
let surtaxMult = 1;
|
|
69
|
+
if (row.surtax) {
|
|
70
|
+
if (provBeforeSurtax > row.surtax.threshold2)
|
|
71
|
+
surtaxMult = 1 + row.surtax.rate1 + row.surtax.rate2;
|
|
72
|
+
else if (provBeforeSurtax > row.surtax.threshold1)
|
|
73
|
+
surtaxMult = 1 + row.surtax.rate1;
|
|
74
|
+
}
|
|
75
|
+
const marginalRate = fedMarg + provProg.marginalRate * surtaxMult;
|
|
76
|
+
return {
|
|
77
|
+
provinceCode: row.code, grossIncome: gross,
|
|
78
|
+
cpp: pay.cpp, cpp2: pay.cpp2, cppTotal, ei: pay.ei, qpip: pay.qpip, payrollTotal,
|
|
79
|
+
rrsp, enhancedDeduction: pay.enhanced, taxableIncome,
|
|
80
|
+
federalBpa, federalTaxBeforeCredits: fedProg.tax, federalCredits, federalAbatement, federalTax, fedSlices: fedProg.slices,
|
|
81
|
+
provincialBpa, provincialTaxBeforeCredits: provProg.tax, provincialCredits, provincialSurtax, provincialTax, provSlices: provProg.slices,
|
|
82
|
+
incomeTax, totalTax, netIncome,
|
|
83
|
+
averageTaxRate: effectiveRate(incomeTax, gross),
|
|
84
|
+
averageDeductionRate: effectiveRate(totalTax, gross),
|
|
85
|
+
marginalRate,
|
|
86
|
+
takeHomePct: gross > 0 ? netIncome / gross : 0,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
// Compare le take-home d'un même revenu dans plusieurs provinces (moat : "où garde-t-on le plus ?").
|
|
90
|
+
export function compareProvinces(grossIncome, rrspContribution, codes) {
|
|
91
|
+
const out = {};
|
|
92
|
+
for (const code of codes)
|
|
93
|
+
out[code] = computeCaIncomeTax({ grossIncome, provinceCode: code, rrspContribution });
|
|
94
|
+
return out;
|
|
95
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Moteur income-tax US (liability annuelle fédérale + estimation état) — logique pure, testable.
|
|
2
|
+
// Réutilise le cœur progressif partagé (us-tax-engine) ; aucune duplication du calcul par tranche.
|
|
3
|
+
import { applyBrackets, effectiveRate } from "./us-tax-engine.js";
|
|
4
|
+
import { FEDERAL_BY_YEAR } from "./us-federal-tax-data.js";
|
|
5
|
+
import { STATE_TAX_BY_CODE, estimateStateTax } from "./us-state-tax-data.js";
|
|
6
|
+
function childTaxCredit(fed, status, dependents, magi) {
|
|
7
|
+
const n = Math.max(Math.floor(dependents), 0);
|
|
8
|
+
if (n === 0)
|
|
9
|
+
return 0;
|
|
10
|
+
const max = n * fed.childTaxCredit.perChild;
|
|
11
|
+
const start = fed.childTaxCredit.phaseoutStart[status];
|
|
12
|
+
if (magi <= start)
|
|
13
|
+
return max;
|
|
14
|
+
const reduction = Math.ceil((magi - start) / 1000) * fed.childTaxCredit.phaseoutPer1000;
|
|
15
|
+
return Math.max(max - reduction, 0);
|
|
16
|
+
}
|
|
17
|
+
export function computeIncomeTax(inp) {
|
|
18
|
+
const fed = FEDERAL_BY_YEAR[inp.taxYear];
|
|
19
|
+
const agi = Math.max(inp.grossIncome - Math.max(inp.preTaxContributions, 0), 0);
|
|
20
|
+
const std = fed.standardDeduction[inp.filingStatus];
|
|
21
|
+
const itemized = Math.max(inp.itemizedAmount, 0);
|
|
22
|
+
const useItemized = inp.useItemized && itemized > std; // standard gagne par défaut
|
|
23
|
+
const deductionApplied = useItemized ? itemized : std;
|
|
24
|
+
const taxableIncome = Math.max(agi - deductionApplied, 0); // clamp ≥ 0 AVANT les tranches
|
|
25
|
+
const prog = applyBrackets(taxableIncome, fed.brackets[inp.filingStatus]);
|
|
26
|
+
const ctc = childTaxCredit(fed, inp.filingStatus, inp.dependents, agi);
|
|
27
|
+
const federalTax = Math.max(prog.tax - ctc, 0); // CTC = crédit, après l'impôt
|
|
28
|
+
// État : base = AGI (la déduction standard d'état est gérée dans estimateStateTax, ≠ fédérale).
|
|
29
|
+
const stateRow = inp.stateCode ? STATE_TAX_BY_CODE[inp.stateCode] : undefined;
|
|
30
|
+
const stateTax = stateRow ? estimateStateTax(stateRow, agi, inp.filingStatus) : 0;
|
|
31
|
+
const totalTax = federalTax + stateTax;
|
|
32
|
+
return {
|
|
33
|
+
agi,
|
|
34
|
+
deductionApplied,
|
|
35
|
+
deductionType: useItemized ? "itemized" : "standard",
|
|
36
|
+
taxableIncome,
|
|
37
|
+
taxBeforeCredits: prog.tax,
|
|
38
|
+
childTaxCredit: ctc,
|
|
39
|
+
federalTax,
|
|
40
|
+
stateTax,
|
|
41
|
+
totalTax,
|
|
42
|
+
marginalRate: prog.marginalRate,
|
|
43
|
+
effectiveRate: effectiveRate(federalTax, inp.grossIncome),
|
|
44
|
+
takeHome: inp.grossIncome - totalTax,
|
|
45
|
+
slices: prog.slices,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
// Comparaison de statuts (moat : MFJ vs MFS, etc.).
|
|
49
|
+
export function compareStatuses(inp, statuses) {
|
|
50
|
+
const out = {};
|
|
51
|
+
for (const s of statuses)
|
|
52
|
+
out[s] = computeIncomeTax({ ...inp, filingStatus: s });
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Moteur paycheck US (take-home par période de paie) — logique pure, testable.
|
|
2
|
+
// Règle d'or : ANNUALISER le brut → appliquer barèmes/plafonds sur l'annuel → diviser par les périodes.
|
|
3
|
+
// Jamais de barème fédéral ni de plafond SS calculé sur un montant par période isolé.
|
|
4
|
+
import { applyBrackets, PERIODS_PER_YEAR } from "./us-tax-engine.js";
|
|
5
|
+
import { FEDERAL_BY_YEAR } from "./us-federal-tax-data.js";
|
|
6
|
+
import { FICA_2026 } from "./us-fica-data.js";
|
|
7
|
+
import { STATE_TAX_BY_CODE, estimateStateTax } from "./us-state-tax-data.js";
|
|
8
|
+
const line = (annual, periods) => ({ annual, perPeriod: periods > 0 ? annual / periods : 0 });
|
|
9
|
+
export function computePaycheck(inp) {
|
|
10
|
+
const fed = FEDERAL_BY_YEAR[inp.taxYear];
|
|
11
|
+
const periods = PERIODS_PER_YEAR[inp.payFrequency];
|
|
12
|
+
const grossAnnual = inp.payType === "salary"
|
|
13
|
+
? Math.max(inp.annualSalary, 0)
|
|
14
|
+
: Math.max(inp.hourlyRate, 0) * Math.max(inp.hoursPerWeek, 0) * 52;
|
|
15
|
+
const retirementAnnual = Math.max(inp.preTaxRetirement, 0) * periods;
|
|
16
|
+
const healthAnnual = Math.max(inp.preTaxHealth, 0) * periods;
|
|
17
|
+
const preTaxAnnual = retirementAnnual + healthAnnual;
|
|
18
|
+
// Fédéral : annualisé d'abord. 401(k) ET santé réduisent le revenu imposable fédéral.
|
|
19
|
+
const fedTaxableAnnual = Math.max(grossAnnual - retirementAnnual - healthAnnual - fed.standardDeduction[inp.filingStatus], 0);
|
|
20
|
+
const fedAnnual = applyBrackets(fedTaxableAnnual, fed.brackets[inp.filingStatus]).tax + Math.max(inp.extraWithholding, 0) * periods;
|
|
21
|
+
// FICA : la base = brut − santé(125) SEULEMENT (le 401(k) reste soumis à FICA). Plafonds annuels.
|
|
22
|
+
const ficaBaseAnnual = Math.max(grossAnnual - healthAnnual, 0);
|
|
23
|
+
const ssAnnual = Math.min(ficaBaseAnnual, FICA_2026.socialSecurity.wageBase) * FICA_2026.socialSecurity.rateEmployee;
|
|
24
|
+
const medicareAnnual = ficaBaseAnnual * FICA_2026.medicare.rateEmployee;
|
|
25
|
+
const addlThreshold = FICA_2026.additionalMedicare.thresholdByStatus[inp.filingStatus];
|
|
26
|
+
const addlMedicareAnnual = Math.max(ficaBaseAnnual - addlThreshold, 0) * FICA_2026.additionalMedicare.rate;
|
|
27
|
+
// État : base = AGI état (brut − pré-tax retraite + santé). Estimation (cf. us-state-tax-data).
|
|
28
|
+
const stateRow = inp.stateCode ? STATE_TAX_BY_CODE[inp.stateCode] : undefined;
|
|
29
|
+
const stateAgiAnnual = Math.max(grossAnnual - retirementAnnual - healthAnnual, 0);
|
|
30
|
+
const stateAnnual = stateRow ? estimateStateTax(stateRow, stateAgiAnnual, inp.filingStatus) : 0;
|
|
31
|
+
const netAnnual = Math.max(grossAnnual - preTaxAnnual - fedAnnual - ssAnnual - medicareAnnual - addlMedicareAnnual - stateAnnual, 0);
|
|
32
|
+
return {
|
|
33
|
+
periodsPerYear: periods,
|
|
34
|
+
grossPerPeriod: periods > 0 ? grossAnnual / periods : 0,
|
|
35
|
+
grossAnnual,
|
|
36
|
+
hourlyEquivalent: inp.payType === "hourly" ? Math.max(inp.hourlyRate, 0) : salaryToHourly(grossAnnual, inp.hoursPerWeek || 40),
|
|
37
|
+
preTax: line(preTaxAnnual, periods),
|
|
38
|
+
federal: line(fedAnnual, periods),
|
|
39
|
+
socialSecurity: line(ssAnnual, periods),
|
|
40
|
+
medicare: line(medicareAnnual, periods),
|
|
41
|
+
additionalMedicare: line(addlMedicareAnnual, periods),
|
|
42
|
+
state: line(stateAnnual, periods),
|
|
43
|
+
net: line(netAnnual, periods),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export function salaryToHourly(annual, hoursPerWeek) {
|
|
47
|
+
const h = hoursPerWeek > 0 ? hoursPerWeek : 40;
|
|
48
|
+
return annual / (h * 52);
|
|
49
|
+
}
|
|
50
|
+
export function hourlyToSalary(rate, hoursPerWeek) {
|
|
51
|
+
return rate * (hoursPerWeek > 0 ? hoursPerWeek : 40) * 52;
|
|
52
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function computePropertyTax(inp) {
|
|
2
|
+
const value = Math.max(inp.homeValue || 0, 0);
|
|
3
|
+
const rate = Math.max(inp.ratePct || 0, 0) / 100;
|
|
4
|
+
const ratio = Math.max(inp.assessmentRatioPct ?? 100, 0) / 100;
|
|
5
|
+
const exemption = Math.max(inp.exemption || 0, 0);
|
|
6
|
+
const assessedValue = value * ratio;
|
|
7
|
+
const taxableValue = Math.max(assessedValue - exemption, 0);
|
|
8
|
+
const annualTax = taxableValue * rate;
|
|
9
|
+
return {
|
|
10
|
+
assessedValue,
|
|
11
|
+
taxableValue,
|
|
12
|
+
annualTax,
|
|
13
|
+
monthlyTax: annualTax / 12,
|
|
14
|
+
effectiveRatePct: value > 0 ? (annualTax / value) * 100 : 0,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Données fiscales UK pour le dividend tax calculator — source autoritative GOV.UK (scrapée 2026-06-29).
|
|
2
|
+
// England, Wales & Northern Ireland (hors Écosse, qui a ses propres bandes de revenu mais PAS de dividende —
|
|
3
|
+
// le dividende reste aux taux UK). Deux années : 2026/27 (live, hausse d'avril 2026) et 2025/26 (comparaison).
|
|
4
|
+
// Seuils de revenu gelés (identiques 2025/26 et 2026/27) ; SEULS les taux DIVIDENDE changent en avril 2026.
|
|
5
|
+
const COMMON = {
|
|
6
|
+
personalAllowance: 12570,
|
|
7
|
+
paTaperStart: 100000,
|
|
8
|
+
paGoneAt: 125140,
|
|
9
|
+
basicBandWidth: 37700,
|
|
10
|
+
additionalThreshold: 125140,
|
|
11
|
+
dividendAllowance: 500,
|
|
12
|
+
itBasic: 0.20,
|
|
13
|
+
itHigher: 0.40,
|
|
14
|
+
itAdditional: 0.45,
|
|
15
|
+
};
|
|
16
|
+
export const DIVIDEND_YEARS = {
|
|
17
|
+
// 2026/27 : hausse de 2 points des taux base & supérieur (Budget nov. 2025), additionnel inchangé. GOV.UK.
|
|
18
|
+
"2026/27": { label: "2026/27", ...COMMON, divBasic: 0.1075, divHigher: 0.3575, divAdditional: 0.3935 },
|
|
19
|
+
// 2025/26 : taux historiques (pour le différentiel "extra tax").
|
|
20
|
+
"2025/26": { label: "2025/26", ...COMMON, divBasic: 0.0875, divHigher: 0.3375, divAdditional: 0.3935 },
|
|
21
|
+
};
|
|
22
|
+
export const DIVIDEND_SOURCE = "GOV.UK — Tax on dividends (rates from 6 April 2026), as of 2026-06-29";
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// Moteur PUR du dividend tax UK (zéro React/DOM). Méthode HMRC : les dividendes sont la TRANCHE
|
|
2
|
+
// SUPÉRIEURE du revenu. La Personal Allowance couvre d'abord l'autre revenu (salaire), le reliquat
|
|
3
|
+
// couvre les dividendes ; l'allowance dividende £500 est à 0 % mais occupe l'espace de tranche.
|
|
4
|
+
import { DIVIDEND_YEARS } from "./uk-dividend-tax-data.js";
|
|
5
|
+
const RATE_KEY = (b) => b;
|
|
6
|
+
function computeForYear(otherIncome, dividends, d) {
|
|
7
|
+
const totalIncome = otherIncome + dividends;
|
|
8
|
+
// PA réduite de £1 par £2 au-delà de £100,000, nulle à £125,140.
|
|
9
|
+
const pa = Math.max(0, d.personalAllowance - Math.max(0, (totalIncome - d.paTaperStart) / 2));
|
|
10
|
+
// Bornes de tranche en "revenu imposable" (au-dessus de la PA).
|
|
11
|
+
const basicTop = d.basicBandWidth; // 37,700
|
|
12
|
+
const higherTop = Math.max(basicTop, d.additionalThreshold - pa); // 125,140 - PA
|
|
13
|
+
// --- Impôt sur le salaire / autre revenu (contexte) ---
|
|
14
|
+
const otherTaxable = Math.max(0, otherIncome - pa);
|
|
15
|
+
const otherIncomeTax = taxAcrossBands(0, otherTaxable, basicTop, higherTop, d.itBasic, d.itHigher, d.itAdditional);
|
|
16
|
+
// --- Dividendes empilés au-dessus ---
|
|
17
|
+
const paUsedByOther = Math.min(otherIncome, pa);
|
|
18
|
+
const remainingPA = pa - paUsedByOther;
|
|
19
|
+
const divFreeByPA = Math.min(dividends, remainingPA); // dividendes couverts par la PA résiduelle (0 %)
|
|
20
|
+
let divRemaining = dividends - divFreeByPA;
|
|
21
|
+
// position de départ des dividendes dans les tranches (en revenu imposable)
|
|
22
|
+
let pos = otherTaxable;
|
|
23
|
+
const slices = [];
|
|
24
|
+
// 1) allowance dividende £500 : 0 % mais occupe l'espace de tranche
|
|
25
|
+
const allowanceUsed = Math.min(d.dividendAllowance, divRemaining);
|
|
26
|
+
const taxFree = divFreeByPA + allowanceUsed;
|
|
27
|
+
if (taxFree > 0)
|
|
28
|
+
slices.push({ band: "Tax-free", amount: taxFree, rate: 0, tax: 0 });
|
|
29
|
+
pos += allowanceUsed;
|
|
30
|
+
divRemaining -= allowanceUsed;
|
|
31
|
+
// 2) reste imposé par tranche depuis `pos`
|
|
32
|
+
let dividendTax = 0;
|
|
33
|
+
const pushTaxed = (band, amount, rate) => {
|
|
34
|
+
if (amount <= 0)
|
|
35
|
+
return;
|
|
36
|
+
const tax = amount * rate;
|
|
37
|
+
dividendTax += tax;
|
|
38
|
+
slices.push({ band, amount, rate, tax });
|
|
39
|
+
};
|
|
40
|
+
while (divRemaining > 1e-9) {
|
|
41
|
+
if (pos < basicTop) {
|
|
42
|
+
const chunk = Math.min(divRemaining, basicTop - pos);
|
|
43
|
+
pushTaxed("Basic rate", chunk, d.divBasic);
|
|
44
|
+
pos += chunk;
|
|
45
|
+
divRemaining -= chunk;
|
|
46
|
+
}
|
|
47
|
+
else if (pos < higherTop) {
|
|
48
|
+
const chunk = Math.min(divRemaining, higherTop - pos);
|
|
49
|
+
pushTaxed("Higher rate", chunk, d.divHigher);
|
|
50
|
+
pos += chunk;
|
|
51
|
+
divRemaining -= chunk;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
pushTaxed("Additional rate", divRemaining, d.divAdditional);
|
|
55
|
+
pos += divRemaining;
|
|
56
|
+
divRemaining = 0;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const dividendTaxable = dividends - taxFree;
|
|
60
|
+
return {
|
|
61
|
+
year: d.label,
|
|
62
|
+
personalAllowance: pa,
|
|
63
|
+
otherIncomeTax,
|
|
64
|
+
dividendTaxFree: taxFree,
|
|
65
|
+
dividendTaxable,
|
|
66
|
+
dividendTax,
|
|
67
|
+
effectiveDivRate: dividends > 0 ? (dividendTax / dividends) * 100 : 0,
|
|
68
|
+
totalIncome,
|
|
69
|
+
totalTax: otherIncomeTax + dividendTax,
|
|
70
|
+
slices,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
// Impôt sur un segment [start, start+amount] de revenu imposable, réparti sur les tranches.
|
|
74
|
+
function taxAcrossBands(start, amount, basicTop, higherTop, rB, rH, rA) {
|
|
75
|
+
let pos = start, rem = amount, tax = 0;
|
|
76
|
+
while (rem > 1e-9) {
|
|
77
|
+
if (pos < basicTop) {
|
|
78
|
+
const c = Math.min(rem, basicTop - pos);
|
|
79
|
+
tax += c * rB;
|
|
80
|
+
pos += c;
|
|
81
|
+
rem -= c;
|
|
82
|
+
}
|
|
83
|
+
else if (pos < higherTop) {
|
|
84
|
+
const c = Math.min(rem, higherTop - pos);
|
|
85
|
+
tax += c * rH;
|
|
86
|
+
pos += c;
|
|
87
|
+
rem -= c;
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
tax += rem * rA;
|
|
91
|
+
rem = 0;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return tax;
|
|
95
|
+
}
|
|
96
|
+
export function computeDividendTax(input) {
|
|
97
|
+
const other = Math.max(0, input.otherIncome || 0);
|
|
98
|
+
const div = Math.max(0, input.dividends || 0);
|
|
99
|
+
const cur = computeForYear(other, div, DIVIDEND_YEARS[input.year]);
|
|
100
|
+
// comparaison : si on calcule 2026/27, comparer à 2025/26 ; sinon comparer à 2024/25-like (même que prior ici).
|
|
101
|
+
const priorKey = input.year === "2026/27" ? "2025/26" : "2025/26";
|
|
102
|
+
const prior = computeForYear(other, div, DIVIDEND_YEARS[priorKey]);
|
|
103
|
+
return {
|
|
104
|
+
...cur,
|
|
105
|
+
extraVsPrior: cur.dividendTax - prior.dividendTax,
|
|
106
|
+
priorYear: priorKey,
|
|
107
|
+
priorDividendTax: prior.dividendTax,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
export { RATE_KEY };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export const US_FEDERAL_TAX_AS_OF = "2026-06-23";
|
|
2
|
+
export const FILING_STATUSES = [
|
|
3
|
+
{ value: "single", label: "Single" },
|
|
4
|
+
{ value: "mfj", label: "Married filing jointly" },
|
|
5
|
+
{ value: "hoh", label: "Head of household" },
|
|
6
|
+
{ value: "mfs", label: "Married filing separately" },
|
|
7
|
+
];
|
|
8
|
+
// Barèmes 2026 (revenu IMPOSABLE = après déduction). lower inclusif ; upper = haut de tranche, null = top.
|
|
9
|
+
const BRACKETS_2026 = {
|
|
10
|
+
single: [
|
|
11
|
+
{ rate: 0.10, lower: 0, upper: 12400 },
|
|
12
|
+
{ rate: 0.12, lower: 12400, upper: 50400 },
|
|
13
|
+
{ rate: 0.22, lower: 50400, upper: 105700 },
|
|
14
|
+
{ rate: 0.24, lower: 105700, upper: 201775 },
|
|
15
|
+
{ rate: 0.32, lower: 201775, upper: 256225 },
|
|
16
|
+
{ rate: 0.35, lower: 256225, upper: 640600 },
|
|
17
|
+
{ rate: 0.37, lower: 640600, upper: null },
|
|
18
|
+
],
|
|
19
|
+
mfj: [
|
|
20
|
+
{ rate: 0.10, lower: 0, upper: 24800 },
|
|
21
|
+
{ rate: 0.12, lower: 24800, upper: 100800 },
|
|
22
|
+
{ rate: 0.22, lower: 100800, upper: 211400 },
|
|
23
|
+
{ rate: 0.24, lower: 211400, upper: 403550 },
|
|
24
|
+
{ rate: 0.32, lower: 403550, upper: 512450 },
|
|
25
|
+
{ rate: 0.35, lower: 512450, upper: 768700 },
|
|
26
|
+
{ rate: 0.37, lower: 768700, upper: null },
|
|
27
|
+
],
|
|
28
|
+
hoh: [
|
|
29
|
+
{ rate: 0.10, lower: 0, upper: 17700 },
|
|
30
|
+
{ rate: 0.12, lower: 17700, upper: 67450 },
|
|
31
|
+
{ rate: 0.22, lower: 67450, upper: 105700 },
|
|
32
|
+
{ rate: 0.24, lower: 105700, upper: 201775 },
|
|
33
|
+
{ rate: 0.32, lower: 201775, upper: 256200 },
|
|
34
|
+
{ rate: 0.35, lower: 256200, upper: 640600 },
|
|
35
|
+
{ rate: 0.37, lower: 640600, upper: null },
|
|
36
|
+
],
|
|
37
|
+
// MFS = Single jusqu'à 32 % ; split 35/37 % à la moitié de MFJ (768 700 / 2 = 384 350). Rev. Proc. 2025-32 Table 3.
|
|
38
|
+
mfs: [
|
|
39
|
+
{ rate: 0.10, lower: 0, upper: 12400 },
|
|
40
|
+
{ rate: 0.12, lower: 12400, upper: 50400 },
|
|
41
|
+
{ rate: 0.22, lower: 50400, upper: 105700 },
|
|
42
|
+
{ rate: 0.24, lower: 105700, upper: 201775 },
|
|
43
|
+
{ rate: 0.32, lower: 201775, upper: 256225 },
|
|
44
|
+
{ rate: 0.35, lower: 256225, upper: 384350 },
|
|
45
|
+
{ rate: 0.37, lower: 384350, upper: null },
|
|
46
|
+
],
|
|
47
|
+
};
|
|
48
|
+
export const FEDERAL_2026 = {
|
|
49
|
+
year: 2026,
|
|
50
|
+
brackets: BRACKETS_2026,
|
|
51
|
+
standardDeduction: { single: 16100, mfj: 32200, hoh: 24150, mfs: 16100 }, // surviving spouse = MFJ
|
|
52
|
+
childTaxCredit: {
|
|
53
|
+
perChild: 2200, // OBBBA : porté à $2 200 (TY2025+), constant 2026, indexé dès 2027
|
|
54
|
+
refundablePerChild: 1700,
|
|
55
|
+
phaseoutStart: { single: 200000, mfj: 400000, hoh: 200000, mfs: 200000 },
|
|
56
|
+
phaseoutPer1000: 50,
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
export const FEDERAL_BY_YEAR = { 2026: FEDERAL_2026 };
|
|
60
|
+
export const FEDERAL_TAX_SOURCES = [
|
|
61
|
+
{ label: "IRS — 2026 inflation adjustments (Rev. Proc. 2025-32 newsroom)", url: "https://www.irs.gov/newsroom/irs-releases-tax-inflation-adjustments-for-tax-year-2026-including-amendments-from-the-one-big-beautiful-bill" },
|
|
62
|
+
{ label: "IRS — Revenue Procedure 2025-32 (primary PDF, Table 3 = MFS)", url: "https://www.irs.gov/pub/irs-drop/rp-25-32.pdf" },
|
|
63
|
+
{ label: "Tax Foundation — 2026 Tax Brackets and Federal Income Tax Rates", url: "https://taxfoundation.org/data/all/federal/2026-tax-brackets/" },
|
|
64
|
+
{ label: "Kiplinger — Child Tax Credit 2026 ($2,200 / $1,700 refundable)", url: "https://www.kiplinger.com/taxes/child-tax-credit" },
|
|
65
|
+
];
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export const US_FICA_AS_OF = "2026-06-23";
|
|
2
|
+
export const FICA_2026 = {
|
|
3
|
+
socialSecurity: {
|
|
4
|
+
rateEmployee: 0.062,
|
|
5
|
+
wageBase: 184500, // plafond de salaire soumis à SS (2026)
|
|
6
|
+
maxEmployeeTax: 11439, // 184 500 × 6,2 %
|
|
7
|
+
},
|
|
8
|
+
medicare: {
|
|
9
|
+
rateEmployee: 0.0145, // aucun plafond
|
|
10
|
+
},
|
|
11
|
+
additionalMedicare: {
|
|
12
|
+
rate: 0.009, // 0,9 % sur les salaires Medicare au-dessus du seuil par statut
|
|
13
|
+
// Seuils statutaires figés depuis 2013 (non indexés) → inchangés 2026.
|
|
14
|
+
thresholdByStatus: { single: 200000, mfj: 250000, mfs: 125000, hoh: 200000 },
|
|
15
|
+
},
|
|
16
|
+
combinedEmployeeRate: 0.0765, // 6,2 % + 1,45 %
|
|
17
|
+
selfEmployedRate: 0.153, // part employeur + employé (hors v1, info)
|
|
18
|
+
};
|
|
19
|
+
export const FICA_SOURCES = [
|
|
20
|
+
{ label: "SSA — 2026 Social Security & Medicare tax fact sheet (COLA)", url: "https://www.ssa.gov/news/en/cola/factsheets/2026.html" },
|
|
21
|
+
{ label: "SSA — Contribution and Benefit Base (wage base history)", url: "https://www.ssa.gov/oact/cola/cbb.html" },
|
|
22
|
+
{ label: "IRS — Questions and Answers for the Additional Medicare Tax", url: "https://www.irs.gov/businesses/small-businesses-self-employed/questions-and-answers-for-the-additional-medicare-tax" },
|
|
23
|
+
];
|