Overview
The application supports two export formats: Excel (.xlsx) and PDF. Both include filtered task data, statistics, and metadata about the processing.
Excel Export
Excel exports create multi-sheet workbooks with separate tabs for tasks and statistics.
Implementation
The Excel export is handled by the /download/excel route in app.py:178:
@app.route ( '/download/excel' )
def download_excel ():
df = get_dataframe_from_temp_file()
if df is None :
return redirect(url_for( 'index' ,
error = "Os resultados expiraram. Por favor, processe o arquivo novamente." ))
stats = session.get( 'last_stats' , {})
output = io.BytesIO()
with pd.ExcelWriter(output, engine = 'openpyxl' ) as writer:
df.to_excel(writer, index = False , sheet_name = 'Tarefas Encontradas' )
stats_df = pd.DataFrame([
[ 'Total de Registros' , stats.get( 'total' , 'N/A' )],
[ 'Tarefas Encontradas' , stats.get( 'filtrados' , 'N/A' )],
[ 'Taxa de Ocorrência (%)' , stats.get( 'percentual' , 'N/A' )],
[ 'Data de Geração' , datetime.now().strftime( ' %d /%m/%Y %H:%M' )]
], columns = [ 'Métrica' , 'Valor' ])
stats_df.to_excel(writer, index = False , sheet_name = 'Estatísticas' )
output.seek( 0 )
return Response(
output,
mimetype = "application/vnd.openxmlformats-officedocument.spreadsheet.sheet" ,
headers = { "Content-Disposition" : "attachment;filename=relatorio_filtrado.xlsx" }
)
Excel Structure
Sheet 1: Tarefas Encontradas Contains all filtered task records with 5 columns:
Data (Date)
Cliente (Client)
Endereco (Address)
OS Digital (Work Order)
Relato (Report)
Sheet 2: Estatísticas Statistics summary with 4 metrics:
Total de Registros
Tarefas Encontradas
Taxa de Ocorrência (%)
Data de Geração (timestamp)
Technical Details
Excel Engine and Libraries
Engine : openpyxl (specified in pd.ExcelWriter)
Format : XLSX (Office Open XML)
MIME type : application/vnd.openxmlformats-officedocument.spreadsheet.sheet
Memory handling : Uses io.BytesIO() for in-memory file generation
Filename : Fixed as relatorio_filtrado.xlsx
The Excel export reads filtered data from a temporary CSV file stored in the temp/ folder, not directly from the session. This prevents session size limits.
PDF Export
PDF exports create formatted, print-ready reports with embedded styling and clickable hyperlinks.
Implementation
The PDF export is handled by the /download/pdf route in app.py:206:
@app.route ( '/download/pdf' )
def download_pdf ():
df = get_dataframe_from_temp_file()
if df is None :
return redirect(url_for( 'index' ,
error = "Os resultados expiraram. Por favor, processe o arquivo novamente." ))
stats = session.get( 'last_stats' , {})
palavras_utilizadas = session.get( 'custom_keywords' , [])
df_render = df.copy()
if not df.empty:
df_render[ 'OS Digital' ] = df_render[ 'OS Digital' ].apply(criar_links)
tabela_html = df_render.to_html(
index = False , escape = False , classes = "tabela-pdf"
) if not df.empty else "<p>Nenhuma tarefa encontrada.</p>"
full_html = f """
<html>
<head>
<meta charset="utf-8">
<style>
body {{ font-family: Arial, sans-serif; margin: 20px; color: #333; }}
h1 {{ color: #0056b3; }}
.header {{ padding: 20px; margin-bottom: 20px; border-radius: 5px;
background-color: #f8f9fa; }}
.stats {{ display: flex; justify-content: space-around; margin: 20px 0;
background-color: #e9ecef; padding: 15px; border-radius: 5px; }}
.stat-box {{ text-align: center; }}
.stat-number {{ font-size: 24px; font-weight: bold; color: #007bff; }}
.palavras-chave {{ background-color: #fff3cd; padding: 15px;
margin: 20px 0; border-radius: 5px;
border-left: 4px solid #ffc107; }}
.tabela-pdf {{ border-collapse: collapse; width: 100%; font-size: 10px; }}
.tabela-pdf th, .tabela-pdf td {{ text-align: left; padding: 6px;
border: 1px solid #ddd; }}
.tabela-pdf th {{ background-color: #f2f2f2; }}
a {{ color: #007bff; text-decoration: none; }}
</style>
</head>
<body>
<div class="header">
<h1>Relatório de Tarefas com Ação Necessária</h1>
<p><strong>Gerado em:</strong> { datetime.now().strftime( ' %d /%m/%Y às %H:%M' ) } </p>
</div>
<div class="stats">
<div class="stat-box">
<div class="stat-number"> { stats.get( 'total' , 'N/A' ) } </div>
<div>Total de Registros</div>
</div>
<div class="stat-box">
<div class="stat-number"> { stats.get( 'filtrados' , 'N/A' ) } </div>
<div>Tarefas Encontradas</div>
</div>
<div class="stat-box">
<div class="stat-number"> { stats.get( 'percentual' , 'N/A' ) } %</div>
<div>Taxa de Ocorrência</div>
</div>
</div>
<div class="palavras-chave">
<h3>Palavras-chave utilizadas:</h3>
<p> { ', ' .join(palavras_utilizadas) } </p>
</div>
{ tabela_html }
</body>
</html>
"""
pdf = HTML( string = full_html).write_pdf()
return Response(
pdf,
mimetype = "application/pdf" ,
headers = { "Content-Disposition" : "attachment;filename=relatorio_filtrado.pdf" }
)
PDF Structure
Header Section
Title and generation timestamp with blue styling
Statistics Dashboard
Visual boxes displaying total, filtered, and percentage metrics
Keywords Section
Yellow-highlighted box showing all keywords used in filtering
Data Table
Formatted table with all filtered tasks and clickable OS Digital links
PDF Features
Clickable Links OS Digital URLs are converted to HTML links using criar_links() function
Custom Styling Inline CSS with Bootstrap-inspired colors and responsive layout
UTF-8 Support Proper encoding for Portuguese characters (ç, ã, õ, etc.)
Print-Ready Optimized font sizes (10px for table) and page margins
Link Creation
The criar_links() helper function (from app.py:26) converts URLs to clickable HTML:
def criar_links ( url ):
"""Transforma URLs em links HTML clicáveis"""
if isinstance (url, str ) and url.startswith( 'http' ):
return f '<a href=" { url } " target="_blank"> { url } </a>'
return url
Temporary File Management
Both export routes use the get_dataframe_from_temp_file() function to retrieve filtered data:
def get_dataframe_from_temp_file ():
"""Pega o nome do arquivo da sessão e o lê como um DataFrame."""
temp_filename = session.get( 'temp_filename' )
if not temp_filename:
return None
file_path = os.path.join( TEMP_FOLDER , temp_filename)
if os.path.exists(file_path):
return pd.read_csv(file_path)
return None
If the temporary file is deleted or the session expires, the export will fail with an error message: “Os resultados expiraram. Por favor, processe o arquivo novamente.”
Comparison Table
Feature Excel PDF Multiple sheets Yes (2 sheets) No Editable Yes No Clickable links No Yes Visual styling Basic Rich HTML/CSS File size Smaller Larger Keywords shown No Yes Generation library openpyxl weasyprint Best for Data analysis Sharing/printing
Both exports use Flask’s Response object with proper MIME types:
mimetype = "application/vnd.openxmlformats-officedocument.spreadsheet.sheet"
headers = { "Content-Disposition" : "attachment;filename=relatorio_filtrado.xlsx" }
mimetype = "application/pdf"
headers = { "Content-Disposition" : "attachment;filename=relatorio_filtrado.pdf" }
The Content-Disposition: attachment header forces the browser to download the file rather than display it inline.
Error Handling
Both routes handle missing data gracefully:
Check if temporary file exists
Redirect to index with error message if not found
Use .get() with default values for stats (prevents KeyError)
Handle empty DataFrames with conditional rendering