ccnl-engine¶
Python engine for auditable Italian payroll simulations: CCNL-aware gross-to-net and employer cost, versioned rules, provenance, and an explicit status on every result. Built for technical teams in HR, payroll, and compensation.
Demo · GitHub · CCNL coverage
Four pillars¶
| Pillar | What it covers |
|---|---|
| Domain | How Italian labor law and CCNLs are represented as typed models |
| Rules | The versioned knowledge base: CCNL tables, INPS rates, IRPEF brackets |
| Engine | How rules are applied to produce a reproducible payroll figure |
| Trust | Provenance, calculation status, versioning, and reference cases that make results verifiable |
Documentation¶
| Section | Description |
|---|---|
| Get started | Install, quickstart, and first payroll in 10 lines |
| Domain | What CCNLs are, Italian labor law, employment types |
| Rules | The versioned knowledge base: CCNL JSON schema, INPS, IRPEF, surtax |
| Engine | How to use PayrollEngine — pay components, fiscal, domestic work |
| Trust | Provenance, calculation status, capability report, versioning, quality gates |
| Data operations | Update policy, release notes, error reporting, version compatibility |
| Correctness layers | Software, source, and case correctness — what each layer means and how to read them |
| Contracts | All 132 supported contracts — salary tables, sources, coverage |
| API reference | Full reference for every public type and function |
Why trust a number from this engine?¶
Every result carries three verifiability layers:
- Provenance: each rule links to its primary source document (CCNL article, INPS circular, tax schedule) with a URL, section, and verification status.
- Versioning:
result.bundle_versionrecords the knowledge-base version, so any figure can be reproduced after a CCNL renewal by pinning the package. - Assurance and decisions:
result.is_payablesays whether the amounts can be paid as computed andresult.blockerswhy not (an issue, a missing fact, a capability not computed, a rule without a located source, a caller-supplied rule, and inoperationalmode a CCNL ruleset that is notproduction);result.assuranceholds the calculation, coverage and evidence axes,result.rulesetsthe rulesets read with their readiness, andresult.decisionswhat each capability decided and from which inputs. An unknown normative fact never yields a payable result.
if not result.is_payable:
for blocker in result.blockers:
print(blocker.code.value, blocker.feature, blocker.detail)
for decision in result.decisions:
print(decision.capability, decision.reason_code, decision.amount)
See the explanation example for a full walkthrough.
Scope¶
Gross and net (L1, L2):
- IRPEF gross and net (Art. 11-13 TUIR), work income deductions, trattamento integrativo (Art. 1 D.L. 3/2020)
- Regional and municipal income tax surcharges (addizionale regionale e comunale IRPEF)
- INPS contributions (employee and employer), resolved by headcount tier
- TFR accrual
- Contractual employer funds
- Part-time scaling, seniority increments, fixed allowances
- Apprenticeship contracts (under-classification and percentage tracks)
- Fixed-term contracts (NASpI addizionale)
- Domestic work (flat per-hour contributions, non-withholding employer)
Second-level (territorial and company) allowances are not an engine input.
What each contract computes, and its limits, is in the capability matrix.
Work rules (L3):
Work events are part of the run: pass them in PeriodFacts.events and they
change gross, net and employer cost according to their treatment. See
Work rules.
- Overtime pay (lavoro straordinario) with the CCNL band or a caller multiplier:
OvertimeEvent - Night, holiday and shift supplements, with the 2026 substitute tax:
NightShiftEvent,HolidayWorkEvent,ShiftWorkEvent - Unpaid absences and sickness:
AbsenceEvent,SicknessEpisode,SickLeaveEvent(override) - Bonuses (ordinary, premio di risultato with its flat tax, renewal
increments):
BonusEvent - Welfare and fringe benefits:
WelfareEvent,FringeEvent
Further facts:
- Family-dependent deductions (Art. 12 TUIR):
PeriodFacts.family_compositionwith dated dependency intervals (omitted, the deductions are undecided and the result not payable;FamilyComposition()states no dependant), andPeriodInput.current_year(CurrentYearTaxFacts) for the income beyond this employment - Regional and municipal surtax:
PeriodFacts.regioneandcomune_belfiore(omitted, the surtax is undecided and the result not payable; a code without a table makes the resultincomplete) - Prior-year income and written waivers for the substitute-tax regimes:
PriorYearTaxFacts
Why this project¶
I never really understood employment contracts or pay slips. The whole system strikes me as needlessly complicated. On top of that, finding reliable CCNL information online feels like an uphill battle: conflicting figures are everywhere, and identifying authoritative sources is harder than it should be. This project grew out of a desire to understand a little more. I make no claim to becoming an expert, but I hope to make this information more accessible and comprehensible for everyone.
Disclaimer¶
Not legal or tax advice. Always verify results against official sources or a qualified payroll professional.