Spaces:
Sleeping
Sleeping
| 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("<b>DAI-ESJL</b><br/>Divisão de Avaliação de Imóveis<br/>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<br/>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<br/>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" | |
| ) | |