irpf-exterior
Núcleo de domínio da apuração de IRPF sobre investimentos no exterior por residentes no Brasil, sob a Lei nº 14.754/2023 ("Lei das Offshores"), com foco inicial em aplicações em Portugal e a compensação do imposto lá retido.
É uma biblioteca, não uma aplicação: só funções puras e dados imutáveis. Ela não lê arquivos, não acessa a rede e não consulta o relógio — tudo chega por parâmetro. Zero dependências de runtime.
Escopo
O que esta biblioteca faz:
- apura o imposto de um resgate, com a compensação Brasil–Portugal;
- explica cada número até as entradas, com fórmula, cotação e base legal;
- separa o que rendeu por câmbio do que rendeu pela aplicação;
- responde se investir compensou, ou compensaria, frente a deixar o dinheiro parado;
- simula cenários e resolve pontos de equilíbrio em forma fechada.
O que ela não faz — e não passará a fazer:
| Fora | Por quê |
|---|---|
| Buscar cotação (PTAX, BCB, qualquer provedor) | um resultado fiscal precisa ser reproduzível anos depois, sem rede |
| Ler extratos ou qualquer arquivo | entrada é parâmetro, não efeito colateral |
| Gerar o arquivo da declaração | formato de terceiro, muda por fora |
| Interface (web, CLI, notebook, Streamlit) | adaptador, mora em projeto separado |
| Persistir qualquer coisa | estado é do consumidor |
Isso não é purismo: uma cotação vinda da rede transforma um cálculo determinístico num cálculo que depende do dia e do uptime de terceiros. Quem busca a cotação é o consumidor; quem a recebe por parâmetro é a biblioteca.
portas.py declara os contratos (typing.Protocol) que um adaptador deve
cumprir — descrever o formato de uma cotação não é buscar uma cotação.
Instalação
pip install irpf-exterior # ou: uv add irpf-exterior
Para desenvolver, clone o repositório
e rode just sync (ou uv sync --all-extras).
Requer Python 3.12+. O extra simbolico (SymPy) só é necessário para gerar a
documentação de auditoria e rodar os testes simbólicos.
Uso
from datetime import date
from decimal import Decimal
from irpf_exterior import ApuracaoResgate, Cambio, brl, entrada, explicar
apuracao = ApuracaoResgate.de_cf(
I=entrada("I", brl("10000.00"), "extrato: aporte"),
F=brl("13200.00"), # sem fonte: tudo bem
C_i=Cambio(Decimal("5.00"), date(2024, 5, 2), "PTAX"),
C_f=Cambio(Decimal("6.00"), date(2025, 3, 10)),
)
print(apuracao.IR_ef.valor) # BRL 480.00
print(apuracao.a_pagar_no_brasil()) # BRL 144.00 (já compensado o IR português)
print(explicar(apuracao.IR_ef)) # a árvore inteira do cálculo
Os campos da declaração saem prontos:
for campo, valor in apuracao.de_para_receita().items():
print(f"{campo}: {valor.valor}")
O que está aqui
| Módulo | Conteúdo |
|---|---|
dominio/moeda.py |
BRL, EUR, Money[M], Cambio |
dominio/parametros.py |
Aliquota, TABELA_ALIQUOTAS, aliquota_vigente |
rastreio.py |
Calculado, entrada, explicar |
formulas/ |
as fórmulas 1–13 (apuração), 14–16 (análise) e 17–19 (projeção), e o REGISTRO |
apuracoes/resgate.py |
ApuracaoResgate |
simulacao/ |
cenários hipotéticos, varreduras preguiçosas e pontos de equilíbrio |
portas.py |
Protocols para os adaptadores externos (contratos, sem implementação) |
simbolico.py |
modelos SymPy que derivam as formas expandidas |
Simulação
Para quem ainda está decidindo investir, irpf_exterior.simulacao projeta o
valor resgatado a partir do juro esperado e responde onde está a virada:
from irpf_exterior.simulacao import Cenario, cambio_de_equilibrio
hipotese = Cenario(
I=brl("10000.00"),
C_i=Cambio(Decimal("5.00"), date(2024, 5, 2), "PTAX"),
J_eu=Decimal("0.01"), # a aplicação promete +1% em euros
C_f=Cambio(Decimal("8.00"), date(2025, 3, 10)),
)
print(cambio_de_equilibrio(hipotese).valor) # equilíbrio em 5.3004
Acima de R$ 5,30/€ não investir teria sido melhor — o imposto sobre o ganho cambial, isento se o dinheiro ficasse parado, come mais do que a aplicação rendeu. Nada é calculado antes de ser pedido.
Desenvolvimento
Os comandos estão no justfile — just sem argumentos lista todos:
just sync # cria a .venv com as dependências de desenvolvimento
just test # unit, propriedades, simbólicos, cenários e doctests
just types # mypy estrito
just lint # ruff check + format --check
just fmt # corrige e formata
just docs # regenera docs/auditoria/ a partir do REGISTRO
just cov # cobertura de testes (o número do badge)
just check # lint + types + test, o que um CI cobraria
just test e just docs repassam argumentos:
just test -q -k "formula_12 or compensacao".
Sem o just instalado, todo comando funciona
direto (uv run pytest, uv run mypy, ...); o justfile só unifica os nomes.
Documentação
docs/auditoria/catalogo-de-formulas.md— o que é calculado e com que base legal (gerado a partir do código).docs/desenvolvimento/guia-de-uso.md— como usar a API, com exemplos que rodam como testes.docs/desenvolvimento/guia-de-contribuicao.md— como acrescentar uma fórmula sem quebrar as garantias.pre-project-docs/ARQUITETURA.md— por que a biblioteca é assim.
Release files for irpf-exterior 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| irpf_exterior-0.1.0.tar.gz | 48.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| irpf_exterior-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 82.9 kB
Release files / irpf_exterior-0.1.0.tar.gz
| Download URL | irpf_exterior-0.1.0.tar.gz |
|---|---|
| Size | 48.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
314c9afe0e02957f9a62740eb6ebde197c5c5d38604e18c3a595684dc3c916df
|
|
BLAKE2b-256 checksum How to use checksums |
b9042095c8c6e4a0862e5f98b3dea0850b332baccd34f62b948c59b18afb0594
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / irpf_exterior-0.1.0-py3-none-any.whl
| Download URL | irpf_exterior-0.1.0-py3-none-any.whl |
|---|---|
| Size | 33.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0570bce98167dc560ac4f2b7fcf886064965e009bfc9390e87ac13a4f6cfae88
|
|
BLAKE2b-256 checksum How to use checksums |
e24bcb10ffbc2e4a5223976b14bc2e9158b78601856daa678f3d6a70340ac244
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|