CatalogPublic data ›Reference
AI

Public money in your area — spending, cohesion funds and tenders

The civic question — *where does public money go?* — answered with official data, municipality by municipality: what your municipality pays and for what (SIOPE cash flows), which projects are funded by cohesion and recovery funds and how far along they are, who wins the tenders. All analyses run IN the browser: none of your data leaves the device.

▶ Use this app

public spendingbudgetmunicipalitysiopeopenbdapcohesionopencuptendersanactransparencyaccountabilitydashboardanomaliesforecast

What it records

  • Search the municipality by name…
  • E.g. which item weighs most per resident?
  • E.g. road maintenance, school canteen, lighting…

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.

  1. Use it right away. Hit “Use this app”: it opens right in your browser, ready to go, with data saved only on your device.
  2. Your data stays yours. Everything is stored locally on your device, kept separate for each app: no account, no server, nothing to set up.
  3. Make it your own. The text below is the entire app: copy it, tweak fields, views and words, and your changes become interactive instantly.
  4. 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
# 💰 Public money in your area

The civic question — *where does public money go?* — answered with official
data, municipality by municipality: what your municipality pays and for
what (SIOPE cash flows), which projects are funded by cohesion and recovery
funds and how far along they are, who wins the tenders. All analyses run IN
the browser: none of your data leaves the device.

:::::page{title="My municipality" icon="🏛️"}

## Your municipality's spending

Search the municipality by name and **pick it from the menu**: every page
updates by itself.

::input[cercaComune]{value="Bologna" placeholder="Search the municipality by name…"}

::od-query{into="comuniTrovati" sql="SELECT codice_istat AS codice_istat, comune || ' (' || sigla || ')' AS etichetta FROM istat_confini_comuni WHERE upper(strip_accents(comune)) LIKE '%' || upper(strip_accents('{#cercaComune}')) || '%' ORDER BY length(comune), comune" limit="20"}

::select-from[comune]{from="comuniTrovati" label="etichetta" value="codice_istat"}

::od-query{into="kpiSpesa" sql="SELECT round(sum(importo)/1e6,1) AS milioni, round(sum(importo)/max(popolazione)) AS procapite, max(anno) AS anno FROM siope_spese WHERE codice_istat='{#comune}' AND mese=12 AND anno=(SELECT max(anno) FROM siope_spese WHERE mese=12 AND codice_istat='{#comune}') AND categoria_codice NOT IN ('7.01','7.02','0.00')"}

::od-query{into="kpiVar" sql="SELECT coalesce(round(100.0*(a.tot-b.tot)/nullif(b.tot,0),1)::VARCHAR || '%','n/a') AS variazione, round(a.inv/1e6,1) AS investimenti FROM (SELECT sum(importo) AS tot, sum(importo) FILTER (WHERE categoria_codice LIKE '2.%') AS inv FROM siope_spese WHERE codice_istat='{#comune}' AND mese=12 AND anno=(SELECT max(anno) FROM siope_spese WHERE mese=12 AND codice_istat='{#comune}')) a, (SELECT sum(importo) AS tot FROM siope_spese WHERE codice_istat='{#comune}' AND mese=12 AND anno=(SELECT max(anno) FROM siope_spese WHERE mese=12 AND codice_istat='{#comune}')-1) b"}

::::grid{cols="3"}
:::cards{path="kpiSpesa" search="false"}
💶 **€{milioni}M** paid in {anno} — **€{procapite}** per resident
:::
---
:::cards{path="kpiVar" search="false"}
📈 **{variazione}** vs the previous year
:::
---
:::cards{path="kpiVar" search="false"}
🏗️ **€{investimenti}M** of capital investment
:::
::::

::od-query{into="kpiRank" sql="SELECT posizione, totale, centile FROM (SELECT codice_istat, rank() OVER (ORDER BY pc DESC) AS posizione, count(*) OVER () AS totale, round(100*percent_rank() OVER (ORDER BY pc)) AS centile FROM (SELECT codice_istat, sum(importo)/max(popolazione) AS pc FROM siope_spese WHERE regione=(SELECT regione FROM siope_spese WHERE codice_istat='{#comune}' LIMIT 1) AND mese=12 AND anno=(SELECT anno FROM siope_spese WHERE mese=12 GROUP BY anno HAVING count(DISTINCT codice_istat)>=6500 ORDER BY anno DESC LIMIT 1) AND popolazione>0 AND categoria_codice NOT IN ('7.01','7.02','0.00') GROUP BY 1)) WHERE codice_istat='{#comune}'"}

