Skip to content

Il comune è l'unità di lettura, e porta tutti e tre i pericoli - #139

Merged
gzileni merged 1 commit into
mainfrom
feat-comuni-multi-pericolo
Sep 28, 2026
Merged

gzileni merged 1 commit into
mainfrom
feat-comuni-multi-pericolo

Conversation

@gzileni

@gzileni gzileni commented Sep 28, 2026

Copy link
Copy Markdown
Contributor

Una interfaccia sola per tutti i rischi, con il comune come unità.

Perché il comune

A scala nazionale la cella da 1 km² non è leggibile: con 78.822 celle Moderate su 312.550 la mappa è una coperta — una texture, non un'informazione — e la colonna a fianco era un albero a tre livelli (regione → comune → celle) che chiedeva tre espansioni per arrivare a un nome di posto.

Il comune è l'unità su cui si decide qualcosa: c'è un piano comunale, un ufficio tecnico, una persona che sa dove sono le case.

Cosa cambia

Il rollup smette di parlare di un pericolo solo. mv_comune_risk era fissata sulle frane in SQL e la dashboard lo dichiarava con un badge: scegliendo «Alluvione» metà colonna parlava d'altro. Ora la riga è per (comune, pericolo) — 23.703 righe — e la colonna mostra i tre indicatori affiancati con una richiesta sola.

I tre indicatori ci sono sempre, anche a zero: un trattino sbiadito invece di una colonna che sparisce. Oggi, con «Alluvione», non si distingueva «è calmo» da «è rotto».

La classe del comune resta quella della cella peggiore, come concordato: una soglia ridurrebbe il rumore ma nasconderebbe il versante singolo sopra un abitato, che è il caso per cui il sistema esiste. Il numero di celle sta accanto.

La ricerca è lato servizio, e con un termine la soglia non si applica: chi cerca il proprio comune vuole vederlo anche quando è tranquillo — ed è il caso in cui «nessun pericolo sopra soglia» è la notizia.

La mappa legge la stessa cosa: v_comune_tiles dà una riga per comune col peggiore dei tre e quale. Serve una vista a sé perché con tre righe per comune pg_tileserv disegnerebbe tre poligoni sovrapposti, e il colore sarebbe quello dell'ultimo disegnato.

Gli strumenti MCP perdono il parametro hazard invece di rifiutarlo: era giusto finché la vista sapeva rispondere a un pericolo solo, ma chiedere quale non ha senso quando la risposta li contiene tutti.

Misura

Sul database di produzione: 23.703 righe di rollup, 7.901 comuni nella vista mappa, 64 comuni con incendio in classe Alta, 4.392 con frana Moderata.

Gate

ruff, ruff format, mypy --strict (315 file), 43 test unitari su comuni/MCP/hazard-API; frontend 118 test, lint e build puliti. I tre test che fissavano il comportamento a pericolo unico sono riscritti sul contratto nuovo, non cancellati.

🤖 Generated with Claude Code

…ericoli

A scala nazionale la cella da 1 km² non è leggibile: con un quarto del paese
in classe Moderata la mappa è una coperta, e la colonna a fianco era un
albero a tre livelli — regione, comune, celle — che chiedeva tre espansioni
per arrivare a un nome di posto. Il comune invece è l'unità su cui si decide
qualcosa: c'è un piano comunale, un ufficio tecnico, una persona che sa dove
sono le case.

**Il rollup smette di parlare di un pericolo solo.** `mv_comune_risk` era
fissata sulle frane in SQL, e la dashboard lo dichiarava con un badge:
scegliendo «Alluvione» metà colonna continuava a parlare d'altro. Ora la
riga è per (comune, pericolo) — 7.901 × 3 fanno 23.703 righe, che è niente —
e la colonna mostra i tre indicatori affiancati con una richiesta sola.

La classe del comune resta quella della **cella peggiore**: una soglia
(«almeno tre celle sopra Moderato») ridurrebbe il rumore ma nasconderebbe il
versante singolo sopra un abitato, che è il caso per cui questo sistema
esiste. Il numero di celle sta accanto a dire quanto è esteso.

**I tre indicatori ci sono sempre, anche a zero.** Un pericolo tranquillo
mostra un trattino sbiadito invece di sparire: un trattino è una risposta,
una colonna che sparisce no — e oggi, con «Alluvione», non si distingueva
«è calmo» da «è rotto».

**La ricerca è lato servizio**, e con un termine la soglia non si applica:
chi cerca il proprio comune vuole vederlo anche quando è tranquillo, ed è
proprio il caso in cui «nessun pericolo sopra soglia» è la notizia. Filtrare
in pagina le trenta righe in mano vorrebbe dire non trovare il proprio fra
ottomila.

