Explorando os contratos da cidade de São Paulo

Na primeira parte deste tutorial, procurei mostrar como:

  1. Fazer o cadastro na API do SOF
  2. Acessar os registros de empenhos (a execução orçamentária)
  3. Utilizar o Pandas para explorar algumas análises
  4. Fazer download da base em CSV

Neste segundo tutorial, vamos focar em outra consulta disponível na mesma API: os contratos.

A Prefeitura de São Paulo ainda não dispõe de um sistema centralizado para gestão de contratos (existe um em implementação).

A boa notícia é que, com relação a contratos, se pode considerar uma das cidades mais transparentes do Brasil por ser uma das poucas a disponibilizar os termos de seus contratos e convênios na íntegra, desde 2014, nesta página do Portal da Transparência. Com o Marco Regulatório da Sociedade Civil, vigente a partir deste ano, também começam a aparecer por lá os Termos de Cooperação (sem transferência de recursos) que antes não eram publicados.Também as doações começaram a aparecer aí.

A má notícia é que nessa base do Portal você consegue extrair as informações publicadas em Diário Oficial, mas pode haver inconsistências (geradas por erros de publicação no D.O.). Às vezes há duplicidade, republicação, valores equivocados (pois são digitados manualmente na hora de publicar).

Por isso, podemos considerar que o registro mais confiável que há sobre a execução dos contratos é o do próprio sistema de Execução Orçamentária (o SOF), uma vez que é necessário cadastrar os contratos para realizar os pagamentos. E é daí a relevância desta API.

Importante!

A diferença com relação ao que está no Portal da Transparência é que os contratos não são imediatamente cadastrados no SOF (ele passa a existir no sistema apenas quando é gerado empenho vinculado a ele). Por isso, se quiser tratar no universo inteiro de contratos e não apenas da execução orçamentária, recomendo o 'double check' com a base do Portal da Transparência.

Passo 1. Consulta à API

Seguiremos os passos detalhados no tutorial anterior para acessar essa consulta na API e utilizar o Pandas para montar um DataFrame.


In [1]:
import pandas as pd
import requests
import json
import numpy as np

In [2]:
TOKEN = '198f959a5f39a1c441c7c863423264'
base_url = "https://gatewayapi.prodam.sp.gov.br:443/financas/orcamento/sof/v2.1.0"
headers={'Authorization' : str('Bearer ' + TOKEN)}

Um parêntesis! Como saber qual é o código do órgão que quero pesquisar?

Na consulta anterior, eu indiquei que o Código da Educação no Orçamento, por exemplo, é 16; o Fundo Municipal de Saúde, 84. Quem mexe com o orçamento, acaba se acostumando com os números. Mas como saber o código de todas as secretarias e empresas municipais? Existe uma consulta específica para isso na API, que você pode fazer lá no próprio console. Mas vamos trazer essa tabela para cá, para facilitar:


In [3]:
url_orgaos = '{base_url}/consultarOrgaos?anoExercicio=2017'.format(base_url=base_url)

In [4]:
request_orgaos = requests.get(url_orgaos,
                                headers=headers,
                                verify=True).json()

In [5]:
df_orgaos = pd.DataFrame(request_orgaos['lstOrgaos'])

In [6]:
df_orgaos


