""" Planning AI Suggestions ────────────────────────────────────────────────────────────────────────────── Reads the business profile and generates actionable task / milestone suggestions grounded in the BFF data and "Built on Rock" philosophy. Routes: POST /api/planning/suggest → task or milestone suggestions """ import json from datetime import datetime, timedelta 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 SuggestRequest(BaseModel): userId: str businessId: str type: str # "tasks" | "milestones" existing: list[str] = [] # existing task/milestone titles (to avoid duplicates) def _read_context(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", "marketData", "businessModel"]: doc = biz.collection("profile_output").document(key).get() if doc.exists: ctx[key] = doc.to_dict() s1 = biz.collection("bff").document("section1").get() if s1.exists: ctx["section1"] = s1.to_dict() return ctx @router.post("/suggest") def suggest_planning_items( body: SuggestRequest, user: dict = Depends(with_rate_limit("generation")), ): if body.type not in ("tasks", "milestones"): raise HTTPException(status_code=400, detail="type must be 'tasks' or 'milestones'") ctx = _read_context(body.userId, body.businessId) biz_name = ctx.get("businessIdentity", {}).get("businessName", "this business") sector = ctx.get("businessIdentity", {}).get("sector", "") value_prop = ctx.get("businessIdentity", {}).get("valueProposition", "") summary = ctx.get("businessIdentity", {}).get("summary", "") book_ctx = query_book_context( f"action plan milestones tasks Nigerian entrepreneur {sector}", top_k=2 ) existing_str = ( f"\nDo NOT duplicate any of these existing items: {body.existing}" if body.existing else "" ) system = ( "You are a business consultant helping a Nigerian entrepreneur plan their next steps. " "Suggest practical, specific, achievable actions grounded in the Business Footing Framework. " "All suggestions must be relevant to their specific business — no generic advice. " "Write in plain English. Return ONLY valid JSON." + (f"\n\n{book_ctx}" if book_ctx else "") ) if body.type == "tasks": prompt = f""" Business: {biz_name} ({sector}) Value proposition: {value_prop} Summary: {summary[:400]} {existing_str} Generate exactly 5 actionable Kanban tasks for this business right now. Tasks should be specific, achievable within 2 weeks, and move the business forward. Spread them across different priorities: some for this week, some for next week. Return JSON: {{ "suggestions": [ {{ "title": "specific task title", "description": "why this matters for {biz_name}", "column": "todo" }}, ... ] }} column must be one of: "todo", "in_progress", "done" Most tasks should be "todo" unless clearly already started. """ else: # milestones today = datetime.now() dates = [(today + timedelta(days=d)).strftime("%Y-%m-%d") for d in [30, 60, 90, 180, 365]] prompt = f""" Business: {biz_name} ({sector}) Value proposition: {value_prop} Summary: {summary[:400]} {existing_str} Generate exactly 5 business milestones for the next 12 months. Milestones should be measurable achievements that mark significant progress. Space them across different timeframes. Available target dates to choose from: {dates} (Choose the most appropriate date for each milestone) Return JSON: {{ "suggestions": [ {{ "title": "milestone title (specific and measurable)", "targetDate": "YYYY-MM-DD" }}, ... ] }} """ try: result = call_groq_json( system, prompt, model=HEAVY_MODEL, max_tokens=800, fixture=f"planning_{body.type}", ) suggestions = result.get("suggestions", []) if not suggestions: raise ValueError("No suggestions returned") return {"suggestions": suggestions} except Exception as e: raise HTTPException(status_code=500, detail=f"Suggestion generation failed: {str(e)}")