**La mappa legge la stessa cosa.** Il livello comunale non è più agganciato
alle frane: `v_comune_tiles` dà una riga per comune con il peggiore dei tre e
**quale**. Serve una vista a sé perché con tre righe per comune pg_tileserv
disegnerebbe tre poligoni sovrapposti e il colore sarebbe quello dell'ultimo
disegnato, cioè un caso.

Gli strumenti MCP perdono il parametro `hazard` invece di rifiutarlo: era la
cosa giusta finché la vista sapeva rispondere a un pericolo solo, ma chiedere
quale non ha senso quando la risposta li contiene tutti. I due test che
fissavano il rifiuto li ho riscritti sul contratto nuovo.

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

Here are some automated review suggestions for this pull request.

Reviewed commit: 5841962f5e

ℹ️ 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 on lines +36 to +39
COALESCE(
(array_agg(m.class ORDER BY m.score DESC NULLS LAST))[1],
'None'
) AS worst_class,

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 Preserve unknown state for unassessed hazards

When an enabled hazard has not yet produced assessments for a comune—for example immediately after enabling it or while its sweep is failing—the left join yields only NULL classes and this COALESCE converts them to the legitimate None risk class. The API and ComuniBoard consequently show the same quiet dash as a successfully assessed hazard whose cells are all None, so the change does not actually distinguish “calm” from “not calculated” and can falsely reassure users; preserve an unknown/unavailable state instead.

Useful? React with 👍 / 👎.

Comment on lines +122 to +126
WITH per_comune AS ({_PER_COMUNE})
SELECT * FROM per_comune
WHERE ($1::text IS NULL OR aoi_id = $1)
AND ($3::text IS NULL OR name ILIKE '%' || $3 || '%')
AND ($3::text IS NOT NULL OR worst_rank >= $4)

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 landslide report hazard-scoped

The shared top_comuni query now selects municipalities based on their worst class across every hazard and sums their alert counts, but src/limen/report/builder.py:335 still uses this helper in the report titled “Zone a maggior rischio frana” at line 338. A comune that is quiet for landslides but Moderate/High for wildfire can therefore be listed as a high-risk comune in a landslide report, with wildfire/flood alerts presented under the landslide context; the report needs a hazard-scoped query or must be converted consistently to a multi-hazard report.

Useful? React with 👍 / 👎.

Comment on lines 35 to +36
) -> ComuneListResponse:
_require_default_hazard(hazard)
rows = await comune_risk.top_comuni(aoi_id=aoi, limit=limit)
rows = await comune_risk.top_comuni(aoi_id=aoi, limit=limit, query=q)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Honor the documented hazard ordering parameter

For callers that send /api/comuni?hazard=flood, FastAPI now treats hazard as an unknown query parameter and silently ignores it, so this call returns the overall multi-hazard ordering. That contradicts this module's stated contract that hazard remains accepted and orders by that hazard, and it silently changes prior clients from a rejection or hazard-specific expectation to unrelated results; accept and pass the parameter into the repository, or explicitly reject it rather than ignoring it.

Useful? React with 👍 / 👎.

Comment on lines +48 to +52
(array_agg(hazard_type::text ORDER BY {_RANK} DESC, max_score DESC NULLS LAST,
hazard_type::text))[1] AS worst_hazard,
(array_agg(worst_class ORDER BY {_RANK} DESC, max_score DESC NULLS LAST,
hazard_type::text))[1] AS worst_class,
max(COALESCE(max_score, 0)) AS max_score,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Pair max_score with the selected worst hazard

Because hazards have different class cutoffs, the numerically largest score need not belong to the hazard selected by the preceding class-ranked array_agg expressions—for example, a flood score of 0.50 is High while a landslide score of 0.54 is only Moderate. In that case the response reports worst_hazard=flood and worst_class=High alongside max_score=0.54 from landslide, and the leaderboard's ORDER BY max_score can also misorder ties; derive the score from the same ordered hazard row as worst_hazard.

Useful? React with 👍 / 👎.

Comment thread src/limen/mcp/server.py
Comment on lines +60 to 63
* top_comuni(limit?, aoi_id?) e comune_risk(istat_code) → rollup per comune,
su tutti i pericoli (migrazione 051).
**Landslide only**: the rollup view is pinned to it, so passing another
hazard returns an error rather than mislabelled numbers.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Remove the obsolete landslide-only MCP instruction

The MCP instructions now introduce the comune tools as multi-hazard, but immediately tell agents that they are landslide-only and that passing another hazard returns an error. The wrappers in this commit removed the hazard parameter entirely, so this user-facing tool guidance is internally contradictory and describes behavior the exposed schema cannot perform; remove or replace the stale landslide-only paragraph.

Useful? React with 👍 / 👎.

@gzileni
gzileni merged commit e50e52c into main Sep 28, 2026
6 checks passed
@gzileni
gzileni deleted the feat-comuni-multi-pericolo branch September 28, 2026 15: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