Added CLAUDE.md

This commit is contained in:
2026-05-07 21:18:13 +02:00
parent 17e178554f
commit 0a6e388831
+223
View File
@@ -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.