Spaces:
Running
Running
File size: 8,426 Bytes
e561008 889e86b e561008 89a70e2 e561008 020705e e561008 020705e e561008 889e86b e561008 89a70e2 e561008 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 | """
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,
}
|