geracao_documentos / modelos_loader.py
gui-sparim's picture
Upload 13 files
bbbd911 verified
"""
modelos_loader.py
Módulo para carregar conteúdo de metodologia e anexos dos modelos de avaliação.
Extrai o conteúdo estruturado dos documentos para reconstrução com formatação padrão.
"""
import os
from docx import Document
from docx.shared import Pt, Inches
from typing import Optional, List, Dict, Any, Tuple
# Diretório base dos modelos (relativo ao script principal)
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
MODELOS_DIR = os.path.join(SCRIPT_DIR, "modelos")
def modelo_tem_arquivos(nome_modelo: str) -> bool:
"""Verifica se um modelo possui pasta com arquivos de metodologia e anexos."""
if not nome_modelo or nome_modelo == "Outros":
return False
pasta_modelo = os.path.join(MODELOS_DIR, nome_modelo)
if not os.path.isdir(pasta_modelo):
return False
metodologia_path = os.path.join(pasta_modelo, "METODOLOGIA.docx")
anexos_path = os.path.join(pasta_modelo, "ANEXOS.docx")
return os.path.exists(metodologia_path) or os.path.exists(anexos_path)
def get_status_modelo(nome_modelo: str) -> str:
"""Retorna uma string descrevendo o status dos arquivos do modelo."""
if not nome_modelo or nome_modelo == "Outros":
return "Modelo genérico (sem arquivos específicos)"
pasta_modelo = os.path.join(MODELOS_DIR, nome_modelo)
if not os.path.isdir(pasta_modelo):
return "⚠️ Pasta do modelo não encontrada"
metodologia = os.path.exists(os.path.join(pasta_modelo, "METODOLOGIA.docx"))
anexos = os.path.exists(os.path.join(pasta_modelo, "ANEXOS.docx"))
if metodologia and anexos:
return "✅ Metodologia e Anexos disponíveis"
elif metodologia:
return "✅ Metodologia disponível | ⚠️ Anexos não encontrados"
elif anexos:
return "⚠️ Metodologia não encontrada | ✅ Anexos disponíveis"
else:
return "⚠️ Nenhum arquivo encontrado para este modelo"
def extrair_texto_tabela(table) -> List[List[Dict]]:
"""
Extrai o conteúdo de uma tabela como lista de listas.
Cada célula é um dict com 'texto' e 'cor' (RGB tuple ou None).
"""
from docx.shared import RGBColor
dados = []
for row in table.rows:
linha = []
for cell in row.cells:
texto = cell.text.strip()
cor = None
# Verificar cor do primeiro run com texto
for p in cell.paragraphs:
for run in p.runs:
if run.text.strip() and run.font.color and run.font.color.rgb:
rgb = run.font.color.rgb
# Verificar se é vermelho
if rgb[0] > 200 and rgb[1] < 100 and rgb[2] < 100:
cor = (rgb[0], rgb[1], rgb[2])
break
if cor:
break
linha.append({'texto': texto, 'cor': cor})
dados.append(linha)
return dados
def extrair_conteudo_documento(filepath: str) -> List[Dict[str, Any]]:
"""
Extrai o conteúdo estruturado de um documento DOCX.
Retorna uma lista de elementos, cada um com:
- tipo: 'secao', 'subsecao', 'subsubsecao', 'subsubsubsecao', 'texto', 'variavel', 'tabela', 'vazio', 'imagem', 'anexo_titulo'
- conteudo: texto ou dados da tabela
- negrito: bool (para texto)
- alinhamento: 'left', 'center', 'right', 'justify'
- cor: tuple RGB ou None (para texto vermelho)
"""
if not os.path.exists(filepath):
return []
try:
doc = Document(filepath)
except Exception as e:
print(f"Erro ao carregar {filepath}: {e}")
return []
elementos = []
# Mapeamento de estilos para tipos
mapa_estilos = {
'LA_X': 'secao', # Seção principal (ex: 4)
'LA_X.X': 'subsecao', # Subseção (ex: 3.2)
'LA_X.X.X': 'subsubsecao', # Sub-subseção (ex: 3.2.1)
'LA_X.X.X.X': 'subsubsubsecao', # Sub-sub-subseção (ex: 3.2.1.1)
'LA_txt': 'texto',
'LA_Vars': 'variavel',
'LA_lb': 'vazio',
'LA_ANEX': 'anexo_titulo', # Título de anexo
'Item N.': 'subtitulo_centralizado', # Subtítulo centralizado
}
# Mapeamento de alinhamento
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.shared import RGBColor
def get_alinhamento(p):
if p.alignment == WD_ALIGN_PARAGRAPH.CENTER:
return 'center'
elif p.alignment == WD_ALIGN_PARAGRAPH.RIGHT:
return 'right'
elif p.alignment == WD_ALIGN_PARAGRAPH.JUSTIFY:
return 'justify'
return 'left'
def get_cor_predominante(p):
"""Retorna a cor predominante do parágrafo (se for vermelho)."""
for run in p.runs:
if run.font.color and run.font.color.rgb:
cor = run.font.color.rgb
# Verificar se é vermelho (vários tons)
r, g, b = cor[0], cor[1], cor[2]
if r > 200 and g < 100 and b < 100: # É vermelho
return (r, g, b)
return None
# Criar um iterador que intercala parágrafos e tabelas na ordem do documento
body = doc.element.body
tabela_idx = 0
para_idx = 0
for elemento in body:
tag = elemento.tag.split('}')[-1] if '}' in elemento.tag else elemento.tag
if tag == 'p':
# É um parágrafo
if para_idx < len(doc.paragraphs):
p = doc.paragraphs[para_idx]
para_idx += 1
estilo = p.style.name if p.style else 'Normal'
texto = p.text.strip()
alinhamento = get_alinhamento(p)
cor = get_cor_predominante(p)
# Verificar se tem negrito nos runs
tem_negrito = any(run.bold for run in p.runs if run.text.strip())
# Verificar se o parágrafo contém imagem
tem_imagem = False
imagem_data = None
# Procurar por drawing/blip no XML do parágrafo
drawings = elemento.findall('.//{http://schemas.openxmlformats.org/wordprocessingml/2006/main}drawing')
if drawings:
tem_imagem = True
# Guardar referência ao elemento XML para copiar depois
imagem_data = {
'paragraph_element': elemento,
'doc': doc
}
tipo = mapa_estilos.get(estilo, 'texto')
if tem_imagem:
elementos.append({
'tipo': 'imagem',
'conteudo': texto,
'imagem_data': imagem_data,
'alinhamento': alinhamento,
'estilo_original': estilo,
'cor': cor
})
elif not texto:
tipo = 'vazio'
elementos.append({
'tipo': tipo,
'conteudo': '',
'alinhamento': alinhamento,
'estilo_original': estilo,
'cor': None
})
else:
elementos.append({
'tipo': tipo,
'conteudo': texto,
'negrito': tem_negrito,
'alinhamento': alinhamento,
'estilo_original': estilo,
'cor': cor
})
elif tag == 'tbl':
# É uma tabela
if tabela_idx < len(doc.tables):
tabela = doc.tables[tabela_idx]
tabela_idx += 1
dados_tabela = extrair_texto_tabela(tabela)
elementos.append({
'tipo': 'tabela',
'conteudo': dados_tabela,
'linhas': len(dados_tabela),
'colunas': len(dados_tabela[0]) if dados_tabela else 0
})
return elementos
def carregar_conteudo_metodologia(nome_modelo: str) -> List[Dict[str, Any]]:
"""Carrega o conteúdo estruturado do arquivo METODOLOGIA.docx."""
if not nome_modelo or nome_modelo == "Outros":
return []
filepath = os.path.join(MODELOS_DIR, nome_modelo, "METODOLOGIA.docx")
return extrair_conteudo_documento(filepath)
def carregar_conteudo_anexos(nome_modelo: str) -> List[Dict[str, Any]]:
"""Carrega o conteúdo estruturado do arquivo ANEXOS.docx."""
if not nome_modelo or nome_modelo == "Outros":
return []
filepath = os.path.join(MODELOS_DIR, nome_modelo, "ANEXOS.docx")
return extrair_conteudo_documento(filepath)
def listar_modelos_com_arquivos() -> list:
"""Lista todos os modelos que possuem arquivos de metodologia/anexos."""
if not os.path.isdir(MODELOS_DIR):
return []
modelos = []
for nome in os.listdir(MODELOS_DIR):
pasta = os.path.join(MODELOS_DIR, nome)
if os.path.isdir(pasta):
metodologia = os.path.exists(os.path.join(pasta, "METODOLOGIA.docx"))
anexos = os.path.exists(os.path.join(pasta, "ANEXOS.docx"))
if metodologia or anexos:
modelos.append({
"nome": nome,
"tem_metodologia": metodologia,
"tem_anexos": anexos
})
return modelos
# Para teste direto do módulo
if __name__ == "__main__":
print("=== Teste do Módulo de Carregamento de Modelos ===\n")
modelos = listar_modelos_com_arquivos()
print(f"Modelos com arquivos: {len(modelos)}")
for modelo in modelos:
print(f"\n--- {modelo['nome']} ---")
print(f" Status: {get_status_modelo(modelo['nome'])}")
if modelo['tem_metodologia']:
elementos = carregar_conteudo_metodologia(modelo['nome'])
print(f"\n Elementos extraídos da metodologia: {len(elementos)}")
# Contar por tipo
tipos = {}
for elem in elementos:
t = elem['tipo']
tipos[t] = tipos.get(t, 0) + 1
print(f" Por tipo: {tipos}")
# Mostrar estrutura de títulos
print("\n Estrutura de títulos:")
for elem in elementos:
if elem['tipo'] in ['secao', 'subsecao', 'subsubsecao', 'subsubsubsecao']:
indent = {'secao': '', 'subsecao': ' ', 'subsubsecao': ' ', 'subsubsubsecao': ' '}
print(f" {indent[elem['tipo']]}{elem['tipo'].upper()}: {elem['conteudo'][:50]}...")
elif elem['tipo'] == 'tabela':
print(f" TABELA: {elem['linhas']}x{elem['colunas']}")