venthrive-api / routes /consultation.py
GitHub Actions
Sync from GitHub · 2026-09-26 23:46 UTC
020705e
Raw History Blame Contribute Delete
6.72 kB
"""
AI Consultation Session Routes
──────────────────────────────────────────────────────────────────────────────
Manages proactive AI Consultation sessions distinct from passive AI Chat.
AI Chat: passive — waits for user questions
Consultation: proactive — AI opens with agenda, leads through structured phases
(Discovery → Analysis → Prescription) and produces a written report
Routes:
POST /api/consultation/start → AI reads profile, generates opening message
POST /api/consultation/report → AI generates structured report from session
"""
import json
from datetime import datetime
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
router = APIRouter()
class StartRequest(BaseModel):
userId: str
businessId: str
class ReportRequest(BaseModel):
userId: str
businessId: str
messages: list[dict] # [{"role": "user"|"assistant", "content": "..."}]
def _read_profile(user_id: str, business_id: str) -> dict:
db = firestore.client()
biz = (
db.collection("user_data").document(user_id)
.collection("businesses").document(business_id)
)
ctx: dict = {}
for key in ["businessIdentity", "customerProfile", "marketData"]:
doc = biz.collection("profile_output").document(key).get()
if doc.exists:
ctx[key] = doc.to_dict()
return ctx
@router.post("/start")
def start_consultation(
body: StartRequest,
user: dict = Depends(with_rate_limit("generation")),
):
"""
Reads the entrepreneur's business profile and generates a warm, structured
opening message with 5 focus areas. The AI has already 'read the brief'
so the session starts with context rather than from scratch.
"""
profile = _read_profile(body.userId, body.businessId)
identity = profile.get("businessIdentity", {})
biz_name = identity.get("businessName", "your business")
sector = identity.get("sector", "")
value_prop = identity.get("valueProposition", "")
summary = identity.get("summary", "")
book_ctx = query_book_context(
"business consultation strategy discovery analysis Nigerian entrepreneur",
top_k=2,
)
system = (
"You are a senior business consultant at VenThrive, trained in the "
"Business Footing Framework from 'Built on Rock' by Iyanuoluwa Joseph Akinnusi. "
"You are warm, direct, and speak in plain everyday English. "
"You do NOT use business jargon. You speak like a trusted advisor, not a textbook. "
"Return ONLY valid JSON."
+ (f"\n\n{book_ctx}" if book_ctx else "")
)
prompt = f"""
You are starting a consultation session with the founder of "{biz_name}"
({sector}). Their value proposition is: "{value_prop}".
Brief summary: {summary[:300]}
Generate a warm, professional opening message that:
1. Greets them by referencing their specific business (not generic)
2. Mentions you've reviewed their Business Footing Framework profile
3. Presents exactly 5 focus areas as a numbered list
4. Asks them to pick the one they want to focus on today
The 5 focus areas should be:
1. Strategy & Market Position
2. Customer Acquisition & Retention
3. Financial Planning & Pricing
4. Operations & Standard Processes
5. Investor Readiness & Growth
Return JSON:
{{
"openingMessage": "<your full opening message here>",
"focusAreas": ["Strategy & Market Position", "Customer Acquisition & Retention", "Financial Planning & Pricing", "Operations & Standard Processes", "Investor Readiness & Growth"]
}}"""
try:
result = call_groq_json(system, prompt, model=HEAVY_MODEL, max_tokens=800, fixture="consultation_start")
return {
"openingMessage": result.get("openingMessage", ""),
"focusAreas": result.get("focusAreas", []),
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Could not start consultation: {str(e)}")
@router.post("/report")
def generate_report(
body: ReportRequest,
user: dict = Depends(with_rate_limit("generation")),
):
"""
Takes the full session transcript and generates a structured consultation
report with findings, specific recommendations, and next steps.
"""
if user.get("uid") != body.userId:
raise HTTPException(status_code=403, detail="Forbidden.")
if len(body.messages) < 2:
raise HTTPException(status_code=400, detail="Session too short to generate a report.")
transcript = "\n".join([
f"{'CONSULTANT' if m['role'] == 'assistant' else 'ENTREPRENEUR'}: {m['content']}"
for m in body.messages
])
book_ctx = query_book_context(
"consultation findings recommendations action plan Nigerian business",
top_k=2,
)
system = (
"You are a business consultant writing a formal post-consultation report. "
"Analyze the conversation and produce a structured, actionable document. "
"Be specific to what was actually discussed — no generic advice. "
"Write in clear, plain English. "
"Return ONLY valid JSON."
+ (f"\n\n{book_ctx}" if book_ctx else "")
)
prompt = f"""
Consultation transcript:
{transcript[:4000]}
Today's date: {datetime.now().strftime('%B %d, %Y')}
Write a formal consultation report. Return JSON:
{{
"date": "{datetime.now().strftime('%B %d, %Y')}",
"sessionSummary": "2-3 sentences summarising what was discussed and the main concern addressed",
"keyFindings": [
"Specific finding 1 from the conversation",
"Specific finding 2",
"Specific finding 3"
],
"recommendations": [
{{
"action": "What to do",
"rationale": "Why this matters for their specific business",
"timeline": "When to do it (e.g. This week / Within 30 days)"
}}
],
"nextSteps": [
"Concrete action step 1",
"Concrete action step 2",
"Concrete action step 3"
],
"closingNote": "A personalised, encouraging closing sentence referencing their business."
}}"""
try:
result = call_groq_json(system, prompt, model=HEAVY_MODEL, max_tokens=2000, fixture="consultation_report")
return {"report": result}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Report generation failed: {str(e)}")