Il comune è l'unità di lettura, e porta tutti e tre i pericoli - #139
Conversation
…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>
There was a problem hiding this comment.
💡 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".
| COALESCE( | ||
| (array_agg(m.class ORDER BY m.score DESC NULLS LAST))[1], | ||
| 'None' | ||
| ) AS worst_class, |
There was a problem hiding this comment.
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 👍 / 👎.
| 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) |
There was a problem hiding this comment.
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 👍 / 👎.
| ) -> 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) |
There was a problem hiding this comment.
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 👍 / 👎.
| (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, |
There was a problem hiding this comment.
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 👍 / 👎.
| * 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. |
There was a problem hiding this comment.
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 👍 / 👎.
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_riskera 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_tilesdà 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
hazardinvece 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