Metadata-Version: 2.4
Name: etl-ativa-investimentos
Version: 0.1.6
Summary: 
Author: Gustavo Rizzo S M de Albuquerque
Author-email: grizzo.albu@gmail.com
Requires-Python: >=3.11
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Provides-Extra: pydantic
Requires-Dist: pandas (>=3.0.1,<4.0.0)
Requires-Dist: pandera (>=0.31.1,<0.32.0)
Requires-Dist: pydantic (>=2.0,<3.0) ; extra == "pydantic"
Requires-Dist: python-calamine (>=0.6.2,<0.7.0)
Description-Content-Type: text/markdown

# etl-ativa-investimentos

[![PyPI](https://img.shields.io/pypi/v/etl-ativa-investimentos.svg)](https://pypi.org/project/etl-ativa-investimentos/)

Biblioteca Python para leitura dos arquivos Excel exportados pela corretora **Ativa Investimentos**, convertendo-os em `pandas.DataFrame` prontos para análise, pipelines de dados e integrações ETL.

---

## Instalação

```bash
pip install etl-ativa-investimentos
```

**Requisitos:** Python >= 3.11

---

## Motivação

A Ativa Investimentos exporta relatórios em Excel com múltiplas abas e nomes de colunas hostis a uso programático (acentuação inconsistente, espaços duplicados, typos). Esta biblioteca abstrai a leitura desses arquivos, entregando DataFrames com colunas em `snake_case` pythônico e tipos coagidos — sem precisar se preocupar com nomes de abas, encoding, engine de leitura ou o de-para das colunas.

---

## Normalização de colunas

Por padrão, todo `read_*` devolve colunas normalizadas: `snake_case`, minúsculas, sem acentos, sem espaços — e colunas numéricas já coagidas (ex.: `qtd`, que no Excel vem como texto no formato pt-BR `"1.500,00"`).

```python
df = carteira_cotizada.read_carteira_analitica(path)
df.columns  # Index(['mercado', 'ativo', 'qtd', 'pu_custo', ...])
df["qtd"].dtype  # float64
```

Para acessar o dado exatamente como está no Excel (nomes originais, sem coerção de tipo), use `raw=True`:

```python
df_raw = carteira_cotizada.read_carteira_analitica(path, raw=True)
df_raw.columns  # Index(['Mercado', 'Ativo', 'QTD', 'PU Custo', ...])
df_raw["QTD"].dtype  # object (string "100", "26,00", ...)
```

O de-para completo (coluna crua → coluna canônica) de cada aba está documentado em `docs/*.md` e implementado em [`normalize.py`](src/etl_ativa_investimentos/normalize.py).

---

## Módulos

A lib é organizada por tipo de relatório. Cada módulo expõe:

- Funções individuais por aba: `read_<nome_da_aba>(path)`
- Uma função `read_all(path)` que retorna todas as abas como `dict[str, DataFrame]`

---

### `posicao_consolidada`

Lê o relatório de **Posição Consolidada** (`.xls`), disponível no portal da corretora.

```python
from etl_ativa_investimentos import posicao_consolidada

path = "posicao_consolidada/2025_12_30.xls"

# Aba individual
df_acoes = posicao_consolidada.read_acoes(path)
df_rf    = posicao_consolidada.read_renda_fixa_privada(path)

# Todas as abas de uma vez
data = posicao_consolidada.read_all(path)
```

| Função | Aba do Excel | Colunas principais (normalizadas) |
|--------|--------------|--------------------|
| `read_acoes(path)` | Ações | codigo, nome, carteira, quantidade, preco, total |
| `read_clubes_e_fundos(path)` | Clubes e Fundos | nome_do_fundo, data, valor_da_aplicacao, cota, valor |
| `read_financeiro(path)` | Financeiro | conta, tipo, disponivel, projecao_liquidacao, total |
| `read_renda_fixa_privada(path)` | Renda Fixa Privada | ticker, emissor, remuneracao, pu_atual, quantidade, total |
| `read_renda_fixa_publica(path)` | Renda Fixa Pública | titulo, emissor, indexador, data_vencimento, pu_atual, total |
| `read_all(path)` | Todas | Retorna `dict[str, DataFrame]` |

Todas aceitam `raw: bool = False` — veja [Normalização de colunas](#normalização-de-colunas).

**Chaves retornadas por `read_all`:**

```python
{
    "acoes": ...,
    "clubes_e_fundos": ...,
    "financeiro": ...,
    "renda_fixa_privada": ...,
    "renda_fixa_publica": ...,
}
```

---

### `carteira_cotizada`

Lê o relatório de **Carteira Cotizada / Painel** (`.xlsx`), exportado pelo sistema SIM da corretora.

```python
from etl_ativa_investimentos import carteira_cotizada

path = "SIM.PAINEL.2026.01.30.10.01.36.xlsx"

# Aba individual
df = carteira_cotizada.read_carteira_analitica(path)

# Todas as abas de uma vez
data = carteira_cotizada.read_all(path)
```

| Função | Conteúdo |
|--------|----------|
| `read_variacao_patrimonial(path)` | Patrimônio bruto, líquido e variação percentual |
| `read_composicao_patrimonio(path)` | Composição por classe de ativo com provisões de IR e IOF |
| `read_carteira_analitica(path)` | Posição consolidada com preços, financeiros e L/P (`qtd` numérico) |
| `read_rentabilidade_carteira(path)` | Performance: dia, mês, 30 dias, ano, 12 meses, início |
| `read_rentabilidade_no_ano(path)` | Rentabilidade mensal no ano corrente por ativo (colunas `jan`…`dez`) |
| `read_rentabilidade_ultimos_meses(path)` | Histórico mensal comparado ao CDI e IBOVESPA (colunas `YYYY_MM`) |
| `read_rentabilidade_ativos(path)` | Performance individual por ativo em múltiplos períodos |
| `read_provisoes(path)` | Provisões de IR e IOF com datas e valores |
| `read_renda_fixa_detalhada(path)` | CDBs e títulos com PU de aquisição, atual, IR e IOF |
| `read_clubes_e_fundos_detalhados(path)` | Fundos com cotas, L/P, IR, IOF e valor líquido |
| `read_all(path)` | Todas as abas — retorna `dict[str, DataFrame]` |

Todas aceitam `raw: bool = False` — veja [Normalização de colunas](#normalização-de-colunas).

**Chaves retornadas por `read_all`:**

```python
{
    "variacao_patrimonial": ...,
    "composicao_patrimonio": ...,
    "carteira_analitica": ...,
    "rentabilidade_carteira": ...,
    "rentabilidade_no_ano": ...,
    "rentabilidade_ultimos_meses": ...,
    "rentabilidade_ativos": ...,
    "provisoes": ...,
    "renda_fixa_detalhada": ...,
    "clubes_e_fundos_detalhados": ...,
}
```

---

### `movimentacao_b3`

Lê o relatório de **Movimentação B3** (`.xlsx`), exportado diretamente pelo portal da B3 ou pela corretora.

```python
from etl_ativa_investimentos import movimentacao_b3

path = "movimentacao-2023-01-01-ate-31-12-2023.xlsx"

# Aba individual
df = movimentacao_b3.read_movimentacao(path)

# Todas as abas de uma vez
data = movimentacao_b3.read_all(path)
```

| Função | Aba do Excel | Colunas principais (normalizadas) |
|--------|--------------|--------------------|
| `read_movimentacao(path)` | Movimentação | entrada_saida, data, movimentacao, produto, instituicao, quantidade, preco_unitario, valor_da_operacao |
| `read_all(path)` | Todas | Retorna `dict[str, DataFrame]` |

Todas aceitam `raw: bool = False` — veja [Normalização de colunas](#normalização-de-colunas).

**Chaves retornadas por `read_all`:**

```python
{
    "movimentacao": ...,
}
```

---

## Tratamento de erros

Falhas de leitura são levantadas como exceções próprias da lib, todas em `etl_ativa_investimentos.exceptions` e derivadas de `EtlAtivaError`. Isso permite capturar qualquer falha da lib com um único `except` e repassar uma mensagem amigável ao usuário final (ex.: numa view Django).

```python
from etl_ativa_investimentos import carteira_cotizada
from etl_ativa_investimentos.exceptions import (
    EtlAtivaError,
    InvalidExcelFileError,
    SheetNotFoundError,
)

try:
    df = carteira_cotizada.read_carteira_analitica(arquivo_enviado)
except SheetNotFoundError as e:
    # e.report, e.sheet -- útil para logging estruturado
    return HttpResponseBadRequest(str(e))
except InvalidExcelFileError as e:
    return HttpResponseBadRequest(str(e))
except EtlAtivaError as e:
    # qualquer outra falha da lib
    return HttpResponseBadRequest(str(e))
```

| Exceção | Quando ocorre |
|---------|----------------|
| `EtlAtivaError` | Classe base — nunca levantada diretamente, use para capturar qualquer falha da lib |
| `SheetNotFoundError` | A aba esperada não existe no arquivo — geralmente indica que o arquivo é de outro relatório (ex.: passar uma Movimentação B3 para `carteira_cotizada.read_*`), ou que o layout do relatório da Ativa mudou. Expõe `.report` e `.sheet`. |
| `InvalidExcelFileError` | O arquivo não pôde ser interpretado como Excel (corrompido, formato incompatível, ou não é um Excel de verdade). Expõe `.report`. |

---

## Interface orientada a objetos (readers)

Além das funções funcionais, a lib oferece classes que encapsulam leitura, cache e validação:

```python
from etl_ativa_investimentos.readers.carteira_cotizada import CarteiraCotizada
from etl_ativa_investimentos.readers.posicao_consolidada import PosicaoConsolidada
from etl_ativa_investimentos.readers.movimentacao_b3 import MovimentacaoB3
```

Cada classe segue o mesmo contrato:

| Método | Descrição |
|--------|-----------|
| `.<aba>(raw=False)` | Retorna o `DataFrame` da aba (lazy load + cache, separado por `raw`) |
| `.<aba>_entities()` | Retorna `list[EntidadeTipada]` — a aba convertida em dataclasses |
| `.infer_excel_date()` | Tenta inferir a data do Excel pelo nome do arquivo; retorna `date` ou `None` |
| `.load()` | Carrega todas as abas em memória de uma vez |
| `.validate_all()` | Valida todas as abas (contra o DataFrame normalizado); retorna `ValidationResult` |
| `.load_and_print_errors()` | Carrega, valida e exibe erros formatados |

### Exemplo

```python
from etl_ativa_investimentos.readers.carteira_cotizada import CarteiraCotizada

carteira = CarteiraCotizada("SIM.PAINEL.2026.01.30.10.01.36.xlsx")

# Inferência da data a partir do nome do arquivo (YYYY.MM.DD)
excel_date = carteira.infer_excel_date()
print(excel_date)  # 2026-01-30

# Lazy load — lê só quando chamado, resultado fica em cache
df = carteira.carteira_analitica()

# Validação estruturada
result = carteira.validate_all()
if not result.ok:
    print(result.errors)  # {"nome_aba": ["mensagem de erro", ...]}

# Ou de forma conveniente
carteira.load_and_print_errors()
```

---

## Entidades tipadas

Além do `DataFrame`, cada aba pode ser obtida como `list[dataclass]` — útil para passar adiante (ex.: para um app Django) sem expor pandas na fronteira.

```python
carteira = CarteiraCotizada("SIM.PAINEL.2026.01.30.10.01.36.xlsx")

ativos = carteira.carteira_analitica_entities()  # list[CarteiraAnaliticaEntity]
ativos[0].ativo, ativos[0].qtd, ativos[0].pu_atual
```

As entidades são `dataclass(frozen=True, slots=True)`, definidas em `entities/<modulo>.py`, com campos espelhando os nomes canônicos (ver [Normalização de colunas](#normalização-de-colunas)). Abas com colunas dinâmicas (`rentabilidade_no_ano`, `rentabilidade_ultimos_meses`) agrupam essas colunas num campo `valores: dict[str, float]`.

### Pydantic (opcional)

Por padrão as entidades são dataclasses puras — zero dependência extra. Para validação/serialização com pydantic, instale o extra e use `as_pydantic`:

```bash
pip install etl-ativa-investimentos[pydantic]
```

```python
from etl_ativa_investimentos.entities._convert import as_pydantic
from etl_ativa_investimentos.entities.carteira_cotizada import CarteiraAnaliticaEntity

CarteiraAnaliticaModel = as_pydantic(CarteiraAnaliticaEntity)
CarteiraAnaliticaModel(**dados)  # valida e levanta pydantic.ValidationError se inválido
```

`as_pydantic` decora o dataclass existente em vez de duplicar a definição — nome e tipo de cada campo continuam vindo de uma única fonte.

---

## Exemplo completo (API funcional)

```python
from etl_ativa_investimentos import (
    posicao_consolidada,
    carteira_cotizada,
    movimentacao_b3,
)

# Posição consolidada
posicao = posicao_consolidada.read_all("posicao_consolidada/2025_12_30.xls")
print(posicao["acoes"])

# Carteira cotizada
painel = carteira_cotizada.read_all("SIM.PAINEL.2026.01.30.10.01.36.xlsx")
print(painel["carteira_analitica"])

# Movimentação B3
mov = movimentacao_b3.read_all("movimentacao-2023-01-01-ate-31-12-2023.xlsx")
print(mov["movimentacao"])
```

---

## Detalhes técnicos

- Todos os arquivos são lidos com `pandas.read_excel` usando o engine [`calamine`](https://github.com/tafia/calamine) — uma implementação em Rust, mais rápida e sem dependência do Java (ao contrário do `xlrd`/`openpyxl` para `.xls` antigos).
- Suporta tanto `.xls` (formato legado) quanto `.xlsx`.
- Por padrão, cada função normaliza nomes de coluna (`snake_case`) e coage tipos conhecidos (ex.: `qtd`). Passe `raw=True` para receber os dados exatamente como estão no Excel, sem nenhuma transformação — veja [Normalização de colunas](#normalização-de-colunas).
- Todo `read_*` aceita tanto um caminho (`str`/`Path`) quanto um objeto file-like em bytes (`BytesIO`, `UploadedFile` do Django, etc.) — útil quando o Excel chega via upload em vez de estar em disco:

  ```python
  df = carteira_cotizada.read_carteira_analitica(uploaded_file)  # objeto file-like
  ```

  `infer_excel_date()` retorna `None` quando a origem não tem nome de arquivo associado (ex.: `BytesIO`).
- A lib publica o marcador [`py.typed`](https://peps.python.org/pep-0561/) — type checkers (mypy/pyright) do projeto consumidor reconhecem os tipos exportados.

---

## Desenvolvimento

```bash
# Clonar e instalar dependências
git clone https://github.com/seu-usuario/etl-ativa-investimentos.git
cd etl-ativa-investimentos
poetry install

# Rodar testes
poetry run pytest -v
```

---

## Documentação completa

A referência completa da lib — guia rápido, inputs/outputs, normalização de colunas, entidades tipadas, tratamento de erros, formato de dados de cada relatório e referência de API — vive em `docs/` como um site [mkdocs-material](https://squidfunk.github.io/mkdocs-material/). Para servir localmente:

```bash
poetry install --with docs
poetry run mkdocs serve
```

Abre em `http://127.0.0.1:8000`. Para gerar o site estático:

```bash
poetry run mkdocs build
```

Atalhos para a documentação de formato de dados (o de-para completo de cada aba):

- [docs/formatos/carteira_cotizada.md](docs/formatos/carteira_cotizada.md) — 10 abas do relatório SIM Painel (`.xlsx`)
- [docs/formatos/posicao_consolidada.md](docs/formatos/posicao_consolidada.md) — 5 abas da Posição Consolidada (`.xls`)
- [docs/formatos/movimentacao_b3.md](docs/formatos/movimentacao_b3.md) — 1 aba da Movimentação B3 (`.xlsx`)

---

## Licença

MIT

