Soumik Bose commited on
Commit
c2a315b
·
1 Parent(s): d54e4f5
Files changed (1) hide show
  1. pdf_report_generation_service.py +148 -11
pdf_report_generation_service.py CHANGED
@@ -1,6 +1,6 @@
1
  """
2
- Professional Q4 Report Generator - Complete Working Version
3
- Generates executive-quality PDF reports with proper image rendering
4
  """
5
  import markdown
6
  from weasyprint import HTML, CSS
@@ -22,7 +22,7 @@ logging.basicConfig(
22
  logger = logging.getLogger(__name__)
23
 
24
  # ---------------------------------------------------------
25
- # EXECUTIVE Q4 REPORT THEME
26
  # ---------------------------------------------------------
27
  CSS_STYLE = """
28
  @page {
@@ -253,20 +253,72 @@ blockquote {
253
  page-break-after: always;
254
  }
255
 
256
- ul, ol {
257
- margin: 14px 0;
258
- padding-left: 28px;
 
 
 
 
 
 
 
 
 
 
259
  }
260
 
261
  li {
262
- margin: 8px 0;
263
  line-height: 1.6;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
264
  }
265
 
266
  strong {
267
  font-weight: 600;
268
  color: #000;
269
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
270
  """
271
 
272
  # ---------------------------------------------------------
@@ -371,16 +423,33 @@ class Q4ReportGenerator:
371
  """
372
 
373
  def _process_section(self, section: Dict, output_dir: str) -> str:
 
374
  html = ""
375
 
376
  content = section.get("content", "")
377
  if content:
 
378
  html_content = markdown.markdown(
379
  content,
380
- extensions=['extra', 'sane_lists', 'tables', 'fenced_code']
 
 
 
 
 
 
 
 
 
 
 
 
 
 
381
  )
382
  html += f"<div class='section'>{html_content}</div>"
383
 
 
384
  images = section.get("images", [])
385
  for img_data in images:
386
  if isinstance(img_data, dict):
@@ -401,6 +470,7 @@ class Q4ReportGenerator:
401
  </div>
402
  """
403
 
 
404
  if section.get("page_break", False):
405
  html += '<div class="page-break"></div>'
406
 
@@ -411,20 +481,24 @@ class Q4ReportGenerator:
411
  filename: str = "Q4_Report.pdf",
412
  output_dir: str = "./output",
413
  cover_page: bool = True) -> bool:
 
414
  try:
415
  os.makedirs(output_dir, exist_ok=True)
416
  output_path = os.path.join(output_dir, filename)
417
 
418
  logger.info(f"Starting Q4 report generation: {output_path}")
419
 
 
420
  body_html = ""
421
  if cover_page:
422
  body_html += self._build_cover_page()
423
 
 
424
  for idx, section in enumerate(sections):
425
  logger.info(f"Processing section {idx + 1}/{len(sections)}")
426
  body_html += self._process_section(section, output_dir)
427
 
 
428
  full_html = f"""
429
  <!DOCTYPE html>
430
  <html>
@@ -440,15 +514,78 @@ class Q4ReportGenerator:
440
  </html>
441
  """
442
 
 
443
  logger.info("Rendering PDF with WeasyPrint...")
444
  html_doc = HTML(string=full_html)
445
  css_doc = CSS(string=CSS_STYLE)
446
 
447
  html_doc.write_pdf(output_path, stylesheets=[css_doc])
448
 
449
- logger.info(f"PDF Generated: {output_path}")
450
  return True
451
 
452
  except Exception as e:
453
- logger.error(f"Error generating report: {e}", exc_info=True)
454
- return False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  """
2
+ Professional Q4 Report Generator - Enhanced Markdown Parsing
3
+ Generates executive-quality PDF reports with proper list rendering
4
  """
5
  import markdown
6
  from weasyprint import HTML, CSS
 
22
  logger = logging.getLogger(__name__)
23
 
24
  # ---------------------------------------------------------
25
+ # EXECUTIVE Q4 REPORT THEME - ENHANCED LIST STYLING
26
  # ---------------------------------------------------------
27
  CSS_STYLE = """
28
  @page {
 
253
  page-break-after: always;
254
  }
255
 
256
+ /* ENHANCED LIST STYLING - ENSURES PROPER BULLET RENDERING */
257
+ ul {
258
+ margin: 16px 0;
259
+ padding-left: 35px;
260
+ list-style-type: disc;
261
+ list-style-position: outside;
262
+ }
263
+
264
+ ol {
265
+ margin: 16px 0;
266
+ padding-left: 35px;
267
+ list-style-type: decimal;
268
+ list-style-position: outside;
269
  }
270
 
271
  li {
272
+ margin: 6px 0;
273
  line-height: 1.6;
274
+ padding-left: 5px;
275
+ display: list-item;
276
+ }
277
+
278
+ ul ul {
279
+ margin: 8px 0;
280
+ padding-left: 25px;
281
+ list-style-type: circle;
282
+ }
283
+
284
+ ul ul ul {
285
+ list-style-type: square;
286
+ }
287
+
288
+ ol ol {
289
+ margin: 8px 0;
290
+ padding-left: 25px;
291
+ list-style-type: lower-alpha;
292
  }
293
 
294
  strong {
295
  font-weight: 600;
296
  color: #000;
297
  }
298
+
299
+ em {
300
+ font-style: italic;
301
+ }
302
+
303
+ code {
304
+ font-family: 'Courier New', monospace;
305
+ background-color: #f4f4f4;
306
+ padding: 2px 6px;
307
+ border-radius: 3px;
308
+ font-size: 10pt;
309
+ }
310
+
311
+ pre {
312
+ background-color: #f4f4f4;
313
+ border: 1px solid #ddd;
314
+ border-radius: 4px;
315
+ padding: 12px;
316
+ overflow-x: auto;
317
+ font-family: 'Courier New', monospace;
318
+ font-size: 9.5pt;
319
+ line-height: 1.4;
320
+ margin: 16px 0;
321
+ }
322
  """
