Skip to content

Instantly share code, notes, and snippets.

@aborruso
Last active January 31, 2026 14:07
Show Gist options
  • Select an option

  • Save aborruso/46ef934581a2d2925e1104b575ced2da to your computer and use it in GitHub Desktop.

Select an option

Save aborruso/46ef934581a2d2925e1104b575ced2da to your computer and use it in GitHub Desktop.
Sinistri Stradali Palermo 2023 - Analisi Open Data
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sinistri Stradali Palermo 2023 - Analisi Open Data</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
@keyframes slideIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.animate-slide-in {
animation: slideIn 0.5s ease-out;
}
</style>
</head>
<body class="bg-gradient-to-br from-slate-50 to-slate-100 min-h-screen">
<!-- Header -->
<header class="bg-white border-b sticky top-0 z-10 shadow-sm">
<div class="max-w-6xl mx-auto px-4 py-6">
<div class="flex items-center gap-3">
<svg class="w-8 h-8 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
</svg>
<div>
<h1 class="text-3xl font-bold text-slate-900">Sinistri Stradali Palermo 2023</h1>
<p class="text-slate-600 mt-1">Analisi dei dati ufficiali della Polizia Municipale</p>
</div>
</div>
</div>
</header>
<main class="max-w-6xl mx-auto px-4 py-8 space-y-8">
<!-- KPI Cards -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 animate-slide-in">
<div class="bg-white rounded-lg shadow-md p-6 border-l-4 border-l-blue-500">
<div class="flex items-center gap-2 text-2xl font-bold text-slate-900">
<svg class="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
</svg>
3,124
</div>
<p class="text-slate-600 text-sm mt-2">Totale sinistri rilevati</p>
</div>
<div class="bg-white rounded-lg shadow-md p-6 border-l-4 border-l-orange-500">
<div class="flex items-center gap-2 text-2xl font-bold text-slate-900">
<svg class="w-6 h-6 text-orange-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"/>
</svg>
2,582
</div>
<p class="text-slate-600 text-sm mt-2">Feriti totali</p>
</div>
<div class="bg-white rounded-lg shadow-md p-6 border-l-4 border-l-red-600">
<div class="flex items-center gap-2 text-2xl font-bold text-slate-900">
<svg class="w-6 h-6 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
24
</div>
<p class="text-slate-600 text-sm mt-2">Sinistri mortali</p>
</div>
</div>
<!-- Severity Distribution -->
<div class="bg-white rounded-lg shadow-md p-6 animate-slide-in" style="animation-delay: 0.1s">
<h2 class="text-xl font-bold text-slate-900 mb-2">Distribuzione per Gravità</h2>
<p class="text-sm text-slate-600 mb-4">Classificazione dei 3,124 sinistri per tipologia di danno</p>
<div class="space-y-4" id="severityChart"></div>
</div>
<!-- Temporal Analysis -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- Monthly Distribution -->
<div class="bg-white rounded-lg shadow-md p-6 animate-slide-in" style="animation-delay: 0.2s">
<h2 class="text-xl font-bold text-slate-900 mb-2">Distribuzione Mensile</h2>
<p class="text-sm text-slate-600 mb-4">Picco in luglio (288 sinistri)</p>
<div class="space-y-3" id="monthlyChart"></div>
</div>
<!-- Hourly Distribution -->
<div class="bg-white rounded-lg shadow-md p-6 animate-slide-in" style="animation-delay: 0.3s">
<h2 class="text-xl font-bold text-slate-900 mb-2">Distribuzione per Fascia Oraria</h2>
<p class="text-sm text-slate-600 mb-4">Picco nelle ore pomeridiane (13-18)</p>
<div class="space-y-4" id="hourlyChart"></div>
</div>
</div>
<!-- Top Locations -->
<div class="bg-white rounded-lg shadow-md p-6 animate-slide-in" style="animation-delay: 0.4s">
<h2 class="text-xl font-bold text-slate-900 mb-2">Luoghi con Maggior Concentrazione</h2>
<p class="text-sm text-slate-600 mb-4">Località con almeno 5 sinistri rilevati</p>
<div class="space-y-4" id="topLocations"></div>
</div>
<!-- Data Quality Analysis -->
<div class="bg-blue-50 border-2 border-blue-200 rounded-lg shadow-md p-6 animate-slide-in" style="animation-delay: 0.5s">
<h2 class="text-xl font-bold text-blue-900 mb-2 flex items-center gap-2">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
</svg>
Analisi Qualità dei Dati Sorgente
</h2>
<p class="text-sm text-slate-600 mb-6">Valutazione tecnica secondo standard AgID/DCAT-AP_IT</p>
<!-- Score Overview -->
<div class="bg-white p-4 rounded-lg border-2 border-blue-300 mb-6">
<div class="flex items-center justify-between mb-3">
<h3 class="font-bold text-lg text-slate-900">Score Qualità Complessivo</h3>
<div class="text-right">
<div class="text-3xl font-bold text-blue-700">64%</div>
<div class="text-xs text-slate-600">32/50 punti</div>
</div>
</div>
<div class="h-3 bg-slate-200 rounded-full overflow-hidden">
<div class="h-full bg-gradient-to-r from-yellow-500 to-orange-500" style="width: 64%"></div>
</div>
<p class="text-sm text-slate-700 mt-3 font-semibold">⚠️ UTILIZZABILE CON PRE-PROCESSING</p>
<p class="text-xs text-slate-600 mt-1">Il dataset contiene dati validi e completi, ma richiede correzioni per essere "ready to use" secondo standard internazionali.</p>
</div>
<!-- Issues Grid -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
<!-- Critical Issues -->
<div class="bg-red-50 border-l-4 border-red-600 p-3 rounded">
<p class="text-sm font-semibold text-red-900">🔴 Coordinate con virgola decimale</p>
<p class="text-xs text-slate-600 mt-1">Incompatibili con GIS standard internazionali</p>
</div>
<div class="bg-red-50 border-l-4 border-red-600 p-3 rounded">
<p class="text-sm font-semibold text-red-900">🔴 Descrizione metadata errata</p>
<p class="text-xs text-slate-600 mt-1">Dice "anno 2022" invece di 2023</p>
</div>
<!-- Major Issues -->
<div class="bg-orange-50 border-l-4 border-orange-600 p-3 rounded">
<p class="text-sm font-semibold text-orange-900">🟠 Separatore non standard</p>
<p class="text-xs text-slate-600 mt-1">Usa ; invece di ,</p>
</div>
<div class="bg-orange-50 border-l-4 border-orange-600 p-3 rounded">
<p class="text-sm font-semibold text-orange-900">🟠 Date non ISO 8601</p>
<p class="text-xs text-slate-600 mt-1">Formato DD/MM/YYYY invece di YYYY-MM-DD</p>
</div>
</div>
<!-- Positive Aspects -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 mb-6">
<div class="bg-green-50 p-3 rounded border border-green-200">
<p class="text-sm font-semibold text-green-900">✓ Completezza: 99.78%</p>
</div>
<div class="bg-green-50 p-3 rounded border border-green-200">
<p class="text-sm font-semibold text-green-900">✓ Struttura Tidy corretta</p>
</div>
<div class="bg-green-50 p-3 rounded border border-green-200">
<p class="text-sm font-semibold text-green-900">✓ Licenza Open (CC BY 4.0)</p>
</div>
<div class="bg-green-50 p-3 rounded border border-green-200">
<p class="text-sm font-semibold text-green-900">✓ Copertura anno completo</p>
</div>
</div>
<!-- Quality Breakdown -->
<div class="bg-slate-100 p-4 rounded-lg">
<h3 class="font-semibold text-slate-900 mb-3">Dettaglio Score per Dimensione</h3>
<div class="space-y-2 text-sm" id="qualityBreakdown"></div>
</div>
</div>
<!-- Methodology -->
<div class="bg-slate-50 border border-slate-300 rounded-lg shadow-md p-6 animate-slide-in" style="animation-delay: 0.6s">
<h2 class="text-xl font-bold text-slate-900 mb-4">Fonte Dati e Metodologia</h2>
<div class="space-y-4">
<div>
<h3 class="font-semibold text-slate-900 mb-2">Fonte</h3>
<p class="text-sm text-slate-700">
Dataset ufficiale <strong>"Sinistri Stradali anno 2023"</strong> pubblicato dal <strong>Comune di Palermo</strong>
su <a href="https://www.dati.gov.it/view-dataset/dataset?id=sinistri-stradali-anno-2023" class="text-blue-600 hover:underline" target="_blank" rel="noopener">dati.gov.it</a>
</p>
<p class="text-xs text-slate-600 mt-1">Dati rilevati dalla Polizia Municipale di Palermo • Formato: CSV georeferenziato (WGS84)</p>
</div>
<hr class="border-slate-300">
<div>
<h3 class="font-semibold text-slate-900 mb-2">Strumenti di Analisi</h3>
<ul class="text-sm text-slate-700 space-y-1 list-disc list-inside">
<li><strong>ckanapi</strong>: Accesso alle API CKAN per download dataset</li>
<li><strong>qsv MCP Server</strong>: Esplorazione iniziale e statistiche descrittive</li>
<li><strong>DuckDB</strong>: Query SQL per aggregazioni e analisi temporali</li>
</ul>
</div>
<hr class="border-slate-300">
<div>
<h3 class="font-semibold text-slate-900 mb-2">Classificazione Gravità</h3>
<div class="grid grid-cols-2 md:grid-cols-4 gap-3 text-xs">
<div class="bg-white p-3 rounded border text-center">
<span class="inline-block px-2 py-1 bg-yellow-500 text-white font-bold rounded mb-1">C</span>
<p class="text-slate-700">Danni solo a cose</p>
</div>
<div class="bg-white p-3 rounded border text-center">
<span class="inline-block px-2 py-1 bg-orange-500 text-white font-bold rounded mb-1">F</span>
<p class="text-slate-700">Con feriti</p>
</div>
<div class="bg-white p-3 rounded border text-center">
<span class="inline-block px-2 py-1 bg-red-400 text-white font-bold rounded mb-1">R</span>
<p class="text-slate-700">Riserva sulla vita</p>
</div>
<div class="bg-white p-3 rounded border text-center">
<span class="inline-block px-2 py-1 bg-black text-white font-bold rounded mb-1">M</span>
<p class="text-slate-700">Mortali</p>
</div>
</div>
</div>
</div>
</div>
</main>
<!-- Footer -->
<footer class="bg-white border-t mt-12 py-6">
<div class="max-w-6xl mx-auto px-4 text-center text-sm text-slate-600">
<p>Analisi open data • <a href="https://www.dati.gov.it/view-dataset/dataset?id=sinistri-stradali-anno-2023" class="text-blue-600 hover:underline" target="_blank" rel="noopener">Dataset "Sinistri Stradali anno 2023"</a> pubblicato dal Comune di Palermo</p>
<p class="mt-1 text-xs text-slate-500">Elaborazione dati: gennaio 2026 • <a href="https://github.com/aborruso" class="text-blue-600 hover:underline">Source</a></p>
</div>
</footer>
<script>
// Data
const severityData = [
{ type: 'F', label: 'Con feriti', count: 1861, percent: 59.57, color: 'bg-orange-500' },
{ type: 'C', label: 'Solo danni', count: 1189, percent: 38.06, color: 'bg-yellow-500' },
{ type: 'R', label: 'Riserva vita', count: 50, percent: 1.60, color: 'bg-red-400' },
{ type: 'M', label: 'Mortali', count: 24, percent: 0.77, color: 'bg-black' }
];
const monthlyData = [
{ month: 'Gen', count: 235 }, { month: 'Feb', count: 226 }, { month: 'Mar', count: 279 },
{ month: 'Apr', count: 243 }, { month: 'Mag', count: 250 }, { month: 'Giu', count: 273 },
{ month: 'Lug', count: 288 }, { month: 'Ago', count: 228 }, { month: 'Set', count: 277 },
{ month: 'Ott', count: 287 }, { month: 'Nov', count: 283 }, { month: 'Dic', count: 255 }
];
const hourlyData = [
{ hour: '00-06', count: 404, label: 'Notte' },
{ hour: '07-12', count: 1017, label: 'Mattina' },
{ hour: '13-18', count: 1135, label: 'Pomeriggio' },
{ hour: '19-23', count: 568, label: 'Sera' }
];
const topLocations = [
{ name: 'VIA ROCCAZZO (civico 85)', accidents: 10, injuries: 1 },
{ name: 'VIALE REGIONE SICILIANA N.O. (s.n.c.)', accidents: 7, injuries: 4 },
{ name: 'PIAZZA EINSTEIN ALBERT (s.n.c.)', accidents: 5, injuries: 2 }
];
const qualityScores = [
{ dimension: 'Completezza', score: 9, maxScore: 10, percent: 90, color: 'bg-green-500' },
{ dimension: 'Struttura Dati', score: 8, maxScore: 10, percent: 80, color: 'bg-blue-500' },
{ dimension: 'Metadata', score: 6, maxScore: 10, percent: 60, color: 'bg-yellow-500' },
{ dimension: 'Formato File', score: 5, maxScore: 10, percent: 50, color: 'bg-orange-500' },
{ dimension: 'Tipi di Dati', score: 4, maxScore: 10, percent: 40, color: 'bg-red-500' }
];
// Render functions
function renderSeverityChart() {
const container = document.getElementById('severityChart');
severityData.forEach(item => {
const div = document.createElement('div');
div.className = 'space-y-2';
div.innerHTML = `
<div class="flex items-center justify-between text-sm">
<div class="flex items-center gap-3">
<span class="px-2 py-1 border border-slate-300 font-mono font-bold rounded">${item.type}</span>
<span class="font-medium">${item.label}</span>
</div>
<div class="flex items-center gap-4">
<span class="text-slate-600">${item.count.toLocaleString('it-IT')}</span>
<span class="font-semibold text-slate-900 w-16 text-right">${item.percent}%</span>
</div>
</div>
<div class="h-3 bg-slate-100 rounded-full overflow-hidden">
<div class="h-full ${item.color} transition-all duration-500" style="width: ${item.percent}%"></div>
</div>
`;
container.appendChild(div);
});
}
function renderMonthlyChart() {
const container = document.getElementById('monthlyChart');
const maxCount = Math.max(...monthlyData.map(d => d.count));
monthlyData.forEach(item => {
const div = document.createElement('div');
div.className = 'flex items-center gap-3';
div.innerHTML = `
<span class="text-sm font-medium w-10 text-slate-700">${item.month}</span>
<div class="flex-1 h-8 bg-slate-100 rounded overflow-hidden relative">
<div class="h-full bg-gradient-to-r from-blue-500 to-blue-600 transition-all duration-500"
style="width: ${(item.count / maxCount) * 100}%"></div>
<span class="absolute inset-0 flex items-center justify-end pr-3 text-sm font-semibold text-slate-900">
${item.count}
</span>
</div>
`;
container.appendChild(div);
});
}
function renderHourlyChart() {
const container = document.getElementById('hourlyChart');
hourlyData.forEach(item => {
const div = document.createElement('div');
div.className = 'space-y-2';
div.innerHTML = `
<div class="flex items-center justify-between">
<div class="flex items-center gap-3">
<span class="px-2 py-1 bg-slate-200 text-slate-700 font-mono text-sm rounded">${item.hour}</span>
<span class="text-sm text-slate-600">${item.label}</span>
</div>
<span class="font-bold text-lg">${item.count.toLocaleString('it-IT')}</span>
</div>
<div class="h-2 bg-slate-100 rounded-full overflow-hidden">
<div class="h-full bg-gradient-to-r from-indigo-500 to-indigo-600"
style="width: ${(item.count / 3124) * 100}%"></div>
</div>
`;
container.appendChild(div);
});
}
function renderTopLocations() {
const container = document.getElementById('topLocations');
topLocations.forEach((location, index) => {
const div = document.createElement('div');
div.className = 'flex items-start gap-4 p-4 bg-slate-50 rounded-lg border border-slate-200';
div.innerHTML = `
<div class="flex-shrink-0 w-8 h-8 rounded-full bg-red-100 text-red-700 font-bold flex items-center justify-center">
${index + 1}
</div>
<div class="flex-1">
<h4 class="font-semibold text-slate-900">${location.name}</h4>
<div class="flex gap-4 mt-2 text-sm text-slate-600">
<span><strong>${location.accidents}</strong> sinistri</span>
<span><strong>${location.injuries}</strong> ferit${location.injuries === 1 ? 'o' : 'i'}</span>
</div>
</div>
`;
container.appendChild(div);
});
}
function renderQualityBreakdown() {
const container = document.getElementById('qualityBreakdown');
qualityScores.forEach(item => {
const div = document.createElement('div');
div.className = 'flex items-center justify-between';
div.innerHTML = `
<span class="text-slate-700">${item.dimension}</span>
<div class="flex items-center gap-2">
<div class="w-32 h-2 bg-slate-200 rounded-full overflow-hidden">
<div class="h-full ${item.color}" style="width: ${item.percent}%"></div>
</div>
<span class="font-semibold text-slate-900 w-12 text-right">${item.score}/${item.maxScore}</span>
</div>
`;
container.appendChild(div);
});
}
// Initialize - works with gisthost and other dynamic loaders
let initialized = false;
function init() {
// Prevent multiple initializations
if (initialized) return;
// Wait for containers to exist
const container = document.getElementById('severityChart');
if (!container) return; // Exit cleanly if not ready yet
// Only render if not already rendered
if (container.innerHTML === '') {
renderSeverityChart();
renderMonthlyChart();
renderHourlyChart();
renderTopLocations();
renderQualityBreakdown();
initialized = true;
}
}
// Try initialization with proper cleanup
// 1. Immediate check
init();
// 2. DOMContentLoaded
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
}
// 3. Single delayed attempt for gisthost (then give up)
if (!initialized) {
setTimeout(init, 300);
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment