Spaces:
Sleeping
Sleeping
| """ | |
| 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']}") | |