Out[6]:
codOrgao txtDescricaoOrgao
0 01 Autarquia Hospitalar Municipal
1 02 Hospital do Servidor Público Municipal
2 03 Instituto de Previdência Municipal de São Paulo
3 04 Serviço Funerário do Município de São Paulo
4 05 Autarquia Hospitalar Municipal Regional Leste
5 06 Autarquia Hospitalar Municipal Regional Sudeste
6 07 Fundo Municipal de Desenvolvimento Social
7 08 Fundo Municipal do Idoso
8 09 Câmara Municipal de São Paulo
9 10 Tribunal de Contas do Município de São Paulo
10 11 Secretaria do Governo Municipal
11 12 SECRETARIA MUNICIPAL DAS PREFEITURAS REGIONAIS
12 13 Secretaria Municipal de Gestão
13 14 Secretaria Municipal de Habitação
14 15 Secretaria Municipal de Modernização, Gestão ...
15 16 Secretaria Municipal de Educação
16 17 Secretaria Municipal da Fazenda
17 18 Secretaria Municipal da Saúde
18 19 SECRETARIA MUNICPAL DE ESPORTES E LAZER
19 20 SECRETARIA MUNICIPAL DE MOBILIDADE E TRANSPORTES
20 21 PROCURADORIA GERAL DO MUNICIPIO DE SÃO PAULO
21 22 SECRETARIA MUNICIPAL DE SERVIÇOS E OBRAS
22 23 SECRETARIA MUNICIPAL DE INOVAÇÃO E TECNOLOGIA
23 24 Secretaria Municipal de Assistência e Desenvol...
24 25 Secretaria Municipal de Cultura
25 26 Secretaria Municipal de Abastecimento
26 27 Secretaria Municipal do Verde e do Meio Ambiente
27 28 Encargos Gerais do Município
28 29 Secretaria Municipal de Comunicação
29 30 SECRETARIA MUNICIPAL DE TRABALHO E EMPREENDEDO...
... ... ...
68 69 PREFEITURA REGIONAL DE VILA PRUDENTE
69 70 PREFEITURA REGIONAL SÃO MATEUS
70 71 PREFEITURA REGIONAL CIDADE TIRADENTES
71 72 PREFEITURA REGIONAL SAPOPEMBA
72 74 Secretaria Municipal de Comunicação e Informaç...
73 75 Fundo Municipal de Parques
74 76 Fundo Especial de Despesas da Câmara Municipal...
75 77 Fundo Especial de Despesas do Tribunal de Contas
76 78 Secretaria Municipal de Licenciamento
77 79 Secretaria Municipal de Políticas para as Mulh...
78 80 FUNDAÇÃO PAULISTANA DE EDUCAÇÃO, TECNOLOGIA E ...
79 81 Autoridade Municipal de Limpeza Urbana
80 82 FUNDAÇÃO CATAVENTO
81 83 Companhia Metropolitana de Habitação de São Paulo
82 84 FUNDO MUNICIPAL DE SAÚDE
83 85 Fundação Theatro Municipal de São Paulo
84 86 Fundo Municipal de Saneamento Ambiental e Infr...
85 87 Fundo Municipal de Desenvolvimento de Trânsito
86 88 Fundo de Preservação do Patrimônio Histórico e...
87 89 Fundo Municipal de Esportes, Lazer e Recreação
88 90 Fundo Municipal dos Direitos da Criança e do A...
89 91 Fundo Municipal de Habitação
90 92 Fundo Mun. do Sist. dos Corredores Segregados ...
91 93 Fundo Municipal de Assistência Social
92 94 Fundo Especial do Meio Ambiente e Desenvolvime...
93 95 Fundo Especial de Promoção de Atividades Cultu...
94 96 Fundo Municipal de Turismo
95 97 Fundo de Proteção do Patrimônio Cultural e Amb...
96 98 Fundo de Desenvolvimento Urbano
97 99 Fundo Municipal de Iluminação Pública

98 rows × 2 columns

Atenção para uma distinção conceitual importante!

  • ÓRGÃO = Administração Direta (Prefeituras Regionais, Secretarias)
  • EMPRESA = Administração Indireta (Fundações, Empresas Públicas)

Os campos na consulta da API são diferentes. Então se você inserir o Código da Prodam (uma empresa pública) no Campo Órgão, por exemplo, a consulta vai retornar 0 Registros.

Pronto, aí temos a lista completa. Fecha parêntesis!

Seguindo, vamos fazer a consulta dos contratos da Secretaria do Verde e Meio Ambiente -- Órgão 27, conforme a lista acima.


