C’è un numero che tiene svegli i responsabili della logistica e i CFO più di ogni altro, ma per motivi opposti. Il responsabile commerciale ha il terrore di vedere lo scaffale vuoto e perdere una vendita; il direttore finanziario ha il terrore di vedere bancali di merce ferma a prendere polvere, divorando liquidità che servirebbe altrove.
La gestione delle scorte è tutta qui: una costante, faticosa ricerca di equilibrio tra la paura di restare senza e il costo di avere troppo.
Spesso questo equilibrio viene gestito “a sensazione” o con fogli di calcolo ereditati da chi c’era prima, pieni di costanti magiche che nessuno osa toccare. Ma quando i margini si assottigliano e la catena di fornitura diventa nervosa, l’intuizione non basta più. Serve la matematica.
In questo articolo non parleremo di teoria astratta.
Costruiremo insieme un template Python che trasforma dubbi operativi (“Quanto ordino?”) in decisioni quantitative. Metteremo insieme tre acronimi fondamentali (EOQ, ROP, SS) per capire non solo come risparmiare, ma come proteggere l’azienda dagli imprevisti, trasformando il magazzino da centro di costo a meccanismo di precisione.
-
EOQ (Economic Order Quantity) → ottimizza quanto ordinare
-
ROP (Reorder Point) → stabilisce quando ordinare
-
SS (Safety Stock) → protegge dall’incertezza della domanda e del lead time
Questa combinazione permette di passare da una gestione “reattiva” delle scorte a una gestione quantitativa, predittiva e strategica.
🎯 Obiettivi del modello
✅ 1. Ottimizzare i costi
Il modello permette di trovare il punto in cui i due costi principali si bilanciano:
- Costo di ordinazione (troppi ordini → inefficienza)
- Costo di mantenimento (troppo stock → capitale immobilizzato)
L’EOQ individua la quantità che minimizza il costo totale annuo.
Questo è un risultato chiave per CFO, responsabili logistica e supply chain.
✅ 2. Gestire l’incertezza della domanda
La domanda reale non è mai perfettamente stabile. Per questo il modello calcola:
- [math]\sigma[/math] (deviazione standard) → misura della variabilità
- SS (scorta di sicurezza) → protezione contro picchi e ritardi
- ROP (punto di riordino) → soglia che attiva un nuovo ordine prima che lo stock si esaurisca
Questi elementi trasformano la variabilità in decisioni operative concrete.
✅ 3. Supportare decisioni strategiche
Il modello consente di rispondere a domande cruciali:
- Quanto stock devo tenere per garantire un livello di servizio del 95%?
- Cosa succede ai costi se aumento il livello di servizio?
- Come cambia il punto di riordino se il lead time aumenta?
- Qual è l’impatto della domanda volatile rispetto a una domanda stabile?
I grafici inclusi nel template rendono queste risposte visive e immediate, perfette per presentazioni a stakeholder non tecnici.
📊 Significato degli output
1. EOQ ([math]Q^*[/math])
È la quantità ottimale da ordinare ogni volta.
Interpretazione pratica:
- Ordini troppo piccoli → costi di ordine alti
- Ordini troppo grandi → costi di magazzino alti
- EOQ → equilibrio perfetto
2. [math]\sigma[/math] (deviazione standard della domanda)
Misura quanto la domanda oscilla. È il cuore del modello perché determina:
- rischio di stockout
- dimensione della scorta di sicurezza
- stabilità del punto di riordino
3. SS (Safety Stock)
È la “cintura di sicurezza” del magazzino. Protegge da:
- picchi di domanda
- ritardi dei fornitori
- errori di previsione
Più è alto il livello di servizio desiderato, più SS cresce.
4. ROP (Reorder Point)
È il livello di stock che attiva automaticamente un nuovo ordine.
Formula:
[math]\displaystyle \begin{aligned} ROP &= \text{domanda media durante il lead time} \\ &{} \quad + SS \end{aligned}[/math]
Interpretazione:
- Se lo stock scende sotto ROP → rischio di stockout
- Se ROP è troppo basso → perdita di vendite
- Se ROP è troppo alto → capitale immobilizzato
Come renderlo uno strumento pratico per l’azienda
✅ 1. Collegarlo ai dati reali
Basta sostituire:
- serie storiche della domanda
- lead time reali
- costi di ordine e mantenimento
per trasformarlo in un modello operativo.
✅ 2. Usarlo per scenari “what-if”
Il template è perfetto per simulare:
- aumento del lead time
- domanda più volatile
- cambiamento del livello di servizio
- variazione dei costi di magazzino
Questo permette di fare analisi di sensibilità e prendere decisioni basate sui dati.
✅ 3. Integrarlo nei processi decisionali
Il modello può diventare:
- uno strumento per il team supply chain
- un supporto per il CFO nelle decisioni di budget
- un asset didattico per formare nuovi analisti
- una base per automatizzare la gestione scorte in ERP o dashboard
✅ 4. Visualizzare per comunicare
I grafici inclusi:
- mostrano il punto ottimale EOQ
- evidenziano il trade-off tra costi
- rendono intuitivo l’impatto del livello di servizio
Perfetti per presentazioni a manager, investitori o team operativi.
Chi usa questi script oggi?
Non serve essere Amazon per usare questi algoritmi. Oggi, il ruolo del Supply Chain Analyst è ibrido: non è più solo chi sposta scatole, ma chi sa scrivere tre righe di codice per simulare uno scenario. Portare un grafico come quello che genereremo qui sotto alla prossima riunione di budget significa smettere di discutere di opinioni (“Secondo me il fornitore B è lento”) e iniziare a discutere di fatti (“Il fornitore B ci costa 14.000€ l’anno in stock extra”).
Struttura del modello
- Parte 1: funzioni di calcolo (EOQ, costi, SS, ROP)
- Parte 2: scenario base con numeri esemplificativi
- Parte 3: grafici:
- EOQ vs dimensione ordine
- Impatto del livello di servizio (k) su SS e ROP
🧮 Parte 1 – Funzioni principali
import numpy as np
import matplotlib.pyplot as plt
# -----------------------------
# Funzioni EOQ e costi
# -----------------------------
def eoq(D, S, H):
"""
EOQ = sqrt(2DS / H)
D: domanda annua
S: costo fisso per ordine
H: costo di mantenimento a magazzino per unità per anno
"""
return np.sqrt(2 * D * S / H)
def costo_totale_annuo(D, S, H, Q):
"""
Costo totale = costo di ordinazione + costo di mantenimento
= (D/Q)*S + (Q/2)*H
(senza considerare costo di acquisto unitario, che è costante rispetto a Q)
"""
costo_ordine = (D / Q) * S
costo_mantenimento = (Q / 2) * H
return costo_ordine + costo_mantenimento
# -----------------------------
# Funzioni deviazione standard, SS, ROP
# -----------------------------
def calcola_sigma(domanda):
"""
Deviazione standard della domanda (popolazione).
domanda: array o lista di valori (es. domanda giornaliera o settimanale).
"""
return np.std(domanda, ddof=0)
def scorta_sicurezza_sigma(sigma_demand, k, L):
"""
Scorta di sicurezza nel caso di variabilità della domanda durante il lead time.
sigma_demand: deviazione standard della domanda per unità di tempo
k: coefficiente di servizio (es. 1.65 per ~95%)
L: lead time (in stesse unità di tempo della domanda)
SS = k * sigma_demand * sqrt(L)
"""
return k * sigma_demand * np.sqrt(L)
def reorder_point(demand_media, L, SS):
"""
ROP = domanda media durante il lead time + scorta di sicurezza
demand_media: domanda media per unità di tempo
L: lead time
SS: scorta di sicurezza
"""
return demand_media * L + SS
📊 Parte 2 – Scenario base (numeri esemplificativi)
# Parametri base dell'inventory
D = 12000 # domanda annua (unità/anno)
S = 50 # costo di emissione di un ordine (€/ordine)
H = 2 # costo di mantenimento (€/unità/anno)
# Dati di domanda per calcolo sigma (es. domanda settimanale)
domanda_settimanale = np.array([200, 230, 210, 250, 240, 220, 205, 260])
# Parametri servizio/logistici
k = 1.65 # coefficiente livello di servizio (≈95%)
lead_time = 2 # lead time in settimane
# Calcoli
Q_eoq = eoq(D, S, H)
sigma_sett = calcola_sigma(domanda_settimanale)
domanda_media_sett = np.mean(domanda_settimanale)
SS = scorta_sicurezza_sigma(sigma_sett, k, lead_time)
ROP = reorder_point(domanda_media_sett, lead_time, SS)
print(f"EOQ (Q*): {Q_eoq:.2f} unità")
print(f"Domanda media settimanale: {domanda_media_sett:.2f}")
print(f"Deviazione standard domanda settimanale (σ): {sigma_sett:.2f}")
print(f"Scorta di sicurezza (SS): {SS:.2f} unità")
print(f"Punto di riordino (ROP): {ROP:.2f} unità")
EOQ (Q*): 774.60 unità
Domanda media settimanale: 226.88
Deviazione standard domanda settimanale (σ): 20.45
Scorta di sicurezza (SS): 47.73 unità
Punto di riordino (ROP): 501.48 unità
⚠️ Il limite dell’EOQ:
Attenzione alla “trappola della media” L’EOQ è un modello elegante, ma ha un difetto: odia la realtà. Presuppone che la domanda sia costante e il prezzo fisso. Nel mondo reale, dove i prezzi delle materie prime oscillano e la domanda è stagionale, seguire ciecamente l’EOQ può essere pericoloso. Usate questo numero come una bussola, non come un GPS: vi indica la direzione del costo minimo, ma sta a voi guidare aggirando gli ostacoli del mercato.
📈 Parte 3 – Grafici: EOQ e sensibilità dei costi
Costo totale in funzione di Q
# Range di Q attorno all'EOQ
Q_values = np.linspace(Q_eoq * 0.2, Q_eoq * 2.0, 100)
costi_totali = [costo_totale_annuo(D, S, H, Q) for Q in Q_values]
plt.figure(figsize=(7, 4))
plt.plot(Q_values, costi_totali, label="Costo totale annuo")
plt.axvline(Q_eoq, color="red", linestyle="--", label=f"EOQ ≈ {Q_eoq:.0f}")
plt.title("Costo totale annuo in funzione di Q")
plt.xlabel("Quantità per ordine (Q)")
plt.ylabel("Costo annuale (solo ordine + mantenimento)")
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()