::od-query{into="kpiDebito" sql="SELECT round((coalesce(sum(importo) FILTER (WHERE categoria_codice='1.07'),0)+coalesce(sum(importo) FILTER (WHERE categoria_codice LIKE '4.%'),0))/max(popolazione)) AS eur_ab FROM siope_spese WHERE codice_istat='{#comune}' AND mese=12 AND anno=(SELECT max(anno) FROM siope_spese WHERE mese=12 AND codice_istat='{#comune}')"}

::::grid{cols="2"}
:::cards{path="kpiRank" search="false"}
🏆 **#{posizione} of {totale}** municipalities in the region by per-capita spending — {centile}th percentile
:::
---
:::cards{path="kpiDebito" search="false"}
💳 Debt costs **€{eur_ab} per resident** a year (interest + loan repayments)
:::
::::

These are **cash** payments (what actually went out, not what was
committed), excluding book transfers and pass-through items. The latest
closed year drives the figures; the current year appears in the trend as
partial.

## What it spends on — click a bar to filter

::od-query{into="spesaCategorie" sql="WITH pc AS (SELECT codice_istat, categoria_codice, categoria, sum(importo) AS imp, max(popolazione) AS pop FROM siope_spese WHERE regione=(SELECT regione FROM siope_spese WHERE codice_istat='{#comune}' LIMIT 1) AND mese=12 AND anno=(SELECT anno FROM siope_spese WHERE mese=12 GROUP BY anno HAVING count(DISTINCT codice_istat)>=6500 ORDER BY anno DESC LIMIT 1) AND popolazione>0 AND categoria_codice NOT IN ('7.01','7.02','0.00') GROUP BY 1,2,3), poptot AS (SELECT sum(pop) AS pop FROM (SELECT codice_istat, max(pop) AS pop FROM pc GROUP BY 1)) SELECT CASE categoria_codice WHEN '1.01' THEN 'Staff' WHEN '1.02' THEN 'Taxes' WHEN '1.03' THEN 'Goods and services' WHEN '1.04' THEN 'Transfers' WHEN '1.07' THEN 'Interest' WHEN '1.09' THEN 'Revenue refunds' WHEN '1.10' THEN 'Other current' WHEN '2.02' THEN 'Investment' WHEN '2.03' THEN 'Investment grants' WHEN '2.05' THEN 'Other capital' WHEN '4.03' THEN 'Loan repayment' WHEN '5.01' THEN 'Advances' ELSE min(categoria) END AS voce, min(categoria) AS categoria, round(sum(imp) FILTER (WHERE codice_istat='{#comune}')/1e3) AS migliaia, round(sum(imp) FILTER (WHERE codice_istat='{#comune}')/nullif(max(pop) FILTER (WHERE codice_istat='{#comune}'),0)) AS procapite, round(sum(imp)/(SELECT pop FROM poptot)) AS media FROM pc GROUP BY categoria_codice ORDER BY migliaia DESC NULLS LAST" limit="30"}

::::dashboard{path="spesaCategorie"}
::chart-bar{data="spesaCategorie" x="voce" y="procapite,media" height="18rem"}

:::table{path="spesaCategorie" headers="Category,Thousand €,€/res (you),€/res (region avg)" search="false" pagesize="0"}
{categoria} | {migliaia} | {procapite} | {media}
:::
::::

The two chart series: **your municipality** against the **regional
average**, in euros per resident (latest full-coverage year).

## Where your municipality sits

The distribution of per-capita spending across every municipality in the
region: compare your card above with the shape of the curve.

::od-query{into="distribuzionePc" sql="SELECT round(sum(importo)/max(popolazione)) AS procapite FROM siope_spese WHERE regione=(SELECT regione FROM siope_spese WHERE codice_istat='{#comune}' LIMIT 1) AND mese=12 AND anno=(SELECT anno FROM siope_spese WHERE mese=12 GROUP BY anno HAVING count(DISTINCT codice_istat)>=6500 ORDER BY anno DESC LIMIT 1) AND popolazione>0 AND categoria_codice NOT IN ('7.01','7.02','0.00') GROUP BY codice_istat" limit="1600"}

