Manual3

FASE 1: AUTOMAÇÃO DAS AÇÕES COLETIVAS

Objetivo: Calcular valores de pelo menos 25 funcionários por dia ou 3 por hora

Método: Criar sistemas e automações que aumentem a velocidade de produção em 12 vezes

Fluxograma:

  • Colocar contracheques (PDFs) na pasta pdfs_trabalhador (C:\= LEO RAYZER SSD\= RAYZER DEV\Ação Coletiva – Automação\pdfs_trabalhador)

  • Gerar planilhas pelo terminal do VS Code:

  • python gerar_csvs_pjecalc.py

  • Importar planilhas no PJe-Calc

Objetivo: Calcular valores de pelo menos 25 funcionários por dia ou 3 por hora

Método: Criar sistemas e automações que aumentem a velocidade de produção em 12 vezes

Fluxograma:

  • Colocar contracheques (PDFs) na pasta pdfs_trabalhador (C:\= LEO RAYZER SSD\= RAYZER DEV\Ação Coletiva – Automação\pdfs_trabalhador)

  • Gerar planilhas pelo terminal do VS Code:

  • python gerar_csvs_pjecalc.py

  • Importar planilhas no PJe-Calc

A DESCOBERTA QUE MUDA TUDO

PJe-Calc possui função NATIVA de importação de XML!

Isso significa:

  • NÃO precisa de Selenium/Playwright
  • NÃO precisa de automação de navegador
  • NÃO precisa de “Calc Machine” ou ferramentas pagas
  • SIM: Python gera XML → PJe-Calc importa pronto!

📊 COMPARAÇÃO: MÉTODOS DE AUTOMAÇÃO

MétodoConfiabilidadeComplexidadeVelocidadeRecomendado?
XML Nativo⭐⭐⭐⭐⭐⭐⭐ Média⭐⭐⭐⭐⭐IDEAL
Playwright/Selenium⭐⭐ Baixa⭐⭐⭐⭐⭐ Alta⭐⭐⭐❌ Desnecessário
Manual Total⭐⭐⭐⭐⭐ Fácil⭐ Lento❌ Ineficiente

VEREDICTO: Use XML! É o método oficial e profissional.


 

1. Python 3.12 (Linguagem de programação)

  • Por quê: Mais recente e estável
  • Alternativas: Nenhuma melhor para automação
  • 📥 Download: https://python.org
  • ✅ Melhor escolha absoluta

2. uv (Gerenciador de pacotes Python)

  • Por quê: 10-100x mais rápido que pip
  • Alternativa ao pip tradicional
  • 📥 Instalar: pip install uv
  • ✅ Revolucionário e gratuito

3. pdfplumber (Extração de PDF)

  • Por quê: Melhor para PDFs com texto
  • Alternativas analisadas:
    • PyPDF2: Menos confiável
    • PyMuPDF: Mais complexo
    • pdfminer.six: Mais lento
  • ✅ pdfplumber vence

4. polars (Manipulação de dados)

  • Por quê: 5-10x mais rápido que pandas
  • Alternativa ao pandas
  • Sintaxe mais moderna
  • ✅ Melhor que pandas

5. lxml (Geração de XML)

  • Por quê: Mais rápido e confiável
  • Alternativas:
    • xml.etree: Mais lento
    • xmltodict: Menos control

e

  • ✅ lxml é superior

6. Rye (Gerenciador de projeto Python)

  • Por quê: Gerencia tudo automaticamente
  • Alternativas:
    • poetry: Mais lento
    • pipenv: Menos features
  • ✅ Rye é o futuro

7. Ruff (Linter/Formatter)

  • Por quê: 10-100x mais rápido que pylint/black
  • Substitui: pylint + black + isort
  • ✅ Ferramenta obrigatória

8. VS Code (Editor)

  • Por quê: Mais completo e gratuito
  • Alternativas:
    • PyCharm Community: Mais pesado
    • Sublime: Menos features
    • Vim/Neovim: Curva de aprendizado
  • ✅ VS Code é ideal para iniciantes

FASE 1: Fundamentos (Dias 1-3)

  • Python + uv + Ambiente
  • Extração básica de PDF
  • Conceitos de XML

FASE 2: Extração Avançada (Dias 4-7)

  • Sistema robusto de extração
  • Validações
  • Polars para dados

FASE 3: Geração de XML (Dias 8-12)

  • Estrutura XML do PJe-Calc
  • Gerador automático
  • Testes com PJe-Calc real

FASE 4: Sistema Completo (Dias 13-15)

  • Pipeline completo
  • Testes finais
  • Documentação

Instalação Correta

# 1. Instalar Python 3.12
# Download: https://python.org (marcar "Add to PATH")

# 2. Verificar instalação
python --version  # Deve mostrar 3.12.x

# 3. Instalar uv (substituir pip)
pip install uv

# 4. Instalar ferramentas modernas
uv pip install ruff

# 5. Instalar bibliotecas do projeto
uv pip install pdfplumber polars lxml openpyxl

Configurar VS Code

Extensões essenciais:

  1. Python (Microsoft)
  2. Ruff (Charliermarsh)
  3. Even Better TOML
  4. XML Tools

Primeiro Script: Teste de Ambiente

# Arquivo: teste_ambiente.py

import sys
print(f"✓ Python {sys.version}")

import pdfplumber
print("✓ pdfplumber instalado")

import polars as pl
print("✓ polars instalado")

import lxml
print("✓ lxml instalado")

print("\n🎉 Ambiente configurado com sucesso!")

Executar: python teste_ambiente.py

Conceitos mínimos necessários

# 1. VARIÁVEIS E TIPOS
nome = "Willian"  # string
matricula = 2452583  # int
salario = 3019.40  # float
tem_periculosidade = True  # bool

# 2. DICIONÁRIOS (essencial para XML)
trabalhador = {
    "nome": "Willian Aguiar",
    "matricula": "2452583",
    "salario_basico": 3019.40,
    "adicional_periculosidade": 905.82
}

