diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..7efd102 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,223 @@ +# CLAUDE.md — Portale Centraline 37100lab + +## Lingua + +Tutto il progetto deve essere in **italiano**: commenti nel codice, messaggi +all'utente, testo dell'interfaccia, documentazione interna, messaggi di errore, +label dei pulsanti, intestazioni CSV. + +Fanno eccezione — e restano in inglese — i nomi di variabili, funzioni, +componenti, file, classi CSS, e qualsiasi termine tecnico consolidato (es. +`fetch`, `endpoint`, `build`, `download`, `timestamp`). + +--- + +## Panoramica + +Portale web **statico** (solo frontend, nessun backend) per visualizzare le +centraline di monitoraggio ambientale di 37100lab e scaricare i dati storici in +formato CSV. + +**Stack**: Svelte + Vite. Output finale: cartella `dist/` da servire con +qualsiasi web server statico. + +--- + +## Setup iniziale + +Usare il template Svelte ufficiale di Vite. I comandi di sviluppo standard sono +`dev`, `build` e `preview`. + +--- + +## Struttura del progetto + +``` +portale-centraline/ +├── CLAUDE.md +├── package.json +├── vite.config.js +├── index.html +└── src/ + ├── App.svelte + ├── lib/ + │ ├── api.js # fetch verso l'API + │ ├── timezone.js # conversione UTC → Europe/Rome, iterazione giorni + │ └── csv.js # costruzione CSV e download + └── components/ + ├── StationList.svelte + ├── StationRow.svelte + └── DownloadProgress.svelte +``` + +--- + +## API + +**Base URL**: `http://37100lab.it:8101` + +### GET `/api/campagne_map_data` — lista centraline + +Risposta: GeoJSON. Le centraline si trovano in `.features[].properties`. + +Campi rilevanti di ogni `.properties`: + +| Campo | Tipo | Note | +|--------------|---------------|-----------------------------------------| +| `title` | string | Nome della centralina | +| `pk` | string | ID numerico, arriva come stringa | +| `created_at` | ISO 8601 UTC | Data/ora di creazione | +| `ended_at` | string o null | Data/ora di fine; null se ancora attiva | + +Il campo `pk` va convertito a numero intero. Le centraline vanno ordinate per ID +decrescente. + +### GET `/api/get_measurements/{id}?day=YYYY-M-D` — misurazioni giornaliere + +- `{id}`: ID numerico della centralina +- `day`: data in **`Europe/Rome`**, formato **`YYYY-M-D` senza zero-padding** + - ✅ `2026-5-6` + - ❌ `2026-05-06` + +La risposta contiene una chiave `misure` con un array di oggetti. Ogni oggetto +ha: + +| Campo | Tipo | Descrizione | +|--------|---------|-----------------------| +| `temp` | number | Temperatura (°C) | +| `umid` | number | Umidità relativa (%) | +| `pm10` | number | PM10 (µg/m³) | +| `time` | ISO UTC | Timestamp misurazione | + +`misure` può essere un array vuoto se il giorno non ha dati. + +--- + +## Formato CSV + +``` +"Data","Temperatura","Umidità","PM10" +"05/05/2026 22:00:53.7",13.45,89.22,16.3 +"05/05/2026 22:01:53.9",13.45,89.02,16.1 +"05/05/2026 22:02:54.1",13.47,88.88,15.5 +``` + +| Caratteristica | Valore | +|-------------------|--------------------------------------------------------------| +| Encoding | **UTF-8 con BOM** (`\uFEFF`) — necessario per Excel italiano | +| Separatore | virgola | +| Intestazioni | tra doppi apici, prima riga | +| Colonna Data | stringa tra doppi apici, formato `DD/MM/YYYY HH:MM:SS.s` | +| Colonne numeriche | senza apici, valori originali (non arrotondare) | +| Timestamp | convertito da UTC a **Europe/Rome** (gestisce ora legale) | + +**Nome file**: `{NomeCentralina}_({id})_{YYYY-MM-DD}.csv` Esempio: +`FabLab_Mantova_(178)_2026-05-07.csv` Sostituire spazi con `_`, rimuovere +caratteri non alfanumerici eccetto `_`. + +--- + +## Moduli + +### `lib/api.js` + +Contiene due funzioni: una per caricare la lista delle centraline e una per +caricare le misurazioni di un singolo giorno. La seconda non deve mai lanciare +eccezioni — in caso di errore HTTP o di rete restituisce un array vuoto, così da +non interrompere il loop di download. + +### `lib/timezone.js` + +Gestisce tutto ciò che riguarda date e timezone. Non usare librerie esterne: +`Intl.DateTimeFormat` con `timeZone: 'Europe/Rome'` è sufficiente e gestisce +automaticamente il cambio ora legale (CET/CEST). + +Contiene tre funzioni: + +- **`formatTimestampRome(isoString)`**: converte un timestamp ISO UTC nella + stringa formattata per il CSV (`DD/MM/YYYY HH:MM:SS.s`). I millisecondi + vengono troncati al primo decimale con `Math.floor`, non arrotondati: `706ms → + .7`. I millisecondi sono indipendenti dal timezone e si leggono direttamente + dall'oggetto `Date`. + +- **`formatDayParam(date)`**: restituisce la data locale Europe/Rome nel formato + `YYYY-M-D` senza zero-padding, da usare come parametro `day` nelle chiamate + API. Usare `Intl.DateTimeFormat` con `month: 'numeric'` e `day: 'numeric'` in + `formatToParts` — in questo modo i valori non hanno zero-padding. + +- **`getDayRange(startIsoUtc, endIsoUtc)`**: restituisce un array di oggetti + `Date`, uno per ogni giorno dal giorno di `startIsoUtc` al giorno di + `endIsoUtc` (estremi inclusi), calcolati in `Europe/Rome`. Se `endIsoUtc` è + `null`, il limite superiore è oggi. Usare **mezzogiorno UTC** (`T12:00:00Z`) + come ancora per ogni cursore giornaliero: mezzogiorno UTC corrisponde alle + 13:00 o 14:00 a Roma, quindi non attraversa mai un cambio di data locale + neanche al cambio ora legale. + +### `lib/csv.js` + +Contiene tre funzioni: una per costruire la stringa CSV da un array di +misurazioni (con BOM), una per triggerare il download nel browser, e una per +costruire il nome del file. + +--- + +## Componenti + +### `App.svelte` + +Componente radice. Al montaggio carica la lista delle centraline e gestisce i +tre stati: caricamento, errore, lista pronta. + +### `StationList.svelte` + +Riceve l'array di centraline e le mostra in una tabella con colonne: ID, nome, +data di creazione (formattata in `Europe/Rome`), stato (attiva/terminata), +pulsante download. + +### `StationRow.svelte` + +Gestisce la riga di una singola centralina e contiene tutta la logica di +download: + +1. Calcola il range di giorni con `getDayRange`. Le centraline **attive** + (`ended_at: null`) usano oggi come limite superiore; le centraline + **terminate** (`ended_at` valorizzato) usano `ended_at` come limite + superiore. +2. Itera i giorni **sequenzialmente** (non in parallelo) per non sovraccaricare + l'API. +3. Per ogni giorno chiama `fetchDay` e accumula le misurazioni. +4. Aggiorna il contatore di progresso ad ogni giorno completato. +5. Supporta la cancellazione: controlla un flag `cancelled` ad ogni iterazione; + se attivo, interrompe il loop senza scaricare nulla. +6. Al termine, se ci sono misurazioni e il download non è stato annullato, + genera e scarica il CSV. + +### `DownloadProgress.svelte` + +Mostra il progresso durante il download: giorno corrente, totale giorni e +percentuale. Ha un pulsante "Annulla" che emette un evento verso `StationRow`. + +--- + +## Edge case e comportamenti attesi + +| Situazione | Comportamento | +|-----------------------------------------------|-----------------------------------------------------------------------| +| Giorno senza misurazioni (`misure: []`) | Ignorare, continuare con il giorno successivo | +| Errore HTTP su un singolo giorno | `fetchDay` restituisce `[]`, ignorare e continuare | +| Download annullato dall'utente | Il loop si interrompe al controllo del flag, nessun file scaricato | +| Nessuna misurazione dopo tutti i giorni | Non generare il CSV, nessun download | +| Centralina attiva (`ended_at: null`) | Range: da `created_at` a **oggi** in Europe/Rome | +| Centralina terminata (`ended_at` valorizzato) | Range: da `created_at` a **`ended_at`** in Europe/Rome | +| Nome centralina con caratteri speciali | Sanitizzare per il nome file: spazi → `_`, resto rimosso | +| Errore nel caricamento della lista | `App.svelte` mostra un messaggio di errore all'utente | + +--- + +## Design + +Il portale riguarda dati ambientali e scientifici raccolti sul campo da stazioni +fisiche. L'estetica deve riflettere questo contesto: strumentale, precisa, +leggibile. Scegliere una direzione stilistica netta — non generica. Evitare +palette pastello, font di sistema e layout cookie-cutter. Curare tipografia, +spaziatura e la gerarchia visiva della tabella.