Your municipality in numbers — a portrait from official data
A portrait of your municipality built from official data: population and age structure (ISTAT), average declared income (MEF), pharmacies (Ministry of Health) and state schools (MIUR). The sources join on their own through the municipality's ISTAT code. Plus a map of Italy and the rankings.
data space «comune-in-cifre» — the suite’s glue: on your device, multi-user with one invite (link or QR), end-to-end encrypted.
What it records
- Municipality's ISTAT code
Composed of 4 micro apps
One job each, over the same data: every micro app reads and writes the shared data space “comune-in-cifre” (with your consent, asked on first open). Everything stays on your device — and with a single invite the whole suite goes multi-user, end-to-end encrypted.
How it works
This is a Reactive app: a plain text document that your browser turns into a working app — forms, live data, tables, statistics and even charts, with nothing to install or configure.
- Use it right away. Hit “Use this app”: it opens right in your browser, ready to go, with data saved only on your device.
- Your data stays yours. Everything is stored locally on your device, kept separate for each app: no account, no server, nothing to set up.
- Make it your own. The text below is the entire app: copy it, tweak fields, views and words, and your changes become interactive instantly.
- Share it as a file. An app is a simple file: save it, export it or share it with a link; and with encrypted collaboration several people edit the same data in real time.
The app’s source
# 🏘️ Your municipality in numbers
A portrait of your municipality built from official data: population and age
structure (ISTAT), average declared income (MEF), pharmacies (Ministry of
Health) and state schools (MIUR). The sources join on their own through the
municipality's ISTAT code. Plus a map of Italy and the rankings.
:::::page{title="Your municipality" icon="🔎"}
## Find your municipality
Enter the **ISTAT code** (six digits): e.g. `058091` Rome, `015146` Milan,
`037006` Bologna, `063049` Naples.
::input[comune]{value="037006" placeholder="Municipality's ISTAT code"}
::od-query{into="profilo" sql="SELECT p.comune AS comune, p.popolazione AS popolazione, r.reddito_medio AS reddito_medio, i.eta_media AS eta_media, i.perc_stranieri AS perc_stranieri, i.indice_vecchiaia AS indice_vecchiaia FROM istat_popolazione p LEFT JOIN mef_redditi r ON r.codice_istat = p.codice_istat LEFT JOIN istat_indicatori i ON i.codice_istat = p.codice_istat WHERE p.codice_istat = '{#comune}'"}
:::cards{path="profilo" search="false"}
### {comune}
**{popolazione}** inhabitants — average income **{reddito_medio} €**, average age **{eta_media}** years, foreign residents **{perc_stranieri}%**, old-age index **{indice_vecchiaia}** (over-65s per 100 under-15s).
:::
::od-query{into="servizi" sql="SELECT (SELECT count(*) FROM farmacie WHERE codice_istat = '{#comune}') AS farmacie, (SELECT count(*) FROM scuole WHERE codice_istat = '{#comune}') AS scuole"}
:::cards{path="servizi" search="false"}
🏥 **{farmacie}** pharmacies · 🏫 **{scuole}** state schools
:::
::od-query{into="ambiente" sql="SELECT percentuale_rd AS percentuale_rd, anno AS anno FROM rifiuti WHERE codice_istat = '{#comune}' ORDER BY anno DESC LIMIT 1"}
:::cards{path="ambiente" search="false"}
♻️ Separate waste collection **{percentuale_rd}%** (year {anno})
:::
::od-query{into="economia" sql="SELECT unita_locali AS unita_locali, addetti AS addetti, anno AS anno FROM imprese WHERE codice_istat = '{#comune}' ORDER BY anno DESC LIMIT 1"}
:::cards{path="economia" search="false"}
🏢 **{unita_locali}** active local units, **{addetti}** employees (year {anno})
:::
::od-query{into="voto" sql="SELECT affluenza AS affluenza, lista_vincente AS lista_vincente, percentuale_vincente AS percentuale_vincente FROM elezioni WHERE codice_istat = '{#comune}'"}
:::cards{path="voto" search="false"}
🗳️ Turnout **{affluenza}%** — top list **{lista_vincente}** ({percentuale_vincente}%) — 2022 general election
:::
::od-query{into="invalsiComune" sql="SELECT round(avg(punteggio_medio), 1) AS punteggio_medio, anno AS anno FROM invalsi WHERE codice_istat = '{#comune}' GROUP BY anno"}
:::cards{path="invalsiComune" search="false"}
📊 Average INVALSI test score **{punteggio_medio}** (year {anno}, municipalities above the student threshold)
:::
::od-query{into="veicoliComune" sql="SELECT autovetture AS autovetture, totale AS totale FROM aci_veicoli WHERE codice_istat = '{#comune}'"}
:::cards{path="veicoliComune" search="false"}
🚗 **{autovetture}** cars, **{totale}** registered vehicles in total (ACI)
:::
Income is the average per taxpayer (MEF, latest available year); age, foreign
residents and old-age index are ISTAT figures as of 1 January. The waste,
business, election, INVALSI and vehicle cards only appear if the municipality
has that data available (INVALSI and election data don't cover every
municipality; vehicles are counted where they are REGISTERED, not where their
owners live — large rental/leasing fleets concentrate in a few cities).
:::::
:::::page{title="The schools" icon="🏫"}
## The municipality's state schools
The list of state schools by level, from the ISTAT code entered on the first
page.
::od-query{into="scuoleComune" sql="SELECT nome AS nome, grado AS grado, indirizzo AS indirizzo FROM scuole WHERE codice_istat = '{#comune}' ORDER BY grado, nome" limit="200"}
:::table{path="scuoleComune" headers="School,Level,Address" pagesize="15"}
{nome} | {grado} | {indirizzo}
:::
:::::
:::::page{title="The map" icon="🗺️"}
## Average income by region
The same Italy seen from above: each region coloured by average declared
income. Hover for the value.
::od-query{into="mappa" sql="SELECT r.regione AS regione, r.geojson AS geojson, m.reddito AS reddito FROM istat_confini_regioni r JOIN (SELECT c.cod_reg, round(sum(x.reddito_complessivo) / sum(x.dichiaranti)) AS reddito FROM istat_confini_comuni c JOIN mef_redditi x ON x.codice_istat = c.codice_istat GROUP BY c.cod_reg) m ON m.cod_reg = r.cod_reg" limit="30"}
:::map{path="mappa" geojson="geojson" fill="reddito" height="30rem"}
**{regione}** — average income {reddito} €
:::
The colour runs from light (lower incomes) to dark (higher): the classic
North-South divide, in a map that draws itself from the data.
:::::
:::::page{title="Rankings" icon="🏆"}
## The municipalities with the highest income
Only municipalities above 10,000 inhabitants, so the small towns don't dominate.
::od-query{into="ricchi" sql="SELECT r.comune AS comune, r.sigla AS sigla, r.reddito_medio AS reddito_medio FROM mef_redditi r JOIN istat_popolazione p ON p.codice_istat = r.codice_istat WHERE p.popolazione >= 10000 ORDER BY r.reddito_medio DESC LIMIT 20" limit="20"}
::chart-bar{data="ricchi" x="comune" y="reddito_medio" height="20rem"}
:::table{path="ricchi" headers="Municipality,Prov,Average income €" pagesize="10"}
{comune} | {sigla} | {reddito_medio}
:::
:::::
---
The data are official and open: **population and indicators** from ISTAT
(demo.istat.it, CC BY 4.0), **IRPEF income** from the MEF — Department of
Finance (CC BY 3.0), **pharmacies** from the Ministry of Health and **schools**
from the MIUR (IODL 2.0), **separate waste collection** from ISPRA,
**businesses and employees** from ISTAT ASIA, **2022 general election
results** from the Ministry of the Interior, **INVALSI test results**
(INVALSI, CC BY 4.0 IT), **registered vehicles** from ACI — Autoritratto (CC
BY 4.0), **boundaries** from ISTAT. They join on their own through the
municipality's ISTAT code. None of your data leaves the device.