323
 
324
  # ---------------------------------------------------------
 
423
  """
424
 
425
  def _process_section(self, section: Dict, output_dir: str) -> str:
426
+ """Process section with enhanced markdown parsing for proper list rendering."""
427
  html = ""
428
 
429
  content = section.get("content", "")
430
  if content:
431
+ # Use comprehensive markdown extensions for full parsing
432
  html_content = markdown.markdown(
433
  content,
434
+ extensions=[
435
+ 'extra', # Tables, footnotes, attributes
436
+ 'sane_lists', # Better list handling
437
+ 'tables', # Table support
438
+ 'fenced_code', # Code blocks
439
+ 'nl2br', # Newline to <br>
440
+ 'codehilite', # Syntax highlighting
441
+ 'toc' # Table of contents
442
+ ],
443
+ extension_configs={
444
+ 'codehilite': {
445
+ 'guess_lang': False,
446
+ 'noclasses': True
447
+ }
448
+ }
449
  )
450
  html += f"<div class='section'>{html_content}</div>"
451
 
452
+ # Handle images
453
  images = section.get("images", [])
454
  for img_data in images:
455
  if isinstance(img_data, dict):
 
470
  </div>
471
  """
472
 
473
+ # Page break if requested
474
  if section.get("page_break", False):
475
  html += '<div class="page-break"></div>'
476
 
 
481
  filename: str = "Q4_Report.pdf",
482
  output_dir: str = "./output",
483
  cover_page: bool = True) -> bool:
484
+ """Generate PDF report with proper markdown parsing."""
485
  try:
486
  os.makedirs(output_dir, exist_ok=True)
487
  output_path = os.path.join(output_dir, filename)
488
 
489
  logger.info(f"Starting Q4 report generation: {output_path}")
490
 
491
+ # Build HTML body
492
  body_html = ""
493
  if cover_page:
494
  body_html += self._build_cover_page()
495
 
496
+ # Process all sections
497
  for idx, section in enumerate(sections):
498
  logger.info(f"Processing section {idx + 1}/{len(sections)}")
499
  body_html += self._process_section(section, output_dir)
500
 
501
+ # Construct full HTML document
502
  full_html = f"""
503
  <!DOCTYPE html>
504
  <html>
 
514
  </html>
515
  """
516
 
517
+ # Generate PDF
518
  logger.info("Rendering PDF with WeasyPrint...")
519
  html_doc = HTML(string=full_html)
520
  css_doc = CSS(string=CSS_STYLE)
521
 
522
  html_doc.write_pdf(output_path, stylesheets=[css_doc])
523
 
524
+ logger.info(f"PDF Generated Successfully: {output_path}")
525
  return True
526
 
527
  except Exception as e:
528
+ logger.error(f"Error generating report: {e}", exc_info=True)
529
+ return False
530
+
531
+ def cleanup_images(self, output_dir: str):
532
+ """Optional: Clean up downloaded images after PDF generation."""
533
+ try:
534
+ img_dir = os.path.join(output_dir, 'images')
535
+ if os.path.exists(img_dir):
536
+ import shutil
537
+ shutil.rmtree(img_dir)
538
+ logger.info("Cleaned up temporary images")
539
+ except Exception as e:
540
+ logger.warning(f"Failed to cleanup images: {e}")
541
+
542
+
543
+ # ---------------------------------------------------------
544
+ # USAGE EXAMPLE
545
+ # ---------------------------------------------------------
546
+ # if __name__ == "__main__":
547
+ # # Example usage
548
+ # generator = Q4ReportGenerator(
549
+ # title="Q4 2024 Performance Report",
550
+ # subtitle="Product Analytics Summary",
551
+ # author="Analytics Team",
552
+ # department="Product Department"
553
+ # )
554
+
555
+ # sections = [
556
+ # {
557
+ # "content": """
558
+ # # Executive Summary
559
+
560
+ # This report provides a comprehensive analysis of Q4 2024 product performance.
561
+
562
+ # ## Key Highlights
563
+
564
+ # * Revenue increased by 23% compared to Q3
565
+ # * Customer satisfaction improved to 4.7/5.0
566
+ # * Successfully launched 3 new product features
567
+
568
+ # ## Daily Product Additions
569
+
570
+ # Example Daily Additions:
571
+
572
+ # * 2023-09-30: 3 products
573
+ # * 2023-10-01: 1 product
574
+ # * 2023-10-02: 4 products
575
+ # * 2024-09-27: 3 products
576
+ # * 2024-09-28: 3 products
577
+ # * 2024-09-29: 2 products
578
+
579
+ # ## Growth Metrics
580
+
581
+ # 1. User acquisition up 45%
582
+ # 2. Retention rate improved to 87%
583
+ # 3. Average session time increased by 12 minutes
584
+ # """,
585
+ # "page_break": False
586
+ # }
587
+ # ]
588
+
589
+ # success = generator.generate(sections, filename="Q4_Report.pdf")
590
+ # if success:
591
+ # print("Report generated successfully!")