Last active
January 31, 2026 14:07
-
-
Save aborruso/46ef934581a2d2925e1104b575ced2da to your computer and use it in GitHub Desktop.
Sinistri Stradali Palermo 2023 - Analisi Open Data
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!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