::chart-histogram{data="distribuzionePc" field="procapite" bins="30"}

## The trend: current spending and investment, year by year

::od-query{into="trendSpesa" sql="SELECT anno AS anno, round(sum(importo) FILTER (WHERE categoria_codice LIKE '1.%')/1e6,2) AS correnti, round(sum(importo) FILTER (WHERE categoria_codice LIKE '2.%')/1e6,2) AS investimenti, round(sum(importo) FILTER (WHERE categoria_codice LIKE '1.%')/max(popolazione)) AS correnti_ab, round(sum(importo) FILTER (WHERE categoria_codice LIKE '2.%')/max(popolazione)) AS investimenti_ab, max(mese) AS mese FROM siope_spese WHERE codice_istat='{#comune}' GROUP BY 1 ORDER BY 1"}

::chart-line{data="trendSpesa" x="anno" y="correnti_ab,investimenti_ab" height="16rem"}

The chart is in **euros per resident** (the honest version: a growing
municipality would otherwise just seem to spend more); the table is in
millions.

:::table{path="trendSpesa" headers="Year,Current (€M),Investment (€M),Months" search="false" pagesize="0" mark="mese<12:text-warning"}
{anno} | {correnti} | {investimenti} | {mese}
:::

A year with fewer than 12 months (in yellow) is the running total of the
current year.

## Ask the data

A plain-language question about the spending items above (needs the AI
assistant configured; the answer is computed on your device).

::ai-query{data="spesaCategorie" into="rispostaSpesa" placeholder="E.g. which item weighs most per resident?"}

::print{label="Print this profile"}

:::::

:::::page{title="Revenue" icon="🧾"}

## What your municipality collects

The other half of the budget: where the money comes from — own taxes,
government transfers, fees and fines.

::od-query{into="kpiEntrate" sql="SELECT round(sum(importo)/1e6,1) AS milioni, round(sum(importo)/max(popolazione)) AS procapite, max(anno) AS anno FROM siope_entrate WHERE codice_istat='{#comune}' AND mese=12 AND anno=(SELECT max(anno) FROM siope_entrate WHERE mese=12 AND codice_istat='{#comune}') AND categoria_codice NOT IN ('9.01','9.02','0.00')"}

::od-query{into="kpiAutonomia" sql="SELECT round(100.0*sum(importo) FILTER (WHERE categoria_codice LIKE '1.%' OR categoria_codice LIKE '3.%')/nullif(sum(importo) FILTER (WHERE categoria_codice LIKE '1.%' OR categoria_codice LIKE '2.%' OR categoria_codice LIKE '3.%'),0),1) AS autonomia FROM siope_entrate WHERE codice_istat='{#comune}' AND mese=12 AND anno=(SELECT max(anno) FROM siope_entrate WHERE mese=12 AND codice_istat='{#comune}')"}

::od-query{into="kpiMulte" sql="SELECT round(coalesce(sum(importo) FILTER (WHERE categoria_codice='3.02'),0)/max(popolazione)) AS eur_ab, round(coalesce(sum(importo) FILTER (WHERE categoria_codice='3.02'),0)/1e3) AS migliaia FROM siope_entrate WHERE codice_istat='{#comune}' AND mese=12 AND anno=(SELECT max(anno) FROM siope_entrate WHERE mese=12 AND codice_istat='{#comune}')"}

::::grid{cols="3"}
:::cards{path="kpiEntrate" search="false"}
🧾 **€{milioni}M** collected in {anno} — **€{procapite}** per resident
:::
---
:::cards{path="kpiAutonomia" search="false"}
🏛️ Financial autonomy **{autonomia}%** (own taxes and fees over current revenue)
:::
---
:::cards{path="kpiMulte" search="false"}
🚔 Fines and penalties: **€{eur_ab}** per resident (€{migliaia}k)
:::
::::

High autonomy = the municipality stands on its own revenue; low = it
depends on state and regional transfers.

## Where the money comes from — click to filter

::od-query{into="entrateCategorie" sql="SELECT categoria AS categoria, round(sum(importo)/1e3) AS migliaia, round(sum(importo)/max(popolazione)) AS procapite FROM siope_entrate WHERE codice_istat='{#comune}' AND mese=12 AND anno=(SELECT max(anno) FROM siope_entrate WHERE mese=12 AND codice_istat='{#comune}') AND categoria_codice NOT IN ('9.01','9.02','0.00') GROUP BY 1 ORDER BY 2 DESC" limit="30"}

