Soumik Bose commited on
Commit
d54e4f5
·
1 Parent(s): 1539a3f
pdf_report_generation_helper.py CHANGED
@@ -6,9 +6,9 @@ import os
6
  import uuid
7
  import logging
8
  from typing import List, Dict, Any
9
- from pathlib import Path
10
  from starlette.concurrency import run_in_threadpool
11
 
 
12
  from pdf_report_generation_service import Q4ReportGenerator
13
 
14
  logger = logging.getLogger(__name__)
@@ -16,7 +16,7 @@ logger = logging.getLogger(__name__)
16
  def _upload_to_supabase_sync(file_path: str, file_name: str, chat_id: str) -> str:
17
  """
18
  Synchronous wrapper for Supabase upload.
19
- Import your actual upload function here.
20
  """
21
  from supabase_service import upload_file_to_supabase
22
  return upload_file_to_supabase(
@@ -92,24 +92,7 @@ async def generate_and_upload_report(
92
  confidential: bool = True
93
  ) -> str:
94
  """
95
- Generates a PDF report from content configuration, uploads it to Supabase,
96
- and returns the public URL. Cleans up temporary files afterwards.
97
-
98
- Args:
99
- config_content: List of section dictionaries for the report
100
- file_name: Desired filename (without or with .pdf extension)
101
- chat_id: Chat ID for Supabase storage organization
102
- title: Report title
103
- subtitle: Report subtitle
104
- author: Report author
105
- department: Department name
106
- output_dir: Directory for temporary file storage
107
-
108
- Returns:
109
- str: Public URL of the uploaded PDF
110
-
111
- Raises:
112
- Exception: If PDF generation or upload fails
113
  """
114
 
115
  local_file_path = None
@@ -118,11 +101,9 @@ async def generate_and_upload_report(
118
  # 1. Setup paths
119
  os.makedirs(output_dir, exist_ok=True)
120
 
121
- # Ensure filename ends with .pdf
122
  if not file_name.endswith('.pdf'):
123
  file_name += '.pdf'
124
 
125
- # Create unique filename to prevent collisions
126
  unique_id = str(uuid.uuid4())[:8]
127
  local_filename = f"{os.path.splitext(file_name)[0]}_{unique_id}.pdf"
128
  local_file_path = os.path.join(output_dir, local_filename)
@@ -177,13 +158,11 @@ async def generate_and_upload_report(
177
  raise Exception(f"Failed to generate and upload report: {str(e)}")
178
 
179
  finally:
180
- # 5. Cleanup - Always execute, even if there's an error
181
  try:
182
- # Delete local PDF
183
  if local_file_path:
184
  _cleanup_files([local_file_path])
185
 
186
- # Delete downloaded images
187
  if 'generator' in locals():
188
  await run_in_threadpool(
189
  _cleanup_downloaded_images,
 
6
  import uuid
7
  import logging
8
  from typing import List, Dict, Any
 
9
  from starlette.concurrency import run_in_threadpool
10
 
11
+ # The Service file defines the Generator class
12
  from pdf_report_generation_service import Q4ReportGenerator
13
 
14
  logger = logging.getLogger(__name__)
 
16
  def _upload_to_supabase_sync(file_path: str, file_name: str, chat_id: str) -> str:
17
  """
18
  Synchronous wrapper for Supabase upload.
19
+ Import inside function to avoid potential circular dependencies if supabase_service changes.
20
  """
21
  from supabase_service import upload_file_to_supabase
22
  return upload_file_to_supabase(
 
92
  confidential: bool = True
93
  ) -> str:
94
  """
95
+ Generates a PDF report, uploads it to Supabase, and returns the public URL.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  """
97
 
98
  local_file_path = None
 
101
  # 1. Setup paths
102
  os.makedirs(output_dir, exist_ok=True)
103
 
 
104
  if not file_name.endswith('.pdf'):
105
  file_name += '.pdf'
106
 
 
107
  unique_id = str(uuid.uuid4())[:8]
108
  local_filename = f"{os.path.splitext(file_name)[0]}_{unique_id}.pdf"
109
  local_file_path = os.path.join(output_dir, local_filename)
 
158
  raise Exception(f"Failed to generate and upload report: {str(e)}")
159
 
160
  finally:
161
+ # 5. Cleanup
162
  try:
 
163
  if local_file_path:
164
  _cleanup_files([local_file_path])
165
 
 
166
  if 'generator' in locals():
167
  await run_in_threadpool(
168
  _cleanup_downloaded_images,
pdf_report_generation_service.py CHANGED
@@ -275,13 +275,6 @@ strong {
275
  def fetch_image(url: str, output_dir: str) -> Optional[str]:
276
  """
277
  Download image from URL to persistent file for PDF rendering.
278
-
279
- Args:
280
- url: The URL of the image
281
- output_dir: Directory to save downloaded images
282
-
283
- Returns:
284
- File URI string or None if failed
285
  """
286
  if not url.startswith(('http://', 'https://')):
287
  if os.path.exists(url):
@@ -299,7 +292,6 @@ def fetch_image(url: str, output_dir: str) -> Optional[str]:
299
  response = requests.get(url, headers=headers, timeout=15)
300
  response.raise_for_status()
301
 
302
- # Determine file extension
303
  content_type = response.headers.get('content-type', '')
304
  ext = '.jpg'
305
  if 'png' in content_type:
@@ -454,206 +446,9 @@ class Q4ReportGenerator:
454
 
455
  html_doc.write_pdf(output_path, stylesheets=[css_doc])
456
 
457
- abs_path = os.path.abspath(output_path)
458
- file_size = os.path.getsize(output_path) / 1024
459
-
460
- print(f"\n{'='*70}")
461
- print(f"✅ PDF GENERATED SUCCESSFULLY!")
462
- print(f"{'='*70}")
463
- print(f"📁 Location: {abs_path}")
464
- print(f"📊 Size: {file_size:.2f} KB")
465
- print(f"🖼️ Images: {len(self.downloaded_images)} embedded")
466
- print(f"{'='*70}\n")
467
-
468
  return True
469
 
470
  except Exception as e:
471
- logger.error(f"Error generating report: {e}", exc_info=True)
472
- return False
473
-
474
- # ---------------------------------------------------------
475
- # COMPLETE TEST CASE WITH FULL Q4 REPORT
476
- # ---------------------------------------------------------
477
- # if __name__ == "__main__":
478
-
479
- # q4_sections = [
480
- # {
481
- # "content": """
482
- # # Executive Summary
483
-
484
- # This report presents the comprehensive Q4 2025 performance analysis for our customer analytics division. The quarter demonstrated strong growth across key metrics with notable improvements in customer engagement and retention.
485
-
486
- # <div class="key-metric-box">
487
-
488
- # ### Key Q4 Highlights
489
-
490
- # - **Revenue Growth**: 23.4% increase over Q3 2025
491
- # - **Customer Acquisition**: 8,547 new customers (+18% QoQ)
492
- # - **Retention Rate**: 94.2% (industry-leading performance)
493
- # - **Data Quality Score**: 98.7% completeness
494
-
495
- # </div>
496
-
497
- # > "Q4 2025 marks our strongest quarter to date, with record-breaking performance across all major KPIs and successful implementation of our new customer engagement strategy."
498
-
499
- # ## Quarter at a Glance
500
-
501
- # The analytics team processed over 2.3 million customer interactions, generating actionable insights that drove strategic decisions across the organization.
502
- # """
503
- # },
504
- # {
505
- # "content": """
506
- # # Performance Metrics
507
-
508
- # ## Overall Q4 Performance
509
-
510
- # | Metric | Q4 2025 | Q3 2025 | Change | Target |
511
- # |--------|---------|---------|--------|--------|
512
- # | Total Revenue | $4.2M | $3.4M | +23.4% | $4.0M |
513
- # | New Customers | 8,547 | 7,243 | +18.0% | 8,000 |
514
- # | Active Users | 45,892 | 42,156 | +8.9% | 44,000 |
515
- # | Retention Rate | 94.2% | 92.8% | +1.4pp | 93.0% |
516
- # | Avg. Order Value | $127 | $118 | +7.6% | $120 |
517
-
518
- # <div class="highlight-box">
519
-
520
- # **Performance Assessment**: All key metrics exceeded quarterly targets, demonstrating the effectiveness of our data-driven strategy and customer-focused initiatives.
521
-
522
- # </div>
523
-
524
- # ## Channel Performance Analysis
525
-
526
- # The following visualization shows the distribution of customer orders across different channels in Q4 2025.
527
- # """,
528
- # "images": [
529
- # {
530
- # "url": "https://quickchart.io/chart?c={type:'bar',data:{labels:['Mobile App','Desktop Web','Mobile Web'],datasets:[{label:'Q4 Orders (%)',data:[52,35,13],backgroundColor:['rgba(0,51,102,0.8)','rgba(0,85,165,0.8)','rgba(52,152,219,0.8)']}]},options:{title:{display:true,text:'Q4 2025 Channel Distribution'},scales:{y:{beginAtZero:true,max:60}},plugins:{legend:{display:true}}}}",
531
- # "caption": "Figure 1: Customer Orders by Channel - Q4 2025"
532
- # }
533
- # ],
534
- # "page_break": True
535
- # },
536
- # {
537
- # "content": """
538
- # # Customer Analytics Deep Dive
539
-
540
- # ## Demographic Insights
541
-
542
- # Our Q4 analysis reveals significant shifts in customer demographics and behavior patterns.
543
-
544
- # ### Age Distribution
545
-
546
- # | Age Group | Customers | % of Total | Avg. Spend | Growth (QoQ) |
547
- # |-----------|-----------|------------|------------|--------------|
548
- # | 18-24 | 8,234 | 17.9% | $89 | +22% |
549
- # | 25-34 | 15,678 | 34.2% | $142 | +15% |
550
- # | 35-44 | 12,456 | 27.1% | $156 | +11% |
551
- # | 45-54 | 6,789 | 14.8% | $178 | +8% |
552
- # | 55+ | 2,735 | 6.0% | $203 | +5% |
553
-
554
- # ## Revenue Trend Analysis
555
-
556
- # Below is the revenue trend across Q4 months showing consistent growth:
557
- # """,
558
- # "images": [
559
- # {
560
- # "url": "https://quickchart.io/chart?c={type:'line',data:{labels:['October','November','December'],datasets:[{label:'Revenue ($M)',data:[1.2,1.5,1.5],borderColor:'rgb(0,51,102)',backgroundColor:'rgba(0,51,102,0.1)',fill:true}]},options:{title:{display:true,text:'Q4 2025 Monthly Revenue Trend'},scales:{y:{beginAtZero:true,max:2}}}}",
561
- # "caption": "Figure 2: Monthly Revenue Trend - Q4 2025"
562
- # }
563
- # ],
564
- # "page_break": True
565
- # },
566
- # {
567
- # "content": """
568
- # # Recommendations & 2026 Strategy
569
-
570
- # ## Key Recommendations
571
-
572
- # ### Immediate Actions (Q1 2026)
573
-
574
- # 1. **Expand Mobile Capabilities**
575
- # - Invest in mobile app feature development
576
- # - Optimize mobile checkout experience
577
- # - Target: 60% mobile order share by Q2 2026
578
-
579
- # 2. **Scale Personalization Engine**
580
- # - Deploy to all customer touchpoints
581
- # - Expected: 35% increase in engagement
582
-
583
- # 3. **Enhance Data Infrastructure**
584
- # - Implement advanced data governance framework
585
- # - Prepare for 3x data volume growth
586
-
587
- # <div class="executive-summary">
588
-
589
- # **2026 Vision**: Position our analytics capabilities as a competitive differentiator through advanced AI, real-time insights, and predictive decision-making.
590
-
591
- # </div>
592
-
593
- # ## Investment Requirements
594
-
595
- # | Initiative | Investment | Expected ROI | Timeline |
596
- # |------------|------------|--------------|----------|
597
- # | Mobile Expansion | $450K | 280% | Q1-Q2 2026 |
598
- # | AI/ML Platform | $780K | 420% | Q1-Q4 2026 |
599
- # | Infrastructure | $320K | 195% | Q1-Q3 2026 |
600
- # | **Total** | **$1.55M** | **310%** | **2026** |
601
- # """
602
- # },
603
- # {
604
- # "content": f"""
605
- # # Conclusion
606
-
607
- # ## Quarter Summary
608
-
609
- # Q4 2025 represents a milestone quarter for our analytics organization. We exceeded all primary objectives, delivered exceptional data quality, and laid the foundation for continued growth.
610
-
611
- # ### Key Achievements
612
-
613
- # - ✓ **23.4% revenue growth** exceeding target by 5%
614
- # - ✓ **8,547 new customers** acquired, 7% above goal
615
- # - ✓ **94.2% retention rate**, best in company history
616
- # - ✓ **98.7% data quality score**, industry-leading
617
-
618
- # <div class="highlight-box">
619
-
620
- # **Strategic Position**: Our analytics capabilities represent a significant competitive advantage for 2026 and beyond.
621
-
622
- # </div>
623
-
624
- # ---
625
-
626
- # **Prepared by**: Data Analytics Team
627
- # **Report Date**: {datetime.now().strftime("%B %d, %Y")}
628
- # **Classification**: Confidential - Internal Use Only
629
- # """
630
- # }
631
- # ]
632
-
633
- # # Generate the complete Q4 report
634
- # generator = Q4ReportGenerator(
635
- # title="Q4 2025 Analytics Performance Report",
636
- # subtitle="Fourth Quarter Results & 2026 Strategy",
637
- # author="Senior Data Analyst",
638
- # department="Data Analytics Division",
639
- # confidential=True
640
- # )
641
-
642
- # success = generator.generate(
643
- # sections=q4_sections,
644
- # filename="Q4_2025_Complete_Report.pdf",
645
- # output_dir="./output",
646
- # cover_page=True
647
- # )
648
-
649
- # if success:
650
- # print("\n📋 Report Features:")
651
- # print(" ✓ Executive cover page")
652
- # print(" ✓ Performance metrics with tables")
653
- # print(" ✓ Embedded charts and visualizations")
654
- # print(" ✓ Customer analytics insights")
655
- # print(" ✓ Strategic recommendations")
656
- # print(" ✓ Professional styling and formatting")
657
- # print("\n💡 Check the 'output' folder for your PDF!")
658
- # else:
659
- # print("\n❌ Report generation failed. Check logs for details.")
 
275
  def fetch_image(url: str, output_dir: str) -> Optional[str]:
276
  """
277
  Download image from URL to persistent file for PDF rendering.
 
 
 
 
 
 
 
278
  """
279
  if not url.startswith(('http://', 'https://')):
280
  if os.path.exists(url):
 
292
  response = requests.get(url, headers=headers, timeout=15)
293
  response.raise_for_status()
294
 
 
295
  content_type = response.headers.get('content-type', '')
296
  ext = '.jpg'
297
  if 'png' in content_type:
 
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