Chi apre la biblioteca?
Quattro addetti sempre in servizio, dalle nove alle diciannove. Ma il pubblico non arriva mai tutto insieme — e i turni possono seguirlo.
- Metodo
- Programmazione lineare intera (ILP)
- Strumenti
- Python, PuLP
- Fonte dati
- Open data comunali — ingressi biblioteca · ISTAT — Aspetti della vita quotidiana
- Risultato
- −15% — ore-persona a parità di copertura
Le code al banco di una biblioteca civica non fanno notizia, ma sono un problema di ottimizzazione da manuale: la domanda varia durante il giorno, l’offerta no. La biblioteca di questo numero apre dalle 9 alle 19 con quattro addetti fissi, dall’apertura alla chiusura. Il contapersone all’ingresso — i cui dati il comune pubblica come open data — racconta una giornata molto meno uniforme.
01Il problema e i dati
Gli ingressi medi per fascia oraria disegnano la classica doppia gobba, con il pomeriggio che vale il doppio della mattina:
La giornata della biblioteca
Ingressi medi per fascia oraria (media dei giorni feriali)
Vedi i dati in tabella
| Ingressi | |
|---|---|
| 9–11 | 34 |
| 11–13 | 61 |
| 13–15 | 45 |
| 15–17 | 78 |
| 17–19 | 96 |
Open data comunali, contapersone all'ingresso (dati esemplificativi)
Vogliamo turni che garantiscano una copertura minima proporzionale agli ingressi — un addetto ogni venti presenze stimate, mai meno di due — al costo minimo di ore-persona, usando turni contrattuali ammissibili (6 oppure 8 ore, con ingresso a ogni ora piena).
02Il modello
È un problema di copertura di turni (set covering pesato): per ogni turno ammissibile t, la variabile intera yt conta quante persone lo fanno.
min Σt ore(t) · yt
t.c. Σt ∋ f yt ≥ copertura(f) per ogni fascia f
yt ∈ ℕ
import pulp
m = pulp.LpProblem("turni_biblioteca", pulp.LpMinimize)
y = {t: pulp.LpVariable(f"turno_{t}", lowBound=0, cat="Integer")
for t in turni_ammissibili}
m += pulp.lpSum(ore[t] * y[t] for t in turni_ammissibili)
for f in fasce:
m += pulp.lpSum(y[t] for t in turni_ammissibili if copre(t, f)) >= copertura[f]
m.solve()
03I risultati
La soluzione ottima usa cinque persone su turni sfalsati invece di quattro su turno pieno — ma meno ore totali, perché nessuno presidia a vuoto le fasce tranquille:
Addetti in servizio per fascia
Prima (presidio fisso) e dopo (turni sfalsati sull'affluenza)
Vedi i dati in tabella
| Turno fisso | Turni ottimizzati | |
|---|---|---|
| 9–11 | 4 | 2 |
| 11–13 | 4 | 3 |
| 13–15 | 4 | 3 |
| 15–17 | 4 | 4 |
| 17–19 | 4 | 5 |
Copertura minima: un addetto ogni 20 ingressi stimati, mai meno di due.
Soluzione del modello ILP (caso esemplificativo)
Il punto non è “tagliare”: è che il vecchio orario sotto-serviva l’ora di punta e sovra-serviva la mattina. Il modello sposta il lavoro dove c’è il pubblico; il risparmio è un sottoprodotto.
Prima di chiedere più risorse, vale la pena chiedersi se quelle attuali sono nel posto giusto. Spesso la risposta costa venti righe di codice.
04I limiti
I turni veri hanno vincoli che qui abbiamo semplificato: pause pranzo, part-time, preferenze del personale, picchi stagionali (sessioni d’esame!). Tutti si aggiungono al modello come righe in più, ed è esattamente ciò che rende l’approccio interessante: il negoziato sindacale può discutere di vincoli espliciti invece che di abitudini implicite.