PostmonAPI / postmon

Uma API para consulta de informações relacionadas a endereçamento e entrega no Brasil.
http://www.postmon.com.br
GNU General Public License v3.0
653 stars 81 forks source link

Serviço de entrega de dados IBGE #56

Closed aleborba closed 10 years ago

aleborba commented 11 years ago

Pessoal, Surgiu uma solicitação via twitter, do João Drummond, de fornecermos os dados de estado e municipios do IBGE, mas especificamente o código IBGE deles, pois isso é usado para geração de NFe. O que acham de implementarmos uma nova rota para isso? O XLS com os dados é este:

http://servicodados.ibge.gov.br/Download/Download.ashx?u=geoftp.ibge.gov.br/organizacao_territorial/municipios_criados_e_instalados/a_legislacao_municipal_municipios_vigentes.xls
iurisilvio commented 11 years ago

Acho que faz sentido para o postmon. :+1:

aleborba commented 11 years ago

Quais dados seriam interessantes de colocarmos no retorno?

rochacbruno commented 10 years ago

Seria interessante ter os dados informados na tabela do IBGE

exemplo:

http://www.ibge.gov.br/home/geociencias/areaterritorial/area.php?nome=Barreiras

Exemplo de retorno do buscarcep.

http://www.buscarcep.com.br/?cep=45930-970&formato=string&chave=Chave_Gratuita_BuscarCep&identificador=cliente1

samuelgrigolato commented 10 years ago

Pessoal, para esta issue, qual caminho se adequa mais ao rumo do projeto?

O que acham?

iurisilvio commented 10 years ago

Os dados que tem no buscarcep são:

ibge_uf=29 -> UF no XLS
ibge_municipio=292200 
ibge_municipio_verificador=2922003 -> UF_MUNIC

O ibge_municipio deve ser UF_MUNIC[:-1], mas não tenho certeza disso.

Tem mais dados úteis nesse XLS? Vale a pena criar rotas /ibge/... ou só adicionar esses campos junto com o resultado dos CEPs é suficiente?

samuelgrigolato commented 10 years ago

Imagino que a criação ou não de nova rota deva ser decidida em função da "proximidade" da informação. Se for necessário executar uma requisição externa ao servidor do postmon para buscar a informação, mesmo que nem sempre (se existir a possibilidade dela estar cacheada no Mongo), então deve ser fornecida nova rota, pois quem não precisa da informação não deve ser prejudicado com a latência de uma nova round-trip no server, e mesmo o server não precisa gastar esse esforço se não for necessário.

Agora, se forem utilizadas informações locais direto do MongoDB (no caso de implementar uma rotina que processa o XLS de tempo em tempo, armazenando de uma forma indexada no Mongo, por exemplo) então não existiria problema em adicionar a informação diretamente nas rotas existentes.

aleborba commented 10 years ago

Gosto da ideia de uma nova rota. Mas não sei se acho legal utilizar /ibge/. Penso que algo mais "abstrato" ficaria mais "elegante". ;)

samuelgrigolato commented 10 years ago

Sobre uma rota mais abstrata, acho que iremos esbarrar em um problema, pois a informação que seria fornecida pela rota ("cód ibge") é especificamente relacionada ao "ibge", não sei se se encaixa em outra abstração. O que vocês acham? Conseguem pensar em outras possibilidades?

@aleborba, sua preferência a nova rota tem relação com o mecanismo interno de coleta e cacheamento do dado? Em outras palavras, você já deu preferência ao método que faz a busca sob demanda e cacheia no Mongo, similar ao que ocorre hoje com o CEP?

aleborba commented 10 years ago

exatamente @samuelgrigolato, eu tava pensando nisso. :)

samuelgrigolato commented 10 years ago

Mecanismo interno: utilizar as URLs abaixo de forma similar ao CepTracker, e cachear o resultado no mongo.

Para município: http://www.ibge.gov.br/home/geociencias/areaterritorial/area.php?nome=Araraquara&codigo=&submit.x=-162&submit.y=-313 Para UF (não tem filtro, o parse mesmo terá que buscar a linha correta): http://www.ibge.gov.br/home/geociencias/areaterritorial/principal.shtm

O que acham? Vale a pena investir tempo nisso?

Me dá medo esses parâmetros "submit.x" e "submit.y", ou são marretas ou são mecanismos antibot. Tirei eles da query string e a consulta continua funcionando (menos mal, devem ser marretas), só que não testei com uma tsunami de requisições em uma pequena janela de tempo ^^.

iurisilvio commented 10 years ago

