customized_farm_planner / pdf_generator_service.py
pranit144's picture
Upload 56 files
429a26d verified
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"""
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Yearly Farming Plan - {farmer_data.get('name', 'Farmer')}</title>
<style>
body {{
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
margin: 20px;
background-color: #f9f9f9;
}}
.container {{
max-width: 800px;
margin: 0 auto;
background: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 0 20px rgba(0,0,0,0.1);
}}
.header {{
text-align: center;
color: #2c5530;
border-bottom: 3px solid #4CAF50;
padding-bottom: 20px;
margin-bottom: 30px;
}}
.section {{
margin-bottom: 30px;
}}
.section h2 {{
color: #1976d2;
border-left: 4px solid #4CAF50;
padding-left: 15px;
}}
table {{
width: 100%;
border-collapse: collapse;
margin: 15px 0;
}}
th, td {{
padding: 12px;
text-align: left;
border: 1px solid #ddd;
}}
th {{
background-color: #4CAF50;
color: white;
}}
tr:nth-child(even) {{
background-color: #f2f2f2;
}}
.info-grid {{
display: grid;
grid-template-columns: 1fr 2fr;
gap: 10px;
margin: 15px 0;
}}
.info-label {{
font-weight: bold;
color: #333;
}}
.footer {{
text-align: center;
margin-top: 40px;
padding-top: 20px;
border-top: 2px solid #eee;
color: #666;
}}
.highlight {{
background-color: #fff3cd;
padding: 15px;
border-left: 4px solid #ffc107;
margin: 15px 0;
}}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🌾 Comprehensive Yearly Farming Plan</h1>
<p>AI-Generated Agricultural Strategy</p>
</div>
<div class="section">
<h2>Farmer Information</h2>
<div class="info-grid">
<div class="info-label">Name:</div>
<div>{farmer_data.get('name', 'N/A')}</div>
<div class="info-label">Contact:</div>
<div>{farmer_data.get('contact_number', 'N/A')}</div>
<div class="info-label">Address:</div>
<div>{farmer_data.get('address', 'N/A')}</div>
<div class="info-label">Plan Year:</div>
<div>{plan_data.get('year', datetime.now().year)}</div>
</div>
</div>
"""
# Add farm details
farms = plan_data.get('farms', [])
for i, farm in enumerate(farms):
html += f"""
<div class="section">
<h2>Farm {i+1}: {farm.get('farm_name', 'Unknown Farm')}</h2>
"""
# 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 += """
<h3>Monthly Farming Schedule</h3>
<table>
<thead>
<tr>
<th>Month</th>
<th>Planned Activities</th>
</tr>
</thead>
<tbody>
"""
for month_data in farm_plan['monthly_plan']:
month = month_data.get('month', '')
tasks = month_data.get('tasks', [])
tasks_html = '<br>'.join(tasks[:4]) # Show max 4 tasks
html += f"""
<tr>
<td><strong>{month}</strong></td>
<td>{tasks_html}</td>
</tr>
"""
html += """
</tbody>
</table>
"""
# Crop Information
crops = farm_plan.get('crops', [])
if crops:
html += """
<h3>Crop Details</h3>
<table>
<thead>
<tr>
<th>Crop Name</th>
<th>Sowing Month</th>
<th>Area</th>
</tr>
</thead>
<tbody>
"""
for crop in crops:
html += f"""
<tr>
<td>{crop.get('name', 'Unknown')}</td>
<td>{crop.get('sowing_month', 'N/A')}</td>
<td>{crop.get('area', 'N/A')}</td>
</tr>
"""
html += """
</tbody>
</table>
"""
# AI Generation Note
if farm_plan.get('ai_generated'):
html += """
<div class="highlight">
<strong>🤖 AI-Generated Plan:</strong> This plan was created using advanced AI analysis of soil conditions, weather patterns, and agricultural best practices.
</div>
"""
except Exception as e:
html += f"<p><em>Plan details could not be processed: {str(e)}</em></p>"
html += " </div>\n"
# Summary
summary_text = plan_data.get('summary', 'Comprehensive yearly plan generated with AI analysis.')
html += f"""
<div class="section">
<h2>Summary & Recommendations</h2>
<p>{summary_text}</p>
<div class="highlight">
<strong>📱 Next Steps:</strong> Access your daily advisories through the farmer dashboard. Monitor weather alerts and market prices for optimal farming decisions.
</div>
</div>
<div class="footer">
<p>Generated on: {datetime.now().strftime('%B %d, %Y at %I:%M %p')}</p>
<p>🌱 <strong>Powered by AI Agriculture Assistant</strong></p>
<p><em>This plan is generated based on AI analysis and should be used in conjunction with local agricultural expertise.</em></p>
</div>
</div>
</body>
</html>
"""
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)}")