Come leggerlo:
- La curva ha una forma a U.
- Il minimo corrisponde all’EOQ: politica d’ordine che equilibra costo di ordine e costo di mantenimento.
Impatto del livello di servizio (k) su SS e ROP
# Variazione del livello di servizio
k_values = np.linspace(0.5, 2.5, 9) # da ~70% a >99% come idea
SS_values = []
ROP_values = []
for k_i in k_values:
SS_i = scorta_sicurezza_sigma(sigma_sett, k_i, lead_time)
ROP_i = reorder_point(domanda_media_sett, lead_time, SS_i)
SS_values.append(SS_i)
ROP_values.append(ROP_i)
fig, ax1 = plt.subplots(figsize=(7, 4))
color1 = "tab:blue"
ax1.set_xlabel("k (coefficiente livello di servizio)")
ax1.set_ylabel("Scorta di sicurezza (SS)", color=color1)
ax1.plot(k_values, SS_values, marker="o", color=color1, label="SS")
ax1.tick_params(axis="y", labelcolor=color1)
ax1.grid(True, alpha=0.3)
ax2 = ax1.twinx()
color2 = "tab:orange"
ax2.set_ylabel("Punto di riordino (ROP)", color=color2)
ax2.plot(k_values, ROP_values, marker="s", color=color2, label="ROP")
ax2.tick_params(axis="y", labelcolor=color2)
plt.title("Impatto del livello di servizio su SS e ROP")
fig.tight_layout()
plt.show()