@samuelgrigolato não é bom confiar nisso, mas usando o nome=% retorna todos os resultados: http://www.ibge.gov.br/home/geociencias/areaterritorial/area.php?nome=%

pbalduino commented 10 years ago

Sinto cheiro de SQL injection

iurisilvio commented 10 years ago

O nome=%' retorna os resultados com '. Provavelmente estão tratando as aspas, mas esqueceram do %.

samuelgrigolato commented 10 years ago

Realmente teremos que fazer o papel deles e tratar do nosso lado, para não prejudicar a performance do próprio servidor do postmon ao receber uma resposta desse tamanho. Valeu a observação!

iurisilvio commented 10 years ago

Também não gosto muito da rota /ibge. São dados sobre estados e cidades, não necessariamente vem tudo do IBGE.

Um exemplo disso é o DDD de cada cidade (uma coisa importante pra mim), que o postmon não tem mas caberia facilmente nessa rota que está sendo criada.

samuelgrigolato commented 10 years ago

O problema de utilizar o conceito "abstrato" é que, ao obter dados de vários serviços diferentes, o usuário (e o servidor do postmon) é prejudicado com o overload de requisitar dados de vários lugares. Uma opção seria usar rota abstrata e permitir alguns parâmetros de switch, por exemplo "/cidade/{nome}?dados=todos", "/cidade/{nome}?dados=ibge+ddd", "/cidade/{nome}?dados=ibge".

iurisilvio commented 10 years ago

Os dados são pré-processados, seria uma chamada no mongodb. O overhead é só de rede, mas com essa quantidade de dados é irrelevante.

Concordo que aumenta um pouco a complexidade para integrar esses dados em registros unificados.

O DDD foi só um exemplo, aposto que tem outros dados importantes para cada cidade. Gosto da opção de filtrar o que quero de resposta.

2013/11/1 Samuel Lopes Grigolato notifications@github.com

O problema de utilizar o conceito "abstrato" é que, ao obter dados de vários serviços diferentes, o usuário (e o servidor do postmon) é prejudicado com o overload de requisitar dados de vários lugares. Uma opção seria usar rota abstrata e permitir alguns parâmetros de switch, por exemplo "/cidade/{nome}?dados=todos", "/cidade/{nome}?dados=ibge+ddd", "/cidade/{nome}?dados=ibge".

— Reply to this email directly or view it on GitHubhttps://github.com/CodingForChange/postmon/issues/56#issuecomment-27565884 .

samuelgrigolato commented 10 years ago

Seriam pré-processados se fossem obtidos do XLS, mas pelo que eu entendi (vide comentários acima do @aleborba) faríamos um mecanismo de busca da informação via HTTP no serviço do site do IBGE, e utilizaríamos o mongo apenas para o cache, de forma similar ao que ocorre com o CEP. Desse modo, o overhead passa a ser uma requisição externa, e deixa de ser irrelevante.

Complementando, concordo que no caso do pré-processamento do XLS, os dados poderiam ser incorporados em qualquer rota existente, sem prejuízo na performance.

aleborba commented 10 years ago

No final, temos que analisar o que é melhor para ambos os lados :)

Alê Borba

Twitter: @ale_borba Linux User #506836 Blog: http://www.aleborba.com.br gTalk: ale.alvesborba@gmail.com TUX-ES Member. www.tux-es.org PHP-SP Member. www.phpsp.org.br Dojo-SP Member. www.dojosp.org Python Meet Up Organizer. credencial.imasters.com.br/encontro-grupy-sp

Em 1 de novembro de 2013 11:48, Samuel Lopes Grigolato < notifications@github.com> escreveu:

Seriam pré-processados se fossem obtidos do XLS, mas pelo que eu entendi (vide comentários acima do @aleborba https://github.com/aleborba) faríamos um mecanismo de busca da informação via HTTP no serviço do site do IBGE, e utilizaríamos o mongo apenas para o cache, de forma similar ao que ocorre com o CEP. Desse modo, o overhead passa a ser uma requisição externa, e deixa de ser irrelevante.

Complementando, concordo que no caso do pré-processamento do XLS, os dados poderiam ser incorporados em qualquer rota existente, sem prejuízo na performance.

— Reply to this email directly or view it on GitHubhttps://github.com/CodingForChange/postmon/issues/56#issuecomment-27566446 .

samuelgrigolato commented 10 years ago

Já existe alguma rotina "em batch" no postmon? Ou pelo menos existe a intenção em permitir tal mecanismo? Se sim, eu acho que a melhor opção é o pré-processamento mesmo, o TTL poderia ser bem alto (não vejo essa informação mudando com frequência) e ficaria mais transparente para o utilizador, além de novas rotas para "cidade" e "uf" poderíamos incorporar facilmente na própria rota do CEP.

iurisilvio commented 10 years ago

Acho que já me perdi na conversa. =)

Usando esse serviço do IBGE, podemos fazer esse request para nome=% e manter em cache por um bom tempo. Ou até buscar todos os códigos (tem isso no XLS) em background e salvar o resultado.

Pelo que entendi, de informação adicional só tem a área territorial nesse serviço do IBGE. Dá pra usar como base o XLS e adicionar outros dados nos registros conforme necessário.

Tem ~5k municípios, dá pra buscar todos os códigos no IBGE em background, nem deve impactar o servidor.

iurisilvio commented 10 years ago

Ainda não tem rotinas sendo processadas em background, mas faz sentido ter. A forma mais fácil de fazer isso é um cronjob, mas tem opções no Python pra isso também...

aleborba commented 10 years ago

@iurisilvio++

samuelgrigolato commented 10 years ago

Não tenho muita familiaridade com o ecossistema python, dei uma pesquisada e achei isso aqui: http://pythonhosted.org/APScheduler. Seria a opção mais adequada?

samuelgrigolato commented 10 years ago

Achei essa também http://docs.python.org/2/library/sched.html, mas senti falta de um mecanismo para executar "repetidamente", sem precisar agendar a próxima na própria execução da rotina. O APSScheduler tem isso: http://pythonhosted.org/APScheduler/intervalschedule.html

rochacbruno commented 10 years ago

Celery com periodic_task

import celery

@celery.task.periodic_task(run_every=timedelta(hours=5))
def do_anything():
    print "I run every 5 hours"
$ celeryd -l info &
samuelgrigolato commented 10 years ago

Pessoal, só pra reunir o que temos até agora:

É isso? Alguém tem algo a adicionar (ou a discordar) ou já dá pra por a mão na massa?

aleborba commented 10 years ago

Acho que é isso ai mesmo @samuelgrigolato.

Algo a acrescentar/remover aqui @iurisilvio @pbalduino @ihercowitz?

samuelgrigolato commented 10 years ago

@aleborba já estou implementando (na medida que o tempo livre permite ^^)

Esbarrei em uma coisa interessante: a URL do IBGE para estados não tem a informação "sigla", apenas o nome, logo não tem como "buscar" a UF pela sigla para fazer o update no mongo. Para resolver esse problema pensei em três opções:

1) Criar um script que carrega previamente (de forma 'hardcoded') os estados (sigla e nome) na base, de forma que o script de track do Ibge consiga realizar o find pelo nome. 2) Realizar primeiro a carga de cidades, montando um dicionário de "código UF" > "sigla" a partir dessa carga (nessa consulta tem o código e a sigla da uf), e depois na carga de estados utilizar esse dicionário para fazer o "lookup" da sigla. 3) Procurar uma outra fonte para realizar a carga dessa informação (acho desnecessário, estados não mudam assim tão frequentemente, rsrsrs)

Qual solução preferem?

iurisilvio commented 10 years ago

Isso aí mesmo. Só não acho necessário rodar isso todo dia, mas sendo só 2 requests, tanto faz. Se alguma hora tiver que rodar pra cada cidade, aí vale a pena pensar nisso.

aleborba commented 10 years ago

@samuelgrigolato acho q a 2 é a menos "workaround", não? :)

iurisilvio commented 10 years ago

:+1: pra opção 2 também.

samuelgrigolato commented 10 years ago

@aleborba Considerando que o conceito de UF, em sua essência, não é dependente do de cidade (apenas possui relação), o fato de precisar carregar as cidades para saber a sigla de uma UF (núcleo da solução 2) também tem um "quê" de workaround na minha opinião, mas concordo com vocês que é a solução mais "limpa" das três, pois na prática não vai existir UF sem cidade e essa lógica vai evitar a necessidade de um hardcode ou uma nova dependência externa apenas para obter essa informação.

Blz pessoal! Conforme for prosseguindo vou postando aqui o andamento. Como é minha primeira contribuição nesse repositório, se fosse possível gostaria de receber alguns feedbacks antes mesmo de um eventual pull request, para ir caminhando de acordo com a expectativa. Vou referenciando os commits do meu fork aqui, =].

aleborba commented 10 years ago

:hammer: :)

samuelgrigolato commented 10 years ago