print(trabalhador["nome"])  # Willian Aguiar

# 3. LISTAS
rubricas = ["Salário Básico", "Periculosidade", "INSS"]

# 4. LOOPS
for rubrica in rubricas:
    print(rubrica)

# 5. FUNÇÕES
def calcular_inss(salario, aliquota=11):
    return salario * (aliquota / 100)

inss = calcular_inss(3019.40)
print(f"INSS: R$ {inss:.2f}")

# 6. CONDICIONAIS
if salario > 3000:
    print("Salário acima de R$ 3.000")
else:
    print("Salário até R$ 3.000")

Exercício Prático: Extrator Simples

# Arquivo: extrator_basico.py
import pdfplumber
import re

def extrair_contracheque(pdf_path):
    """Extrai dados básicos do contracheque"""
    
    with pdfplumber.open(pdf_path) as pdf:
        texto = pdf.pages[0].extract_text()
    
    # Extrair matrícula
    match = re.search(r'(\d{7})\s+Matrícula', texto)
    matricula = match.group(1) if match else None
    
    # Extrair nome
    match = re.search(r'Nome\s+\d+\s+([A-ZÀ-Ú\s]+)\s+Matrícula', texto)
    nome = match.group(1).strip() if match else None
    
    # Extrair salário
    match = re.search(r'Salário Básico\s+\d+\s+R\$\s+([\d.,]+)', texto)
    salario = match.group(1) if match else None
    
    return {
        "matricula": matricula,
        "nome": nome,
        "salario_basico": salario
    }

# Testar
dados = extrair_contracheque("seu_contracheque.pdf")
print(dados)

Por que Polars em vez de Pandas?

# PANDAS (antigo, lento)
import pandas as pd
df = pd.read_csv("dados.csv")
resultado = df.groupby("categoria").agg({"valor": "sum"})

# POLARS (moderno, 10x mais rápido)
import polars as pl
df = pl.read_csv("dados.csv")
resultado = df.group_by("categoria").agg(pl.col("valor").sum())

Polars é:

  • ✅ 5-10x mais rápido
  • ✅ Usa menos memória
  • ✅ Sintaxe mais clara
  • ✅ Paralelização automática

Sistema Completo de Extração

# Arquivo: extrator_completo.py
import pdfplumber
import polars as pl
import re
from pathlib import Path
from dataclasses import dataclass

@dataclass
class DadosContracheque:
    """Estrutura de dados do contracheque"""
    arquivo: str
    matricula: str | None = None
    nome: str | None = None
    mes_ano: str | None = None
    salario_basico: float = 0.0
    adicional_periculosidade: float = 0.0
    adicional_noturno: float = 0.0
    adicional_hra: float = 0.0
    total_proventos: float = 0.0
    inss: float = 0.0
    ir: float = 0.0
    total_descontos: float = 0.0
    total_liquido: float = 0.0
    status: str = "pendente"

def converter_valor_br(texto: str) -> float:
    """Converte R$ 1.234,56 para 1234.56"""
    if not texto:
        return 0.0
    try:
        return float(texto.replace('.', '').replace(',', '.'))
    except:
        return 0.0

PADROES_REGEX = {
    'matricula': r'(\d{7})\s+Matrícula',
    'nome': r'Nome\s+\d+\s+([A-ZÀ-Ú\s]+)\s+Matrícula',
    'mes_ano': r'(\d{2}/\d{4})\s+Mês/Ano',
    'salario_basico': r'0001\s+Salário Básico\s+\d+\s+R\$\s+([\d.,]+)',
    'adicional_periculosidade': r'0201\s+Adicional Periculosidade\s+\d+\s+R\$\s+([\d.,]+)',
    'adicional_noturno': r'1061\s+Adicional Trab\.?\s*Noturno\s+\d+\s+R\$\s+([\d.,]+)',
    'adicional_hra': r'1062\s+Adicional HRA\s+\d+\s+R\$\s+([\d.,]+)',
    'total_proventos': r'Total de Proventos\s+\.+\s+R\$\s+([\d.,]+)',
    'inss': r'0923\s+Contribuição INSS\s+[\d,]+\s+R\$\s+([\d.,]+)',
    'ir': r'0927\s+Imposto de Renda\s+[\d,]+\s+R\$\s+([\d.,]+)',
    'total_descontos': r'Total de Descontos\s+\.+\s+R\$\s+([\d.,]+)',
    'total_liquido': r'Total Líquido\s+R\$\s+([\d.,]+)',
}

def extrair_contracheque(pdf_path: Path) -> DadosContracheque:
    """Extrai dados completos de um contracheque"""
    
    dados = DadosContracheque(arquivo=pdf_path.name)
    
    try:
        with pdfplumber.open(pdf_path) as pdf:
            texto = pdf.pages[0].extract_text()
            
            if not texto:
                dados.status = "erro: PDF vazio"
                return dados
            
            # Extrair cada campo
            for campo, padrao in PADROES_REGEX.items():
                match = re.search(padrao, texto)
                if match:
                    valor = match.group(1)
                    
                    # Converter valores monetários
                    if campo in ['salario_basico', 'adicional_periculosidade', 
                                'adicional_noturno', 'adicional_hra',
                                'total_proventos', 'inss', 'ir', 
                                'total_descontos', 'total_liquido']:
                        valor = converter_valor_br(valor)
                    
                    setattr(dados, campo, valor)
            
            # Validar
            if dados.matricula and dados.total_liquido > 0:
                dados.status = "sucesso"
            else:
                dados.status = "dados incompletos"
                
    except Exception as e:
        dados.status = f"erro: {str(e)}"
    
    return dados

