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"
)