""" Centralized Tax Configuration for Nigeria Tax Act 2026. Single source of truth for tax brackets, rates, reliefs, and thresholds. All tax calculations MUST reference this module. """ from dataclasses import dataclass, field from typing import Dict, List, Optional, Any from datetime import date from enum import Enum class TaxRegime(Enum): """Available tax regimes.""" PITA_2025 = "pita_2025" # Personal Income Tax Act (pre-2026) NTA_2026 = "nta_2026" # Nigeria Tax Act 2026 (primary) @dataclass(frozen=True) class TaxBand: """Immutable tax band definition.""" lower: float upper: float # Use float('inf') for unbounded rate: float # Decimal (0.15 = 15%) @property def rate_percent(self) -> float: return self.rate * 100 @dataclass(frozen=True) class TaxRegimeConfig: """Complete configuration for a tax regime.""" name: str code: str effective_from: date effective_to: Optional[date] # Tax bands (progressive) bands: tuple # Tuple of TaxBand # Relief settings cra_enabled: bool cra_fixed_amount: float # e.g., 200,000 cra_percent_of_gross: float # e.g., 0.01 (1%) cra_additional_percent: float # e.g., 0.20 (20%) # Rent relief (NTA 2026) rent_relief_enabled: bool rent_relief_cap: float rent_relief_percent: float # Minimum tax minimum_tax_rate: float # e.g., 0.01 (1%) # Minimum wage exemption minimum_wage_monthly: float # Standard deduction rates pension_rate: float # Employee contribution nhf_rate: float # National Housing Fund nhis_rate: float # National Health Insurance # Legal citation authority: str # Nigeria Tax Act 2026 - PRIMARY REGIME NTA_2026_CONFIG = TaxRegimeConfig( name="Nigeria Tax Act 2026", code="NTA_2026", effective_from=date(2026, 1, 1), effective_to=None, bands=( TaxBand(0, 800_000, 0.00), # 0% - Tax free TaxBand(800_000, 3_000_000, 0.15), # 15% TaxBand(3_000_000, 12_000_000, 0.18), # 18% TaxBand(12_000_000, 25_000_000, 0.21), # 21% TaxBand(25_000_000, 50_000_000, 0.23), # 23% TaxBand(50_000_000, float('inf'), 0.25), # 25% ), # CRA replaced by rent relief in NTA 2026 cra_enabled=False, cra_fixed_amount=0, cra_percent_of_gross=0, cra_additional_percent=0, # Rent relief replaces CRA rent_relief_enabled=True, rent_relief_cap=500_000, rent_relief_percent=0.20, # Minimum tax - NOT in NTA 2026 (was in old PITA only) minimum_tax_rate=0.0, # Minimum wage (2024 rate, pending update) minimum_wage_monthly=70_000, # Standard deductions pension_rate=0.08, # 8% employee contribution nhf_rate=0.025, # 2.5% nhis_rate=0.05, # 5% (if enrolled) authority="Nigeria Tax Act, 2025 (effective 2026)" ) # PITA 2025 - LEGACY (for reference/comparison) PITA_2025_CONFIG = TaxRegimeConfig( name="Personal Income Tax Act 2025", code="PITA_2025", effective_from=date(2011, 1, 1), effective_to=date(2025, 12, 31), bands=( TaxBand(0, 300_000, 0.07), TaxBand(300_000, 600_000, 0.11), TaxBand(600_000, 1_100_000, 0.15), TaxBand(1_100_000, 1_600_000, 0.19), TaxBand(1_600_000, 3_200_000, 0.21), TaxBand(3_200_000, float('inf'), 0.24), ), # CRA enabled cra_enabled=True, cra_fixed_amount=200_000, cra_percent_of_gross=0.01, cra_additional_percent=0.20, # No rent relief rent_relief_enabled=False, rent_relief_cap=0, rent_relief_percent=0, minimum_tax_rate=0.01, minimum_wage_monthly=70_000, pension_rate=0.08, nhf_rate=0.025, nhis_rate=0.05, authority="Personal Income Tax Act (as amended), PITA s.33, First Schedule" ) # Registry of all regimes TAX_REGIMES: Dict[str, TaxRegimeConfig] = { "NTA_2026": NTA_2026_CONFIG, "PITA_2025": PITA_2025_CONFIG, } # Default regime DEFAULT_REGIME = "NTA_2026" def get_regime(code: str = None) -> TaxRegimeConfig: """Get a tax regime configuration by code.""" code = code or DEFAULT_REGIME if code not in TAX_REGIMES: raise ValueError(f"Unknown tax regime: {code}. Available: {list(TAX_REGIMES.keys())}") return TAX_REGIMES[code] def get_active_regime(as_of: date = None) -> TaxRegimeConfig: """Get the applicable tax regime for a given date.""" as_of = as_of or date.today() for regime in TAX_REGIMES.values(): if regime.effective_from <= as_of: if regime.effective_to is None or as_of <= regime.effective_to: return regime # Fallback to default return TAX_REGIMES[DEFAULT_REGIME] def format_bands(regime: TaxRegimeConfig = None) -> str: """Format tax bands for display.""" regime = regime or get_regime() lines = [f"Tax Bands - {regime.name}", "=" * 50] for band in regime.bands: if band.upper == float('inf'): lines.append(f"Above N{band.lower:,.0f}: {band.rate_percent:.0f}%") elif band.rate == 0: lines.append(f"N{band.lower:,.0f} - N{band.upper:,.0f}: TAX FREE") else: lines.append(f"N{band.lower:,.0f} - N{band.upper:,.0f}: {band.rate_percent:.0f}%") lines.append(f"\nLegal Basis: {regime.authority}") return "\n".join(lines) # Company Income Tax rates (NTA 2026) CIT_RATES = { "small": { "threshold": 25_000_000, "rate": 0.00, "description": "Small company (turnover <= N25m): 0%" }, "medium": { "threshold": 100_000_000, "rate": 0.20, "description": "Medium company (N25m < turnover < N100m): 20%" }, "large": { "threshold": float('inf'), "rate": 0.30, "description": "Large company (turnover >= N100m): 30%" } } # VAT configuration VAT_CONFIG = { "rate": 0.075, # 7.5% "registration_threshold": 25_000_000, "exempt_goods": [ "basic food items", "medical and pharmaceutical products", "educational materials", "exported services" ] } # Withholding Tax rates WHT_RATES = { "dividend": 0.10, "interest": 0.10, "rent": 0.10, "royalty": 0.10, "contract": 0.05, "consultancy": 0.05, "director_fees": 0.10, }