def processar_pasta(pasta_pdfs: str) -> pl.DataFrame:
    """Processa todos os PDFs de uma pasta"""
    
    pasta = Path(pasta_pdfs)
    pdfs = list(pasta.glob("*.pdf"))
    
    print(f"📄 Encontrados {len(pdfs)} PDFs\n")
    
    resultados = []
    for i, pdf in enumerate(pdfs, 1):
        print(f"[{i}/{len(pdfs)}] {pdf.name[:50]}", end=" ... ")
        dados = extrair_contracheque(pdf)
        
        # Converter para dict
        resultados.append(dados.__dict__)
        
        status_symbol = "✓" if dados.status == "sucesso" else "✗"
        print(f"{status_symbol} {dados.status}")
    
    # Criar DataFrame com Polars
    df = pl.DataFrame(resultados)
    
    # Salvar
    df.write_excel("contracheques_extraidos.xlsx")
    
    # Estatísticas
    total = len(df)
    sucessos = df.filter(pl.col("status") == "sucesso").height
    
    print(f"\n📊 RESULTADO:")
    print(f"   Total: {total}")
    print(f"   ✓ Sucessos: {sucessos} ({sucessos/total*100:.1f}%)")
    print(f"   ✗ Erros: {total - sucessos}")
    
    return df

# Executar
if __name__ == "__main__":
    df = processar_pasta(".")

DESCOBERTA CRÍTICA: Estrutura XML do PJe-Calc

O PJe-Calc aceita importação de XML com estrutura específica.

Primeiro, precisamos descobrir o formato correto:

  1. Abra o PJe-Calc Cidadão
  2. Crie um cálculo manualmente (simples)
  3. Exporte como XML (menu Cálculo → Exportar)
  4. Abra o XML no VS Code
  5. Analise a estrutura

Exemplo típico da estrutura:

<?xml version="1.0" encoding="UTF-8"?>
<calculo>
    <dadosProcesso>
        <numeroProcesso>0000000-00.0000.0.00.0000</numeroProcesso>
        <reclamante>
            <nome>WILLIAN AGUIAR DOS SANTOS</nome>
            <cpf>000.000.000-00</cpf>
        </reclamante>
        <reclamado>
            <nome>PETRÓLEO BRASILEIRO S.A.</nome>
            <cnpj>00.000.000/0000-00</cnpj>
        </reclamado>
    </dadosProcesso>
    
    <dadosContratuais>
        <dataAdmissao>2010-01-01</dataAdmissao>
        <dataDispensa>2019-12-31</dataDispensa>
        <cargaHoraria>168</cargaHoraria>
    </dadosContratuais>
    
    <verbas>
        <verba>
            <codigo>SALARIO_BASICO</codigo>
            <descricao>Salário Básico</descricao>
            <valor>3019.40</valor>
            <tipo>PROVENTO</tipo>
        </verba>
        <verba>
            <codigo>ADICIONAL_PERICULOSIDADE</codigo>
            <descricao>Adicional de Periculosidade</descricao>
            <valor>905.82</valor>
            <tipo>PROVENTO</tipo>
        </verba>
        <verba>
            <codigo>INSS</codigo>
            <descricao>Contribuição INSS</descricao>
            <valor>642.34</valor>
            <tipo>DESCONTO</tipo>
        </verba>
    </verbas>
</calculo>

Gerador de XML com lxml

# Arquivo: gerador_xml.py
from lxml import etree
from datetime import datetime
import polars as pl