::::dashboard{path="entrateCategorie"}
::chart-bar{data="entrateCategorie" x="categoria" y="procapite" height="18rem"}

:::table{path="entrateCategorie" headers="Item,Thousand €,€ per resident" search="false" pagesize="0"}
{categoria} | {migliaia} | {procapite}
:::
::::

## Revenue vs spending: the cash balance

::od-query{into="saldoCassa" sql="SELECT e.anno AS anno, e.entrate AS entrate, s.spese AS spese, round(e.entrate - s.spese,2) AS saldo FROM (SELECT anno, round(sum(importo)/1e6,2) AS entrate FROM siope_entrate WHERE codice_istat='{#comune}' AND categoria_codice NOT IN ('9.01','9.02','0.00') GROUP BY 1) e JOIN (SELECT anno, round(sum(importo)/1e6,2) AS spese FROM siope_spese WHERE codice_istat='{#comune}' AND categoria_codice NOT IN ('7.01','7.02','0.00') GROUP BY 1) s USING (anno) ORDER BY e.anno"}

::chart-line{data="saldoCassa" x="anno" y="entrate,spese" height="16rem"}

:::table{path="saldoCassa" headers="Year,Revenue (€M),Spending (€M),Balance (€M)" search="false" pagesize="0" mark="saldo<0:text-error font-bold"}
{anno} | {entrate} | {spese} | {saldo}
:::

A negative balance (in red) means more went out than came in that year: not
an alarm by itself (investments are also paid from accumulated surpluses),
but a streak of red years tells a story.

:::::

:::::page{title="Funded projects" icon="🏗️"}

## Projects funded in your area

Cohesion funds, recovery funds and public investments tracked by their CUP
code: what was funded in the chosen municipality, with which money, and how
far along it is.

::od-query{into="kpiCoesione" sql="SELECT count(*) AS progetti, round(sum(finanz_totale_pubblico)/1e6,1) AS milioni, round(100.0*sum(costo_realizzato)/nullif(sum(finanz_totale_pubblico),0),1) AS avanzamento FROM opencoesione WHERE codice_istat='{#comune}'"}

::od-query{into="kpiConclusi" sql="SELECT count(*) FILTER (WHERE stato_progetto ILIKE '%conclus%') AS conclusi, count(*) FILTER (WHERE stato_progetto NOT ILIKE '%conclus%') AS in_corso FROM opencoesione WHERE codice_istat='{#comune}'"}

::::grid{cols="3"}
:::cards{path="kpiCoesione" search="false"}
📦 **{progetti}** projects worth **€{milioni}M** of public funding
:::
---
:::cards{path="kpiCoesione" search="false"}
⏱️ Average financial progress **{avanzamento}%**
:::
---
:::cards{path="kpiConclusi" search="false"}
✅ **{conclusi}** completed — **{in_corso}** ongoing
:::
::::

## The largest projects, theme by theme

::od-query{into="progettiComune" sql="SELECT titolo AS titolo, tema AS tema, ciclo AS ciclo, round(finanz_totale_pubblico/1e3) AS migliaia, round(100.0*costo_realizzato/nullif(finanz_totale_pubblico,0)) AS avanzamento, stato_progetto AS stato FROM opencoesione WHERE codice_istat='{#comune}' ORDER BY finanz_totale_pubblico DESC" limit="200"}

::::dashboard{path="progettiComune"}
::chart-pie{data="temiComune" label="tema" value="migliaia" height="16rem"}

::od-query{into="temiComune" sql="SELECT tema AS tema, round(sum(finanz_totale_pubblico)/1e3) AS migliaia FROM opencoesione WHERE codice_istat='{#comune}' GROUP BY 1 ORDER BY 2 DESC" limit="20"}

:::table{path="progettiComune" headers="Project,Theme,Cycle,Thousand €,Progress %,Status" pagesize="10" mark="avanzamento<25:text-error,avanzamento>=75:text-success"}
{titolo} | {tema} | {ciclo} | {migliaia} | {avanzamento} | {stato}
:::
::::

Red = financial progress below 25%, green = above 75%. The search box above
the table filters by keyword.

