"""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 @dataclass class CabecalhoExtraido: codigo_empresa: str nome_empresa: str cnpj: str periodo_inicio: date periodo_fim: date @dataclass class LinhaBalanceteExtraida: conta_numero: int 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 @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]