Domestic work (lavoro domestico)¶
Domestic employment (lavoro domestico) follows a contribution system entirely different from the general regime. Understanding the differences is essential before computing payrolls for this sector.
See Domain: Social contributions for the background on the general and flat-rate systems.
Key differences¶
| Feature | General regime | Domestic work |
|---|---|---|
| INPS contributions | % of gross salary | Flat rate per hour worked (INPS quarterly tables) |
| INPS base | Proportional | Depends on wage bracket |
| Employer as sostituto d'imposta | Yes, withholds IRPEF at source | No, the worker files directly |
weekly_hours required |
No | Yes, needed to compute flat INPS |
| IRPEF and surcharges withheld | Yes | No |
| Trattamento integrativo, somma esente, ulteriore detrazione | Paid on the payslip | No |
| Substitute taxes (PdR, renewal, night and shift regimes) | When eligible | No, the amount is ordinary income |
Convivente vs. non-convivente¶
The library includes three separate JSON files:
lavoro-domestico-convivente.json: live-in domestic worker, Tabella A (art. 14 c. 1 lett. a, up to 54 weekly hours)lavoro-domestico-convivente-orario-ridotto.json: live-in worker of levels B, B super and C hired under art. 14 c. 2, up to 30 weekly hours, Tabella Blavoro-domestico-non-convivente.json: non-live-in domestic worker
Each file carries the correct INPS rate table and level structure for that variant.
The monthly minimum of a non convivente is the Tabella C hourly rate times 40 × 52 / 12 (CCNL of 28 October 2025, art. 14 c. 1 lett. b and chiarimento a verbale 1): level CS, €8.30 an hour, is €1,438.67 a month. The hourly divisor of the file is 173.33, so the hourly rate the INPS bracket reads is the table rate to the cent.
A convivente under art. 14 c. 2 is paid the Tabella B minimum "qualunque sia
l'orario di lavoro osservato nel limite massimo delle 30 ore settimanali",
board and lodging in full: level B super is €737.39 a month at 20 or at 30
weekly hours. The file sets flat_pay_max_weekly_hours to 30, so the pay is
not proportioned, no full time is needed and more than 30 weekly hours raise
InvalidInputError. Seniority is 4% of the Tabella B minimum (art. 37). The
hourly pay is the monthly pay over the hours worked, weekly_hours x 52 / 12,
for the INPS hourly bracket, the overtime and the proration: B super at 10
weekly hours is €17.02 an hour, the third INPS bracket.
A convivente under art. 14 c. 1 may agree fewer than 54 weekly hours, but
Tabella A gives monthly values and the CCNL no rule to proportion them. The
engine scales the pay on the stated full time (C super at 40 of 54 hours:
€884.33) and reports the open limitation
lavoro-domestico-convivente/part_time_scaling, so the run is not payable.
Cas.Sa.Colf contribution¶
The CCNL charges contributi di assistenza contrattuale for the Cas.Sa.Colf and
its other joint bodies: €0.06 per paid hour, of which €0.02 withheld from the
worker and €0.04 paid by the employer (art. 54 c. 2, due at this rate from 1
January 2021, chiarimento a verbale 6). The engine charges them on the
contributable_hours of the run, posts the worker share to the
bilateral_fund_employee account (it reduces the net) and the employer share
to bilateral_fund_employer (it adds to the employer cost), and records an
assistance_contribution decision with the hours, rates and the clause as its
source. A run is charged on the hours it states, INPS and Cas.Sa.Colf alike:
a tredicesima stated with 0 hours charges nothing, but a competence year gives
every run, the tredicesima included, the hours of its default_facts (see
the extra_month_hours limitation below).
Board and lodging of a convivente¶
A convivente receives board and lodging in kind. Their valore convenzionale
is set by Tabella F (art. 36 c. 3): in 2026 €2.33 for pranzo e/o colazione,
€2.33 for cena and €2.00 for alloggio a day, a month being 30 days, so
€199.80 a month. Every level of lavoro-domestico-convivente.json carries it
as the allowance vitto_alloggio, flagged in_kind:
| Run | Effect of board and lodging | Source |
|---|---|---|
| Regular | Not paid in cash; counted in the TFR base: (1,193.84 + 199.80) / 13.5 = €103.23 for level CS | art. 41 c. 1 |
| Tredicesima | Paid in cash: 1,193.84 + 199.80 = €1,393.64 for a full year | art. 39 c. 1, chiarimento a verbale 5 |
| Ratei at termination | Included in the tredicesima ratei | art. 39 c. 2 |
| Reduced hours | Not reduced | art. 14 c. 2 |
Three cases are open limitations. The first two are recorded on every regular and termination run of the CCNL, because the request has no fact for them; the third on every tredicesima run of both files:
lavoro-domestico-convivente/board_lodging_substitute: the cash indennità sostitutiva for the days a convivente does not take board and lodging (ferie art. 17 c. 7, sospensioni art. 18 c. 1, matrimonio art. 24 c. 2, malattia and infortunio outside hospital art. 27 c. 9 and art. 29 c. 7);lavoro-domestico-non-convivente/meal_indennity: the meal, or its valore convenzionale, owed to a non convivente on six or more hours a day with continuous presence (art. 14 c. 8);<ccnl>/extra_month_hours: no source in the bundle says whether a tredicesima carries contributable hours; the engine charges INPS and Cas.Sa.Colf on the hours the run states, and a competence year states the hours of a regular month.
Usage¶
Pass weekly_hours on Employment and the contributable_hours of the run
on PeriodFacts. The engine uses them together with the flat-rate INPS table
to compute contributions. Pass full_time_weekly_hours too: the monthly
minimum of the bundle is the pay of a full-time week (54 hours for
conviventi, 40 for non conviventi, the weeks of its hourly divisors), and
weekly hours without it leave the part-time fraction unknown, with a
missing_fact blocker.
Both facts are value objects validated when they are built: WeeklyHours must
be positive (and not above full_time_weekly_hours when that is given), and
ContributableHours must be a non-negative Decimal. Impossible values raise
InvalidInputError instead of producing negative
contributions.
"""Domestic work (CCNL colf): flat per-hour INPS, Cas.Sa.Colf, no withholding."""
from datetime import date
from decimal import Decimal
from ccnl_engine import (
EmployerProfile,
Employment,
Headcount,
PayrollEngine,
PayrollRun,
PeriodFacts,
PeriodInput,
)
from ccnl_engine.inputs import ContributableHours, WeeklyHours
from ccnl_engine.inputs import Permanent
engine = PayrollEngine.bundled()
# Non-convivente domestic worker, level B, 25 h of a 40-hour week (the
# week of the non-convivente minimum), 108 hours in January
result = engine.calculate_period(
PeriodInput(
run=PayrollRun.regular(year=2026, month=1),
payment_date=date(2026, 1, 28),
employment=Employment(
ccnl_slug="lavoro-domestico-non-convivente.json",
level_code="B",
weekly_hours=WeeklyHours(25),
full_time_weekly_hours=WeeklyHours(40),
contract_type=Permanent(),
),
employer=EmployerProfile(headcount=Headcount(1)),
facts=PeriodFacts(contributable_hours=ContributableHours(Decimal(108))),
)
)
print(f"Period gross: {result.period_gross}")
print(f"Period net: {result.period_net}")
print(f"Period employer cost: {result.period_employer_cost}")
cb = result.contribution_breakdown
print(f"Employee INPS: {cb.employee}")
print(f"Employer INPS: {cb.employer}")
# Cas.Sa.Colf (CCNL art. 54 c. 2): 0.02 per hour withheld, 0.04 paid by the employer
assistance = next(
d for d in result.decisions if d.capability == "assistance_contribution"
)
print(f"Cas.Sa.Colf worker: {assistance.inputs['employee']}")
print(f"Cas.Sa.Colf employer: {assistance.inputs['employer']}")
# A household employer is not a withholding agent: net = gross - employee shares
skipped = [
d.capability for d in result.decisions if d.reason_code == "not_withholding_agent"
]
print(f"Not withheld: {', '.join(skipped)}")
No withholding on the payslip¶
A household employer (datore di lavoro domestico) is not a withholding agent. The withholding agents are listed by art. 23 c. 1 DPR 600/1973 (in force until 31 December 2026) and, from 1 January 2027, by art. 33 c. 1 of the testo unico of D.Lgs. 33/2025 (art. 243, as amended by D.L. 200/2025 art. 4): entities and companies, partnerships, individuals running a business or a profession, the condominium. A private household is not among them. The payroll taxes and credits of an ordinary payslip all go through the withholding agent:
| Item | Source | Domestic payslip |
|---|---|---|
| IRPEF withholding and conguaglio | art. 23 c. 1-3 DPR 600/1973 | none |
| Addizionale regionale | D.Lgs. 446/1997 art. 50 c. 4 | none |
| Addizionale comunale | D.Lgs. 360/1998 art. 1 c. 5 | none |
| Trattamento integrativo | D.L. 3/2020 art. 1 c. 3 | none |
| Somma esente, ulteriore detrazione | L. 207/2024 art. 1 c. 7 | none |
The net pay is therefore the gross less the employee INPS contributions and the worker share of the Cas.Sa.Colf contribution (and any other non-tax deduction, such as unpaid absences). Convivente level A, September 2026, 40 weekly hours and 173 contributable hours: gross €908.10, employee INPS 173 × €0.31 = €53.63, Cas.Sa.Colf 173 × €0.02 = €3.46, net €851.01.
The engine takes this from the CCNL: CCNLMeta.withholding_agent is false for
the lavoro-domestico tax sector. For such a run:
tax_computationis empty (no components, no withholding, no credit) and the ledger has noordinary_tax,surtax,substitute_taxorcreditsentry;- each skipped capability (
irpef,family_deductions,ulteriore_detrazione_lavoro,trattamento_integrativo,somma_esente,addizionale_regionale,addizionale_comunale) records a finalCalculationDecisionwith reasonnot_withholding_agent, amount 0 and the article as its source, and its capability trace isnot_applicable; - a PdR or a pay item under a substitute regime is ordinary income: its
decision has reason
not_withholding_agentand no substitute tax; - the reconciliation invariant
non_agent_untaxedchecks that no tax or credit is posted and that the net does not exceed the gross; - an opening state carrying a credit recovery, a withholding shortfall or
IRPEF or surtax withheld is rejected with
InvalidInputError: a household employer cannot have produced it.
The taxable income is still accumulated in state.cash.earnings.taxable, since
the worker declares it.
API reference: Employment, PeriodFacts,
PeriodResult