class GeradorXMLPJeCalc:
    """Gera XML compatível com PJe-Calc"""
    
    def __init__(self):
        self.namespace = None  # Ajustar se PJe-Calc usar namespace
    
    def gerar_xml(self, dados: dict, dados_adicionais: dict) -> str:
        """
        Gera XML de um trabalhador
        
        dados: dict com dados extraídos do PDF
        dados_adicionais: dict com info manual (carga horária, datas, etc)
        """
        
        # Criar elemento raiz
        root = etree.Element("calculo")
        root.set("versao", "2.13.2")  # Versão do PJe-Calc
        
        # Dados do processo
        processo = etree.SubElement(root, "dadosProcesso")
        
        numero_processo = etree.SubElement(processo, "numeroProcesso")
        numero_processo.text = dados_adicionais.get("numero_processo", "")
        
        # Reclamante
        reclamante = etree.SubElement(processo, "reclamante")
        
        nome_rec = etree.SubElement(reclamante, "nome")
        nome_rec.text = dados.get("nome", "").upper()
        
        cpf_rec = etree.SubElement(reclamante, "cpf")
        cpf_rec.text = dados_adicionais.get("cpf", "")
        
        matricula = etree.SubElement(reclamante, "matricula")
        matricula.text = str(dados.get("matricula", ""))
        
        # Reclamado
        reclamado = etree.SubElement(processo, "reclamado")
        
        nome_reclamado = etree.SubElement(reclamado, "nome")
        nome_reclamado.text = "PETRÓLEO BRASILEIRO S.A."
        
        cnpj = etree.SubElement(reclamado, "cnpj")
        cnpj.text = "33.000.167/0001-01"  # CNPJ Petrobras
        
        # Dados contratuais
        contratuais = etree.SubElement(root, "dadosContratuais")
        
        data_admissao = etree.SubElement(contratuais, "dataAdmissao")
        data_admissao.text = dados_adicionais.get("data_admissao", "")
        
        data_dispensa = etree.SubElement(contratuais, "dataDispensa")
        data_dispensa.text = dados_adicionais.get("data_dispensa", "")
        
        carga_horaria = etree.SubElement(contratuais, "cargaHoraria")
        carga_horaria.text = str(dados_adicionais.get("carga_horaria", 168))
        
        cargo = etree.SubElement(contratuais, "cargo")
        cargo.text = dados_adicionais.get("cargo", "")
        
        # Verbas
        verbas = etree.SubElement(root, "verbas")
        
        # Mapear rubricas do contracheque para verbas do PJe-Calc
        mapeamento_verbas = {
            'salario_basico': {
                'codigo': 'SALARIO_BASICO',
                'descricao': 'Salário Básico',
                'tipo': 'PROVENTO'
            },
            'adicional_periculosidade': {
                'codigo': 'ADICIONAL_PERICULOSIDADE',
                'descricao': 'Adicional de Periculosidade',
                'tipo': 'PROVENTO'
            },
            'adicional_noturno': {
                'codigo': 'ADICIONAL_NOTURNO',
                'descricao': 'Adicional Noturno',
                'tipo': 'PROVENTO'
            },
            'adicional_hra': {
                'codigo': 'ADICIONAL_HRA',
                'descricao': 'Adicional HRA',
                'tipo': 'PROVENTO'
            },
            'inss': {
                'codigo': 'INSS',
                'descricao': 'Contribuição INSS',
                'tipo': 'DESCONTO'
            },
            'ir': {
                'codigo': 'IRRF',
                'descricao': 'Imposto de Renda',
                'tipo': 'DESCONTO'
            }
        }
        
        # Adicionar cada verba
        for campo, config in mapeamento_verbas.items():
            valor = dados.get(campo, 0)
            
            if valor and valor > 0:
                verba = etree.SubElement(verbas, "verba")
                
                codigo = etree.SubElement(verba, "codigo")
                codigo.text = config['codigo']
                
                descricao = etree.SubElement(verba, "descricao")
                descricao.text = config['descricao']
                
                valor_elem = etree.SubElement(verba, "valor")
                valor_elem.text = f"{valor:.2f}"
                
                tipo = etree.SubElement(verba, "tipo")
                tipo.text = config['tipo']
        
        # Converter para string XML
        xml_string = etree.tostring(
            root,
            pretty_print=True,
            xml_declaration=True,
            encoding='UTF-8'
        ).decode('utf-8')
        
        return xml_string
    
    def gerar_lote(self, df: pl.DataFrame, dados_adicionais_df: pl.DataFrame, 
                   pasta_saida: str = "xmls_pjecalc"):
        """
        Gera XMLs para múltiplos trabalhadores
        
        df: DataFrame com dados extraídos
        dados_adicionais_df: DataFrame com dados manuais (CPF, datas, etc)
        """
        from pathlib import Path
        
        pasta = Path(pasta_saida)
        pasta.mkdir(exist_ok=True)
        
        print(f"\n🔄 Gerando XMLs...\n")
        
        # Join dos dataframes
        df_completo = df.join(
            dados_adicionais_df,
            on="matricula",
            how="left"
        )
        
        gerados = 0
        erros = 0
        
        for row in df_completo.iter_rows(named=True):
            try:
                matricula = row['matricula']
                
                # Gerar XML
                xml = self.gerar_xml(row, row)
                
                # Salvar arquivo
                arquivo_xml = pasta / f"calculo_{matricula}.xml"
                with open(arquivo_xml, 'w', encoding='utf-8') as f:
                    f.write(xml)
                
                print(f"✓ {arquivo_xml.name}")
                gerados += 1
                
            except Exception as e:
                print(f"✗ Erro na matrícula {row.get('matricula', 'N/A')}: {e}")
                erros += 1
        
        print(f"\n📊 RESULTADO:")
        print(f"   ✓ XMLs gerados: {gerados}")
        print(f"   ✗ Erros: {erros}")
        print(f"   📁 Pasta: {pasta.absolute()}")
        
        return gerados

# Exemplo de uso
if __name__ == "__main__":
    # Carregar dados extraídos
    df = pl.read_excel("contracheques_extraidos.xlsx")
    
    # Carregar dados adicionais (você preenche manualmente)
    dados_adicionais = pl.read_csv("dados_adicionais.csv")
    
    # Gerar XMLs
    gerador = GeradorXMLPJeCalc()
    gerador.gerar_lote(df, dados_adicionais)

Arquivo de Dados Adicionais (você preenche)

# dados_adicionais.csv
matricula,cpf,numero_processo,data_admissao,data_dispensa,carga_horaria,cargo
2452583,000.000.000-00,0000000-00.2020.5.01.0000,2010-01-01,2019-12-31,168,Técnico
    import pdfplumber
import polars as pl
import re
from pathlib import Path




def converter_valor_br(texto):
    """Converte R$ 1.234,56 para float 1234.56"""
    if not texto:
        return 0.0
    return float(texto.replace('.', '').replace(',', '.'))




