Skip to content

Redesign della colonna: un numero per comune, una lista sola, Mantine - #141

Merged
gzileni merged 5 commits into
mainfrom
redesign-ui-comuni
Sep 29, 2026
Merged

gzileni merged 5 commits into
mainfrom
redesign-ui-comuni

Conversation

@gzileni

@gzileni gzileni commented Sep 29, 2026

Copy link
Copy Markdown
Contributor

Il redesign concordato, in tre pezzi.

Un numero per comune, pesato su chi c'è intorno

Non la somma dei tre punteggi: sommando, 0,40+0,40+0,40 passerebbe davanti a 0,80+0+0 — tre pericoli blandi davanti all'unico comune dove sta per succedere qualcosa — e i tre numeri non sono nemmeno commensurabili.

È il massimo della priorità già usata per gli alert (punteggio per uno più l'esposizione) con un incremento quando i pericoli oltre soglia sono più di uno. La regola sta in cascades.yaml.

L'esposizione era il pezzo mancante: il rollup la leggeva da factors->>'e', una chiave che solo il breakdown delle frane ha, quindi per incendio e alluvione valeva zero. Ora è materializzata per cella da limen calibrate con la stessa funzione pura del dispacciatore — in SQL non si poteva chiamare, e riscriverla lì avrebbe voluto dire due copie delle stesse soglie.

Effetto misurato: la classifica passa da Arta Terme, Erto e Casso, Verzegnis (comuni di montagna isolati) a Pontebba, Trieste, Spotorno, Zoagli, Genova — stessa classe di rischio, ma con persone e strade sotto.

Una lista sola

«Celle sopra soglia · 72h» e «Comuni · dal peggiore» erano due classifiche degli stessi posti con due unità diverse, una sotto l'altra. Ne resta una: il comune, con le celle come dettaglio che si apre sulla riga.

Il selettore dei pericoli lascia la testata e scende sulla mappa, dove filtrare serve davvero.

Mantine 8

Non la 9: pretende React 19, qui siamo sulla 18. Si prendono TextInput, Skeleton, Alert, Badge, Progress, Tooltip, Collapse, Stack/Group e useDebouncedValue. Il tema eredita i colori del progetto; la scala del rischio resta fuori dal tema, perché mappa e colonna devono condividerla byte per byte.

Il costo: bundle a 1,28 MB (380 KB compressi), frontend da quattro a sei dipendenze. In cambio ~200 righe di CSS in meno e accessibilità già risolta.

Gate

ruff, mypy --strict, 44 test unitari backend (7 nuovi sull'indice di attenzione, che fissano il controesempio contro la somma); frontend 113 test, lint e build puliti.

Due trappole documentate nel codice, entrambe con un messaggio che manda a cercare altrove: export * da testing-library copriva il render con provider, e jsdom senza matchMedia/ResizeObserver fa dire a ogni figlio «MantineProvider was not found».

🤖 Generated with Claude Code

gzileni and others added 2 commits September 29, 2026 10:34
La colonna mostra i tre pericoli affiancati, ma per decidere quale comune
guardare per primo serviva un numero. Non è la somma dei tre: sommando,
0,40+0,40+0,40 passerebbe davanti a 0,80+0+0 — tre pericoli blandi davanti
all'unico comune dove sta per succedere qualcosa — e i tre punteggi non sono
nemmeno commensurabili, perché hanno calibrazioni diverse.

È il **massimo della priorità già usata per gli alert** — punteggio per uno
più l'esposizione — con un incremento quando i pericoli oltre soglia sono
più di uno: due pericoli insieme sono una notizia diversa da uno solo, ma
non il doppio. La regola sta in `cascades.yaml` come tutte le altre.

**L'esposizione era il pezzo mancante.** Il rollup la leggeva da
`factors->>'e'`, una chiave che solo il breakdown delle frane ha: per
incendio e alluvione valeva zero. Ma «quante persone e quante strade ci sono
intorno» è una proprietà del posto, non del pericolo. Ora è materializzata
per cella in `cell_static_factors.exposure_norm`, calcolata da `limen
calibrate` con la stessa funzione pura del dispacciatore — in SQL non si
poteva chiamare, e riscriverla lì avrebbe voluto dire due copie delle stesse
soglie libere di divergere.

Senza il termine WUI, di proposito: pesa solo per l'incendio, e qui serve
una proprietà del posto. Lasciandolo dentro, lo stesso comune avrebbe
un'esposizione diversa a seconda del pericolo guardato — la confusione che
questo lavoro toglie.

**L'effetto misurato in produzione.** Prima la classifica era Arta Terme,
Erto e Casso, Verzegnis: comuni di montagna isolati. Ora è Pontebba,
Trieste, Spotorno, Zoagli, Genova — stessa classe di rischio, ma con
persone e strade sotto. 150.591 celle su 312.550 hanno un'esposizione
maggiore di zero.

L'ordinamento avviene in due tempi e non è pigrizia: in SQL sulla priorità
massima, che è il termine dominante, e in Python per l'incremento
multi-pericolo, che ha una soglia di classe e quindi vive nella
configurazione. Si prende un margine di tre volte il limite, abbastanza
perché un incremento del 15% non possa far entrare in pagina un comune
escluso dal taglio.

Con le tre skill Mantine installate nel progetto (`.agents/skills/`), che
servono al redesign della colonna.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Tre cose che la schermata mostrava e che questo cambio toglie.

**Le due liste che dicevano la stessa cosa.** «Celle sopra soglia · 72h» era
un albero regione → comune → celle, «Comuni · dal peggiore» una lista di
comuni: due classifiche degli stessi posti con due unità diverse, una sotto
l'altra. Ne resta una, il comune, con le celle come dettaglio che si apre
sulla riga. `RegionAccordion` è cancellato, e con lui il terzo livello che
chiedeva tre espansioni per arrivare a un nome di posto.

**Il selettore dei pericoli lascia la testata.** Se la lettura è «tutti i
pericoli insieme», quattro bottoni che cambiano modalità sono il residuo del
disegno precedente. Scende sulla mappa, dove filtrare un pericolo alla volta
serve davvero, e smette di ridipingere anche la colonna.

**Il numero di attenzione** sta accanto al nome, con una barra sotto: è il
massimo della priorità pesata per quanto c'è intorno, non la somma dei tre
punteggi. Un suggerimento lo spiega a parole, perché «1,35» da solo non dice
niente a chi non ha letto la documentazione.

**Mantine 8**, non la 9: la 9 pretende React 19 e qui siamo sulla 18, e
migrare React è un'altra impresa. Si prendono `TextInput`, `Skeleton`,
`Alert`, `Badge`, `Progress`, `Tooltip`, `Collapse`, `Stack`/`Group`, e
`useDebouncedValue` al posto del debounce scritto a mano. Il tema eredita i
colori del progetto; **la scala del rischio resta fuori dal tema**, in
`risk-colors.ts`, perché mappa e colonna devono condividerla byte per byte e
un tema che cambia cambierebbe il significato di una cella.

Il costo, detto chiaro: il bundle passa a 1,28 MB (380 KB compressi) e il
frontend da quattro a sei dipendenze a runtime, in un repository che due
settimane fa ha scritto un parser Markdown per non aggiungerne una. In
cambio spariscono ~200 righe di CSS e arrivano accessibilità e stati già
risolti.

Due trappole trovate per strada, entrambe con un messaggio che manda a
cercare nel posto sbagliato. `export *` da testing-library ri-esporta
`render` e copriva il nostro con provider. E jsdom non implementa
`matchMedia` né `ResizeObserver`: senza quei due il provider non monta e
ogni componente figlio riporta «MantineProvider was not found», che sembra
un errore di albero ed è un polyfill mancante.

113 test verdi, lint e build puliti.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

h: {
"class": str(v["class"]),
"score": round(float(v["score"] or 0.0), 3),
"n_cells": int(v["n_cells"]),
"n_alert": int(v["n_alert"]),
}

P2 Badge Preserve each hazard's computed priority

For every comune response, hazards is rebuilt without copying the priority value that _PER_COMUNE placed in the JSON. Pydantic then silently supplies ComuneHazard.priority's default of 0.0, so API consumers receive zero for every per-hazard priority even though the same nonzero value was just used to calculate attention; include the rounded priority in this output dictionary.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread frontend/src/App.tsx
onCellClick={onMapClick}
selectedCellId={selected?.cellId ?? null}
/>
<HazardSelector />

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep the hazard selector inside the visible map

When two or more hazards are enabled, the selector is now rendered after RiskMap, whose .map-wrap consumes 100% of the map area's height. Since .hazard-selector has no absolute positioning, it enters normal flow below that full-height map and falls outside the viewport, leaving dashboard users unable to switch hazards. Position it as an overlay within .map-area or reserve visible layout space for it.

Useful? React with 👍 / 👎.

Comment on lines +24 to +25
ALTER TABLE cell_static_factors
ADD COLUMN IF NOT EXISTS exposure_norm double precision;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Backfill exposure values during upgrades

On an existing deployment, normal API startup only runs migrations, so this adds exposure_norm as NULL for every existing cell and immediately refreshes mv_comune_risk with priorities that fall back to raw scores. The only backfill is in the separately invoked limen calibrate command, meaning ordinary upgrades silently ignore exposure indefinitely unless an operator knows to run that command; arrange an automatic backfill or an explicit upgrade step before refreshing the materialized view.

Useful? React with 👍 / 👎.

gzileni and others added 3 commits September 29, 2026 10:56
`exposure_rank` non è più la somma del termine E delle celle in allerta,
letto dal breakdown delle frane: è il massimo di `exposure_norm` sulle celle
del comune, che è una proprietà del posto e vale per tutti e tre i pericoli.
Il test fissava il vecchio significato, quindi semina l'esposizione dove ora
sta e verifica anche la cosa nuova — che la priorità superi il punteggio
nudo, cioè che il peso ci sia davvero.

Più la formattazione di `calibrate.py`, che avevo lasciato indietro.

Refs #141

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Il taglio del blocco CSS del vecchio pannello aveva lasciato il file senza
newline finale, e il gancio `end-of-file-fixer` lo segnala.

Refs #141

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Il commit precedente ne aggiungeva una a un file che ne aveva già una: il
gancio ne vuole esattamente una, e stavo correggendo a naso invece di
guardare il file.

Refs #141

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@gzileni
gzileni merged commit 8f2baae into main Sep 29, 2026
6 checks passed
@gzileni
gzileni deleted the redesign-ui-comuni branch September 29, 2026 11:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant