├── schema
├── cnae_secundaria.csv
├── cnae_cnpj.csv
├── holding.csv
├── cnae.csv
├── socio.csv
└── empresa.csv
├── schema-full
├── cnae_secundaria.csv
├── cnae_cnpj.csv
├── holding.csv
├── cnae.csv
├── socio.csv
└── empresa.csv
├── requirements-development.txt
├── .github
└── FUNDING.yml
├── requirements.txt
├── sql
├── 01-create-pks.sql
├── 00-create-tables.sql
├── 02-create-fks.sql
├── 03-create-indexes.sql
└── 04-create-old-views.sql
├── Makefile
├── .gitignore
├── check_pais_socio.py
├── headers
├── header.csv
├── trailler.csv
├── cnae_secundaria.csv
├── socio.csv
└── empresa.csv
├── extract_cnae_cnpj.py
├── import-postgresql.sh
├── run.sh
├── natureza_juridica.py
├── extract_holding.py
├── qualificacao-socio.csv
├── historia-do-dataset.md
├── data
└── natureza-juridica.csv
├── cnae.py
├── test_parser.py
├── LICENSE
├── README.md
└── extract_dump.py
/schema/cnae_secundaria.csv:
--------------------------------------------------------------------------------
1 | field_name,field_type
2 | cnpj,text
3 | cnae,integer
4 |
--------------------------------------------------------------------------------
/schema-full/cnae_secundaria.csv:
--------------------------------------------------------------------------------
1 | field_name,field_type
2 | cnpj,text
3 | cnae,integer
4 |
--------------------------------------------------------------------------------
/requirements-development.txt:
--------------------------------------------------------------------------------
1 | autoflake
2 | black
3 | flake8
4 | ipython
5 | isort
6 | pytest
7 |
--------------------------------------------------------------------------------
/schema/cnae_cnpj.csv:
--------------------------------------------------------------------------------
1 | field_name,field_type
2 | cnpj,text
3 | cnae,integer
4 | primaria,bool
5 |
--------------------------------------------------------------------------------
/schema-full/cnae_cnpj.csv:
--------------------------------------------------------------------------------
1 | field_name,field_type
2 | cnpj,text
3 | cnae,integer
4 | primaria,bool
5 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | custom: https://apoia.se/brasilio
4 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | click
2 | git+https://github.com/turicas/rows.git@develop#egg=rows
3 | lxml
4 | requests
5 | requests-cache
6 | scrapy
7 | tqdm==4.66.3
8 |
--------------------------------------------------------------------------------
/sql/01-create-pks.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE empresa ADD CONSTRAINT pk_empresa_id PRIMARY KEY (cnpj);
2 | ALTER TABLE cnae ADD CONSTRAINT pk_cnae_id PRIMARY KEY (id);
3 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | lint:
2 | autoflake --in-place --recursive --remove-unused-variables --remove-all-unused-imports .
3 | isort .
4 | black -l 120 .
5 | flake8
6 |
7 | .PHONY: lint
8 |
--------------------------------------------------------------------------------
/schema/holding.csv:
--------------------------------------------------------------------------------
1 | field_name,field_type
2 | holding_cnpj,text
3 | holding_razao_social,text
4 | cnpj,text
5 | razao_social,text
6 | codigo_qualificacao_socia,integer
7 | qualificacao_socia,text
8 |
--------------------------------------------------------------------------------
/schema-full/holding.csv:
--------------------------------------------------------------------------------
1 | field_name,field_type
2 | holding_cnpj,text
3 | holding_razao_social,text
4 | cnpj,text
5 | razao_social,text
6 | codigo_qualificacao_socia,integer
7 | qualificacao_socia,text
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.egg-info/
2 | *.pyc
3 | *~
4 | .*.sw?
5 | .DS_Store
6 | .activate
7 | .coverage
8 | .directory
9 | .env
10 | .idea/*
11 | .tox
12 | MANIFEST
13 | build/*
14 | dist/*
15 | download.sh
16 | data/*
17 | reg_settings.py
18 |
--------------------------------------------------------------------------------
/check_pais_socio.py:
--------------------------------------------------------------------------------
1 | import csv
2 | from collections import Counter
3 |
4 | import rows
5 | from tqdm import tqdm
6 |
7 | reader = csv.DictReader(rows.utils.open_compressed("data/output/socio.csv.gz"))
8 | paises = Counter((row["codigo_pais"], row["nome_pais"]) for row in tqdm(reader))
9 | print(paises.most_common())
10 |
--------------------------------------------------------------------------------
/schema/cnae.csv:
--------------------------------------------------------------------------------
1 | field_name,field_type
2 | id_subclasse,text
3 | descricao_subclasse,text
4 | id_classe,text
5 | descricao_classe,text
6 | id_grupo,text
7 | descricao_grupo,text
8 | id_divisao,text
9 | descricao_divisao,text
10 | id_secao,text
11 | descricao_secao,text
12 | notas_explicativas,text
13 | url,text
14 | id,integer
15 | versao,text
16 |
--------------------------------------------------------------------------------
/schema-full/cnae.csv:
--------------------------------------------------------------------------------
1 | field_name,field_type
2 | id_subclasse,text
3 | descricao_subclasse,text
4 | id_classe,text
5 | descricao_classe,text
6 | id_grupo,text
7 | descricao_grupo,text
8 | id_divisao,text
9 | descricao_divisao,text
10 | id_secao,text
11 | descricao_secao,text
12 | notas_explicativas,text
13 | url,text
14 | id,integer
15 | versao,text
16 |
--------------------------------------------------------------------------------
/schema/socio.csv:
--------------------------------------------------------------------------------
1 | field_name,field_type
2 | cnpj,text
3 | identificador_de_socio,integer
4 | nome_socio,text
5 | cnpj_cpf_do_socio,text
6 | codigo_qualificacao_socio,integer
7 | percentual_capital_social,integer
8 | data_entrada_sociedade,date
9 | cpf_representante_legal,text
10 | nome_representante_legal,text
11 | codigo_qualificacao_representante_legal,integer
12 |
--------------------------------------------------------------------------------
/schema-full/socio.csv:
--------------------------------------------------------------------------------
1 | field_name,field_type
2 | cnpj,text
3 | identificador_de_socio,integer
4 | nome_socio,text
5 | cnpj_cpf_do_socio,text
6 | codigo_qualificacao_socio,integer
7 | percentual_capital_social,integer
8 | data_entrada_sociedade,date
9 | codigo_pais,integer
10 | nome_pais,text
11 | cpf_representante_legal,text
12 | nome_representante_legal,text
13 | codigo_qualificacao_representante_legal,integer
14 |
--------------------------------------------------------------------------------
/headers/header.csv:
--------------------------------------------------------------------------------
1 | start_column,name,size,type,description
2 | 1,TIPO DE REGISTRO,1,A,CONTEM O VALOR 0 PARA IDENTIFICAR O REGISTRO HEADER
3 | 2,FILLER,16,A,BRANCOS
4 | 18,NOME DO ARQUIVO,11,A,NOME DO ARQUIVO: F.KXXXXXA
5 | 29,DATA DE GRAVAÇÃO,8,N,DATA DE GRAVAÇÃO DO ARQUIVO NO FORMATO ‘AAAAMMDD’
6 | 37,NUMERO DA REMESSA,8,N,"PARTINDO DE ‘00000001’, DEVENDO SER INCREMENTADO DE 1 A CADA NOVO ARQUIVO (quando exceder 99999999 retornar a 000001)"
7 | 45,FILLER,1155,A,BRANCOS
8 | 1200,FIM,1,A,CONTEÚDO 'F' - INDICA FINAL DE REGISTRO
9 |
--------------------------------------------------------------------------------
/headers/trailler.csv:
--------------------------------------------------------------------------------
1 | start_column,name,size,type,description
2 | 1,TIPO DE REGISTRO,1,A,CONTEM O VALOR 9 PARA IDENTIFICAR O REGISTRO HEADER
3 | 2,FILLER,16,A,NORMALIZADO COM '99999999999999'
4 | 18,TOTAL DE REGISTROS T1,9,N,TOTAL DE REGISTROS TIPO 1 DO ARQUIVO
5 | 27,TOTAL DE REGISTROS T2,9,N,TOTAL DE REGISTROS TIPO 2 DO ARQUIVO
6 | 36,TOTAL DE REGISTROS T3,9,N,TOTAL DE REGISTROS TIPO 3 DO ARQUIVO
7 | 45,TOTAL DE REGISTROS,11,N,"TOTAL DE REGISTROS DO ARQUIVO, INCLUINDO REGISTROS HEADER/TRAILER"
8 | 56,FILLER,1144,C,BRANCOS
9 | 1200,FIM,1,A,CONTEÚDO 'F' - INDICA FINAL DE REGISTRO
10 |
--------------------------------------------------------------------------------
/sql/00-create-tables.sql:
--------------------------------------------------------------------------------
1 | DROP TABLE IF EXISTS cnae;
2 | CREATE TABLE cnae AS SELECT * FROM cnae_23;
3 | INSERT INTO cnae SELECT c22.* FROM cnae_22 AS c22 WHERE NOT EXISTS(SELECT * FROM cnae AS c WHERE c.id = c22.id);
4 | INSERT INTO cnae SELECT c21.* FROM cnae_21 AS c21 WHERE NOT EXISTS(SELECT * FROM cnae AS c WHERE c.id = c21.id);
5 | INSERT INTO cnae SELECT c20.* FROM cnae_20 AS c20 WHERE NOT EXISTS(SELECT * FROM cnae AS c WHERE c.id = c20.id);
6 | INSERT INTO cnae SELECT c11.* FROM cnae_11 AS c11 WHERE NOT EXISTS(SELECT * FROM cnae AS c WHERE c.id = c11.id);
7 | INSERT INTO cnae SELECT c10.* FROM cnae_10 AS c10 WHERE NOT EXISTS(SELECT * FROM cnae AS c WHERE c.id = c10.id);
8 |
--------------------------------------------------------------------------------
/sql/02-create-fks.sql:
--------------------------------------------------------------------------------
1 | -- CNPJ
2 | ALTER TABLE cnae_cnpj ADD CONSTRAINT fk_cnae_cnpj_cnpj FOREIGN KEY (cnpj) REFERENCES empresa (cnpj);
3 | ALTER TABLE holding ADD CONSTRAINT fk_holding_cnpj FOREIGN KEY (cnpj) REFERENCES empresa (cnpj);
4 | ALTER TABLE holding ADD CONSTRAINT fk_holding_holding_cnpj FOREIGN KEY (holding_cnpj) REFERENCES empresa (cnpj);
5 | ALTER TABLE socio ADD CONSTRAINT fk_socio_cnpj FOREIGN KEY (cnpj) REFERENCES empresa (cnpj);
6 |
7 | -- CNAE
8 | ALTER TABLE empresa ADD CONSTRAINT fk_empresa_cnae_fiscal FOREIGN KEY (cnae_fiscal) REFERENCES cnae (id);
9 | ALTER TABLE cnae_cnpj ADD CONSTRAINT fk_cnae_cnpj_cnae FOREIGN KEY (cnae) REFERENCES cnae (id);
10 |
--------------------------------------------------------------------------------
/schema/empresa.csv:
--------------------------------------------------------------------------------
1 | field_name,field_type
2 | cnpj,text
3 | identificador_matriz_filial,integer
4 | razao_social,text
5 | nome_fantasia,text
6 | situacao_cadastral,integer
7 | data_situacao_cadastral,date
8 | motivo_situacao_cadastral,integer
9 | nome_cidade_exterior,text
10 | codigo_natureza_juridica,integer
11 | data_inicio_atividade,date
12 | cnae_fiscal,integer
13 | descricao_tipo_logradouro,text
14 | logradouro,text
15 | numero,text
16 | complemento,text
17 | bairro,text
18 | cep,integer
19 | uf,text
20 | codigo_municipio,integer
21 | municipio,text
22 | ddd_telefone_1,text
23 | ddd_telefone_2,text
24 | ddd_fax,text
25 | qualificacao_do_responsavel,integer
26 | capital_social,decimal
27 | porte,integer
28 | opcao_pelo_simples,bool
29 | data_opcao_pelo_simples,text
30 | data_exclusao_do_simples,text
31 | opcao_pelo_mei,bool
32 | situacao_especial,text
33 | data_situacao_especial,text
34 |
--------------------------------------------------------------------------------
/headers/cnae_secundaria.csv:
--------------------------------------------------------------------------------
1 | start_column,name,size,type,description
2 | 1,TIPO DE REGISTRO,1,A,CONTEM O VALOR 6 PARA IDENTIFICAR AS CNAES SECUNDÁRIAS
3 | 2,INDICADOR-FULL-DIARIO,1,A,"INDICA FORMA DE ENVIO DO ARQUIVO:
4 | F – QUANDO FULL
5 | D – QUANDO DIÁRIO
6 | M – QUANDO MENSAL
7 | T – QUANDO TRIMEST"
8 | 3,TIPO DE ATUALIZACAO,1,A,"NO CASO DE ENVIO FULL ESTE CAMPO ESTA EM BRANCO. NO CASO DE ENVIO PERÍODICO, TERÁ OS SEGUINTES DOMÍNIOS:
9 | A – ATUALIZAÇÃO DO ESTABELECIMENTO
10 | I – INCLUSÃO DE UM NOVO ESTABELECIMENTO
11 | E – EXCLUSÃO DO ESTABELECIMENTO"
12 | 4,CNPJ,14,A,CONTEM O NÚMERO DE INSCRIÇÃO NO CNPJ (CADASTRO NACIONAL DA PESSOA JURÍDICA).
13 | 18,CNAE,693,A,"TAMANHO DE CADA CNAE SECUNDÁRIA: 7
14 | OCORRÊNCIA =99. COMO SE TRATA DE UM ATRIBUTO OPCIONAL, QUANDO UMA DAS OCORRENCIAS NÃO FOR INFORMADA, ESTE ATRIBUTO ESTARA PREENCHIDO COM ZEROS."
15 | 711,FILLER,489,A,BRANCOS
16 | 1200,FIM,1,A,PREENCHIDO COM ‘F’ INDICANDO FINAL DE REGISTRO
17 |
--------------------------------------------------------------------------------
/schema-full/empresa.csv:
--------------------------------------------------------------------------------
1 | field_name,field_type
2 | cnpj,text
3 | identificador_matriz_filial,integer
4 | razao_social,text
5 | nome_fantasia,text
6 | situacao_cadastral,integer
7 | data_situacao_cadastral,date
8 | motivo_situacao_cadastral,integer
9 | nome_cidade_exterior,text
10 | codigo_pais,integer
11 | nome_pais,text
12 | codigo_natureza_juridica,integer
13 | data_inicio_atividade,date
14 | cnae_fiscal,integer
15 | descricao_tipo_logradouro,text
16 | logradouro,text
17 | numero,text
18 | complemento,text
19 | bairro,text
20 | cep,integer
21 | uf,text
22 | codigo_municipio,integer
23 | municipio,text
24 | ddd_telefone_1,text
25 | ddd_telefone_2,text
26 | ddd_fax,text
27 | correio_eletronico,text
28 | qualificacao_do_responsavel,integer
29 | capital_social,decimal
30 | porte,integer
31 | opcao_pelo_simples,bool
32 | data_opcao_pelo_simples,text
33 | data_exclusao_do_simples,text
34 | opcao_pelo_mei,bool
35 | situacao_especial,text
36 | data_situacao_especial,text
37 |
--------------------------------------------------------------------------------
/extract_cnae_cnpj.py:
--------------------------------------------------------------------------------
1 | import argparse
2 | import csv
3 |
4 | import rows
5 | from tqdm import tqdm
6 |
7 |
8 | def main():
9 | parser = argparse.ArgumentParser()
10 | parser.add_argument("empresa_csv_filename")
11 | parser.add_argument("cnae_secundaria_csv_filename")
12 | parser.add_argument("output_csv_filename")
13 | args = parser.parse_args()
14 |
15 | writer = rows.utils.CsvLazyDictWriter(args.output_csv_filename)
16 |
17 | fobj = rows.utils.open_compressed(args.empresa_csv_filename)
18 | reader = csv.DictReader(fobj)
19 | for row in tqdm(reader):
20 | writer.writerow({"cnpj": row["cnpj"], "cnae": row["cnae_fiscal"], "primaria": "t"})
21 | fobj.close()
22 |
23 | fobj = rows.utils.open_compressed(args.cnae_secundaria_csv_filename)
24 | reader = csv.DictReader(fobj)
25 | for row in tqdm(reader):
26 | writer.writerow({"cnpj": row["cnpj"], "cnae": row["cnae"], "primaria": "f"})
27 | fobj.close()
28 |
29 | writer.close()
30 |
31 |
32 | if __name__ == "__main__":
33 | main()
34 |
--------------------------------------------------------------------------------
/import-postgresql.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | SCHEMA_PATH="schema"
4 | OUTPUT_PATH="data/output"
5 |
6 | function import_table() {
7 | tablename="$1"
8 |
9 | echo "DROP TABLE IF EXISTS ${tablename};" | psql "$POSTGRESQL_URI"
10 | time rows pgimport \
11 | --schema="$SCHEMA_PATH/${tablename}.csv" \
12 | --input-encoding="utf-8" \
13 | --dialect="excel" \
14 | "$OUTPUT_PATH/${tablename}.csv.gz" \
15 | "$POSTGRESQL_URI" \
16 | "$tablename"
17 | }
18 |
19 | function import_cnae_tables() {
20 | schema="cnae"
21 | for filename in $OUTPUT_PATH/cnae_1*.csv* $OUTPUT_PATH/cnae_2*.csv*; do
22 | versao="$(basename $filename | sed 's/.csv.*//; s/cnae_//; s/\.//')"
23 | filename=$(basename $filename | sed 's/.csv.gz//')
24 | import_table $filename $schema "cnae_$versao"
25 | done
26 | }
27 |
28 | function execute_sql_files() {
29 | ## Execute SQL queries in sql/*.sql
30 | for filename in sql/*.sql; do
31 | echo "Executing ${filename}..."
32 | time cat $filename | psql $POSTGRESQL_URI
33 | done
34 | }
35 |
36 | import_table empresa
37 | import_table cnae_cnpj
38 | import_table socio
39 | import_table holding
40 | import_cnae_tables
41 | execute_sql_files
42 |
--------------------------------------------------------------------------------
/headers/socio.csv:
--------------------------------------------------------------------------------
1 | start_column,name,size,type,description
2 | 1,TIPO DE REGISTRO,1,N,CONTEM O VALOR 2 PARA IDENTIFICAR O REGISTRO DETALHE SOCIOS
3 | 2,INDICADOR-FULL-DIARIO,1,A,"INDICA FORMA DE ENVIO DO ARQUIVO :
4 | F – QUANDO FULL"
5 | 3,TIPO DE ATUALIZACAO,1,A,"NO CASO DE ENVIO FULL, ESTE CAMPO ESTA EM BRANCO."
6 | 4,CNPJ,14,A,CONTEM O NÚMERO DE INSCRIÇÃO NO CNPJ (CADASTRO NACIONAL DA PESSOA JURÍDICA).
7 | 18,IDENTIFICADOR DE SOCIO,1,N,"1 – PESSOA JURÍDICA
8 | 2 – PESSOA FISICA
9 | 3 – ESTRANGEIRO"
10 | 19,NOME SOCIO,150,A,"CORRESPONDE AO NOME SOCIO PESSOA FISICA, RAZÃO SOCIAL E/OU NOME EMPRESARIAL DA PESSOA JURÍDICA E NOME DO SÓCIO/RAZAO SOCIAL DO SOCIO ESTRANGEIRO"
11 | 169,CNPJ/CPF DO SOCIO,14,A,"É PREENCHIDO COM CPF OU CNPJ DO SOCIO, NO CASO DE SÓCIO ESTRANGEIRO É PREENCHIDO COM ‘NOVES’ O ALINHAMENTO PARA CPF É FORMATADO COM ZEROS À ESQUERDA."
12 | 183,CODIGO QUALIFICACAO SOCIO,2,A,CODIGO QUALIFICACAO SOCIO
13 | 185,PERCENTUAL CAPITAL SOCIAL,5,N,PERCENTUAL CAPITAL SOCIAL
14 | 190,DATA ENTRADA SOCIEDADE,8,N,DATA DE ENTRADA NA SOCIEDADE
15 | 198,CODIGO PAIS,3,A,CODIGO PAIS DO SOCIO ESTRANGEIRO
16 | 201,NOME PAIS,70,A,CORRESPONDE AO NOME DO PAIS DO SÓCIO
17 | 271,CPF REPRESENTANTE LEGAL,11,A,CORRESPONDE AO NÚMERO DO CPF DO REPRESENTANTE LEGAL
18 | 282,NOME REPRESENTANTE LEGAL,60,A,CORRESPONDE AO NOME DO REPRESENTANTE LEGAL
19 | 342,CODIGO QUALIFICACAO REPRESENTANTE LEGAL,2,A,CORRESPONDE AO CÓDIGO DA QUALIFICACAO DO REPRESENTANTE LEGAL
20 | 344,FILLER,855,A,BRANCOS
21 | 1199,CAMPO DESCONHECIDO,1,A,Essa parte do registro não veio especificada pela Receita Federal
22 | 1200,FIM,1,A,PREENCHIDO COM ‘F’ INDICANDO FINAL DE REGISTRO.
23 |
--------------------------------------------------------------------------------
/sql/03-create-indexes.sql:
--------------------------------------------------------------------------------
1 | CREATE INDEX IF NOT EXISTS idx_socio_cnpj ON socio (cnpj);
2 | CREATE INDEX IF NOT EXISTS idx_socio_documento_socio ON socio (cnpj_cpf_do_socio, cnpj);
3 | CREATE INDEX IF NOT EXISTS idx_holding_cnpjs ON holding (cnpj, holding_cnpj);
4 | CREATE INDEX IF NOT EXISTS idx_cnae_cnpj_cnpj ON cnae_cnpj (cnpj);
5 | CREATE INDEX IF NOT EXISTS idx_cnae_cnpj_cnae ON cnae_cnpj (cnae, primaria);
6 | CREATE INDEX IF NOT EXISTS idx_cnae_id ON cnae (id);
7 |
8 | CREATE INDEX IF NOT EXISTS idx_empresa_cnae_fiscal ON empresa (cnae_fiscal);
9 | CREATE INDEX IF NOT EXISTS idx_empresa_location ON empresa (uf, municipio, bairro);
10 | CREATE INDEX IF NOT EXISTS idx_empresa_mei_simples ON empresa (opcao_pelo_mei, opcao_pelo_simples);
11 | CREATE INDEX IF NOT EXISTS idx_empresa_situacao_cadastral ON empresa (situacao_cadastral);
12 |
13 | CREATE EXTENSION IF NOT EXISTS pg_trgm; -- PostgreSQL-only
14 | CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_trgm_socio_nome_socio ON socio USING gin (nome_socio gin_trgm_ops);
15 | CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_trgm_empresa_razao_social ON empresa USING gin (razao_social gin_trgm_ops);
16 | CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_trgm_empresa_nome_fantasia ON empresa USING gin (nome_fantasia gin_trgm_ops);
17 | CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_trgm_cnae_descricao ON cnae USING gin (descricao_subclasse gin_trgm_ops);
18 | CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_trgm_empresa_uf ON empresa USING gin (uf gin_trgm_ops);
19 | CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_trgm_empresa_municipio ON empresa USING gin (municipio gin_trgm_ops);
20 | CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_trgm_empresa_bairro ON empresa USING gin (bairro gin_trgm_ops);
21 |
--------------------------------------------------------------------------------
/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | DOWNLOAD_PATH=data/download
6 | OUTPUT_PATH=data/output
7 | mkdir -p $DOWNLOAD_PATH $OUTPUT_PATH
8 |
9 | if [ "$1" = "--use-mirror" ]; then
10 | USE_MIRROR=true
11 | else
12 | USE_MIRROR=false
13 | fi
14 |
15 | function download_data() {
16 | CONNECTIONS=4
17 | DOWNLOAD_URL="https://receita.economia.gov.br/orientacao/tributaria/cadastros/cadastro-nacional-de-pessoas-juridicas-cnpj/dados-publicos-cnpj"
18 | FILE_URLS=$(wget --quiet --no-check-certificate -O - "$DOWNLOAD_URL" \
19 | | grep --color=no DADOS_ABERTOS_CNPJ \
20 | | grep --color=no ".zip" \
21 | | sed 's/.*"http:/http:/; s/".*//' \
22 | | sort)
23 | MIRROR_URL="https://data.brasil.io/mirror/socios-brasil"
24 |
25 | for url in $FILE_URLS; do
26 | if $USE_MIRROR; then
27 | url="$MIRROR_URL/$(basename $url)"
28 | fi
29 | time aria2c \
30 | --auto-file-renaming=false \
31 | --continue=true \
32 | -s $CONNECTIONS \
33 | -x $CONNECTIONS \
34 | --dir="$DOWNLOAD_PATH" \
35 | "$url"
36 | done
37 | }
38 |
39 | function extract_data() {
40 | time python extract_dump.py $OUTPUT_PATH $DOWNLOAD_PATH/DADOS_ABERTOS_CNPJ*.zip
41 | time python extract_cnae_cnpj.py $OUTPUT_PATH/{empresa,cnae_secundaria,cnae_cnpj}.csv.gz
42 | }
43 |
44 | function extract_holding() {
45 | time python extract_holding.py $OUTPUT_PATH/{socio,empresa,holding}.csv.gz
46 | }
47 |
48 | function extract_cnae() {
49 | for versao in "1.0" "1.1" "2.0" "2.1" "2.2" "2.3"; do
50 | filename="$OUTPUT_PATH/cnae_$versao.csv"
51 | rm -rf "$filename"
52 | time scrapy runspider \
53 | -s RETRY_HTTP_CODES="500,503,504,400,404,408" \
54 | -s HTTPCACHE_ENABLED=true \
55 | --loglevel=INFO \
56 | -a versao="$versao" \
57 | -o "$filename" \
58 | cnae.py
59 | gzip "$filename"
60 | done
61 | }
62 |
63 | download_data
64 | extract_data
65 | extract_holding
66 | extract_cnae
67 |
--------------------------------------------------------------------------------
/natureza_juridica.py:
--------------------------------------------------------------------------------
1 | import csv
2 | import io
3 | from itertools import chain
4 |
5 | import requests
6 | import rows
7 |
8 |
9 | def clear_text(text):
10 | if text is None:
11 | return text
12 | text = text.replace("\t", " ").replace("\n", " ")
13 | while " " in text:
14 | text = text.replace(" ", " ")
15 | return text
16 |
17 |
18 | def extract_data():
19 | url = "https://www.receita.fazenda.gov.br/pessoajuridica/cnpj/tabelas/natjurqualificaresponsavel.htm"
20 | response = requests.get(url, verify=False)
21 | table_1 = rows.import_from_html(
22 | io.BytesIO(response.content), encoding=response.encoding, index=0, ignore_colspan=False
23 | )
24 | table_2 = rows.import_from_html(
25 | io.BytesIO(response.content), encoding=response.encoding, index=1, ignore_colspan=False
26 | )
27 |
28 | categoria, codigo_categoria = None, None
29 | for row in chain(table_1, table_2):
30 | row = {key: clear_text(value) for key, value in row._asdict().items()}
31 |
32 | codigo = row["codigo"]
33 | if ". " in codigo:
34 | categoria = codigo.title()
35 | split_index = categoria.find(". ")
36 | codigo_categoria, categoria = categoria[:split_index], categoria[split_index + 2 :]
37 | continue
38 | else:
39 | row["codigo"] = int(codigo.replace("-", ""))
40 | row["categoria"] = categoria
41 | row["codigo_categoria"] = codigo_categoria
42 | row["qualificacao"] = [item.strip() for item in row["qualificacao"].replace(" ou ", ", ").split(",")]
43 |
44 | yield row
45 |
46 |
47 | if __name__ == "__main__":
48 | filename = "data/natureza-juridica.csv"
49 |
50 | writer = None
51 | with open(filename, mode="w") as fobj:
52 | for row in extract_data():
53 | row["qualificacao"] = "|".join(row["qualificacao"])
54 | if writer is None:
55 | writer = csv.DictWriter(fobj, fieldnames=list(row.keys()))
56 | writer.writeheader()
57 | writer.writerow(row)
58 |
--------------------------------------------------------------------------------
/extract_holding.py:
--------------------------------------------------------------------------------
1 | from argparse import ArgumentParser
2 | from csv import DictReader
3 | from pathlib import Path
4 |
5 | import rows
6 | from rows.utils import CsvLazyDictWriter, open_compressed
7 | from tqdm import tqdm
8 |
9 | QUALIFICACAO_SOCIO = {row.codigo: row.descricao for row in rows.import_from_csv("qualificacao-socio.csv")}
10 |
11 |
12 | def convert_socio(row):
13 | codigo_qualificacao = int(row["codigo_qualificacao_socio"])
14 | return {
15 | "holding_cnpj": row["cnpj_cpf_do_socio"],
16 | "holding_razao_social": row["nome_socio"],
17 | "cnpj": row["cnpj"],
18 | "razao_social": "",
19 | "codigo_qualificacao_socia": codigo_qualificacao,
20 | "qualificacao_socia": QUALIFICACAO_SOCIO.get(codigo_qualificacao, None),
21 | }
22 |
23 |
24 | def convert_empresa(row):
25 | return {
26 | "cnpj": row["cnpj"],
27 | "razao_social": row["razao_social"],
28 | }
29 |
30 |
31 | def filter_csv(input_filename, filter_function, convert_function, progress=True):
32 | fobj_reader = open_compressed(input_filename, mode="r")
33 | csv_reader = DictReader(fobj_reader)
34 | if progress:
35 | csv_reader = tqdm(csv_reader, desc=f"Reading {Path(input_filename).name}")
36 | for row in csv_reader:
37 | if filter_function(row):
38 | yield convert_function(row)
39 | fobj_reader.close()
40 |
41 |
42 | def main():
43 | parser = ArgumentParser()
44 | parser.add_argument("socio_filename")
45 | parser.add_argument("empresa_filename")
46 | parser.add_argument("output_filename")
47 | args = parser.parse_args()
48 |
49 | holdings_it = filter_csv(
50 | args.socio_filename,
51 | lambda row: row["identificador_de_socio"] == "1",
52 | convert_function=convert_socio,
53 | progress=True,
54 | )
55 | holdings = {row["cnpj"]: row for row in holdings_it}
56 |
57 | cnpjs = set(holdings.keys())
58 | company_names_it = filter_csv(
59 | args.empresa_filename, lambda row: row["cnpj"] in cnpjs, convert_function=convert_empresa, progress=True,
60 | )
61 | company_names = {row["cnpj"]: row["razao_social"] for row in company_names_it}
62 |
63 | fobj_writer = open_compressed(args.output_filename, mode="w")
64 | csv_writer = CsvLazyDictWriter(fobj_writer)
65 | for holding in tqdm(holdings.values(), desc="Writting output file"):
66 | holding["razao_social"] = company_names.get(holding["cnpj"], "")
67 | csv_writer.writerow(holding)
68 | fobj_writer.close()
69 |
70 |
71 | if __name__ == "__main__":
72 | main()
73 |
--------------------------------------------------------------------------------
/qualificacao-socio.csv:
--------------------------------------------------------------------------------
1 | codigo,descricao,coletado_atualmente
2 | 1,Acionista,Não
3 | 2,Acionista Controlador,Não
4 | 3,Acionista Diretor,Não
5 | 4,Acionista Presidente,Não
6 | 5,Administrador,Sim
7 | 6,Administradora de consórcio de Empresas ou Grupo de Empresas,Não
8 | 7,Comissário,Não
9 | 8,Conselheiro de Administração,Sim
10 | 9,Curador,Sim
11 | 10,Diretor,Sim
12 | 11,Interventor,Sim
13 | 12,Inventariante,Sim
14 | 13,Liquidante,Sim
15 | 14,Mãe,Sim
16 | 15,Pai,Sim
17 | 16,Presidente,Sim
18 | 17,Procurador,Sim
19 | 18,Secretário,Sim
20 | 19,Síndico (Condomínio),Sim
21 | 20,Sociedade Consorciada,Sim
22 | 21,Sociedade Filiada,Sim
23 | 22,Sócio,Sim
24 | 23,Sócio Capitalista,Sim
25 | 24,Sócio Comanditado,Sim
26 | 25,Sócio Comanditário,Sim
27 | 26,Sócio de Indústria,Sim
28 | 27,Sócio Residente ou Domiciliado no Exterior,Não
29 | 28,Sócio-Gerente,Sim
30 | 29,Sócio ou Acionista Incapaz ou Relativamente Incapaz (exceto menor),Sim
31 | 30,Sócio ou Acionista Menor (Assistido/Representado),Sim
32 | 31,Sócio Ostensivo,Sim
33 | 32,Tabelião,Sim
34 | 33,Tesoureiro,Sim
35 | 34,Titular de Empresa Individual Imobiliária,Sim
36 | 35,Tutor,Sim
37 | 36,Gerente-Delegado,Não
38 | 37,Sócio Pessoa Jurídica Domiciliado no Exterior,Sim
39 | 38,Sócio Pessoa Física Residente ou Domiciliado no Exterior,Sim
40 | 39,Diplomata,Sim
41 | 40,Cônsul,Sim
42 | 41,Representante de Organização Internacional,Sim
43 | 42,Oficial de Registro,Sim
44 | 43,Responsável,Sim
45 | 44,Sócio Participante,Não
46 | 45,Sócio Investidor,Não
47 | 46,Ministro de Estado das Relações Exteriores,Sim
48 | 47,Sócio Pessoa Física Residente no Brasil,Sim
49 | 48,Sócio Pessoa Jurídica Domiciliado no Brasil,Sim
50 | 49,Sócio-Administrador,Sim
51 | 50,Empresário,Sim
52 | 51,Candidato a Cargo Político Eletivo,Sim
53 | 52,Sócio com Capital,Sim
54 | 53,Sócio sem Capital,Sim
55 | 54,Fundador,Sim
56 | 55,Sócio Comanditado Residente no Exterior,Sim
57 | 56,Sócio Comanditário Pessoa Física Residente no Exterior,Sim
58 | 57,Sócio Comanditário Pessoa Jurídica Domiciliado no Exterior,Sim
59 | 58,Sócio Comanditário Incapaz,Sim
60 | 59,Produtor Rural,Sim
61 | 60,Cônsul Honorário,Sim
62 | 61,Responsável Indigena,Sim
63 | 62,Representante das Instituições Extraterritoriais,Sim
64 | 63,Cotas em Tesouraria,Sim
65 | 64,Administrador Judicial,Sim
66 | 65,Titular Pessoa Física Residente ou Domiciliado no Brasil,Sim
67 | 66,Titular Pessoa Física Residente ou Domiciliado no Exterior,Sim
68 | 67,Titular Pessoa Física Incapaz ou Relativamente Incapaz (exceto menor),Sim
69 | 68,Titular Pessoa Física Menor (Assistido/Representado),Sim
70 | 69,Beneficiário Final,Não
71 | 70,Administrador Residente ou Domiciliado no Exterior,Sim
72 | 71,Conselheiro de Administração Residente ou Domiciliado no Exterior,Sim
73 | 72,Diretor Residente ou Domiciliado no Exterior,Sim
74 | 73,Presidente Residente ou Domiciliado no Exterior,Sim
75 | 74,Sócio-Administrador Residente ou Domiciliado no Exterior,Sim
76 | 75,Fundador Residente ou Domiciliado no Exterior,Sim
77 | 76,Protetor,Sim
78 | 77,Vice-Presidente,Sim
79 | 78,Titular Pessoa Jurídica Domiciliada no Brasil,Sim
80 | 79,Titular Pessoa Jurídica Domiciliada no Exterior,Sim
81 |
--------------------------------------------------------------------------------
/headers/empresa.csv:
--------------------------------------------------------------------------------
1 | start_column,name,size,type,description
2 | 1,TIPO DE REGISTRO,1,N,CONTEM O VALOR 1 PARA IDENTIFICAR O REGISTRO
3 | 2,INDICADOR-FULL-DIARIO,1,A,"INDICA FORMA DE ENVIO DO ARQUIVO:
4 | F – QUANDO FULL"
5 | 3,TIPO DE ATUALIZACAO,1,A,NO CASO DE ENVIO FULL ESTE CAMPO ESTA EM BRANCO.
6 | 4,CNPJ,14,A,CONTEM O NÚMERO DE INSCRIÇÃO NO CNPJ (CADASTRO NACIONAL DA PESSOA JURÍDICA).
7 | 18,IDENTIFICADOR MATRIZ/FILIAL,1,N,"1: MATRIZ
8 | 2: FILIAL"
9 | 19,RAZAO SOCIAL,150,A,CORRESPONDE AO NOME EMPRESARIAL DA PESSOA JURÍDICA
10 | 169,NOME FANTASIA,55,A,CORRESPONDE AO NOME FANTASIA
11 | 224,SITUACAO CADASTRAL,2,N,"2 DIGITOS CÓDIGO DA SITUAÇÃO CADASTRAL
12 | 01: NULA
13 | 02: ATIVA
14 | 03: SUSPENSA
15 | 04: INAPTA
16 | 08: BAIXADA"
17 | 226,DATA SITUACAO CADASTRAL,8,N,DATA DO EVENTO DA SITUACAO CADASTRAL
18 | 234,MOTIVO SITUACAO CADASTRAL,2,N,CÓDIGO DO MOTIVO DA SITUAÇÃO CADASTRAL
19 | 236,NOME CIDADE EXTERIOR,55,A,NOME DA CIDADE NO EXTERIOR
20 | 291,CODIGO PAIS,3,A,CODIGO DO PAIS
21 | 294,NOME PAIS,70,A,NOME DO PAIS
22 | 364,CODIGO NATUREZA JURIDICA,4,N,CÓDIGO DA NATUREZA JURÍDICA
23 | 368,DATA INICIO ATIVIDADE,8,N,DATA DE INICIO DA ATIVIDADE
24 | 376,CNAE-FISCAL,7,N,INDICA O CÓDIGO DA ATIVIDADE ECONÔMICA PRINCIPAL DO ESTABELECIMENTO
25 | 383,DESCRICAO TIPO LOGRADOURO,20,A,CORRESPONDE A DESCRIÇÃO DO LOGRADOURO
26 | 403,LOGRADOURO,60,A,CORRESPONDE AO NOME DO LOGRADOURO ONDE SE LOCALIZA O ESTABELECIMENTO.
27 | 463,NUMERO,6,A,"CORRESPONDE AO NÚMERO ONDE SE LOCALIZA O ESTABELECIMENTO, QUANDO NÃO HOUVER PREENCHIMENTO DO NÚMERO HAVERÁ ‘S/N’."
28 | 469,COMPLEMENTO,156,A,CORRESPONDE AO COMPLEMENTO PARA O ENDEREÇO DE LOCALIZAÇÃO DO ESTABELECIMENTO
29 | 625,BAIRRO,50,A,CORRESPONDE AO BAIRRO ONDE SE LOCALIZA O ESTABELECIMENTO.
30 | 675,CEP,8,N,CÓDIGO DE ENDEREÇAMENTO POSTAL REFERENTE AO LOGRADOURO NO QUAL O ESTABELECIMENTO ESTA LOCALIZADO
31 | 683,UF,2,A,CORRESPONDE A SIGLA DA UNIDADE DA FEDERAÇÃO EM QUE SE ENCONTRA O ESTABELECIMENTO
32 | 685,CODIGO MUNICIPIO,4,N,CORRESPONDE AO CODIGO DO MUNICIPIO DE JURISDIÇÃO ONDE SE ENCONTRA O ESTABELECIMENTO
33 | 689,MUNICIPIO,50,A,CORRESPONDE AO MUNICIPIO DE JURISDIÇÃO ONDE SE ENCONTRA O ESTABELECIMENTO
34 | 739,DDD-TELEFONE-1,12,A,"DDD-1: tamanho 4, TELEFONE-1: tamanho 8"
35 | 751,DDD-TELEFONE-2,12,A,"DDD-2: tamanho 4, TELEFONE-2: tamanho 8"
36 | 763,DDD-FAX,12,A,"DDD-FAX: tamanho 4, TELEFONE-FAX: tamanho 8"
37 | 775,CORREIO ELETRONICO,115,A,E-MAIL DO CONTRIBUINTE
38 | 890,QUALIFICACAO DO RESPONSAVEL,2,N,QUALIFICAÇÃO DA PESSOA FÍSICA RESPONSÁVEL PELA EMPRESA
39 | 892,CAPITAL SOCIAL,14,N,CAPITAL SOCIAL DA EMPRESA
40 | 906,PORTE,2,A,"CÓDIGO DO PORTE DA EMPRESA
41 | 00: NAO INFORMADO
42 | 01: MICRO EMPRESA
43 | 03: EMPRESA DE PEQUENO PORTE
44 | 05: DEMAIS"
45 | 908,OPCAO PELO SIMPLES,1,A,"INDICADOR DA EXISTÊNCIA DA OPÇÃO PELO SIMPLES.
46 | 0 OU BRANCO: NÃO OPTANTE
47 | 5 E 7: OPTANTE PELO SIMPLES
48 | 6 E 8: EXCLUÍDO DO SIMPLES"
49 | 909,DATA OPCAO PELO SIMPLES,8,N,DATA DE OPÇÃO PELO SIMPLES
50 | 917,DATA EXCLUSAO DO SIMPLES,8,N,DATA DE EXCLUSÃO DO SIMPLES
51 | 925,OPCAO PELO MEI,1,A,"INDICADOR DA EXISTÊNCIA DA OPÇÃO PELO MEI
52 | S: SIM
53 | N: NÃO"
54 | 926,SITUACAO ESPECIAL,23,A,SITUACAO ESPECIAL DA EMPRESA
55 | 949,DATA SITUACAO ESPECIAL,8,N,DATA EM QUE A EMPRESA ENTROU EM SITUACAO ESPECIAL (AAAAMMDD)
56 | 957,FILLER,243,A,BRANCOS
57 | 1200,FIM,1,A,CONTEÚDO 'F' - INDICA FINAL DE REGISTRO
58 |
--------------------------------------------------------------------------------
/historia-do-dataset.md:
--------------------------------------------------------------------------------
1 | # História do dataset socios-brasil
2 |
3 | 1. No fim de 2017 a Receita Federal do Brasil [liberou um dataset com os sócios
4 | das empresas
5 | brasileiras](http://receita.economia.gov.br/orientacao/tributaria/cadastros/cadastro-nacional-de-pessoas-juridicas-cnpj/dados-abertos-do-cnpj);
6 |
7 | 2. No início de 2018 [criei um programa para baixar, corrigir e converter os
8 | dados para CSV](https://github.com/turicas/socios-brasil/);
9 |
10 | 3. No meio de 2018 fiz junto com alguns amigos um pedido de acesso à informação
11 | para termos mais dados, como identificador único dos sócios (CPF) e CNAEs
12 | das empresas - [parte da história foi contada
13 | aqui](https://medium.com/serenata/o-dia-que-a-receita-nos-mandou-pagar-r-500-mil-para-ter-dados-públicos-8e18438f3076).
14 |
15 | 4. Meses (e vários recursos no pedido via LAI) depois, recebemos um pendrive
16 | pelos Correios com os dados e eu criei um outro script (dentro do mesmo
17 | repositório de código no GitHub) para converter os novos dados para CSV - o
18 | formato é parecido, mas diferente, dado que tem informações diferentes; eu
19 | subi os dados originais e convertidos para o Google Drive e compartilhei o
20 | link publicamente, para que todos pudessem acessar os dados sem precisar
21 | fazer o pedido como fizemos (afinal, é dado público). Nota: outras pessoas
22 | também fizeram pedidos similares e receberam o dump.
23 |
24 | 5. Um tempo depois que recebemos o pendrive a [RFB liberou no próprio site os
25 | arquivos](http://receita.economia.gov.br/orientacao/tributaria/cadastros/cadastro-nacional-de-pessoas-juridicas-cnpj/dados-publicos-cnpj),
26 | atualizei então o programa para baixar essa nova versão dos dados do site e
27 | convertê-los.
28 |
29 | 6. Após rodar análises nos dados, percebi que a residência dos sócios está
30 | errada - [veja mais
31 | detalhes](https://gist.github.com/turicas/ad0ab80b8eb3337dafb62fcfd2924ccd)
32 | - e criei um chamado na Ouvidoria do Ministério da Economia alertando sobre
33 | o problema; responderam dizendo que passariam para o setor técnico, mas
34 | depois nunca mais me atualizaram sobre.
35 |
36 | 7. No dia 05 de abril de 2019 tentei baixar o arquivo (que parece ter sido
37 | atualizado) no site deles e a previsão de download era de 4 dias; consegui
38 | baixar em umas 7h porque fiz o download em paralelo de diversos servidores
39 | do [Brasil.IO](https://brasil.io/). Abri um protocolo na ouvidoria com a
40 | reclamação e a resposta não foi muito diferente da resposta do item
41 | anterior. Deixei então os dados originais disponíveis para download no
42 | Brasil.IO (além dos dados convertidos), assim quem quiser acesso ao dado
43 | original poderá baixá-lo de 10 a 100x mais rápido que usando o site da RFB -
44 | [veja mais
45 | detalhes](https://twitter.com/turicas/status/1114185311372873729).
46 |
47 | 8. A partir de então, toda vez que a RFB atualiza os dados em seu site eu faço
48 | o processo de baixar os arquivos originais, deixá-los disponíveis para
49 | download mais rápido no Brasil.IO, converter os dados para CSV e também
50 | disponibilizar os dados já convertidos. Em geral publico no Twitter - [veja
51 | a última conversão que
52 | fiz](https://twitter.com/turicas/status/1197125153047662592). Ainda preciso
53 | adicionar esses detalhes na [página desse dataset no
54 | Brasil.IO](https://brasil.io/dataset/socios-brasil).
55 |
--------------------------------------------------------------------------------
/data/natureza-juridica.csv:
--------------------------------------------------------------------------------
1 | codigo,natureza_juridica,representante_da_entidade,qualificacao,categoria,codigo_categoria
2 | 1015,Órgão Público do Poder Executivo Federal,Administrador,05,Administração Pública,1
3 | 1023,Órgão Público do Poder Executivo Estadual ou do Distrito Federal,Administrador,05,Administração Pública,1
4 | 1031,Órgão Público do Poder Executivo Municipal,Administrador,05,Administração Pública,1
5 | 1040,Órgão Público do Poder Legislativo Federal,Administrador,05,Administração Pública,1
6 | 1058,Órgão Público do Poder Legislativo Estadual ou do Distrito Federal,Administrador,05,Administração Pública,1
7 | 1066,Órgão Público do Poder Legislativo Municipal,Administrador,05,Administração Pública,1
8 | 1074,Órgão Público do Poder Judiciário Federal,Administrador,05,Administração Pública,1
9 | 1082,Órgão Público do Poder Judiciário Estadual,Administrador,05,Administração Pública,1
10 | 1104,Autarquia Federal,Administrador ou Presidente,05|16,Administração Pública,1
11 | 1112,Autarquia Estadual ou do Distrito Federal,Administrador ou Presidente,05|16,Administração Pública,1
12 | 1120,Autarquia Municipal,Administrador ou Presidente,05|16,Administração Pública,1
13 | 1139,Fundação Federal,Presidente,16,Administração Pública,1
14 | 1147,Fundação Estadual ou do Distrito Federal,Presidente,16,Administração Pública,1
15 | 1155,Fundação Municipal,Presidente,16,Administração Pública,1
16 | 1163,Órgão Público Autônomo Federal,Administrador,05,Administração Pública,1
17 | 1171,Órgão Público Autônomo Estadual ou do Distrito Federal,Administrador,05,Administração Pública,1
18 | 1180,Órgão Público Autônomo Municipal,Administrador,05,Administração Pública,1
19 | 1198,Comissão Polinacional,Administrador,05,Administração Pública,1
20 | 1201,Fundo Público,Administrador,05,Administração Pública,1
21 | 1210,Associação Pública,Presidente,16,Administração Pública,1
22 | 2011,Empresa Pública,"Administrador, Diretor ou Presidente",05|10|16,Entidades Empresariais,2
23 | 2038,Sociedade de Economia Mista,Diretor ou Presidente,10|16,Entidades Empresariais,2
24 | 2046,Sociedade Anônima Aberta,"Administrador, Diretor ou Presidente",05|10|16,Entidades Empresariais,2
25 | 2054,Sociedade Anônima Fechada,"Administrador, Diretor ou Presidente",05|10|16,Entidades Empresariais,2
26 | 2062,Sociedade Empresária Limitada,Administrador ou Sócio-Administrador,05|49,Entidades Empresariais,2
27 | 2070,Sociedade Empresária em Nome Coletivo,Sócio-Administrador,49,Entidades Empresariais,2
28 | 2089,Sociedade Empresária em Comandita Simples,Sócio Comanditado,24,Entidades Empresariais,2
29 | 2097,Sociedade Empresária em Comandita por Ações,Diretor ou Presidente,10|16,Entidades Empresariais,2
30 | 2127,Sociedade em Conta de Participação,Procurador ou Sócio Ostensivo,17|31,Entidades Empresariais,2
31 | 2135,Empresário (Individual),Empresário,50,Entidades Empresariais,2
32 | 2143,Cooperativa,Diretor ou Presidente,10|16,Entidades Empresariais,2
33 | 2151,Consórcio de Sociedades,Administrador,05,Entidades Empresariais,2
34 | 2160,Grupo de Sociedades,Administrador,05,Entidades Empresariais,2
35 | 2178,"Estabelecimento, no Brasil, de Sociedade Estrangeira",Procurador,17,Entidades Empresariais,2
36 | 2194,"Estabelecimento, no Brasil, de Empresa Binacional Argentino-Brasileira",Procurador,17,Entidades Empresariais,2
37 | 2216,Empresa Domiciliada no Exterior,Procurador,17,Entidades Empresariais,2
38 | 2224,Clube/Fundo de Investimento,Responsável,43,Entidades Empresariais,2
39 | 2232,Sociedade Simples Pura,Administrador ou Sócio-Administrador,05|49,Entidades Empresariais,2
40 | 2240,Sociedade Simples Limitada,Administrador ou Sócio-Administrador,05|49,Entidades Empresariais,2
41 | 2259,Sociedade Simples em Nome Coletivo,Sócio-Administrador,49,Entidades Empresariais,2
42 | 2267,Sociedade Simples em Comandita Simples,Sócio Comanditado,24,Entidades Empresariais,2
43 | 2275,Empresa Binacional,Diretor,10,Entidades Empresariais,2
44 | 2283,Consórcio de Empregadores,Administrador,05,Entidades Empresariais,2
45 | 2291,Consórcio Simples,Administrador,05,Entidades Empresariais,2
46 | 2305,Empresa Individual de Responsabilidade Limitada (de Natureza Empresária),"Administrador, Procurador ou Titular Pessoa Física Residente ou Domiciliado no Brasil",05|17|65,Entidades Empresariais,2
47 | 2313,Empresa Individual de Responsabilidade Limitada (de Natureza Simples),"Administrador, Procurador ou Titular Pessoa Física Residente ou Domiciliado no Brasil",05|17|65,Entidades Empresariais,2
48 | 3034,Serviço Notarial e Registral (Cartório),Tabelião ou Oficial de Registro,32|42,Entidades Sem Fins Lucrativos,3
49 | 3069,Fundação Privada,"Administrador, Diretor, Presidente ou Fundador",05|10|16|54,Entidades Sem Fins Lucrativos,3
50 | 3077,Serviço Social Autônomo,Administrador,05,Entidades Sem Fins Lucrativos,3
51 | 3085,Condomínio Edilício,Administrador ou Síndico (Condomínio),05|19,Entidades Sem Fins Lucrativos,3
52 | 3107,Comissão de Conciliação Prévia,Administrador,05,Entidades Sem Fins Lucrativos,3
53 | 3115,Entidade de Mediação e Arbitragem,Administrador,05,Entidades Sem Fins Lucrativos,3
54 | 3123,Partido Político,Administrador ou Presidente,05|16,Entidades Sem Fins Lucrativos,3
55 | 3131,Entidade Sindical,Administrador ou Presidente,05|16,Entidades Sem Fins Lucrativos,3
56 | 3204,"Estabelecimento, no Brasil, de Fundação ou Associação Estrangeiras",Procurador,17,Entidades Sem Fins Lucrativos,3
57 | 3212,Fundação ou Associação domiciliada no exterior,Procurador,17,Entidades Sem Fins Lucrativos,3
58 | 3220,Organização Religiosa,"Administrador, Diretor ou Presidente",05|10|16,Entidades Sem Fins Lucrativos,3
59 | 3239,Comunidade Indígena,Responsável Indígena,61,Entidades Sem Fins Lucrativos,3
60 | 3247,Fundo Privado,Administrador,05,Entidades Sem Fins Lucrativos,3
61 | 3999,Associação Privada,"Administrador, Diretor ou Presidente",05|10|16,Entidades Sem Fins Lucrativos,3
62 | 4014,Empresa Individual Imobiliária,Titular,34,Pessoas Físicas,4
63 | 4081,Contribuinte Individual,Produtor Rural,59,Pessoas Físicas,4
64 | 4090,Candidato a Cargo Político Eletivo,Candidato a Cargo Político Eletivo,51,Pessoas Físicas,4
65 | 5010,Organização Internacional,Representante de Organização Internacional,41,Instituições Extraterritoriais,5
66 | 5029,Representação Diplomática Estrangeira,"Diplomata, Cônsul, Ministro de Estado das Relações Exteriores ou Cônsul Honorário",39|40|46|60,Instituições Extraterritoriais,5
67 | 5037,Outras Instituições Extraterritoriais,Representante da Instituição Extraterritorial,62,Instituições Extraterritoriais,5
68 |
--------------------------------------------------------------------------------
/cnae.py:
--------------------------------------------------------------------------------
1 | from urllib.parse import urljoin
2 |
3 | import scrapy
4 | from lxml.html import document_fromstring
5 |
6 |
7 | def get_text(lines):
8 | """Return text from "text()" XPath result as a string, removing whitespaces
9 |
10 | >>> get_text(["\\t", " teste ", "\\n", "123 "])
11 | 'teste 123'
12 | """
13 |
14 | return " ".join([line.strip() for line in lines if line.strip()])
15 |
16 |
17 | class CNAESpider(scrapy.Spider):
18 | name = "cnae"
19 | versoes = {
20 | "1.0": "https://cnae.ibge.gov.br/?option=com_cnae&view=estrutura&Itemid=6160&tipo=cnae&versao_classe=1.0.1&versao_subclasse=2.1.1",
21 | "1.1": "https://cnae.ibge.gov.br/?option=com_cnae&view=estrutura&Itemid=6160&tipo=cnae&versao_classe=3.0.1&versao_subclasse=4.1.1",
22 | "2.0": "https://cnae.ibge.gov.br/?option=com_cnae&view=estrutura&Itemid=6160&tipo=cnae&versao_classe=5.0.1&versao_subclasse=6.1.1",
23 | "2.1": "https://cnae.ibge.gov.br/?option=com_cnae&view=estrutura&Itemid=6160&tipo=cnae&versao_classe=7.0.0&versao_subclasse=8.1.1",
24 | "2.2": "https://cnae.ibge.gov.br/?option=com_cnae&view=estrutura&Itemid=6160&tipo=cnae&versao_classe=7.0.0&versao_subclasse=9.1.1",
25 | "2.3": "https://cnae.ibge.gov.br/?option=com_cnae&view=estrutura&Itemid=6160&tipo=cnae&versao_classe=7.0.0&versao_subclasse=10.1.0",
26 | }
27 | parsers = {
28 | "root": {
29 | "xpath_items": "//table[@id = 'tbEstrutura']/tbody/tr",
30 | "xpath_id": ".//td[1]/a/text()",
31 | "xpath_description": ".//td[3]/text()",
32 | "xpath_url": ".//td[1]/a/@href",
33 | "next": "secao",
34 | },
35 | "secao": {
36 | "id_length": 1,
37 | "xpath_items": "//table[@class = 'tabela-hierarquia']//td[a[contains(@href, 'divisao=')]]",
38 | "xpath_id": ".//a/text()",
39 | "xpath_description": ".//text()",
40 | "xpath_url": ".//a/@href",
41 | "next": "divisao",
42 | },
43 | "divisao": {
44 | "id_length": 2,
45 | "xpath_items": "//table[@class = 'tabela-hierarquia']//td[a[contains(@href, 'grupo=')]]",
46 | "xpath_id": ".//a/text()",
47 | "xpath_description": ".//text()",
48 | "xpath_url": ".//a/@href",
49 | "next": "grupo",
50 | },
51 | "grupo": {
52 | "id_length": 4,
53 | "xpath_items": "//table[@class = 'tabela-hierarquia']//td[a[contains(@href, 'classe=')]]",
54 | "xpath_id": ".//a/text()",
55 | "xpath_description": ".//text()",
56 | "xpath_url": ".//a/@href",
57 | "next": "classe",
58 | },
59 | "classe": {
60 | "id_length": 7,
61 | "xpath_items": "//table[@class = 'tabela-hierarquia']//td[a[contains(@href, 'subclasse=')]]",
62 | "xpath_id": ".//a/text()",
63 | "xpath_description": ".//text()",
64 | "xpath_url": ".//a/@href",
65 | "next": "subclasse",
66 | },
67 | }
68 |
69 | def __init__(self, versao=None, *args, **kwargs):
70 | super().__init__(*args, **kwargs)
71 | if not versao:
72 | raise ValueError("O parâmetro 'versao' é obrigatório")
73 | self.versao = versao
74 |
75 | def start_requests(self):
76 | return [scrapy.Request(url=self.versoes[self.versao], callback=self.parse)]
77 |
78 | def parse(self, response):
79 | yield {
80 | "id_subclasse": "8888-8/88",
81 | "descricao_subclasse": "Não identificada",
82 | "id_classe": "88.88-8",
83 | "descricao_classe": "Não identificada",
84 | "id_grupo": "88.8",
85 | "descricao_grupo": "Não identificado",
86 | "id_divisao": "88",
87 | "descricao_divisao": "Não identificada",
88 | "id_secao": "8",
89 | "descricao_secao": "Não identificada",
90 | "notas_explicativas": "",
91 | "url": "",
92 | "id": 8888888,
93 | "versao": self.versao,
94 | }
95 | yield from self.parse_items(response, root_name="root")
96 |
97 | def parse_items(self, response, root_name=None):
98 | """Recursively get data/make requests for all parser hierarchical levels"""
99 |
100 | data = response.request.meta.get("data", {})
101 | root_name = root_name or response.request.meta["root_name"]
102 | metadata = self.parsers[root_name]
103 | xpath_id = metadata["xpath_id"]
104 | xpath_description = metadata["xpath_description"]
105 | xpath_url = metadata["xpath_url"]
106 | item_name = metadata["next"]
107 | for item in response.xpath(metadata["xpath_items"]):
108 | tree = document_fromstring(item.extract())
109 | url = urljoin("https://cnae.ibge.gov.br/", tree.xpath(xpath_url)[0])
110 | item_id = get_text(tree.xpath(xpath_id))
111 | item_description = get_text(tree.xpath(xpath_description))
112 | item_data = {}
113 | if item_name == "subclasse" or len(item_id) == self.parsers[item_name]["id_length"]:
114 | next_root_name = item_name
115 | else:
116 | descricao = response.xpath("//span[@class = 'destaque']//text()").extract()[0]
117 | item_data[f"id_{item_name}"] = descricao.split()[0]
118 | item_data[f"descricao_{item_name}"] = descricao
119 | next_root_name = self.parsers[item_name]["next"]
120 | item_data.update(
121 | {f"id_{next_root_name}": item_id.strip(), f"descricao_{next_root_name}": item_description.strip(),}
122 | )
123 | item_data.update(data)
124 |
125 | callback = self.parse_items if next_root_name != "subclasse" else self.parse_subclasse
126 | yield scrapy.Request(
127 | url=url, meta={"data": item_data, "root_name": next_root_name}, callback=callback,
128 | )
129 |
130 | def parse_subclasse(self, response):
131 | """Yield the subclass item (last mile of the recursive strategy)"""
132 | data = response.request.meta["data"]
133 | tree = document_fromstring(response.body)
134 | data["notas_explicativas"] = "\n".join(
135 | [line.strip() for line in tree.xpath('//div[@id = "notas-explicativas"]//text()') if line.strip()]
136 | )
137 | data["url"] = response.request.url
138 | data["id"] = int(data["id_subclasse"].replace("/", "").replace("-", ""))
139 | data["versao"] = self.versao
140 | yield data
141 |
--------------------------------------------------------------------------------
/test_parser.py:
--------------------------------------------------------------------------------
1 | from extract_dump import parse_row, read_header
2 |
3 | headers = {
4 | "1": read_header("headers/empresa.csv"),
5 | "2": read_header("headers/socio.csv"),
6 | "6": read_header("headers/cnae_secundaria.csv"),
7 | }
8 |
9 |
10 | def test_empresa():
11 | data = "1F 000000000001911BANCO DO BRASIL SA DIRECAO GERAL 022005110300 2038196608016422100QUADRA SAUN QUADRA 5 LOTE B TORRES I, II E III SN ANDAR 1 A 16 SALA 101 A 1601 ANDAR 1 A 16 SALA 101 A 1601 ANDAR 1 A 16 SALA 101 A 1601 ASA NORTE 70040912DF9701BRASILIA 61 34939002 61 34931040SECEX@BB.COM.BR 10060000000000000500000000000000000N F\n"
12 | header = headers[data[0]]
13 | result = parse_row(header, data)
14 | expected = {
15 | "bairro": "ASA NORTE",
16 | "capital_social": 6000000000000,
17 | "cep": 70040912,
18 | "cnae_fiscal": 6422100,
19 | "cnpj": "00000000000191",
20 | "codigo_municipio": 9701,
21 | "codigo_natureza_juridica": 2038,
22 | "codigo_pais": "",
23 | "complemento": "ANDAR 1 A 16 SALA 101 A 1601 ANDAR 1 A 16 SALA 101 A 1601 ANDAR 1 A 16 SALA 101 A 1601",
24 | "correio_eletronico": "SECEX@BB.COM.BR",
25 | "data_exclusao_do_simples": "",
26 | "data_inicio_atividade": "1966-08-01",
27 | "data_opcao_pelo_simples": "",
28 | "data_situacao_cadastral": "2005-11-03",
29 | "data_situacao_especial": None,
30 | "ddd_fax": "61 34931040",
31 | "ddd_telefone_1": "61 34939002",
32 | "ddd_telefone_2": "",
33 | "descricao_tipo_logradouro": "QUADRA",
34 | "identificador_matriz_filial": 1,
35 | "logradouro": "SAUN QUADRA 5 LOTE B TORRES I, II E III",
36 | "motivo_situacao_cadastral": 0,
37 | "municipio": "BRASILIA",
38 | "nome_cidade_exterior": "",
39 | "nome_fantasia": "DIRECAO GERAL",
40 | "nome_pais": "",
41 | "numero": "SN",
42 | "opcao_pelo_mei": "N",
43 | "opcao_pelo_simples": "0",
44 | "porte": "05",
45 | "qualificacao_do_responsavel": 10,
46 | "razao_social": "BANCO DO BRASIL SA",
47 | "situacao_cadastral": 2,
48 | "situacao_especial": "",
49 | "uf": "DF",
50 | }
51 | assert result == expected
52 |
53 |
54 | def test_socio():
55 | data = "2F 000000000001912MARCIO HAMILTON FERREIRA 000***923641**100000020101117249ESTADOS UNIDOS ***000000**CPF INVALIDO 00 F\n"
56 | header = headers[data[0]]
57 | result = parse_row(header, data)
58 | expected = {
59 | "campo_desconhecido": "",
60 | "cnpj": "00000000000191",
61 | "cnpj_cpf_do_socio": "000***923641**",
62 | "codigo_pais": "249",
63 | "codigo_qualificacao_representante_legal": "00",
64 | "codigo_qualificacao_socio": "10",
65 | "cpf_representante_legal": "***000000**",
66 | "data_entrada_sociedade": "2010-11-17",
67 | "identificador_de_socio": 2,
68 | "nome_pais_socio": "ESTADOS UNIDOS",
69 | "nome_representante_legal": "CPF INVALIDO",
70 | "nome_socio": "MARCIO HAMILTON FERREIRA",
71 | "percentual_capital_social": 0,
72 | }
73 | assert result == expected
74 |
75 |
76 | def test_cnae_secundaria():
77 | data = "6F 00000000000191649999900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 F\n"
78 | header = headers[data[0]]
79 | result = parse_row(header, data)
80 | expected = {
81 | "cnae": "649999900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
82 | "cnpj": "00000000000191",
83 | }
84 |
85 | assert result == expected
86 |
--------------------------------------------------------------------------------
/sql/04-create-old-views.sql:
--------------------------------------------------------------------------------
1 | CREATE VIEW view_socios AS
2 | SELECT
3 | s.cnpj AS cnpj,
4 | e.razao_social AS razao_social,
5 | s.cnpj_cpf_do_socio AS cpf_cnpj_socio,
6 | s.nome_socio AS nome_socio,
7 | s.codigo_qualificacao_socio AS codigo_qualificacao_socio,
8 | s.identificador_de_socio AS codigo_tipo_socio,
9 | CASE
10 | WHEN s.identificador_de_socio = 1 THEN 'Pessoa Jurídica'
11 | WHEN s.identificador_de_socio = 2 THEN 'Pessoa Física'
12 | WHEN s.identificador_de_socio = 3 THEN 'Nome Exterior'
13 | ELSE NULL
14 | END AS tipo_socio,
15 | CASE
16 | WHEN s.codigo_qualificacao_socio = 1 THEN 'Acionista'
17 | WHEN s.codigo_qualificacao_socio = 2 THEN 'Acionista Controlador'
18 | WHEN s.codigo_qualificacao_socio = 3 THEN 'Acionista Diretor'
19 | WHEN s.codigo_qualificacao_socio = 4 THEN 'Acionista Presidente'
20 | WHEN s.codigo_qualificacao_socio = 5 THEN 'Administrador'
21 | WHEN s.codigo_qualificacao_socio = 6 THEN 'Administradora de consórcio de Empresas ou Grupo de Empresas'
22 | WHEN s.codigo_qualificacao_socio = 7 THEN 'Comissário'
23 | WHEN s.codigo_qualificacao_socio = 8 THEN 'Conselheiro de Administração'
24 | WHEN s.codigo_qualificacao_socio = 9 THEN 'Curador'
25 | WHEN s.codigo_qualificacao_socio = 10 THEN 'Diretor'
26 | WHEN s.codigo_qualificacao_socio = 11 THEN 'Interventor'
27 | WHEN s.codigo_qualificacao_socio = 12 THEN 'Inventariante'
28 | WHEN s.codigo_qualificacao_socio = 13 THEN 'Liquidante'
29 | WHEN s.codigo_qualificacao_socio = 14 THEN 'Mãe'
30 | WHEN s.codigo_qualificacao_socio = 15 THEN 'Pai'
31 | WHEN s.codigo_qualificacao_socio = 16 THEN 'Presidente'
32 | WHEN s.codigo_qualificacao_socio = 17 THEN 'Procurador'
33 | WHEN s.codigo_qualificacao_socio = 18 THEN 'Secretário'
34 | WHEN s.codigo_qualificacao_socio = 19 THEN 'Síndico (Condomínio)'
35 | WHEN s.codigo_qualificacao_socio = 20 THEN 'Sociedade Consorciada'
36 | WHEN s.codigo_qualificacao_socio = 21 THEN 'Sociedade Filiada'
37 | WHEN s.codigo_qualificacao_socio = 22 THEN 'Sócio'
38 | WHEN s.codigo_qualificacao_socio = 23 THEN 'Sócio Capitalista'
39 | WHEN s.codigo_qualificacao_socio = 24 THEN 'Sócio Comanditado'
40 | WHEN s.codigo_qualificacao_socio = 25 THEN 'Sócio Comanditário'
41 | WHEN s.codigo_qualificacao_socio = 26 THEN 'Sócio de Indústria'
42 | WHEN s.codigo_qualificacao_socio = 27 THEN 'Sócio Residente ou Domiciliado no Exterior'
43 | WHEN s.codigo_qualificacao_socio = 28 THEN 'Sócio-Gerente'
44 | WHEN s.codigo_qualificacao_socio = 29 THEN 'Sócio ou Acionista Incapaz ou Relativamente Incapaz (exceto menor)'
45 | WHEN s.codigo_qualificacao_socio = 30 THEN 'Sócio ou Acionista Menor (Assistido/Representado)'
46 | WHEN s.codigo_qualificacao_socio = 31 THEN 'Sócio Ostensivo'
47 | WHEN s.codigo_qualificacao_socio = 32 THEN 'Tabelião'
48 | WHEN s.codigo_qualificacao_socio = 33 THEN 'Tesoureiro'
49 | WHEN s.codigo_qualificacao_socio = 34 THEN 'Titular de Empresa Individual Imobiliária'
50 | WHEN s.codigo_qualificacao_socio = 35 THEN 'Tutor'
51 | WHEN s.codigo_qualificacao_socio = 36 THEN 'Gerente-Delegado'
52 | WHEN s.codigo_qualificacao_socio = 37 THEN 'Sócio Pessoa Jurídica Domiciliado no Exterior'
53 | WHEN s.codigo_qualificacao_socio = 38 THEN 'Sócio Pessoa Física Residente ou Domiciliado no Exterior'
54 | WHEN s.codigo_qualificacao_socio = 39 THEN 'Diplomata'
55 | WHEN s.codigo_qualificacao_socio = 40 THEN 'Cônsul'
56 | WHEN s.codigo_qualificacao_socio = 41 THEN 'Representante de Organização Internacional'
57 | WHEN s.codigo_qualificacao_socio = 42 THEN 'Oficial de Registro'
58 | WHEN s.codigo_qualificacao_socio = 43 THEN 'Responsável'
59 | WHEN s.codigo_qualificacao_socio = 44 THEN 'Sócio Participante'
60 | WHEN s.codigo_qualificacao_socio = 45 THEN 'Sócio Investidor'
61 | WHEN s.codigo_qualificacao_socio = 46 THEN 'Ministro de Estado das Relações Exteriores'
62 | WHEN s.codigo_qualificacao_socio = 47 THEN 'Sócio Pessoa Física Residente no Brasil'
63 | WHEN s.codigo_qualificacao_socio = 48 THEN 'Sócio Pessoa Jurídica Domiciliado no Brasil'
64 | WHEN s.codigo_qualificacao_socio = 49 THEN 'Sócio-Administrador'
65 | WHEN s.codigo_qualificacao_socio = 50 THEN 'Empresário'
66 | WHEN s.codigo_qualificacao_socio = 51 THEN 'Candidato a Cargo Político Eletivo'
67 | WHEN s.codigo_qualificacao_socio = 52 THEN 'Sócio com Capital'
68 | WHEN s.codigo_qualificacao_socio = 53 THEN 'Sócio sem Capital'
69 | WHEN s.codigo_qualificacao_socio = 54 THEN 'Fundador'
70 | WHEN s.codigo_qualificacao_socio = 55 THEN 'Sócio Comanditado Residente no Exterior'
71 | WHEN s.codigo_qualificacao_socio = 56 THEN 'Sócio Comanditário Pessoa Física Residente no Exterior'
72 | WHEN s.codigo_qualificacao_socio = 57 THEN 'Sócio Comanditário Pessoa Jurídica Domiciliado no Exterior'
73 | WHEN s.codigo_qualificacao_socio = 58 THEN 'Sócio Comanditário Incapaz'
74 | WHEN s.codigo_qualificacao_socio = 59 THEN 'Produtor Rural'
75 | WHEN s.codigo_qualificacao_socio = 60 THEN 'Cônsul Honorário'
76 | WHEN s.codigo_qualificacao_socio = 61 THEN 'Responsável Indigena'
77 | WHEN s.codigo_qualificacao_socio = 62 THEN 'Representante das Instituições Extraterritoriais'
78 | WHEN s.codigo_qualificacao_socio = 63 THEN 'Cotas em Tesouraria'
79 | WHEN s.codigo_qualificacao_socio = 64 THEN 'Administrador Judicial'
80 | WHEN s.codigo_qualificacao_socio = 65 THEN 'Titular Pessoa Física Residente ou Domiciliado no Brasil'
81 | WHEN s.codigo_qualificacao_socio = 66 THEN 'Titular Pessoa Física Residente ou Domiciliado no Exterior'
82 | WHEN s.codigo_qualificacao_socio = 67 THEN 'Titular Pessoa Física Incapaz ou Relativamente Incapaz (exceto menor)'
83 | WHEN s.codigo_qualificacao_socio = 68 THEN 'Titular Pessoa Física Menor (Assistido/Representado)'
84 | WHEN s.codigo_qualificacao_socio = 69 THEN 'Beneficiário Final'
85 | WHEN s.codigo_qualificacao_socio = 70 THEN 'Administrador Residente ou Domiciliado no Exterior'
86 | WHEN s.codigo_qualificacao_socio = 71 THEN 'Conselheiro de Administração Residente ou Domiciliado no Exterior'
87 | WHEN s.codigo_qualificacao_socio = 72 THEN 'Diretor Residente ou Domiciliado no Exterior'
88 | WHEN s.codigo_qualificacao_socio = 73 THEN 'Presidente Residente ou Domiciliado no Exterior'
89 | WHEN s.codigo_qualificacao_socio = 74 THEN 'Sócio-Administrador Residente ou Domiciliado no Exterior'
90 | WHEN s.codigo_qualificacao_socio = 75 THEN 'Fundador Residente ou Domiciliado no Exterior'
91 | WHEN s.codigo_qualificacao_socio = 76 THEN 'Protetor'
92 | WHEN s.codigo_qualificacao_socio = 77 THEN 'Vice-Presidente'
93 | WHEN s.codigo_qualificacao_socio = 78 THEN 'Titular Pessoa Jurídica Domiciliada no Brasil'
94 | WHEN s.codigo_qualificacao_socio = 79 THEN 'Titular Pessoa Jurídica Domiciliada no Exterior'
95 | END AS qualificacao_socio
96 | FROM socio AS s
97 | LEFT JOIN empresa AS e
98 | ON s.cnpj = e.cnpj;
99 |
100 |
101 | CREATE VIEW view_holdings AS
102 | SELECT
103 | h.cnpj AS cnpj,
104 | h.razao_social AS razao_social,
105 | h.holding_cnpj AS cnpj_socia,
106 | h.qualificacao_socia AS qualificacao_socia,
107 | h.holding_razao_social AS razao_social_socia
108 | FROM holding AS h;
109 |
110 |
111 | CREATE VIEW view_empresas AS
112 | SELECT
113 | e.cnpj AS cnpj,
114 | e.razao_social AS razao_social,
115 | e.uf AS uf
116 | FROM empresa AS e;
117 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
166 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Sócios de Empresas Brasileiras
2 |
3 | Script que baixa todos os dados de sócios das empresas brasileiras [disponíveis
4 | no site da Receita
5 | Federal](https://receita.economia.gov.br/orientacao/tributaria/cadastros/cadastro-nacional-de-pessoas-juridicas-cnpj/dados-publicos-cnpj),
6 | extrai, limpa e converte para CSV. Para entender melhor sobre quais dados estão
7 | disponíveis, consulte a [história desse dataset](historia-do-dataset.md).
8 |
9 |
10 | ## Licença
11 |
12 | A licença do código é [LGPL3](https://www.gnu.org/licenses/lgpl-3.0.en.html) e
13 | dos dados convertidos [Creative Commons Attribution
14 | ShareAlike](https://creativecommons.org/licenses/by-sa/4.0/). Caso utilize os
15 | dados, **cite a fonte original e quem tratou os dados**, como: **Fonte: Receita
16 | Federal do Brasil, dados tratados por Álvaro
17 | Justen/[Brasil.IO](https://brasil.io/)**. Caso compartilhe os dados, **utilize
18 | a mesma licença**.
19 |
20 |
21 | ## Dados
22 |
23 | ### Entrada
24 |
25 | Os dados publicados pela Receita Federal do Brasil contemplam as seguintes
26 | tabelas:
27 |
28 | - Cadastro das empresas, incluindo CNPJ, razão social, nome fantasia, endereço,
29 | CNAE fiscal e outros;
30 | - Cadastro de sócios, contendo CNPJ da empresa, documento do sócio, nome do
31 | sócio e outros;
32 | - CNAEs secundários para cada CNPJ.
33 |
34 | Os dados originalmente estão em um formato [fixed-width
35 | file](http://www.softinterface.com/Convert-XLS/Features/Fixed-Width-Text-File-Definition.htm)
36 | e cada linha possui um tipo diferente de registro (empresa, sócio, CNAE
37 | secundária, header ou trailler), que dificulta qualquer tipo de análise, sendo
38 | necessária a conversão para formatos mais amigáveis.
39 |
40 | O campo de qualificação do sócio foi definido com base [na tabela
41 | disponibilizada pela Receita
42 | Federal](http://idg.receita.fazenda.gov.br/orientacao/tributaria/cadastros/cadastro-nacional-de-pessoas-juridicas-cnpj/Qualificacao_socio.pdf)
43 | e está disponível no arquivo
44 | [`qualificacao-socio.csv`](qualificacao-socio.csv). Em breve também teremos
45 | arquivos com os nomes dos CNAEs e situação cadastral ([veja mais detalhes
46 | aqui](https://github.com/turicas/socios-brasil/issues/20)).
47 |
48 |
49 | ### Saída
50 |
51 | Além de extrair os dados do arquivo origingal, o script gera uma nova tabela
52 | contendo as empresas que são sócias de outras empresas (para facilitar buscas
53 | de *holdings*).
54 |
55 | Caso você não queira/possa rodar o script, **[acesse diretamente os dados
56 | convertidos no Brasil.IO](https://brasil.io/dataset/socios-brasil)**.
57 |
58 | Se esse programa e/ou os dados resultantes foram úteis a você ou à sua empresa,
59 | considere [fazer uma doação ao projeto Brasil.IO](https://brasil.io/doe), que é
60 | mantido voluntariamente.
61 |
62 | Como resultado temos os seguintes arquivos:
63 |
64 | - `empresa.csv.gz`: cadastro das empresas;
65 | - `socio.csv.gz`: cadastro dos sócios;
66 | - `cnae-secundaria.csv.gz`: lista de CNAEs secundárias;
67 | - `holding.csv.gz`: cadastro das empresas que são sócias de outras
68 | empresas (é o arquivo `socio.csv.gz` filtrado por sócios do tipo PJ).
69 |
70 | Além disso, os arquivos contidos nas pastas [schema](schema/) e
71 | [schema-full](schema-full/) podem te ajudar a importar os dados para um banco
72 | de dados (veja comandos para [SQLite](#sqlite) e [PostgreSQL](#postgresql)
73 | abaixo).
74 |
75 | > Nota 1: a extensão `.gz` quer dizer que o arquivo foi compactado usando gzip.
76 | > Para descompactá-lo execute o comando `gunzip arquivo.gz` (**não é necessário
77 | > descompactá-los** caso você siga as instruções de importação em
78 | > [SQLite](#sqlite) e [PostgreSQL](#postgresql)).
79 |
80 | > Nota 2: a codificação de caracteres original é ISO-8859-15, mas o script gera
81 | > os arquivos CSV em UTF-8.
82 |
83 | > Nota 3: se você estava usando os dados no formato anterior, veja como
84 | > converter os novos para o padrão antigo no arquivo
85 | > `sql/04-create-old-views.sql`.
86 |
87 |
88 | ### Privacidade
89 |
90 | Para garantir a privacidade, evitar SPAM e publicar apenas dados corretos, o
91 | script deleta/limpa algumas colunas com informações sensíveis ou incorretas.
92 | Essa é a forma padrão de funcionamento para não facilitar a exposição desses
93 | dados. Os dados censurados são:
94 |
95 | - Na tabela `empresa`:
96 | - Deletadas as colunas `codigo_pais` e `nome_pais`, pois os dados contidos
97 | nelas estão incorretos;
98 | - Deletada a coluna `correio_eletronico`, para evitar SPAM;
99 | - Na tabela `socio`:
100 | - Deletadas as colunas `codigo_pais` e `nome_pais`, pois os dados contidos
101 | nelas estão incorretos;
102 | - As colunas `complemento`, `ddd_fax`, `ddd_telefone_1`, `ddd_telefone_2`,
103 | `descricao_tipo_logradouro`, `logradouro`, `numero` terão seus dados
104 | deletados (ficarão em branco) para empresas que são empreendedores
105 | individuais (MEI, EI, EIRELI etc.) e, provavelmente, correspondem aos dados
106 | do sócio (endereço residencial, por exemplo);
107 | - Para os casos de empresas individuais que constarem o CPF na razão social
108 | (como é comum no caso de MEIs), o CPF será deletado.
109 |
110 | Caso queira rodar o script sem o modo censura, altere o script `run.sh` e
111 | adicione a opção `--no_censorship` para o script `extract_dump.py`.
112 |
113 |
114 | ### Dados auxiliares
115 |
116 | - Cadastro Nacional de Atividades Empresariais (CNAE): existe um spider que
117 | baixa os metadados das [atividades empresariais (CNAEs) do site do
118 | IBGE](https://cnae.ibge.gov.br). Veja a função `extract_cnae` no arquivo
119 | `run.sh`, ela baixará os dados para as versões 1.0, 1.1, 2.0, 2.1, 2.2 e 2.3
120 | e salvará em `data/output`. **Nota**: esse script será melhorado/alterado,
121 | veja a [issue #36](https://github.com/turicas/socios-brasil/issues/36).
122 | - Natureza jurídica: o arquivo `data/natureza-juridica.csv` contém o cadsatro
123 | de naturezas jurídicas das empresas (coluna `codigo_natureza_juridica` da
124 | tabela `empresa`). Esse arquivo é gerado pelo script `natureza_juridica.py`,
125 | que baixa os [dados do site da Receita
126 | Federal](https://www.receita.fazenda.gov.br/pessoajuridica/cnpj/tabelas/natjurqualificaresponsavel.htm).
127 |
128 |
129 | ## Rodando
130 |
131 | ### Instalando as Dependências
132 |
133 | Esse script depende de Python 3.7, de algumas bibliotecas e do software
134 | [aria2](https://aria2.github.io/). Depois de instalar o Python 3.7 e o aria2,
135 | instale as bibliotecas executando:
136 |
137 | ```bash
138 | pip install -r requirements.txt
139 | ```
140 |
141 | ### Executando
142 |
143 | Então basta executar o script `run.sh` para baixar os arquivos necessários e
144 | fazer as conversões:
145 |
146 | ```bash
147 | ./run.sh
148 | ```
149 |
150 | Você poderá rodar etapas separadamente também (leia o script [run.sh](run.sh)
151 | para mais detalhes).
152 |
153 | #### Agilizando o Download
154 |
155 | [O servidor da Receita Federal onde os dados estão hospedados é **muito
156 | lento**](https://twitter.com/turicas/status/1114185311372873729) e, por isso, o
157 | [Brasil.IO](https://brasil.io/) disponibiliza um *mirror* de onde o download
158 | pode ser feito mais rapidamente. Para executar o script baixando os dados do
159 | *mirror*, execute:
160 |
161 | ```bash
162 | ./run.sh --use-mirror
163 | ```
164 |
165 | > Nota: os *mirrors* do Brasil.IO ainda estão em fase de testes e não é
166 | > garantido que estejam sempre atualizados.
167 |
168 |
169 | ## Importando em Bancos de Dados
170 |
171 | Depois de executar o script ou baixar os dados já convertidos, o ideal é
172 | importá-los em um banco de dados para facilitar consultas. Com a [interface de
173 | linha de comando da rows](http://turicas.info/rows/cli/) é possível importá-los
174 | rapidamente em bancos SQLite e PostgreSQL.
175 |
176 | > Nota 1: depois de importar os dados em um banco de dados é recomendável a
177 | > criação de índices para agilizar as consultas. Um índice bem comum é na
178 | > coluna `cnpj` (de todas as tabelas), para facilitar encontrar uma determinada
179 | > empresa, seus sócios e CNAEs secundários através do CNPJ. Exemplo:
180 | > `CREATE INDEX IF NOT EXISTS idx_empresa_cnpj ON empresa (cnpj);`. Veja o
181 | > arquivo [sql/create-indexes.sql](sql/create-indexes.sql) para uma lista de
182 | > índices sugeridos; veja também os outros arquivos da pasta `sql/` para
183 | > criação de tabelas auxiliares, chaves primárias e estrangeiras e o arquivo
184 | > `import-postgresql.sh` para automatizar o processo de importação e criação
185 | > dos índices.
186 |
187 | > Nota 2: caso utilize a opção `--no_censorship`, utilize os arquivos da pasta
188 | > `schema-full` em vez da pasta `schema`, pois a versão "sem censura" possui
189 | > mais colunas.
190 |
191 | ### SQLite
192 |
193 | Instale a CLI da rows e a versão de desenvolvimento da biblioteca rodando
194 | (requer Python 3.7+):
195 |
196 | ```bash
197 | pip install rows[cli]
198 | pip install -U https://github.com/turicas/rows/archive/develop.zip
199 | ```
200 |
201 | Agora, com os arquivos na pasta `data/output` basta executar os seguintes
202 | comandos:
203 |
204 | ```bash
205 | DB_NAME="data/output/socios-brasil.sqlite"
206 | rows csv2sqlite --schemas=schema/empresa.csv data/output/empresa.csv.gz "$DB_NAME"
207 | rows csv2sqlite --schemas=schema/holding.csv data/output/holding.csv.gz "$DB_NAME"
208 | rows csv2sqlite --schemas=schema/socio.csv data/output/socio.csv.gz "$DB_NAME"
209 | rows csv2sqlite --schemas=schema/cnae-secundaria.csv data/output/cnae-secundaria.csv.gz "$DB_NAME"
210 | ```
211 |
212 | Pegue um café, aguarde alguns minutos e depois desfrute do banco de dados em
213 | `data/output/socios-brasil.sqlite`. :)
214 |
215 |
216 | ### PostgreSQL
217 |
218 | Instale a CLI da rows, as dependências do PostgreSQL e a versão de
219 | desenvolvimento da biblioteca rodando (requer Python 3.7+):
220 |
221 | ```bash
222 | pip install rows[cli]
223 | pip install rows[postgresql]
224 | pip install -U https://github.com/turicas/rows/archive/develop.zip
225 | ```
226 |
227 | Agora, com os arquivos na pasta `data/output` basta executar os seguintes
228 | comandos (não esqueça de preencher a variável `POSTGRESQL_URI` corretamente):
229 |
230 | ```bash
231 | POSTGRESQL_URI="postgres://:@:/" # PREENCHA!
232 | rows pgimport --schema=schema/empresa.csv data/output/empresa.csv.gz $POSTGRESQL_URI empresa
233 | rows pgimport --schema=schema/socio.csv data/output/empresa-socia.csv.gz $POSTGRESQL_URI empresa_socia
234 | rows pgimport --schema=schema/socio.csv data/output/socio.csv.gz $POSTGRESQL_URI socio
235 | rows pgimport --schema=schema/cnae-secundaria.csv data/output/cnae-secundaria.csv.gz $POSTGRESQL_URI cnae_secundaria
236 | ```
237 |
238 | Pegue um café, aguarde alguns minutos e depois desfrute do banco de dados em
239 | `$POSTGRESQL_URI`. :)
240 |
241 |
242 | ## Outras Implementações
243 |
244 | Em R:
245 |
246 | - [qsacnpj](https://github.com/georgevbsantiago/qsacnpj/)
247 | - [RFBCNPJ](http://curso-r.com/blog/2018/05/13/2018-05-13-rfbcnpj/)
248 |
249 | Em Python:
250 |
251 | - [CNPJ-full](https://github.com/fabioserpa/CNPJ-full)
252 |
--------------------------------------------------------------------------------
/extract_dump.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """
3 | Extrai os dados do dump do QSA da Receita Federal
4 |
5 | Dentro dos arquivos ZIP existe apenas um arquivo do tipo fixed-width file,
6 | contendo registros de diversas tabelas diferentes. O script descompacta sob
7 | demanda o ZIP e, conforme vai lendo os registros contidos, cria os arquivos de
8 | saída, em formato CSV. Você deve especificar o arquivo de entrada e o diretório
9 | onde ficarão os CSVs de saída (que por padrão ficam compactados também, em
10 | gzip, para diminuir o tempo de escrita e economizar espaço em disco).
11 | """
12 |
13 | from argparse import ArgumentParser
14 | from decimal import Decimal
15 | from io import TextIOWrapper
16 | from pathlib import Path
17 | from zipfile import ZipFile
18 |
19 | import rows
20 | from rows.fields import slug
21 | from rows.plugins.utils import ipartition
22 | from rows.utils import CsvLazyDictWriter, open_compressed
23 | from tqdm import tqdm
24 |
25 | # Fields to delete/clean in some cases so we don't expose personal information
26 | FIELDS_TO_DELETE_COMPANY = ("codigo_pais", "correio_eletronico", "nome_pais")
27 | FIELDS_TO_DELETE_PARTNER = ("codigo_pais", "nome_pais")
28 | FIELDS_TO_CLEAR_INDIVIDUAL_COMPANY = (
29 | "complemento",
30 | "ddd_fax",
31 | "ddd_telefone_1",
32 | "ddd_telefone_2",
33 | "descricao_tipo_logradouro",
34 | "logradouro",
35 | "numero",
36 | )
37 | ONE_CENT = Decimal("0.01")
38 | INDIVIDUAL_COMPANIES = tuple(
39 | row.codigo
40 | for row in rows.import_from_csv("data/natureza-juridica.csv")
41 | if "individual" in row.natureza_juridica.lower()
42 | )
43 |
44 |
45 | def clear_company_name(words):
46 | """Remove CPF from company name (useful to remove sensitive data from MEI)
47 |
48 | >>> clear_company_name(['FULANO', 'DE', 'TAL', '12345678901'])
49 | 'FULANO DE TAL'
50 | >>> clear_company_name(['FULANO', 'DE', 'TAL', 'CPF', '12345678901'])
51 | 'FULANO DE TAL'
52 | >>> clear_company_name(['FULANO', 'DE', 'TAL', '-', 'CPF', '12345678901'])
53 | 'FULANO DE TAL'
54 | >>> clear_company_name(['123456'])
55 | '123456'
56 | """
57 | if len(words) == 1 and words[0].isdigit(): # Weird name, but doesn't have a CPF
58 | return words[0]
59 |
60 | last_word = words[-1]
61 | if last_word.isdigit() and len(last_word) == 11: # Remove CPF (numbers)
62 | words.pop()
63 |
64 | if words[-1].upper() == "CPF": # Remove CPF (word)
65 | words.pop()
66 |
67 | if words[-1] == "-":
68 | words.pop()
69 |
70 | return " ".join(words).strip()
71 |
72 |
73 | class ParsingError(ValueError):
74 | def __init__(self, line, error):
75 | super().__init__()
76 | self.line = line
77 | self.error = error
78 |
79 |
80 | def clear_email(email):
81 | """
82 | >>> clear_email('-') is None
83 | True
84 | >>> clear_email('.') is None
85 | True
86 | >>> clear_email('0') is None
87 | True
88 | >>> clear_email('0000000000000000000000000000000000000000') is None
89 | True
90 | >>> clear_email('N/TEM') is None
91 | True
92 | >>> clear_email('NAO POSSUI') is None
93 | True
94 | >>> clear_email('NAO TEM') is None
95 | True
96 | >>> clear_email('NT') is None
97 | True
98 | >>> clear_email('S/N') is None
99 | True
100 | >>> clear_email('XXXXXXXX') is None
101 | True
102 | >>> clear_email('________________________________________') is None
103 | True
104 | >>> clear_email('n/t') is None
105 | True
106 | >>> clear_email('nao tem') is None
107 | True
108 | """
109 |
110 | clean = email.lower().replace("/", "").replace("_", "")
111 | if len(set(clean)) < 3 or clean in ("nao tem", "n tem", "ntem", "nao possui", "nt"):
112 | return None
113 | return email
114 |
115 |
116 | def read_header(filename):
117 | """Read a CSV file which describes a fixed-width file
118 |
119 | The CSV must have the following columns:
120 |
121 | - name (final field name)
122 | - size (fixed size of the field, in bytes)
123 | - start_column (column in the fwf file where the fields starts)
124 | - type ("A" for text, "N" for int)
125 | """
126 |
127 | table = rows.import_from_csv(filename)
128 | table.order_by("start_column")
129 | header = []
130 | for row in table:
131 | row = dict(row._asdict())
132 | row["field_name"] = slug(row["name"])
133 | row["start_index"] = row["start_column"] - 1
134 | row["end_index"] = row["start_index"] + row["size"]
135 | header.append(row)
136 | return header
137 |
138 |
139 | def transform_empresa(row, censor):
140 | """Transform row of type company"""
141 |
142 | if censor:
143 | for field_name in FIELDS_TO_DELETE_COMPANY:
144 | del row[field_name]
145 |
146 | if row["codigo_natureza_juridica"] in INDIVIDUAL_COMPANIES: # "eupresa"
147 | for field_name in FIELDS_TO_CLEAR_INDIVIDUAL_COMPANY:
148 | row[field_name] = ""
149 | for field_name in ("razao_social", "nome_fantasia"):
150 | words = row[field_name].split()
151 | if words and words[-1].isdigit():
152 | row[field_name] = clear_company_name(words)
153 |
154 | if "correio_eletronico" in row: # Could be deleted by censorship
155 | row["correio_eletronico"] = clear_email(row["correio_eletronico"])
156 |
157 | if row["opcao_pelo_simples"] in ("", "0", "6", "8"):
158 | row["opcao_pelo_simples"] = "0"
159 | elif row["opcao_pelo_simples"] in ("5", "7"):
160 | row["opcao_pelo_simples"] = "1"
161 | else:
162 | raise ValueError(f"Opção pelo Simples inválida: {row['opcao_pelo_simples']} (CNPJ: {row['cnpj']})")
163 |
164 | if row["opcao_pelo_mei"] in ("N", ""):
165 | row["opcao_pelo_mei"] = "0"
166 | elif row["opcao_pelo_mei"] == "S":
167 | row["opcao_pelo_mei"] = "1"
168 | else:
169 | raise ValueError(f"Opção pelo MEI inválida: {row['opcao_pelo_mei']} (CNPJ: {row['cnpj']})")
170 |
171 | if set(row["nome_fantasia"]) == set(["0"]):
172 | row["nome_fantasia"] = ""
173 |
174 | if row["capital_social"] is not None:
175 | row["capital_social"] = Decimal(row["capital_social"]) * ONE_CENT
176 |
177 | return [row]
178 |
179 |
180 | def transform_socio(row, censor):
181 | """Transform row of type partner"""
182 |
183 | if row["campo_desconhecido"] != "":
184 | raise ValueError(f"Campo desconhecido preenchido - checar: {row}")
185 | del row["campo_desconhecido"]
186 |
187 | if row["nome_representante_legal"] == "CPF INVALIDO":
188 | row["cpf_representante_legal"] = None
189 | row["nome_representante_legal"] = None
190 | row["codigo_qualificacao_representante_legal"] = None
191 |
192 | if row["cnpj_cpf_do_socio"] == "000***000000**":
193 | row["cnpj_cpf_do_socio"] = ""
194 |
195 | if row["identificador_de_socio"] == 2: # Pessoa Física
196 | row["cnpj_cpf_do_socio"] = row["cnpj_cpf_do_socio"][-11:]
197 |
198 | # TODO: convert percentual_capital_social
199 |
200 | # Delete some fields if needed
201 | if censor:
202 | for field_name in FIELDS_TO_DELETE_PARTNER:
203 | del row[field_name]
204 |
205 | return [row]
206 |
207 |
208 | def transform_cnae_secundaria(row, censor):
209 | """Transform row of type CNAE"""
210 |
211 | cnaes = ["".join(digits) for digits in ipartition(row.pop("cnae"), 7) if set(digits) != set(["0"])]
212 | data = []
213 | for cnae in cnaes:
214 | new_row = row.copy()
215 | new_row["cnae"] = cnae
216 | data.append(new_row)
217 |
218 | return data
219 |
220 |
221 | def parse_row(header, line):
222 | """Parse a fixed-width file line and returns a dict, based on metadata
223 |
224 | The `header` parameter is the return from `read_header`.
225 | Notes:
226 | 1- There's no check whether all fields are parsed (this function trusts
227 | the `header` was created in the correct way).
228 | 2- `line` is already decoded and since the input encoding is `latin1`, one
229 | character equals to one byte. If the input encoding does not have this
230 | characteristic then this function needs to be changed.
231 | """
232 | line = line.replace("\x00", " ").replace("\x02", " ")
233 | row = {}
234 | for field in header:
235 | field_name = field["field_name"]
236 | value = line[field["start_index"] : field["end_index"]].strip()
237 |
238 | if field_name == "filler":
239 | if set(value) not in (set(), {"9"}):
240 | raise ParsingError(line=line, error="Wrong filler")
241 | continue # Do not save `filler`
242 | elif field_name == "tipo_de_registro":
243 | continue # Do not save row type (will be saved in separate files)
244 | elif field_name == "fim":
245 | if value.strip() != "F":
246 | raise ParsingError(line=line, error="Wrong end")
247 | continue # Do not save row end mark
248 | elif field_name in ("indicador_full_diario", "tipo_de_atualizacao"):
249 | continue # These fields are usually useless
250 |
251 | if field_name.startswith("data_") and value:
252 | if len(str(value)) > 8:
253 | raise ParsingError(line=line, error="Wrong date size")
254 | value = f"{value[:4]}-{value[4:6]}-{value[6:8]}"
255 | if value == "0000-00-00":
256 | value = ""
257 | elif field["type"] == "N" and "*" not in value:
258 | try:
259 | value = int(value) if value else None
260 | except ValueError:
261 | raise ParsingError(line=line, error=f"Cannot convert {repr(value)} to int")
262 |
263 | row[field_name] = value
264 |
265 | return row
266 |
267 |
268 | def extract_files(
269 | filenames,
270 | header_definitions,
271 | transform_functions,
272 | output_writers,
273 | error_filename,
274 | input_encoding="latin1",
275 | censorship=True,
276 | ):
277 | """Extract files from a fixed-width file containing more than one row type
278 |
279 | `filenames` is expected to be a list of ZIP files having only one file
280 | inside each. The file is read and metadata inside `fobjs` is used to parse
281 | it and save the output files.
282 | """
283 | error_fobj = open_compressed(error_filename, mode="w", encoding="latin1")
284 | error_writer = CsvLazyDictWriter(error_fobj)
285 |
286 | for filename in filenames:
287 | # TODO: use another strategy to open this file (like using rows'
288 | # open_compressed when archive support is implemented)
289 | zf = ZipFile(filename)
290 | inner_filenames = zf.filelist
291 | assert len(inner_filenames) == 1, f"Only one file inside the zip is expected (got {len(inner_filenames)})"
292 | # XXX: The current approach of decoding here and then extracting
293 | # fixed-width-file data will work only for encodings where 1 character is
294 | # represented by 1 byte, such as latin1. If the encoding can represent one
295 | # character using more than 1 byte (like UTF-8), this approach will make
296 | # incorrect results.
297 | fobj = TextIOWrapper(zf.open(inner_filenames[0]), encoding=input_encoding)
298 | for line in tqdm(fobj, desc=f"Extracting {filename}"):
299 | row_type = line[0]
300 | try:
301 | row = parse_row(header_definitions[row_type], line)
302 | except ParsingError as exception:
303 | error_writer.writerow({"error": exception.error, "line": exception.line})
304 | continue
305 | data = transform_functions[row_type](row, censorship)
306 | for row in data:
307 | output_writers[row_type].writerow(row)
308 |
309 | fobj.close()
310 | zf.close()
311 |
312 | error_fobj.close()
313 |
314 |
315 | def main():
316 | base_path = Path(__file__).parent
317 | output_path = base_path / "data" / "output"
318 | error_filename = output_path / "errors.csv"
319 |
320 | parser = ArgumentParser()
321 | parser.add_argument("output_path", default=str(output_path))
322 | parser.add_argument("input_filenames", nargs="+")
323 | parser.add_argument("--no_censorship", action="store_true")
324 | args = parser.parse_args()
325 |
326 | input_encoding = "latin1"
327 | input_filenames = args.input_filenames
328 | output_path = Path(args.output_path)
329 | if not output_path.exists():
330 | output_path.mkdir(parents=True)
331 | error_filename = output_path / "error.csv.gz"
332 | censorship = not args.no_censorship
333 |
334 | row_types = {
335 | "0": {
336 | "header_filename": "headers/header.csv",
337 | "output_filename": output_path / "header.csv.gz",
338 | "transform_function": lambda row, censor: [row],
339 | },
340 | "1": {
341 | "header_filename": "headers/empresa.csv",
342 | "output_filename": output_path / "empresa.csv.gz",
343 | "transform_function": transform_empresa,
344 | },
345 | "2": {
346 | "header_filename": "headers/socio.csv",
347 | "output_filename": output_path / "socio.csv.gz",
348 | "transform_function": transform_socio,
349 | },
350 | "6": {
351 | "header_filename": "headers/cnae_secundaria.csv",
352 | "output_filename": output_path / "cnae_secundaria.csv.gz",
353 | "transform_function": transform_cnae_secundaria,
354 | },
355 | "9": {
356 | "header_filename": "headers/trailler.csv",
357 | "output_filename": output_path / "trailler.csv.gz",
358 | "transform_function": lambda row, censor: [row],
359 | },
360 | }
361 | header_definitions, output_writers, transform_functions = {}, {}, {}
362 | for row_type, data in row_types.items():
363 | header_definitions[row_type] = read_header(data["header_filename"])
364 | output_writers[row_type] = CsvLazyDictWriter(data["output_filename"])
365 | transform_functions[row_type] = data["transform_function"]
366 | extract_files(
367 | filenames=input_filenames,
368 | header_definitions=header_definitions,
369 | transform_functions=transform_functions,
370 | output_writers=output_writers,
371 | error_filename=error_filename,
372 | input_encoding=input_encoding,
373 | censorship=censorship,
374 | )
375 |
376 |
377 | if __name__ == "__main__":
378 | main()
379 |
--------------------------------------------------------------------------------