diff --git a/src/dataset/mit-incidentalita.md b/src/dataset/mit-incidentalita.md
index c9e7422..20ed079 100644
--- a/src/dataset/mit-incidentalita.md
+++ b/src/dataset/mit-incidentalita.md
@@ -1,6 +1,6 @@
---
title: Incidenti stradali
-description: Incidenti stradali, morti e feriti in Italia — serie mensile MIT, 2001-2018
+description: Incidenti stradali, morti e feriti in Italia — serie mensile MIT 2001-2018, con indice di mortalità e stagionalità
source: MIT — Ministero delle Infrastrutture e dei Trasporti
source_url: https://www.mit.gov.it/
period: "2001–2018"
@@ -11,14 +11,14 @@ data_driven: true
# Incidenti stradali
-**Nel ${String(annoSel)} in Italia si sono registrati ${num(totIncidenti)} incidenti stradali, con ${num(totMorti)} morti e ${num(totFeriti)} feriti. Dal 2001 i morti sono diminuiti del ${numFix((1 - annuale[annuale.length - 1].morti / annuale[0].morti) * 100, 0)}%, ma il cammino verso la "Vision Zero" è ancora lungo.**
+**Nel ${String(annoSel)} in Italia si sono registrati ${num(totIncidenti)} incidenti stradali, con ${num(totMorti)} morti e ${num(totFeriti)} feriti. Dal ${first} i morti sono diminuiti del ${numFix(caloMorti, 0)}%, ma la discesa si è fermata: nei ${SOGLIA - first} anni prima del ${SOGLIA} si risparmiavano ${num(viteAnnoPrima)} vite all'anno, nei ${last - SOGLIA} successivi appena ${num(Math.abs(viteAnnoDopo))}. Intanto ogni singolo incidente uccide meno — l'indice di mortalità cala del ${numFix(caloIndice, 0)}% — e ad ${mesePiuLetale.mese.toLowerCase()} è il ${numFix(letalitaExtra, 0)}% più letale che a ${meseMenoLetale.mese.toLowerCase()}.**
-Serie mensile di incidenti stradali, morti e feriti in Italia. I dati mostrano l'evoluzione della sicurezza stradale dal 2001 al 2018 e gli effetti delle campagne di prevenzione. Ogni numero di questa pagina è calcolato dal dato a build-time.
+Serie mensile di incidenti stradali, morti e feriti in Italia. I dati mostrano l'evoluzione della sicurezza stradale dal ${first} al ${last} e quali mesi concentrano il rischio. Ogni numero di questa pagina è calcolato dal dato a build-time.
-**Fonte**: [MIT](https://www.mit.gov.it/) · **Periodo**: 2001–2018 · Dati mensili
+**Fonte**: [MIT](https://www.mit.gov.it/) · **Periodo**: ${first}–${last} · Dati mensili
```js
-import { num, numFix, pct, tableFormat } from "../import/format-utils.js";
+import { num, numFix, tableFormat } from "../import/format-utils.js";
```
```js
@@ -31,10 +31,15 @@ const annoSel = view(Inputs.select(new Map(anni.map(a => [String(a), a])), {labe
```
```js
-const filtered = data.filter(d => d.anno === annoSel);
+const filtered = data.filter(d => d.anno === annoSel).map(d => ({
+ ...d,
+ indice_mortalita: d.incidenti ? (d.morti / d.incidenti) * 100 : null,
+}));
const totIncidenti = d3.sum(filtered, d => d.incidenti);
const totMorti = d3.sum(filtered, d => d.morti);
const totFeriti = d3.sum(filtered, d => d.feriti);
+const totMortali = d3.sum(filtered, d => d.incidenti_mortali);
+const indiceAnno = totIncidenti ? (totMorti / totIncidenti) * 100 : null;
```
```js
@@ -44,12 +49,91 @@ const annuale = Array.from(
incidenti: d3.sum(v, d => d.incidenti),
morti: d3.sum(v, d => d.morti),
feriti: d3.sum(v, d => d.feriti),
+ incidenti_mortali: d3.sum(v, d => d.incidenti_mortali),
}), d => d.anno),
([anno, v]) => ({anno, ...v})
).sort((a, b) => a.anno - b.anno);
```
-
+```js
+// Serie annuale con l'indice di mortalità calcolato sul rapporto aggregato
+// (morti / incidenti dell'anno), non come media dei valori mensili della fonte.
+const serie = annuale.map(d => ({
+ ...d,
+ indice: d.incidenti ? (d.morti / d.incidenti) * 100 : null,
+}));
+
+const nAnni = serie.length;
+const first = serie[0].anno;
+const last = serie[nAnni - 1].anno;
+const base = serie[0];
+const ultimo = serie[nAnni - 1];
+
+const caloMorti = (base.morti - ultimo.morti) / base.morti * 100;
+const caloIncidenti = (base.incidenti - ultimo.incidenti) / base.incidenti * 100;
+const caloFeriti = (base.feriti - ultimo.feriti) / base.feriti * 100;
+const indiceBase = base.indice;
+const indiceUltimo = ultimo.indice;
+const caloIndice = (indiceBase - indiceUltimo) / indiceBase * 100;
+
+// Punto di svolta: dal 2013 la discesa dei morti si esaurisce.
+// viteAnnoPrima = vite risparmiate in media ogni anno fino alla soglia;
+// viteAnnoDopo = saldo medio annuo dopo la soglia (vicino a zero).
+const SOGLIA = 2013;
+const prima = serie.filter(d => d.anno <= SOGLIA);
+const dopo = serie.filter(d => d.anno >= SOGLIA);
+const viteAnnoPrima = (prima[0].morti - prima[prima.length - 1].morti) / (prima[prima.length - 1].anno - prima[0].anno);
+const viteAnnoDopo = (dopo[dopo.length - 1].morti - dopo[0].morti) / (dopo[dopo.length - 1].anno - dopo[0].anno);
+const plateauDelta = ultimo.morti - dopo[0].morti;
+```
+
+```js
+// Stagionalità: aggregato su tutti gli anni, mese per mese.
+// morti_medi = morti per mese in un anno medio; indice = morti ogni 100 incidenti.
+const stagionale = Array.from(
+ d3.rollup(data, v => ({
+ mese: v[0].mese,
+ incidenti: d3.sum(v, d => d.incidenti),
+ morti: d3.sum(v, d => d.morti),
+ }), d => d.mese_numero),
+ ([mese_numero, v]) => ({
+ mese_numero,
+ mese: v.mese,
+ incidenti_medi: v.incidenti / nAnni,
+ morti_medi: v.morti / nAnni,
+ indice: v.incidenti ? (v.morti / v.incidenti) * 100 : null,
+ })
+).sort((a, b) => a.mese_numero - b.mese_numero);
+
+const mesePiuMorti = stagionale.reduce((a, b) => (b.morti_medi > a.morti_medi ? b : a));
+const mesePiuLetale = stagionale.reduce((a, b) => (b.indice > a.indice ? b : a));
+const meseMenoLetale = stagionale.reduce((a, b) => (b.indice < a.indice ? b : a));
+const letalitaExtra = (mesePiuLetale.indice / meseMenoLetale.indice - 1) * 100;
+
+// Posizione dei due mesi chiave nella classifica per volume di incidenti:
+// serve a mostrare che la letalità alta non dipende dal numero di incidenti.
+const perIncidenti = [...stagionale].sort((a, b) => a.incidenti_medi - b.incidenti_medi);
+const posIncidentiPiuLetale = perIncidenti.findIndex(d => d.mese === mesePiuLetale.mese) + 1;
+```
+
+```js
+// Serie indicizzata base 2001 = 100: incidenti, morti e feriti hanno scale
+// molto diverse, l'indice rende confrontabili i tre cali.
+const indicizzato = serie.map(d => ({
+ anno: d.anno,
+ incidenti: d.incidenti / base.incidenti * 100,
+ morti: d.morti / base.morti * 100,
+ feriti: d.feriti / base.feriti * 100,
+}));
+
+const trendLines = indicizzato.flatMap(d => [
+ {anno: d.anno, tipo: "Incidenti", valore: d.incidenti},
+ {anno: d.anno, tipo: "Morti", valore: d.morti},
+ {anno: d.anno, tipo: "Feriti", valore: d.feriti}
+]);
+```
+
+
Incidenti
${num(totIncidenti)}
@@ -63,17 +147,23 @@ const annuale = Array.from(
Feriti
${num(totFeriti)}
+
+
Morti ogni 100 incidenti
+ ${numFix(indiceAnno, 2)}
+ indice di mortalità
+
---
## 1. Riepilogo annuale — ${String(annoSel)}
-Il bilancio dei ${String(annoSel)}: ${num(totIncidenti)} incidenti, ${num(totMorti)} morti, ${num(totFeriti)} feriti. L'indice di mortalità (morti per incidente) è dell'${pct(d3.sum(filtered, d => d.morti) / d3.sum(filtered, d => d.incidenti) * 100)}.
+Il bilancio del ${String(annoSel)}: ${num(totIncidenti)} incidenti, di cui ${num(totMortali)} mortali, con ${num(totMorti)} morti e ${num(totFeriti)} feriti. L'indice di mortalità — morti ogni 100 incidenti — è di ${numFix(indiceAnno, 2)}.
```js
const riepilogo = [
{voce: "Incidenti", valore: totIncidenti},
+ {voce: "Incidenti mortali", valore: totMortali},
{voce: "Morti", valore: totMorti},
{voce: "Feriti", valore: totFeriti},
];
@@ -95,85 +185,179 @@ Plot.plot({
})
```
-> **Nota di lettura**: il grafico mostra lo **stock** del ${String(annoSel)}: il numero totale di incidenti, morti e feriti nell'anno. La lettura del trend è nel grafico successivo.
+> **Nota di lettura**: il grafico mostra lo **stock** del ${String(annoSel)}: il numero totale di incidenti (di cui quelli mortali), morti e feriti nell'anno. Il trend è nei grafici successivi.
---
-## 2. Trend 2001–2018
+## 2. Il trend ${first}–${last}: i morti scendono più in fretta degli incidenti
-Incidenti, morti e feriti sono in costante calo dal 2001. Il miglioramento della sicurezza stradale è visibile su tutti e tre gli indicatori.
+Per confrontare grandezze con scale molto diverse, il grafico indicizza ogni serie al ${first} (base 100). Gli incidenti calano del ${numFix(caloIncidenti, 0)}%, i feriti del ${numFix(caloFeriti, 0)}%, i morti del ${numFix(caloMorti, 0)}%: la strada è diventata più sicura molto più di quanto sia diventata meno trafficata.
```js
Plot.plot({
- title: "Incidenti stradali in Italia — trend annuale 2001-2018",
+ title: `Incidenti, morti e feriti — base ${first} = 100`,
width: 800,
- height: 400,
+ height: 360,
x: {tickFormat: d => String(d), label: null},
- y: {grid: true, tickFormat: "~s"},
+ y: {grid: true, label: `% rispetto al ${first}`},
+ color: {legend: true, scheme: "Set1"},
marks: [
- Plot.lineY(annuale, {x: "anno", y: "incidenti", tip: true, stroke: "#4e79a7"}),
- Plot.dot(annuale, {x: "anno", y: "incidenti", fill: "#4e79a7", r: 2}),
- Plot.areaY(annuale, {x: "anno", y: "incidenti", fill: "#4e79a7", fillOpacity: 0.05})
+ Plot.ruleY([100], {stroke: "var(--theme-foreground-muted)", strokeDasharray: "4,4"}),
+ Plot.line(trendLines, {x: "anno", y: "valore", z: "tipo", stroke: "tipo", tip: true}),
+ Plot.dot(trendLines, {x: "anno", y: "valore", z: "tipo", fill: "tipo", r: 1.5}),
+ Plot.text(trendLines.filter(d => d.anno === last), {
+ x: "anno", y: "valore", text: d => numFix(d.valore, 0),
+ dx: 6, textAnchor: "start", fill: "tipo", fontSize: 11
+ })
]
})
```
+La linea tratteggiata è il livello del ${first}. I tre cali non sono paralleli: i morti scendono quasi il doppio degli incidenti, i feriti ancora meno.
+
---
-## 3. Calo percentuale dal 2001
+## 3. La discesa si ferma: le variazioni annue dal ${SOGLIA}
-Per confrontare la riduzione di morti e feriti — che hanno scale molto diverse — il grafico mostra la variazione percentuale rispetto al 2001 (base 100). I morti sono diminuiti molto più rapidamente dei feriti.
+Il trend aggregato nasconde il dato più importante dell'ultimo tratto della serie. Guardando la **variazione rispetto all'anno precedente**, le barre blu — gli anni in cui i morti diminuivano — si accorciano fino quasi a sparire, e dal ${SOGLIA} diventano arancioni: non c'è più una tendenza, solo oscillazioni.
```js
-const base2001 = annuale.find(d => d.anno === 2001);
-const indicizzato = annuale.map(d => ({
+const variazioni = serie.slice(1).map((d, i) => ({
anno: d.anno,
- incidenti: Math.round(d.incidenti / base2001.incidenti * 100),
- morti: Math.round(d.morti / base2001.morti * 100),
- feriti: Math.round(d.feriti / base2001.feriti * 100)
+ variazione: d.morti - serie[i].morti,
}));
+const mediaVarPrima = d3.mean(variazioni.filter(d => d.anno <= SOGLIA), d => d.variazione);
+const mediaVarDopo = d3.mean(variazioni.filter(d => d.anno > SOGLIA), d => d.variazione);
+```
-const trendLines = indicizzato.flatMap(d => [
- {anno: d.anno, tipo: "Incidenti", valore: d.incidenti},
- {anno: d.anno, tipo: "Morti", valore: d.morti},
- {anno: d.anno, tipo: "Feriti", valore: d.feriti}
-]);
+```js
+Plot.plot({
+ title: "Variazione annua dei morti rispetto all'anno precedente",
+ width: 800,
+ height: 300,
+ x: {tickFormat: d => String(d), label: null},
+ y: {grid: true, label: "vite in meno (negativo) o in più (positivo)"},
+ marks: [
+ Plot.barY(variazioni, {
+ x: "anno",
+ y: "variazione",
+ fill: d => (d.anno >= SOGLIA ? "#d95f0e" : "#4e79a7"),
+ tip: true
+ }),
+ Plot.ruleY([0])
+ ]
+})
```
+Fino al ${SOGLIA} ogni anno si contavano in media ${num(Math.abs(mediaVarPrima))} vittime in meno dell'anno precedente; dal ${SOGLIA} al ${last} il saldo medio scende a ${num(Math.abs(mediaVarDopo))} vite in meno l'anno, per un totale di ${num(Math.abs(plateauDelta))} in ${last - SOGLIA} anni. Il miglioramento non si è fermato perché la strada è tornata pericolosa: si è fermato perché la parte di riduzione "facile" era già stata fatta.
+
+```js
+// Discontinuità fra il 2012 e il 2013: gli incidenti rilevati calano di colpo
+// mentre i morti calano molto meno, quindi l'indice di mortalità sale.
+const annoBreak = serie.find(d => d.anno === SOGLIA);
+const annoPreBreak = serie.find(d => d.anno === SOGLIA - 1);
+const caloIncidentiBreak = (1 - annoBreak.incidenti / annoPreBreak.incidenti) * 100;
+const saltoIndiceBreak = (annoBreak.indice / annoPreBreak.indice - 1) * 100;
+```
+
+---
+
+## 4. Ogni incidente è meno letale: l'indice di mortalità
+
+L'indice di mortalità — morti ogni 100 incidenti — misura la letalità di un incidente, indipendentemente da quanti ne avvengono. È la ragione per cui i morti calano più degli incidenti: è passato da ${numFix(indiceBase, 2)} nel ${first} a ${numFix(indiceUltimo, 2)} nel ${last}, il ${numFix(caloIndice, 0)}% in meno.
+
```js
Plot.plot({
- title: "Incidenti, morti e feriti — base 2001 = 100",
+ title: `Indice di mortalità — morti ogni 100 incidenti (${first}–${last})`,
width: 800,
- height: 350,
+ height: 320,
x: {tickFormat: d => String(d), label: null},
- y: {grid: true, label: "% rispetto al 2001"},
- color: {legend: true, scheme: "Set1"},
+ y: {grid: true, label: "morti ogni 100 incidenti"},
marks: [
- Plot.line(trendLines, {x: "anno", y: "valore", z: "tipo", stroke: "tipo", tip: true}),
- Plot.dot(trendLines, {x: "anno", y: "valore", z: "tipo", fill: "tipo", r: 1.5}),
- Plot.ruleY([100], {stroke: "var(--theme-foreground-muted)", strokeDasharray: "4,4"})
+ Plot.ruleY([indiceBase], {stroke: "var(--theme-foreground-muted)", strokeDasharray: "4,4"}),
+ Plot.lineY(serie, {x: "anno", y: "indice", stroke: "#4e79a7", strokeWidth: 2, tip: true}),
+ Plot.dot(serie, {x: "anno", y: "indice", fill: "#fff", stroke: "#4e79a7"}),
+ Plot.dot(serie.filter(d => d.anno === last), {x: "anno", y: "indice", fill: "#d95f0e", r: 4, tip: true})
]
})
```
+La linea tratteggiata è il livello del ${first}. Anche questo indicatore si appiattisce dal ${SOGLIA}: la letalità non scende più, oscilla intorno al valore raggiunto.
+
+> **Come leggerlo**: fra il ${SOGLIA - 1} e il ${SOGLIA} gli incidenti rilevati calano del ${numFix(caloIncidentiBreak, 0)}% in un solo anno — un salto che segnala una discontinuità nella rilevazione, non solo un calo del traffico — e l'indice di mortalità sale del ${numFix(saltoIndiceBreak, 0)}% pur con ${num(Math.abs(annoPreBreak.morti - annoBreak.morti))} morti in meno. È un effetto di composizione, non un peggioramento improvviso delle strade.
+
---
-## Dettaglio mensile
+## 5. La stagionalità: ${mesePiuLetale.mese.toLowerCase()} è il mese più letale
+
+Il rischio segue la mobilità. ${mesePiuMorti.mese.toLowerCase()} è il mese con più vittime in assoluto (${num(mesePiuMorti.morti_medi)} al mese in media nei ${nAnni} anni della serie), ${meseMenoLetale.mese.toLowerCase()} quello con meno (${num(meseMenoLetale.morti_medi)}). Ma la lettura più utile è la **letalità**: ad ${mesePiuLetale.mese.toLowerCase()} ci sono meno incidenti — è il ${num(posIncidentiPiuLetale)}º mese su ${num(stagionale.length)} per numero di incidenti — eppure ogni incidente fa più vittime che in qualsiasi altro mese.
+
+```js
+Plot.plot({
+ title: "Morti al mese in un anno medio",
+ width: 800,
+ height: 300,
+ marginLeft: 60,
+ x: {label: null, domain: stagionale.map(d => d.mese)},
+ y: {grid: true, label: "morti al mese (media)"},
+ marks: [
+ Plot.barY(stagionale, {
+ x: "mese",
+ y: "morti_medi",
+ fill: d => (d.mese === mesePiuMorti.mese ? "#d95f0e" : "#4e79a7"),
+ tip: true
+ }),
+ Plot.text(stagionale, {x: "mese", y: "morti_medi", text: d => num(d.morti_medi), dy: -6, fontSize: 10}),
+ Plot.ruleY([0])
+ ]
+})
+```
+
+```js
+Plot.plot({
+ title: "Quanto è letale un incidente, mese per mese",
+ width: 800,
+ height: 300,
+ marginLeft: 60,
+ x: {label: null, domain: stagionale.map(d => d.mese)},
+ y: {grid: true, label: "morti ogni 100 incidenti"},
+ marks: [
+ Plot.lineY(stagionale, {x: "mese", y: "indice", stroke: "#9ecae1", strokeWidth: 2}),
+ Plot.dot(stagionale, {
+ x: "mese",
+ y: "indice",
+ fill: d => (d.mese === mesePiuLetale.mese ? "#d95f0e" : "#4e79a7"),
+ r: 4,
+ tip: true
+ }),
+ Plot.text(stagionale, {x: "mese", y: "indice", text: d => numFix(d.indice, 2), dy: -8, fontSize: 10})
+ ]
+})
+```
+
+Ad ${mesePiuLetale.mese.toLowerCase()} un incidente è il **${numFix(letalitaExtra, 0)}% più letale** che a ${meseMenoLetale.mese.toLowerCase()}: ${numFix(mesePiuLetale.indice, 2)} morti ogni 100 incidenti contro ${numFix(meseMenoLetale.indice, 2)}, la forbice più ampia dell'anno. Le spiegazioni più plausibili non stanno nei dati — strade più scorrevoli, spostamenti lunghi, velocità — ma il dato lascia una domanda civica aperta: **se il rischio si concentra in pochi mesi, perché le campagne di sicurezza sono distribuite su tutto l'anno?**
+
+---
+
+## Dettaglio mensile — ${String(annoSel)}
+
+Ogni riga è un mese dell'anno selezionato. L'indice di mortalità è calcolato sul rapporto del mese (morti diviso incidenti), non come media dei valori già pubblicati dalla fonte.
```js
const { header, format } = tableFormat({
mese: { label: "Mese", fmt: "string" },
incidenti: { label: "Incidenti", fmt: "num" },
+ incidenti_mortali: { label: "Incidenti mortali", fmt: "num" },
morti: { label: "Morti", fmt: "num" },
feriti: { label: "Feriti", fmt: "num" },
- indice_mortalita: { label: "Mort.%", fmt: "pct" },
- indice_gravita: { label: "Grav.%", fmt: "pct" },
+ indice_mortalita: { label: "Morti ogni 100 inc.", fmt: "num", decimals: 2 },
+ indice_gravita: { label: "Indice di gravità", fmt: "num", decimals: 2 },
});
```
```js
Inputs.table(filtered, {
- columns: ["mese", "incidenti", "morti", "feriti", "indice_mortalita", "indice_gravita"],
+ columns: ["mese", "incidenti", "incidenti_mortali", "morti", "feriti", "indice_mortalita", "indice_gravita"],
header,
format,
rows: 15,
@@ -183,11 +367,43 @@ Inputs.table(filtered, {
---
+## Serie annuale completa
+
+```js
+const { header: headerAnni, format: formatAnni } = tableFormat({
+ anno: { label: "Anno", fmt: "string" },
+ incidenti: { label: "Incidenti", fmt: "num" },
+ incidenti_mortali: { label: "Incidenti mortali", fmt: "num" },
+ morti: { label: "Morti", fmt: "num" },
+ feriti: { label: "Feriti", fmt: "num" },
+ indice: { label: "Morti ogni 100 inc.", fmt: "num", decimals: 2 },
+});
+```
+
+```js
+const tabellaAnni = serie.map(d => ({ ...d, anno: String(d.anno) }));
+```
+
+```js
+Inputs.table(tabellaAnni, {
+ columns: ["anno", "incidenti", "incidenti_mortali", "morti", "feriti", "indice"],
+ header: headerAnni,
+ format: formatAnni,
+ rows: 18,
+ width: "100%",
+ sort: "anno",
+ reverse: true
+})
+```
+
+---
+
## Limiti
-- **Copertura**: la serie copre il periodo 2001-2018. Dati successivi al 2018 non sono disponibili in questo dataset (la fonte MIT ha cambiato metodologia).
-- **Nazionale**: i dati sono aggregati a livello nazionale. Non è disponibile la disaggregazione regionale o provinciale.
-- **Metodologia**: la rilevazione degli incidenti stradali ha subito cambiamenti metodologici nel periodo considerato. I dati pre e post 2010 potrebbero non essere perfettamente confrontabili.
+- **Copertura**: la serie copre ${first}–${last} (${nAnni * 12} mesi rilevati). Il dato dopo il ${last} non è in questo dataset: la fonte MIT ha cambiato metodologia.
+- **Nazionale**: i dati sono aggregati a livello nazionale. Non è disponibile la disaggregazione regionale o provinciale, quindi non si possono confrontare i territori.
+- **Discontinuità nella rilevazione**: fra il ${SOGLIA - 1} e il ${SOGLIA} gli incidenti rilevati calano del ${numFix(caloIncidentiBreak, 0)}% in un solo anno e l'indice di mortalità sale, pur con meno vittime. Il confronto degli indici prima e dopo quella soglia va letto con cautela.
+- **Indici calcolati**: l'indice di mortalità e l'indice di lesività di questa pagina sono rapporti aggregati (morti o feriti diviso incidenti del periodo), non le medie mensili pre-calcolate dalla fonte.
---