Spaces:
Runtime error
Runtime error
Commit Β·
d734bf7
1
Parent(s): 450b2cd
SOC2
Browse files- app/DocumentGeneration/pdf_generator.py +20 -5
- app/DocumentGeneration/word_generator.py +17 -4
- app/main.py +36 -32
app/DocumentGeneration/pdf_generator.py
CHANGED
|
@@ -44,12 +44,19 @@ def create_pdf_document(request: ExportRequest):
|
|
| 44 |
raise Exception("fpdf2 is not installed. Please install it with: pip install fpdf2")
|
| 45 |
|
| 46 |
# Filter out empty sections to prevent blank pages
|
| 47 |
-
|
|
|
|
|
|
|
|
|
|
| 48 |
if not filtered_sections:
|
| 49 |
raise Exception("No valid sections found after filtering. All sections appear to be empty.")
|
| 50 |
|
| 51 |
# Generate table of contents
|
| 52 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
# logging.info(f"Generated TOC with {len(toc_items)} items: {[item['title'] for item in toc_items[:3]]}...")
|
| 54 |
|
| 55 |
# Create PDF with professional settings
|
|
@@ -254,8 +261,15 @@ def create_pdf_document(request: ExportRequest):
|
|
| 254 |
pdf.set_xy(25, pdf.get_y()) # Use current Y position for consistent spacing
|
| 255 |
|
| 256 |
# Process content
|
| 257 |
-
|
| 258 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 259 |
|
| 260 |
# Remove chart data markers
|
| 261 |
content_text = re.sub(r'<!--CHARTDATASTART-->.*?<!--CHARTDATAEND-->', '', content_text, flags=re.DOTALL)
|
|
@@ -408,7 +422,8 @@ def create_pdf_document(request: ExportRequest):
|
|
| 408 |
|
| 409 |
charts_added += 1
|
| 410 |
total_charts_placed += 1
|
| 411 |
-
except Exception
|
|
|
|
| 412 |
continue
|
| 413 |
else:
|
| 414 |
# Debug: Check if there are any charts that might match this section
|
|
|
|
| 44 |
raise Exception("fpdf2 is not installed. Please install it with: pip install fpdf2")
|
| 45 |
|
| 46 |
# Filter out empty sections to prevent blank pages
|
| 47 |
+
try:
|
| 48 |
+
filtered_sections = filter_empty_sections(request.sections)
|
| 49 |
+
except Exception as e:
|
| 50 |
+
raise Exception(f"Failed to filter sections: {str(e)}")
|
| 51 |
if not filtered_sections:
|
| 52 |
raise Exception("No valid sections found after filtering. All sections appear to be empty.")
|
| 53 |
|
| 54 |
# Generate table of contents
|
| 55 |
+
try:
|
| 56 |
+
toc_items = generate_table_of_contents(filtered_sections, request.businessIdea)
|
| 57 |
+
except Exception as e:
|
| 58 |
+
toc_items = []
|
| 59 |
+
# Continue with a fallback; TOC page builder already has fallback handling
|
| 60 |
# logging.info(f"Generated TOC with {len(toc_items)} items: {[item['title'] for item in toc_items[:3]]}...")
|
| 61 |
|
| 62 |
# Create PDF with professional settings
|
|
|
|
| 261 |
pdf.set_xy(25, pdf.get_y()) # Use current Y position for consistent spacing
|
| 262 |
|
| 263 |
# Process content
|
| 264 |
+
try:
|
| 265 |
+
content_text = clean_text_for_pdf(section.content)
|
| 266 |
+
except Exception as e:
|
| 267 |
+
content_text = section.content or ""
|
| 268 |
+
try:
|
| 269 |
+
content_text = convert_currency_symbols_to_iso(content_text)
|
| 270 |
+
except Exception:
|
| 271 |
+
# If currency conversion fails, keep original text
|
| 272 |
+
pass
|
| 273 |
|
| 274 |
# Remove chart data markers
|
| 275 |
content_text = re.sub(r'<!--CHARTDATASTART-->.*?<!--CHARTDATAEND-->', '', content_text, flags=re.DOTALL)
|
|
|
|
| 422 |
|
| 423 |
charts_added += 1
|
| 424 |
total_charts_placed += 1
|
| 425 |
+
except Exception:
|
| 426 |
+
# Skip faulty chart and continue rendering others
|
| 427 |
continue
|
| 428 |
else:
|
| 429 |
# Debug: Check if there are any charts that might match this section
|
app/DocumentGeneration/word_generator.py
CHANGED
|
@@ -41,7 +41,10 @@ def create_word_document(request: ExportRequest):
|
|
| 41 |
raise Exception("python-docx is not installed. Please install it with: pip install python-docx")
|
| 42 |
|
| 43 |
# Filter out empty sections to prevent blank pages
|
| 44 |
-
|
|
|
|
|
|
|
|
|
|
| 45 |
if not filtered_sections:
|
| 46 |
raise Exception("No valid sections found after filtering. All sections appear to be empty.")
|
| 47 |
|
|
@@ -138,7 +141,11 @@ def create_word_document(request: ExportRequest):
|
|
| 138 |
doc.add_paragraph()
|
| 139 |
|
| 140 |
# Sort sections according to predefined order (do this before TOC generation)
|
| 141 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 142 |
|
| 143 |
# Generate TOC entries with actual page numbers
|
| 144 |
# Since each section starts on a new page, we can calculate page numbers
|
|
@@ -205,8 +212,14 @@ def create_word_document(request: ExportRequest):
|
|
| 205 |
doc.add_heading(section.title, level=1)
|
| 206 |
|
| 207 |
# Clean and process content
|
| 208 |
-
|
| 209 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 210 |
|
| 211 |
# Remove chart data markers
|
| 212 |
clean_content = re.sub(r'<!--CHARTDATASTART-->.*?<!--CHARTDATAEND-->', '', clean_content, flags=re.DOTALL)
|
|
|
|
| 41 |
raise Exception("python-docx is not installed. Please install it with: pip install python-docx")
|
| 42 |
|
| 43 |
# Filter out empty sections to prevent blank pages
|
| 44 |
+
try:
|
| 45 |
+
filtered_sections = filter_empty_sections(request.sections)
|
| 46 |
+
except Exception as e:
|
| 47 |
+
raise Exception(f"Failed to filter sections: {str(e)}")
|
| 48 |
if not filtered_sections:
|
| 49 |
raise Exception("No valid sections found after filtering. All sections appear to be empty.")
|
| 50 |
|
|
|
|
| 141 |
doc.add_paragraph()
|
| 142 |
|
| 143 |
# Sort sections according to predefined order (do this before TOC generation)
|
| 144 |
+
try:
|
| 145 |
+
sorted_sections = sort_sections_by_order(filtered_sections)
|
| 146 |
+
except Exception as e:
|
| 147 |
+
# Fallback: use filtered order if sorting fails
|
| 148 |
+
sorted_sections = filtered_sections
|
| 149 |
|
| 150 |
# Generate TOC entries with actual page numbers
|
| 151 |
# Since each section starts on a new page, we can calculate page numbers
|
|
|
|
| 212 |
doc.add_heading(section.title, level=1)
|
| 213 |
|
| 214 |
# Clean and process content
|
| 215 |
+
try:
|
| 216 |
+
clean_content = clean_text_for_pdf(section.content)
|
| 217 |
+
except Exception:
|
| 218 |
+
clean_content = section.content or ""
|
| 219 |
+
try:
|
| 220 |
+
clean_content = convert_currency_symbols_to_iso(clean_content)
|
| 221 |
+
except Exception:
|
| 222 |
+
pass
|
| 223 |
|
| 224 |
# Remove chart data markers
|
| 225 |
clean_content = re.sub(r'<!--CHARTDATASTART-->.*?<!--CHARTDATAEND-->', '', clean_content, flags=re.DOTALL)
|
app/main.py
CHANGED
|
@@ -1457,36 +1457,32 @@ async def export_business_plan(request: ExportRequest):
|
|
| 1457 |
FLOW: Server generates document β Returns base64 data β Frontend uploads to UploadThing β User gets download link
|
| 1458 |
"""
|
| 1459 |
try:
|
| 1460 |
-
|
| 1461 |
-
|
| 1462 |
-
|
| 1463 |
-
|
| 1464 |
-
|
| 1465 |
-
|
| 1466 |
-
|
| 1467 |
-
|
| 1468 |
-
|
| 1469 |
-
|
| 1470 |
-
|
| 1471 |
-
|
| 1472 |
-
|
| 1473 |
-
|
| 1474 |
-
|
| 1475 |
-
|
| 1476 |
-
|
| 1477 |
-
|
| 1478 |
-
|
| 1479 |
-
|
| 1480 |
-
|
| 1481 |
-
|
| 1482 |
-
|
| 1483 |
-
|
| 1484 |
-
|
| 1485 |
-
|
| 1486 |
-
|
| 1487 |
-
# Record this request
|
| 1488 |
-
export_business_plan.last_requests[request_fingerprint] = current_time
|
| 1489 |
-
|
| 1490 |
|
| 1491 |
|
| 1492 |
# Validate request format
|
|
@@ -1503,10 +1499,13 @@ async def export_business_plan(request: ExportRequest):
|
|
| 1503 |
if len(request.sections) == 0:
|
| 1504 |
raise HTTPException(status_code=400, detail="At least one section is required for export.")
|
| 1505 |
|
| 1506 |
-
# Check section content
|
| 1507 |
for i, section in enumerate(request.sections):
|
|
|
|
|
|
|
|
|
|
| 1508 |
if not section.content or not section.content.strip():
|
| 1509 |
-
raise HTTPException(status_code=400, detail=f"Section '{
|
| 1510 |
|
| 1511 |
# logging.info(f"Exporting business plan for: {request.businessIdea} in {request.format.upper()} format")
|
| 1512 |
# logging.info(f"Request has {len(request.sections)} sections and summary length: {len(request.summary)}")
|
|
@@ -1539,6 +1538,11 @@ async def export_business_plan(request: ExportRequest):
|
|
| 1539 |
doc_bytes, filename = create_pdf_document(request)
|
| 1540 |
else: # word
|
| 1541 |
doc_bytes, filename = create_word_document(request)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1542 |
except ImportError as import_error:
|
| 1543 |
raise HTTPException(
|
| 1544 |
status_code=500,
|
|
|
|
| 1457 |
FLOW: Server generates document β Returns base64 data β Frontend uploads to UploadThing β User gets download link
|
| 1458 |
"""
|
| 1459 |
try:
|
| 1460 |
+
# Duplicate detection (best-effort; non-fatal if bookkeeping fails)
|
| 1461 |
+
try:
|
| 1462 |
+
request_id = str(id(request))
|
| 1463 |
+
request_fingerprint = f"{request.businessIdea}_{request.format}_{len(request.sections)}_{hash(str(request.sections))}"
|
| 1464 |
+
current_time = time.time()
|
| 1465 |
+
if hasattr(export_business_plan, 'last_requests'):
|
| 1466 |
+
# Clean old requests (older than 5 seconds)
|
| 1467 |
+
export_business_plan.last_requests = {
|
| 1468 |
+
fp: timestamp for fp, timestamp in export_business_plan.last_requests.items()
|
| 1469 |
+
if current_time - timestamp < 5
|
| 1470 |
+
}
|
| 1471 |
+
# Check for duplicate
|
| 1472 |
+
if request_fingerprint in export_business_plan.last_requests:
|
| 1473 |
+
raise HTTPException(
|
| 1474 |
+
status_code=429,
|
| 1475 |
+
detail="Duplicate export request detected. Please wait a few seconds before trying again."
|
| 1476 |
+
)
|
| 1477 |
+
else:
|
| 1478 |
+
export_business_plan.last_requests = {}
|
| 1479 |
+
# Record this request
|
| 1480 |
+
export_business_plan.last_requests[request_fingerprint] = current_time
|
| 1481 |
+
except HTTPException:
|
| 1482 |
+
raise
|
| 1483 |
+
except Exception:
|
| 1484 |
+
# Do not block export on dedupe bookkeeping errors
|
| 1485 |
+
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1486 |
|
| 1487 |
|
| 1488 |
# Validate request format
|
|
|
|
| 1499 |
if len(request.sections) == 0:
|
| 1500 |
raise HTTPException(status_code=400, detail="At least one section is required for export.")
|
| 1501 |
|
| 1502 |
+
# Check section content and shape
|
| 1503 |
for i, section in enumerate(request.sections):
|
| 1504 |
+
title = getattr(section, 'title', None)
|
| 1505 |
+
if not title or not str(title).strip():
|
| 1506 |
+
raise HTTPException(status_code=400, detail=f"Section at index {i} is missing a title.")
|
| 1507 |
if not section.content or not section.content.strip():
|
| 1508 |
+
raise HTTPException(status_code=400, detail=f"Section '{title}' has no content.")
|
| 1509 |
|
| 1510 |
# logging.info(f"Exporting business plan for: {request.businessIdea} in {request.format.upper()} format")
|
| 1511 |
# logging.info(f"Request has {len(request.sections)} sections and summary length: {len(request.summary)}")
|
|
|
|
| 1538 |
doc_bytes, filename = create_pdf_document(request)
|
| 1539 |
else: # word
|
| 1540 |
doc_bytes, filename = create_word_document(request)
|
| 1541 |
+
# Basic sanity check on output
|
| 1542 |
+
if not doc_bytes or (isinstance(doc_bytes, (bytes, bytearray)) and len(doc_bytes) < 1000):
|
| 1543 |
+
raise HTTPException(status_code=500, detail=f"Generated {request.format.upper()} appears empty or invalid.")
|
| 1544 |
+
except HTTPException:
|
| 1545 |
+
raise
|
| 1546 |
except ImportError as import_error:
|
| 1547 |
raise HTTPException(
|
| 1548 |
status_code=500,
|