venthrive-api / routes /documents.py
GitHub Actions
Sync from GitHub · 2026-10-22 20:43 UTC
89a70e2
Raw History Blame Contribute Delete
8.43 kB
"""
Document Content Generation Route
──────────────────────────────────────────────────────────────────────────────
Generates structured content for Business Plan and Pitch Deck using Groq
with full key rotation. The generated content is saved to Firestore and
returned to the frontend for client-side PDF rendering via jsPDF.
Profile-based documents (BMC, Identity, Customer, Market) do NOT use this
endpoint — they generate PDF directly from profile_output in the frontend.
This endpoint handles only the documents that require AI narrative generation:
- business_plan → 9-section written plan
- pitch_deck → 7-slide pitch outline
"""
import json
from fastapi import APIRouter, HTTPException, Depends
from pydantic import BaseModel
from firebase_admin import firestore
from utils.rate_limiter import with_rate_limit
from utils.groq_client import call_groq_json, HEAVY_MODEL
from utils.pinecone_client import query_book_context
from utils import business_type
router = APIRouter()
class GenerateDocRequest(BaseModel):
userId: str
businessId: str
docType: str # "business_plan" | "pitch_deck"
def _get_bff_and_profile(user_id: str, business_id: str) -> dict:
"""Reads BFF sections + profile_output from Firestore into one context dict."""
db = firestore.client()
biz = (
db.collection("user_data")
.document(user_id)
.collection("businesses")
.document(business_id)
)
context: dict = {}
# BFF sections
for section in ["section1", "section2", "section3"]:
doc = biz.collection("bff").document(section).get()
if doc.exists:
context[section] = doc.to_dict()
# Profile outputs
for key in ["businessIdentity", "customerProfile", "marketData", "businessModel"]:
doc = biz.collection("profile_output").document(key).get()
if doc.exists:
context[key] = doc.to_dict()
return context
# ─── Business Plan ────────────────────────────────────────────────────────────
def _generate_business_plan(ctx: dict, book_context: str) -> dict:
system = (
"You are a professional business consultant specializing in Nigerian MSMEs. "
"Write a complete, practical Business Plan for this entrepreneur. "
"Use plain English — no jargon. Every section must be actionable and specific "
"to the Nigerian market. Return ONLY valid JSON.\n\n"
+ (book_context or "")
)
prompt = f"""
Business context:
{json.dumps(ctx, indent=2)[:3000]}
Generate a comprehensive Business Plan. Return JSON:
{{
"executiveSummary": "2-3 paragraphs summarizing the business, opportunity, and goals",
"businessOpportunity": "The problem being solved and why now",
"companyDescription": "What the business does, its mission, and legal structure",
"targetMarket": "Detailed description of the target market in Nigeria",
"productsServices": "What is being sold, key features, pricing approach",
"marketingStrategy": "How the business will reach and retain customers",
"operationsPlan": "Day-to-day operations, location, equipment, team",
"financialSummary": "Startup costs, expected revenue, break-even estimate",
"implementationTimeline": "Key milestones for the first 12 months"
}}"""
return call_groq_json(system, prompt, model=HEAVY_MODEL, max_tokens=2500, fixture="business_plan")
# ─── Pitch Deck ────────────────────────────────────────────────────────────────
def _generate_pitch_deck(ctx: dict, book_context: str) -> dict:
system = (
"You are a venture capital pitch consultant. "
"Create a compelling 7-slide investor pitch deck for this Nigerian entrepreneur. "
"Be concise — each slide has 3-4 punchy bullet points. "
"Ground everything in real Nigerian market data where possible. "
"Return ONLY valid JSON.\n\n"
+ (book_context or "")
)
prompt = f"""
Business context:
{json.dumps(ctx, indent=2)[:3000]}
Generate a 7-slide Pitch Deck. Return JSON where each key is a slide:
{{
"slide1_problem": {{
"title": "The Problem",
"bullets": ["...", "...", "..."]
}},
"slide2_solution": {{
"title": "Our Solution",
"bullets": ["...", "...", "..."]
}},
"slide3_market": {{
"title": "Market Opportunity",
"bullets": ["TAM...", "SAM...", "SOM..."]
}},
"slide4_business_model": {{
"title": "How We Make Money",
"bullets": ["...", "...", "..."]
}},
"slide5_traction": {{
"title": "Traction & Milestones",
"bullets": ["...", "...", "..."]
}},
"slide6_strategy": {{
"title": "Go-To-Market Strategy",
"bullets": ["...", "...", "..."]
}},
"slide7_ask": {{
"title": "The Ask",
"bullets": ["Seeking: ...", "Use of funds: ...", "Expected outcome: ..."]
}}
}}"""
return call_groq_json(system, prompt, model=HEAVY_MODEL, max_tokens=2000, fixture="pitch_deck")
# ─── Route ────────────────────────────────────────────────────────────────────
@router.post("/generate-content")
def generate_document_content(
body: GenerateDocRequest,
user: dict = Depends(with_rate_limit("generation")),
):
"""
Generates AI content for Business Plan or Pitch Deck.
Saves the result to Firestore and returns the document ID.
PDF rendering happens client-side in the browser.
"""
if user.get("uid") != body.userId:
raise HTTPException(status_code=403, detail="Forbidden.")
if body.docType not in ("business_plan", "pitch_deck"):
raise HTTPException(
status_code=400,
detail="docType must be 'business_plan' or 'pitch_deck'",
)
# ── Read context ─────────────────────────────────────────────────────────
ctx = _get_bff_and_profile(body.userId, body.businessId)
if not ctx:
raise HTTPException(status_code=404, detail="Business profile not found.")
# ── BFF Philosophy context from Pinecone ──────────────────────────────────
t = business_type.describe(ctx.get("section1", {}))
query = f"business plan entrepreneurship Nigeria {t['subsector']} {t['niche']}"
book_context = query_book_context(query, top_k=3)
# ── Generate ──────────────────────────────────────────────────────────────
try:
if body.docType == "business_plan":
content = _generate_business_plan(ctx, book_context)
doc_name = f"{ctx.get('businessIdentity', {}).get('businessName', 'Business')}_Plan"
else:
content = _generate_pitch_deck(ctx, book_context)
doc_name = f"{ctx.get('businessIdentity', {}).get('businessName', 'Business')}_PitchDeck"
except Exception as e:
raise HTTPException(status_code=500, detail=f"Generation failed: {str(e)}")
# ── Save to Firestore ─────────────────────────────────────────────────────
from google.cloud.firestore_v1 import SERVER_TIMESTAMP
db = firestore.client()
biz = (
db.collection("user_data")
.document(body.userId)
.collection("businesses")
.document(body.businessId)
)
doc_ref = biz.collection("documents").add({
"name": doc_name,
"type": body.docType,
"dataSnapshot": content,
"createdAt": SERVER_TIMESTAMP,
"status": "ready",
})
doc_id = doc_ref[1].id
return {
"success": True,
"docId": doc_id,
"dataSnapshot": content,
}