import os import json from datetime import datetime from typing import Dict, Optional import logging try: from reportlab.lib.pagesizes import letter, A4 from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle from reportlab.lib.units import inch from reportlab.lib import colors from reportlab.lib.enums import TA_CENTER, TA_LEFT REPORTLAB_AVAILABLE = True except ImportError: REPORTLAB_AVAILABLE = False # Configure logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) class PDFGeneratorService: """Service for generating PDF reports for yearly plans and other documents""" def __init__(self, output_dir: str = "generated_pdfs"): self.output_dir = output_dir os.makedirs(output_dir, exist_ok=True) if not REPORTLAB_AVAILABLE: logger.warning("ReportLab not available. PDF generation will use fallback HTML to PDF") def generate_yearly_plan_pdf(self, farmer_data: Dict, plan_data: Dict) -> Optional[str]: """Generate PDF for yearly plan""" try: if REPORTLAB_AVAILABLE: return self._generate_with_reportlab(farmer_data, plan_data) else: return self._generate_with_html_fallback(farmer_data, plan_data) except Exception as e: logger.error(f"Error generating yearly plan PDF: {str(e)}") return None def _generate_with_reportlab(self, farmer_data: Dict, plan_data: Dict) -> str: """Generate PDF using ReportLab""" try: # Create filename timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") filename = f"yearly_plan_{farmer_data.get('name', 'farmer')}_{timestamp}.pdf" filepath = os.path.join(self.output_dir, filename) # Create document doc = SimpleDocTemplate(filepath, pagesize=A4, topMargin=0.5*inch) styles = getSampleStyleSheet() # Custom styles title_style = ParagraphStyle( 'CustomTitle', parent=styles['Heading1'], fontSize=24, textColor=colors.darkgreen, spaceAfter=20, alignment=TA_CENTER ) heading_style = ParagraphStyle( 'CustomHeading', parent=styles['Heading2'], fontSize=16, textColor=colors.blue, spaceAfter=12, spaceBefore=20 ) story = [] # Title story.append(Paragraph("🌾 Comprehensive Yearly Farming Plan", title_style)) story.append(Spacer(1, 20)) # Farmer Information story.append(Paragraph("Farmer Information", heading_style)) farmer_info = [ ['Name:', farmer_data.get('name', 'N/A')], ['Contact:', farmer_data.get('contact_number', 'N/A')], ['Address:', farmer_data.get('address', 'N/A')], ['Plan Year:', plan_data.get('year', datetime.now().year)] ] farmer_table = Table(farmer_info, colWidths=[2*inch, 4*inch]) farmer_table.setStyle(TableStyle([ ('FONTNAME', (0, 0), (-1, -1), 'Helvetica'), ('FONTSIZE', (0, 0), (-1, -1), 12), ('TEXTCOLOR', (0, 0), (0, -1), colors.darkblue), ('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'), ('VALIGN', (0, 0), (-1, -1), 'TOP'), ('BOTTOMPADDING', (0, 0), (-1, -1), 8), ])) story.append(farmer_table) story.append(Spacer(1, 20)) # Farm Plans farms = plan_data.get('farms', []) for i, farm in enumerate(farms): story.append(Paragraph(f"Farm {i+1}: {farm.get('farm_name', 'Unknown Farm')}", heading_style)) # Parse farm plan try: farm_plan = json.loads(farm.get('plan', '{}')) if isinstance(farm.get('plan'), str) else farm.get('plan', {}) # Monthly Plan Table if 'monthly_plan' in farm_plan: story.append(Paragraph("Monthly Farming Schedule", styles['Heading3'])) monthly_data = [['Month', 'Planned Activities']] for month_data in farm_plan['monthly_plan']: month = month_data.get('month', '') tasks = month_data.get('tasks', []) tasks_text = '\n'.join(tasks[:3]) # Show max 3 tasks monthly_data.append([month, tasks_text]) monthly_table = Table(monthly_data, colWidths=[1.5*inch, 4.5*inch]) monthly_table.setStyle(TableStyle([ ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'), ('FONTSIZE', (0, 0), (-1, -1), 10), ('BACKGROUND', (0, 0), (-1, 0), colors.lightgreen), ('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke), ('ALIGN', (0, 0), (-1, -1), 'LEFT'), ('VALIGN', (0, 0), (-1, -1), 'TOP'), ('GRID', (0, 0), (-1, -1), 1, colors.black), ('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.lightgrey]), ])) story.append(monthly_table) story.append(Spacer(1, 15)) # Crop Information crops = farm_plan.get('crops', []) if crops: story.append(Paragraph("Crop Details", styles['Heading3'])) crop_data = [['Crop Name', 'Sowing Month', 'Area']] for crop in crops: crop_data.append([ crop.get('name', 'Unknown'), crop.get('sowing_month', 'N/A'), crop.get('area', 'N/A') ]) crop_table = Table(crop_data, colWidths=[2*inch, 2*inch, 2*inch]) crop_table.setStyle(TableStyle([ ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'), ('BACKGROUND', (0, 0), (-1, 0), colors.lightblue), ('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke), ('ALIGN', (0, 0), (-1, -1), 'CENTER'), ('GRID', (0, 0), (-1, -1), 1, colors.black), ])) story.append(crop_table) story.append(Spacer(1, 15)) except Exception as e: logger.error(f"Error processing farm plan: {str(e)}") story.append(Paragraph("Plan details could not be processed", styles['Normal'])) story.append(Spacer(1, 10)) # Summary and Recommendations story.append(Paragraph("Summary & Recommendations", heading_style)) summary_text = plan_data.get('summary', 'Comprehensive yearly plan generated with AI analysis.') story.append(Paragraph(summary_text, styles['Normal'])) story.append(Spacer(1, 10)) # Footer story.append(Spacer(1, 30)) footer_text = f"Generated on: {datetime.now().strftime('%B %d, %Y at %I:%M %p')}" story.append(Paragraph(footer_text, styles['Normal'])) story.append(Paragraph("🌱 Powered by AI Agriculture Assistant", styles['Normal'])) # Build PDF doc.build(story) logger.info(f"Successfully generated PDF: {filepath}") return filepath except Exception as e: logger.error(f"Error generating ReportLab PDF: {str(e)}") return None def _generate_with_html_fallback(self, farmer_data: Dict, plan_data: Dict) -> str: """Generate HTML file as fallback when ReportLab is not available""" try: # Create filename timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") filename = f"yearly_plan_{farmer_data.get('name', 'farmer')}_{timestamp}.html" filepath = os.path.join(self.output_dir, filename) # Generate HTML content html_content = self._create_html_report(farmer_data, plan_data) # Save HTML file with open(filepath, 'w', encoding='utf-8') as f: f.write(html_content) logger.info(f"Successfully generated HTML report: {filepath}") return filepath except Exception as e: logger.error(f"Error generating HTML fallback: {str(e)}") return None def _create_html_report(self, farmer_data: Dict, plan_data: Dict) -> str: """Create HTML report content""" html = f""" Yearly Farming Plan - {farmer_data.get('name', 'Farmer')}

