"""RFP/Brief analysis engine - extract structured requirements from client documents.""" import logging from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.models.project import Project from app.utils.claude_client import call_claude, extract_tool_result logger = logging.getLogger(__name__) ANALYSIS_TOOL = { "name": "save_analysis", "description": "Save the structured RFP/brief analysis.", "input_schema": { "type": "object", "properties": { "summary": { "type": "string", "description": "2-3 sentence executive summary of the brief" }, "objectives": { "type": "array", "items": {"type": "string"}, "description": "Key business objectives and goals" }, "kpis": { "type": "array", "items": {"type": "string"}, "description": "KPIs and success metrics mentioned or implied" }, "channels": { "type": "array", "items": {"type": "string"}, "description": "Marketing/media channels in scope (social, web, print, video, OOH, etc.)" }, "audiences": { "type": "array", "items": {"type": "string"}, "description": "Target audiences or customer segments" }, "deliverable_categories": { "type": "array", "items": {"type": "string"}, "description": "High-level categories of deliverables (e.g. 'Toolbox assets', 'Paid media', 'eCommerce')" }, "constraints": { "type": "array", "items": {"type": "string"}, "description": "Constraints, requirements, or restrictions (timeline, budget, legal, brand, tech)" }, "timeline": { "type": "string", "description": "Timeline or deadline information if mentioned" }, "budget_band": { "type": "string", "description": "Budget range or band if mentioned" }, "missing_info": { "type": "array", "items": { "type": "object", "properties": { "priority": {"type": "string", "enum": ["red", "amber", "green"]}, "category": {"type": "string"}, "question": {"type": "string"}, "rationale": {"type": "string"}, }, "required": ["priority", "category", "question", "rationale"], }, "description": "Discovery questions for missing information, prioritized Red/Amber/Green" }, "complexity_assessment": { "type": "string", "enum": ["low", "medium", "high"], "description": "Overall scope complexity" }, "notes": { "type": "string", "description": "Any other observations or flags for the scoping team" }, }, "required": ["summary", "objectives", "channels", "deliverable_categories", "missing_info", "complexity_assessment"], }, } SYSTEM_PROMPT = """You are an expert creative agency strategist analyzing a client RFP or brief document. Your job is to extract structured requirements that will help the production team scope the work accurately. Be thorough but practical. Focus on what the scoping team needs to know: - What are they actually asking for? - What channels and formats are involved? - What's the volume and complexity? - What information is MISSING that we'd need to scope accurately? For missing_info, prioritize questions: - RED (must-have): Without this, we cannot scope accurately (e.g. primary KPIs, data access, volumes) - AMBER (important): Needed for accurate sizing (e.g. budget, timeline, audience segments) - GREEN (nice-to-have): Would improve the scope but not a blocker (e.g. brand guidelines, vendor preferences) Be specific in your questions - reference the actual document content.""" async def analyze_brief(db: AsyncSession, project: Project, document_text: str) -> dict: """Analyze a client brief/RFP and extract structured requirements. Returns the analysis dict and saves it to the project. """ response = call_claude( system=SYSTEM_PROMPT, user_message=f"Analyze this client brief/RFP and extract structured requirements:\n\n{document_text}", tools=[ANALYSIS_TOOL], tool_choice={"type": "tool", "name": "save_analysis"}, max_tokens=8192, ) usage = getattr(response, '_usage_info', {}) project.ai_input_tokens = (project.ai_input_tokens or 0) + usage.get("input_tokens", 0) project.ai_output_tokens = (project.ai_output_tokens or 0) + usage.get("output_tokens", 0) project.ai_cost_usd = float(project.ai_cost_usd or 0) + usage.get("cost_usd", 0) project.ai_call_count = (project.ai_call_count or 0) + 1 result = extract_tool_result(response) if not result: return {"summary": "Analysis failed - no structured data returned", "missing_info": []} # Store analysis as JSON in project description (or a new field) import json project.brief_analysis = json.dumps(result) await db.commit() return result