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.
Metadata
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 | Provenance | Actions |
|---|---|---|---|---|
| irpf_exterior-0.1.0.tar.gz | 48.9 kB | Without provenance | Details for irpf_exterior-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| irpf_exterior-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for irpf_exterior-0.1.0-py3-none-any.whl |
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}
|
Security for irpf-exterior 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.