## Investments in the national CUP registry

::od-query{into="cupComune" sql="SELECT descrizione AS descrizione, natura_intervento AS natura, round(costo_progetto/1e3) AS migliaia, anno_decisione AS anno, stato_progetto AS stato FROM opencup WHERE codice_istat='{#comune}' AND costo_progetto>0 ORDER BY costo_progetto DESC" limit="150"}

:::table{path="cupComune" headers="Project,Nature,Thousand €,Year,Status" pagesize="10"}
{descrizione} | {natura} | {migliaia} | {anno} | {stato}
:::

:::::

:::::page{title="Tenders" icon="🔨"}

## Local tenders and their winners

Public tenders published by authorities in the chosen municipality's
province, with outcomes and winners (source ANAC; amounts can carry source
errors).

::od-query{into="gareProvincia" sql="SELECT g.cig AS cig, g.oggetto_gara AS oggetto, g.amministrazione AS amministrazione, round(g.importo_lotto/1e3) AS migliaia, g.data_pubblicazione AS data, coalesce(a.vincitore,'—') AS vincitore, coalesce(round(a.ribasso,1),0) AS ribasso FROM anac_cig g LEFT JOIN (SELECT cig, any_value(denominazione) AS vincitore, max(ribasso) AS ribasso FROM anac_aggiudicatari GROUP BY cig) a ON a.cig=g.cig WHERE substr(g.luogo_istat,1,3)=substr('{#comune}',1,3) AND g.importo_lotto>0 ORDER BY g.data_pubblicazione DESC" limit="300"}

:::table{path="gareProvincia" headers="Subject,Authority,Thousand €,Date,Winner,Discount %" pagesize="10" mark="ribasso>40:text-error font-bold"}
{oggetto} | {amministrazione} | {migliaia} | {data} | {vincitore} | {ribasso}
:::

Discounts above 40% (in red) deserve a look: they can indicate poorly
estimated tenders or aggressive bids — they are **statistical outliers, not
accusations**.

## Search the tenders (semantic search)

::od-search{into="gareTrovate" table="anac_cig" placeholder="E.g. road maintenance, school canteen, lighting…"}

:::table{path="gareTrovate" headers="Subject,Authority,Amount,Province,Outcome" pagesize="10"}
{oggetto_gara} | {amministrazione} | {importo_lotto} | {provincia} | {esito}
:::

:::::

:::::page{title="ML analysis" icon="🤖"}

## Machine learning in the browser

These analyses run **on your device** (scikit-learn via Python in the
browser): the first run downloads the engine (~60 MB, then cached) behind
the button. They are reading tools, not verdicts.

## Where spending is headed: the forecast

The closed-year series projected forward (the R² in the status says how much
of the series the trend explains).

::od-query{into="serieSpesa" sql="SELECT anno AS anno, round(sum(importo)/1e6,2) AS spesa FROM siope_spese WHERE codice_istat='{#comune}' AND mese=12 AND categoria_codice NOT IN ('7.01','7.02','0.00') GROUP BY 1 ORDER BY 1"}

::range[anniPrev]{min="1" max="4" value="2" legend="Forecast years"}

::ml-forecast{data="serieSpesa" x="anno" y="spesa" horizon="#anniPrev" into="spesaPrevista"}

::chart-line{data="spesaPrevista" x="anno" y="spesa,previsione" height="16rem"}

## Out-of-pattern spending: anomalies across municipalities

Your municipality's per-capita profile compared with EVERY municipality in
the region (staff, goods and services, transfers, investment): an Isolation
Forest flags the anomalous profiles — yours included, if it is one.

::od-query{into="profiliRegione" sql="SELECT comune AS comune, codice_istat AS codice_istat, round(sum(importo) FILTER (WHERE categoria_codice='1.01')/max(popolazione)) AS personale, round(sum(importo) FILTER (WHERE categoria_codice='1.03')/max(popolazione)) AS beni_servizi, round(sum(importo) FILTER (WHERE categoria_codice='1.04')/max(popolazione)) AS trasferimenti, round(sum(importo) FILTER (WHERE categoria_codice LIKE '2.%')/max(popolazione)) AS investimenti FROM siope_spese WHERE regione=(SELECT regione FROM siope_spese WHERE codice_istat='{#comune}' LIMIT 1) AND mese=12 AND anno=(SELECT anno FROM siope_spese WHERE mese=12 GROUP BY anno HAVING count(DISTINCT codice_istat)>=6500 ORDER BY anno DESC LIMIT 1) AND popolazione>0 GROUP BY 1,2" limit="1600"}

