sabato 23 febbraio 2019

rappresentazione treemap del bilancio comunale, analisi caso Milano

1. cos'è la treemap
2. esempio Milano  (http://dati.comune.milano.it/openbilancio/spese2019)
3. analisi del codice html/js, con riferimento alla struttura dati
4. collegamento con Socrata
5. suggerimenti alle Amministrazioni


1. cos'è la treemap


2. esempio Milano  (http://dati.comune.milano.it/openbilancio/spese2019)

il sorgente come al solito si guarda qui:
view-source:dati.comune.milano.it/openbilancio/spese2019


3. analisi del codice html/js, con riferimento alla struttura dati
...
var data = {
'DIRITTI SOCIALI, POLITICHE SOCIALI E FAMIGLIA' :{
'INTERVENTI PER L`INFANZIA E I MINORI E PER ASILI NIDO' :{
'CONTRATTI DI SERVIZIO PUBBLICO' :'108140720',
'RETRIBUZIONI IN DENARO' :'47197600',
'CONTRIBUTI SOCIALI EFFETTIVI A CARICO DELL`ENTE' :'13760700',
'UTENZE E CANONI' :'3819410',
'MANUTENZIONE ORDINARIA E RIPARAZIONI' :'3049760',
'ALTRO' :'4523820'},
'INTERVENTI PER GLI ANZIANI' :{
'CONTRATTI DI SERVIZIO PUBBLICO' :'66118870',
'INTERVENTI ASSISTENZIALI' :'4862260',
'ALTRI TRASFERIMENTI A FAMIGLIE' :'3400000',
'RETRIBUZIONI IN DENARO' :'1988990',
'ALTRO' :'2096600'},
'INTERVENTI PER LA DISABILITA`' :{
'CONTRATTI DI SERVIZIO PUBBLICO' :'47249980',
'TRASFERIMENTI CORRENTI A AMMINISTRAZIONI CENTRALI' :'7253480',
'RETRIBUZIONI IN DENARO' :'6053250',
'INTERVENTI ASSISTENZIALI' :'2543870',
'CONTRIBUTI SOCIALI EFFETTIVI A CARICO DELL`ENTE' :'1750230',
'TRASFERIMENTI CORRENTI A ISTITUZIONI SOCIALI PRIVATE' :'1250000',
'ALTRO' :'2007100'},
etc...
come si vede i dati devono essere in qualche modo 'annidati' con le parentesi graffe

i programmini python servono a creare la variabile data che deve rimpiazzare quella di Milano con i dati di Crema (magari un giorno anche tutto html...).

4. collegamento con Socrata
il json del bilancio  è molto grezzo, non annida le voci in base alle missioni e programmi
occorre un programmino python per riorganizzarlo in base al codice ministeriale:

appunti su come accedere a Socrata, spezzare i campi e ricostruirli con python (esempio riga 101):

Nota : lo split in base al trattino "-" del campo descrizione darà liste di lunghezza 1 invece che 2  perché non sempre presente nelle righe; da gestire



per prima cosa occorre riorganizzare i campi (soprattutto quelli del codice MEF) come NON vuole fare l'Amministrazione, poi creare l'albero.

import requests
from string import split
url="https://www.dati.lombardia.it/api/views/gy48-yges/rows.csv"
      
sito=requests.get(url)

listarighe=split(sito.text,"\n");

riga=[split(s,",") for s in listarighe]

f=open("output.txt","w")
r=1

while r<len(riga):
    
    codiceMEF=riga[r][1]
    listacodiceMEF=split(riga[r][1],".")
    
    #gestione trattino non sempre presente o troppo presente, maledizione
    listadescrizione=split(riga[r][2],"-")
    if len(listadescrizione)==1:
        campodescr1=listadescrizione[0]
        campodescr2=" "
    else:
        campodescr1=listadescrizione[0]
        campodescr2=' '.join(listadescrizione[1:])
    bilanciostrut = riga[r][0]+"\t"+listacodiceMEF[0]+"\t"+listacodiceMEF[1]+"\t"+listacodiceMEF[2][0:5]+"\t"+listacodiceMEF[2][5:8]+"\t"+campodescr1+"\t"+campodescr2+"\t"+riga[r][4]+"\n"
    
    
   #solo uscite U, E per entrate
    if riga[r][0]== "U":
        #ignorare codici no ascii , per evitare casini
        print(str(r)+"\t" + bilanciostrut+"\n")
        f.write(bilanciostrut.encode('ascii', 'ignore'))
        
    r +=1
f.close()
#chiude il file, salvando modifiche ecc

il file output.txt si presenta così, coi campi separati:



ACHTUNG: Crema usa gli apici per gli accenti (') questo è drammatico per javascript, highcharts; occorre sostituire gli apici con un altro tipo di apice come fanno a Milano  (`)

Passiamo alla creazione dell'albero con le parentesi graffe.
Si percorre in modo ricorsivo ciascuna colonna di output.txt generato sopra  da sinistra a destra  pregando che i codici siano giusti...




import csv
with open('output.txt', 'rb') as csvfile:
    reader = csv.reader(csvfile, delimiter='\t', quotechar='|')
    varTreeMap="var data = { \n '"
    numrig = 0
    currentc1=""
    currentc2=""
    previousc1=""
    previousc2=""
    for row in reader:
        colnum = 0
        for col in row:
            a=row[colnum]
            a=a.replace("'","`")
            row[colnum]=a
            colnum += 1
        if not row[7] == '0':
            if numrig == 0:
                #inizializzo var data
                print row[1]
                currentc1=row[1]
                currentc2=row[2]
                previousc1=row[1]
                previousc2=row[2]
                varTreeMap+=currentc1
                varTreeMap+=" ':{ \n \t \t'"
                varTreeMap+=currentc2
                varTreeMap+=" ':{ \n \t \t \t '"
                varTreeMap+=row[6]
                varTreeMap+=" ': '"
                varTreeMap+=row[7]
                varTreeMap+=" ', \n "
            else:
                #tutte le altre righe
                currentc1=row[1]
                currentc2=row[2]
                if currentc1==previousc1:
                    if currentc2==previousc2:
                            #è un'altra foglia
                            varTreeMap+="\t \t \t  '"
                            varTreeMap+=row[6]
                            varTreeMap+=" ': '"
                            varTreeMap+=row[7]
                            varTreeMap+=" ', \n "
                    else:
                            #cambio rametto c2
                            varTreeMap+="\t \t \t 'altro':'0'},\n"
                            varTreeMap+="\t \t '"
                            varTreeMap+=currentc2
                            varTreeMap+=" ':{ \n \t \t \t '"
                            varTreeMap+=row[6]
                            varTreeMap+=" ': '"
                            varTreeMap+=row[7]
                            varTreeMap+=" ', \n "
                else:
                     #cambio missione c1
                    varTreeMap+="\t \t \t 'altro':'0' }\n"
                    varTreeMap+="\t  },\n '"
                    varTreeMap+=currentc1
                    varTreeMap+=" ':{ \n \t '"
                    varTreeMap+=currentc2
                    varTreeMap+=" ':{ \n \t \t '"
                    varTreeMap+=row[6]
                    varTreeMap+=" ': '"
                    varTreeMap+=row[7]
                    varTreeMap+=" ', \n "
                previousc1=currentc1
                previousc2=currentc2
            numrig += 1
    varTreeMap+="\t\t\t'altro':'0' }\n\t\t}\n},"

    f=open('vartreemap.txt','w')
    f.write(varTreeMap.encode('ascii', 'ignore'))
    f.close()
    
filenames = ['primopezzohtml.txt', 'vartreemap.txt', 'secondopezzohtml.txt']
with open('treemapCrema2019.html', 'w') as outfile:
    for fname in filenames:
        with open(fname) as infile:
            for line in infile:
                outfile.write(line)

 


A questo punto nel file  vartreemap.txt c'è la variabile da copiare tale e quale nel codice html di Milano al posto della loro
il file con la sola variabile da copiare

e il gioco è fatto:
treemapCrema2019.html

Ovviamente i pezzi di python andranno messi insieme uno dopo l'altro , inclusa l'inclusione finale di vardata fra i due pezzi (il prima primopezzohtml.txt e il dopo  secondopezzohtml.txt, preparati all'uopo; ultime 6 righe del programma) di html.


5. suggerimenti alle Amministrazioni
a. usare apici diversi da quelli di javascript  (`) altrimenti uno è costretto a lavorarci
b. controllare formato numerico caricato su Socrata (al momento in centesimi di euro, per cui il valore in output.txt va diviso per 100 - per ora - per avere numeri coretti nel treemap:
bilanciostrut = riga[r][0]+"\t"+listacodiceMEF[0]+"\t"+listacodiceMEF[1]+"\t"+listacodiceMEF[2][0:5]+"\t"+listacodiceMEF[2][5:8]+"\t"+campodescr1+"\t"+campodescr2+"\t"+str(int(riga[r][5])/100)+"\n"
)

c. divulgare una tabella di corrispondenze fra codice di campo e significato, in modo che sul treemap appaiano parole invece che numeri
d. un codice di costo è sbagliato...., e non appare nel treemap
'50024 ':{ 
  '01 ':{ 
  '  ': '17897586000 ', 
  'altro':'0'},
'03 ':{ 
  '  ': '1557266300 ', 
    '  ': '61169600 ', 
    '  ': '54138100 ', 
    '  ': '1510858200 ', 
  'altro':'0' }
  },




Nessun commento:

Posta un commento