# Dicionário com TODOS os padrões de rubricas
PADROES_RUBRICAS = {
    'Salario_Basico': r'0001\s+Salário Básico\s+\d+\s+R\$\s+([\d.,]+)',
    'Adicional_Periculosidade': r'0201\s+Adicional Periculosidade\s+\d+\s+R\$\s+([\d.,]+)',
    'Adicional_Noturno': r'1061\s+Adicional Trab\.\s+Noturno\s+\d+\s+R\$\s+([\d.,]+)',
    'Adicional_HRA': r'1062\s+Adicional HRA\s+\d+\s+R\$\s+([\d.,]+)',
    'Complemento_RMNR': r'0192\s+Complemento da RMNR\s+R\$\s+([\d.,]+)',
    'Anuenio': r'0015\s+Anuênio\s+[\d,]+\s+R\$\s+([\d.,]+)',
    'Ferias_Petros': r'1079\s+Férias C/Petros\s+\d+\s+R\$\s+([\d.,]+)',
    'Gratificacao_Ferias': r'0403\s+Gratificação de Ferias\s+\d+\s+R\$\s+([\d.,]+)',
    'Media_Hora_Extra': r'0395\s+Média Hora Extra\s+\d+\s+R\$\s+([\d.,]+)',
    'Media_Rep_Sem': r'0392\s+Média Rep\.\s+Sem\.\s+Rem HE\s+\d+\s+R\$\s+([\d.,]+)',
    'Compl_Cand_Interno': r'0075\s+Compl Re Cand Interno C/P\s+\d+\s+R\$\s+([\d.,]+)',
    'HE_Troca_Turno': r'0528\s+HE Troca de Turno\s+\d+\s+R\$\s+([\d.,]+)',
    'RSR_HE_Troca': r'0532\s+RSR-HE Troca de Turno\s+R\$\s+([\d.,]+)',
    'Imposto_Renda': r'0927\s+Imposto de Renda\s+[\d,]+\s+R\$\s+([\d.,]+)',
    'IR_Ferias': r'0926\s+Imp Renda Férias\s+[\d,]+\s+R\$\s+([\d.,]+)',
    'INSS': r'0923\s+Contribuição INSS\s+[\d,]+\s+R\$\s+([\d.,]+)',
    'Petros': r'0985\s+Petros II Contr Regular\s+\d+\s+R\$\s+([\d.,]+)',
    'Desc_Adiant_Sal': r'5636\s+Desc Adiant sal sem IR\s+R\$\s+([\d.,]+)',
    'Desc_Adto_Ferias': r'5118\s+Desc Adto Férias\s+\d+\s+R\$\s+([\d.,]+)',
    'Desc_Adto_GFE': r'5119\s+Desc Adto GFE\s+\d+\s+R\$\s+([\d.,]+)',
    'AMS_Grande_Risco': r'0980\s+AMS Grande Risco\s+[\d,]+\s+R\$\s+([\d.,]+)',
    'CA_Sindipetro': r'1408\s+CA Sindipetro Caxias\s+R\$\s+([\d.,]+)',
    'Sindipetro': r'0827\s+Sindipetro Caxias\s+\d+\s+R\$\s+([\d.,]+)',
    'Dif_AMS': r'2980\s+Dif AMS Grande Risco\s+[\d,]+\s+R\$\s+([\d.,]+)',
    'Falta_Nao_Just': r'0446\s+Falta Não Just C/Pet\s+[\d,]+\s+R\$\s+([\d.,]+)',
    'Beneficio_Farmacia': r'0569\s+Novo Benefício Farmácia\s+[\d,]+\s+R\$\s+([\d.,]+)',
}




def extrair_mes_ano_e_rubricas(pdf_path):
    """Extrai mês/ano e todas as rubricas de um PDF"""
    try:
        with pdfplumber.open(pdf_path) as pdf:
            texto = pdf.pages[0].extract_text()
           
            if not texto:
                return None, {}
           
            # Extrair Mês/Ano
            match = re.search(r'(\d{2}/\d{4})', texto)
            mes_ano = match.group(1) if match else None
           
            if not mes_ano:
                return None, {}
           
            # Extrair todas as rubricas
            rubricas_encontradas = {}
           
            for nome_rubrica, padrao in PADROES_RUBRICAS.items():
                match = re.search(padrao, texto)
                if match:
                    valor = converter_valor_br(match.group(1))
                    if valor > 0:
                        rubricas_encontradas[nome_rubrica] = valor
           
            return mes_ano, rubricas_encontradas
           
    except Exception as e:
        print(f"   ✗ Erro: {e}")
        return None, {}




def processar_trabalhador():
    """Processa todos os PDFs e gera CSVs para o PJe-Calc"""
   
    # Caminhos
    pasta_pdfs = Path("pdfs_trabalhador")
    pasta_saida = Path("csvs_gerados")
   
    # Verificar se pasta de PDFs existe
    if not pasta_pdfs.exists():
        print(f"❌ ERRO: Pasta '{pasta_pdfs}' não encontrada!")
        print(f"📁 Crie a pasta e coloque os PDFs dentro.")
        return
   
    # Buscar PDFs
    pdfs = sorted(list(pasta_pdfs.glob("*.pdf")))
   
    if not pdfs:
        print(f"⚠️ Nenhum PDF encontrado em: {pasta_pdfs}")
        print(f"📁 Coloque os PDFs na pasta e tente novamente.")
        return
   
    print(f"\n{'='*70}")
    print(f"  GERADOR DE CSVS PARA PJe-CALC")
    print(f"{'='*70}")
    print(f"\n📄 Encontrados {len(pdfs)} PDFs")
    print(f"🔄 Extraindo rubricas...\n")
   
    # Dicionário: {rubrica: [(mes_ano, valor), ...]}
    dados_por_rubrica = {}
   
    # Processar cada PDF
    for i, pdf in enumerate(pdfs, 1):
        print(f"[{i:3d}/{len(pdfs)}] {pdf.name[:50]:<50}", end=" ")
       
        mes_ano, rubricas = extrair_mes_ano_e_rubricas(pdf)
       
        if mes_ano:
            print(f"✓ {mes_ano} - {len(rubricas)} rubricas")
           
            # Adicionar ao dicionário
            for nome_rubrica, valor in rubricas.items():
                if nome_rubrica not in dados_por_rubrica:
                    dados_por_rubrica[nome_rubrica] = []
               
                dados_por_rubrica[nome_rubrica].append({
                    'MES_ANO': mes_ano,
                    'VALOR': valor
                })
        else:
            print("✗ Mês/Ano não encontrado")
   
    # Criar pasta de saída
    pasta_saida.mkdir(exist_ok=True)
   
    print(f"\n{'='*70}")
    print(f"📊 GERANDO CSVS...")
    print(f"{'='*70}\n")
   
    # Gerar 1 CSV por rubrica
    total_csvs = 0
   
    for nome_rubrica, dados in dados_por_rubrica.items():
        # Ordenar por mês/ano
        dados_ordenados = sorted(dados, key=lambda x: x['MES_ANO'])
       
        # Adicionar colunas fixas
        for item in dados_ordenados:
            item['FGTS'] = 'N'
            item['FGTS_REC'] = 'N'
            item['CONTRIBUICAO_SOCIAL'] = 'N'
            item['CONTRIBUICAO_SOCIAL_REC'] = 'N'
       
        # Criar DataFrame
        df = pl.DataFrame(dados_ordenados)
       
        # Reordenar colunas
        df = df.select(['MES_ANO', 'VALOR', 'FGTS', 'FGTS_REC', 'CONTRIBUICAO_SOCIAL', 'CONTRIBUICAO_SOCIAL_REC'])
       
        # Formatar valores com 2 casas decimais
        df = df.with_columns([
            pl.col('VALOR').round(2)
        ])
       
        # Nome do arquivo
        nome_arquivo = f"{nome_rubrica}.csv"
        caminho_arquivo = pasta_saida / nome_arquivo
       
        # Salvar CSV manualmente para garantir formato correto
        with open(caminho_arquivo, 'w', encoding='utf-8') as f:
            # Escrever cabeçalho
            f.write('MES_ANO;VALOR;FGTS;FGTS_REC;CONTRIBUICAO_SOCIAL;CONTRIBUICAO_SOCIAL_REC\n')
           
            # Escrever dados
            for row in df.iter_rows():
                mes_ano, valor, fgts, fgts_rec, contrib_social, contrib_social_rec = row
                # Formatar valor com vírgula (padrão brasileiro)
                valor_formatado = f"{valor:.2f}".replace('.', ',')
                f.write(f'{mes_ano};{valor_formatado};{fgts};{fgts_rec};{contrib_social};{contrib_social_rec}\n')
       
        print(f"✓ {nome_arquivo:<40} ({len(dados_ordenados)} meses)")
        total_csvs += 1
   
    print(f"\n{'='*70}")
    print(f"✅ CONCLUÍDO!")
    print(f"{'='*70}")
    print(f"\n📊 Estatísticas:")
    print(f"   PDFs processados: {len(pdfs)}")
    print(f"   Rubricas encontradas: {len(dados_por_rubrica)}")
    print(f"   CSVs gerados: {total_csvs}")
    print(f"\n📁 Arquivos salvos em: {pasta_saida.absolute()}")




