import streamlit as st import os from datetime import date import io from reportlab.platypus import ( BaseDocTemplate, PageTemplate, Frame, Paragraph, Spacer, Table, TableStyle, PageBreak ) from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle from reportlab.lib.pagesizes import A4 from reportlab.lib import colors from reportlab.lib.units import cm from reportlab.lib.enums import TA_CENTER, TA_JUSTIFY, TA_LEFT, TA_RIGHT from docx import Document from docx.shared import Inches, Pt, Cm from docx.enum.text import WD_ALIGN_PARAGRAPH from io import BytesIO import os # ================================================================================= # FUNÇÃO DE GERAÇÃO DE PDF (MODIFICADA PARA ACEITAR DADOS E RETORNAR BYTES) # ================================================================================= def create_pdf_report(user_data): """ Gera o relatório em PDF em memória, preenchido com os dados do usuário. """ buffer = io.BytesIO() doc = BaseDocTemplate(buffer, pagesize=A4) HEADER_HEIGHT = 3.5 * cm main_frame = Frame( doc.leftMargin, doc.bottomMargin, doc.width, doc.height - HEADER_HEIGHT, id='main_frame' ) def header(canvas, doc): canvas.saveState() page_width = doc.width + doc.leftMargin + doc.rightMargin # Logos (mantenha a lógica de verificar se existem) if os.path.exists('src/logo_pref.png'): canvas.drawImage('src/logo_pref.png', doc.leftMargin, 740, width=4*cm, preserveAspectRatio=True, mask='auto') if os.path.exists('src/logo_receita.png'): logo_width = 3.5 * cm x_centered = (page_width - logo_width) / 2 canvas.drawImage('src/logo_receita.png', x_centered, 740, width=logo_width, preserveAspectRatio=True, mask='auto') header_style = ParagraphStyle(name='HeaderStyle', fontSize=8, alignment=TA_RIGHT) p = Paragraph("DAI-ESJL
Divisão de Avaliação de Imóveis
Equipe de Suporte, Judiciais e Locações", header_style) p.wrapOn(canvas, doc.width, doc.topMargin) p.drawOn(canvas, doc.leftMargin, 757.5) # PROCESSO com número do usuário processo_style = ParagraphStyle(name='ProcessoStyle', fontSize=10, alignment=TA_RIGHT, textColor=colors.black) p_proc = Paragraph(f"PROCESSO {user_data['processo_numero']}", processo_style) p_proc.wrapOn(canvas, doc.width, 1*cm) p_proc.drawOn(canvas, doc.leftMargin, 730) canvas.restoreState() def footer(canvas, doc): canvas.saveState() canvas.setFont('Helvetica', 9) canvas.drawRightString(A4[0] - doc.rightMargin, doc.bottomMargin - 0.5*cm, str(doc.page)) canvas.restoreState() doc.addPageTemplates([PageTemplate(id='main', frames=main_frame, onPage=header, onPageEnd=footer)]) styles = getSampleStyleSheet() styles.add(ParagraphStyle(name='Justify', alignment=TA_JUSTIFY, fontSize=10, leading=14)) styles.add(ParagraphStyle(name='Center', alignment=TA_CENTER, fontSize=10, leading=14)) styles.add(ParagraphStyle(name='MainTitle', fontSize=12, fontName='Helvetica-Bold', alignment=TA_CENTER, textColor=colors.black, borderPadding=(5, 2, 5, 2))) styles.add(ParagraphStyle(name='SectionTitle', fontSize=10, fontName='Helvetica-Bold', spaceBefore=16, spaceAfter=6)) styles.add(ParagraphStyle(name='TableLabel', fontName='Helvetica-Bold', fontSize=9, alignment=TA_LEFT)) styles.add(ParagraphStyle(name='TableContent', fontSize=9, alignment=TA_LEFT, leading=11)) def create_styled_table(data, col_widths): styled_data = [] for row_idx, row in enumerate(data): styled_row = [] for col_idx, cell_content in enumerate(row): # O conteúdo da segunda coluna é tratado como 'TableContent' style = styles['TableLabel'] if col_idx == 0 else styles['TableContent'] styled_row.append(Paragraph(str(cell_content), style)) styled_data.append(styled_row) tbl = Table(styled_data, colWidths=col_widths) tbl.setStyle(TableStyle([ ('VALIGN', (0, 0), (-1, -1), 'TOP'), ('GRID', (0, 0), (-1, -1), 0.5, colors.grey), ('LEFTPADDING', (0, 0), (-1, -1), 6), ('RIGHTPADDING', (0, 0), (-1, -1), 6), ('TOPPADDING', (0, 0), (-1, -1), 4), ('BOTTOMPADDING', (0, 0), (-1, -1), 4), ])) return tbl story = [] # --- PÁGINA 1 --- story.append(Spacer(1, 0.5 * cm)) # Título com número do usuário story.append(Paragraph(f"INFORMAÇÃO TÉCNICA
IT_{user_data['it_numero']}", styles['MainTitle'])) story.append(Paragraph("• SOLICITAÇÃO", styles['SectionTitle'])) solicitacao_data = [ ['Unidade demandante:', user_data['unidade_demandante']], ['Finalidade da Avaliação:', user_data['finalidade_avaliacao']] ] story.append(create_styled_table(solicitacao_data, col_widths=[4.5*cm, '*'])) story.append(Paragraph("• IMÓVEL OBJETO", styles['SectionTitle'])) imovel_data = [ ['Endereço - SMF:', user_data['endereco_smf']], ['Bairro (Setor/Quarteirão) - SMF:', user_data['bairro_smf']], ['Lote Fiscal / Inscrição - SMF:', user_data['lote_fiscal']], ['Registro imóvel nº - SMF:', user_data['registro_imovel']], ['Finalidade Imóvel - SMF:', user_data['finalidade_imovel']], ['Área Territorial - SMF:', user_data['area_territorial']], ['Área construída – SMF:', user_data['area_construida']], ['Exercícios em análise:', user_data['exercicios_analise']], ['Valores Venais Guias IPTU (Exercícios):', user_data['valores_venais']] ] story.append(create_styled_table(imovel_data, col_widths=[4.5*cm, '*'])) story.append(PageBreak()) # --- PÁGINA 2 --- story.append(Paragraph("• ANÁLISE TÉCNICA PRELIMINAR", styles['SectionTitle'])) analise_data = [ ['Unidade responsável:', user_data['unidade_responsavel']], ['Técnico responsável:', user_data['tecnico_responsavel']], ['Método de Avaliação (ABNT NBR 14.653-2):', user_data['metodo_avaliacao']] ] story.append(create_styled_table(analise_data, col_widths=[4.5*cm, '*'])) story.append(Paragraph("• CONCLUSÃO TÉCNICA", styles['SectionTitle'])) conclusao_data = [ ['Características particularmente
desvalorizantes:', user_data['caracteristicas_desvalorizantes']], ['Conclusão:', user_data['conclusao_preliminar']] ] story.append(create_styled_table(conclusao_data, col_widths=[6*cm, '*'])) story.append(PageBreak()) # --- PÁGINAS SEGUINTES (CONTEÚDO TEXTUAL) --- story.append(Paragraph("1. CONSIDERAÇÕES INICIAIS", styles['SectionTitle'])) story.append(Paragraph(user_data['texto_consideracoes'], styles['Justify'])) story.append(Paragraph("2. ANÁLISE TÉCNICA PRELIMINAR", styles['SectionTitle'])) story.append(Paragraph(user_data['texto_analise'], styles['Justify'])) # ... Adicione os outros parágrafos da mesma forma ... story.append(Spacer(1, 3*cm)) # Data automática today = date.today() meses = ("janeiro", "fevereiro", "março", "abril", "maio", "junho", "julho", "agosto", "setembro", "outubro", "novembro", "dezembro") data_formatada = f"Porto Alegre, {today.day} de {meses[today.month - 1]} de {today.year}." story.append(Paragraph(data_formatada, styles['Center'])) doc.build(story) # Retorna os bytes do PDF gerado pdf_bytes = buffer.getvalue() buffer.close() return pdf_bytes def create_docx_report(user_data): doc = Document() # --- Adicionar o cabeçalho que se repetirá em cada página --- section = doc.sections[0] header = section.header table = header.add_table(rows=1, cols=3) table.autofit = False widths = [Inches(2), Inches(2), Inches(4)] # Ajustar larguras das colunas for i, width in enumerate(widths): table.columns[i].width = width # --- Logo 1: 'src/logo_pref.png' à esquerda --- if os.path.exists('src/logo_pref.png'): cell_logo_pref = table.cell(0, 0) paragraph = cell_logo_pref.paragraphs[0] run = paragraph.add_run() run.add_picture('src/logo_pref.png', width=Inches(2)) paragraph.alignment = WD_ALIGN_PARAGRAPH.LEFT # --- Logo 2: 'src/logo_receita.png' ao centro --- if os.path.exists('src/logo_receita.png'): cell_logo_receita = table.cell(0, 1) paragraph = cell_logo_receita.paragraphs[0] run = paragraph.add_run() run.add_picture('src/logo_receita.png', width=Inches(1.75)) paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER # --- Texto à direita (cabeçalho) --- cell_text = table.cell(0, 2) paragraph = cell_text.paragraphs[0] run = paragraph.add_run("DAI-ESJL\nDivisão de Avaliação de Imóveis\nEquipe de Suporte, Judiciais e Locações") font = run.font font.size = Pt(8) font.bold = True paragraph.alignment = WD_ALIGN_PARAGRAPH.RIGHT # --- Número do processo no cabeçalho --- cell_proc = table.cell(0, 2) # Número do processo na terceira coluna paragraph_proc = cell_proc.paragraphs[0] run_proc = paragraph_proc.add_run(f"PROCESSO {user_data.get('processo_numero', '')}") run_proc.font.size = Pt(10) run_proc.font.bold = True paragraph_proc.alignment = WD_ALIGN_PARAGRAPH.RIGHT # --- Adicionar um parágrafo vazio para dar espaço após os logos e o cabeçalho --- doc.add_paragraph() # Função auxiliar para criar tabelas simples def add_table_from_data(data, col_widths_cm=[4.5, 10]): table = doc.add_table(rows=0, cols=2) table.style = 'Table Grid' # Ajustar larguras das colunas for idx, width in enumerate(col_widths_cm): for cell in table.columns[idx].cells: cell.width = Cm(width) for label, content in data: row_cells = table.add_row().cells # Primeira coluna - bold p_label = row_cells[0].paragraphs[0] run_label = p_label.add_run(label) run_label.bold = True p_label.alignment = WD_ALIGN_PARAGRAPH.LEFT # Segunda coluna - normal p_content = row_cells[1].paragraphs[0] p_content.add_run(str(content)) p_content.alignment = WD_ALIGN_PARAGRAPH.LEFT doc.add_paragraph() # espaço depois da tabela return table # --- Continue com o resto do conteúdo do relatório --- # Título principal centralizado title = doc.add_paragraph(f"INFORMAÇÃO TÉCNICA\nIT_{user_data.get('it_numero','')}") title.alignment = WD_ALIGN_PARAGRAPH.CENTER title.runs[0].font.size = Pt(14) title.runs[0].font.bold = True # Página 1 - Solicitação doc.add_heading("• SOLICITAÇÃO", level=2) solicitacao_data = [ ('Unidade demandante:', user_data.get('unidade_demandante','')), ('Finalidade da Avaliação:', user_data.get('finalidade_avaliacao','')) ] add_table_from_data(solicitacao_data) # Continue com as outras páginas conforme sua necessidade... # Data final today = date.today() meses = ("janeiro", "fevereiro", "março", "abril", "maio", "junho", "julho", "agosto", "setembro", "outubro", "novembro", "dezembro") data_formatada = f"Porto Alegre, {today.day} de {meses[today.month - 1]} de {today.year}." p_date = doc.add_paragraph() p_date.alignment = WD_ALIGN_PARAGRAPH.CENTER p_date.add_run(data_formatada) # Salvar em memória doc_io = BytesIO() doc.save(doc_io) doc_io.seek(0) return doc_io # ================================================================================= # INTERFACE DO STREAMLIT # ================================================================================= st.set_page_config(layout="wide") st.title("Gerador de Relatório Técnico") # Coletando os dados do usuário em colunas para melhor organização col1, col2 = st.columns(2) with col1: st.header("Dados Gerais e de Capa") user_data = {} user_data['processo_numero'] = st.text_input("Número do Processo:", "SEI-...") user_data['it_numero'] = st.text_input("Número da Informação Técnica (ex: 001/2025):", "XXX/2025") st.subheader("Solicitação (Página 1)") user_data['unidade_demandante'] = st.text_input("Unidade demandante:", "Equipe da Planta Genérica de Valores - EPGV (DAI/RM/SMF)") user_data['finalidade_avaliacao'] = st.text_input("Finalidade da Avaliação:", "Análise Técnica Preliminar - Finalidade Conforme Demanda EPGV") st.subheader("Imóvel Objeto (Página 1)") user_data['endereco_smf'] = st.text_input("Endereço - SMF:") user_data['bairro_smf'] = st.text_input("Bairro (Setor/Quarteirão) - SMF:") user_data['lote_fiscal'] = st.text_input("Lote Fiscal / Inscrição - SMF:") user_data['registro_imovel'] = st.text_input("Registro imóvel nº - SMF:", "Matrícula xxxxx / yª Zona de Porto Alegre") user_data['finalidade_imovel'] = st.text_area("Finalidade Imóvel - SMF:", "Opção A: 10-Terreno\nOpção B: 20 - Residência Isolada", height=100) user_data['area_territorial'] = st.text_input("Área Territorial - SMF:", "0,00 m² (privativa) / 0,00 m² (total)") user_data['area_construida'] = st.text_area("Área construída – SMF:", "00,00m² / YYYY / 35-ALVENARIA (C) - CASAS ATE 2 PAVIM.\n00,00m² / YYYY / 35-ALVENARIA (C) - CASAS ATE 2 PAVIM.", height=100) user_data['exercicios_analise'] = st.text_area("Exercícios em análise:", "XXXX a YYYY (não há lançamentos anteriores a XXXX)", height=150) user_data['valores_venais'] = st.text_input("Valores Venais Guias IPTU (Exercícios):") with col2: st.header("Análise e Conclusão (Página 2)") user_data['unidade_responsavel'] = st.text_input("Unidade responsável:", "Equipe de Suporte, Judiciais e Locações - ESJL (DAI/RM/SMF)") user_data['tecnico_responsavel'] = st.text_input("Técnico responsável:") user_data['metodo_avaliacao'] = st.text_area("Método de Avaliação (ABNT NBR 14.653-2):", "Opção A: Método Comparativo de Dados de Mercado\nOpção B:\n- Método Comparativo de Dados de Mercado\n- Método de Quantificação de Custo", height=150) user_data['caracteristicas_desvalorizantes'] = st.text_input("Características particularmente desvalorizantes:") user_data['conclusao_preliminar'] = st.text_area("Conclusão:", "Opção A: Requer análise técnica pormenorizada\n\nOpção B: Não requer análise técnica pormenorizada.\n\nOpção C: Sugerida análise fiscal quanto a ocorrência de característica particularmente desvalorizante prevista na Legislação.", height=200) st.header("Corpo do Relatório (Textos Longos)") user_data['texto_consideracoes'] = st.text_area( "1. CONSIDERAÇÕES INICIAIS", "A presente Informação Técnica foi elaborada por esta Equipe de Suporte, Judiciais e Locações (ESJL)...", height=200 ) user_data['texto_analise'] = st.text_area( "2. ANÁLISE TÉCNICA PRELIMINAR", "Para garantir o embasamento técnico desta análise preliminar...", height=200 ) # Adicione mais st.text_area para cada parágrafo longo que você precisa editar st.markdown("---") if st.button("Gerar Relatório em PDF"): with st.spinner("Gerando PDF..."): # Chamar a função para gerar o PDF em memória pdf_bytes = create_pdf_report(user_data) # Armazenar os bytes do PDF no estado da sessão para o download st.session_state.pdf_bytes = pdf_bytes st.session_state.file_name = f"IT_{user_data['it_numero'].replace('/', '_')}.pdf" # Botão de download só aparece se o PDF foi gerado if 'pdf_bytes' in st.session_state and st.session_state.pdf_bytes: st.success("Seu relatório está pronto para download!") st.download_button( label="Baixar PDF", data=st.session_state.pdf_bytes, file_name=st.session_state.file_name, mime="application/pdf" ) if st.button("Gerar Relatório em DOCX"): with st.spinner("Gerando DOCX..."): docx_buffer = create_docx_report(user_data) st.session_state.docx_bytes = docx_buffer.read() st.session_state.docx_file_name = f"IT_{user_data['it_numero'].replace('/', '_')}.docx" if 'docx_bytes' in st.session_state and st.session_state.docx_bytes: st.success("Seu relatório Word está pronto para download!") st.download_button( label="Baixar DOCX", data=st.session_state.docx_bytes, file_name=st.session_state.docx_file_name, mime="application/vnd.openxmlformats-officedocument.wordprocessingml.document" )