vra-anac 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vra_anac-0.1.0/LICENSE +21 -0
- vra_anac-0.1.0/PKG-INFO +153 -0
- vra_anac-0.1.0/README.md +134 -0
- vra_anac-0.1.0/pyproject.toml +48 -0
- vra_anac-0.1.0/setup.cfg +4 -0
- vra_anac-0.1.0/tests/test_cli.py +109 -0
- vra_anac-0.1.0/tests/test_indicador.py +127 -0
- vra_anac-0.1.0/tests/test_leitura.py +107 -0
- vra_anac-0.1.0/tests/test_pacote.py +71 -0
- vra_anac-0.1.0/tests/test_readme.py +39 -0
- vra_anac-0.1.0/tests/test_tabelas.py +42 -0
- vra_anac-0.1.0/vra_anac/__init__.py +26 -0
- vra_anac-0.1.0/vra_anac/__main__.py +132 -0
- vra_anac-0.1.0/vra_anac/argumentos.py +82 -0
- vra_anac-0.1.0/vra_anac/dados/aeroportos.csv +142 -0
- vra_anac-0.1.0/vra_anac/dados/companhias.csv +7 -0
- vra_anac-0.1.0/vra_anac/indicador.py +126 -0
- vra_anac-0.1.0/vra_anac/leitura.py +165 -0
- vra_anac-0.1.0/vra_anac/tabelas.py +72 -0
- vra_anac-0.1.0/vra_anac.egg-info/PKG-INFO +153 -0
- vra_anac-0.1.0/vra_anac.egg-info/SOURCES.txt +22 -0
- vra_anac-0.1.0/vra_anac.egg-info/dependency_links.txt +1 -0
- vra_anac-0.1.0/vra_anac.egg-info/entry_points.txt +2 -0
- vra_anac-0.1.0/vra_anac.egg-info/top_level.txt +1 -0
vra_anac-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 peterwkdev-creator
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
vra_anac-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vra-anac
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Pontualidade dos voos domésticos do Brasil pelo VRA da ANAC: lê o CSV do mês e calcula o indicador
|
|
5
|
+
Author: Peter
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/peterwkdev-creator/vra-anac
|
|
8
|
+
Project-URL: Source, https://github.com/peterwkdev-creator/vra-anac
|
|
9
|
+
Project-URL: Issues, https://github.com/peterwkdev-creator/vra-anac/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/peterwkdev-creator/vra-anac/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: anac,vra,aviacao,voos,pontualidade,atrasos,dados-abertos,brasil
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Natural Language :: Portuguese (Brazilian)
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
15
|
+
Requires-Python: >=3.11
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# vra-anac
|
|
21
|
+
|
|
22
|
+
[](https://github.com/peterwkdev-creator/vra-anac/actions/workflows/testes.yml)
|
|
23
|
+
[](https://pypi.org/project/vra-anac/)
|
|
24
|
+
[](https://pypi.org/project/vra-anac/)
|
|
25
|
+
[](https://github.com/peterwkdev-creator/vra-anac/blob/main/LICENSE)
|
|
26
|
+
|
|
27
|
+
Quantos voos domésticos chegaram no horário, por voo, rota, companhia ou
|
|
28
|
+
aeroporto, a partir do arquivo mensal do **VRA (Voo Regular Ativo) da
|
|
29
|
+
ANAC**. Lê o CSV como a ANAC publica e aplica a mesma régua do
|
|
30
|
+
[chegounahora.com.br](https://chegounahora.com.br/): **no horário = chegada
|
|
31
|
+
até 15 min depois da prevista**.
|
|
32
|
+
|
|
33
|
+
Só a biblioteca padrão do Python, sem rede: você baixa o CSV, ela faz a conta.
|
|
34
|
+
|
|
35
|
+

|
|
36
|
+
|
|
37
|
+
## Instalar
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install vra-anac
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Ou uma versão exata, pela tag do GitHub:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install "git+https://github.com/peterwkdev-creator/vra-anac@v0.1.0"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Python 3.11 ou mais novo.
|
|
50
|
+
|
|
51
|
+
## Usar
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
import vra_anac
|
|
55
|
+
|
|
56
|
+
# o CSV do mês que você baixou da ANAC (aqui, a amostra dos testes)
|
|
57
|
+
atualizado, voos = vra_anac.ler_arquivo("tests/fixtures/vra-amostra.csv")
|
|
58
|
+
t = vra_anac.tabelas()
|
|
59
|
+
|
|
60
|
+
for chave, c in vra_anac.agrupar(voos, por="voo").items():
|
|
61
|
+
print(f"{t.rotulo(chave)}: {c.pct('chegada')}% no horário (n = {c.n})")
|
|
62
|
+
# G3 1015 SDU→CGH: 100% no horário (n = 1)
|
|
63
|
+
# G3 1016 CGH→SDU: 0% no horário (n = 2)
|
|
64
|
+
# ...
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Pela linha de comando, a tabela do mês (ou `--json`):
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
vra-anac VRA_20268.csv --por companhia
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
VRA da ANAC, 08/2026 (arquivo atualizado em 2026-10-07): 68.355 voos domésticos regulares, 1 fora do mês.
|
|
75
|
+
No horário = chegada até 15 min depois da prevista. Só os grupos com 20 voos ou mais.
|
|
76
|
+
|
|
77
|
+
no horário cancelado voos companhia
|
|
78
|
+
87% 1% 23.080 Azul (AZU)
|
|
79
|
+
84% 2% 20.295 GOL (GLO)
|
|
80
|
+
78% 1% 23.850 LATAM (TAM)
|
|
81
|
+
76% 16% 1.098 Azul Conecta (ACN)
|
|
82
|
+
0% 100% 24 Apuí Táxi Aéreo (CQB)
|
|
83
|
+
|
|
84
|
+
Dados: ANAC, Voo Regular Ativo (VRA), licença CC-BY. https://www.gov.br/anac/...
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`--por` aceita `voo` (padrão), `rota`, `companhia`, `origem` e `destino`;
|
|
88
|
+
`--minimo` (padrão 20) deixa de fora o grupo com poucos voos, que oscila por
|
|
89
|
+
acaso de um mês para o outro.
|
|
90
|
+
|
|
91
|
+
## Onde baixar o CSV
|
|
92
|
+
|
|
93
|
+
Na página do [Voo Regular Ativo (VRA)](https://www.gov.br/anac/pt-br/acesso-a-informacao/dados-abertos/areas-de-atuacao/voos-e-operacoes-aereas/voo-regular-ativo-vra)
|
|
94
|
+
da ANAC, um arquivo por mês (`VRA_AAAAM.csv`, o mês sem zero). A ANAC
|
|
95
|
+
publica com cerca de 4 semanas de atraso.
|
|
96
|
+
|
|
97
|
+
## O que a conta faz
|
|
98
|
+
|
|
99
|
+
- **Voo doméstico regular**: tipo de linha `N` e código de autorização `0`.
|
|
100
|
+
Extras, fretamentos e voos internacionais ficam de fora.
|
|
101
|
+
- **No horário**: chegada real até 15 min depois da prevista; adiantado
|
|
102
|
+
conta como no horário. A partida tem o mesmo critério, ao lado
|
|
103
|
+
(`c.pct("partida")`).
|
|
104
|
+
- **Faixas**: até 30 min, até 60 min, mais de 60 min e cancelado
|
|
105
|
+
(`c.distribuicao("chegada")`). O atraso é contado em segundos inteiros:
|
|
106
|
+
30 min exatos ficam na faixa de até 30.
|
|
107
|
+
- **Cancelado** conta no total e nunca como no horário.
|
|
108
|
+
- **Porcentagem** arredondada com o meio para cima (12,5 → 13).
|
|
109
|
+
- **O voo** é empresa + número + origem + destino. O número sai sem o `Z`
|
|
110
|
+
que o VRA às vezes põe na frente (`Z1001` e `1001` são o mesmo voo
|
|
111
|
+
vendido). Isso é inferência, conferida em alguns voos contra o horário
|
|
112
|
+
publicado pela companhia, não regra escrita da ANAC.
|
|
113
|
+
- **O mês** sai das datas das linhas, não do nome do arquivo; o que cai em
|
|
114
|
+
outro mês aparece contado à parte (`vra_anac.periodo`).
|
|
115
|
+
- Os horários são os do arquivo, em Brasília.
|
|
116
|
+
|
|
117
|
+
## O que vem junto
|
|
118
|
+
|
|
119
|
+
As tabelas que levam o código ICAO do VRA ao que aparece no bilhete
|
|
120
|
+
(`GLO` → `G3`, `SBSP` → `CGH Congonhas`): companhias do cadastro da ANAC e
|
|
121
|
+
do diretório da IATA, e aeroportos da lista de aeródromos públicos da ANAC
|
|
122
|
+
e do OurAirports (domínio público). Cada linha cita a fonte e a data da
|
|
123
|
+
consulta (`t.companhias`, `t.aeroportos`). Código fora da tabela aparece
|
|
124
|
+
como veio no arquivo.
|
|
125
|
+
|
|
126
|
+
## Como é testado
|
|
127
|
+
|
|
128
|
+
`python -m unittest`, do checkout, sem instalar nada. A amostra dos testes
|
|
129
|
+
tem linhas reais do VRA de julho/2026, com os bytes do arquivo da ANAC (BOM
|
|
130
|
+
e CRLF), e cada número esperado foi contado à mão. O CI roda do Python 3.11
|
|
131
|
+
ao 3.14 e no Windows, e instala o pacote num ambiente limpo antes de cada
|
|
132
|
+
versão.
|
|
133
|
+
|
|
134
|
+
## Fontes e limites
|
|
135
|
+
|
|
136
|
+
- **Dados: ANAC, Voo Regular Ativo (VRA), licença
|
|
137
|
+
[CC-BY](https://opendefinition.org/licenses/cc-by/).** Quem publicar
|
|
138
|
+
resultado feito com esta biblioteca precisa dar o crédito à ANAC; a linha
|
|
139
|
+
de comando já escreve a atribuição na saída.
|
|
140
|
+
- Esta biblioteca **não é da ANAC** nem de companhia aérea. O resultado
|
|
141
|
+
mostra o que aconteceu no mês, não prevê o próximo voo.
|
|
142
|
+
- O VRA não traz o motivo do atraso (o campo de justificativa vem vazio).
|
|
143
|
+
|
|
144
|
+
## Contribuir
|
|
145
|
+
|
|
146
|
+
Ver o [CONTRIBUTING.md](https://github.com/peterwkdev-creator/vra-anac/blob/main/CONTRIBUTING.md).
|
|
147
|
+
Resultado diferente da sua conta: abra uma issue pelo modelo "Resultado
|
|
148
|
+
diferente do esperado".
|
|
149
|
+
|
|
150
|
+
## Licença
|
|
151
|
+
|
|
152
|
+
Código sob a [licença MIT](https://github.com/peterwkdev-creator/vra-anac/blob/main/LICENSE).
|
|
153
|
+
Os dados do VRA são da ANAC, sob CC-BY.
|
vra_anac-0.1.0/README.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# vra-anac
|
|
2
|
+
|
|
3
|
+
[](https://github.com/peterwkdev-creator/vra-anac/actions/workflows/testes.yml)
|
|
4
|
+
[](https://pypi.org/project/vra-anac/)
|
|
5
|
+
[](https://pypi.org/project/vra-anac/)
|
|
6
|
+
[](https://github.com/peterwkdev-creator/vra-anac/blob/main/LICENSE)
|
|
7
|
+
|
|
8
|
+
Quantos voos domésticos chegaram no horário, por voo, rota, companhia ou
|
|
9
|
+
aeroporto, a partir do arquivo mensal do **VRA (Voo Regular Ativo) da
|
|
10
|
+
ANAC**. Lê o CSV como a ANAC publica e aplica a mesma régua do
|
|
11
|
+
[chegounahora.com.br](https://chegounahora.com.br/): **no horário = chegada
|
|
12
|
+
até 15 min depois da prevista**.
|
|
13
|
+
|
|
14
|
+
Só a biblioteca padrão do Python, sem rede: você baixa o CSV, ela faz a conta.
|
|
15
|
+
|
|
16
|
+

|
|
17
|
+
|
|
18
|
+
## Instalar
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install vra-anac
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Ou uma versão exata, pela tag do GitHub:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install "git+https://github.com/peterwkdev-creator/vra-anac@v0.1.0"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Python 3.11 ou mais novo.
|
|
31
|
+
|
|
32
|
+
## Usar
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
import vra_anac
|
|
36
|
+
|
|
37
|
+
# o CSV do mês que você baixou da ANAC (aqui, a amostra dos testes)
|
|
38
|
+
atualizado, voos = vra_anac.ler_arquivo("tests/fixtures/vra-amostra.csv")
|
|
39
|
+
t = vra_anac.tabelas()
|
|
40
|
+
|
|
41
|
+
for chave, c in vra_anac.agrupar(voos, por="voo").items():
|
|
42
|
+
print(f"{t.rotulo(chave)}: {c.pct('chegada')}% no horário (n = {c.n})")
|
|
43
|
+
# G3 1015 SDU→CGH: 100% no horário (n = 1)
|
|
44
|
+
# G3 1016 CGH→SDU: 0% no horário (n = 2)
|
|
45
|
+
# ...
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Pela linha de comando, a tabela do mês (ou `--json`):
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
vra-anac VRA_20268.csv --por companhia
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
VRA da ANAC, 08/2026 (arquivo atualizado em 2026-10-07): 68.355 voos domésticos regulares, 1 fora do mês.
|
|
56
|
+
No horário = chegada até 15 min depois da prevista. Só os grupos com 20 voos ou mais.
|
|
57
|
+
|
|
58
|
+
no horário cancelado voos companhia
|
|
59
|
+
87% 1% 23.080 Azul (AZU)
|
|
60
|
+
84% 2% 20.295 GOL (GLO)
|
|
61
|
+
78% 1% 23.850 LATAM (TAM)
|
|
62
|
+
76% 16% 1.098 Azul Conecta (ACN)
|
|
63
|
+
0% 100% 24 Apuí Táxi Aéreo (CQB)
|
|
64
|
+
|
|
65
|
+
Dados: ANAC, Voo Regular Ativo (VRA), licença CC-BY. https://www.gov.br/anac/...
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`--por` aceita `voo` (padrão), `rota`, `companhia`, `origem` e `destino`;
|
|
69
|
+
`--minimo` (padrão 20) deixa de fora o grupo com poucos voos, que oscila por
|
|
70
|
+
acaso de um mês para o outro.
|
|
71
|
+
|
|
72
|
+
## Onde baixar o CSV
|
|
73
|
+
|
|
74
|
+
Na página do [Voo Regular Ativo (VRA)](https://www.gov.br/anac/pt-br/acesso-a-informacao/dados-abertos/areas-de-atuacao/voos-e-operacoes-aereas/voo-regular-ativo-vra)
|
|
75
|
+
da ANAC, um arquivo por mês (`VRA_AAAAM.csv`, o mês sem zero). A ANAC
|
|
76
|
+
publica com cerca de 4 semanas de atraso.
|
|
77
|
+
|
|
78
|
+
## O que a conta faz
|
|
79
|
+
|
|
80
|
+
- **Voo doméstico regular**: tipo de linha `N` e código de autorização `0`.
|
|
81
|
+
Extras, fretamentos e voos internacionais ficam de fora.
|
|
82
|
+
- **No horário**: chegada real até 15 min depois da prevista; adiantado
|
|
83
|
+
conta como no horário. A partida tem o mesmo critério, ao lado
|
|
84
|
+
(`c.pct("partida")`).
|
|
85
|
+
- **Faixas**: até 30 min, até 60 min, mais de 60 min e cancelado
|
|
86
|
+
(`c.distribuicao("chegada")`). O atraso é contado em segundos inteiros:
|
|
87
|
+
30 min exatos ficam na faixa de até 30.
|
|
88
|
+
- **Cancelado** conta no total e nunca como no horário.
|
|
89
|
+
- **Porcentagem** arredondada com o meio para cima (12,5 → 13).
|
|
90
|
+
- **O voo** é empresa + número + origem + destino. O número sai sem o `Z`
|
|
91
|
+
que o VRA às vezes põe na frente (`Z1001` e `1001` são o mesmo voo
|
|
92
|
+
vendido). Isso é inferência, conferida em alguns voos contra o horário
|
|
93
|
+
publicado pela companhia, não regra escrita da ANAC.
|
|
94
|
+
- **O mês** sai das datas das linhas, não do nome do arquivo; o que cai em
|
|
95
|
+
outro mês aparece contado à parte (`vra_anac.periodo`).
|
|
96
|
+
- Os horários são os do arquivo, em Brasília.
|
|
97
|
+
|
|
98
|
+
## O que vem junto
|
|
99
|
+
|
|
100
|
+
As tabelas que levam o código ICAO do VRA ao que aparece no bilhete
|
|
101
|
+
(`GLO` → `G3`, `SBSP` → `CGH Congonhas`): companhias do cadastro da ANAC e
|
|
102
|
+
do diretório da IATA, e aeroportos da lista de aeródromos públicos da ANAC
|
|
103
|
+
e do OurAirports (domínio público). Cada linha cita a fonte e a data da
|
|
104
|
+
consulta (`t.companhias`, `t.aeroportos`). Código fora da tabela aparece
|
|
105
|
+
como veio no arquivo.
|
|
106
|
+
|
|
107
|
+
## Como é testado
|
|
108
|
+
|
|
109
|
+
`python -m unittest`, do checkout, sem instalar nada. A amostra dos testes
|
|
110
|
+
tem linhas reais do VRA de julho/2026, com os bytes do arquivo da ANAC (BOM
|
|
111
|
+
e CRLF), e cada número esperado foi contado à mão. O CI roda do Python 3.11
|
|
112
|
+
ao 3.14 e no Windows, e instala o pacote num ambiente limpo antes de cada
|
|
113
|
+
versão.
|
|
114
|
+
|
|
115
|
+
## Fontes e limites
|
|
116
|
+
|
|
117
|
+
- **Dados: ANAC, Voo Regular Ativo (VRA), licença
|
|
118
|
+
[CC-BY](https://opendefinition.org/licenses/cc-by/).** Quem publicar
|
|
119
|
+
resultado feito com esta biblioteca precisa dar o crédito à ANAC; a linha
|
|
120
|
+
de comando já escreve a atribuição na saída.
|
|
121
|
+
- Esta biblioteca **não é da ANAC** nem de companhia aérea. O resultado
|
|
122
|
+
mostra o que aconteceu no mês, não prevê o próximo voo.
|
|
123
|
+
- O VRA não traz o motivo do atraso (o campo de justificativa vem vazio).
|
|
124
|
+
|
|
125
|
+
## Contribuir
|
|
126
|
+
|
|
127
|
+
Ver o [CONTRIBUTING.md](https://github.com/peterwkdev-creator/vra-anac/blob/main/CONTRIBUTING.md).
|
|
128
|
+
Resultado diferente da sua conta: abra uma issue pelo modelo "Resultado
|
|
129
|
+
diferente do esperado".
|
|
130
|
+
|
|
131
|
+
## Licença
|
|
132
|
+
|
|
133
|
+
Código sob a [licença MIT](https://github.com/peterwkdev-creator/vra-anac/blob/main/LICENSE).
|
|
134
|
+
Os dados do VRA são da ANAC, sob CC-BY.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "vra-anac"
|
|
7
|
+
# A versão tem uma fonte só: o __version__ do pacote (lido sem importar).
|
|
8
|
+
dynamic = ["version"]
|
|
9
|
+
# Uma frase só, até 100 caracteres (teto do catálogo MCP): a mesma no About
|
|
10
|
+
# do GitHub (gh repo edit --description) e no server.json, se houver.
|
|
11
|
+
description = "Pontualidade dos voos domésticos do Brasil pelo VRA da ANAC: lê o CSV do mês e calcula o indicador"
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
license = "MIT"
|
|
14
|
+
license-files = ["LICENSE"]
|
|
15
|
+
authors = [{ name = "Peter" }]
|
|
16
|
+
requires-python = ">=3.11"
|
|
17
|
+
dependencies = []
|
|
18
|
+
keywords = ["anac", "vra", "aviacao", "voos", "pontualidade", "atrasos", "dados-abertos", "brasil"]
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Natural Language :: Portuguese (Brazilian)",
|
|
22
|
+
"Topic :: Scientific/Engineering :: Information Analysis",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
# sem vitrine (Pages) na 0.1: a página inicial é o próprio repositório
|
|
26
|
+
# (nomes que o PyPI reconhece)
|
|
27
|
+
[project.urls]
|
|
28
|
+
Homepage = "https://github.com/peterwkdev-creator/vra-anac"
|
|
29
|
+
Source = "https://github.com/peterwkdev-creator/vra-anac"
|
|
30
|
+
Issues = "https://github.com/peterwkdev-creator/vra-anac/issues"
|
|
31
|
+
Changelog = "https://github.com/peterwkdev-creator/vra-anac/blob/main/CHANGELOG.md"
|
|
32
|
+
|
|
33
|
+
# o comando que o pip instala no PATH, além do `python -m vra_anac`
|
|
34
|
+
[project.scripts]
|
|
35
|
+
vra-anac = "vra_anac.__main__:main"
|
|
36
|
+
|
|
37
|
+
[tool.setuptools]
|
|
38
|
+
# a pasta das tabelas entra aqui também: sem isto, o setuptools avisa que a
|
|
39
|
+
# vê como pacote e não sabe se é para levar
|
|
40
|
+
packages = ["vra_anac", "vra_anac.dados"]
|
|
41
|
+
|
|
42
|
+
# as tabelas de companhias e aeroportos só vão na instalação se estiverem
|
|
43
|
+
# aqui; sem isto, FileNotFoundError fora do checkout.
|
|
44
|
+
[tool.setuptools.package-data]
|
|
45
|
+
vra_anac = ["dados/*.csv"]
|
|
46
|
+
|
|
47
|
+
[tool.setuptools.dynamic]
|
|
48
|
+
version = { attr = "vra_anac.__version__" }
|
vra_anac-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""A linha de comando: saída em texto e JSON, erros e ajuda em português."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import contextlib
|
|
5
|
+
import io
|
|
6
|
+
import json
|
|
7
|
+
import re
|
|
8
|
+
import sys
|
|
9
|
+
import unittest
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from unittest import mock
|
|
12
|
+
|
|
13
|
+
from vra_anac.__main__ import ATRIBUICAO, _milhar, main
|
|
14
|
+
|
|
15
|
+
FIXTURE = str(Path(__file__).parent / "fixtures" / "vra-amostra.csv")
|
|
16
|
+
INGLES = re.compile(r"\b(usage|options|positional|arguments?|required|invalid|"
|
|
17
|
+
r"error|show this help|expected)\b", re.I)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _rodar(*args):
|
|
21
|
+
saida, erro = io.StringIO(), io.StringIO()
|
|
22
|
+
with contextlib.redirect_stdout(saida), contextlib.redirect_stderr(erro):
|
|
23
|
+
try:
|
|
24
|
+
codigo = main(list(args))
|
|
25
|
+
except SystemExit as e:
|
|
26
|
+
codigo = e.code
|
|
27
|
+
return codigo, saida.getvalue(), erro.getvalue()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Saida(unittest.TestCase):
|
|
31
|
+
def test_texto_por_voo(self):
|
|
32
|
+
codigo, saida, _ = _rodar(FIXTURE, "--minimo", "1")
|
|
33
|
+
self.assertEqual(codigo, 0)
|
|
34
|
+
self.assertIn("07/2026 (arquivo atualizado em 2026-08-28): 5 voos domésticos "
|
|
35
|
+
"regulares, 1 fora do mês.", saida)
|
|
36
|
+
self.assertIn("Só os grupos com 1 voo ou mais.", saida)
|
|
37
|
+
linhas = [l.split() for l in saida.splitlines() if "%" in l]
|
|
38
|
+
# o de 100% primeiro; depois, no empate em 0%, o de mais voos
|
|
39
|
+
self.assertEqual(linhas[0], ["100%", "0%", "1", "G3", "1015", "SDU→CGH"])
|
|
40
|
+
self.assertEqual(linhas[1], ["0%", "50%", "2", "G3", "1016", "CGH→SDU"])
|
|
41
|
+
self.assertTrue(saida.rstrip().endswith(ATRIBUICAO))
|
|
42
|
+
|
|
43
|
+
def test_milhar_com_ponto(self):
|
|
44
|
+
self.assertEqual([_milhar(n) for n in (5, 999, 1098, 68355, 1234567)],
|
|
45
|
+
["5", "999", "1.098", "68.355", "1.234.567"])
|
|
46
|
+
|
|
47
|
+
def test_minimo_padrao_20_deixa_a_fixture_vazia(self):
|
|
48
|
+
codigo, saida, _ = _rodar(FIXTURE)
|
|
49
|
+
self.assertEqual(codigo, 0)
|
|
50
|
+
self.assertIn("Nenhum grupo com 20 voos ou mais.", saida)
|
|
51
|
+
|
|
52
|
+
def test_json(self):
|
|
53
|
+
codigo, saida, _ = _rodar(FIXTURE, "--por", "companhia", "--minimo", "1", "--json")
|
|
54
|
+
self.assertEqual(codigo, 0)
|
|
55
|
+
d = json.loads(saida)
|
|
56
|
+
self.assertEqual((d["mes"], d["voos"], d["fora_do_mes"]), ("2026-07", 5, 1))
|
|
57
|
+
self.assertIn("ANAC", d["atribuicao"])
|
|
58
|
+
glo = d["grupos"][0]
|
|
59
|
+
self.assertEqual((glo["rotulo"], glo["voos"], glo["chegada_no_horario_pct"],
|
|
60
|
+
glo["cancelado_pct"]), ("GOL (GLO)", 4, 25, 25))
|
|
61
|
+
self.assertEqual(glo["chegada"], {"no_horario": 25, "ate30": 0, "ate60": 50,
|
|
62
|
+
"acima60": 0, "cancelado": 25})
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class Erros(unittest.TestCase):
|
|
66
|
+
def test_arquivo_ausente(self):
|
|
67
|
+
codigo, _, erro = _rodar("nao-existe.csv")
|
|
68
|
+
self.assertEqual(codigo, 1)
|
|
69
|
+
self.assertIn("erro: arquivo não encontrado: nao-existe.csv", erro)
|
|
70
|
+
|
|
71
|
+
def test_pasta(self):
|
|
72
|
+
codigo, _, erro = _rodar(str(Path(__file__).parent))
|
|
73
|
+
self.assertEqual(codigo, 1)
|
|
74
|
+
self.assertIn("é uma pasta", erro)
|
|
75
|
+
|
|
76
|
+
def test_arquivo_que_nao_e_o_vra(self):
|
|
77
|
+
codigo, _, erro = _rodar(__file__)
|
|
78
|
+
self.assertEqual(codigo, 1)
|
|
79
|
+
self.assertIn("1ª linha não é o metadado esperado", erro)
|
|
80
|
+
|
|
81
|
+
def test_erros_do_argparse_em_portugues(self):
|
|
82
|
+
for args in ([], [FIXTURE, "--minimo", "0"], [FIXTURE, "--por", "estado"],
|
|
83
|
+
[FIXTURE, "--xyz"]):
|
|
84
|
+
with self.subTest(args=args):
|
|
85
|
+
codigo, _, erro = _rodar(*args)
|
|
86
|
+
self.assertEqual(codigo, 2)
|
|
87
|
+
self.assertIn("uso:", erro)
|
|
88
|
+
self.assertIsNone(INGLES.search(erro), erro)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class Ajuda(unittest.TestCase):
|
|
92
|
+
def test_ajuda_em_portugues_com_atribuicao(self):
|
|
93
|
+
codigo, saida, _ = _rodar("--help")
|
|
94
|
+
self.assertEqual(codigo, 0)
|
|
95
|
+
self.assertTrue(saida.startswith("uso: "))
|
|
96
|
+
self.assertIsNone(INGLES.search(saida), saida)
|
|
97
|
+
self.assertIn("ANAC", saida)
|
|
98
|
+
|
|
99
|
+
def test_nome_do_comando_instalado(self):
|
|
100
|
+
with mock.patch.object(sys, "argv", ["/venv/bin/vra-anac"]):
|
|
101
|
+
_, saida, _ = _rodar("--help")
|
|
102
|
+
self.assertTrue(saida.startswith("uso: vra-anac "))
|
|
103
|
+
with mock.patch.object(sys, "argv", ["/x/vra_anac/__main__.py"]):
|
|
104
|
+
_, saida, _ = _rodar("--help")
|
|
105
|
+
self.assertTrue(saida.startswith("uso: python -m vra_anac "))
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
if __name__ == "__main__":
|
|
109
|
+
unittest.main()
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""O indicador: no horário até 15 min pela chegada, faixas 30 e 60,
|
|
2
|
+
cancelado sobre o total. Cada número esperado foi contado à mão."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import unittest
|
|
6
|
+
from datetime import datetime, timedelta
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import vra_anac
|
|
10
|
+
from vra_anac import indicador
|
|
11
|
+
from vra_anac.leitura import Operacao
|
|
12
|
+
|
|
13
|
+
FIXTURE = Path(__file__).parent / "fixtures" / "vra-amostra.csv"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _op(chegada_min=0, partida_min=0, cancelado=False, numero="1000",
|
|
17
|
+
empresa="GLO", origem="SBSP", destino="SBRJ"):
|
|
18
|
+
"""Voo inventado: atrasos em minutos sobre o previsto."""
|
|
19
|
+
pp = datetime(2026, 7, 1, 10, 0)
|
|
20
|
+
cp = pp + timedelta(hours=1)
|
|
21
|
+
pr = None if cancelado else pp + timedelta(minutes=partida_min)
|
|
22
|
+
cr = None if cancelado else cp + timedelta(minutes=chegada_min)
|
|
23
|
+
return Operacao(empresa, numero, origem, destino, pp, pr, cp, cr, cancelado)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Porcento(unittest.TestCase):
|
|
27
|
+
def test_meio_para_cima(self):
|
|
28
|
+
self.assertEqual(indicador.porcento(1, 8), 13) # 12,5
|
|
29
|
+
self.assertEqual(indicador.porcento(1, 200), 1) # 0,5; round() daria 0
|
|
30
|
+
self.assertEqual(indicador.porcento(0, 3), 0)
|
|
31
|
+
|
|
32
|
+
def test_sobre_zero(self):
|
|
33
|
+
with self.assertRaises(ValueError):
|
|
34
|
+
indicador.porcento(0, 0)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Limites(unittest.TestCase):
|
|
38
|
+
def test_15_min_exatos_e_no_horario_15_e_1_s_nao(self):
|
|
39
|
+
c = vra_anac.Contagem()
|
|
40
|
+
c.somar(_op(chegada_min=15))
|
|
41
|
+
op = _op()
|
|
42
|
+
c.somar(Operacao(op.empresa, op.numero, op.origem, op.destino, op.partida_prevista,
|
|
43
|
+
op.partida_real, op.chegada_prevista,
|
|
44
|
+
op.chegada_prevista + timedelta(minutes=15, seconds=1), False))
|
|
45
|
+
self.assertEqual(c.chegada_no_horario, 1)
|
|
46
|
+
|
|
47
|
+
def test_30_e_60_exatos_nao_passam_da_faixa(self):
|
|
48
|
+
c = vra_anac.Contagem()
|
|
49
|
+
for m in (30, 31, 60, 61):
|
|
50
|
+
c.somar(_op(chegada_min=m))
|
|
51
|
+
self.assertEqual(c.chegada_acima, {30: 3, 60: 1})
|
|
52
|
+
|
|
53
|
+
def test_adiantado_conta_no_horario(self):
|
|
54
|
+
c = vra_anac.Contagem()
|
|
55
|
+
c.somar(_op(chegada_min=-20, partida_min=-5))
|
|
56
|
+
self.assertEqual((c.chegada_no_horario, c.partida_no_horario), (1, 1))
|
|
57
|
+
|
|
58
|
+
def test_realizado_sem_horario_real_para(self):
|
|
59
|
+
op = _op()
|
|
60
|
+
sem = Operacao(op.empresa, op.numero, op.origem, op.destino, op.partida_prevista,
|
|
61
|
+
op.partida_real, op.chegada_prevista, None, False)
|
|
62
|
+
with self.assertRaisesRegex(ValueError, "sem horário real"):
|
|
63
|
+
vra_anac.Contagem().somar(sem)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class Contagem(unittest.TestCase):
|
|
67
|
+
def setUp(self):
|
|
68
|
+
self.c = vra_anac.Contagem()
|
|
69
|
+
# 8 voos: 4 no horário, 1 com 20 min, 1 com 45, 1 com 90, 1 cancelado
|
|
70
|
+
for m in (0, 5, -3, 15, 20, 45, 90):
|
|
71
|
+
self.c.somar(_op(chegada_min=m, partida_min=m + 10))
|
|
72
|
+
self.c.somar(_op(cancelado=True))
|
|
73
|
+
|
|
74
|
+
def test_percentuais(self):
|
|
75
|
+
self.assertEqual(self.c.n, 8)
|
|
76
|
+
self.assertEqual(self.c.pct("chegada"), 50)
|
|
77
|
+
self.assertEqual(self.c.pct("cancelado"), 13) # 12,5
|
|
78
|
+
self.assertEqual(self.c.pct("chegada>30"), 25) # 45 e 90
|
|
79
|
+
self.assertEqual(self.c.pct("chegada>60"), 13) # 90
|
|
80
|
+
# partida = chegada + 10: no horário 0, 5 e -3 (o 15 vira 25)
|
|
81
|
+
self.assertEqual(self.c.pct("partida"), 38) # 3/8 = 37,5
|
|
82
|
+
|
|
83
|
+
def test_distribuicao_soma_o_n(self):
|
|
84
|
+
fatias = self.c.distribuicao("chegada")
|
|
85
|
+
self.assertEqual([(f.chave, f.voos) for f in fatias],
|
|
86
|
+
[("no_horario", 4), ("ate30", 1), ("ate60", 1),
|
|
87
|
+
("acima60", 1), ("cancelado", 1)])
|
|
88
|
+
self.assertEqual(sum(f.voos for f in fatias), self.c.n)
|
|
89
|
+
self.assertEqual(fatias[1].largura, "12.500")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class Agrupar(unittest.TestCase):
|
|
93
|
+
def setUp(self):
|
|
94
|
+
_, self.ops = vra_anac.ler_arquivo(FIXTURE)
|
|
95
|
+
|
|
96
|
+
def test_por_voo_na_fixture(self):
|
|
97
|
+
g = vra_anac.agrupar(self.ops, "voo")
|
|
98
|
+
self.assertEqual(len(g), 4)
|
|
99
|
+
c = g[("GLO", "1016", "SBSP", "SBRJ")]
|
|
100
|
+
# 23/07 chegou 56 min depois; 15/07 cancelado
|
|
101
|
+
self.assertEqual((c.n, c.pct("chegada"), c.pct("cancelado"), c.pct("chegada>30")),
|
|
102
|
+
(2, 0, 50, 50))
|
|
103
|
+
self.assertEqual(g[("GLO", "1015", "SBRJ", "SBSP")].pct("chegada"), 100)
|
|
104
|
+
|
|
105
|
+
def test_por_companhia_rota_origem_destino(self):
|
|
106
|
+
self.assertEqual({k: c.n for k, c in vra_anac.agrupar(self.ops, "companhia").items()},
|
|
107
|
+
{("GLO",): 4, ("AZU",): 1})
|
|
108
|
+
self.assertEqual(vra_anac.agrupar(self.ops, "companhia")[("GLO",)].pct("chegada"), 25)
|
|
109
|
+
self.assertEqual(vra_anac.agrupar(self.ops, "rota")[("SBSP", "SBRJ")].n, 2)
|
|
110
|
+
self.assertEqual(vra_anac.agrupar(self.ops, "origem")[("SBSP",)].n, 2)
|
|
111
|
+
self.assertEqual(vra_anac.agrupar(self.ops, "destino")[("SBRJ",)].n, 2)
|
|
112
|
+
|
|
113
|
+
def test_minimo(self):
|
|
114
|
+
self.assertEqual(list(vra_anac.agrupar(self.ops, "voo", minimo=2)),
|
|
115
|
+
[("GLO", "1016", "SBSP", "SBRJ")])
|
|
116
|
+
|
|
117
|
+
def test_numero_com_z_soma_no_mesmo_voo(self):
|
|
118
|
+
g = vra_anac.agrupar([_op(numero="1000"), _op(numero="Z1000")], "voo")
|
|
119
|
+
self.assertEqual({k: c.n for k, c in g.items()}, {("GLO", "1000", "SBSP", "SBRJ"): 2})
|
|
120
|
+
|
|
121
|
+
def test_agrupamento_desconhecido(self):
|
|
122
|
+
with self.assertRaisesRegex(ValueError, "agrupamento desconhecido"):
|
|
123
|
+
vra_anac.agrupar(self.ops, "estado")
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
if __name__ == "__main__":
|
|
127
|
+
unittest.main()
|