Speed / src /models /fiscal_llm.py
LesterCerioli's picture
Building Speed LLM
d9d7b41
Raw
History Blame Contribute Delete
52.1 kB
from __future__ import annotations
import math
from dataclasses import dataclass
from datetime import date
from decimal import Decimal
from pathlib import Path
from typing import Any, Optional
import torch
import torch.nn as nn
import torch.nn.functional as F
# ---------------------------------------------------------------------------
# Tokenizador simplificado (char-level) para texto fiscal em português
# ---------------------------------------------------------------------------
class TokenizadorFiscal:
VOCAB_ESPECIAL = ["<PAD>", "<UNK>", "<BOS>", "<EOS>", "<SEP>", "<MASK>"]
def __init__(self, vocab_size: int = 2048):
self.vocab_size = vocab_size
self._char2idx: dict[str, int] = {}
self._idx2char: dict[int, str] = {}
self._construir_vocab_base()
def _construir_vocab_base(self) -> None:
idx = 0
for tok in self.VOCAB_ESPECIAL:
self._char2idx[tok] = idx
self._idx2char[idx] = tok
idx += 1
for c in range(32, 127):
ch = chr(c)
self._char2idx[ch] = idx
self._idx2char[idx] = ch
idx += 1
for ch in "áéíóúâêîôûãõàèìòùäëïöüçñÁÉÍÓÚÂÊÎÔÛÃÕÀÈÌÒÙÄËÏÖÜÇÑ":
if ch not in self._char2idx:
self._char2idx[ch] = idx
self._idx2char[idx] = ch
idx += 1
self.pad_id = self._char2idx["<PAD>"]
self.unk_id = self._char2idx["<UNK>"]
self.bos_id = self._char2idx["<BOS>"]
self.eos_id = self._char2idx["<EOS>"]
self.sep_id = self._char2idx["<SEP>"]
self.mask_id = self._char2idx["<MASK>"]
self._vocab_atual = idx
def encode(self, texto: str, max_len: int = 512, add_special: bool = True) -> list[int]:
ids: list[int] = []
if add_special:
ids.append(self.bos_id)
for ch in texto[: max_len - (2 if add_special else 0)]:
ids.append(self._char2idx.get(ch, self.unk_id))
if add_special:
ids.append(self.eos_id)
return ids
def decode(self, ids: list[int]) -> str:
especiais = {self.pad_id, self.bos_id, self.eos_id, self.sep_id}
return "".join(self._idx2char.get(i, "?") for i in ids if i not in especiais)
def __len__(self) -> int:
return self._vocab_atual
# ---------------------------------------------------------------------------
# Positional Encoding
# ---------------------------------------------------------------------------
class PositionalEncoding(nn.Module):
def __init__(self, d_model: int, max_len: int = 1024, dropout: float = 0.1):
super().__init__()
self.dropout = nn.Dropout(p=dropout)
pe = torch.zeros(max_len, d_model)
pos = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
div = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))
pe[:, 0::2] = torch.sin(pos * div)
pe[:, 1::2] = torch.cos(pos * div)
self.register_buffer("pe", pe.unsqueeze(0))
def forward(self, x: torch.Tensor) -> torch.Tensor:
x = x + self.pe[:, : x.size(1)]
return self.dropout(x)
# ---------------------------------------------------------------------------
# Transformer Encoder para texto fiscal
# ---------------------------------------------------------------------------
class TransformerFiscal(nn.Module):
"""
Transformer encoder compacto para análise de texto fiscal.
Usado para: embeddings, classificação de obrigações, extração de entidades.
Arquitetura:
- 4 camadas encoder, d_model=256, 8 cabeças de atenção
- Pre-LN (norm_first=True) para estabilidade de treinamento
- Cabeça de classificação linear (256 → 128 → num_classes)
- Cabeça de extração de valor escalar (256 → 64 → 1)
"""
def __init__(
self,
vocab_size: int = 2048,
d_model: int = 256,
nhead: int = 8,
num_layers: int = 4,
dim_feedforward: int = 1024,
dropout: float = 0.1,
max_len: int = 1024,
num_classes: int = 16,
):
super().__init__()
self.d_model = d_model
self.embedding = nn.Embedding(vocab_size, d_model, padding_idx=0)
self.pos_enc = PositionalEncoding(d_model, max_len, dropout)
encoder_layer = nn.TransformerEncoderLayer(
d_model=d_model,
nhead=nhead,
dim_feedforward=dim_feedforward,
dropout=dropout,
batch_first=True,
norm_first=True,
)
self.encoder = nn.TransformerEncoder(encoder_layer, num_layers=num_layers)
self.norm = nn.LayerNorm(d_model)
self.classificador = nn.Sequential(
nn.Linear(d_model, d_model // 2),
nn.GELU(),
nn.Dropout(dropout),
nn.Linear(d_model // 2, num_classes),
)
self.extrator_valor = nn.Sequential(
nn.Linear(d_model, d_model // 4),
nn.GELU(),
nn.Linear(d_model // 4, 1),
)
self._init_weights()
def _init_weights(self) -> None:
for p in self.parameters():
if p.dim() > 1:
nn.init.xavier_uniform_(p)
def encode(self, input_ids: torch.Tensor, attention_mask: Optional[torch.Tensor] = None) -> torch.Tensor:
"""Retorna embeddings de sequência (shape: B × L × d_model)."""
x = self.embedding(input_ids) * math.sqrt(self.d_model)
x = self.pos_enc(x)
key_padding_mask = (attention_mask == 0) if attention_mask is not None else None
x = self.encoder(x, src_key_padding_mask=key_padding_mask)
return self.norm(x)
def cls_embedding(self, input_ids: torch.Tensor, attention_mask: Optional[torch.Tensor] = None) -> torch.Tensor:
"""Embedding CLS (primeiro token) como representação da sentença."""
return self.encode(input_ids, attention_mask)[:, 0, :]
def forward(
self,
input_ids: torch.Tensor,
attention_mask: Optional[torch.Tensor] = None,
task: str = "classificar",
) -> torch.Tensor:
cls = self.cls_embedding(input_ids, attention_mask)
if task == "classificar":
return self.classificador(cls)
if task == "extrair_valor":
return self.extrator_valor(cls)
return cls
def save(self, path: str | Path) -> None:
torch.save({
"state_dict": self.state_dict(),
"config": {
"vocab_size": self.embedding.num_embeddings,
"d_model": self.d_model,
},
}, path)
@classmethod
def load(cls, path: str | Path, **kwargs) -> "TransformerFiscal":
ckpt = torch.load(path, map_location="cpu", weights_only=False)
cfg = {**ckpt.get("config", {}), **kwargs}
model = cls(**cfg)
model.load_state_dict(ckpt["state_dict"])
return model
# ---------------------------------------------------------------------------
# Banco de Embeddings Fiscais (RAG em memória)
# ---------------------------------------------------------------------------
class BancoEmbeddingsFiscais:
"""
Armazena embeddings de conhecimento fiscal para recuperação semântica (RAG).
Usa cosine similarity para busca eficiente.
"""
def __init__(self, modelo: TransformerFiscal, tokenizador: TokenizadorFiscal, device: str = "cpu"):
self.modelo = modelo
self.tokenizador = tokenizador
self.device = device
self._textos: list[str] = []
self._embeddings: Optional[torch.Tensor] = None
self.modelo.to(device)
self.modelo.eval()
def _embed(self, textos: list[str]) -> torch.Tensor:
ids_list = [self.tokenizador.encode(t, max_len=512) for t in textos]
max_len = max(len(i) for i in ids_list)
padded = torch.zeros(len(ids_list), max_len, dtype=torch.long)
mask = torch.zeros(len(ids_list), max_len, dtype=torch.long)
for k, ids in enumerate(ids_list):
padded[k, : len(ids)] = torch.tensor(ids)
mask[k, : len(ids)] = 1
padded = padded.to(self.device)
mask = mask.to(self.device)
with torch.no_grad():
embs = self.modelo.cls_embedding(padded, mask)
return F.normalize(embs, dim=-1)
def indexar(self, textos: list[str]) -> None:
self._textos = textos
self._embeddings = self._embed(textos)
def buscar(self, query: str, top_k: int = 5) -> list[tuple[str, float]]:
if self._embeddings is None or not self._textos:
return []
q_emb = self._embed([query])
scores = (self._embeddings @ q_emb.T).squeeze(-1)
top_idx = scores.argsort(descending=True)[:top_k]
return [(self._textos[i], float(scores[i])) for i in top_idx]
# ---------------------------------------------------------------------------
# Classificador de Intenção Fiscal
# ---------------------------------------------------------------------------
CLASSES_OBRIGACAO = [
"EFD_ICMS_IPI",
"EFD_CONTRIBUICOES",
"ECD",
"ECF",
"NFe",
"NFSe",
"CTe",
"eSocial",
"EFD_REINF",
"DCTF",
"PGDAS",
"calculo_icms",
"calculo_ipi",
"calculo_pis_cofins",
"calculo_irpj_csll",
"calculo_iss",
]
class ClassificadorIntencaoFiscal:
"""Classifica intenções em consultas fiscais usando o TransformerFiscal."""
def __init__(self, modelo: TransformerFiscal, tokenizador: TokenizadorFiscal, device: str = "cpu"):
self.modelo = modelo
self.tokenizador = tokenizador
self.device = device
self.classes = CLASSES_OBRIGACAO
self.modelo.to(device)
self.modelo.eval()
def classificar(self, texto: str, top_k: int = 3) -> list[tuple[str, float]]:
ids = self.tokenizador.encode(texto, max_len=256)
t = torch.tensor([ids], dtype=torch.long).to(self.device)
mask = torch.ones_like(t)
with torch.no_grad():
logits = self.modelo(t, mask, task="classificar")
probs = F.softmax(logits[0], dim=-1)
top = probs.argsort(descending=True)[:top_k]
return [(self.classes[i], float(probs[i])) for i in top]
# ---------------------------------------------------------------------------
# Resultado do pipeline
# ---------------------------------------------------------------------------
@dataclass
class ResultadoFiscal:
"""Resultado de uma operação processada pelo PipelineFiscal."""
operacao: str
dados: dict[str, Any]
contexto_rag: list[tuple[str, float]]
intencoes: list[tuple[str, float]]
arquivos_gerados: list[str]
def sucesso(self) -> bool:
return "erro" not in self.dados
def resumo(self) -> str:
linhas = [f"[{self.operacao}]"]
for k, v in self.dados.items():
if k in ("instrucoes", "partilha", "atividades"):
continue
if isinstance(v, float):
linhas.append(f" {k}: R$ {v:,.2f}")
elif isinstance(v, list):
linhas.append(f" {k}: {len(v)} item(ns)")
else:
linhas.append(f" {k}: {v}")
if self.arquivos_gerados:
linhas.append(f" arquivos: {', '.join(self.arquivos_gerados)}")
return "\n".join(linhas)
# ---------------------------------------------------------------------------
# PipelineFiscal — ponto de entrada principal do LLM
# ---------------------------------------------------------------------------
class PipelineFiscal:
"""
Pipeline LLM completo para obrigações fiscais brasileiras.
Combina:
- TransformerFiscal (PyTorch): classificação de intenção + embeddings RAG
- Calculadores tributários: ICMS, IPI, PIS/COFINS, IRPJ/CSLL, ISS, Simples Nacional
- Geradores SPED: EFD ICMS/IPI, EFD Contribuições, ECD, ECF, DCTF, EFD-Reinf, e-Social
- Transmissor NF-e: SEFAZ SOAP webservices
Uso básico::
pipeline = PipelineFiscal(diretorio_saida="./output")
resultado = pipeline.processar(
"calcule o ICMS de uma venda",
{"valor_mercadoria": 10000, "aliquota": 18},
)
print(resultado.resumo())
# Geração de todas as obrigações de um período
arquivos = pipeline.gerar_obrigacoes(periodo)
Uso com modelo treinado::
pipeline = PipelineFiscal(caminho_modelo="modelo_fiscal.pt")
"""
_MAPA_PALAVRAS: dict[str, list[str]] = {
"calcular_icms": ["icms", "difal", "substituição tributária", "icms-st"],
"calcular_ipi": ["ipi", "industrializado", "tipi"],
"calcular_pis_cofins": ["pis", "cofins", "contribuição pis", "contribuição cofins"],
"calcular_irpj_csll": ["irpj", "csll", "lucro real", "lucro presumido", "imposto renda"],
"calcular_iss": ["iss", "imposto serviços", "serviço municipal"],
"calcular_simples": ["simples nacional", "das", "mei", "microempresa"],
"calcular_pgdas": ["pgdas", "pgdas-d", "declaração simples"],
"gerar_efd_icms_ipi": ["efd icms", "sped icms", "escrituração fiscal digital icms", "bloco k", "ciap"],
"gerar_efd_contribuicoes":["efd contribuições", "sped pis", "sped cofins"],
"gerar_ecd": ["ecd", "escrituração contábil digital", "livro diário"],
"gerar_ecf": ["ecf", "escrituração contábil fiscal", "lalur", "dipj"],
"gerar_dctf": ["dctf", "declaração débitos", "débitos tributários federais"],
"gerar_efd_reinf": ["reinf", "efd-reinf", "retenção previdenciária"],
"gerar_esocial": ["esocial", "e-social", "folha pagamento", "admissão", "rescisão"],
"gerar_cte": ["cte", "ct-e", "conhecimento transporte", "frete eletrônico"],
"gerar_nfce": ["nfce", "nfc-e", "modelo 65", "nota consumidor", "pdv"],
"gerar_mdfe": ["mdfe", "mdf-e", "manifesto documentos fiscais"],
"gerar_dirf": ["dirf", "imposto renda retido fonte", "declaração irrf anual"],
"gerar_defis": ["defis", "declaração informações simples nacional", "defis anual"],
"gerar_destda": ["destda", "de-stda", "icms-st simples", "diferencial alíquota simples"],
"gerar_gia": ["gia", "gia-st", "guia informação apuração icms sp", "scanc"],
"verificar_sefaz": ["sefaz", "status nfe", "autorizar nfe"],
}
def __init__(
self,
diretorio_saida: str | Path = "./output",
caminho_modelo: Optional[str | Path] = None,
device: Optional[str] = None,
):
self.diretorio_saida = Path(diretorio_saida)
self.diretorio_saida.mkdir(parents=True, exist_ok=True)
self.device = device or ("cuda" if torch.cuda.is_available() else "cpu")
# Modelo PyTorch
self.tokenizador = TokenizadorFiscal()
if caminho_modelo and Path(caminho_modelo).exists():
self.modelo = TransformerFiscal.load(caminho_modelo, num_classes=len(CLASSES_OBRIGACAO))
else:
self.modelo = TransformerFiscal(
vocab_size=len(self.tokenizador),
d_model=256,
nhead=8,
num_layers=4,
dim_feedforward=1024,
num_classes=len(CLASSES_OBRIGACAO),
)
self.rag = BancoEmbeddingsFiscais(self.modelo, self.tokenizador, self.device)
self.classificador = ClassificadorIntencaoFiscal(self.modelo, self.tokenizador, self.device)
self.rag.indexar(BASE_CONHECIMENTO_FISCAL)
# ------------------------------------------------------------------
# API pública
# ------------------------------------------------------------------
def processar(self, query: str, params: Optional[dict] = None) -> ResultadoFiscal:
"""
Processa uma consulta fiscal em linguagem natural.
Args:
query: Texto em português descrevendo a operação desejada.
params: Dicionário com os parâmetros numéricos/fiscais necessários.
Returns:
ResultadoFiscal com dados calculados, contexto RAG e intenções.
"""
intencoes = self.classificador.classificar(query, top_k=3)
contexto = self.rag.buscar(query, top_k=2)
operacao = self._detectar_operacao(query, intencoes)
dados: dict[str, Any] = {}
arquivos: list[str] = []
if operacao and params is not None:
dados = self._executar(operacao, params)
if "arquivo" in dados:
arquivos = [dados["arquivo"]]
elif "arquivos" in dados:
arquivos = dados.get("arquivos", [])
return ResultadoFiscal(
operacao=operacao or "consulta",
dados=dados,
contexto_rag=contexto,
intencoes=intencoes,
arquivos_gerados=arquivos,
)
def gerar_obrigacoes(self, periodo: Any, obrigacoes: Optional[list[str]] = None) -> dict[str, ResultadoFiscal]:
"""
Gera automaticamente todos os arquivos SPED do período conforme o regime tributário.
Args:
periodo: PeriodoApuracao com dados da empresa e notas fiscais.
obrigacoes: Lista opcional de obrigações específicas.
Se None, determina automaticamente pelo regime.
Returns:
Dicionário {nome_obrigacao: ResultadoFiscal}.
"""
from src.fiscal.entities import RegimeTributario
regime = periodo.empresa.regime_tributario
if obrigacoes is None:
if regime == RegimeTributario.LUCRO_REAL:
obrigacoes = ["EFD_ICMS_IPI", "EFD_CONTRIBUICOES", "ECD", "ECF", "DCTF"]
elif regime == RegimeTributario.LUCRO_PRESUMIDO:
obrigacoes = ["EFD_ICMS_IPI", "EFD_CONTRIBUICOES", "DCTF"]
else:
obrigacoes = []
resultados: dict[str, ResultadoFiscal] = {}
for ob in obrigacoes:
dados = self._gerar_sped(ob, periodo)
arquivos = [dados.get("arquivo", "")] if "arquivo" in dados else dados.get("arquivos", [])
resultados[ob] = ResultadoFiscal(
operacao=ob,
dados=dados,
contexto_rag=[],
intencoes=[],
arquivos_gerados=[a for a in arquivos if a],
)
return resultados
def gerar_cte(self, params: dict) -> dict:
"""Gera o CT-e (Conhecimento de Transporte Eletrônico) XML v4.00."""
from src.generators.cte import GeradorCTe, CargaCTe, ParteCTe, DadosTransporte, DocumentoReferenciado
from datetime import date as _date
emitente = params.get("_emitente_obj")
if emitente is None:
return {"erro": "Emitente (objeto Empresa) não fornecido em params['_emitente_obj']"}
rem_d = params.get("remetente", {})
dest_d = params.get("destinatario", {})
remetente = ParteCTe(
cnpj=rem_d.get("cnpj", ""),
razao_social=rem_d.get("razao_social", "REMETENTE"),
endereco=rem_d.get("endereco", ""),
municipio=rem_d.get("municipio", ""),
uf=rem_d.get("uf", "SP"),
cep=rem_d.get("cep", ""),
cod_municipio=rem_d.get("cod_municipio", ""),
)
destinatario = ParteCTe(
cnpj=dest_d.get("cnpj", ""),
razao_social=dest_d.get("razao_social", "DESTINATÁRIO"),
endereco=dest_d.get("endereco", ""),
municipio=dest_d.get("municipio", ""),
uf=dest_d.get("uf", "SP"),
cep=dest_d.get("cep", ""),
cod_municipio=dest_d.get("cod_municipio", ""),
)
carga_d = params.get("carga", {})
carga = CargaCTe(
descricao=carga_d.get("descricao", "CARGA GERAL"),
produto=carga_d.get("produto", "00"),
valor_carga=Decimal(str(carga_d.get("valor_carga", 0))),
peso_kg=Decimal(str(carga_d.get("peso_kg", 0))),
)
transp_d = params.get("transporte", {})
transporte = DadosTransporte(
rntrc=transp_d.get("rntrc", "00000000"),
placa_veiculo=transp_d.get("placa", "AAA0000"),
uf_veiculo=transp_d.get("uf_veiculo", "SP"),
data_prevista_entrega=_date.today(),
)
documentos = [
DocumentoReferenciado(chave_nfe=c)
for c in params.get("chaves_nfe", [])
]
gerador = GeradorCTe(
emitente=emitente,
remetente=remetente,
destinatario=destinatario,
carga=carga,
documentos=documentos,
transporte=transporte,
numero=str(params.get("numero", "1")),
ambiente=str(params.get("ambiente", "2")),
)
caminho = gerador.salvar(self.diretorio_saida)
return {"tipo": "CTe", "arquivo": str(caminho), "valido": True, "erros": []}
def gerar_nfce(self, params: dict) -> dict:
"""Gera a NFC-e (Nota Fiscal do Consumidor Eletrônica, modelo 65)."""
from src.generators.nfce_xml import GeradorNFCeXML, ConsumidorNFCe
emitente = params.get("_emitente_obj")
itens = params.get("_itens_obj", [])
if emitente is None or not itens:
return {"erro": "Forneça '_emitente_obj' e '_itens_obj' em params"}
cons_d = params.get("consumidor", {})
consumidor = ConsumidorNFCe(
cpf=cons_d.get("cpf", ""),
nome=cons_d.get("nome", "CONSUMIDOR"),
uf=cons_d.get("uf", "SP"),
)
gerador = GeradorNFCeXML(
emitente=emitente,
itens=itens,
consumidor=consumidor,
numero=str(params.get("numero", "1")),
forma_pagamento=str(params.get("forma_pagamento", "01")),
ambiente=str(params.get("ambiente", "2")),
)
caminho = gerador.salvar(self.diretorio_saida)
return {"tipo": "NFCe", "arquivo": str(caminho), "valido": True, "erros": []}
def gerar_mdfe(self, params: dict) -> dict:
"""Gera o MDF-e (Manifesto de Documentos Fiscais Eletrônicos)."""
from src.generators.mdfe import GeradorMDFe, MunicipioDescarga, DocumentoMDFe, ConductorMDFe, SeguroMDFe
emitente = params.get("_emitente_obj")
if emitente is None:
return {"erro": "Forneça '_emitente_obj' em params"}
municipios = [
MunicipioDescarga(
cod_municipio=m.get("cod_municipio", ""),
nome_municipio=m.get("nome", ""),
uf=m.get("uf", "SP"),
documentos=[DocumentoMDFe(chave=d["chave"], tipo=d.get("tipo", "NFe"))
for d in m.get("documentos", [])],
)
for m in params.get("municipios", [])
]
condutores = [
ConductorMDFe(nome=c.get("nome", ""), cpf=c.get("cpf", ""))
for c in params.get("condutores", [])
]
seg_d = params.get("seguro", {})
seguro = SeguroMDFe(
resp_seg=seg_d.get("resp_seg", "1"),
cnpj_seg=seg_d.get("cnpj_seg", ""),
nome_seg=seg_d.get("nome_seg", ""),
nApol=seg_d.get("nApol", ""),
nCT=seg_d.get("nCT", []),
)
gerador = GeradorMDFe(
emitente=emitente,
municipios_descarrega=municipios,
condutores=condutores,
seguro=seguro,
uf_ini=params.get("uf_ini", "SP"),
uf_fim=params.get("uf_fim", "SP"),
placa_veiculo=params.get("placa", "AAA0000"),
uf_veiculo=params.get("uf_veiculo", "SP"),
rntrc=params.get("rntrc", "00000000"),
numero=str(params.get("numero", "1")),
ambiente=str(params.get("ambiente", "2")),
)
caminho = gerador.salvar(self.diretorio_saida)
return {"tipo": "MDFe", "arquivo": str(caminho), "valido": True, "erros": []}
def gerar_dirf(self, params: dict) -> dict:
"""Gera o arquivo DIRF (Declaração do IR Retido na Fonte) anual."""
from src.generators.dirf import GeradorDIRF, BeneficiarioDIRF, ResponsavelDIRF
empresa = params.get("_empresa_obj")
if empresa is None:
return {"erro": "Forneça '_empresa_obj' em params"}
resp_d = params.get("responsavel", {})
responsavel = ResponsavelDIRF(
cpf=resp_d.get("cpf", ""),
nome=resp_d.get("nome", "RESPONSÁVEL"),
cargo=resp_d.get("cargo", "CONTADOR"),
ddd=resp_d.get("ddd", "11"),
telefone=resp_d.get("telefone", ""),
)
bens = [
BeneficiarioDIRF(
cpf_cnpj=b.get("cpf_cnpj", ""),
nome=b.get("nome", ""),
tipo=b.get("tipo", "PF"),
cod_receita=b.get("cod_receita", "0561"),
rendimentos_por_mes={int(k): Decimal(str(v)) for k, v in b.get("rendimentos", {}).items()},
ir_retido_por_mes={int(k): Decimal(str(v)) for k, v in b.get("ir_retido", {}).items()},
)
for b in params.get("beneficiarios", [])
]
gerador = GeradorDIRF(
empresa=empresa,
ano_calendario=int(params.get("ano", date.today().year - 1)),
beneficiarios=bens,
responsavel=responsavel,
)
caminho = gerador.salvar(self.diretorio_saida)
return {"tipo": "DIRF", "arquivo": str(caminho), "valido": True, "erros": []}
def gerar_defis(self, params: dict) -> dict:
"""Gera o XML DEFIS (Declaração Anual do Simples Nacional)."""
from src.generators.defis import GeradorDEFIS, ReceitaMensalDEFIS, SocioDEFIS
empresa = params.get("_empresa_obj")
if empresa is None:
return {"erro": "Forneça '_empresa_obj' em params"}
receitas = [
ReceitaMensalDEFIS(
mes=r["mes"],
receita_bruta_total=Decimal(str(r.get("receita_bruta_total", 0))),
receita_bruta_exportacao=Decimal(str(r.get("receita_bruta_exportacao", 0))),
receita_bruta_isenta=Decimal(str(r.get("receita_bruta_isenta", 0))),
)
for r in params.get("receitas_mensais", [])
]
socios = [
SocioDEFIS(
cpf_cnpj=s.get("cpf_cnpj", ""),
nome=s.get("nome", ""),
percentual_capital=Decimal(str(s.get("percentual_capital", 100))),
)
for s in params.get("socios", [])
]
gerador = GeradorDEFIS(
empresa=empresa,
ano_calendario=int(params.get("ano", date.today().year - 1)),
receitas_mensais=receitas,
socios=socios,
)
caminho = gerador.salvar(self.diretorio_saida)
return {"tipo": "DEFIS", "arquivo": str(caminho), "valido": True, "erros": [],
"relatorio": gerador.relatorio_resumo()}
def gerar_destda(self, params: dict) -> dict:
"""Gera o XML DeSTDA (ICMS-ST e DIFAL do Simples Nacional)."""
from src.generators.destda import GeradorDeSTDA, OperacaoSTDeSTDA
empresa = params.get("_empresa_obj")
if empresa is None:
return {"erro": "Forneça '_empresa_obj' em params"}
operacoes = [
OperacaoSTDeSTDA(
uf_origem=o.get("uf_origem", "SP"),
uf_destino=o.get("uf_destino", "SP"),
tipo=o.get("tipo", "ST"),
base_calculo=Decimal(str(o.get("base_calculo", 0))),
aliquota=Decimal(str(o.get("aliquota", 18))),
valor_imposto=Decimal(str(o.get("valor_imposto", 0))),
valor_pago=Decimal(str(o.get("valor_pago", 0))),
)
for o in params.get("operacoes", [])
]
gerador = GeradorDeSTDA(
empresa=empresa,
periodo=params.get("periodo", date.today().strftime("%Y-%m")),
operacoes=operacoes,
uf_declarante=params.get("uf_declarante", "SP"),
)
caminho = gerador.salvar(self.diretorio_saida)
return {"tipo": "DeSTDA", "arquivo": str(caminho), "valido": True, "erros": [],
"relatorio": gerador.relatorio_resumo(), "vencimento": gerador.data_vencimento().isoformat()}
def gerar_gia(self, params: dict) -> dict:
"""Gera o arquivo AIE da GIA/GIA-ST (São Paulo)."""
from src.generators.gia import GeradorGIA, ApuracaoGIA, ApuracaoGIAST
empresa = params.get("_empresa_obj")
if empresa is None:
return {"erro": "Forneça '_empresa_obj' em params"}
apur_d = params.get("apuracao", {})
apuracao = ApuracaoGIA(
debitos_operacoes_proprias=Decimal(str(apur_d.get("debitos_operacoes_proprias", 0))),
debitos_st_retencao=Decimal(str(apur_d.get("debitos_st_retencao", 0))),
estorno_credito=Decimal(str(apur_d.get("estorno_credito", 0))),
outros_debitos=Decimal(str(apur_d.get("outros_debitos", 0))),
creditos_entradas=Decimal(str(apur_d.get("creditos_entradas", 0))),
creditos_outros=Decimal(str(apur_d.get("creditos_outros", 0))),
estorno_debito=Decimal(str(apur_d.get("estorno_debito", 0))),
outros_creditos=Decimal(str(apur_d.get("outros_creditos", 0))),
compensacoes=Decimal(str(apur_d.get("compensacoes", 0))),
saldo_credor_anterior=Decimal(str(apur_d.get("saldo_credor_anterior", 0))),
)
sts = [
ApuracaoGIAST(
uf_substituto=s.get("uf", ""),
base_calculo=Decimal(str(s.get("base_calculo", 0))),
aliquota=Decimal(str(s.get("aliquota", 18))),
imposto_retido=Decimal(str(s.get("imposto_retido", 0))),
valor_entradas_st=Decimal(str(s.get("valor_entradas_st", 0))),
)
for s in params.get("apuracoes_st", [])
]
gerador = GeradorGIA(
empresa=empresa,
periodo=params.get("periodo", date.today().strftime("%Y-%m")),
apuracao=apuracao,
apuracoes_st=sts,
)
caminho = gerador.salvar(self.diretorio_saida)
return {"tipo": "GIA", "arquivo": str(caminho), "valido": True, "erros": [],
"relatorio": gerador.relatorio_resumo(),
"icms_a_recolher": float(gerador.icms_a_recolher)}
def verificar_sefaz(self, uf: str = "SP", ambiente: str = "2") -> dict:
"""Verifica a disponibilidade dos serviços SEFAZ para a UF."""
from src.transmitters.receita_federal import TransmissorNFe
t = TransmissorNFe(uf=uf, ambiente=ambiente)
r = t.verificar_status_servico()
return {
"uf": uf,
"ambiente": "homologação" if ambiente == "2" else "produção",
"sucesso": r.sucesso,
"codigo": r.codigo_retorno,
"mensagem": r.mensagem,
"timestamp": r.timestamp.isoformat(),
}
def treinar(
self,
exemplos: Optional[list] = None,
epochs: int = 30,
caminho_saida: str = "modelo_fiscal.pt",
) -> dict:
"""
Treina o TransformerFiscal com exemplos de classificação.
Args:
exemplos: Lista de ExemploTreinamento. Se None, usa os 70+ exemplos padrão.
epochs: Número de épocas de treinamento.
caminho_saida: Caminho para salvar o modelo treinado.
Returns:
Histórico de treinamento com loss e acurácia por época.
"""
from src.models.trainer import TrainerFiscal, EXEMPLOS_CLASSIFICACAO
exemplos = exemplos or EXEMPLOS_CLASSIFICACAO
trainer = TrainerFiscal(self.modelo, self.tokenizador, epochs=epochs, device=self.device)
historico = trainer.treinar(exemplos, caminho_saida=caminho_saida)
return historico
# ------------------------------------------------------------------
# Calculadores tributários
# ------------------------------------------------------------------
def calcular_icms(self, params: dict) -> dict:
from src.calculators.icms import ParametrosICMS, calcular_icms
p = ParametrosICMS(
valor_mercadoria=Decimal(str(params.get("valor_mercadoria", 0))),
aliquota=Decimal(str(params.get("aliquota", 18))),
cst=params.get("cst", "000"),
uf_origem=params.get("uf_origem", "SP"),
uf_destino=params.get("uf_destino", "SP"),
reducao_base=Decimal(str(params.get("reducao_base", 0))),
frete=Decimal(str(params.get("frete", 0))),
seguro=Decimal(str(params.get("seguro", 0))),
outras_despesas=Decimal(str(params.get("outras_despesas", 0))),
desconto=Decimal(str(params.get("desconto", 0))),
calcular_difal=params.get("calcular_difal", False),
consumidor_final=params.get("consumidor_final", False),
)
r = calcular_icms(p)
return {
"base_calculo": float(r.base_calculo),
"aliquota": float(r.aliquota),
"valor_icms": float(r.valor_icms),
"valor_icms_st": float(r.valor_st),
"valor_fcp": float(r.valor_fcp),
"valor_difal": float(r.valor_difal_destino + r.valor_difal_origem),
"valor_total_icms": float(r.valor_total_icms),
}
def calcular_ipi(self, params: dict) -> dict:
from src.calculators.ipi import ParametrosIPI, calcular_ipi
p = ParametrosIPI(
valor_produtos=Decimal(str(params.get("valor_produtos", 0))),
aliquota=Decimal(str(params.get("aliquota", 5))),
cst=params.get("cst", "50"),
frete=Decimal(str(params.get("frete", 0))),
)
r = calcular_ipi(p)
return {
"base_calculo": float(r.base_calculo),
"aliquota": float(r.aliquota),
"valor_ipi": float(r.valor_tributo),
}
def calcular_pis_cofins(self, params: dict) -> dict:
from src.calculators.pis_cofins import ReceitaBruta, calcular_pis_cofins
from src.fiscal.entities import RegimeTributario
receitas = [
ReceitaBruta(
descricao=r.get("descricao", "Receita"),
valor=Decimal(str(r.get("valor", 0))),
cst_pis=r.get("cst_pis", "01"),
cst_cofins=r.get("cst_cofins", "01"),
)
for r in params.get(
"receitas",
[{"descricao": "Receita", "valor": params.get("valor", 0)}],
)
]
regime_map = {
"lucro_real": RegimeTributario.LUCRO_REAL,
"lucro_presumido": RegimeTributario.LUCRO_PRESUMIDO,
"simples": RegimeTributario.SIMPLES_NACIONAL,
}
regime = regime_map.get(params.get("regime", "lucro_presumido"), RegimeTributario.LUCRO_PRESUMIDO)
r = calcular_pis_cofins(receitas, regime)
return {
"regime": r.regime,
"base_pis": float(r.base_pis),
"aliquota_pis": float(r.aliq_pis),
"valor_pis": float(r.valor_pis_debito),
"creditos_pis": float(r.creditos_pis),
"pis_a_recolher": float(r.pis_a_recolher),
"base_cofins": float(r.base_cofins),
"aliquota_cofins": float(r.aliq_cofins),
"valor_cofins": float(r.valor_cofins_debito),
"creditos_cofins": float(r.creditos_cofins),
"cofins_a_recolher": float(r.cofins_a_recolher),
}
def calcular_irpj_csll(self, params: dict) -> dict:
from src.calculators.irpj_csll import (
calcular_csll_lucro_presumido, calcular_csll_lucro_real,
calcular_irpj_lucro_presumido, calcular_irpj_lucro_real,
)
regime = params.get("regime", "lucro_presumido")
valor = Decimal(str(params.get("valor", 0)))
atividade = params.get("atividade", "venda_mercadorias")
if regime == "lucro_real":
irpj = calcular_irpj_lucro_real(lucro_antes_ir=valor)
csll = calcular_csll_lucro_real(lucro_antes_csll=valor)
else:
irpj = calcular_irpj_lucro_presumido(receita_bruta=valor, atividade=atividade)
csll = calcular_csll_lucro_presumido(
receita_bruta=valor,
atividade="comercio_industria" if atividade != "servicos_em_geral" else "servicos_em_geral",
)
return {
"regime": regime,
"base_irpj": float(irpj.base_calculo),
"valor_irpj_base": float(irpj.valor_base),
"valor_irpj_adicional": float(irpj.valor_adicional),
"irpj_total": float(irpj.valor_irpj_total),
"irpj_a_recolher": float(irpj.irpj_a_recolher),
"base_csll": float(csll.base_calculo),
"aliquota_csll": float(csll.aliquota),
"csll_total": float(csll.valor_csll),
"csll_a_recolher": float(csll.csll_a_recolher),
"total_impostos": float(irpj.irpj_a_recolher + csll.csll_a_recolher),
}
def calcular_iss(self, params: dict) -> dict:
from src.calculators.iss import ParametrosISS, calcular_iss
p = ParametrosISS(
valor_servico=Decimal(str(params.get("valor_servico", 0))),
codigo_servico=params.get("codigo_servico", "17"),
aliquota_municipal=Decimal(str(params["aliquota"])) if "aliquota" in params else None,
retencao_fonte=params.get("retencao_fonte", False),
)
r = calcular_iss(p)
return {
"base_calculo": float(r.base_calculo),
"aliquota": float(r.aliquota),
"valor_iss": float(r.valor_tributo),
"retencao_na_fonte": p.retencao_fonte,
}
def calcular_simples(self, params: dict) -> dict:
from src.calculators.simples_nacional import calcular_das
receita_mes = Decimal(str(params.get("receita_mes", 0)))
rbt12 = Decimal(str(params.get("rbt12", receita_mes * 12)))
anexo = params.get("anexo", "I")
r = calcular_das(receita_mes=receita_mes, rbt12=rbt12, anexo=anexo)
return {
"receita_mes": float(receita_mes),
"rbt12": float(rbt12),
"anexo": anexo,
"faixa": r.faixa,
"aliquota_nominal": float(r.aliquota_nominal),
"aliquota_efetiva": float(r.aliquota_efetiva),
"valor_das": float(r.valor_das),
"partilha": {k: float(v) for k, v in r.partilha.items()},
}
def calcular_pgdas(self, params: dict) -> dict:
from src.calculators.simples_nacional import calcular_pgdas
periodo = params.get("periodo", date.today().strftime("%Y-%m"))
atividades = params.get("atividades", [{"tipo": "comercio", "receita": params.get("receita_mes", 0)}])
rbt12 = Decimal(str(params.get("rbt12", 0)))
r = calcular_pgdas(
periodo=periodo,
atividades=[{"tipo": a["tipo"], "receita": Decimal(str(a["receita"]))} for a in atividades],
rbt12=rbt12,
)
return {
"periodo": r.periodo,
"rbt12": float(r.rbt12),
"receita_total_mes": float(r.receita_total_mes),
"valor_total_das": float(r.valor_total_das),
"data_vencimento": r.data_vencimento.isoformat(),
"atividades": [
{
"tipo": a.tipo,
"receita": float(a.receita),
"anexo": a.anexo,
"aliquota_efetiva": float(a.resultado.aliquota_efetiva),
"valor_das": float(a.resultado.valor_das),
}
for a in r.atividades
],
}
# ------------------------------------------------------------------
# Geradores SPED/XML
# ------------------------------------------------------------------
def _gerar_sped(self, obrigacao: str, periodo: Any) -> dict:
from src.generators.efd_icms_ipi import GeradorEFDICMSIPI
from src.generators.efd_contribuicoes import GeradorEFDContribuicoes
from src.generators.ecd import GeradorECD
from src.generators.ecf import GeradorECF
from src.generators.dctf import montar_dctf_do_periodo
from src.transmitters.receita_federal import TransmissorSPEDLocal, ValidadorArquivoSPED
transmissor = TransmissorSPEDLocal(self.diretorio_saida)
if obrigacao == "EFD_ICMS_IPI":
caminho = GeradorEFDICMSIPI(periodo).gerar(self.diretorio_saida)
return transmissor.preparar_efd_icms_ipi(caminho)
if obrigacao == "EFD_CONTRIBUICOES":
caminho = GeradorEFDContribuicoes(periodo).gerar(self.diretorio_saida)
return transmissor.preparar_efd_contribuicoes(caminho)
if obrigacao == "ECD":
caminho = GeradorECD(periodo).gerar(self.diretorio_saida)
return transmissor.preparar_ecd(caminho)
if obrigacao == "ECF":
caminho = GeradorECF(periodo).gerar(self.diretorio_saida)
_, erros = ValidadorArquivoSPED().validar(caminho)
import hashlib
return {
"tipo": "ECF",
"arquivo": str(caminho),
"valido": not erros,
"erros": erros,
"hash_md5": hashlib.md5(caminho.read_bytes()).hexdigest(),
"tamanho_bytes": caminho.stat().st_size,
}
if obrigacao == "DCTF":
gerador = montar_dctf_do_periodo(
empresa=periodo.empresa,
periodo=periodo.data_inicio.strftime("%Y-%m"),
)
caminho = gerador.salvar(self.diretorio_saida)
return {
"tipo": "DCTF",
"arquivo": str(caminho),
"valido": True,
"erros": [],
"relatorio": gerador.relatorio_resumo(),
"tamanho_bytes": caminho.stat().st_size,
}
return {"erro": f"Obrigação desconhecida: {obrigacao}"}
# ------------------------------------------------------------------
# Despacho interno
# ------------------------------------------------------------------
def _detectar_operacao(self, query: str, intencoes: list[tuple[str, float]]) -> Optional[str]:
q = query.lower()
for operacao, palavras in self._MAPA_PALAVRAS.items():
if any(p in q for p in palavras):
return operacao
if intencoes:
top = intencoes[0][0]
if "calculo" in top:
return f"calcular_{top.replace('calculo_', '')}"
mapa = {
"EFD_ICMS_IPI": "gerar_efd_icms_ipi",
"EFD_CONTRIBUICOES": "gerar_efd_contribuicoes",
"ECD": "gerar_ecd",
"ECF": "gerar_ecf",
"DCTF": "gerar_dctf",
"eSocial": "gerar_esocial",
"EFD_REINF": "gerar_efd_reinf",
"PGDAS": "calcular_pgdas",
}
return mapa.get(top)
return None
def _executar(self, operacao: str, params: dict) -> dict:
metodos = {
"calcular_icms": self.calcular_icms,
"calcular_ipi": self.calcular_ipi,
"calcular_pis_cofins": self.calcular_pis_cofins,
"calcular_irpj_csll": self.calcular_irpj_csll,
"calcular_iss": self.calcular_iss,
"calcular_simples": self.calcular_simples,
"calcular_pgdas": self.calcular_pgdas,
"gerar_cte": self.gerar_cte,
"gerar_nfce": self.gerar_nfce,
"gerar_mdfe": self.gerar_mdfe,
"gerar_dirf": self.gerar_dirf,
"gerar_defis": self.gerar_defis,
"gerar_destda": self.gerar_destda,
"gerar_gia": self.gerar_gia,
"verificar_sefaz": self.verificar_sefaz,
}
if operacao in metodos:
try:
return metodos[operacao](params)
except Exception as e:
return {"erro": str(e)}
return {"erro": f"Operação '{operacao}' requer PeriodoApuracao — use gerar_obrigacoes()"}
# ---------------------------------------------------------------------------
# Função auxiliar para instanciar o pipeline (retrocompatibilidade)
# ---------------------------------------------------------------------------
def criar_pipeline_fiscal(
caminho_modelo: Optional[str | Path] = None,
device: Optional[str] = None,
) -> tuple[TransformerFiscal, TokenizadorFiscal, BancoEmbeddingsFiscais, ClassificadorIntencaoFiscal]:
"""Instancia os componentes individuais do pipeline (uso avançado)."""
pipeline = PipelineFiscal(caminho_modelo=caminho_modelo, device=device)
return pipeline.modelo, pipeline.tokenizador, pipeline.rag, pipeline.classificador
# ---------------------------------------------------------------------------
# Base de conhecimento fiscal (RAG)
# ---------------------------------------------------------------------------
BASE_CONHECIMENTO_FISCAL = [
"A EFD ICMS IPI é a Escrituração Fiscal Digital que substitui os livros fiscais de ICMS e IPI. "
"Deve ser entregue mensalmente até o 15º dia útil do mês subsequente. "
"Obrigatória para contribuintes do ICMS e IPI, exceto optantes do Simples Nacional.",
"O arquivo EFD ICMS IPI é composto pelos blocos: 0 (identificação), C (NF-e mercadorias), "
"D (documentos de transporte), E (apuração ICMS e IPI), G (CIAP), H (inventário), "
"K (produção), 1 (outros), 9 (controle). Cada bloco começa com registro X001 e termina com X990.",
"No Bloco E da EFD ICMS IPI, o registro E110 contém a apuração do ICMS: "
"VL_TOT_DEBITOS - VL_TOT_CREDITOS = saldo devedor (ICMS a recolher) ou credor (transportar).",
"A EFD Contribuições escritura a apuração de PIS/PASEP e COFINS. "
"Entregue mensalmente até o 10º dia útil do 2º mês subsequente. "
"Obrigatória para pessoas jurídicas sujeitas ao IRPJ (Lucro Real e Presumido).",
"PIS não-cumulativo (Lucro Real): alíquota 1,65%. "
"COFINS não-cumulativa (Lucro Real): alíquota 7,60%. "
"PIS cumulativo (Lucro Presumido): alíquota 0,65%. "
"COFINS cumulativa (Lucro Presumido): alíquota 3,00%.",
"Créditos de PIS/COFINS (regime não-cumulativo, Lucro Real) podem ser tomados sobre: "
"aquisições de mercadorias para revenda, insumos, energia elétrica, aluguéis, "
"depreciação de máquinas e equipamentos, entre outros (Lei 10.637/2002 e 10.833/2003).",
"A ECD (Escrituração Contábil Digital) é obrigatória para todas as pessoas jurídicas "
"sujeitas ao IRPJ pelo Lucro Real. Entregue até o último dia útil de junho do ano seguinte. "
"Contém livro diário, razão e balancetes.",
"A ECF (Escrituração Contábil Fiscal) substitui a DIPJ. "
"Obrigatória para pessoas jurídicas tributadas pelo IRPJ (Lucro Real, Presumido ou Arbitrado). "
"Entregue até o último dia útil de julho do ano seguinte.",
"O ICMS (Imposto sobre Circulação de Mercadorias e Serviços) é estadual. "
"Base de cálculo: valor da mercadoria + frete + seguro + outras despesas. "
"Alíquotas internas variam por estado (17% a 22%). "
"Alíquotas interestaduais: 4%, 7% ou 12%.",
"Substituição Tributária (ST) de ICMS: o responsável tributário recolhe o imposto "
"de toda a cadeia. A base ST é calculada pela MVA (Margem de Valor Agregado): "
"Base ST = Base ICMS próprio × (1 + MVA%). ICMS ST = Base ST × alíquota interna - ICMS próprio.",
"DIFAL (Diferencial de Alíquota - EC 87/2015): aplicável em operações interestaduais "
"para consumidor final não contribuinte. DIFAL = Base × (alíquota interna - alíquota interestadual). "
"100% para o estado de destino a partir de 2019.",
"O IPI (Imposto sobre Produtos Industrializados) é federal, incide na saída de produtos "
"do estabelecimento industrial ou a ele equiparado. "
"Alíquotas variam por produto conforme a TIPI (Tabela de Incidência do IPI). "
"Base de cálculo: valor total da operação.",
"IRPJ (Lucro Real): 15% sobre lucro real + adicional de 10% sobre lucro que exceder "
"R$20.000/mês ou R$60.000/trimestre. "
"Lucro real = lucro contábil + adições - exclusões - compensações de prejuízos.",
"IRPJ (Lucro Presumido): alíquota 15% + adicional 10% sobre lucro presumido. "
"Lucro presumido = % sobre receita bruta: 8% para comércio/indústria, 32% para serviços. "
"Apuração trimestral.",
"CSLL: alíquota 9% para empresas em geral, 15% para instituições financeiras. "
"No Lucro Presumido: base de presunção 12% (comércio/indústria) ou 32% (serviços).",
"ISS (Imposto sobre Serviços): municipal, regido pela LC 116/2003. "
"Alíquota mínima 2%, máxima 5%. "
"Incide sobre prestação de serviços da lista anexa à LC 116/2003. "
"Retenção obrigatória pelo tomador para serviços específicos.",
"NF-e (Nota Fiscal Eletrônica - Modelo 55): documento fiscal eletrônico para operações "
"com mercadorias. Autorização via SEFAZ (webservice). "
"Chave de acesso: 44 dígitos. Protocolada com nProt. "
"Validade: 24 horas após emissão (cancelamento).",
"NFC-e (Nota Fiscal de Consumidor Eletrônica - Modelo 65): para vendas a consumidor final "
"presencial (PDV/frente de caixa). Substituiu o ECF (cupom fiscal).",
"e-Social: escrituração digital das obrigações fiscais, previdenciárias e trabalhistas. "
"Substitui GFIP, RAIS, CAGED, DIRF, MANAD, PPP, SEFIP entre outros. "
"Grupos de eventos: S-1000 (empregador), S-2200 (trabalhadores), S-2299/2399 (rescisão).",
"EFD-Reinf: escrituração de retenções e informações da previdência social. "
"Substitui parte da GFIP. Obrigatória para empresas que retêm IR, CSLL, PIS, COFINS "
"de serviços prestados por PJ, e que pagam rendimentos a PF/PJ sujeitos a retenção.",
"DCTF (Declaração de Débitos e Créditos Tributários Federais): informa débitos apurados "
"e pagamentos/compensações dos tributos federais. "
"Entregue mensalmente até o 15º dia útil do 2º mês subsequente.",
"Simples Nacional: regime unificado de arrecadação. "
"Abrange IRPJ, CSLL, PIS, COFINS, IPI, CPP, ICMS e ISS em documento único (DAS). "
"Obrigações acessórias: PGDAS-D (mensal, até dia 20), DEFIS (anual, até 31/03), "
"e-Social simplificado para folha.",
"DARF (Documento de Arrecadação de Receitas Federais): guia de recolhimento para tributos federais. "
"Código de receita identifica o tributo: 6912=IRPJ estimativa, 2089=IRPJ LP, "
"8109=PIS não-cumulativo, 2172=COFINS não-cumulativa.",
"Calendário fiscal mensal: DAS Simples até dia 20; DARF IRPJ estimativa até último dia útil; "
"DARF PIS/COFINS até dia 25; EFD ICMS/IPI até 15º dia útil; DCTF até 15º dia útil do 2º mês.",
"Calendário fiscal anual: ECF até último dia útil de julho; ECD até último dia útil de junho; "
"DIRF até último dia útil de fevereiro; RAIS até data definida pelo MT.",
]