Come leggerlo didatticamente:
All’aumentare di [math]k[/math]:
- SS cresce linearmente → più scorta per ridurre stockout.
- ROP si alza perché include SS → si riordina “prima” per essere più sicuri.
Perfetto per discutere il trade-off tra:
- livello di servizio elevato
- aumento dei costi di magazzino
🚀 Parte 4: Quando la realtà colpisce il modello (L’incertezza del Fornitore)
Finora abbiamo lavorato in un mondo ideale. Abbiamo ipotizzato che, una volta inviato l’ordine, il camion del fornitore arrivi esattamente dopo 2 settimane.
Chiunque lavori nella supply chain sa che questo non succede quasi mai. Scioperi, dogane bloccate, materie prime mancanti: il Lead Time non è un numero fisso, è una variabile impazzita. Se il vostro modello ignora questo fatto, vi troverete con gli scaffali vuoti proprio quando il cliente chiama.
In questa sezione, faremo un passo avanti cruciale: smetteremo di guardare solo al cliente (domanda variabile) e inizieremo a guardare anche al fornitore (consegna variabile). Vedremo matematicamente perché un fornitore “economico ma inaffidabile” può costare all’azienda il triplo in scorte di sicurezza rispetto a uno puntuale.
1. La Formula della Scorta di Sicurezza “Totale”
Per essere veramente protetti, dobbiamo considerare la variabilità combinata. La nuova formula per la Scorta di Sicurezza (SS)
diventa:
[math]\displaystyle SS = k \cdot \sqrt{(L \cdot \sigma_D^2) + (\bar{D}^2 \cdot \sigma_L^2)}[/math]
Dove:
- [math]\sigma_D[/math]: Deviazione standard della domanda.
- [math]\sigma_L[/math]: Deviazione standard del Lead Time (l’incertezza del fornitore).
- [math]\bar{D}[/math]: Domanda media.
2. Simulazione: L’impatto di un fornitore inaffidabile
Riprendiamo i dati precedenti e confrontiamo due fornitori:
| Variabile | Fornitore A (Affidabile) | Fornitore B (Instabile) |
|---|---|---|
| Lead Time Medio | 2 settimane | 2 settimane |
| Variabilità LT ([math]\sigma_L[/math]) | 0 (Sempre puntuale) | 0.5 settimane (Oscilla) |
| Scorta di Sicurezza (SS) | ~47 unità | ~193 unità |
Risultato Shock: Solo perché il fornitore B è “instabile” nelle consegne, l’azienda deve quasi quadruplicare lo stock di sicurezza per garantire lo stesso livello di servizio (95%).
3. Visualizzare la Sensibilità (Python Concept)
Un grafico di sensibilità ci permette di vedere come il Costo Totale reagisce al variare di un parametro chiave (es. il costo di mantenimento H).
Cosa ci dice questa analisi?
- Se la curva è molto ripida, quel parametro è critico: un piccolo errore di stima costerà migliaia di euro.
- Se la curva è piatta (come spesso accade vicino all’EOQ), il modello è robusto: anche se non ordiniamo l’esatta quantità ottimale, l’impatto economico sarà minimo.
✅Simulazione Fornitori A vs B + Grafico Sensibilità
import numpy as np
import matplotlib.pyplot as plt
# NOTA: Assicurati di aver eseguito le funzioni della Parte 1 (eoq, reorder_point, costo_totale_annuo)
# Se stai eseguendo solo questo blocco, ri-definisci reorder_point qui sotto:
def reorder_point(D_media, L, SS): return D_media * L + SS
# ---------------------------------------------------------
# 1. Nuova Funzione: Scorta di Sicurezza "Totale"
# ---------------------------------------------------------
def scorta_sicurezza_totale(sigma_D, sigma_L, D_media, L, k):
"""
Calcola la SS considerando due fonti di incertezza:
1. Variabilità della domanda (sigma_D)
2. Variabilità del Lead Time del fornitore (sigma_L)
Formula: SS = k * sqrt( L*sigma_D^2 + D_media^2*sigma_L^2 )
"""
# Componente di rischio legata alla domanda (cliente)
rischio_domanda = L * (sigma_D**2)
# Componente di rischio legata al fornitore
# NOTA: Questo termine pesa molto perché moltiplica la domanda media al quadrato!
rischio_fornitore = (D_media**2) * (sigma_L**2)
return k * np.sqrt(rischio_domanda + rischio_fornitore)
# ---------------------------------------------------------
# 2. Simulazione Scenario: Fornitore A vs Fornitore B
# ---------------------------------------------------------
# Dati Storici (Domanda Settimanale)
domanda = np.array([200, 230, 210, 250, 240, 220, 205, 260])
sigma_D = np.std(domanda) # ≈ 20.45
D_media = np.mean(domanda) # ≈ 226.88
# Parametri Logistici
k = 1.65 # Livello di servizio 95%
L = 2 # Lead time medio (settimane)
# Confronto Fornitori
# Fornitore A: Puntuale come un orologio svizzero (sigma_L = 0)
sigma_L_A = 0.0
# Fornitore B: Instabile, a volte ritarda mezza settimana (sigma_L = 0.5)
sigma_L_B = 0.5
# Calcolo SS e ROP
SS_A = scorta_sicurezza_totale(sigma_D, sigma_L_A, D_media, L, k)
SS_B = scorta_sicurezza_totale(sigma_D, sigma_L_B, D_media, L, k)
ROP_A = reorder_point(D_media, L, SS_A)
ROP_B = reorder_point(D_media, L, SS_B)
print(f"=== ANALISI IMPATTO FORNITORE ===")
print(f"Domanda media: {D_media:.2f} | Dev.Std Domanda: {sigma_D:.2f}")
print("-" * 40)
print(f"FORNITORE A (Affidabile, σ_L=0.0)")
print(f" >> Scorta di Sicurezza: {SS_A:.2f} unità")
print(f" >> Punto di Riordino: {ROP_A:.2f} unità")
print("-" * 40)
print(f"FORNITORE B (Instabile, σ_L=0.5)")
print(f" >> Scorta di Sicurezza: {SS_B:.2f} unità")
print(f" >> Punto di Riordino: {ROP_B:.2f} unità")
print("-" * 40)
print(f"RISULTATO: L'instabilità del fornitore B richiede +{SS_B - SS_A:.0f} unità di stock extra (+{((SS_B/SS_A)-1)*100:.1f}%)")
# ---------------------------------------------------------
# 3. Grafico di Sensibilità: Robustezza dell'EOQ
# ---------------------------------------------------------
# Parametri per lo scenario annuale
D_annua = 12000
S = 50
# Variamo il costo di mantenimento (H) per vedere quanto impatta sul Costo Totale
H_values = np.linspace(0.5, 10, 50)
# Ricalcoliamo EOQ e Costi per ogni possibile valore di H
costi_ottimali = []
for h_val in H_values:
# 1. Calcolo EOQ ottimale per quel costo H
q_opt = np.sqrt(2 * D_annua * S / h_val)
# 2. Calcolo costo totale
c_tot = (D_annua / q_opt) * S + (q_opt / 2) * h_val
costi_ottimali.append(c_tot)
plt.figure(figsize=(8, 5))
plt.plot(H_values, costi_ottimali, color="#2c3e50", linewidth=2, label="Costo Minimo Ottimizzabile")
plt.fill_between(H_values, costi_ottimali, color="#2c3e50", alpha=0.1)
plt.title("Analisi di Sensibilità: Come reagiscono i costi al variare di H", fontsize=12)
plt.xlabel("Costo di Mantenimento H (€/unità/anno)")
plt.ylabel("Costo Totale Annuo Minimo (€)")
plt.grid(True, linestyle="--", alpha=0.6)
plt.legend()
plt.tight_layout()
plt.show()
=== ANALISI IMPATTO FORNITORE ===
Domanda media: 226.88 | Dev.Std Domanda: 20.45
----------------------------------------
FORNITORE A (Affidabile, σ_L=0.0)
>> Scorta di Sicurezza: 47.73 unità
>> Punto di Riordino: 501.48 unità
----------------------------------------
FORNITORE B (Instabile, σ_L=0.5)
>> Scorta di Sicurezza: 193.16 unità
>> Punto di Riordino: 646.91 unità
----------------------------------------
RISULTATO: L'instabilità del fornitore B richiede +145 unità di stock extra (+304.7%)