In [7]:
url_contratos = '{base_url}/consultaContrato?anoContrato=2017&codOrgao=27'.format(base_url=base_url)

In [8]:
request_contratos = requests.get(url_contratos,
                                headers=headers,
                                verify=True).json()

In [9]:
number_of_pages = request_contratos['metadados']['qtdPaginas']

todos_contratos = []
todos_contratos = todos_contratos + request_contratos['lstContratos']
if number_of_pages>1:
    for p in range(2, number_of_pages+1):
        request_contratos = requests.get(url_contratos + pagination.format(PAGE=p), headers=headers, verify=True).json()
        todos_contratos = todos_contratos + request_contratos['lstContratos']

Passo 2. Mãos ao Pandas!


In [10]:
df_contratos = pd.DataFrame(todos_contratos)

Vamos checar como ficou nossa base de dados de contratos, mostrando os últimos 5 registros:


In [11]:
df_contratos.tail()


Out[11]:
anoContrato codContrato codEmpresa codEvento codModalidade codOrgao codTipoContratacao datAssinaturaContrato datPublicacaoContrato datVigencia ... txtTipoContratacao valAditamentos valAnulacao valAnuladoEmpenho valEmpenhadoLiquido valLiquidado valPago valPrincipal valReajustes valTotalEmpenhado
75 2017 3487 01 None 6 27 5 None None 12/04/2017 ... Nota de Empenho 0 0 0.0 1896.0 1896.00 1896.00 1896.0 0 1896.0
76 2017 2259 01 None 8 27 5 None None 31/12/2017 ... Nota de Empenho 0 0 0.0 1000.0 0.00 0.00 1000.0 0 1000.0
77 2017 2216 01 None 6 27 5 None None 15/03/2017 ... Nota de Empenho 0 0 0.0 2565.0 2565.00 2565.00 2565.0 0 2565.0
78 2017 2221 01 None 8 27 5 None None 31/12/2017 ... Nota de Empenho 0 0 0.0 7500.0 1878.26 1878.26 7500.0 0 7500.0
79 2017 196 01 None 6 27 5 None None 31/12/2017 ... Nota de Empenho 0 0 0.0 2232.0 413.42 413.42 2232.0 0 2232.0

5 rows × 25 columns

São muitas as colunas dessa consulta (a visualização acima omite algumas). Vamos checar quais são:


In [12]:
list(df_contratos)


Out[12]:
['anoContrato',
 'codContrato',
 'codEmpresa',
 'codEvento',
 'codModalidade',
 'codOrgao',
 'codTipoContratacao',
 'datAssinaturaContrato',
 'datPublicacaoContrato',
 'datVigencia',
 'numOriginalContrato',
 'txtDescricaoModalidade',
 'txtDescricaoOrgao',
 'txtObjetoContrato',
 'txtRazaoSocial',
 'txtTipoContratacao',
 'valAditamentos',
 'valAnulacao',
 'valAnuladoEmpenho',
 'valEmpenhadoLiquido',
 'valLiquidado',
 'valPago',
 'valPrincipal',
 'valReajustes',
 'valTotalEmpenhado']

In [13]:
df_contratos.to_csv('contratos_verde.csv')

Vamos remover o txtRazaoSocial, para não nos atrapalhar mais tarde (vamos obter o dado de outro local). Esta consulta não retorna o nome das empresas contratadas, mas "PREFEITURA DE SÃO PAULO" para todos os valores -- provavelmente um erro da API.


In [14]:
df_contratos.drop('txtRazaoSocial', axis=1, inplace=True)

Cruzando as bases

Infelizmente, a consulta não vem "pronta". Vamos precisar cruzar com a base de empenhos do primeiro tutorial para conseguir, por exemplo, saber quais são os credores POR CONTRATO. A imagem abaixo foi exibida durante o lançamento da API, no Café Hacker da Controladoria Geral do Município e da Secretaria Municipal da Fazenda, e explica como as consultas se relacionam (o 'Simplificada' é por conta deles, hehe):

