Spaces:
Runtime error
Runtime error
| """ | |
| Service for AI-powered report content generation. | |
| """ | |
| from typing import Dict, Optional | |
| import uuid | |
| from app.llm.client import llm_client | |
| class ReportGenerationService: | |
| """Service for generating report content using AI.""" | |
| def generate_section_content( | |
| section_name: str, | |
| context: Dict[str, str] | |
| ) -> str: | |
| """ | |
| Generate content for a specific report section using AI. | |
| Args: | |
| section_name: Name/type of the section (e.g., 'summary', 'recommendations') | |
| context: Dictionary with project/property details for context | |
| Returns: | |
| Generated content for the section | |
| """ | |
| # Build context string | |
| context_str = "\n".join([f"- {k}: {v}" for k, v in context.items() if v]) | |
| # Create section-specific prompts | |
| prompts = { | |
| "summary": f"""Generate a professional executive summary for a construction/property report based on this information: | |
| {context_str} | |
| Write a comprehensive 2-3 paragraph summary that: | |
| - Highlights key project details | |
| - Emphasizes unique selling points | |
| - Uses professional, formal language | |
| - Is suitable for stakeholders and investors | |
| Return ONLY the summary text, no titles or extra formatting:""", | |
| "recommendations": f"""Generate professional recommendations for a construction/property report based on this information: | |
| {context_str} | |
| Provide 3-5 specific, actionable recommendations that: | |
| - Address investment potential | |
| - Cover risk mitigation | |
| - Suggest improvements or considerations | |
| - Use bullet points (•) format | |
| - Are data-driven and practical | |
| Return ONLY the recommendations:""", | |
| "legal_notes": f"""Generate legal compliance notes for a construction/property report based on this information: | |
| {context_str} | |
| Write a professional legal analysis covering: | |
| - Regulatory compliance status | |
| - Required permits and approvals | |
| - Legal clearances | |
| - Compliance recommendations | |
| - 2-3 paragraphs, formal tone | |
| Return ONLY the legal notes:""", | |
| "risk_assessment": f"""Generate a risk assessment section for a construction/property report based on this information: | |
| {context_str} | |
| Provide a comprehensive risk analysis covering: | |
| - Market risks | |
| - Regulatory/legal risks | |
| - Construction/execution risks | |
| - Financial risks | |
| - Risk mitigation strategies | |
| - Use professional language | |
| - 2-3 paragraphs | |
| Return ONLY the risk assessment:""", | |
| "financial_summary": f"""Generate a financial summary for a construction/property report based on this information: | |
| {context_str} | |
| Create a professional financial overview covering: | |
| - Investment requirements | |
| - Revenue projections | |
| - Cost breakdowns | |
| - ROI expectations | |
| - Financial highlights | |
| - 2-3 paragraphs, data-focused | |
| Return ONLY the financial summary:""", | |
| "market_opportunity": f"""Generate a market opportunity analysis for a construction/property report based on this information: | |
| {context_str} | |
| Write a compelling market analysis that: | |
| - Describes market demand | |
| - Highlights growth potential | |
| - Identifies target segments | |
| - Discusses competitive advantages | |
| - 2-3 paragraphs, persuasive yet professional | |
| Return ONLY the market opportunity analysis:""", | |
| "default": f"""Generate professional content for the "{section_name}" section of a construction/property report based on this information: | |
| {context_str} | |
| Write 2-3 professional paragraphs that: | |
| - Are relevant to the section title | |
| - Use formal, business-appropriate language | |
| - Include specific details from the context | |
| - Are suitable for professional reports | |
| Return ONLY the content:""" | |
| } | |
| # Get appropriate prompt | |
| prompt = prompts.get(section_name.lower().replace(' ', '_'), prompts['default']) | |
| try: | |
| # Generate content | |
| content = llm_client.get_completion( | |
| messages=[{"role": "user", "content": prompt}], | |
| temperature=0.7, | |
| max_tokens=500 | |
| ) | |
| return content.strip() | |
| except Exception as e: | |
| print(f"[Report Generation] Error: {e}") | |
| return f"Error generating content for {section_name}. Please try again or edit manually." | |
| def generate_full_pdf( | |
| template_id: str, | |
| data: Dict[str, str], | |
| user_id: Optional[str] = None | |
| ) -> str: | |
| """ | |
| Generate a full PDF report from an HTML template. | |
| Args: | |
| template_id: ID of the template to use | |
| data: Data to populate the template with | |
| user_id: Optional user ID | |
| Returns: | |
| ID of the generated report record | |
| """ | |
| import os | |
| from xhtml2pdf import pisa | |
| from app.database.models import Report | |
| from app.database.connection import SessionLocal | |
| from datetime import datetime | |
| template_map = { | |
| 'property_evaluation': 'property-evaluation.html', | |
| 'investor_pitch_deck': 'investor-pitch-deck.html', | |
| 'legal_compliance': 'legal-compliance.html' | |
| } | |
| template_file = template_map.get(template_id) | |
| if not template_file: | |
| raise ValueError(f"Template {template_id} not found") | |
| # Get absolute path to template | |
| base_dir = os.path.dirname(os.path.dirname(__file__)) | |
| template_path = os.path.join(base_dir, "templates", template_file) | |
| if not os.path.exists(template_path): | |
| raise FileNotFoundError(f"Template file not found at {template_path}") | |
| # Load template | |
| with open(template_path, "r", encoding="utf-8") as f: | |
| template_html = f.read() | |
| # Populate template (simple replacement) | |
| populated_html = template_html | |
| # Add date | |
| today = datetime.now().strftime("%d %b %Y") | |
| populated_html = populated_html.replace("{{DATE}}", today) | |
| # Add data placeholders | |
| for key, value in data.items(): | |
| placeholder = f"{{{{{key.upper()}}}}}" | |
| populated_html = populated_html.replace(placeholder, str(value or "")) | |
| # Remove AI buttons and other non-print elements | |
| populated_html = populated_html.replace('<button class="ai-button"', '<div style="display:none"') | |
| populated_html = populated_html.replace('</button>', '</div>') | |
| # Define output path | |
| reports_dir = os.path.join(os.getcwd(), "data", "generated_reports") | |
| os.makedirs(reports_dir, exist_ok=True) | |
| report_id = str(uuid.uuid4()) | |
| filename = f"{template_id}_{report_id}.pdf" | |
| file_path = os.path.join(reports_dir, filename) | |
| # Generate PDF | |
| with open(file_path, "wb") as pdf_file: | |
| pisa_status = pisa.CreatePDF(populated_html, dest=pdf_file) | |
| if pisa_status.err: | |
| raise RuntimeError(f"PDF generation failed: {pisa_status.err}") | |
| # Save to database | |
| db = SessionLocal() | |
| try: | |
| report_record = Report( | |
| id=report_id, | |
| user_id=user_id, | |
| template_id=template_id, | |
| filename=filename, | |
| file_path=file_path | |
| ) | |
| db.add(report_record) | |
| db.commit() | |
| return report_id | |
| finally: | |
| db.close() | |
| # Global service instance | |
| report_generation_service = ReportGenerationService() | |