Stato: bozza completa, da rifinire. Lavoro gated: si progetta e si costruisce ora, va live al run #1 della storicizzazione (anomalie risolte, vedi task #18/#20). Nessuna attivazione nella nightly finché non si chiude il gate.
La pagina Statistiche è il rendiconto pubblico, elaborato live dal software, dello stato e dell'evoluzione nel tempo della sovranità digitale della posta elettronica della PA italiana. Espone un insieme documentato di KPI, organizzati per categoria, ciascuno con valore attuale + andamento storico. Serve la Trasparenza: numeri sempre coerenti col dato corrente, mai compilati a mano, riproducibili dalla formula qui sotto.
- Una sola realtà. Niente distinzione reality/methodology: la metodologia è congelata al run #1, ogni variazione successiva è un cambiamento reale misurato.
- Live = calcolato dalla pipeline. Il valore attuale di ogni KPI è ricalcolato a ogni
build da
data.json; l'andamento dallo storico (history/runs.jsonl+timeseries/). - Riuso, non duplicazione. Le segmentazioni riusano
TIPOLOGIA_CLUSTERSeCATEGORY_LABELSdiscripts/report_it_by_category.py; la sovranità riusasovereignty_of()disrc/mail_sovereignty/historicize.py.
Insieme corrente E = enti IT in data.json (country == "IT"), N = |E|.
Per ogni ente e (campi da material_row, salvo nota):
| simbolo | campo | valori |
|---|---|---|
prov(e) |
provider |
microsoft, google, aruba, regional-public, independent, istruzione-miur-tenant, unknown, … |
sov(e) |
sovereignty_of(prov) |
6 bucket (sotto) |
jur(e) |
mx_jurisdiction |
domestic / foreign / mixed / unknown |
conf(e) |
classification_confidence |
[0,1] o ∅ |
cat(e) |
categoria IPA da bfs (IT-{cat}-{ipa}) |
L6, C1, L33, … |
hasMX(e) |
has_mx |
bool |
dkim(e) |
dkim_tenant≠∅ |
bool |
spf(e) |
spf (entità raw) |
bool |
region(e) |
regione IT (seed/data-regions.json) |
20 regioni |
Bucket di sovranità (sovereignty_of):
| bucket | provider tipici | gruppo |
|---|---|---|
USA (CLOUD Act) |
microsoft, google, istruzione-miur-tenant | ESTERO |
Altri provider esteri |
zoho, yandex, … | ESTERO |
Italia — Cloud sovrano |
regional-public (PSN/regionali) | ITALIA |
Italia — Provider commerciali |
aruba, register, … | ITALIA |
Italia — Infrastruttura autonoma |
independent (self-hosted) | ITALIA |
Sconosciuto |
unknown | N/D |
- E_class =
{e : sov(e) ≠ "Sconosciuto"}, N_class = |E_class| (enti classificati). - ITA = i 3 bucket Italia —. EST = i 2 bucket esteri.
Legenda implementazione: [✓] già calcolato dalla macchina di storicizzazione ·
[+] nuovo (da aggiungere a build_manifest/build_timeseries o al compute corrente).
| # | KPI | Formula | Fonte / stato |
|---|---|---|---|
| 1.1 | Indice di Sovranità Digitale (ISD) | `100 · | {e: sov(e)∈ITA} |
| 1.2 | Quota CLOUD Act (USA) | `100 · | {sov=USA (CLOUD Act)} |
| 1.3 | Quota cloud sovrano IT | `100 · | {sov=Italia — Cloud sovrano} |
| 1.4 | Quota provider commerciali IT | `100 · | {sov=Italia — Provider commerciali} |
| 1.5 | Quota infrastruttura autonoma IT | `100 · | {sov=Italia — Infrastruttura autonoma} |
| 1.6 | Quota altri provider esteri | `100 · | {sov=Altri provider esteri} |
1.2–1.6 sommano a 100% di N_class. L'ISD (1.1) = 1.3+1.4+1.5.
| # | KPI | Formula | Fonte / stato |
|---|---|---|---|
| 2.1 | MX domestico / estero / misto / unknown | `100 · | {jur(e)=x} |
Dimensione complementare alla sovranità: dove atterra fisicamente la posta, indipendentemente da chi è il provider legale. Uno scarto ISD↔MX-domestic è esso stesso un segnale (es. provider IT su infrastruttura estera).
| # | KPI | Formula | Fonte / stato |
|---|---|---|---|
| 3.1 | Top-N provider per quota | `share(p) = | {prov(e)=p} |
| 3.2 | Concentrazione top-3 | Σ delle 3 share maggiori |
[+] |
| 3.3 | HHI (Herfindahl) | Σ_p share(p)² · 10000 |
[+] opzionale |
| 3.4 | Provider IT vs esteri | share per nazionalità provider | [+] (≈ ITA vs EST di Cat.1; tenere solo se aggiunge valore) |
| # | KPI | Formula | Fonte / stato |
|---|---|---|---|
| 4.1 | Coverage | 100 · N_class / N |
[✓] (serie coverage) |
| 4.2 | Confidenza media | mean(conf(e)) su conf≠∅ |
[✓] (mean_confidence) |
| 4.3 | Distribuzione bande confidenza | % alta (≥0.8) / media (0.5–0.8) / bassa (<0.5) | [+] |
| 4.4 | Ricchezza segnali | % hasMX, % dkim, % spf |
[+] |
Per ogni cluster di TIPOLOGIA_CLUSTERS (Territoriali, Istruzione, Sanità, Stato
centrale, Ordini/Camere, …): ISD del cluster + breakdown a 6 bucket.
| # | KPI | Formula | Fonte / stato |
|---|---|---|---|
| 5.1 | ISD per cluster | `100 · | {e∈cluster: sov∈ITA} |
| 5.2 | Breakdown sovranità per cluster | quote dei 6 bucket dentro il cluster | [+] |
KPI-faro atteso: Istruzione → quota CLOUD Act alta (scuole sul tenant MIM
istruzione-miur-tenant= Microsoft 365).
| # | KPI | Formula | Fonte / stato |
|---|---|---|---|
| 6.1 | ISD per regione | `100 · | {e∈reg: sov∈ITA} |
| 6.2 | Breakdown sovranità per regione | quote 6 bucket per regione (heat/ranking) | [+] |
| # | KPI | Formula | Fonte / stato |
|---|---|---|---|
| 7.1 | Cambiamenti/giorno per tipo | conteggio eventi {resolved, regressed, provider_change, sovereignty_change, jurisdiction_change} per run |
changelog · [+] (eventi esistono, da serializzare in serie) |
| 7.2 | Migrazioni di sovranità (saldo netto) | ` | {sov_change: EST→ITA} |
| 7.3 | Unknown nel tempo | N − N_class per run |
[+] (deriva da coverage) |
| 7.4 | Stabilità | `100 · | enti senza eventi nel run |
| # | KPI | Formula | Fonte / stato |
|---|---|---|---|
| 8.1 | Anomalie aperte nel tempo, per tipo | conteggio da data/reports/anomalies.json per run |
[+] (da storicizzare nel manifest) |
La pagina apre con 4 carte grandi (valore attuale + sparkline):
- ISD (1.1) — il numero-simbolo
- Quota CLOUD Act (1.2) — l'esposizione USA
- Coverage (4.1) — onestà sul dato
- Enti monitorati (N) — la scala
→ Decisione tua: confermi questi 4 o ne vuoi altri? (§8)
history/timeseries/sovereignty.json [✓] serie quote 6 bucket
history/timeseries/jurisdiction.json [✓] serie MX domestic/foreign/mixed/unknown
history/timeseries/coverage.json [✓] serie coverage + mean_confidence
history/timeseries/provider_national.json [✓] serie top provider
history/timeseries/confidence_bands.json [+] serie bande (4.3)
history/timeseries/market_concentration.json [+] serie top-3 / HHI (3.2/3.3)
history/timeseries/changes.json [+] eventi per tipo per giorno (7.1)
history/timeseries/migrations.json [+] saldo netto sovranità (7.2)
history/timeseries/anomalies.json [+] anomalie per tipo per giorno (8.1)
data/summary/stats_current.json [+] TUTTI i KPI correnti (valore "oggi")
data/summary/stats_by_category.json [+] Cat.5 (ISD + breakdown per cluster)
data/summary/stats_by_region.json [+] Cat.6 (ISD + breakdown per regione)
I *_current / by_* si calcolano a ogni build da data.json (non richiedono storico →
si possono produrre anche prima del run #1, se si vuole anticipare la sola fotografia
attuale senza le serie temporali). Le timeseries/* restano gated col run #1.
- Stile coerente con storia.html: tema chiaro, grafici 100%-stacked dove sono quote, Chart.js, ordinamento numerico corretto.
- Layout: 4 carte di testata → sezione per categoria (Cat.1–8) con il grafico adatto (stacked area per le quote, line per i trend, bar/heat per le segmentazioni) → tabella segmentazioni (cluster ente, regione) con ISD per riga.
- Deciso (✓): pagina nuova
statistiche.html— cruscotto KPI;storia.htmlresta il diario dei cambiamenti.
Chiuse (✓):
- Denominatore ISD → sui classificati (
N_class): non diluisce con gli unknown. - Base ISD → bucket di sovranità (controllo legale).
mx_jurisdictionmostrato a parte come indicatore tecnico complementare (lo scarto ISD↔MX-domestic è un segnale). - 4 KPI di testata → ISD · Quota CLOUD Act · Coverage · Enti monitorati.
- Mercato → teniamo top-3 (3.2) e HHI (3.3); droppata provider-IT-vs-esteri (3.4), ridondante con la Cat.1.
- Fotografia anticipata → SÌ:
stats_current+stats_by_categorysono non-gated e già live (scripts/build_stats.py). Solo letimeseries/*restano gated al run #1. - Pagina nuova
statistiche.html(cruscotto);storia.htmlresta il diario.
Stato implementazione (questo commit):
scripts/build_stats.py→data/summary/stats_current.json+stats_by_category.json(riusasovereignty_of/material_row). Cablato in nightly (non-gated) + CI smoke.statistiche.html→ testata + composizione sovranità + giurisdizione MX + sovranità per tipo di ente (15 cluster, barre 100%-stacked) + mercato + qualità + trend (gated).- Linkata da
index.html(Trasparenza dataset).
Correzione importante (segmentazione): i codici categoria del bfs IT sono quelli
propri del seed (COM=Comuni, PRO=Province, CMM=Città metrop., REG=Regioni,
CONS=Consorzi…), NON i codici IndicePA L6/L5/... del report. Il mapping in
build_stats.py (CLUSTERS) è stato verificato sui 54 codici reali → copertura totale,
nessun "other".
Chiuse — aggiornamento:
5. Cat.6 — Sovranità per regione → ✅ FATTA. La fonte è la chiave-sede pulita
ipa_codice_comune_istat (100%) risolta sul crosswalk ufficiale ISTAT da
scripts/enrich_geo.py (logica in
geo.py), che scrive regione/provincia/comune/macroarea
su ogni ente — non dal campo region sporco del seed. stats.compute_by_region
(unit-testata + assert_integrity esteso a regioni/macroaree) produce
data/summary/stats_by_region.json e alimenta la sezione "Analisi per aree" del report.
scripts/build_kpi.py (+ logica src/mail_sovereignty/kpi.py)
produce kpi.json alla root del repo (servito alla root del deploy GitHub Pages,
come data-summary.json), file statico pubblico (CC BY-SA 4.0) consumato dal sito Hugo
dell'Osservatorio Nazionale Sovranità Digitale
per sostituire i placeholder —%.
- URL pubblico:
https://mxmap.it/kpi.json - Schema:
generated_at,run_id(dahistory/runs.jsonl,nullfinché lo storico è gated),totals{n_entities,n_with_mx,coverage_pct},indices{isd,cloud_act_pct,n_classified}(numeri di testata: ISD e CLOUD Act calcolati sui classificati — definizione canonica, identici astatistiche.html/report.html),sovereignty{extra_eu,eu_non_it,it,unknown}(count/pct/label, pct sul totale → somma 100; la fettait≠ l'ISD),top_providers[≤10](aggregati per nome-display, con bucket a 4 valori),by_cluster(15 cluster citizen: n,usa_pct,dominant_provider),confidence{mean,high_pct}. - ISD vs composizione (importante): la testata usa
indices.isd(sovranità IT sui classificati, 52–53%); la torta usasovereignty.it.pct(sul totale, ~51%, perché include il bucketunknown). Sono due denominatori diversi: non confonderli mostrandoli come lo stesso numero.assert_kpi_integrityverifica entrambi. - Mappatura 6→4 bucket (a livello provider,
kpi.provider_to_sov4):extra_eu= USA (CLOUD Act) + esteri non europei (zoho/yandex) ·eu_non_it=EU_NON_IT_PROVIDERS(OVH, Hetzner, IONOS, Scaleway, Gandi, Infomaniak; CH/UK contati come europei — mxmap.it#21) ·it= i 3 bucket Italia ·unknown. usa_pctper cluster = quota del bucket USA (CLOUD Act) (include il tenant MIM delle scuole), più ampio del set{microsoft,google,aws}direport_it_by_cluster.py.- Integrità:
assert_kpi_integrity()(somma bucket = enti, quote ~100, range, cluster = totale) girata a ogni build (exit 1 se viola) + 11 unit test intests/test_kpi.py. - Pipeline: eseguito nella nightly dopo
build_stats.pye nel job CIsmoke.kpi.json(root) è già nel git-add notturno e nell'artifact Pages → servito alla root del deploy.