Added CLAUDE.md
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user