Dêem uma olhada aí quando der!

Uma coisa que já vou avisando que não sei se ficou bom: os novos métodos que adicionei no "database.py". Não sei se ficaria melhor refatorar para 'orientar mais a dados' e não criar essas funções com nomes de entidade de negócio na interface desse módulo.

Da forma que está já dá pra rodar com "ipython IbgeTracker.py", adicionei um modo "standalone" igual tem no PostmonServer. Já adiciona ou atualiza no mongo conforme necessário, nas bases 'ufs' e 'cidades' respectivamente. A partir de então os routes 'cep', 'cidade' e 'uf' incorporam essas informações no retorno. Atenção especial também para os nomes que usei no retorno do cep, 'estado_info' e 'cidade_info', fiz isso para não trocar o atributo 'estado' e quebrar a compatibilidade (o que iria exigir uma v2 na API =S).

Tem uma coisa que está faltando: adicionar o suporte para rodar com o celeryd, mas não acho que isso vá levar muito tempo, e também não impacta na lógica!

Durante o desenvolvimento lembrei que existem cidades com mesmo nome em estados diferentes, logo a route para cidade acabou que ficou assim: '/cidade/sigla_uf/nome'.

Algumas URLs que funcionam:

aleborba commented 10 years ago

:+1:

samuelgrigolato commented 10 years ago

Adicionei o suporte ao Celery. Para rodar (depois de executar o pip):

celery worker -B -A PostmonTaskScheduler -l info

O "-B" é pro Celery rodar o "celery beat" junto ao worker. O "beat" é o responsável por agendar as periodic tasks.

Usei como "broker" o MongoDB, visto que já existe essa dependência no projeto. Ele está usando um database chamado "kombu_default".

Sugiro mudar o timedelta para "minutes=1" ao invés de "days=1" para testar, por motivos óbvios =D.

Acabei de lembrar que não rodei o make test =S, já já vem um novo commit com os ajustes pro pep8.

samuelgrigolato commented 10 years ago

Pronto.

Tenho mais uma observação. O Celery cria um arquivo no diretório onde o beat é executado, chamado celerybeat-schedule (é onde ele armazena os últimos timestamp de execução, algo assim). Inicialmente eu pensei que adicionar no .gitignore já resolveria (para evitar ter que adicionar uma configuração a mais no Celery pra jogar esse arquivo em outro lugar da máquina), só que o make test está tentando compilar esse arquivo e obviamente não consegue. Tem algum lugar pra fazer o test do pep8 ignorar esse cara? Ou esse tipo de coisa é resolvido de outra forma?

aleborba commented 10 years ago

Sabe alguma forma de resolver isso @rochacbruno?

rochacbruno commented 10 years ago

@aleborba @samuelgrigolato é possivel passar como parametro o caminho onde o Celery beat irá criar o arquivo de schedule http://docs.celeryproject.org/en/latest/userguide/periodic-tasks.html#starting-the-scheduler

celery beat -s /home/celery/var/run/celerybeat-schedule

rochacbruno commented 10 years ago

OU vc pode configurar no make para o pep8 ignorar o arquivo

pep8 --exclude='*/a/foo*' /path/to/dir

ou seja,

pep8 --exclude='*celerybeat-schedule*' /

samuelgrigolato commented 10 years ago

Coloquei o arquivo no exclude do pep8, funcionou!

Testei também passando um caminho customizado (via parâmetro '-s') e funcionou também! Ou seja, o usuário pode escolher a forma que ele quer usar.

Corrigi também a autenticação com o broker MongoDB, pois lembrei que no database.py existe uma forma de informar parâmetros de autenticação via variáveis de ambiente.

aleborba commented 10 years ago

A implementação foi feita. Mas tive problema pra colocar o celery e vou precisar de ajuda. Vou deixar a issue aberta até resolvermos :)

iurisilvio commented 10 years ago

@aleborba o problema é em alguma permissão. Se você rodar o supervisord como root, funciona (não é recomendado).

Sei que o supervisord precisa de acesso no /var/run e no /tmp, você sabe melhor que eu como estão as permissões na máquina do postmon.

aleborba commented 10 years ago

@iurisilvio então cara, na verdade o supervisord rodou, acho q eu agarrei no celery mesmo. Amanha a gente faz um hangout e ve isso direitinho, pode ser? cc/ @samuelgrigolato

samuelgrigolato commented 10 years ago

@aleborba se puder ser depois das 20 horas, eu estarei online.

aleborba commented 10 years ago

Up And Running! ;)