O Código de Contrato é um parâmetro facultativo da consulta de empenhos, mas não vem no retorno de dados (outro possível furo da API, que vamos alertar aos desenvolvedores para tentar corrigir). O que isso significa? Que precisaríamos consultar um a um para obter todos os contratos na lista de empenhos! Felizmente, a programação tá aí pra isso. Vamos criar uma estrutura de repetição semelhante à que fizemos para a paginação:


In [15]:
url_empenho = '{base_url}/consultaEmpenhos?anoEmpenho=2017&mesEmpenho=08&codOrgao=27'.format(base_url=base_url)
num_contrato = '&codContrato={CONTRATO}'

In [16]:
lista_contratos = list(df_contratos['codContrato'])
len(lista_contratos)


Out[16]:
80

In [17]:
request_empenhos = requests.get(url_empenho,
                                headers=headers,
                                verify=True).json()

In [18]:
def add_codigo_contrato(empenhos, cod_contrato):
    """""
    Adiciona Código de Contrato no dict de cada empenho consultado. 
    """""
    for item in empenhos:
        item.update({'codContrato': cod_contrato})
    return empenhos

In [19]:
todos_empenhos = []

todos_empenhos = todos_empenhos + request_empenhos['lstEmpenhos']

for n in lista_contratos:
    response = requests.get(url_empenho + num_contrato.format(CONTRATO=n), headers=headers, verify=True).json()
    empenhos_c_cod = add_codigo_contrato(response['lstEmpenhos'], n)
    todos_empenhos = todos_empenhos + empenhos_c_cod

In [20]:
df_empenhos_c_contratos = pd.DataFrame(todos_empenhos)

Agora já temos o DataFrame que junta todos Empenhos do Verde de 2017 com as informações de Contrato, deixando a base mais rica -- e corrigindo essa falha da falta de Razão Social e CNPJ na consulta de contratos!


In [25]:
df_empenhos_c_contratos.tail()


Out[25]:
anoEmpenho codCategoria codContrato codElemento codEmpenho codEmpresa codFonteRecurso codFuncao codGrupo codItemDespesa ... txtDescricaoUnidade txtGrupoDespesa txtModalidadeAplicacao txtRazaoSocial valAnuladoEmpenho valEmpenhadoLiquido valLiquidado valPagoExercicio valPagoRestos valTotalEmpenhado
491 2017 3 3487.0 30 24512 01 00 18 3 99 ... Gabinete do Secretário Outras Despesas Correntes Aplicações Diretas ROSEMARY DA SILVA MENDES -ME 0.0 1896.0 1896.00 1896.00 0 1896.0
492 2017 3 2259.0 39 16031 01 00 18 3 02 ... Gabinete do Secretário Outras Despesas Correntes Aplicações Diretas SECRETARIA DA FAZENDA 0.0 1000.0 0.00 0.00 0 1000.0
493 2017 3 2216.0 30 16174 01 00 18 3 99 ... Gabinete do Secretário Outras Despesas Correntes Aplicações Diretas ANILHAS PARA PASSARO E AVES CAPRI INDÚSTRIA E ... 0.0 2565.0 2565.00 2565.00 0 2565.0
494 2017 3 2221.0 39 15778 01 00 18 3 02 ... Gabinete do Secretário Outras Despesas Correntes Aplicações Diretas SÃO PAULO SECRETARIA MUNICIPAL DE TRANSPORTES 0.0 7500.0 1878.26 1878.26 0 7500.0
495 2017 3 196.0 39 30 01 00 18 3 01 ... Gabinete do Secretário Outras Despesas Correntes Aplicações Diretas COMPANHIA DE GAS DE SAO PAULO COMGAS 0.0 2232.0 413.42 413.42 0 2232.0

