Spaces:
Sleeping
Sleeping
Commit
·
f547301
1
Parent(s):
fde5743
New change
Browse files- evidence_pack_export.py +33 -16
evidence_pack_export.py
CHANGED
|
@@ -3,6 +3,22 @@ from fpdf import FPDF
|
|
| 3 |
import tempfile
|
| 4 |
import os
|
| 5 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
def export_evidence_pack_pdf(data, filename=None):
|
| 7 |
"""
|
| 8 |
Export evidence pack as PDF. Data should include clause, summary, checklist, scenario, metadata.
|
|
@@ -19,7 +35,7 @@ def export_evidence_pack_pdf(data, filename=None):
|
|
| 19 |
pdf.cell(0, 8, txt="Clause:", ln=True)
|
| 20 |
pdf.set_font("Arial", size=10)
|
| 21 |
clause_text = data.get('clause_text', 'No clause information available')
|
| 22 |
-
pdf.multi_cell(0, 6, clause_text)
|
| 23 |
pdf.ln(3)
|
| 24 |
|
| 25 |
# Summary section
|
|
@@ -27,7 +43,7 @@ def export_evidence_pack_pdf(data, filename=None):
|
|
| 27 |
pdf.cell(0, 8, txt="Summary:", ln=True)
|
| 28 |
pdf.set_font("Arial", size=10)
|
| 29 |
summary_text = data.get('summary', 'No summary available')
|
| 30 |
-
pdf.multi_cell(0, 6, summary_text)
|
| 31 |
pdf.ln(3)
|
| 32 |
|
| 33 |
# Checklist section
|
|
@@ -37,7 +53,8 @@ def export_evidence_pack_pdf(data, filename=None):
|
|
| 37 |
checklist = data.get('role_checklist', [])
|
| 38 |
if checklist:
|
| 39 |
for item in checklist:
|
| 40 |
-
|
|
|
|
| 41 |
else:
|
| 42 |
pdf.multi_cell(0, 6, "No checklist items available")
|
| 43 |
pdf.ln(3)
|
|
@@ -46,27 +63,27 @@ def export_evidence_pack_pdf(data, filename=None):
|
|
| 46 |
pdf.set_font("Arial", "B", size=12)
|
| 47 |
pdf.cell(0, 8, txt="Source Information:", ln=True)
|
| 48 |
pdf.set_font("Arial", size=10)
|
| 49 |
-
pdf.multi_cell(0, 6, f"Source: {data.get('source_title', 'Not specified')}")
|
| 50 |
-
pdf.multi_cell(0, 6, f"Clause ID: {data.get('clause_id', 'Not assigned')}")
|
| 51 |
-
pdf.multi_cell(0, 6, f"Date: {data.get('date', 'Not specified')}")
|
| 52 |
-
pdf.multi_cell(0, 6, f"URL: {data.get('url', 'Not available')}")
|
| 53 |
|
| 54 |
# User role and context information
|
| 55 |
if data.get('user_role'):
|
| 56 |
-
pdf.multi_cell(0, 6, f"User Role: {data.get('user_role', '').title()}")
|
| 57 |
if data.get('language_preference'):
|
| 58 |
-
pdf.multi_cell(0, 6, f"Language: {data.get('language_preference', '').title()}")
|
| 59 |
|
| 60 |
pdf.ln(5)
|
| 61 |
scenario = data.get('scenario_analysis',{})
|
| 62 |
if scenario:
|
| 63 |
-
pdf.multi_cell(0, 8, f"Scenario Analysis:")
|
| 64 |
-
pdf.multi_cell(0, 8, f"Yearly Results: {scenario.get('yearly_results','')}")
|
| 65 |
-
pdf.multi_cell(0, 8, f"Cumulative Base: {scenario.get('cumulative_base','')}")
|
| 66 |
-
pdf.multi_cell(0, 8, f"Cumulative Scenario: {scenario.get('cumulative_scenario','')}")
|
| 67 |
-
pdf.multi_cell(0, 8, f"Optimistic: {scenario.get('optimistic','')}")
|
| 68 |
-
pdf.multi_cell(0, 8, f"Pessimistic: {scenario.get('pessimistic','')}")
|
| 69 |
-
pdf.multi_cell(0, 8, f"Driver Breakdown: {scenario.get('driver_breakdown','')}")
|
| 70 |
if not filename:
|
| 71 |
filename = os.path.join(tempfile.gettempdir(), f"evidence_pack_{os.getpid()}.pdf")
|
| 72 |
pdf.output(filename)
|
|
|
|
| 3 |
import tempfile
|
| 4 |
import os
|
| 5 |
|
| 6 |
+
def _safe_text_for_pdf(s: object) -> str:
|
| 7 |
+
"""
|
| 8 |
+
Ensure text passed to FPDF only contains characters encodable in latin-1.
|
| 9 |
+
FPDF (classic) writes using latin-1 encoding by default and will raise
|
| 10 |
+
an error when encountering characters outside that range (e.g. •).
|
| 11 |
+
This helper replaces unsupported characters with a best-effort replacement
|
| 12 |
+
so PDF generation doesn't fail.
|
| 13 |
+
"""
|
| 14 |
+
if s is None:
|
| 15 |
+
return ""
|
| 16 |
+
try:
|
| 17 |
+
text = str(s)
|
| 18 |
+
except Exception:
|
| 19 |
+
text = ""
|
| 20 |
+
# encode -> decode using latin-1 with replacement to avoid exceptions
|
| 21 |
+
return text.encode('latin-1', 'replace').decode('latin-1')
|
| 22 |
def export_evidence_pack_pdf(data, filename=None):
|
| 23 |
"""
|
| 24 |
Export evidence pack as PDF. Data should include clause, summary, checklist, scenario, metadata.
|
|
|
|
| 35 |
pdf.cell(0, 8, txt="Clause:", ln=True)
|
| 36 |
pdf.set_font("Arial", size=10)
|
| 37 |
clause_text = data.get('clause_text', 'No clause information available')
|
| 38 |
+
pdf.multi_cell(0, 6, _safe_text_for_pdf(clause_text))
|
| 39 |
pdf.ln(3)
|
| 40 |
|
| 41 |
# Summary section
|
|
|
|
| 43 |
pdf.cell(0, 8, txt="Summary:", ln=True)
|
| 44 |
pdf.set_font("Arial", size=10)
|
| 45 |
summary_text = data.get('summary', 'No summary available')
|
| 46 |
+
pdf.multi_cell(0, 6, _safe_text_for_pdf(summary_text))
|
| 47 |
pdf.ln(3)
|
| 48 |
|
| 49 |
# Checklist section
|
|
|
|
| 53 |
checklist = data.get('role_checklist', [])
|
| 54 |
if checklist:
|
| 55 |
for item in checklist:
|
| 56 |
+
# use a simple hyphen bullet and sanitize text
|
| 57 |
+
pdf.multi_cell(0, 6, _safe_text_for_pdf(f"- {item}"))
|
| 58 |
else:
|
| 59 |
pdf.multi_cell(0, 6, "No checklist items available")
|
| 60 |
pdf.ln(3)
|
|
|
|
| 63 |
pdf.set_font("Arial", "B", size=12)
|
| 64 |
pdf.cell(0, 8, txt="Source Information:", ln=True)
|
| 65 |
pdf.set_font("Arial", size=10)
|
| 66 |
+
pdf.multi_cell(0, 6, _safe_text_for_pdf(f"Source: {data.get('source_title', 'Not specified')}"))
|
| 67 |
+
pdf.multi_cell(0, 6, _safe_text_for_pdf(f"Clause ID: {data.get('clause_id', 'Not assigned')}"))
|
| 68 |
+
pdf.multi_cell(0, 6, _safe_text_for_pdf(f"Date: {data.get('date', 'Not specified')}"))
|
| 69 |
+
pdf.multi_cell(0, 6, _safe_text_for_pdf(f"URL: {data.get('url', 'Not available')}"))
|
| 70 |
|
| 71 |
# User role and context information
|
| 72 |
if data.get('user_role'):
|
| 73 |
+
pdf.multi_cell(0, 6, _safe_text_for_pdf(f"User Role: {data.get('user_role', '').title()}"))
|
| 74 |
if data.get('language_preference'):
|
| 75 |
+
pdf.multi_cell(0, 6, _safe_text_for_pdf(f"Language: {data.get('language_preference', '').title()}"))
|
| 76 |
|
| 77 |
pdf.ln(5)
|
| 78 |
scenario = data.get('scenario_analysis',{})
|
| 79 |
if scenario:
|
| 80 |
+
pdf.multi_cell(0, 8, _safe_text_for_pdf(f"Scenario Analysis:"))
|
| 81 |
+
pdf.multi_cell(0, 8, _safe_text_for_pdf(f"Yearly Results: {scenario.get('yearly_results','')}"))
|
| 82 |
+
pdf.multi_cell(0, 8, _safe_text_for_pdf(f"Cumulative Base: {scenario.get('cumulative_base','')}"))
|
| 83 |
+
pdf.multi_cell(0, 8, _safe_text_for_pdf(f"Cumulative Scenario: {scenario.get('cumulative_scenario','')}"))
|
| 84 |
+
pdf.multi_cell(0, 8, _safe_text_for_pdf(f"Optimistic: {scenario.get('optimistic','')}"))
|
| 85 |
+
pdf.multi_cell(0, 8, _safe_text_for_pdf(f"Pessimistic: {scenario.get('pessimistic','')}"))
|
| 86 |
+
pdf.multi_cell(0, 8, _safe_text_for_pdf(f"Driver Breakdown: {scenario.get('driver_breakdown','')}"))
|
| 87 |
if not filename:
|
| 88 |
filename = os.path.join(tempfile.gettempdir(), f"evidence_pack_{os.getpid()}.pdf")
|
| 89 |
pdf.output(filename)
|