125 lines
4.2 KiB
Python
125 lines
4.2 KiB
Python
"""Dataclasses puras (sem ORM) do Dashboard Contábil: o formato intermediário
|
|
entre o PDF extraído (`parser.py`) e as regras de auditoria (`regras.py`).
|
|
Mesmo padrão de `portal_api.planos_saude.modelos`/`portal_api.indicadores.pipeline`
|
|
— quem persiste em model é a view, este módulo só representa dados em memória."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
from datetime import date
|
|
from decimal import Decimal
|
|
|
|
# Sistema contábil que gerou o PDF — detectado pelo próprio parser
|
|
# (`parser.extrai_balancete_dre()`), nunca informado pelo usuário. Muda o
|
|
# plano de contas (códigos fixos das regras), a convenção de sinal dos
|
|
# saldos e se a apuração tem indicadores (ver CLAUDE.md do pacote).
|
|
LEIAUTE_QUESTOR = "questor"
|
|
LEIAUTE_CONTABIT = "contabit"
|
|
|
|
|
|
@dataclass
|
|
class CabecalhoExtraido:
|
|
codigo_empresa: str
|
|
nome_empresa: str
|
|
cnpj: str
|
|
periodo_inicio: date
|
|
periodo_fim: date
|
|
|
|
|
|
@dataclass
|
|
class LinhaBalanceteExtraida:
|
|
# `None` só no Contabit, onde a conta sintética não tem número próprio.
|
|
conta_numero: int | None
|
|
codigo: str
|
|
descricao: str
|
|
tipo: str # "S" (sintética) ou "A" (analítica)
|
|
saldo_anterior: Decimal
|
|
debito: Decimal
|
|
credito: Decimal
|
|
saldo_atual: Decimal
|
|
|
|
|
|
@dataclass
|
|
class LinhaDreExtraida:
|
|
ordem: int
|
|
descricao: str
|
|
nivel: int
|
|
valor: Decimal
|
|
totalizador: bool
|
|
|
|
|
|
@dataclass
|
|
class ValorMensalAnaliseVertical:
|
|
valor: Decimal
|
|
percentual: Decimal
|
|
|
|
|
|
@dataclass
|
|
class LinhaAnaliseVerticalExtraida:
|
|
"""Uma linha da seção "Demonstração Mensal (Análise Vertical)" — mesma
|
|
descrição/nível/negrito de `LinhaDreExtraida` (é a mesma árvore da DRE),
|
|
só que com um valor+percentual por mês em vez de um valor único (ver
|
|
`parser.py`)."""
|
|
|
|
ordem: int
|
|
descricao: str
|
|
nivel: int
|
|
totalizador: bool
|
|
valores: list[ValorMensalAnaliseVertical]
|
|
|
|
|
|
@dataclass
|
|
class ResultadoExtracao:
|
|
cabecalho: CabecalhoExtraido
|
|
contas: list[LinhaBalanceteExtraida]
|
|
linhas_dre: list[LinhaDreExtraida]
|
|
meses_analise_vertical: list[str] = field(default_factory=list)
|
|
linhas_analise_vertical: list[LinhaAnaliseVerticalExtraida] = field(default_factory=list)
|
|
# `True` quando o título de seção (DRE/Análise Vertical) só bateu depois
|
|
# de normalizar acento (ver `_normaliza_titulo`/`fonte_pdf_atipica` em
|
|
# `parser.py`) — sinal de que este PDF foi gerado com uma fonte
|
|
# diferente da do relatório de referência, o mesmo tipo de variação que
|
|
# também pode grudar palavras em descrições de conta (confirmado contra
|
|
# `1751 - Balancete 07.2026.pdf`). A view persiste isto em
|
|
# `ContabilApuracao.fonte_pdf_atipica`, só pra alertar o contador — não
|
|
# bloqueia nem tenta corrigir nada automaticamente.
|
|
fonte_pdf_atipica: bool = False
|
|
leiaute: str = LEIAUTE_QUESTOR
|
|
|
|
|
|
@dataclass
|
|
class AchadoDetectado:
|
|
regra: str
|
|
severidade: str # "alta" | "media" | "baixa"
|
|
titulo: str
|
|
mensagem: str
|
|
codigo_conta: str | None = None
|
|
valor_referencia: Decimal | None = None
|
|
# Preenchido só por regras que não têm uma conta do Balancete por trás
|
|
# (hoje, só `regra_variacao_atipica_dre`) — é o `ordem` (posição de
|
|
# leitura do PDF) da `LinhaAnaliseVerticalExtraida` referenciada, único
|
|
# dentro da apuração, usado pela view pra casar com a
|
|
# `ContabilLinhaAnaliseVertical` já persistida e alimentar o botão "Ver
|
|
# na tabela" (ver `AchadoDetectado`/`ContabilAchado.linha_analise_vertical`
|
|
# em views.py/models.py).
|
|
ordem_linha_analise_vertical: int | None = None
|
|
|
|
|
|
@dataclass
|
|
class SnapshotHistorico:
|
|
"""Retrato mínimo de uma apuração anterior já concluída/em revisão da
|
|
mesma empresa — usado pelas regras de variação mês a mês (ver
|
|
`regras.py`). `contas`/`linhas_dre` são dicionários já resolvidos
|
|
(codigo/descrição → valor) pra não precisar reconsultar o ORM dentro das
|
|
regras, que são funções puras."""
|
|
|
|
competencia: date
|
|
contas: dict[str, Decimal] = field(default_factory=dict)
|
|
linhas_dre: dict[str, Decimal] = field(default_factory=dict)
|
|
|
|
|
|
@dataclass
|
|
class ResultadoProcessamento:
|
|
extracao: ResultadoExtracao
|
|
achados: list[AchadoDetectado]
|