AI Analysis and Content Strategy fixes. Enhanced Strategy Routes refactoring.
This commit is contained in:
237
backend/api/research/models.py
Normal file
237
backend/api/research/models.py
Normal file
@@ -0,0 +1,237 @@
|
||||
"""
|
||||
Research API Models
|
||||
|
||||
All Pydantic request/response models for research endpoints.
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional, List, Dict, Any
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Research Execution Models
|
||||
# ============================================================================
|
||||
|
||||
class ResearchRequest(BaseModel):
|
||||
"""API request for research."""
|
||||
query: str = Field(..., description="Main research query or topic")
|
||||
keywords: List[str] = Field(default_factory=list, description="Additional keywords")
|
||||
|
||||
# Research configuration
|
||||
goal: Optional[str] = Field(default="factual", description="Research goal: factual, trending, competitive, etc.")
|
||||
depth: Optional[str] = Field(default="standard", description="Research depth: quick, standard, comprehensive, expert")
|
||||
provider: Optional[str] = Field(default="auto", description="Provider preference: auto, exa, tavily, google")
|
||||
|
||||
# Personalization
|
||||
content_type: Optional[str] = Field(default="general", description="Content type: blog, podcast, video, etc.")
|
||||
industry: Optional[str] = None
|
||||
target_audience: Optional[str] = None
|
||||
tone: Optional[str] = None
|
||||
|
||||
# Constraints
|
||||
max_sources: int = Field(default=10, ge=1, le=25)
|
||||
recency: Optional[str] = None # day, week, month, year
|
||||
|
||||
# Domain filtering
|
||||
include_domains: List[str] = Field(default_factory=list)
|
||||
exclude_domains: List[str] = Field(default_factory=list)
|
||||
|
||||
# Advanced mode
|
||||
advanced_mode: bool = False
|
||||
|
||||
# Raw provider parameters (only if advanced_mode=True)
|
||||
exa_category: Optional[str] = None
|
||||
exa_search_type: Optional[str] = None
|
||||
tavily_topic: Optional[str] = None
|
||||
tavily_search_depth: Optional[str] = None
|
||||
tavily_include_answer: bool = False
|
||||
tavily_time_range: Optional[str] = None
|
||||
|
||||
|
||||
class ResearchResponse(BaseModel):
|
||||
"""API response for research."""
|
||||
success: bool
|
||||
task_id: Optional[str] = None # For async requests
|
||||
|
||||
# Results (if synchronous)
|
||||
sources: List[Dict[str, Any]] = Field(default_factory=list)
|
||||
keyword_analysis: Dict[str, Any] = Field(default_factory=dict)
|
||||
competitor_analysis: Dict[str, Any] = Field(default_factory=dict)
|
||||
suggested_angles: List[str] = Field(default_factory=list)
|
||||
|
||||
# Metadata
|
||||
provider_used: Optional[str] = None
|
||||
search_queries: List[str] = Field(default_factory=list)
|
||||
|
||||
# Error handling
|
||||
error_message: Optional[str] = None
|
||||
error_code: Optional[str] = None
|
||||
|
||||
|
||||
class ProviderStatusResponse(BaseModel):
|
||||
"""Response for provider status check."""
|
||||
exa: Dict[str, Any]
|
||||
tavily: Dict[str, Any]
|
||||
google: Dict[str, Any]
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Intent-Driven Research Models
|
||||
# ============================================================================
|
||||
|
||||
class AnalyzeIntentRequest(BaseModel):
|
||||
"""Request to analyze user research intent."""
|
||||
user_input: str = Field(..., description="User's keywords, question, or goal")
|
||||
keywords: List[str] = Field(default_factory=list, description="Extracted keywords")
|
||||
use_persona: bool = Field(True, description="Use research persona for context")
|
||||
use_competitor_data: bool = Field(True, description="Use competitor data for context")
|
||||
# User-provided intent settings (optional - if provided, use these instead of inferring)
|
||||
user_provided_purpose: Optional[str] = Field(None, description="User-selected purpose (learn, create_content, etc.)")
|
||||
user_provided_content_output: Optional[str] = Field(None, description="User-selected content output (blog, podcast, etc.)")
|
||||
user_provided_depth: Optional[str] = Field(None, description="User-selected depth (overview, detailed, expert)")
|
||||
|
||||
|
||||
class AnalyzeIntentResponse(BaseModel):
|
||||
"""Response from intent analysis with optimized provider parameters."""
|
||||
success: bool
|
||||
intent: Dict[str, Any]
|
||||
analysis_summary: str
|
||||
suggested_queries: List[Dict[str, Any]]
|
||||
suggested_keywords: List[str]
|
||||
suggested_angles: List[str]
|
||||
quick_options: List[Dict[str, Any]]
|
||||
confidence_reason: Optional[str] = None
|
||||
great_example: Optional[str] = None
|
||||
error_message: Optional[str] = None
|
||||
|
||||
# Unified: Optimized provider parameters based on intent
|
||||
optimized_config: Optional[Dict[str, Any]] = None # Provider settings auto-configured from intent
|
||||
recommended_provider: Optional[str] = None # Best provider for this intent (exa, tavily, google)
|
||||
|
||||
# Google Trends configuration (if trends in deliverables)
|
||||
trends_config: Optional[Dict[str, Any]] = None # Trends keywords and settings with justifications
|
||||
|
||||
|
||||
class IntentDrivenResearchRequest(BaseModel):
|
||||
"""Request for intent-driven research."""
|
||||
# Intent from previous analyze step, or minimal input for auto-inference
|
||||
user_input: str = Field(..., description="User's original input")
|
||||
|
||||
# Optional: Confirmed intent from UI (if user modified the inferred intent)
|
||||
confirmed_intent: Optional[Dict[str, Any]] = None
|
||||
|
||||
# Optional: Specific queries to run (if user selected from suggested)
|
||||
selected_queries: Optional[List[Dict[str, Any]]] = None
|
||||
|
||||
# Research configuration
|
||||
max_sources: int = Field(default=10, ge=1, le=25)
|
||||
include_domains: List[str] = Field(default_factory=list)
|
||||
exclude_domains: List[str] = Field(default_factory=list)
|
||||
|
||||
# Google Trends configuration (from intent analysis)
|
||||
trends_config: Optional[Dict[str, Any]] = None # Trends keywords and settings
|
||||
|
||||
# Skip intent inference (for re-runs with same intent)
|
||||
skip_inference: bool = False
|
||||
|
||||
|
||||
class IntentDrivenResearchResponse(BaseModel):
|
||||
"""Response from intent-driven research."""
|
||||
success: bool
|
||||
|
||||
# Direct answers
|
||||
primary_answer: str = ""
|
||||
secondary_answers: Dict[str, Optional[str]] = Field(default_factory=dict)
|
||||
focus_areas_coverage: Dict[str, Optional[str]] = Field(default_factory=dict)
|
||||
also_answering_coverage: Dict[str, Optional[str]] = Field(default_factory=dict)
|
||||
|
||||
# Deliverables
|
||||
statistics: List[Dict[str, Any]] = Field(default_factory=list)
|
||||
expert_quotes: List[Dict[str, Any]] = Field(default_factory=list)
|
||||
case_studies: List[Dict[str, Any]] = Field(default_factory=list)
|
||||
trends: List[Dict[str, Any]] = Field(default_factory=list)
|
||||
comparisons: List[Dict[str, Any]] = Field(default_factory=list)
|
||||
best_practices: List[str] = Field(default_factory=list)
|
||||
step_by_step: List[str] = Field(default_factory=list)
|
||||
pros_cons: Optional[Dict[str, Any]] = None
|
||||
definitions: Dict[str, str] = Field(default_factory=dict)
|
||||
examples: List[str] = Field(default_factory=list)
|
||||
predictions: List[str] = Field(default_factory=list)
|
||||
|
||||
# Content-ready outputs
|
||||
executive_summary: str = ""
|
||||
key_takeaways: List[str] = Field(default_factory=list)
|
||||
suggested_outline: List[str] = Field(default_factory=list)
|
||||
|
||||
# Sources and metadata
|
||||
sources: List[Dict[str, Any]] = Field(default_factory=list)
|
||||
confidence: float = 0.8
|
||||
gaps_identified: List[str] = Field(default_factory=list)
|
||||
follow_up_queries: List[str] = Field(default_factory=list)
|
||||
intent: Optional[Dict[str, Any]] = None
|
||||
google_trends_data: Optional[Dict[str, Any]] = None
|
||||
error_message: Optional[str] = None
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Research Project Models
|
||||
# ============================================================================
|
||||
|
||||
class SaveResearchProjectRequest(BaseModel):
|
||||
"""Request to save a research project to database."""
|
||||
project_id: Optional[str] = Field(None, description="Project ID for updates (optional, auto-generated if not provided)")
|
||||
title: Optional[str] = Field(None, description="Project title")
|
||||
keywords: List[str] = Field(..., description="Research keywords")
|
||||
industry: str = Field(..., description="Industry")
|
||||
target_audience: str = Field(..., description="Target audience")
|
||||
research_mode: str = Field(..., description="Research mode (comprehensive, targeted, basic)")
|
||||
config: Dict[str, Any] = Field(..., description="Research configuration")
|
||||
intent_analysis: Optional[Dict[str, Any]] = Field(None, description="Intent analysis result")
|
||||
confirmed_intent: Optional[Dict[str, Any]] = Field(None, description="Confirmed research intent")
|
||||
intent_result: Optional[Dict[str, Any]] = Field(None, description="Intent-driven research result")
|
||||
legacy_result: Optional[Dict[str, Any]] = Field(None, description="Legacy research result")
|
||||
current_step: int = Field(1, description="Current wizard step")
|
||||
description: Optional[str] = Field(None, description="Project description")
|
||||
|
||||
|
||||
class SaveResearchProjectResponse(BaseModel):
|
||||
"""Response after saving research project."""
|
||||
success: bool
|
||||
asset_id: Optional[int] = None # Database ID (for backward compatibility)
|
||||
project_id: Optional[str] = None # Project UUID (for lookups)
|
||||
message: str
|
||||
|
||||
|
||||
class ResearchProjectResponse(BaseModel):
|
||||
"""Response model for research project."""
|
||||
id: int
|
||||
project_id: str
|
||||
user_id: str
|
||||
title: Optional[str] = None
|
||||
keywords: List[str]
|
||||
industry: Optional[str] = None
|
||||
target_audience: Optional[str] = None
|
||||
research_mode: Optional[str] = None
|
||||
config: Optional[Dict[str, Any]] = None
|
||||
intent_analysis: Optional[Dict[str, Any]] = None
|
||||
confirmed_intent: Optional[Dict[str, Any]] = None
|
||||
intent_result: Optional[Dict[str, Any]] = None
|
||||
legacy_result: Optional[Dict[str, Any]] = None
|
||||
trends_config: Optional[Dict[str, Any]] = None
|
||||
current_step: int = 1
|
||||
status: str = "draft"
|
||||
is_favorite: bool = False
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class ResearchProjectListResponse(BaseModel):
|
||||
"""Response model for listing research projects."""
|
||||
projects: List[ResearchProjectResponse]
|
||||
total: int
|
||||
limit: int
|
||||
offset: int
|
||||
Reference in New Issue
Block a user