::ml-anomaly{data="profiliRegione" features="personale,beni_servizi,trasferimenti,investimenti" contamination="0.05" into="comuniAnomali"}

:::filter{path="comuniAnomali" field="flag" equals="1" as="table" headers="Municipality,Staff €/res,Goods & services,Transfers,Investment,Score" pagesize="10"}
{comune} | {personale} | {beni_servizi} | {trasferimenti} | {investimenti} | {anomalia}
:::

## Anomalous discounts in local tenders

::od-query{into="ribassiProvincia" sql="SELECT g.oggetto_gara AS oggetto, g.amministrazione AS amministrazione, round(g.importo_lotto/1e3) AS migliaia, round(a.ribasso,1) AS ribasso, a.offerte_ammesse AS offerte FROM anac_cig g JOIN anac_aggiudicatari a ON a.cig=g.cig WHERE substr(g.luogo_istat,1,3)=substr('{#comune}',1,3) AND a.ribasso IS NOT NULL AND a.ribasso BETWEEN 0.1 AND 99 AND g.importo_lotto>10000" limit="1000"}

::ml-anomaly{data="ribassiProvincia" features="migliaia,ribasso" contamination="0.04" into="gareAnomale"}

:::filter{path="gareAnomale" field="flag" equals="1" as="table" headers="Subject,Authority,Thousand €,Discount %,Bids" pagesize="10"}
{oggetto} | {amministrazione} | {migliaia} | {ribasso} | {offerte}
:::

Unusual amount/discount combinations compared with the rest of the
province: a starting point for scrutiny, not a list of culprits.

## Municipalities that spend like yours

K-means groups the region's municipalities by per-capita spending profile:
find yours in the table and meet its family.

::range[kCluster]{min="3" max="8" value="5" legend="Number of groups"}

::ml-cluster{data="profiliRegione" features="personale,beni_servizi,trasferimenti,investimenti" k="#kCluster" into="famiglieComuni"}

:::table{path="famiglieComuni" headers="Municipality,Group,Staff,Goods & services,Investment" pagesize="10"}
{comune} | {cluster} | {personale} | {beni_servizi} | {investimenti}
:::

## Spending and income: what moves together

The correlation between per-capita spending items and average income across
the region's municipalities (r near ±1 = strong link; correlation, not
causation).

::od-query{into="spesaReddito" sql="SELECT s.comune AS comune, round(sum(s.importo) FILTER (WHERE s.categoria_codice='1.01')/max(s.popolazione)) AS personale, round(sum(s.importo) FILTER (WHERE s.categoria_codice='1.03')/max(s.popolazione)) AS beni_servizi, round(sum(s.importo) FILTER (WHERE s.categoria_codice LIKE '2.%')/max(s.popolazione)) AS investimenti, max(r.reddito_medio) AS reddito FROM siope_spese s JOIN mef_redditi r ON r.codice_istat=s.codice_istat AND r.anno=(SELECT max(anno) FROM mef_redditi) WHERE s.regione=(SELECT regione FROM siope_spese WHERE codice_istat='{#comune}' LIMIT 1) AND s.mese=12 AND s.anno=(SELECT anno FROM siope_spese WHERE mese=12 GROUP BY anno HAVING count(DISTINCT codice_istat)>=6500 ORDER BY anno DESC LIMIT 1) AND s.popolazione>0 GROUP BY 1" limit="1600"}

::ml-correlate{data="spesaReddito" features="personale,beni_servizi,investimenti,reddito" into="correlazioni"}

:::table{path="correlazioni" headers="Variable A,Variable B,r" search="false" pagesize="0" mark="r>0.5:text-success,r<-0.5:text-error"}
{a} | {b} | {r}
:::

:::::

:::::page{title="The map" icon="🗺️"}

## Per-capita spending, municipality by municipality

The choropleth of the chosen municipality's region: total spending per
resident in the latest closed year (deeper blue = higher spending).

