Substitute-tax regimes¶
A preferential regime replaces ordinary IRPEF and the regional and municipal
surtaxes with a flat substitute tax (imposta sostitutiva) on the pay items it
covers. The engine models each regime as data (PreferentialTaxRegime, read
from knowledge/variable_pay/2026.json) and checks every covered pay
item against the worker facts declared on the input before applying it.
The regime model¶
| Field | Meaning |
|---|---|
regime_id |
Stable identifier, e.g. rinnovo |
eligible_kinds |
Pay item kinds the regime covers |
valid_from_year, valid_until_year |
Tax years the regime is in force (tax year of the payment) |
flat_tax_rate |
Substitute rate |
annual_cap |
Largest amount per tax year at the substitute rate, null when uncapped; the excess is ordinary income |
income_ceiling, income_reference_year |
Largest employment income of the reference year that keeps the worker eligible (the ceiling itself is eligible) |
required_sector |
private or public, null when every sector qualifies |
waivable |
Whether the worker may renounce the regime in writing |
excluded_activities |
Employer activities the regime does not apply to; when not empty the employer activity is a required fact |
agreements_signed_from, agreements_signed_until |
Signing window of the agreements whose increments qualify, null when the regime has none |
source |
Normative source: document, section and verbatim quote |
Eligibility decision¶
Each covered pay item gets one of three outcomes, recorded as a
CalculationDecision in result.decisions with a stable reason_code:
| Outcome | Reason codes | Taxation | Result status |
|---|---|---|---|
eligible |
requirements_met |
substitute rate on the eligible amount, ordinary on any excess over the cap | final |
ineligible |
regime_not_in_force, waived_by_worker, sector_not_eligible, employer_activity_excluded, agreement_signed_outside_window, prior_income_above_ceiling |
ordinary | final |
unknown |
sector_unknown, activity_unknown, agreement_signing_date_unknown, prior_income_unknown |
ordinary | provisional |
A definite ineligibility wins over a missing fact: a public-sector worker is
ineligible whatever the prior-year income. Reason codes are checked in the
order of the table. An unknown outcome also adds a
CalculationIssue coded <regime_id>_eligibility_unknown whose fact is
the public field to set: sector (Employment.sector), activity
(EmployerProfile.activity), agreement_signed_on
(BonusEvent.agreement_signed_on) or employment_income
(PriorYearTaxFacts.employment_income), so the result
status becomes provisional: the substitute rate is never applied to a worker
who may turn out ineligible, and the amounts change once the missing fact is
provided.
The decision inputs hold the normalized facts it was taken from:
eligibility, tax_year, prior_income and sector (unknown when
missing), waived, eligible_amount and ordinary_amount; a regime with
excluded activities adds employer_activity, a regime with a signing window
adds agreement_signed_on (both unknown when missing). Its amount is the
substitute tax posted.
Worker facts¶
Every regime reads the same facts, declared once on the input and never inferred from the CCNL:
| Fact | Input | None means |
|---|---|---|
| Prior-year employment income (reddito di lavoro dipendente) | PriorYearTaxFacts.employment_income |
unknown: every regime with an income ceiling is unknown, the PdR is not applied |
| Written renunciation | PriorYearTaxFacts.waived_regimes, a set of SubstituteTaxRegime |
no waiver |
| Private or public sector | Employment.sector (EmploymentSector) |
unknown: a regime restricted to one sector is unknown |
| Employer activity | EmployerProfile.activity (EmployerActivity) |
unknown: the night, holiday and shift regime is unknown |
| Signing date of the renewal | BonusEvent.agreement_signed_on |
unknown: the renewal regime is unknown |
The sector is not derived from the CCNL: a public employer may apply a private CCNL, and it is then a public-sector employment for these regimes. For a 2026 payment the prior year is 2025, the reference year of both L. 199/2025 regimes.
from decimal import Decimal
from ccnl_engine import EmployerProfile, Employment, Headcount
from ccnl_engine.inputs import (
EmployerActivity,
EmploymentSector,
Permanent,
PriorYearTaxFacts,
SubstituteTaxRegime,
)
employment = Employment(
ccnl_slug="commercio-confcommercio.json",
level_code="4",
contract_type=Permanent(),
sector=EmploymentSector.PRIVATE,
)
employer = EmployerProfile(headcount=Headcount(50), activity=EmployerActivity.OTHER)
prior_year = PriorYearTaxFacts(employment_income=Decimal(20_000)) # 2025 income
waived = PriorYearTaxFacts(
employment_income=Decimal(20_000),
waived_regimes=frozenset({SubstituteTaxRegime.RINNOVO}),
)
Contract renewal increments (rinnovo)¶
L. 199/2025 art. 1 c. 7: salary increments paid in 2026 under CCNL renewals signed from 1 January 2024 to 31 December 2026 are taxed at 5% instead of IRPEF and surtaxes. The regime applies only to private-sector employees whose 2025 employment income (reddito di lavoro dipendente) does not exceed 33,000 EUR, unless the worker renounces it in writing. There is no annual cap.
The renewal is a BonusEvent with kind="contract_renewal" and the signing
date of the renewal in agreement_signed_on. The date is checked against the
window the data record as agreements_signed_from and
agreements_signed_until, bounds included:
from datetime import date
from decimal import Decimal
from ccnl_engine.events import BonusEvent
renewal = BonusEvent(
event_date=date(2026, 3, 10),
amount=Decimal(2_000),
kind="contract_renewal",
agreement_signed_on=date(2025, 3, 1),
)
# With the private-sector employment and 2025 income of 20,000 declared
# above: substitute tax 2,000 x 5% = 100.00.
# Income 100,000 or a waiver of SubstituteTaxRegime.RINNOVO: ordinary IRPEF.
# Unknown income, sector or signing date: ordinary IRPEF, provisional result.
# Signed on 2023-12-31: agreement_signed_outside_window, ordinary IRPEF.
agreement_signed_on is accepted only on a contract renewal.
Increments paid inside the minimo¶
A renewal usually raises the minimo tabellare, so its increments are paid in the base salary of every month, not as a separate event. The bundle does not record which agreement set each table of the minimo, and how much of the minimo counts as a renewal increment is a reading the engine does not settle. It never posts the 5% on the minimo; it decides what the worker facts alone decide.
A run of tax year 2026 by a withholding agent assesses the regime on its
minimo when the level has a table dated within the signing window, on or
before the competence date (Metalmeccanico C3: the tables of 1 June 2024,
2025 and 2026). The run then records a rinnovo_substitute_tax decision
with paid_in="minimum", amount zero, and the date of the first such table
in table_from:
| Worker facts | Decision | Effect on the run |
|---|---|---|
| A known fact excludes the worker: public sector, 2025 income above 33,000, written waiver | final, the reason (e.g. prior_income_above_ceiling) |
none |
| Sector or 2025 income unknown | provisional, e.g. prior_income_unknown |
one rinnovo_eligibility_unknown issue per missing fact: a missing_fact blocker naming sector or employment_income |
| Every requirement met | provisional, renewal_increment_in_minimum_unquantified |
issue rinnovo_minimum_increment_unquantified: a calculation_issue blocker, since the ordinary IRPEF on the whole minimo may be too high |
The signing date is not asked for on the minimo: no event carries it. A run whose minimo has no table dated within the window (e.g. Farmacie private, last table of 2021) takes no decision on it.
Night, holiday and shift supplements¶
L. 199/2025 art. 1 cc. 10-11: for tax year 2026 the supplements paid for night work, for work on public holidays and weekly rest days, and for shift work are taxed at 15% instead of IRPEF and surtaxes, within 1,500 EUR a year. The regime applies only to private-sector employers ("sostituti d'imposta del settore privato") and to workers whose 2025 employment income does not exceed 40,000 EUR, unless the worker renounces it in writing. The part above the annual cap is ordinary income.
| Letter of c. 10 | Pay | Event |
|---|---|---|
| a) | maggiorazioni and indennita for night work (D.Lgs. 66/2003 art. 1 c. 2, CCNL) | NightShiftEvent |
| b) | maggiorazioni and indennita for work on public holidays and weekly rest days (CCNL) | HolidayWorkEvent |
| c) | indennita di turno and other shift-work pay (CCNL) | ShiftWorkEvent |
The three events post the pay item kind night_holiday_shift_earning, which
is the one kind the regime covers. The worker facts are those of the input:
2025 income and waivers in PriorYearTaxFacts, the sector on Employment,
the activity on EmployerProfile.
c. 11 excludes the activities of c. 18, whose workers receive the
trattamento integrativo speciale instead: "esercizi di somministrazione di
alimenti e bevande, di cui all'articolo 5 della legge 25 agosto 1991, n. 287"
and "comparto del turismo, ivi inclusi gli stabilimenti termali". An employer
declared as FOOD_AND_BEVERAGE_SERVICE, TOURISM or THERMAL_ESTABLISHMENT
is ineligible (employer_activity_excluded); OTHER passes the check; an
activity left None is unknown (activity_unknown), because the law makes
the exclusion depend on it.
from datetime import date
from decimal import Decimal
from ccnl_engine.events import HolidayWorkEvent, NightShiftEvent
night = NightShiftEvent(
event_date=date(2026, 3, 10), supplement_amount=Decimal(2_000)
)
# Eligible worker: substitute tax 1,500 x 15% = 225.00; the other 500 is
# ordinary income.
holiday = HolidayWorkEvent(event_date=date(2026, 3, 1), supplement_amount=Decimal(500))
# Alone: 500 x 15% = 75.00. After the night supplement above, in the same
# run or a later run of 2026: the cap is used up and the 500 is ordinary.
Annual cap account¶
The used part of the cap is a year-to-date account of the tax year state,
PeriodState.cash.work_time_regime (RegimeCapAccount.used). It restarts
at zero when close_tax_year() opens the next tax year. Each supplement
gets the substitute rate only on min(amount, cap - used); the rest is
ordinary. The account grows by the eligible amount after every supplement,
within a run and across runs through the closing state, so the cap is shared
by all night, holiday and shift supplements of the tax year. Ineligible and
unknown supplements do not consume the cap. Premi di risultato do not count
toward it (c. 11), since they are a separate pay item kind.
The account rejects a negative used amount. Each decision of a capped regime
records annual_cap and cap_available (the part of the cap left before
that supplement) among its inputs. The reconciliation invariant
substitute_tax_plafond checks that the closing account equals the opening
one plus the eligible amounts of the run and never exceeds the annual cap,
that each decision sees the cap the earlier ones left and takes no more than
it, and that the PdR eligible YTD advances by the bonus_pdr decision and
stays within the PdR annual limit. The invariant substitute_tax_eligibility
checks that the SUBSTITUTE_TAX posted equals the substitute tax of the
decisions, and that a decision that is not eligible taxes nothing at the
substitute rate.
Not modelled¶
- Pay that, although called a supplement, replaces ordinary salary is excluded by c. 11; declaring it as a night, holiday or shift event is the caller's responsibility.
- The written statement of 2025 income owed when another employer issued the
2025 certificazione unica is a caller obligation; the engine reads the
declared
PriorYearTaxFacts.employment_income. - The trattamento integrativo speciale of c. 18 itself.
Other regimes¶
The PdR regime (productivity bonus) still uses its dedicated rules
(PdRRules) and reads the same PriorYearTaxFacts.employment_income; it
fails closed to ordinary IRPEF when the income is not provided.