Skip to content

Rules

The rules layer is the versioned knowledge base that the engine reads at runtime. It contains no computation logic — only structured data loaded by the loaders and validated against Pydantic schemas.

Three kinds of rules exist:

Kind What it holds Format
CCNL Salary tables, seniority amounts, apprenticeship tracks, work-rule parameters JSON per contract
Tax IRPEF brackets, deductions, contribution ceilings — by year JSON per year
Surtax Regional and municipal addizionale brackets — by year JSON per year

How the knowledge base is structured

src/ccnl_engine/knowledge/
├── ccnl/
│   └── data/           ← one JSON file per contract (e.g. metalmeccanico-federmeccanica.json)
├── tax/
│   └── data/           ← one JSON file per year (e.g. 2026-industria.json)
├── surtax/
│   └── data/
│       ├── regionale-2026.json    ← regional brackets, all regions
│       └── comunale-2026.json     ← municipal brackets, ~7 000 comuni
└── __version__         ← dataset version string (e.g. "2026.2")

All files are bundled inside the Python package and read via importlib.resources — no filesystem paths, no network access.

Versioning

The entire knowledge base carries a single dataset version (__version__, e.g. "2026.2"). It is bumped whenever any data file changes.

Every CCNL JSON additionally carries a per-contract ruleset.version and a ruleset.source_hash (SHA-256 of the file content). The loaders verify the hash at load time and raise ValueError on mismatch — tampered or accidentally edited files are caught before any computation.

from ccnl_engine.catalog.loaders import load_ccnl

ccnl = load_ccnl("metalmeccanico-federmeccanica.json")
print(ccnl.ruleset.id)  # "ccnl/metalmeccanico-federmeccanica"
print(ccnl.ruleset.version)  # "2026.2"
print(ccnl.ruleset.source_hash)  # "4a7b..."

The ruleset identity is propagated into every Calculation so a payroll figure can be reproduced exactly from any historical snapshot. See Trust: Versioning.

CCNL JSON schema

Each CCNL file follows schema version 0.5. The top-level keys:

Key Type Description
schema_version string Schema revision (e.g. "0.5")
meta object Identity: CNEL code, name, sector, signatories, sources, extraction metadata
coverage object Implementation status: gross, net, work_rules, plus notes
parameters object Contract-wide parameters: hourly divisor, additional months, seniority
levels array One object per classification level: code, base salary time-series, allowances
apprenticeship array Apprenticeship tracks (under-classification or percentage)
ruleset object Identity for integrity verification: id, version, source_hash, verification_status
work_rules object Time supplements, absence, leave, and sickness rules

meta.sources

Every fact in a CCNL file must trace back to a primary source. The sources array on meta lists all documents used during extraction:

{
  "document_id": "tabelle-retributive-commercio-2024",
  "title": "Tabelle retributive CCNL Terziario...",
  "kind": "tabella_retributiva",
  "url": "https://...",
  "published_on": "2024-03-28"
}

Individual payable rules (levels, salary periods, allowances, seniority, additional months) also carry a provenance record with a status (verified, derived, assumed or missing) that pins the exact source_document, section, and extraction metadata for that specific fact. See Trust: Provenance.

coverage.notes

Coverage notes explain what is modelled, what is simplified, and what is absent. Four kind values:

Kind Meaning
source Records which source document was used and why
info Documents a modelling decision or derivation (e.g. hourly divisor formula)
simplification Marks a deliberate approximation — something real that the model gets slightly wrong
missing Data the engine supports but the file lacks; names the capability it leaves partial

simplification notes are the most important for callers: they are the known errors. Each one states its monetary_impact on what the engine computes from the bundle (yes, no, unknown). One that can move an amount (yes or unknown) names its capability and declares the limitation the engine records on the runs it concerns; a file that leaves one unmapped does not load:

{
  "kind": "simplification",
  "text": "Overtime bands over 48 weekly hours are not modelled.",
  "capability": "overtime",
  "monetary_impact": "yes",
  "limitation": {
    "variant": "higher_overtime_bands",
    "applies_when": {"levels": ["C1"], "effective_from": "2026-01-01"},
    "remediation": "Model the bands over 48 weekly hours."
  }
}

applies_when narrows the runs: trigger (run, or outside_input when the fact has no request field), contract_types, levels, worker_categories, run_kinds, seniority_months_from, headcount_from, effective_from, effective_until. The limitation id is <ccnl id>/<variant>; see Model limitations. Every simplification appears on the contract's documentation page, under "Known simplifications".

Tax rules

Tax rules are loaded by year and employer size. The loader selects the appropriate INPS rate tier (based on num_employees) and applies the correct IRPEF bracket schedule:

from ccnl_engine.agreement.facade import TaxSector
from ccnl_engine.sector.loaders import load_year_rules

rules = load_year_rules(2026, TaxSector.INDUSTRIA, num_employees=50)

The sector must match the CCNL's meta.tax_sector. Valid values: terziario, industria, edilizia, credito, artigianato, pubblica-amministrazione, lavoro-domestico, agricoltura.

Surtax rules

Regional and municipal surtax tables are loaded by the engine for the tax year. The caller selects them with regione and comune_belfiore on the PeriodFacts of the run; a component whose code is None is skipped. See Fiscal computation.

from ccnl_engine.surtax.loaders import load_surtax_rules

surtax = load_surtax_rules(2026)

The SurtaxRules object covers all Italian regions and ~7 000 comuni (identified by their codice catastale).