if __name__ == "__main__":
    processar_trabalhador()  

import pdfplumber
import polars as pl
import re
from pathlib import Path

 

def converter_valor_br(texto):
    “””Converte R$ 1.234,56 para float 1234.56″””
    if not texto:
        return 0.0
    return float(texto.replace(‘.’, ”).replace(‘,’, ‘.’))

 

# Dicionário com TODOS os padrões de rubricas
PADROES_RUBRICAS = {
    ‘Salario_Basico’: r’0001\s+Salário Básico\s+\d+\s+R\$\s+([\d.,]+)’,
    ‘Adicional_Periculosidade’: r’0201\s+Adicional Periculosidade\s+\d+\s+R\$\s+([\d.,]+)’,
    ‘Adicional_Noturno’: r’1061\s+Adicional Trab\.\s+Noturno\s+\d+\s+R\$\s+([\d.,]+)’,
    ‘Adicional_HRA’: r’1062\s+Adicional HRA\s+\d+\s+R\$\s+([\d.,]+)’,
    ‘Complemento_RMNR’: r’0192\s+Complemento da RMNR\s+R\$\s+([\d.,]+)’,
    ‘Anuenio’: r’0015\s+Anuênio\s+[\d,]+\s+R\$\s+([\d.,]+)’,
    ‘Ferias_Petros’: r’1079\s+Férias C/Petros\s+\d+\s+R\$\s+([\d.,]+)’,
    ‘Gratificacao_Ferias’: r’0403\s+Gratificação de Ferias\s+\d+\s+R\$\s+([\d.,]+)’,
    ‘Media_Hora_Extra’: r’0395\s+Média Hora Extra\s+\d+\s+R\$\s+([\d.,]+)’,
    ‘Media_Rep_Sem’: r’0392\s+Média Rep\.\s+Sem\.\s+Rem HE\s+\d+\s+R\$\s+([\d.,]+)’,
    ‘Compl_Cand_Interno’: r’0075\s+Compl Re Cand Interno C/P\s+\d+\s+R\$\s+([\d.,]+)’,
    ‘HE_Troca_Turno’: r’0528\s+HE Troca de Turno\s+\d+\s+R\$\s+([\d.,]+)’,
    ‘RSR_HE_Troca’: r’0532\s+RSR-HE Troca de Turno\s+R\$\s+([\d.,]+)’,
    ‘Imposto_Renda’: r’0927\s+Imposto de Renda\s+[\d,]+\s+R\$\s+([\d.,]+)’,
    ‘IR_Ferias’: r’0926\s+Imp Renda Férias\s+[\d,]+\s+R\$\s+([\d.,]+)’,
    ‘INSS’: r’0923\s+Contribuição INSS\s+[\d,]+\s+R\$\s+([\d.,]+)’,
    ‘Petros’: r’0985\s+Petros II Contr Regular\s+\d+\s+R\$\s+([\d.,]+)’,
    ‘Desc_Adiant_Sal’: r’5636\s+Desc Adiant sal sem IR\s+R\$\s+([\d.,]+)’,
    ‘Desc_Adto_Ferias’: r’5118\s+Desc Adto Férias\s+\d+\s+R\$\s+([\d.,]+)’,
    ‘Desc_Adto_GFE’: r’5119\s+Desc Adto GFE\s+\d+\s+R\$\s+([\d.,]+)’,
    ‘AMS_Grande_Risco’: r’0980\s+AMS Grande Risco\s+[\d,]+\s+R\$\s+([\d.,]+)’,
    ‘CA_Sindipetro’: r’1408\s+CA Sindipetro Caxias\s+R\$\s+([\d.,]+)’,
    ‘Sindipetro’: r’0827\s+Sindipetro Caxias\s+\d+\s+R\$\s+([\d.,]+)’,
    ‘Dif_AMS’: r’2980\s+Dif AMS Grande Risco\s+[\d,]+\s+R\$\s+([\d.,]+)’,
    ‘Falta_Nao_Just’: r’0446\s+Falta Não Just C/Pet\s+[\d,]+\s+R\$\s+([\d.,]+)’,
    ‘Beneficio_Farmacia’: r’0569\s+Novo Benefício Farmácia\s+[\d,]+\s+R\$\s+([\d.,]+)’,
}

 

