buildersai / app /services /report_service.py
Kushal
Fix: Sync missing report routes and templates for production
6621cba
Raw
History Blame Contribute Delete
7.63 kB
"""
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."""
@staticmethod
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."
@staticmethod
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()