-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstoria.html
More file actions
185 lines (169 loc) · 10.3 KB
/
Copy pathstoria.html
File metadata and controls
185 lines (169 loc) · 10.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="color-scheme" content="light">
<title>Storia — Osservatorio Nazionale Sovranità Digitale · Sovranità digitale della posta della PA italiana</title>
<meta name="description" content="Andamento nel tempo della sovranità digitale della posta elettronica della PA italiana: serie storica e changelog. MxMap Italia.">
<meta name="keywords" content="MxMap Italia, MXMap Italia, mxmap.it, sovranità digitale, posta elettronica PA, CLOUD Act, IndicePA, provider email pubblica amministrazione">
<meta name="application-name" content="Osservatorio Nazionale Sovranità Digitale">
<meta name="robots" content="index, follow, max-image-preview:large">
<link rel="canonical" href="https://mxmap.it/storia.html">
<link rel="icon" type="image/svg+xml" href="brand/favicon.svg">
<link rel="apple-touch-icon" href="brand/apple-touch-icon.png">
<meta name="theme-color" content="#0066CC">
<meta property="og:type" content="website">
<meta property="og:site_name" content="Osservatorio Nazionale Sovranità Digitale">
<meta property="og:title" content="MxMap.it — Andamento nel tempo della sovranità della posta PA">
<meta property="og:image" content="https://mxmap.it/brand/og-image.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:type" content="image/png">
<meta name="twitter:image" content="https://mxmap.it/brand/og-image.png">
<meta name="twitter:card" content="summary_large_image">
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
<style>
:root{color-scheme:light}
html,body{background:#fff}
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;max-width:1000px;margin:0 auto;padding:32px 24px;line-height:1.6;color:#222}
h1{font-size:26px;border-bottom:2px solid #009246;padding-bottom:8px}
h2{font-size:20px;margin-top:36px;color:#005a2a;border-bottom:1px solid #ddd;padding-bottom:4px}
.intro{background:#fafafa;border:1px solid #ddd;border-radius:6px;padding:16px 20px;margin:18px 0}
.cards{display:flex;flex-wrap:wrap;gap:12px;margin:18px 0}
.card{flex:1;min-width:160px;background:#fafafa;border:1px solid #ddd;border-left:4px solid #009246;border-radius:6px;padding:12px 16px}
.card .big{font-size:26px;font-weight:700;color:#005a2a;font-variant-numeric:tabular-nums}
.card .lbl{font-size:12px;color:#666}
.card .delta{font-size:12px;font-weight:600}
.delta.up{color:#cf222e}.delta.down{color:#1a7f37}
.chartbox{position:relative;height:340px;margin:14px 0 28px}
.note{font-size:12px;color:#777;margin-top:-18px;margin-bottom:18px}
table.feed{border-collapse:collapse;width:100%;font-size:13px}
table.feed th,table.feed td{padding:5px 10px;border-bottom:1px solid #eee;text-align:left;vertical-align:top}
table.feed th{background:#f4f4f4}
.muted{color:#888;font-size:12px}
code{background:#f4f4f4;padding:1px 6px;border-radius:3px;font-size:12px}
</style>
</head>
<body>
<p style="font-size:13px"><a href="index.html">← Torna alla mappa</a> · <a href="methodology.html">Metodologia</a> · <a href="anomalie.html">Anomalie</a></p>
<h1>Storia</h1>
<p style="font-size:13px;color:#666;margin-top:-8px">Osservatorio sulla Sovranità Digitale della Posta Elettronica della Pubblica Amministrazione Italiana</p>
<div class="intro">
<p>Come cambia nel tempo la sovranità della posta elettronica della PA italiana.
La metodologia di rilevamento è stabile dal primo scan ufficiale, quindi ciò che
si muove nei grafici è il dato reale. Dati: <code>history/</code> (snapshot +
changelog opendata, aggiornati a ogni run).</p>
</div>
<div class="cards" id="cards"></div>
<h2>Sovranità nel tempo</h2>
<p class="note">Numero di enti per bucket di sovranità (narrazione CLOUD Act), per run.</p>
<div class="chartbox"><canvas id="sovChart"></canvas></div>
<h2>Giurisdizione dell'MX nel tempo</h2>
<p class="note">Dove risiede fisicamente il server di posta in entrata (modello ESORICS): 🇮🇹 domestico vs 🌍 estero.</p>
<div class="chartbox"><canvas id="jurChart"></canvas></div>
<h2>Copertura del rilevamento</h2>
<p class="note">% enti classificati (non <code>unknown</code>) e confidenza media — il <em>nostro</em> lavoro, distinto dalla realtà.</p>
<div class="chartbox"><canvas id="covChart"></canvas></div>
<h2>Cosa è cambiato nell'ultimo run</h2>
<table class="feed"><thead><tr><th>Ente</th><th>Cambiamento</th><th>Da → A</th></tr></thead><tbody id="feed"></tbody></table>
<p class="muted" id="gen" style="margin-top:20px"></p>
<script>
const SOV_COLORS = {
"USA (CLOUD Act)": "#D42E2E", "Italia — Cloud sovrano": "#009246",
"Italia — Provider commerciali": "#4a9e5c", "Italia — Infrastruttura autonoma": "#6b8e23",
"Altri provider esteri": "#FF8C42", "Sconosciuto": "#9aa0a6",
};
const JUR_COLORS = { domestic: "#009246", foreign: "#D42E2E", mixed: "#E8A33D", unknown: "#9aa0a6" };
const JUR_LABEL = { domestic: "🇮🇹 Domestico", foreign: "🌍 Estero", mixed: "Misto", unknown: "Sconosciuto" };
let allEvents = [];
async function getJSON(u){ const r = await fetch(u); return r.ok ? r.json() : null; }
async function getJSONL(u){ const r = await fetch(u); if(!r.ok) return []; return (await r.text()).trim().split("\n").filter(Boolean).map(JSON.parse); }
function esc(s){ const e=document.createElement("div"); e.textContent=s==null?"":s; return e.innerHTML; }
if (window.Chart) { Chart.defaults.color = "#444"; Chart.defaults.borderColor = "#eee"; Chart.defaults.font.family = "-apple-system,Segoe UI,Roboto,sans-serif"; }
function rgba(hex, a) {
const m = (hex || "#cccccc").replace("#", "");
const h = m.length === 3 ? m.split("").map(c => c + c).join("") : m;
const n = parseInt(h, 16);
return `rgba(${(n >> 16) & 255},${(n >> 8) & 255},${n & 255},${a})`;
}
function stackedArea(canvasId, series, keys, colors, labels) {
const dates = series.map(s => s.date);
const total = series.map(s => keys.reduce((a, k) => a + (s[k] || 0), 0));
// 100%-stacked: l'altezza di ogni banda È la sua quota → coerente coi numeri.
const datasets = keys.map(k => ({
label: (labels && labels[k]) || k,
data: series.map((s, i) => total[i] ? +(100 * (s[k] || 0) / total[i]).toFixed(2) : 0),
abs: series.map(s => s[k] || 0),
backgroundColor: rgba(colors[k], 0.65), borderColor: colors[k] || "#ccc",
fill: true, tension: 0.25, pointRadius: 0, borderWidth: 1.5,
}));
new Chart(document.getElementById(canvasId), {
type: "line",
data: { labels: dates, datasets },
options: { responsive: true, maintainAspectRatio: false, interaction: { mode: "index", intersect: false },
scales: { x: { grid: { display: false } },
y: { stacked: true, beginAtZero: true, max: 100, ticks: { callback: v => v + "%" } } },
plugins: { legend: { position: "bottom" },
tooltip: { callbacks: { label: ctx => `${ctx.dataset.label}: ${ctx.parsed.y}% (${ctx.dataset.abs[ctx.dataIndex].toLocaleString("it")})` } } } },
});
}
(async function () {
const [runs, sov, jur, cov] = await Promise.all([
getJSONL("history/runs.jsonl"),
getJSON("history/timeseries/sovereignty.json"),
getJSON("history/timeseries/jurisdiction.json"),
getJSON("history/timeseries/coverage.json"),
]);
if (!runs.length || !sov) { document.getElementById("cards").innerHTML = "<p>Storico non ancora disponibile (parte dal primo scan ufficiale).</p>"; return; }
const first = runs[0], last = runs[runs.length - 1];
const usaNow = last.sovereignty["USA (CLOUD Act)"] || 0;
const usaFirst = first.sovereignty["USA (CLOUD Act)"] || 0;
const pct = (n, tot) => tot ? (100 * n / tot).toFixed(1) : "0";
const dUsa = usaNow - usaFirst;
document.getElementById("cards").innerHTML =
card(pct(usaNow, last.n_entities) + "%", "PA sotto CLOUD Act (USA)", dUsa, "enti") +
card(last.coverage_pct + "%", "copertura (classificati)", (last.coverage_pct - first.coverage_pct).toFixed(2), "pt") +
card(last.mean_confidence.toFixed(3), "confidenza media", null, "") +
card(runs.length, "run nello storico", null, "");
// grafici
const sovKeys = Object.keys(SOV_COLORS).filter(k => sov.some(s => s[k]));
stackedArea("sovChart", sov, sovKeys, SOV_COLORS);
if (jur) stackedArea("jurChart", jur, ["domestic", "mixed", "foreign", "unknown"], JUR_COLORS, JUR_LABEL);
if (cov) {
new Chart(document.getElementById("covChart"), {
type: "line",
data: { labels: cov.map(c => c.date), datasets: [
{ label: "Copertura %", data: cov.map(c => c.coverage_pct), borderColor: "#1a7f37", backgroundColor: "#1a7f3722", yAxisID: "y", fill: true, tension: 0.25 },
{ label: "Confidenza media", data: cov.map(c => c.mean_confidence), borderColor: "#1f6feb", yAxisID: "y2", tension: 0.25 },
] },
options: { responsive: true, maintainAspectRatio: false,
scales: { y: { position: "left", suggestedMin: 90, suggestedMax: 100, title: { display: true, text: "copertura %" } },
y2: { position: "right", suggestedMin: 0, suggestedMax: 1, grid: { drawOnChartArea: false }, title: { display: true, text: "confidenza" } } },
plugins: { legend: { position: "bottom" } } },
});
}
// feed changelog dell'ultimo run
const month = last.run_id.slice(0, 7);
const events = await getJSONL("history/changelog/" + month + ".jsonl");
allEvents = events.filter(e => e.run_id === last.run_id && e.change !== "new").reverse();
renderFeed();
document.getElementById("gen").textContent = "Ultimo run: " + last.run_id + " · " + last.n_entities + " enti · " + last.n_changed + " cambiamenti. Sorgente: history/ (opendata).";
})();
function card(big, lbl, delta, unit) {
let d = "";
if (delta !== null && delta !== undefined && !isNaN(delta)) {
const up = Number(delta) > 0;
d = `<div class="delta ${up ? "up" : "down"}">${up ? "▲" : "▼"} ${Math.abs(delta)} ${esc(unit)}</div>`;
}
return `<div class="card"><div class="big">${esc(big)}</div><div class="lbl">${esc(lbl)}</div>${d}</div>`;
}
function renderFeed() {
document.getElementById("feed").innerHTML = allEvents.slice(0, 300).map(e =>
`<tr><td>${esc(e.name)}</td><td>${esc(e.change)}</td>` +
`<td><code>${esc(e.from)}</code> → <code>${esc(e.to)}</code></td></tr>`
).join("") || `<tr><td colspan="3" class="muted">Nessun cambiamento.</td></tr>`;
}
</script>
</body>
</html>