Spaces:
Running
Running
| """ | |
| app.py | |
| Aplicação Principal - Sistema de Geração de Laudos | |
| Inclui interface gráfica e lógica de geração de documentos DOCX. | |
| """ | |
| import gradio as gr | |
| import os | |
| import re | |
| import shutil | |
| from datetime import datetime | |
| from docx import Document | |
| from docx.shared import Pt, Inches, Cm, RGBColor | |
| from docx.enum.text import WD_ALIGN_PARAGRAPH | |
| from docx.enum.table import WD_TABLE_ALIGNMENT, WD_CELL_VERTICAL_ALIGNMENT | |
| # Importar formatadores DOCX | |
| from docx_formatters import ( | |
| NumeradorSecoes, | |
| aplicar_cor_run, | |
| set_cell_shading, | |
| criar_paragrafo_formatado, | |
| add_heading_custom, | |
| add_section_title, | |
| add_subsection_title, | |
| add_subsubsection_title, | |
| add_subsubsubsection_title, | |
| add_body_text, | |
| add_placeholder_text, | |
| add_bullet_text, | |
| formatar_celula_tabela, | |
| add_simple_table, | |
| configurar_linha_tabela_altura, | |
| criar_celula_cabecalho_tabela, | |
| criar_celula_dados_tabela, | |
| inserir_imagem_de_documento, | |
| add_image_placeholder, | |
| ) | |
| # Importar funções do módulo utils | |
| from utils import ( | |
| aplicar_mascara_monetaria, | |
| processar_upload_pdf, | |
| formatar_valor_monetario, | |
| formatar_documentacao_para_documento, | |
| formatar_valores_mercado_para_documento, | |
| formatar_motivos_desvalorizantes | |
| ) | |
| # Importar valores | |
| from valores import ( | |
| TEXTO_CONSIDERACOES_INICIAIS, | |
| TEXTO_DIAGNOSTICO_MERCADO, | |
| TEXTO_OBSERVACOES_COMPLEMENTARES, | |
| UNIDADES_DEMANDANTES, | |
| TIPOS_REQUERIMENTO, | |
| UNIDADES_RESPONSAVEIS, | |
| TECNICOS_RESPONSAVEIS, | |
| METODOS_AVALIACAO, | |
| MODELOS_AVALIACAO, | |
| MAX_MOTIVOS_DESVALORIZANTES, | |
| MAX_VALORES_MERCADO, | |
| MAX_DOCUMENTACAO_PADRAO, | |
| MAX_DOCUMENTACAO_ESPECIFICA | |
| ) | |
| # Importar carregador de modelos | |
| from modelos_loader import ( | |
| modelo_tem_arquivos, | |
| get_status_modelo, | |
| carregar_conteudo_metodologia, | |
| carregar_conteudo_anexos | |
| ) | |
| # ============================================================================ | |
| # CONFIGURAÇÕES E CONSTANTES | |
| # ============================================================================ | |
| SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) | |
| TEMPLATE_PATH = os.path.join(SCRIPT_DIR, "modelo_laudo_tributario.docx") | |
| HEADER_PATH = os.path.join(SCRIPT_DIR, "header.docx") | |
| OUTPUT_DIR = os.path.join(SCRIPT_DIR, "laudos_gerados") | |
| os.makedirs(OUTPUT_DIR, exist_ok=True) | |
| WD_ALIGN_VERTICAL = WD_CELL_VERTICAL_ALIGNMENT | |
| # ============================================================================ | |
| # INSERÇÃO DE CONTEÚDO DE METODOLOGIA E ANEXOS | |
| # ============================================================================ | |
| def inserir_conteudo_metodologia(doc, elementos, numerador): | |
| """ | |
| Insere o conteúdo da metodologia no documento. | |
| Args: | |
| doc: Documento python-docx | |
| elementos: Lista de elementos da metodologia | |
| numerador: Instância de NumeradorSecoes para manter numeração | |
| """ | |
| for elem in elementos: | |
| tipo = elem['tipo'] | |
| conteudo = elem.get('conteudo', '') | |
| cor = elem.get('cor') | |
| if tipo == 'secao': | |
| p = add_section_title(doc, numerador.secao(), conteudo) | |
| if cor: | |
| for run in p.runs: | |
| aplicar_cor_run(run, cor) | |
| elif tipo == 'subsecao': | |
| p = add_subsection_title(doc, numerador.subsecao(), conteudo.strip()) | |
| if cor: | |
| for run in p.runs: | |
| aplicar_cor_run(run, cor) | |
| elif tipo == 'subsubsecao': | |
| p = add_subsubsection_title(doc, numerador.subsubsecao(), conteudo.strip()) | |
| if cor: | |
| for run in p.runs: | |
| aplicar_cor_run(run, cor) | |
| elif tipo == 'subsubsubsecao': | |
| p = add_subsubsubsection_title(doc, numerador.subsubsubsecao(), conteudo.strip()) | |
| if cor: | |
| for run in p.runs: | |
| aplicar_cor_run(run, cor) | |
| elif tipo == 'texto' and conteudo: | |
| p = add_body_text(doc, conteudo, indent=True) | |
| if cor: | |
| for run in p.runs: | |
| aplicar_cor_run(run, cor) | |
| elif tipo == 'variavel' and conteudo: | |
| p = add_bullet_text(doc, conteudo) | |
| if cor: | |
| for run in p.runs: | |
| aplicar_cor_run(run, cor) | |
| elif tipo == 'tabela' and conteudo: | |
| add_simple_table(doc, conteudo, header_row=True) | |
| def inserir_conteudo_anexos(doc, elementos): | |
| """Insere o conteúdo dos anexos no documento.""" | |
| if not elementos: | |
| return False | |
| for elem in elementos: | |
| tipo = elem['tipo'] | |
| conteudo = elem.get('conteudo', '') | |
| alinhamento = elem.get('alinhamento', 'left') | |
| cor = elem.get('cor') | |
| conteudo_texto = conteudo if isinstance(conteudo, str) else '' | |
| align = WD_ALIGN_PARAGRAPH.CENTER if alinhamento == 'center' else WD_ALIGN_PARAGRAPH.LEFT | |
| if tipo == 'anexo_titulo' or (conteudo_texto and conteudo_texto.upper().startswith('ANEXO')): | |
| doc.add_page_break() | |
| criar_paragrafo_formatado( | |
| doc, conteudo_texto, negrito=True, tamanho=12, cor=cor, | |
| espaco_antes=12, espaco_depois=12, alinhamento=WD_ALIGN_PARAGRAPH.CENTER | |
| ) | |
| elif tipo == 'subtitulo_centralizado' or (tipo == 'texto' and alinhamento == 'center' and conteudo_texto): | |
| criar_paragrafo_formatado( | |
| doc, conteudo_texto, negrito=True, tamanho=11, cor=cor, | |
| espaco_antes=10, espaco_depois=6, alinhamento=WD_ALIGN_PARAGRAPH.CENTER | |
| ) | |
| elif tipo == 'imagem': | |
| imagem_data = elem.get('imagem_data') | |
| if not inserir_imagem_de_documento(doc, imagem_data): | |
| add_image_placeholder(doc) | |
| elif tipo in ['secao', 'subsecao', 'subsubsecao']: | |
| criar_paragrafo_formatado( | |
| doc, conteudo_texto.strip(), negrito=True, tamanho=11, cor=cor, | |
| espaco_antes=10, espaco_depois=6, alinhamento=align | |
| ) | |
| elif tipo == 'texto' and conteudo_texto: | |
| p = add_body_text(doc, conteudo_texto, indent=False) | |
| if alinhamento == 'center': | |
| p.alignment = WD_ALIGN_PARAGRAPH.CENTER | |
| if cor: | |
| for run in p.runs: | |
| aplicar_cor_run(run, cor) | |
| elif tipo == 'variavel' and conteudo_texto: | |
| p = add_bullet_text(doc, conteudo_texto) | |
| if cor: | |
| for run in p.runs: | |
| aplicar_cor_run(run, cor) | |
| elif tipo == 'tabela' and conteudo: | |
| add_simple_table(doc, conteudo, header_row=True) | |
| return True | |
| # ============================================================================ | |
| # GERADORES DE SEÇÕES DO LAUDO | |
| # ============================================================================ | |
| def gerar_cabecalho(doc, dados): | |
| """Gera o cabeçalho/tabela inicial do laudo.""" | |
| # Título | |
| criar_paragrafo_formatado( | |
| doc, "LAUDO DE AVALIAÇÃO", negrito=True, sublinhado=True, tamanho=14, | |
| alinhamento=WD_ALIGN_PARAGRAPH.CENTER | |
| ) | |
| # Número do laudo | |
| criar_paragrafo_formatado( | |
| doc, dados.get('numero_laudo', 'LA_XXX_XXXX'), tamanho=12, | |
| alinhamento=WD_ALIGN_PARAGRAPH.CENTER, espaco_depois=12 | |
| ) | |
| # Tabela de dados | |
| table = doc.add_table(rows=0, cols=2) | |
| table.style = 'Table Grid' | |
| table.alignment = WD_TABLE_ALIGNMENT.CENTER | |
| def add_section_header(text): | |
| row = table.add_row() | |
| row.height = Cm(1.2) | |
| cell = row.cells[0] | |
| cell.merge(row.cells[1]) | |
| criar_celula_cabecalho_tabela(cell, text) | |
| def add_row(label, value): | |
| row = table.add_row() | |
| configurar_linha_tabela_altura(row, 0.6) | |
| criar_celula_dados_tabela(row.cells[0], label, is_label=True) | |
| criar_celula_dados_tabela(row.cells[1], str(value) if value else "") | |
| # Definição das seções e seus campos | |
| secoes = [ | |
| ("SOLICITAÇÃO", [ | |
| ("Unidade demandante:", 'unidade_demandante'), | |
| ("Requerimento:", 'dados_requerimento'), | |
| ("Requerente/Representante legal:", 'representante_legal'), | |
| ("Motivos desvalorizantes alegados:", 'motivos_alegados'), | |
| ("Valor venal proposto contribuinte:", 'valor_proposto'), | |
| ("Documentação Padrão:", 'documentacao_padrao'), | |
| ("Documentação Específica:", 'documentacao_especifica'), | |
| ]), | |
| ("IMÓVEL OBJETO", [ | |
| ("Endereço:", 'endereco_imovel'), | |
| ("Bairro (Setor/Quarteirão):", 'bairro_imovel'), | |
| ("Lote Fiscal:", 'lote_imovel'), | |
| ("Inscrição:", 'inscricao_imovel'), | |
| ("Finalidade Imóvel:", 'finalidade_imovel'), | |
| ("Área Territorial Total / Privativa:", 'area_territorial'), | |
| ("Área construída:", 'construcoes_imovel'), | |
| ("Valores Venais Guias IPTU:", 'valores_venais'), | |
| ]), | |
| ("ANÁLISE VALOR DE MERCADO", [ | |
| ("Unidade responsável:", 'unidade_responsavel'), | |
| ("Técnico responsável:", 'tecnico_responsavel'), | |
| ("Método de Avaliação:", 'metodo_avaliacao'), | |
| ("Modelo de Avaliação utilizado:", 'modelo_avaliacao'), | |
| ]), | |
| ("CONCLUSÃO TÉCNICA", [ | |
| ("Valores de Mercado:", 'valores_mercado'), | |
| ("Referências:", 'datas_referencia'), | |
| ("Motivos desvalorizantes existentes:", 'motivos_existentes'), | |
| ]), | |
| ] | |
| for header, rows in secoes: | |
| add_section_header(header) | |
| for label, key in rows: | |
| add_row(label, dados.get(key, '')) | |
| for row in table.rows: | |
| row.cells[0].width = Inches(2.5) | |
| row.cells[1].width = Inches(4.0) | |
| def gerar_corpo_laudo(doc, dados, motivos_formatados, nome_modelo=None): | |
| """Gera o corpo principal do laudo de avaliação com numeração automática.""" | |
| num = NumeradorSecoes() # Numerador automático | |
| # 1. SOLICITAÇÃO | |
| add_section_title(doc, num.secao(), "SOLICITAÇÃO") | |
| add_subsection_title(doc, num.subsecao(), "CONSIDERAÇÕES INICIAIS") | |
| for paragrafo in TEXTO_CONSIDERACOES_INICIAIS.strip().split('\n\n'): | |
| add_body_text(doc, paragrafo.strip(), indent=True) | |
| add_subsection_title(doc, num.subsecao(), "DOCUMENTAÇÃO APRESENTADA") | |
| add_placeholder_text(doc, indent=True) | |
| # 2. IMÓVEL OBJETO | |
| add_section_title(doc, num.secao(), "IMÓVEL OBJETO") | |
| add_subsection_title(doc, num.subsecao(), "DESCRIÇÃO DO IMÓVEL") | |
| add_placeholder_text(doc, indent=True) | |
| add_subsection_title(doc, num.subsecao(), "CARACTERÍSTICAS PARTICULARMENTE DESVALORIZANTES") | |
| secoes_motivos = motivos_formatados.get('secoes', []) | |
| if not secoes_motivos: | |
| add_placeholder_text(doc, indent=True) | |
| else: | |
| for secao in secoes_motivos: | |
| add_subsubsection_title(doc, num.subsubsecao(), secao['titulo']) | |
| p = doc.add_paragraph() | |
| p.add_run(f"Status: {secao['status']}").italic = True | |
| p.paragraph_format.left_indent = Inches(0.5) | |
| add_placeholder_text(doc, indent=True) | |
| # 3. ANÁLISE DE MERCADO | |
| add_section_title(doc, num.secao(), "ANÁLISE VALOR DE MERCADO") | |
| add_subsection_title(doc, num.subsecao(), "DIAGNÓSTICO DE MERCADO") | |
| for paragrafo in TEXTO_DIAGNOSTICO_MERCADO.strip().split('\n\n'): | |
| add_body_text(doc, paragrafo.strip(), indent=True) | |
| # 3.2 METODOLOGIA - inserir conteúdo do modelo se disponível | |
| metodologia_inserida = False | |
| if nome_modelo and nome_modelo != "Outros": | |
| elementos_metodologia = carregar_conteudo_metodologia(nome_modelo) | |
| if elementos_metodologia: | |
| # Passa o numerador para manter a sequência | |
| inserir_conteudo_metodologia(doc, elementos_metodologia, num) | |
| metodologia_inserida = True | |
| print(f"Metodologia do modelo '{nome_modelo}' inserida.") | |
| if not metodologia_inserida: | |
| add_subsection_title(doc, num.subsecao(), "METODOLOGIA DE AVALIAÇÃO") | |
| add_placeholder_text(doc, indent=True) | |
| add_subsection_title(doc, num.subsecao(), "AVALIAÇÃO DO TERRENO") | |
| add_placeholder_text(doc, indent=True) | |
| # 4. ESPECIFICAÇÃO | |
| add_section_title(doc, num.secao(), "ESPECIFICAÇÃO DA AVALIAÇÃO") | |
| add_body_text(doc, f"Método: {dados.get('metodo_avaliacao', '')}", indent=True) | |
| add_placeholder_text(doc, indent=True) | |
| # CONCLUSÃO (numeração automática continua de onde parou) | |
| add_section_title(doc, num.secao(), "CONCLUSÃO TÉCNICA") | |
| add_body_text(doc, "Em face do acima exposto, esta EAV concluiu que:", indent=True) | |
| # Guardar número da seção de conclusão para referências | |
| secao_conclusao = num.numero_atual(0) | |
| add_subsection_title(doc, num.subsecao(), "SOBRE AS CARACTERÍSTICAS PARTICULARMENTE DESVALORIZANTES") | |
| if not secoes_motivos: | |
| add_body_text(doc, "- não foram identificados motivos desvalorizantes;", indent=True) | |
| else: | |
| for i, secao in enumerate(secoes_motivos, 1): | |
| if secao['confirmado']: | |
| texto = f"- {secao['titulo'].lower()}, conforme item 2.2.{i};" | |
| else: | |
| texto = f"- quanto ao motivo \"{secao['titulo'].lower()}\", não foi confirmado;" | |
| add_body_text(doc, texto, indent=True) | |
| add_subsection_title(doc, num.subsecao(), "SOBRE O VALOR DE MERCADO") | |
| # Gerar tabela de valores de mercado | |
| valores_mercado_lista = dados.get('valores_mercado_lista', []) | |
| if valores_mercado_lista: | |
| _gerar_tabela_valores_mercado(doc, valores_mercado_lista) | |
| else: | |
| add_placeholder_text(doc, indent=True) | |
| add_subsection_title(doc, num.subsecao(), "OBSERVAÇÕES COMPLEMENTARES") | |
| texto_obs = TEXTO_OBSERVACOES_COMPLEMENTARES.replace("{{num_paginas}}", "[XX]") | |
| texto_obs = texto_obs.replace("{{modelo_avaliacao}}", dados.get('modelo_avaliacao', '[preencher modelo de avaliação]')) | |
| texto_obs = texto_obs.replace("{{datas_referencia}}", dados.get('datas_referencia', '[preencher datas de referência]')) | |
| for linha in texto_obs.strip().split('\n\n'): | |
| add_body_text(doc, linha.strip(), cor=RGBColor(255, 0, 0), indent=True) | |
| # ANEXOS | |
| add_section_title(doc, num.secao(), "ANEXOS") | |
| for anexo in ["I. BANCO DE DADOS", "II. PLANILHA DE CÁLCULO", "III. GRÁFICOS", | |
| "IV. CÁLCULO DO VALOR", "V. DADOS DO IMÓVEL", "VI. REGISTRO FOTOGRÁFICO"]: | |
| add_body_text(doc, anexo, indent=True) | |
| def _gerar_tabela_valores_mercado(doc, valores_lista): | |
| """ | |
| Gera tabela de valores de mercado no estilo: | |
| | Exercício IPTU | Ano Base | VTOTAL Imóvel | | |
| Args: | |
| doc: Documento python-docx | |
| valores_lista: Lista de dicts com 'ano' e 'valor' | |
| """ | |
| # Cabeçalho | |
| dados_tabela = [ | |
| ["Exercício IPTU", "Ano Base", "VTOTAL Imóvel"] | |
| ] | |
| # Dados | |
| for item in valores_lista: | |
| ano = item.get('ano', '') | |
| valor = item.get('valor', '') | |
| valor_extenso = item.get('valor_extenso', '') | |
| if ano and valor: | |
| exercicio = str(ano) | |
| ano_base = str(int(ano) - 1) if ano else '' | |
| # Formatar valor com extenso | |
| if valor_extenso: | |
| vtotal = f"R$ {valor} ({valor_extenso})" | |
| else: | |
| vtotal = f"R$ {valor}" | |
| dados_tabela.append([exercicio, ano_base, vtotal]) | |
| if len(dados_tabela) > 1: # Tem dados além do cabeçalho | |
| add_simple_table(doc, dados_tabela, header_row=True, largura_colunas=[1,1,8]) | |
| def gerar_assinatura(doc, dados): | |
| """Gera a seção de assinatura do laudo.""" | |
| doc.add_paragraph() | |
| criar_paragrafo_formatado( | |
| doc, f"Porto Alegre, {dados.get('data_laudo', datetime.now().strftime('%d de %B de %Y'))}.", | |
| tamanho=11, alinhamento=WD_ALIGN_PARAGRAPH.RIGHT | |
| ) | |
| doc.add_paragraph() | |
| doc.add_paragraph() | |
| p = doc.add_paragraph() | |
| p.paragraph_format.first_line_indent = Inches(0) | |
| p.paragraph_format.left_indent = Inches(0) | |
| p.add_run("_" * 40) | |
| p.alignment = WD_ALIGN_PARAGRAPH.CENTER | |
| if dados.get('tecnico_responsavel'): | |
| criar_paragrafo_formatado( | |
| doc, dados.get('tecnico_responsavel'), tamanho=11, | |
| alinhamento=WD_ALIGN_PARAGRAPH.CENTER | |
| ) | |
| p = doc.add_paragraph() | |
| p.paragraph_format.first_line_indent = Inches(0) | |
| p.paragraph_format.left_indent = Inches(0) | |
| run = p.add_run("Equipe de Avaliações\nDAI-RM-SMF") | |
| run.bold = True | |
| run.font.size = Pt(11) | |
| run.font.name = 'Arial' | |
| p.alignment = WD_ALIGN_PARAGRAPH.CENTER | |
| def gerar_paginas_anexos(doc, nome_modelo=None): | |
| """Gera as páginas de anexos do laudo.""" | |
| anexos_inseridos = False | |
| if nome_modelo and nome_modelo != "Outros": | |
| elementos_anexos = carregar_conteudo_anexos(nome_modelo) | |
| if elementos_anexos: | |
| anexos_inseridos = inserir_conteudo_anexos(doc, elementos_anexos) | |
| if anexos_inseridos: | |
| print(f"Anexos do modelo '{nome_modelo}' inseridos.") | |
| if not anexos_inseridos: | |
| titulos = [ | |
| "ANEXO I – BANCO DE DADOS", | |
| "ANEXO II – PLANILHA DE CÁLCULO E RESULTADOS ESTATÍSTICOS", | |
| "ANEXO III – GRÁFICOS", | |
| "ANEXO IV – CÁLCULO DO VALOR", | |
| "ANEXO V – DADOS E LOCALIZAÇÃO DO IMÓVEL", | |
| "ANEXO VI – REGISTRO FOTOGRÁFICO", | |
| ] | |
| for titulo in titulos: | |
| doc.add_page_break() | |
| add_heading_custom(doc, titulo, level=1) | |
| add_placeholder_text(doc) | |
| # ============================================================================ | |
| # GERAÇÃO DO LAUDO COMPLETO | |
| # ============================================================================ | |
| def gerar_laudo_completo(output_path: str, dados: dict, motivos_desvalorizantes: list, incluir_anexos: bool = True) -> bool: | |
| """Gera o documento de laudo completo.""" | |
| try: | |
| using_template = os.path.exists(HEADER_PATH) | |
| doc = Document(HEADER_PATH) if using_template else Document() | |
| if using_template: | |
| print(f"Utilizando template de cabeçalho: {HEADER_PATH}") | |
| else: | |
| print("Template de cabeçalho não encontrado. Usando documento em branco.") | |
| for section in doc.sections: | |
| section.top_margin = Cm(2.5) | |
| section.bottom_margin = Cm(2.5) | |
| section.left_margin = Cm(3) | |
| section.right_margin = Cm(2) | |
| # Substituir processo no header | |
| numero_processo = dados.get('numero_processo', '') | |
| if using_template and numero_processo: | |
| for section in doc.sections: | |
| if section.header: | |
| for paragraph in section.header.paragraphs: | |
| if "XXX" in paragraph.text: | |
| new_text = paragraph.text.replace("XXX", numero_processo) | |
| for run in paragraph.runs: | |
| run.clear() | |
| run = paragraph.add_run(new_text) | |
| run.bold = True | |
| run.font.name = 'Arial' | |
| run.font.size = Pt(10) | |
| # Verificar arquivos do modelo | |
| modelo_avaliacao = dados.get('modelo_avaliacao', '') | |
| if modelo_avaliacao and modelo_avaliacao != "Outros": | |
| if modelo_tem_arquivos(modelo_avaliacao): | |
| print(f"Modelo '{modelo_avaliacao}' possui arquivos de metodologia/anexos.") | |
| else: | |
| print(f"Modelo '{modelo_avaliacao}' não possui arquivos específicos.") | |
| motivos_formatados = formatar_motivos_desvalorizantes(motivos_desvalorizantes) | |
| dados['motivos_alegados'] = motivos_formatados['alegados_texto'] | |
| dados['motivos_existentes'] = motivos_formatados['confirmados_texto'] | |
| gerar_cabecalho(doc, dados) | |
| gerar_corpo_laudo(doc, dados, motivos_formatados, modelo_avaliacao) | |
| gerar_assinatura(doc, dados) | |
| if incluir_anexos: | |
| gerar_paginas_anexos(doc, modelo_avaliacao) | |
| doc.save(output_path) | |
| return True | |
| except Exception as e: | |
| print(f"Erro ao gerar laudo: {e}") | |
| import traceback | |
| traceback.print_exc() | |
| return False | |
| def substituir_placeholders_docx(template_path: str, output_path: str, replacements: dict) -> bool: | |
| """Substitui placeholders em um template DOCX.""" | |
| try: | |
| shutil.copy2(template_path, output_path) | |
| doc = Document(output_path) | |
| def replace_in_paragraph(paragraph, replacements): | |
| for placeholder, value in replacements.items(): | |
| if placeholder in paragraph.text: | |
| new_text = paragraph.text.replace(placeholder, str(value) if value else '') | |
| if paragraph.runs: | |
| first_run = paragraph.runs[0] | |
| font_name = first_run.font.name | |
| font_size = first_run.font.size | |
| bold = first_run.font.bold | |
| italic = first_run.font.italic | |
| for run in paragraph.runs: | |
| run.text = '' | |
| first_run.text = new_text | |
| if font_name: first_run.font.name = font_name | |
| if font_size: first_run.font.size = font_size | |
| if bold is not None: first_run.font.bold = bold | |
| if italic is not None: first_run.font.italic = italic | |
| for paragraph in doc.paragraphs: | |
| replace_in_paragraph(paragraph, replacements) | |
| for table in doc.tables: | |
| for row in table.rows: | |
| for cell in row.cells: | |
| for paragraph in cell.paragraphs: | |
| replace_in_paragraph(paragraph, replacements) | |
| for section in doc.sections: | |
| if section.header: | |
| for p in section.header.paragraphs: | |
| replace_in_paragraph(p, replacements) | |
| if section.footer: | |
| for p in section.footer.paragraphs: | |
| replace_in_paragraph(p, replacements) | |
| doc.save(output_path) | |
| return True | |
| except Exception as e: | |
| print(f"Erro ao substituir placeholders: {e}") | |
| return False | |
| # ============================================================================ | |
| # FUNÇÃO PRINCIPAL DE GERAÇÃO | |
| # ============================================================================ | |
| def _criar_lista_valores_mercado(anos, valores): | |
| """ | |
| Cria lista estruturada de valores de mercado para a tabela. | |
| Args: | |
| anos: Lista de anos | |
| valores: Lista de valores monetários | |
| Returns: | |
| Lista de dicts com 'ano', 'valor' e 'valor_extenso' | |
| """ | |
| lista = [] | |
| for ano, valor in zip(anos, valores): | |
| ano_str = str(ano).strip() if ano else '' | |
| valor_str = str(valor).strip() if valor else '' | |
| if ano_str and valor_str: | |
| try: | |
| valor_formatado, valor_extenso = formatar_valor_monetario(valor_str) | |
| lista.append({ | |
| 'ano': ano_str, | |
| 'valor': valor_formatado, | |
| 'valor_extenso': valor_extenso or '' | |
| }) | |
| except Exception as e: | |
| # Se falhar a formatação, usa o valor como está | |
| print(f"Aviso: erro ao formatar valor '{valor_str}': {e}") | |
| lista.append({ | |
| 'ano': ano_str, | |
| 'valor': valor_str, | |
| 'valor_extenso': '' | |
| }) | |
| return lista | |
| def gerar_documento( | |
| template_path: str, output_dir: str, numero_laudo: str, numero_processo: str, data_laudo: str, | |
| unidade_demandante: str, unidade_demandante_custom: str, | |
| dados_requerimento: str, dados_requerimento_custom: str, | |
| representante_legal: str, valor_proposto: str, ano_proposto: str, | |
| documentacao_padrao: list, documentacao_especifica: list, | |
| motivos_desvalorizantes: list, | |
| inscricao_imovel: str, endereco_imovel: str, bairro_imovel: str, | |
| setor_quarteirao: str, lote_imovel: str, finalidade_imovel: str, | |
| area_territorial: str, construcoes_imovel: str, valores_venais: str, | |
| unidade_responsavel: str, unidade_responsavel_custom: str, | |
| tecnico_responsavel: str, tecnico_responsavel_custom: str, | |
| metodo_avaliacao: str, metodo_avaliacao_custom: str, | |
| modelo_avaliacao: str, modelo_avaliacao_custom: str, | |
| valores_mercado_anos: list, valores_mercado_valores: list, | |
| datas_referencia: str, | |
| gerar_laudo_completo_flag: bool = True | |
| ) -> tuple: | |
| """Função principal que orquestra a geração do documento.""" | |
| if not gerar_laudo_completo_flag and not os.path.exists(template_path): | |
| return f"Erro: Template não encontrado em {template_path}", None | |
| def get_valor(dropdown, custom): | |
| return custom if dropdown == "Outros" and custom else dropdown | |
| valor_formatado, valor_extenso = formatar_valor_monetario(valor_proposto) | |
| valor_proposto_completo = f"{valor_formatado} ({valor_extenso})" if valor_extenso else valor_formatado | |
| setor_parts = setor_quarteirao.split(" / ") if " / " in setor_quarteirao else [setor_quarteirao, ""] | |
| setor, quarteirao = (setor_parts[0], setor_parts[1]) if len(setor_parts) > 1 else (setor_parts[0], "") | |
| bairro_completo = f"{bairro_imovel} ({setor} / {quarteirao})" | |
| ano_proposto_str = str(ano_proposto).strip() if ano_proposto else "" | |
| # Criar lista estruturada de valores de mercado para a tabela | |
| valores_mercado_lista = _criar_lista_valores_mercado(valores_mercado_anos, valores_mercado_valores) | |
| dados = { | |
| 'numero_laudo': numero_laudo, | |
| 'numero_processo': numero_processo, | |
| 'data_laudo': data_laudo, | |
| 'unidade_demandante': get_valor(unidade_demandante, unidade_demandante_custom), | |
| 'dados_requerimento': get_valor(dados_requerimento, dados_requerimento_custom), | |
| 'representante_legal': representante_legal, | |
| 'valor_proposto': f"{valor_proposto_completo} (referente ao IPTU {ano_proposto_str})" if valor_proposto_completo else valor_proposto_completo, | |
| 'documentacao_padrao': formatar_documentacao_para_documento(documentacao_padrao), | |
| 'documentacao_especifica': formatar_documentacao_para_documento(documentacao_especifica), | |
| 'endereco_imovel': endereco_imovel, | |
| 'bairro_imovel': bairro_completo, | |
| 'setor_imovel': setor, | |
| 'quarteirao_imovel': quarteirao, | |
| 'lote_imovel': lote_imovel, | |
| 'inscricao_imovel': inscricao_imovel, | |
| 'finalidade_imovel': finalidade_imovel, | |
| 'area_territorial': area_territorial, | |
| 'construcoes_imovel': construcoes_imovel, | |
| 'valores_venais': valores_venais, | |
| 'unidade_responsavel': get_valor(unidade_responsavel, unidade_responsavel_custom), | |
| 'tecnico_responsavel': get_valor(tecnico_responsavel, tecnico_responsavel_custom), | |
| 'metodo_avaliacao': get_valor(metodo_avaliacao, metodo_avaliacao_custom), | |
| 'modelo_avaliacao': get_valor(modelo_avaliacao, modelo_avaliacao_custom), | |
| 'valores_mercado': formatar_valores_mercado_para_documento(valores_mercado_anos, valores_mercado_valores), | |
| 'valores_mercado_lista': valores_mercado_lista, # Lista estruturada para tabela | |
| 'datas_referencia': datas_referencia, | |
| 'motivos_alegados': formatar_motivos_desvalorizantes(motivos_desvalorizantes)['alegados_texto'], | |
| 'motivos_existentes': formatar_motivos_desvalorizantes(motivos_desvalorizantes)['confirmados_texto'] | |
| } | |
| timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") | |
| inscricao_safe = re.sub(r'[^\w\-]', '_', inscricao_imovel) if inscricao_imovel else "sem_inscricao" | |
| numero_safe = re.sub(r'[^\w\-]', '_', numero_laudo) if numero_laudo else "sem_numero" | |
| if gerar_laudo_completo_flag: | |
| filename = f"Laudo_{numero_safe}_{inscricao_safe}_{timestamp}.docx" | |
| path = os.path.join(output_dir, filename) | |
| success = gerar_laudo_completo(path, dados, motivos_desvalorizantes, incluir_anexos=True) | |
| else: | |
| filename = f"Cabecalho_{numero_safe}_{inscricao_safe}_{timestamp}.docx" | |
| path = os.path.join(output_dir, filename) | |
| success = substituir_placeholders_docx(template_path, path, dados) | |
| if success: | |
| return f"✅ Documento gerado: {filename}\nLocal: {path}", path | |
| else: | |
| return "❌ Erro ao gerar documento", None | |
| # ============================================================================ | |
| # INTERFACE GRÁFICA - CALLBACKS | |
| # ============================================================================ | |
| def toggle_custom_field(dropdown_value): | |
| """Callback para mostrar/esconder campo customizado.""" | |
| return gr.update(visible=(dropdown_value == "Outros")) | |
| def adicionar_campo(count, max_count): | |
| """Adiciona um campo dinâmico.""" | |
| new = min(count + 1, max_count) | |
| return [new] + [gr.update(visible=(i < new)) for i in range(max_count)] | |
| def remover_campo(count, max_count): | |
| """Remove um campo dinâmico.""" | |
| new = max(count - 1, 1) | |
| return [new] + [gr.update(visible=(i < new)) for i in range(max_count)] | |
| def gerar_campos_valores_mercado(ano_ini, ano_fim): | |
| """Gera campos de valores de mercado para intervalo de anos.""" | |
| try: | |
| ano_ini, ano_fim = int(ano_ini), int(ano_fim) | |
| except: | |
| return [gr.update(visible=False, value=""), gr.update(value=""), gr.update(value="")] * MAX_VALORES_MERCADO + [gr.update(value="")] | |
| if ano_fim < ano_ini: | |
| ano_ini, ano_fim = ano_fim, ano_ini | |
| anos = list(range(ano_ini, ano_fim + 1)) | |
| updates = [] | |
| for i in range(MAX_VALORES_MERCADO): | |
| if i < min(len(anos), MAX_VALORES_MERCADO): | |
| updates.extend([gr.update(visible=True), gr.update(value=str(anos[i])), gr.update(value="")]) | |
| else: | |
| updates.extend([gr.update(visible=False), gr.update(value=""), gr.update(value="")]) | |
| referencias_texto = ", ".join([f"Dezembro/{ano-1}" for ano in anos]) | |
| updates.append(gr.update(value=referencias_texto)) | |
| return updates | |
| def limpar_campos_valores_mercado(): | |
| """Limpa todos os campos de valores de mercado.""" | |
| return [gr.update(visible=False, value=""), gr.update(value=""), gr.update(value="")] * MAX_VALORES_MERCADO + [gr.update(value="")] | |
| # ============================================================================ | |
| # INTERFACE GRÁFICA - WRAPPER DE GERAÇÃO | |
| # ============================================================================ | |
| def gerar_documento_wrapper(*args): | |
| """Wrapper que extrai argumentos da interface e chama gerar_documento.""" | |
| idx = 0 | |
| numero_laudo = args[idx]; idx += 1 | |
| numero_processo = args[idx]; idx += 1 | |
| data_laudo = args[idx]; idx += 1 | |
| unidade_demandante = args[idx]; idx += 1 | |
| unidade_demandante_custom = args[idx]; idx += 1 | |
| dados_requerimento = args[idx]; idx += 1 | |
| dados_requerimento_custom = args[idx]; idx += 1 | |
| representante_legal = args[idx]; idx += 1 | |
| valor_proposto = args[idx]; idx += 1 | |
| ano_proposto = args[idx]; idx += 1 | |
| documentacao_padrao = [args[idx+i] for i in range(MAX_DOCUMENTACAO_PADRAO)] | |
| idx += MAX_DOCUMENTACAO_PADRAO | |
| documentacao_especifica = [args[idx+i] for i in range(MAX_DOCUMENTACAO_ESPECIFICA)] | |
| idx += MAX_DOCUMENTACAO_ESPECIFICA | |
| motivos_desvalorizantes = [] | |
| for _ in range(MAX_MOTIVOS_DESVALORIZANTES): | |
| desc = args[idx]; idx += 1 | |
| aleg = args[idx]; idx += 1 | |
| conf = args[idx]; idx += 1 | |
| if desc and desc.strip(): | |
| motivos_desvalorizantes.append({'descricao': desc.strip(), 'alegado': aleg, 'confirmado': conf}) | |
| inscricao_imovel = args[idx]; idx += 1 | |
| endereco_imovel = args[idx]; idx += 1 | |
| bairro_imovel = args[idx]; idx += 1 | |
| setor_quarteirao = args[idx]; idx += 1 | |
| lote_imovel = args[idx]; idx += 1 | |
| finalidade_imovel = args[idx]; idx += 1 | |
| area_territorial = args[idx]; idx += 1 | |
| construcoes_imovel = args[idx]; idx += 1 | |
| valores_venais = args[idx]; idx += 1 | |
| unidade_responsavel = args[idx]; idx += 1 | |
| unidade_responsavel_custom = args[idx]; idx += 1 | |
| tecnico_responsavel = args[idx]; idx += 1 | |
| tecnico_responsavel_custom = args[idx]; idx += 1 | |
| metodo_avaliacao = args[idx]; idx += 1 | |
| metodo_avaliacao_custom = args[idx]; idx += 1 | |
| modelo_avaliacao = args[idx]; idx += 1 | |
| modelo_avaliacao_custom = args[idx]; idx += 1 | |
| valores_mercado_anos = [] | |
| valores_mercado_valores = [] | |
| for _ in range(MAX_VALORES_MERCADO): | |
| valores_mercado_anos.append(args[idx]); idx += 1 | |
| valores_mercado_valores.append(args[idx]); idx += 1 | |
| datas_referencia = args[idx]; idx += 1 | |
| return gerar_documento( | |
| TEMPLATE_PATH, OUTPUT_DIR, numero_laudo, numero_processo, data_laudo, | |
| unidade_demandante, unidade_demandante_custom, | |
| dados_requerimento, dados_requerimento_custom, | |
| representante_legal, valor_proposto, ano_proposto, | |
| documentacao_padrao, documentacao_especifica, | |
| motivos_desvalorizantes, | |
| inscricao_imovel, endereco_imovel, bairro_imovel, | |
| setor_quarteirao, lote_imovel, finalidade_imovel, | |
| area_territorial, construcoes_imovel, valores_venais, | |
| unidade_responsavel, unidade_responsavel_custom, | |
| tecnico_responsavel, tecnico_responsavel_custom, | |
| metodo_avaliacao, metodo_avaliacao_custom, | |
| modelo_avaliacao, modelo_avaliacao_custom, | |
| valores_mercado_anos, valores_mercado_valores, | |
| datas_referencia | |
| ) | |
| # ============================================================================ | |
| # INTERFACE GRÁFICA - CRIAÇÃO | |
| # ============================================================================ | |
| def _get_data_atual_formatada(): | |
| """Retorna a data atual formatada em português.""" | |
| meses = { | |
| "January": "janeiro", "February": "fevereiro", "March": "março", | |
| "April": "abril", "May": "maio", "June": "junho", | |
| "July": "julho", "August": "agosto", "September": "setembro", | |
| "October": "outubro", "November": "novembro", "December": "dezembro" | |
| } | |
| data = datetime.now().strftime("%d de %B de %Y") | |
| for en, pt in meses.items(): | |
| data = data.replace(en, pt) | |
| return data | |
| def criar_interface(): | |
| """Cria a interface Gradio completa.""" | |
| with gr.Blocks(title="Sistema de Avaliação de Imóvel", theme=gr.themes.Soft(primary_hue="blue")) as app: | |
| gr.Markdown("# 📋 Sistema de Geração de Documentos") | |
| with gr.Blocks(): | |
| gr.Markdown("### Dados gerais:") | |
| with gr.Row(): | |
| numero_laudo = gr.Textbox(label="Número do Laudo", placeholder="LA_XXX_2025") | |
| numero_processo = gr.Textbox(label="Processo Administrativo", placeholder="000.000000.00.0") | |
| data_laudo = gr.Textbox(label="Data do Laudo", value=_get_data_atual_formatada()) | |
| with gr.Row(): | |
| u_demand = gr.Dropdown(UNIDADES_DEMANDANTES, label="Unidade Demandante", value=UNIDADES_DEMANDANTES[0]) | |
| u_demand_custom = gr.Textbox(label="Especifique", visible=False) | |
| u_demand.change(toggle_custom_field, u_demand, u_demand_custom) | |
| with gr.Row(): | |
| req_tipo = gr.Dropdown(TIPOS_REQUERIMENTO, label="Requerimento", value=TIPOS_REQUERIMENTO[0]) | |
| req_custom = gr.Textbox(label="Especifique", visible=False) | |
| req_tipo.change(toggle_custom_field, req_tipo, req_custom) | |
| rep_legal = gr.Textbox(label="Requerente/Representante Legal") | |
| with gr.Blocks(): | |
| gr.Markdown("### Valor Proposto pelo contribuinte (preencher somente se houver)") | |
| with gr.Row(): | |
| v_proposto = gr.Textbox(label="Valor Proposto (R$)", placeholder="Digite e saia do campo") | |
| a_proposto = gr.Textbox(label="Ano Ref.", value="2025") | |
| v_proposto.blur(aplicar_mascara_monetaria, v_proposto, v_proposto) | |
| # Docs Padrão | |
| with gr.Blocks(): | |
| gr.Markdown("### Documentação Padrão") | |
| docs_padrao_inputs = [] | |
| docs_padrao_rows = [] | |
| for i in range(MAX_DOCUMENTACAO_PADRAO): | |
| with gr.Row(visible=(i==0)) as row: | |
| docs_padrao_inputs.append(gr.Textbox(label=f"Documento {i+1}")) | |
| docs_padrao_rows.append(row) | |
| docs_padrao_count = gr.State(1) | |
| with gr.Row(): | |
| gr.Button("➕ Adicionar").click(lambda c: adicionar_campo(c, MAX_DOCUMENTACAO_PADRAO), docs_padrao_count, [docs_padrao_count] + docs_padrao_rows) | |
| gr.Button("➖ Remover").click(lambda c: remover_campo(c, MAX_DOCUMENTACAO_PADRAO), docs_padrao_count, [docs_padrao_count] + docs_padrao_rows) | |
| with gr.Blocks(): | |
| # Docs Específica | |
| gr.Markdown("### Documentação Específica") | |
| docs_esp_inputs = [] | |
| docs_esp_rows = [] | |
| for i in range(MAX_DOCUMENTACAO_ESPECIFICA): | |
| with gr.Row(visible=(i==0)) as row: | |
| docs_esp_inputs.append(gr.Textbox(label=f"Doc Específico {i+1}")) | |
| docs_esp_rows.append(row) | |
| docs_esp_count = gr.State(1) | |
| with gr.Row(): | |
| gr.Button("➕ Adicionar").click(lambda c: adicionar_campo(c, MAX_DOCUMENTACAO_ESPECIFICA), docs_esp_count, [docs_esp_count] + docs_esp_rows) | |
| gr.Button("➖ Remover").click(lambda c: remover_campo(c, MAX_DOCUMENTACAO_ESPECIFICA), docs_esp_count, [docs_esp_count] + docs_esp_rows) | |
| with gr.Blocks(): | |
| gr.Markdown("### Motivos desvalorizantes") | |
| motivos_rows = [] | |
| motivos_desc = [] | |
| motivos_aleg = [] | |
| motivos_conf = [] | |
| for i in range(MAX_MOTIVOS_DESVALORIZANTES): | |
| with gr.Row(visible=(i==0)) as row: | |
| d = gr.Textbox(show_label=False, scale=3, placeholder=f"Motivo Desvalorizante {i+1}") | |
| a = gr.Checkbox(label="Alegado pelo contribuinte") | |
| c = gr.Checkbox(label="Confirmado pelo avaliador") | |
| motivos_desc.append(d); motivos_aleg.append(a); motivos_conf.append(c) | |
| motivos_rows.append(row) | |
| motivos_count = gr.State(1) | |
| with gr.Row(): | |
| gr.Button("➕ Adicionar").click(lambda c: adicionar_campo(c, MAX_MOTIVOS_DESVALORIZANTES), motivos_count, [motivos_count] + motivos_rows) | |
| gr.Button("➖ Remover").click(lambda c: remover_campo(c, MAX_MOTIVOS_DESVALORIZANTES), motivos_count, [motivos_count] + motivos_rows) | |
| # --- SEÇÃO 3: IMÓVEL --- | |
| with gr.Blocks(): | |
| gr.Markdown("### Imóvel Objeto") | |
| with gr.Row(): | |
| pdf_up = gr.File(label="PDF SIAT", type="filepath") | |
| with gr.Row(): | |
| btn_pdf = gr.Button("🔍 Processar PDF") | |
| inscricao = gr.Textbox(label="Inscrição", placeholder="Aguardando processamento do PDF ou preenchimento manual") | |
| with gr.Row(): | |
| endereco = gr.Textbox(label="Endereço", placeholder="Aguardando processamento do PDF ou preenchimento manual") | |
| bairro = gr.Textbox(label="Bairro", placeholder="Aguardando processamento do PDF ou preenchimento manual") | |
| with gr.Row(): | |
| setor_q = gr.Textbox(label="Setor / Quarteirão", placeholder="Aguardando processamento do PDF ou preenchimento manual") | |
| lote = gr.Textbox(label="Lote Fiscal", placeholder="Aguardando processamento do PDF ou preenchimento manual") | |
| with gr.Row(): | |
| finalidade = gr.Textbox(label="Finalidade", placeholder="Aguardando processamento do PDF ou preenchimento manual") | |
| area_ter = gr.Textbox(label="Área Territorial", placeholder="Aguardando processamento do PDF ou preenchimento manual") | |
| construcoes = gr.Textbox(label="Construções", lines=2, placeholder="Aguardando processamento do PDF ou preenchimento manual") | |
| val_venais = gr.Textbox(label="Valores Venais", lines=2, placeholder="Aguardando processamento do PDF ou preenchimento manual") | |
| vm_extraidos = gr.Textbox(visible=False) | |
| btn_pdf.click(processar_upload_pdf, pdf_up, [inscricao, endereco, bairro, setor_q, lote, finalidade, area_ter, construcoes, val_venais, vm_extraidos]) | |
| # --- SEÇÃO 4: ANÁLISE --- | |
| with gr.Blocks(): | |
| gr.Markdown("### Dados do avaliador e do modelo") | |
| with gr.Row(): | |
| u_resp = gr.Dropdown(UNIDADES_RESPONSAVEIS, label="Unidade Resp.", value=UNIDADES_RESPONSAVEIS[0]) | |
| u_resp_custom = gr.Textbox(label="Esp.", visible=False) | |
| u_resp.change(toggle_custom_field, u_resp, u_resp_custom) | |
| with gr.Row(): | |
| tec_resp = gr.Dropdown(TECNICOS_RESPONSAVEIS, label="Técnico", value=TECNICOS_RESPONSAVEIS[0]) | |
| tec_resp_custom = gr.Textbox(label="Esp.", visible=False) | |
| tec_resp.change(toggle_custom_field, tec_resp, tec_resp_custom) | |
| with gr.Row(): | |
| metodo = gr.Dropdown(METODOS_AVALIACAO, label="Método", value=METODOS_AVALIACAO[0]) | |
| metodo_custom = gr.Textbox(label="Esp.", visible=False) | |
| metodo.change(toggle_custom_field, metodo, metodo_custom) | |
| with gr.Row(): | |
| modelo = gr.Dropdown(MODELOS_AVALIACAO, label="Modelo", value=MODELOS_AVALIACAO[0]) | |
| modelo_custom = gr.Textbox(label="Esp.", visible=False) | |
| modelo_status = gr.Markdown(value=get_status_modelo(MODELOS_AVALIACAO[0])) | |
| def atualizar_status_modelo(modelo_selecionado): | |
| custom_visible = modelo_selecionado == "Outros" | |
| status = get_status_modelo(modelo_selecionado) | |
| return gr.update(visible=custom_visible), status | |
| modelo.change(atualizar_status_modelo, modelo, [modelo_custom, modelo_status]) | |
| # --- SEÇÃO 5: CONCLUSÃO --- | |
| with gr.Blocks(): | |
| gr.Markdown("### Valores de Mercado (selecione de que ano até que ano haverá análise)") | |
| with gr.Row(): | |
| ano_ini = gr.Number(value=2020, label="Ano Inicial") | |
| ano_fim = gr.Number(value=2025, label="Ano Final") | |
| with gr.Column(): | |
| btn_gerar_anos = gr.Button("📅 Gerar Campos") | |
| btn_limpar_anos = gr.Button("🗑️ Limpar") | |
| vm_rows = [] | |
| vm_anos = [] | |
| vm_vals = [] | |
| vm_outputs_flat = [] | |
| for i in range(MAX_VALORES_MERCADO): | |
| with gr.Row(visible=False) as row: | |
| a = gr.Textbox(label="Ano", interactive=False, scale=1) | |
| v = gr.Textbox(label="Valor", scale=2) | |
| v.blur(aplicar_mascara_monetaria, v, v) | |
| vm_anos.append(a); vm_vals.append(v) | |
| vm_rows.append(row) | |
| vm_outputs_flat.extend([row, a, v]) | |
| ref_datas = gr.Textbox(label="Referências (datas-base)") | |
| btn_gerar_anos.click(gerar_campos_valores_mercado, [ano_ini, ano_fim], vm_outputs_flat + [ref_datas]) | |
| btn_limpar_anos.click(limpar_campos_valores_mercado, [], vm_outputs_flat + [ref_datas]) | |
| # --- GERAÇÃO --- | |
| gr.Markdown("---") | |
| btn_gerar = gr.Button("📄 Gerar Documento", variant="primary", size="lg") | |
| res_md = gr.Markdown() | |
| res_file = gr.File(label="Download") | |
| # Coleta de Inputs | |
| all_inputs = [numero_laudo, numero_processo, data_laudo, u_demand, u_demand_custom, req_tipo, req_custom, rep_legal, v_proposto, a_proposto] | |
| all_inputs.extend(docs_padrao_inputs) | |
| all_inputs.extend(docs_esp_inputs) | |
| for d, a, c in zip(motivos_desc, motivos_aleg, motivos_conf): | |
| all_inputs.extend([d, a, c]) | |
| all_inputs.extend([inscricao, endereco, bairro, setor_q, lote, finalidade, area_ter, construcoes, val_venais]) | |
| all_inputs.extend([u_resp, u_resp_custom, tec_resp, tec_resp_custom, metodo, metodo_custom, modelo, modelo_custom]) | |
| for a, v in zip(vm_anos, vm_vals): | |
| all_inputs.extend([a, v]) | |
| all_inputs.append(ref_datas) | |
| btn_gerar.click(gerar_documento_wrapper, all_inputs, [res_md, res_file]) | |
| return app | |
| if __name__ == "__main__": | |
| app = criar_interface() | |
| app.launch(server_name="0.0.0.0", server_port=7860, share=False) |