diff --git a/src/data/silos-infrastrutture.json.py b/src/data/silos-infrastrutture.json.py
new file mode 100644
index 0000000..87be7e7
--- /dev/null
+++ b/src/data/silos-infrastrutture.json.py
@@ -0,0 +1,74 @@
+#!/usr/bin/env python3
+"""Data loader: SILOS — infrastrutture strategiche e prioritarie.
+
+Produce dati curati per la pagina Observable Framework. Il dataset SILOS è
+gerarchico: la pagina deve distinguere i livelli invece di sommarli insieme.
+"""
+import json
+import sys
+
+from lab_connectors.duckdb import safe_connect
+from lab_connectors.gcs.paths import https_url
+
+
+SLUG = "silos_infrastrutture"
+YEAR = 2024
+URL = https_url("clean", "clean_parquet", slug=SLUG, year=YEAR)
+
+
+def rows(con, query: str) -> list[dict]:
+ rel = con.sql(query)
+ cols = [c[0] for c in rel.description]
+ return [dict(zip(cols, row)) for row in rel.fetchall()]
+
+
+with safe_connect() as con:
+ table = f"read_parquet('{URL}')"
+
+ output = {
+ "meta": {
+ "slug": SLUG,
+ "year": YEAR,
+ "source_parquet": URL,
+ "note": "SILOS è gerarchico: i livelli vanno letti separatamente per evitare doppio conteggio.",
+ },
+ "per_livello": rows(
+ con,
+ f"""
+ SELECT
+ livello,
+ COUNT(*) AS righe,
+ COUNT(cup) AS righe_con_cup,
+ SUM(COALESCE(costi_mln_euro, 0)) AS costi_mln_euro,
+ SUM(COALESCE(disponibilita_mln_euro, 0)) AS disponibilita_mln_euro,
+ SUM(COALESCE(fabbisogno_mln_euro, 0)) AS fabbisogno_mln_euro
+ FROM {table}
+ GROUP BY livello
+ ORDER BY livello
+ """,
+ ),
+ "interventi": rows(
+ con,
+ f"""
+ SELECT
+ livello,
+ progressivo,
+ cup,
+ denominazione,
+ COALESCE(sistema_infrastrutturale, 'Non indicato') AS sistema_infrastrutturale,
+ COALESCE(soggetto_competente, 'Non indicato') AS soggetto_competente,
+ COALESCE(luogo_lavori, 'Non indicato') AS luogo_lavori,
+ COALESCE(stato_attuazione, 'Non indicato') AS stato_attuazione,
+ anno_ultimazione_previsto,
+ costi_mln_euro,
+ disponibilita_mln_euro,
+ fabbisogno_mln_euro,
+ link_scheda
+ FROM {table}
+ WHERE COALESCE(costi_mln_euro, 0) > 0
+ ORDER BY livello, costi_mln_euro DESC
+ """,
+ ),
+ }
+
+json.dump(output, sys.stdout, ensure_ascii=False)
diff --git a/src/data/themes.json.py b/src/data/themes.json.py
index 76e71da..8f8cf5e 100644
--- a/src/data/themes.json.py
+++ b/src/data/themes.json.py
@@ -7,7 +7,7 @@
"slug": "territorio-ambiente",
"name": "Territorio e ambiente",
"description": "Trasformazioni territoriali, ambiente, energia, rifiuti e sicurezza stradale",
- "datasets": ["rifiuti-urbani", "capacita-rinnovabile", "produzione-elettrica-fonti", "mit-incidentalita"],
+ "datasets": ["rifiuti-urbani", "capacita-rinnovabile", "produzione-elettrica-fonti", "mit-incidentalita", "silos-infrastrutture"],
},
{
"slug": "finanza-pubblica",
diff --git a/src/dataset/silos-infrastrutture.md b/src/dataset/silos-infrastrutture.md
new file mode 100644
index 0000000..b6ab916
--- /dev/null
+++ b/src/dataset/silos-infrastrutture.md
@@ -0,0 +1,313 @@
+---
+title: Infrastrutture strategiche SILOS
+description: Opere strategiche e prioritarie censite da SILOS — costi, disponibilità finanziaria, fabbisogno residuo, sistema infrastrutturale, livello gerarchico e stato di attuazione
+source: Camera dei Deputati — SILOS
+source_url: https://silos.infrastrutturestrategiche.it/
+period: "2004–2024"
+last_modified: 2026-07-03
+dataset_slug: silos_infrastrutture
+---
+
+# Infrastrutture strategiche SILOS
+
+Il dataset raccoglie le infrastrutture strategiche e prioritarie censite nel sistema SILOS: ferrovie, strade, sistemi urbani, porti, aeroporti, opere idriche e altri interventi. La struttura è gerarchica: livelli alti descrivono programmi, direttrici o macro-opere; livelli più bassi descrivono articolazioni operative e sotto-interventi.
+
+**Fonte**: [SILOS — Camera dei Deputati](https://silos.infrastrutturestrategiche.it/) · **Periodo**: 2004–2024 · Snapshot 2024
+
+```js
+import { num, euroCompact, pct } from "../import/format-utils.js";
+```
+
+```js
+const data = await FileAttachment("../data/silos-infrastrutture.json").json();
+```
+
+```js
+const interventi = data.interventi;
+const perLivello = data.per_livello;
+const livelli = perLivello.map(d => d.livello).sort((a, b) => a - b);
+const livelloSel = view(Inputs.select(
+ new Map(livelli.map(l => [`Livello ${l}`, l])),
+ {label: "Livello gerarchico", value: 2}
+));
+```
+
+```js
+const filtered = interventi.filter(d => d.livello === livelloSel);
+const mlnToEuro = d => (d || 0) * 1_000_000;
+const euroMln = d => euroCompact(mlnToEuro(d || 0));
+
+const totali = {
+ interventi: filtered.length,
+ cup: new Set(filtered.filter(d => d.cup).map(d => d.cup)).size,
+ costi_mln_euro: d3.sum(filtered, d => d.costi_mln_euro || 0),
+ disponibilita_mln_euro: d3.sum(filtered, d => d.disponibilita_mln_euro || 0),
+ fabbisogno_mln_euro: d3.sum(filtered, d => d.fabbisogno_mln_euro || 0),
+};
+
+const quotaCoperta = totali.costi_mln_euro ? (totali.disponibilita_mln_euro / totali.costi_mln_euro) * 100 : 0;
+
+function aggregateBy(rows, key, valueName = key) {
+ return Array.from(
+ d3.rollup(
+ rows,
+ v => ({
+ interventi: v.length,
+ costi_mln_euro: d3.sum(v, d => d.costi_mln_euro || 0),
+ disponibilita_mln_euro: d3.sum(v, d => d.disponibilita_mln_euro || 0),
+ fabbisogno_mln_euro: d3.sum(v, d => d.fabbisogno_mln_euro || 0),
+ }),
+ d => d[key] || "Non indicato"
+ ),
+ ([label, values]) => ({[valueName]: label, ...values})
+ ).sort((a, b) => b.costi_mln_euro - a.costi_mln_euro);
+}
+
+const perSistema = aggregateBy(filtered, "sistema_infrastrutturale").slice(0, 18);
+const perLuogo = aggregateBy(filtered, "luogo_lavori").slice(0, 18);
+const perStato = aggregateBy(filtered, "stato_attuazione").slice(0, 18);
+const perUltimazione = aggregateBy(
+ filtered.filter(d => d.anno_ultimazione_previsto),
+ "anno_ultimazione_previsto"
+).sort((a, b) => d3.ascending(a.anno_ultimazione_previsto, b.anno_ultimazione_previsto));
+const topInterventi = filtered.slice().sort((a, b) => b.costi_mln_euro - a.costi_mln_euro).slice(0, 50);
+```
+
+
+
+
Righe nel livello
+ ${num(totali.interventi)}
+ livello ${String(livelloSel)}
+
+
+
Costo
+ ${euroMln(totali.costi_mln_euro)}
+
+
+
Disponibilità
+ ${euroMln(totali.disponibilita_mln_euro)}
+ ${pct(quotaCoperta)} del costo
+
+
+
Fabbisogno
+ ${euroMln(totali.fabbisogno_mln_euro)}
+
+
+
+---
+
+## Livelli della gerarchia
+
+La tabella mostra quanto cambia il quadro passando da un livello all'altro. Non bisogna sommare i livelli tra loro: una stessa opera può comparire come direttrice, intervento e sotto-intervento.
+
+```js
+const levelHeader = {
+ livello: "Livello",
+ righe: "Righe",
+ righe_con_cup: "Con CUP",
+ costi_mln_euro: "Costo",
+ disponibilita_mln_euro: "Disponibilità",
+ fabbisogno_mln_euro: "Fabbisogno",
+};
+const levelFormat = {
+ livello: x => num(x),
+ righe: x => num(x),
+ righe_con_cup: x => num(x),
+ costi_mln_euro: x => euroMln(x),
+ disponibilita_mln_euro: x => euroMln(x),
+ fabbisogno_mln_euro: x => euroMln(x),
+};
+```
+
+```js
+Inputs.table(perLivello, {
+ columns: ["livello", "righe", "righe_con_cup", "costi_mln_euro", "disponibilita_mln_euro", "fabbisogno_mln_euro"],
+ header: levelHeader,
+ format: levelFormat,
+ rows: 10,
+ width: "100%"
+})
+```
+
+---
+
+## Sistemi infrastrutturali
+
+Il grafico legge solo il livello selezionato. A livelli alti emergono macro-direttrici e programmi; a livelli bassi aumenta il dettaglio operativo.
+
+```js
+Plot.plot({
+ title: `Costo e fabbisogno per sistema infrastrutturale — livello ${livelloSel}`,
+ width: 900,
+ height: Math.max(340, perSistema.length * 32 + 50),
+ marginLeft: 170,
+ x: {grid: true, label: null, tickFormat: d => euroCompact(mlnToEuro(d))},
+ y: {label: null, tickSize: 0},
+ color: {legend: true, domain: ["Costo", "Fabbisogno"], range: ["#4e79a7", "#e15759"]},
+ marks: [
+ Plot.barX(perSistema, {
+ y: "sistema_infrastrutturale",
+ x: "costi_mln_euro",
+ fill: "Costo",
+ sort: {y: "-x"},
+ tip: {format: {x: euroMln}}
+ }),
+ Plot.tickX(perSistema, {
+ y: "sistema_infrastrutturale",
+ x: "fabbisogno_mln_euro",
+ stroke: "Fabbisogno",
+ strokeWidth: 3,
+ tip: {format: {x: euroMln}}
+ }),
+ Plot.ruleX([0])
+ ]
+})
+```
+
+---
+
+## Localizzazione dichiarata
+
+La localizzazione SILOS non è sempre una singola regione: molte opere sono multi-regionali o non ripartibili a livello regionale. La vista territoriale va letta come localizzazione dichiarata, non come ripartizione puntuale dei costi.
+
+```js
+Plot.plot({
+ title: `Prime localizzazioni per costo — livello ${livelloSel}`,
+ width: 900,
+ height: 560,
+ marginLeft: 260,
+ x: {grid: true, label: null, tickFormat: d => euroCompact(mlnToEuro(d))},
+ y: {label: null, tickSize: 0},
+ color: {scheme: "Greens"},
+ marks: [
+ Plot.barX(perLuogo, {
+ y: "luogo_lavori",
+ x: "costi_mln_euro",
+ fill: "costi_mln_euro",
+ sort: {y: "-x"},
+ tip: {format: {x: euroMln}}
+ }),
+ Plot.ruleX([0])
+ ]
+})
+```
+
+---
+
+## Stato di attuazione
+
+Lo stato non è compilato in modo uniforme su tutti i livelli. Quando il livello selezionato contiene molte righe "Non indicato", il grafico segnala un limite informativo della fonte, non necessariamente assenza di avanzamento.
+
+```js
+Plot.plot({
+ title: `Costo per stato di attuazione — livello ${livelloSel}`,
+ width: 900,
+ height: Math.max(300, perStato.length * 30 + 50),
+ marginLeft: 240,
+ x: {grid: true, label: null, tickFormat: d => euroCompact(mlnToEuro(d))},
+ y: {label: null, tickSize: 0},
+ color: {scheme: "Purples"},
+ marks: [
+ Plot.barX(perStato, {
+ y: "stato_attuazione",
+ x: "costi_mln_euro",
+ fill: "costi_mln_euro",
+ sort: {y: "-x"},
+ tip: {format: {x: euroMln}}
+ }),
+ Plot.ruleX([0])
+ ]
+})
+```
+
+---
+
+## Ultimazione prevista
+
+Molti interventi non riportano un anno di ultimazione previsto. Dove la data è presente, il profilo temporale cambia molto al variare del livello gerarchico.
+
+```js
+Plot.plot({
+ title: `Costo per anno di ultimazione previsto — livello ${livelloSel}`,
+ width: 900,
+ height: 340,
+ x: {tickFormat: d => String(d), label: null},
+ y: {grid: true, label: null, tickFormat: d => euroCompact(mlnToEuro(d))},
+ marks: [
+ Plot.barY(perUltimazione, {
+ x: "anno_ultimazione_previsto",
+ y: "costi_mln_euro",
+ fill: "#f28e2b",
+ tip: {format: {y: euroMln}}
+ }),
+ Plot.ruleY([0])
+ ]
+})
+```
+
+---
+
+## Interventi nel livello selezionato
+
+```js
+const header = {
+ progressivo: "Prog.",
+ denominazione: "Intervento",
+ sistema_infrastrutturale: "Sistema",
+ luogo_lavori: "Localizzazione",
+ soggetto_competente: "Soggetto",
+ stato_attuazione: "Stato",
+ anno_ultimazione_previsto: "Ultimazione",
+ costi_mln_euro: "Costo",
+ disponibilita_mln_euro: "Disponibilità",
+ fabbisogno_mln_euro: "Fabbisogno",
+};
+const format = {
+ progressivo: x => num(x),
+ anno_ultimazione_previsto: x => x == null ? "—" : num(x),
+ costi_mln_euro: x => euroMln(x),
+ disponibilita_mln_euro: x => euroMln(x),
+ fabbisogno_mln_euro: x => euroMln(x),
+};
+```
+
+```js
+Inputs.table(topInterventi, {
+ columns: [
+ "progressivo",
+ "denominazione",
+ "sistema_infrastrutturale",
+ "luogo_lavori",
+ "soggetto_competente",
+ "stato_attuazione",
+ "anno_ultimazione_previsto",
+ "costi_mln_euro",
+ "disponibilita_mln_euro",
+ "fabbisogno_mln_euro"
+ ],
+ header,
+ format,
+ rows: 20,
+ width: "100%"
+})
+```
+
+---
+
+## Limiti
+
+- **Gerarchia**: i livelli SILOS non vanno sommati tra loro. La pagina li differenzia con un selettore, ma una lettura contabile rigorosa richiede di decidere prima quale livello rappresenta l'unità di analisi.
+- **Snapshot**: il parquet pubblicato è uno snapshot 2024 di un censimento che documenta opere con storia pluriennale.
+- **Localizzazione**: `luogo_lavori` può indicare aree vaste, combinazioni di regioni o voci non ripartibili. Non è una ripartizione territoriale contabile.
+- **Stati**: lo stato di attuazione non è compilato in modo uniforme su tutti i livelli della gerarchia.
+- **Importi**: costi, disponibilità e fabbisogno sono espressi dalla fonte in milioni di euro e non sono rivalutati.
+
+---
+
+## Risorse
+
+- [SILOS — Infrastrutture strategiche](https://silos.infrastrutturestrategiche.it/)
+- [Dump dati.camera.it](https://dati.camera.it/ocd/dump/silos/PISRapportoCSV2024.zip)
+- [Scarica il parquet pulito](https://storage.googleapis.com/dataciviclab-clean/silos_infrastrutture/2024/silos_infrastrutture_2024_clean.parquet)
+- [Pipeline](https://github.com/dataciviclab/dataset-incubator/tree/main/candidates/silos-infrastrutture)
+- [Esplora i dati con Query SQL](https://dataciviclab-dashboard.streamlit.app/Query_SQL)