From 706f21c023b2a3f645496c7e8d040f45c4fbaac6 Mon Sep 17 00:00:00 2001 From: matteocavo Date: Fri, 3 Jul 2026 13:46:14 +0200 Subject: [PATCH] feat: add SILOS infrastructure explorer page --- src/data/silos-infrastrutture.json.py | 74 ++++++ src/data/themes.json.py | 2 +- src/dataset/silos-infrastrutture.md | 313 ++++++++++++++++++++++++++ 3 files changed, 388 insertions(+), 1 deletion(-) create mode 100644 src/data/silos-infrastrutture.json.py create mode 100644 src/dataset/silos-infrastrutture.md 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)