🌾 Comprehensive Yearly Farming Plan

AI-Generated Agricultural Strategy

Farmer Information

Name:
{farmer_data.get('name', 'N/A')}
Contact:
{farmer_data.get('contact_number', 'N/A')}
Address:
{farmer_data.get('address', 'N/A')}
Plan Year:
{plan_data.get('year', datetime.now().year)}
""" # Add farm details farms = plan_data.get('farms', []) for i, farm in enumerate(farms): html += f"""

Farm {i+1}: {farm.get('farm_name', 'Unknown Farm')}

""" # Parse farm plan try: farm_plan = json.loads(farm.get('plan', '{}')) if isinstance(farm.get('plan'), str) else farm.get('plan', {}) # Monthly Plan if 'monthly_plan' in farm_plan: html += """

Monthly Farming Schedule

""" for month_data in farm_plan['monthly_plan']: month = month_data.get('month', '') tasks = month_data.get('tasks', []) tasks_html = '
'.join(tasks[:4]) # Show max 4 tasks html += f""" """ html += """
Month Planned Activities
{month} {tasks_html}
""" # Crop Information crops = farm_plan.get('crops', []) if crops: html += """

Crop Details

""" for crop in crops: html += f""" """ html += """
Crop Name Sowing Month Area
{crop.get('name', 'Unknown')} {crop.get('sowing_month', 'N/A')} {crop.get('area', 'N/A')}
""" # AI Generation Note if farm_plan.get('ai_generated'): html += """
🤖 AI-Generated Plan: This plan was created using advanced AI analysis of soil conditions, weather patterns, and agricultural best practices.
""" except Exception as e: html += f"

Plan details could not be processed: {str(e)}

" html += "
\n" # Summary summary_text = plan_data.get('summary', 'Comprehensive yearly plan generated with AI analysis.') html += f"""

Summary & Recommendations

{summary_text}

📱 Next Steps: Access your daily advisories through the farmer dashboard. Monitor weather alerts and market prices for optimal farming decisions.
""" return html def cleanup_old_files(self, days_old: int = 30): """Clean up old generated files""" try: import time cutoff_time = time.time() - (days_old * 24 * 60 * 60) for filename in os.listdir(self.output_dir): filepath = os.path.join(self.output_dir, filename) if os.path.isfile(filepath) and os.path.getctime(filepath) < cutoff_time: os.remove(filepath) logger.info(f"Removed old file: {filename}") except Exception as e: logger.error(f"Error cleaning up old files: {str(e)}")