::od-query{into="mappaSpesa" sql="SELECT c.comune AS comune, c.geojson AS geojson, round(sum(s.importo) FILTER (WHERE s.categoria_codice NOT IN ('7.01','7.02','0.00'))/max(s.popolazione)) AS procapite, round(coalesce(sum(s.importo) FILTER (WHERE s.categoria_codice LIKE '2.%'),0)/max(s.popolazione)) AS investimenti FROM siope_spese s JOIN istat_confini_comuni c ON c.codice_istat=s.codice_istat WHERE s.regione=(SELECT regione FROM siope_spese WHERE codice_istat='{#comune}' LIMIT 1) AND s.mese=12 AND s.anno=(SELECT anno FROM siope_spese WHERE mese=12 GROUP BY anno HAVING count(DISTINCT codice_istat)>=6500 ORDER BY anno DESC LIMIT 1) AND s.popolazione>0 GROUP BY 1,2" limit="1600"}

:::map{path="mappaSpesa" geojson="geojson" fill="procapite" height="28rem"}
**{comune}** — €{procapite} per resident (€{investimenti} of investment)
:::

## Investment per resident

Same boundaries, different metric: the second map reuses the data already
downloaded (polygons travel once).

:::map{path="mappaSpesa" geojson="geojson" fill="investimenti" height="28rem"}
**{comune}** — €{investimenti} of investment per resident
:::

:::::

:::::page{title="Explore" icon="🔍"}

## The free pivot

All the chosen municipality's spending, year by year and item by item, in
an explorable view: drag columns, group, switch charts.

::od-query{into="pivotSpesa" sql="SELECT s.anno AS anno, s.mese AS mese, s.titolo AS titolo, s.categoria AS categoria, round(s.importo) AS importo FROM siope_spese s WHERE s.codice_istat='{#comune}' ORDER BY s.anno, s.categoria" limit="1000"}

:::explore{path="pivotSpesa" view="bar" group-by="categoria" columns="importo" height="30rem"}
:::

Sources, licences and methodology notes on the **Sources** page.

:::::

:::::page{title="Sources" icon="📚"}

## Where the data comes from

Every source is **official open data**, refreshed periodically by Reactive's
data service and queried on the fly: this app stores nothing on its own
servers and the analyses run in your browser.

| Data | Source | Licence | Refresh |
|---|---|---|---|
| Municipal cash spending | [OpenBDAP — MEF/State General Accounting Office](https://bdap-opendata.rgs.mef.gov.it/catalog/RND_SPE_SIO) (SIOPE flows) | CC BY | monthly |
| Cohesion and recovery projects | [OpenCoesione](https://opencoesione.gov.it/it/opendata/) | CC BY 4.0 | bimonthly |
| Public investments (CUP) | [OpenCUP — Presidency of the Council/DIPE](https://opencup.gov.it/portale/web/opencup/open-data) | CC BY | monthly |
| Tenders and awardees | [ANAC — dati.anticorruzione.it](https://dati.anticorruzione.it/) | CC BY | monthly |
| Personal income tax by municipality | [MEF — Department of Finance](https://www1.finanze.gov.it/finanze/analisi_stat/public/index.php) | IODL/CC BY | yearly |
| Population and municipal boundaries | [ISTAT](https://www.istat.it/) | CC BY | yearly |
| Base map | © OpenStreetMap contributors | ODbL | — |

## Methodology notes

- **Cash, not accrual**: SIOPE amounts are payments MADE in the year, not
  budget commitments. An investment decided this year may be paid over the
  next ones; comparisons across municipalities remain valid because the
  metric is the same for everyone.
- **Pass-through items excluded**: totals leave out book transfers
  (spending 7.01/7.02, revenue 9.01/9.02) and movements pending
  regularization — they are not real spending or revenue.
- **Financial autonomy** = (tax + non-tax revenue) / current revenue: how
  much the municipality stands on its own resources.
- **Categories**: items are level II of the harmonized chart of accounts
  (Legislative Decree 118/2011), ~25 readable categories.
- **Partial year**: the current year (and municipalities that have not yet
  transmitted December) shows the running total to the latest available
  month, shown in the "Months" column.
- **ANAC amounts**: can carry source loading errors (including plainly
  wrong figures); out-of-scale discounts are filtered out of the analyses.
- **Machine learning**: forecasts, anomalies and clusters are statistical
  reading tools computed on your device — deviations, not verdicts.

:::::