def extrair_mes_ano_e_rubricas(pdf_path):
    “””Extrai mês/ano e todas as rubricas de um PDF”””
    try:
        with pdfplumber.open(pdf_path) as pdf:
            texto = pdf.pages[0].extract_text()
           
            if not texto:
                return None, {}
           
            # Extrair Mês/Ano
            match = re.search(r'(\d{2}/\d{4})’, texto)
            mes_ano = match.group(1) if match else None
           
            if not mes_ano:
                return None, {}
           
            # Extrair todas as rubricas
            rubricas_encontradas = {}
           
            for nome_rubrica, padrao in PADROES_RUBRICAS.items():
                match = re.search(padrao, texto)
                if match:
                    valor = converter_valor_br(match.group(1))
                    if valor > 0:
                        rubricas_encontradas[nome_rubrica] = valor
           
            return mes_ano, rubricas_encontradas
           
    except Exception as e:
        print(f”   ✗ Erro: {e}”)
        return None, {}

 

def processar_trabalhador():
    “””Processa todos os PDFs e gera CSVs para o PJe-Calc”””
   
    # Caminhos
    pasta_pdfs = Path(“pdfs_trabalhador”)
    pasta_saida = Path(“csvs_gerados”)
   
    # Verificar se pasta de PDFs existe
    if not pasta_pdfs.exists():
        print(f”❌ ERRO: Pasta ‘{pasta_pdfs}’ não encontrada!”)
        print(f”📁 Crie a pasta e coloque os PDFs dentro.”)
        return
   
    # Buscar PDFs
    pdfs = sorted(list(pasta_pdfs.glob(“*.pdf”)))
   
    if not pdfs:
        print(f”⚠️ Nenhum PDF encontrado em: {pasta_pdfs}”)
        print(f”📁 Coloque os PDFs na pasta e tente novamente.”)
        return
   
    print(f”\n{‘=’*70}”)
    print(f”  GERADOR DE CSVS PARA PJe-CALC”)
    print(f”{‘=’*70}”)
    print(f”\n📄 Encontrados {len(pdfs)} PDFs”)
    print(f”🔄 Extraindo rubricas…\n”)
   
    # Dicionário: {rubrica: [(mes_ano, valor), …]}
    dados_por_rubrica = {}
   
    # Processar cada PDF
    for i, pdf in enumerate(pdfs, 1):
        print(f”[{i:3d}/{len(pdfs)}] {pdf.name[:50]:<50}”, end=” “)
       
        mes_ano, rubricas = extrair_mes_ano_e_rubricas(pdf)
       
        if mes_ano:
            print(f”✓ {mes_ano} – {len(rubricas)} rubricas”)
           
            # Adicionar ao dicionário
            for nome_rubrica, valor in rubricas.items():
                if nome_rubrica not in dados_por_rubrica:
                    dados_por_rubrica[nome_rubrica] = []
               
                dados_por_rubrica[nome_rubrica].append({
                    ‘MES_ANO’: mes_ano,
                    ‘VALOR’: valor
                })
        else:
            print(“✗ Mês/Ano não encontrado”)
   
    # Criar pasta de saída
    pasta_saida.mkdir(exist_ok=True)
   
    print(f”\n{‘=’*70}”)
    print(f”📊 GERANDO CSVS…”)
    print(f”{‘=’*70}\n”)
   
    # Gerar 1 CSV por rubrica
    total_csvs = 0
   
    for nome_rubrica, dados in dados_por_rubrica.items():
        # Ordenar por mês/ano
        dados_ordenados = sorted(dados, key=lambda x: x[‘MES_ANO’])
       
        # Adicionar colunas fixas
        for item in dados_ordenados:
            item[‘FGTS’] = ‘N’
            item[‘FGTS_REC’] = ‘N’
            item[‘CONTRIBUICAO_SOCIAL’] = ‘N’
            item[‘CONTRIBUICAO_SOCIAL_REC’] = ‘N’
       
        # Criar DataFrame
        df = pl.DataFrame(dados_ordenados)
       
        # Reordenar colunas
        df = df.select([‘MES_ANO’, ‘VALOR’, ‘FGTS’, ‘FGTS_REC’, ‘CONTRIBUICAO_SOCIAL’, ‘CONTRIBUICAO_SOCIAL_REC’])
       
        # Formatar valores com 2 casas decimais
        df = df.with_columns([
            pl.col(‘VALOR’).round(2)
        ])
       
        # Nome do arquivo
        nome_arquivo = f”{nome_rubrica}.csv”
        caminho_arquivo = pasta_saida / nome_arquivo
       
        # Salvar CSV manualmente para garantir formato correto
        with open(caminho_arquivo, ‘w’, encoding=’utf-8′) as f:
            # Escrever cabeçalho
            f.write(‘MES_ANO;VALOR;FGTS;FGTS_REC;CONTRIBUICAO_SOCIAL;CONTRIBUICAO_SOCIAL_REC\n’)
           
            # Escrever dados
            for row in df.iter_rows():
                mes_ano, valor, fgts, fgts_rec, contrib_social, contrib_social_rec = row
                # Formatar valor com vírgula (padrão brasileiro)
                valor_formatado = f”{valor:.2f}”.replace(‘.’, ‘,’)
                f.write(f'{mes_ano};{valor_formatado};{fgts};{fgts_rec};{contrib_social};{contrib_social_rec}\n’)
       
        print(f”✓ {nome_arquivo:<40} ({len(dados_ordenados)} meses)”)
        total_csvs += 1
   
    print(f”\n{‘=’*70}”)
    print(f”✅ CONCLUÍDO!”)
    print(f”{‘=’*70}”)
    print(f”\n📊 Estatísticas:”)
    print(f”   PDFs processados: {len(pdfs)}”)
    print(f”   Rubricas encontradas: {len(dados_por_rubrica)}”)
    print(f”   CSVs gerados: {total_csvs}”)
    print(f”\n📁 Arquivos salvos em: {pasta_saida.absolute()}”)

 

if __name__ == “__main__”:
    processar_trabalhador()

import pdfplumber
import pandas as pd
import re

def extrair_contracheque(caminho_pdf):
“””Extrai dados do contracheque Petrobras”””
dados = {
‘matricula’: None,
‘nome’: None,
‘mes_ano’: None,
‘proventos’: {},
‘descontos’: {},
‘total_liquido’: None
}

with pdfplumber.open(caminho_pdf) as pdf:
texto = pdf.pages[0].extract_text()

# Extrair informações básicas
if match := re.search(r'(\d{7})\s+Matrícula’, texto):
dados[‘matricula’] = match.group(1)

if match := re.search(r’Nome\s+(\d+)\s+([A-Z\s]+)\s+Matrícula’, texto):
dados[‘nome’] = match.group(2).strip()

# Extrair rubricas (exemplo para Adicional Periculosidade)
if match := re.search(r’0201\s+Adicional Periculosidade\s+\d+\s+R\$\s+([\d.,]+)’, texto):
valor = match.group(1).replace(‘.’, ”).replace(‘,’, ‘.’)
dados[‘proventos’][‘adicional_periculosidade’] = float(valor)

# Total líquido
if match := re.search(r’Total Líquido\s+R\$\s+([\d.,]+)’, texto):
valor = match.group(1).replace(‘.’, ”).replace(‘,’, ‘.’)
dados[‘total_liquido’] = float(valor)

return dados

# Processar múltiplos PDFs
import os
resultados = []

pasta_pdfs = “caminho/para/pasta/com/pdfs”
for arquivo in os.listdir(pasta_pdfs):
if arquivo.endswith(‘.pdf’):
caminho = os.path.join(pasta_pdfs, arquivo)
try:
dados = extrair_contracheque(caminho)
resultados.append(dados)
print(f”✓ Processado: {arquivo}”)
except Exception as e:
print(f”✗ ERRO em {arquivo}: {e}”)

# Exportar para Excel para revisão
df = pd.DataFrame(resultados)
df.to_excel(‘contracheques_extraidos.xlsx’, index=False)

import pdfplumber
import pandas as pd
import re

def extrair_contracheque(caminho_pdf):
“””Extrai dados do contracheque Petrobras”””
dados = {
‘matricula’: None,
‘nome’: None,
‘mes_ano’: None,
‘proventos’: {},
‘descontos’: {},
‘total_liquido’: None
}

with pdfplumber.open(caminho_pdf) as pdf:
texto = pdf.pages[0].extract_text()

# Extrair informações básicas
if match := re.search(r'(\d{7})\s+Matrícula’, texto):
dados[‘matricula’] = match.group(1)

if match := re.search(r’Nome\s+(\d+)\s+([A-Z\s]+)\s+Matrícula’, texto):
dados[‘nome’] = match.group(2).strip()

# Extrair rubricas (exemplo para Adicional Periculosidade)
if match := re.search(r’0201\s+Adicional Periculosidade\s+\d+\s+R\$\s+([\d.,]+)’, texto):
valor = match.group(1).replace(‘.’, ”).replace(‘,’, ‘.’)
dados[‘proventos’][‘adicional_periculosidade’] = float(valor)

# Total líquido
if match := re.search(r’Total Líquido\s+R\$\s+([\d.,]+)’, texto):
valor = match.group(1).replace(‘.’, ”).replace(‘,’, ‘.’)
dados[‘total_liquido’] = float(valor)

return dados

# Processar múltiplos PDFs
import os
resultados = []

pasta_pdfs = “caminho/para/pasta/com/pdfs”
for arquivo in os.listdir(pasta_pdfs):
if arquivo.endswith(‘.pdf’):
caminho = os.path.join(pasta_pdfs, arquivo)
try:
dados = extrair_contracheque(caminho)
resultados.append(dados)
print(f”✓ Processado: {arquivo}”)
except Exception as e:
print(f”✗ ERRO em {arquivo}: {e}”)

# Exportar para Excel para revisão
df = pd.DataFrame(resultados)
df.to_excel(‘contracheques_extraidos.xlsx’, index=False)

⚠️ AVISOS CRÍTICOS

1. RISCO ALTO: Sistema complexo para iniciante
2. TESTE EXAUSTIVO: Valide MANUALMENTE os primeiros 50 casos
3. REVISÃO OBRIGATÓRIA: Sempre revise antes de finalizar
4. BACKUP: Mantenha todos os PDFs originais
5. RESPONSABILIDADE: Erros podem ter consequências jurídicas graves

Ferramentas

 

Extração de dados de tabelas em PDF: Camelot, pdfplumber, PyMuPDF

Leitor e criador de planilhas Excel: openpyxl

Análise e manipulação de dados: PandasPolars

Workflow automation: N8NZapier

Workflow orchestration: Apache Airflow

Excel automation: xlwings

Sequência de Caracteres: Regex (Regular expression)

RPA (Robotic Process Automation): UiPath, TagUI, Power Automate DesktopOpenRPAAutomation AnywhereRobocorpLiberRPAUi.Vision RPARobocorp

Bibliotecas de browser automation: Playwright, Selenium

GUI (Graphical User Interface) automation: PyAutoGUI, SikuliX, TagUI, Automation Anywhere

OCR: Automation Anywhere, Tesseract

ERP: ERPNext, Dolibarr

Contabilidade: GnuCash

Linguagens de programação: Python, Rust, Go, Elixir

Bibliotecas Python: PyMuPDFPyAutoGUI, Pandasxlsxwriter

Editores de código: Virtual Studio CodePyCharm

 

Manuais Fase 1

 

Rayzer Dev Manual: By ChatGPTBy ClaudeBy PerplexityBy GrokBy ManusBy DeepSeekBy Le ChatBy Gemini

Estratégias

Links oficiais Python: The Python TutorialUsing Python on Windows, Python 3.14.0 documentation, What’s new in Python 3.14

Rolar para cima