Analisi Strategica dei Risultati: L’Invisibile Costo dell’Inaffidabilità
I numeri estratti dalla simulazione non sono solo dati statistici, ma rivelano una verità economica profonda sulla gestione della Supply Chain.
Fornitore A (Affidabile)
48 unità
Scorta di Sicurezza necessaria
Fornitore B (Instabile)
193 unità
Scorta di Sicurezza necessaria
Perché questo aumento del 300%?
La spiegazione risiede nella struttura della formula matematica. Mentre la variabilità della domanda ([math]\sigma_D[/math]) impatta in modo moderato, l’incertezza del fornitore ([math]\sigma_L[/math]) ha un effetto moltiplicativo devastante:
“Nell’incertezza del fornitore, l’errore non riguarda solo qualche pezzo in più o in meno, ma l’intera domanda media ([math]\bar{D}[/math]) che rimane ‘scoperta’ durante i giorni di ritardo.”
💡 Lezione di Business: Il Paradosso del Risparmio
Spesso l’ufficio acquisti è tentato da fornitori che offrono un prezzo unitario inferiore (es. 0,10€ in meno al pezzo). Tuttavia, se quel fornitore è irregolare nelle consegne:
- Capitale Immobilizzato: Dovrai bloccare liquidità per 193 unità invece di 48.
- Costi di Magazzino: Più spazio occupato, più rischio di obsolescenza e costi di assicurazione.
- Rischio Operativo: Nonostante l’alta scorta, il rischio di fermo produzione o perdita vendite rimane superiore.
Conclusione: Un fornitore “economico” ma inaffidabile costa quasi sempre di più di un fornitore “premium” puntuale. L’analisi della varianza trasforma una percezione qualitativa in un costo quantificabile.
Insight Finale per il Management: Spesso conviene pagare un fornitore leggermente di più per avere la certezza dei tempi di consegna, piuttosto che risparmiare sul prezzo unitario e dover poi immobilizzare capitali enormi in scorte di sicurezza.
Dalla Statistica alla Strategia: Oltre il Prezzo Unitario
Questo esempio non è un semplice calcolo matematico, ma uno strumento di Business Intelligence che smantella il mito del “prezzo più basso ad ogni costo”. Ecco come questi dati si traducono in vantaggi competitivi reali:
1. Potere Negoziale basato sui Dati
Spesso le trattative con i fornitori si basano su sensazioni. Con questa analisi, il responsabile acquisti dispone di un’arma oggettiva:
- Puoi dimostrare al Fornitore B che la sua inaffidabilità obbliga l’azienda a mantenere 145 unità di stock extra (193 – 48).
- Questo valore può essere convertito in Euro (costo del capitale + costo dello spazio) per pretendere penali sui ritardi o uno sconto che compensi realmente i costi logistici extra.
2. Mitigazione del Rischio: L’Effetto Moltiplicatore
L’esercizio evidenzia una gerarchia del rischio fondamentale:
Il rischio fornitore ([math]\sigma_L[/math]) pesa spesso più del rischio domanda ([math]\sigma_D[/math]) perché, matematicamente, viene moltiplicato per il quadrato della domanda media ([math]\bar{D}^2[/math]).
In prodotti ad alta rotazione (dove [math]\bar{D}[/math] è elevata), l’affidabilità del fornitore diventa il fattore dominante. Un errore di un giorno nella consegna può essere più costoso di un errore del 20% nella previsione delle vendite.
3. Strategia di Sourcing: Locale vs Globale
Questa analisi rende visibili i “costi invisibili” della logistica globale:
- Fornitore Internazionale: Prezzo basso, ma lead time lungo e incerto (alto [math]L[/math] e [math]\sigma_L[/math]). Risultato: Magazzino enorme.
- Fornitore Locale: Prezzo più alto, ma lead time breve e stabile. Risultato: Magazzino snello (Lean) e capitale libero.
📌 KPI Dashboard di Sintesi
Per una gestione eccellente, un manager dovrebbe monitorare mensilmente:
| Indice di Affidabilità Fornitore: | [math]\sigma_L[/math] (deve tendere a 0) |
| Efficienza del Capitale: | Valore SS / Fatturato Totale |
| Livello di Servizio Reale: | % ordini evasi senza stockout |
Supply Chain, KPI & Gestione Operativa
👉 Deviazione standard: calcolo pratico ed esempi di gestione scorte
👉 Valutazione fornitori: metodi efficaci, KPI e esempi pratici
👉 Valutazione fornitori: costo totale di proprietà (TCO)
👉 Vendor Managed Inventory (VMI): guida completa
👉 Gestione magazzino: FIFO, LIFO, FEFO, JIT, VMI e ottimizzazione ERP





