PensionBot / evidence_pack_export.py
ChAbhishek28's picture
New change
f547301
import csv
from fpdf import FPDF
import tempfile
import os
def _safe_text_for_pdf(s: object) -> str:
"""
Ensure text passed to FPDF only contains characters encodable in latin-1.
FPDF (classic) writes using latin-1 encoding by default and will raise
an error when encountering characters outside that range (e.g. •).
This helper replaces unsupported characters with a best-effort replacement
so PDF generation doesn't fail.
"""
if s is None:
return ""
try:
text = str(s)
except Exception:
text = ""
# encode -> decode using latin-1 with replacement to avoid exceptions
return text.encode('latin-1', 'replace').decode('latin-1')
def export_evidence_pack_pdf(data, filename=None):
"""
Export evidence pack as PDF. Data should include clause, summary, checklist, scenario, metadata.
Returns path to PDF file.
"""
pdf = FPDF()
pdf.add_page()
pdf.set_font("Arial", "B", size=16)
pdf.cell(200, 10, txt="Evidence Pack", ln=True, align='C')
pdf.ln(10)
# Clause section
pdf.set_font("Arial", "B", size=12)
pdf.cell(0, 8, txt="Clause:", ln=True)
pdf.set_font("Arial", size=10)
clause_text = data.get('clause_text', 'No clause information available')
pdf.multi_cell(0, 6, _safe_text_for_pdf(clause_text))
pdf.ln(3)
# Summary section
pdf.set_font("Arial", "B", size=12)
pdf.cell(0, 8, txt="Summary:", ln=True)
pdf.set_font("Arial", size=10)
summary_text = data.get('summary', 'No summary available')
pdf.multi_cell(0, 6, _safe_text_for_pdf(summary_text))
pdf.ln(3)
# Checklist section
pdf.set_font("Arial", "B", size=12)
pdf.cell(0, 8, txt="Checklist:", ln=True)
pdf.set_font("Arial", size=10)
checklist = data.get('role_checklist', [])
if checklist:
for item in checklist:
# use a simple hyphen bullet and sanitize text
pdf.multi_cell(0, 6, _safe_text_for_pdf(f"- {item}"))
else:
pdf.multi_cell(0, 6, "No checklist items available")
pdf.ln(3)
# Source information section
pdf.set_font("Arial", "B", size=12)
pdf.cell(0, 8, txt="Source Information:", ln=True)
pdf.set_font("Arial", size=10)
pdf.multi_cell(0, 6, _safe_text_for_pdf(f"Source: {data.get('source_title', 'Not specified')}"))
pdf.multi_cell(0, 6, _safe_text_for_pdf(f"Clause ID: {data.get('clause_id', 'Not assigned')}"))
pdf.multi_cell(0, 6, _safe_text_for_pdf(f"Date: {data.get('date', 'Not specified')}"))
pdf.multi_cell(0, 6, _safe_text_for_pdf(f"URL: {data.get('url', 'Not available')}"))
# User role and context information
if data.get('user_role'):
pdf.multi_cell(0, 6, _safe_text_for_pdf(f"User Role: {data.get('user_role', '').title()}"))
if data.get('language_preference'):
pdf.multi_cell(0, 6, _safe_text_for_pdf(f"Language: {data.get('language_preference', '').title()}"))
pdf.ln(5)
scenario = data.get('scenario_analysis',{})
if scenario:
pdf.multi_cell(0, 8, _safe_text_for_pdf(f"Scenario Analysis:"))
pdf.multi_cell(0, 8, _safe_text_for_pdf(f"Yearly Results: {scenario.get('yearly_results','')}"))
pdf.multi_cell(0, 8, _safe_text_for_pdf(f"Cumulative Base: {scenario.get('cumulative_base','')}"))
pdf.multi_cell(0, 8, _safe_text_for_pdf(f"Cumulative Scenario: {scenario.get('cumulative_scenario','')}"))
pdf.multi_cell(0, 8, _safe_text_for_pdf(f"Optimistic: {scenario.get('optimistic','')}"))
pdf.multi_cell(0, 8, _safe_text_for_pdf(f"Pessimistic: {scenario.get('pessimistic','')}"))
pdf.multi_cell(0, 8, _safe_text_for_pdf(f"Driver Breakdown: {scenario.get('driver_breakdown','')}"))
if not filename:
filename = os.path.join(tempfile.gettempdir(), f"evidence_pack_{os.getpid()}.pdf")
pdf.output(filename)
return filename
def export_evidence_pack_csv(data, filename=None):
"""
Export evidence pack as CSV. Data should include clause, summary, checklist, scenario, metadata.
Returns path to CSV file.
"""
if not filename:
filename = os.path.join(tempfile.gettempdir(), f"evidence_pack_{os.getpid()}.csv")
with open(filename, 'w', newline='', encoding='utf-8') as csvfile:
writer = csv.writer(csvfile)
writer.writerow(["Field", "Value"])
writer.writerow(["Clause", data.get('clause_text', 'No clause information available')])
writer.writerow(["Summary", data.get('summary', 'No summary available')])
# Handle checklist properly
checklist = data.get('role_checklist', [])
if checklist:
checklist_text = '; '.join(checklist)
else:
checklist_text = "No checklist items available"
writer.writerow(["Checklist", checklist_text])
writer.writerow(["Source", data.get('source_title', 'Not specified')])
writer.writerow(["Clause ID", data.get('clause_id', 'Not assigned')])
writer.writerow(["Date", data.get('date', 'Not specified')])
writer.writerow(["URL", data.get('url', 'Not available')])
# Add timestamp if available
if 'timestamp' in data:
writer.writerow(["Generated At", data.get('timestamp', '')])
# Add original query if available
if 'original_query' in data:
writer.writerow(["Original Query", data.get('original_query', '')])
# Add user context information
if 'user_role' in data:
writer.writerow(["User Role", data.get('user_role', '').title()])
if 'language_preference' in data:
writer.writerow(["Language Preference", data.get('language_preference', '').title()])
scenario = data.get('scenario_analysis', {})
if scenario:
writer.writerow(["=== SCENARIO ANALYSIS ===", ""])
writer.writerow(["Yearly Results", scenario.get('yearly_results', '')])
writer.writerow(["Cumulative Base", scenario.get('cumulative_base', '')])
writer.writerow(["Cumulative Scenario", scenario.get('cumulative_scenario', '')])
writer.writerow(["Optimistic", scenario.get('optimistic', '')])
writer.writerow(["Pessimistic", scenario.get('pessimistic', '')])
writer.writerow(["Driver Breakdown", scenario.get('driver_breakdown', '')])
return filename