5 rows × 42 columns

A tabela acima vai ter muitos valores "Nan" para codContrato -- pois há 493 empenhos e apenas 80 contratos. Como o interesse agora é só nos contratos, vamos retirar esses casos e montar um novo DataFrame que contém apenas contratos com algum empenho relacionado:


In [26]:
df_empenhos_c_contratos = df_empenhos_c_contratos.dropna(axis=0).reset_index(drop=True)

In [27]:
df_empenhos_c_contratos['codContrato'] = df_empenhos_c_contratos.loc[:,'codContrato'].astype(int)

Agora temos duas bases de dados para trabalhar:

  • df_contratos = Primeira base que extraímos, contém todos os contratos (sem Razão Social ou CNPJ, por limitação da API)
  • df_empenhos_c_contratos = Base que contém todos os empenhos com código de contrato, depois do cruzamento que fizemos

Ambas têm várias colunas em comum. Então, antes de juntá-las, vamos apenas retirar as colunas únicas nas duas, com o método "difference":


In [29]:
cols_to_use = df_empenhos_c_contratos.columns.difference(df_contratos.columns)

In [30]:
df_contratos_empenhados = df_contratos.merge(df_empenhos_c_contratos[cols_to_use], left_index=True, right_index=True, how='outer')

'Top 10' Contratos de 2017

Agora com a lista de todos os contratos do ano, vamos montar uma tabela e ordenar os dados pelo Valor Principal do Contrato. No Manual da API, aprende-se que esse campo 'valPrincipal'significa o "Valor do contrato sem ocorrência de reajustamentos, ou aditamentos".


In [31]:
df_contratos_empenhados.columns


Out[31]:
Index(['anoContrato', 'codContrato', 'codEmpresa', 'codEvento',
       'codModalidade', 'codOrgao', 'codTipoContratacao',
       'datAssinaturaContrato', 'datPublicacaoContrato', 'datVigencia',
       'numOriginalContrato', 'txtDescricaoModalidade', 'txtDescricaoOrgao',
       'txtObjetoContrato', 'txtTipoContratacao', 'valAditamentos',
       'valAnulacao', 'valAnuladoEmpenho', 'valEmpenhadoLiquido',
       'valLiquidado', 'valPago', 'valPrincipal', 'valReajustes',
       'valTotalEmpenhado', 'anoEmpenho', 'codCategoria', 'codElemento',
       'codEmpenho', 'codFonteRecurso', 'codFuncao', 'codGrupo',
       'codItemDespesa', 'codPrograma', 'codProjetoAtividade',
       'codSubElemento', 'codSubFuncao', 'codUnidade', 'datEmpenho',
       'mesEmpenho', 'nomEmpresa', 'numCpfCnpj', 'numReserva',
       'txtCategoriaEconomica', 'txtDescricaoElemento',
       'txtDescricaoFonteRecurso', 'txtDescricaoFuncao',
       'txtDescricaoItemDespesa', 'txtDescricaoPrograma',
       'txtDescricaoProjetoAtividade', 'txtDescricaoSubElemento',
       'txtDescricaoSubFuncao', 'txtDescricaoUnidade', 'txtGrupoDespesa',
       'txtModalidadeAplicacao', 'txtRazaoSocial', 'valPagoExercicio',
       'valPagoRestos'],
      dtype='object')

In [32]:
top20 = df_contratos_empenhados[['txtDescricaoModalidade',
                                 'txtObjetoContrato',
                                 'txtRazaoSocial',
                                 'numCpfCnpj',
                                 'valPrincipal']]

In [ ]:
#top20.sort_values(['valPrincipal'], ascending=False)

Modalidades de Contratação

Várias são as análises possíveis com os campos acima. Vamos ver quantas as contratações a SMVMA fez em 2017, por tipo de licitação:


In [ ]:
tipo_licitacao = df_contratos_Verde[]