Recovered state: integrated TrendSurferAgent, restored frontend/backend files, and cleaned up recovery scripts
This commit is contained in:
@@ -5,10 +5,10 @@ This package contains all onboarding-related services and utilities.
|
||||
All onboarding data is stored in the database with proper user isolation.
|
||||
|
||||
Services:
|
||||
- OnboardingDatabaseService: Core database operations for onboarding data
|
||||
- OnboardingDataIntegrationService: Canonical SSOT for onboarding data
|
||||
- OnboardingProgressService: Progress tracking and step management
|
||||
- OnboardingDataService: Data validation and processing
|
||||
- OnboardingProgress: Progress tracking with database persistence (from api_key_manager)
|
||||
- APIKeyManager: API key management
|
||||
|
||||
|
||||
Architecture:
|
||||
- Database-first: All data stored in PostgreSQL with proper foreign keys
|
||||
@@ -18,15 +18,11 @@ Architecture:
|
||||
"""
|
||||
|
||||
# Import all public classes for easy access
|
||||
from .database_service import OnboardingDatabaseService
|
||||
from .progress_service import OnboardingProgressService
|
||||
from .data_service import OnboardingDataService
|
||||
from .api_key_manager import OnboardingProgress, APIKeyManager, get_onboarding_progress, get_user_onboarding_progress, get_onboarding_progress_for_user
|
||||
|
||||
__all__ = [
|
||||
'OnboardingDatabaseService',
|
||||
'OnboardingProgressService',
|
||||
'OnboardingDataService',
|
||||
'OnboardingProgress',
|
||||
'APIKeyManager',
|
||||
'get_onboarding_progress',
|
||||
|
||||
@@ -11,7 +11,8 @@ from datetime import datetime
|
||||
from loguru import logger
|
||||
from enum import Enum
|
||||
|
||||
from services.database import get_db_session
|
||||
from services.database import get_session_for_user
|
||||
from models.onboarding import OnboardingSession, APIKey, WebsiteAnalysis, ResearchPreferences, PersonaData
|
||||
|
||||
|
||||
class StepStatus(Enum):
|
||||
@@ -50,15 +51,16 @@ class OnboardingProgress:
|
||||
|
||||
# Initialize database service for persistence
|
||||
try:
|
||||
from .database_service import OnboardingDatabaseService
|
||||
self.db_service = OnboardingDatabaseService()
|
||||
from api.content_planning.services.content_strategy.onboarding import OnboardingDataIntegrationService
|
||||
self.integration_service = OnboardingDataIntegrationService()
|
||||
self.use_database = True
|
||||
logger.info(f"Database service initialized for user {user_id}")
|
||||
logger.info(f"Database/Integration service initialized for user {user_id}")
|
||||
except Exception as e:
|
||||
logger.error(f"Database service not available: {e}")
|
||||
self.db_service = None
|
||||
self.integration_service = None
|
||||
self.use_database = False
|
||||
raise Exception(f"Database service required but not available: {e}")
|
||||
# raise Exception(f"Database service required but not available: {e}") # Don't raise, fallback gracefully if possible
|
||||
|
||||
|
||||
# Load existing progress from database if available
|
||||
if self.use_database and self.user_id:
|
||||
@@ -219,23 +221,136 @@ class OnboardingProgress:
|
||||
self.save_progress()
|
||||
logger.info("Onboarding completed successfully")
|
||||
|
||||
def _save_api_key_to_db(self, db, provider: str, key: str):
|
||||
"""Save API key to database."""
|
||||
try:
|
||||
api_key_record = db.query(APIKey).filter(
|
||||
APIKey.user_id == self.user_id,
|
||||
APIKey.provider == provider
|
||||
).first()
|
||||
|
||||
if not api_key_record:
|
||||
api_key_record = APIKey(
|
||||
user_id=self.user_id,
|
||||
provider=provider,
|
||||
api_key=key,
|
||||
is_active=True,
|
||||
created_at=datetime.utcnow()
|
||||
)
|
||||
db.add(api_key_record)
|
||||
else:
|
||||
api_key_record.api_key = key
|
||||
api_key_record.updated_at = datetime.utcnow()
|
||||
|
||||
db.commit()
|
||||
except Exception as e:
|
||||
logger.error(f"Error saving API key to DB: {e}")
|
||||
# db.rollback() # Handled by outer try/except
|
||||
|
||||
def _save_website_analysis_to_db(self, db, analysis_data: Dict[str, Any]):
|
||||
"""Save website analysis to database."""
|
||||
try:
|
||||
# Get session ID
|
||||
session = db.query(OnboardingSession).filter(OnboardingSession.user_id == self.user_id).first()
|
||||
if not session:
|
||||
logger.warning(f"No session found for user {self.user_id} when saving website analysis")
|
||||
return
|
||||
|
||||
analysis = db.query(WebsiteAnalysis).filter(WebsiteAnalysis.session_id == session.id).first()
|
||||
|
||||
# Filter valid columns only to avoid errors
|
||||
valid_cols = WebsiteAnalysis.__table__.columns.keys()
|
||||
filtered_data = {k: v for k, v in analysis_data.items() if k in valid_cols}
|
||||
|
||||
if not analysis:
|
||||
analysis = WebsiteAnalysis(session_id=session.id, **filtered_data)
|
||||
db.add(analysis)
|
||||
else:
|
||||
for k, v in filtered_data.items():
|
||||
setattr(analysis, k, v)
|
||||
analysis.updated_at = datetime.utcnow()
|
||||
|
||||
db.commit()
|
||||
except Exception as e:
|
||||
logger.error(f"Error saving website analysis to DB: {e}")
|
||||
|
||||
def _save_research_preferences_to_db(self, db, prefs_data: Dict[str, Any]):
|
||||
"""Save research preferences to database."""
|
||||
try:
|
||||
session = db.query(OnboardingSession).filter(OnboardingSession.user_id == self.user_id).first()
|
||||
if not session:
|
||||
return
|
||||
|
||||
prefs = db.query(ResearchPreferences).filter(ResearchPreferences.session_id == session.id).first()
|
||||
|
||||
valid_cols = ResearchPreferences.__table__.columns.keys()
|
||||
filtered_data = {k: v for k, v in prefs_data.items() if k in valid_cols}
|
||||
|
||||
if not prefs:
|
||||
prefs = ResearchPreferences(session_id=session.id, **filtered_data)
|
||||
db.add(prefs)
|
||||
else:
|
||||
for k, v in filtered_data.items():
|
||||
setattr(prefs, k, v)
|
||||
prefs.updated_at = datetime.utcnow()
|
||||
|
||||
db.commit()
|
||||
except Exception as e:
|
||||
logger.error(f"Error saving research prefs to DB: {e}")
|
||||
|
||||
def _save_persona_data_to_db(self, db, persona_data: Dict[str, Any]):
|
||||
"""Save persona data to database."""
|
||||
try:
|
||||
session = db.query(OnboardingSession).filter(OnboardingSession.user_id == self.user_id).first()
|
||||
if not session:
|
||||
return
|
||||
|
||||
persona = db.query(PersonaData).filter(PersonaData.session_id == session.id).first()
|
||||
|
||||
valid_cols = PersonaData.__table__.columns.keys()
|
||||
filtered_data = {k: v for k, v in persona_data.items() if k in valid_cols}
|
||||
|
||||
if not persona:
|
||||
persona = PersonaData(session_id=session.id, **filtered_data)
|
||||
db.add(persona)
|
||||
else:
|
||||
for k, v in filtered_data.items():
|
||||
setattr(persona, k, v)
|
||||
persona.updated_at = datetime.utcnow()
|
||||
|
||||
db.commit()
|
||||
except Exception as e:
|
||||
logger.error(f"Error saving persona data to DB: {e}")
|
||||
|
||||
def save_progress(self):
|
||||
"""Save progress to database."""
|
||||
if not self.use_database or not self.db_service or not self.user_id:
|
||||
"""Save progress to database using direct access (no legacy service)."""
|
||||
if not self.use_database or not self.user_id:
|
||||
logger.error("Cannot save progress: database service not available or user_id not set")
|
||||
return
|
||||
|
||||
try:
|
||||
from services.database import SessionLocal
|
||||
db = SessionLocal()
|
||||
db = get_session_for_user(self.user_id)
|
||||
try:
|
||||
# Update session progress
|
||||
self.db_service.update_step(self.user_id, self.current_step, db)
|
||||
session = db.query(OnboardingSession).filter(OnboardingSession.user_id == self.user_id).first()
|
||||
if not session:
|
||||
session = OnboardingSession(
|
||||
user_id=self.user_id,
|
||||
current_step=self.current_step,
|
||||
progress=0.0,
|
||||
started_at=datetime.utcnow()
|
||||
)
|
||||
db.add(session)
|
||||
|
||||
session.current_step = self.current_step
|
||||
|
||||
# Calculate progress percentage
|
||||
completed_count = sum(1 for s in self.steps if s.status == StepStatus.COMPLETED)
|
||||
progress_pct = (completed_count / len(self.steps)) * 100
|
||||
self.db_service.update_progress(self.user_id, progress_pct, db)
|
||||
session.progress = progress_pct
|
||||
session.updated_at = datetime.utcnow()
|
||||
|
||||
db.commit()
|
||||
|
||||
# Save step-specific data to appropriate tables
|
||||
for step in self.steps:
|
||||
@@ -245,11 +360,9 @@ class OnboardingProgress:
|
||||
for provider, key in api_keys.items():
|
||||
if key:
|
||||
# Save to database (for user isolation in production)
|
||||
self.db_service.save_api_key(self.user_id, provider, key, db)
|
||||
self._save_api_key_to_db(db, provider, key)
|
||||
|
||||
# Also save to .env file ONLY in local development
|
||||
# This allows local developers to have keys in .env for convenience
|
||||
# In production, keys are fetched from database per user
|
||||
is_local = os.getenv('DEPLOY_ENV', 'local') == 'local'
|
||||
if is_local:
|
||||
try:
|
||||
@@ -285,13 +398,13 @@ class OnboardingProgress:
|
||||
if 'status' not in analysis_for_db:
|
||||
analysis_for_db['status'] = 'completed'
|
||||
|
||||
self.db_service.save_website_analysis(self.user_id, analysis_for_db, db)
|
||||
self._save_website_analysis_to_db(db, analysis_for_db)
|
||||
logger.info(f"✅ DATABASE: Website analysis saved to database for user {self.user_id}")
|
||||
elif step.step_number == 3: # Research Preferences
|
||||
self.db_service.save_research_preferences(self.user_id, step.data, db)
|
||||
self._save_research_preferences_to_db(db, step.data)
|
||||
logger.info(f"✅ DATABASE: Research preferences saved to database for user {self.user_id}")
|
||||
elif step.step_number == 4: # Persona Generation
|
||||
self.db_service.save_persona_data(self.user_id, step.data, db)
|
||||
self._save_persona_data_to_db(db, step.data)
|
||||
logger.info(f"✅ DATABASE: Persona data saved to database for user {self.user_id}")
|
||||
|
||||
logger.info(f"Progress saved to database for user {self.user_id}")
|
||||
@@ -303,46 +416,56 @@ class OnboardingProgress:
|
||||
raise
|
||||
|
||||
def load_progress_from_db(self):
|
||||
"""Load progress from database."""
|
||||
if not self.use_database or not self.db_service or not self.user_id:
|
||||
"""Load progress from database using SSOT Integration Service."""
|
||||
if not self.use_database or not self.user_id:
|
||||
logger.warning("Cannot load progress: database service not available or user_id not set")
|
||||
return
|
||||
|
||||
try:
|
||||
from services.database import SessionLocal
|
||||
db = SessionLocal()
|
||||
db = get_session_for_user(self.user_id)
|
||||
try:
|
||||
# Get integrated data (SSOT)
|
||||
integrated_data = self.integration_service.get_integrated_data_sync(self.user_id, db)
|
||||
|
||||
# Get session data
|
||||
session = self.db_service.get_session_by_user(self.user_id, db)
|
||||
if not session:
|
||||
session_data = integrated_data.get('onboarding_session', {})
|
||||
if not session_data:
|
||||
logger.info(f"No existing onboarding session found for user {self.user_id}, starting fresh")
|
||||
return
|
||||
|
||||
# Restore session data
|
||||
self.current_step = session.current_step or 1
|
||||
self.started_at = session.started_at.isoformat() if session.started_at else self.started_at
|
||||
self.last_updated = session.last_updated.isoformat() if session.last_updated else self.last_updated
|
||||
self.is_completed = session.is_completed or False
|
||||
self.completed_at = session.completed_at.isoformat() if session.completed_at else None
|
||||
self.current_step = session_data.get('current_step', 1)
|
||||
self.started_at = session_data.get('started_at') or self.started_at
|
||||
self.last_updated = session_data.get('updated_at') or self.last_updated
|
||||
|
||||
# Load step-specific data from database
|
||||
self._load_step_data_from_db(db)
|
||||
# Calculate completion status
|
||||
self.is_completed = (self.current_step >= 6) or (session_data.get('progress', 0) >= 100.0)
|
||||
if self.is_completed:
|
||||
self.completed_at = session_data.get('updated_at')
|
||||
|
||||
# Load step-specific data from integrated data
|
||||
self._load_step_data_from_integrated_data(integrated_data)
|
||||
|
||||
# Fix any corrupted state
|
||||
self._fix_corrupted_state()
|
||||
|
||||
logger.info(f"Progress loaded from database for user {self.user_id}")
|
||||
logger.info(f"Progress loaded from database (SSOT) for user {self.user_id}")
|
||||
finally:
|
||||
db.close()
|
||||
except Exception as e:
|
||||
logger.error(f"Error loading progress from database: {str(e)}")
|
||||
# Don't fail if database loading fails - start fresh
|
||||
|
||||
def _load_step_data_from_db(self, db):
|
||||
"""Load step-specific data from database tables."""
|
||||
|
||||
def _load_step_data_from_integrated_data(self, integrated_data: Dict[str, Any]):
|
||||
"""Load step-specific data from integrated data dictionary."""
|
||||
try:
|
||||
# Load API keys (step 1)
|
||||
api_keys = self.db_service.get_api_keys(self.user_id, db)
|
||||
api_keys_data = integrated_data.get('api_keys_data', {})
|
||||
# api_keys_data structure from integration service might be different, let's check
|
||||
# It usually returns {'openai_api_key': '...', ...}
|
||||
# We need to filter for actual keys
|
||||
api_keys = {k: v for k, v in api_keys_data.items() if v and 'api_key' in k}
|
||||
|
||||
if api_keys:
|
||||
step1 = self.get_step_data(1)
|
||||
if step1:
|
||||
@@ -351,7 +474,7 @@ class OnboardingProgress:
|
||||
step1.completed_at = datetime.now().isoformat()
|
||||
|
||||
# Load website analysis (step 2)
|
||||
website_analysis = self.db_service.get_website_analysis(self.user_id, db)
|
||||
website_analysis = integrated_data.get('website_analysis', {})
|
||||
if website_analysis:
|
||||
step2 = self.get_step_data(2)
|
||||
if step2:
|
||||
@@ -360,7 +483,7 @@ class OnboardingProgress:
|
||||
step2.completed_at = datetime.now().isoformat()
|
||||
|
||||
# Load research preferences (step 3)
|
||||
research_prefs = self.db_service.get_research_preferences(self.user_id, db)
|
||||
research_prefs = integrated_data.get('research_preferences', {})
|
||||
if research_prefs:
|
||||
step3 = self.get_step_data(3)
|
||||
if step3:
|
||||
@@ -369,7 +492,7 @@ class OnboardingProgress:
|
||||
step3.completed_at = datetime.now().isoformat()
|
||||
|
||||
# Load persona data (step 4)
|
||||
persona_data = self.db_service.get_persona_data(self.user_id, db)
|
||||
persona_data = integrated_data.get('persona_data', {})
|
||||
if persona_data:
|
||||
step4 = self.get_step_data(4)
|
||||
if step4:
|
||||
@@ -377,9 +500,9 @@ class OnboardingProgress:
|
||||
step4.data = persona_data
|
||||
step4.completed_at = datetime.now().isoformat()
|
||||
|
||||
logger.info("Step data loaded from database")
|
||||
logger.info("Step data loaded from integrated data")
|
||||
except Exception as e:
|
||||
logger.error(f"Error loading step data from database: {str(e)}")
|
||||
logger.error(f"Error loading step data from integrated data: {str(e)}")
|
||||
|
||||
def _fix_corrupted_state(self):
|
||||
"""Fix any corrupted progress state."""
|
||||
|
||||
@@ -1,291 +0,0 @@
|
||||
"""
|
||||
Onboarding Data Service
|
||||
Extracts real user data from onboarding to personalize AI inputs
|
||||
"""
|
||||
|
||||
from typing import Dict, Any, List, Optional
|
||||
from sqlalchemy.orm import Session
|
||||
from loguru import logger
|
||||
from datetime import datetime
|
||||
import json
|
||||
|
||||
from services.database import get_db_session
|
||||
from models.onboarding import OnboardingSession, WebsiteAnalysis, ResearchPreferences
|
||||
|
||||
class OnboardingDataService:
|
||||
"""Service to extract and use real onboarding data for AI personalization."""
|
||||
|
||||
def __init__(self, db: Optional[Session] = None):
|
||||
"""Initialize the onboarding data service."""
|
||||
self.db = db
|
||||
logger.info("OnboardingDataService initialized")
|
||||
|
||||
def get_user_website_analysis(self, user_id: int) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Get website analysis data for a specific user.
|
||||
|
||||
Args:
|
||||
user_id: User ID to get data for
|
||||
|
||||
Returns:
|
||||
Website analysis data or None if not found
|
||||
"""
|
||||
try:
|
||||
session = self.db or get_db_session()
|
||||
|
||||
# Find onboarding session for user
|
||||
onboarding_session = session.query(OnboardingSession).filter(
|
||||
OnboardingSession.user_id == user_id
|
||||
).first()
|
||||
|
||||
if not onboarding_session:
|
||||
logger.warning(f"No onboarding session found for user {user_id}")
|
||||
return None
|
||||
|
||||
# Get website analysis for this session
|
||||
website_analysis = session.query(WebsiteAnalysis).filter(
|
||||
WebsiteAnalysis.session_id == onboarding_session.id
|
||||
).first()
|
||||
|
||||
if not website_analysis:
|
||||
logger.warning(f"No website analysis found for user {user_id}")
|
||||
return None
|
||||
|
||||
return website_analysis.to_dict()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting website analysis for user {user_id}: {str(e)}")
|
||||
return None
|
||||
|
||||
def get_user_research_preferences(self, user_id: int) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Get research preferences for a specific user.
|
||||
|
||||
Args:
|
||||
user_id: User ID to get data for
|
||||
|
||||
Returns:
|
||||
Research preferences data or None if not found
|
||||
"""
|
||||
try:
|
||||
session = self.db or get_db_session()
|
||||
|
||||
# Find onboarding session for user
|
||||
onboarding_session = session.query(OnboardingSession).filter(
|
||||
OnboardingSession.user_id == user_id
|
||||
).first()
|
||||
|
||||
if not onboarding_session:
|
||||
logger.warning(f"No onboarding session found for user {user_id}")
|
||||
return None
|
||||
|
||||
# Get research preferences for this session
|
||||
research_prefs = session.query(ResearchPreferences).filter(
|
||||
ResearchPreferences.session_id == onboarding_session.id
|
||||
).first()
|
||||
|
||||
if not research_prefs:
|
||||
logger.warning(f"No research preferences found for user {user_id}")
|
||||
return None
|
||||
|
||||
return research_prefs.to_dict()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting research preferences for user {user_id}: {str(e)}")
|
||||
return None
|
||||
|
||||
def get_personalized_ai_inputs(self, user_id: int) -> Dict[str, Any]:
|
||||
"""
|
||||
Get personalized AI inputs based on user's onboarding data.
|
||||
|
||||
Args:
|
||||
user_id: User ID to get personalized data for
|
||||
|
||||
Returns:
|
||||
Personalized data for AI analysis
|
||||
"""
|
||||
try:
|
||||
logger.info(f"Getting personalized AI inputs for user {user_id}")
|
||||
|
||||
# Get website analysis
|
||||
website_analysis = self.get_user_website_analysis(user_id)
|
||||
research_prefs = self.get_user_research_preferences(user_id)
|
||||
|
||||
if not website_analysis:
|
||||
logger.warning(f"No onboarding data found for user {user_id}, using defaults")
|
||||
return self._get_default_ai_inputs()
|
||||
|
||||
# Extract real data from website analysis
|
||||
writing_style = website_analysis.get('writing_style', {})
|
||||
target_audience = website_analysis.get('target_audience', {})
|
||||
content_type = website_analysis.get('content_type', {})
|
||||
recommended_settings = website_analysis.get('recommended_settings', {})
|
||||
|
||||
# Build personalized AI inputs
|
||||
personalized_inputs = {
|
||||
"website_analysis": {
|
||||
"website_url": website_analysis.get('website_url', ''),
|
||||
"content_types": self._extract_content_types(content_type),
|
||||
"writing_style": writing_style.get('tone', 'professional'),
|
||||
"target_audience": target_audience.get('demographics', ['professionals']),
|
||||
"industry_focus": target_audience.get('industry_focus', 'general'),
|
||||
"expertise_level": target_audience.get('expertise_level', 'intermediate')
|
||||
},
|
||||
"competitor_analysis": {
|
||||
"top_performers": self._generate_competitor_suggestions(target_audience),
|
||||
"industry": target_audience.get('industry_focus', 'general'),
|
||||
"target_demographics": target_audience.get('demographics', [])
|
||||
},
|
||||
"gap_analysis": {
|
||||
"content_gaps": self._identify_content_gaps(content_type, writing_style),
|
||||
"target_keywords": self._generate_target_keywords(target_audience),
|
||||
"content_opportunities": self._identify_opportunities(content_type)
|
||||
},
|
||||
"keyword_analysis": {
|
||||
"high_value_keywords": self._generate_high_value_keywords(target_audience),
|
||||
"content_topics": self._generate_content_topics(content_type),
|
||||
"search_intent": self._analyze_search_intent(target_audience)
|
||||
}
|
||||
}
|
||||
|
||||
# Add research preferences if available
|
||||
if research_prefs:
|
||||
personalized_inputs["research_preferences"] = {
|
||||
"research_depth": research_prefs.get('research_depth', 'Standard'),
|
||||
"content_types": research_prefs.get('content_types', []),
|
||||
"auto_research": research_prefs.get('auto_research', True),
|
||||
"factual_content": research_prefs.get('factual_content', True)
|
||||
}
|
||||
|
||||
logger.info(f"✅ Generated personalized AI inputs for user {user_id}")
|
||||
return personalized_inputs
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error generating personalized AI inputs for user {user_id}: {str(e)}")
|
||||
return self._get_default_ai_inputs()
|
||||
|
||||
def _extract_content_types(self, content_type: Dict[str, Any]) -> List[str]:
|
||||
"""Extract content types from content type analysis."""
|
||||
types = []
|
||||
if content_type.get('primary_type'):
|
||||
types.append(content_type['primary_type'])
|
||||
if content_type.get('secondary_types'):
|
||||
types.extend(content_type['secondary_types'])
|
||||
return types if types else ['blog', 'article']
|
||||
|
||||
def _generate_competitor_suggestions(self, target_audience: Dict[str, Any]) -> List[str]:
|
||||
"""Generate competitor suggestions based on target audience."""
|
||||
industry = target_audience.get('industry_focus', 'general')
|
||||
demographics = target_audience.get('demographics', ['professionals'])
|
||||
|
||||
# Generate industry-specific competitors
|
||||
if industry == 'technology':
|
||||
return ['techcrunch.com', 'wired.com', 'theverge.com']
|
||||
elif industry == 'marketing':
|
||||
return ['hubspot.com', 'marketingland.com', 'moz.com']
|
||||
else:
|
||||
return ['competitor1.com', 'competitor2.com', 'competitor3.com']
|
||||
|
||||
def _identify_content_gaps(self, content_type: Dict[str, Any], writing_style: Dict[str, Any]) -> List[str]:
|
||||
"""Identify content gaps based on current content type and style."""
|
||||
gaps = []
|
||||
primary_type = content_type.get('primary_type', 'blog')
|
||||
|
||||
if primary_type == 'blog':
|
||||
gaps.extend(['Video tutorials', 'Case studies', 'Infographics'])
|
||||
elif primary_type == 'video':
|
||||
gaps.extend(['Blog posts', 'Whitepapers', 'Webinars'])
|
||||
|
||||
# Add style-based gaps
|
||||
tone = writing_style.get('tone', 'professional')
|
||||
if tone == 'professional':
|
||||
gaps.append('Personal stories')
|
||||
elif tone == 'casual':
|
||||
gaps.append('Expert interviews')
|
||||
|
||||
return gaps
|
||||
|
||||
def _generate_target_keywords(self, target_audience: Dict[str, Any]) -> List[str]:
|
||||
"""Generate target keywords based on audience analysis."""
|
||||
industry = target_audience.get('industry_focus', 'general')
|
||||
expertise = target_audience.get('expertise_level', 'intermediate')
|
||||
|
||||
if industry == 'technology':
|
||||
return ['AI tools', 'Digital transformation', 'Tech trends']
|
||||
elif industry == 'marketing':
|
||||
return ['Content marketing', 'SEO strategies', 'Social media']
|
||||
else:
|
||||
return ['Industry insights', 'Best practices', 'Expert tips']
|
||||
|
||||
def _identify_opportunities(self, content_type: Dict[str, Any]) -> List[str]:
|
||||
"""Identify content opportunities based on current content type."""
|
||||
opportunities = []
|
||||
purpose = content_type.get('purpose', 'informational')
|
||||
|
||||
if purpose == 'informational':
|
||||
opportunities.extend(['How-to guides', 'Tutorials', 'Educational content'])
|
||||
elif purpose == 'promotional':
|
||||
opportunities.extend(['Case studies', 'Testimonials', 'Success stories'])
|
||||
|
||||
return opportunities
|
||||
|
||||
def _generate_high_value_keywords(self, target_audience: Dict[str, Any]) -> List[str]:
|
||||
"""Generate high-value keywords based on audience analysis."""
|
||||
industry = target_audience.get('industry_focus', 'general')
|
||||
|
||||
if industry == 'technology':
|
||||
return ['AI marketing', 'Content automation', 'Digital strategy']
|
||||
elif industry == 'marketing':
|
||||
return ['Content marketing', 'SEO optimization', 'Social media strategy']
|
||||
else:
|
||||
return ['Industry trends', 'Best practices', 'Expert insights']
|
||||
|
||||
def _generate_content_topics(self, content_type: Dict[str, Any]) -> List[str]:
|
||||
"""Generate content topics based on content type analysis."""
|
||||
topics = []
|
||||
primary_type = content_type.get('primary_type', 'blog')
|
||||
|
||||
if primary_type == 'blog':
|
||||
topics.extend(['Industry trends', 'How-to guides', 'Expert insights'])
|
||||
elif primary_type == 'video':
|
||||
topics.extend(['Tutorials', 'Product demos', 'Expert interviews'])
|
||||
|
||||
return topics
|
||||
|
||||
def _analyze_search_intent(self, target_audience: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Analyze search intent based on target audience."""
|
||||
expertise = target_audience.get('expertise_level', 'intermediate')
|
||||
|
||||
if expertise == 'beginner':
|
||||
return {'intent': 'educational', 'focus': 'basic concepts'}
|
||||
elif expertise == 'intermediate':
|
||||
return {'intent': 'practical', 'focus': 'implementation'}
|
||||
else:
|
||||
return {'intent': 'advanced', 'focus': 'strategic insights'}
|
||||
|
||||
def _get_default_ai_inputs(self) -> Dict[str, Any]:
|
||||
"""Get default AI inputs when no onboarding data is available."""
|
||||
return {
|
||||
"website_analysis": {
|
||||
"content_types": ["blog", "video", "social"],
|
||||
"writing_style": "professional",
|
||||
"target_audience": ["professionals"],
|
||||
"industry_focus": "general",
|
||||
"expertise_level": "intermediate"
|
||||
},
|
||||
"competitor_analysis": {
|
||||
"top_performers": ["competitor1.com", "competitor2.com"],
|
||||
"industry": "general",
|
||||
"target_demographics": ["professionals"]
|
||||
},
|
||||
"gap_analysis": {
|
||||
"content_gaps": ["AI content", "Video tutorials", "Case studies"],
|
||||
"target_keywords": ["Industry insights", "Best practices"],
|
||||
"content_opportunities": ["How-to guides", "Tutorials"]
|
||||
},
|
||||
"keyword_analysis": {
|
||||
"high_value_keywords": ["AI marketing", "Content automation", "Digital strategy"],
|
||||
"content_topics": ["Industry trends", "Expert insights"],
|
||||
"search_intent": {"intent": "practical", "focus": "implementation"}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Database-only Onboarding Progress Service
|
||||
Replaces file-based progress tracking with database-only implementation.
|
||||
Refactored to use direct DB access and eliminate legacy OnboardingDatabaseService dependency.
|
||||
"""
|
||||
|
||||
from typing import Dict, Any, List, Optional
|
||||
@@ -9,23 +10,47 @@ from loguru import logger
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
|
||||
from services.database import SessionLocal
|
||||
from .database_service import OnboardingDatabaseService
|
||||
from services.database import SessionLocal, get_session_for_user
|
||||
from models.onboarding import OnboardingSession
|
||||
|
||||
|
||||
class OnboardingProgressService:
|
||||
"""Database-only onboarding progress management."""
|
||||
|
||||
def __init__(self):
|
||||
self.db_service = OnboardingDatabaseService()
|
||||
from api.content_planning.services.content_strategy.onboarding import OnboardingDataIntegrationService
|
||||
self.integration_service = OnboardingDataIntegrationService()
|
||||
|
||||
def get_onboarding_status(self, user_id: str) -> Dict[str, Any]:
|
||||
"""Get current onboarding status from database only."""
|
||||
def get_completion_data(self, user_id: str) -> Dict[str, Any]:
|
||||
"""Get full completion data for all steps using SSOT."""
|
||||
try:
|
||||
db = SessionLocal()
|
||||
db = get_session_for_user(user_id)
|
||||
try:
|
||||
# Get session data
|
||||
session = self.db_service.get_session_by_user(user_id, db)
|
||||
# Use SSOT integration service to get all data
|
||||
integrated_data = self.integration_service.get_integrated_data_sync(user_id, db)
|
||||
|
||||
# Map to format expected by StepManagementService
|
||||
return {
|
||||
"api_keys": integrated_data.get('api_keys_data', {}),
|
||||
"website_analysis": integrated_data.get('website_analysis', {}),
|
||||
"research_preferences": integrated_data.get('research_preferences', {}),
|
||||
"persona_data": integrated_data.get('persona_data', {}),
|
||||
"onboarding_session": integrated_data.get('onboarding_session', {})
|
||||
}
|
||||
finally:
|
||||
db.close()
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting completion data: {e}")
|
||||
return {}
|
||||
|
||||
def get_onboarding_status(self, user_id: str) -> Dict[str, Any]:
|
||||
"""Get current onboarding status from database."""
|
||||
try:
|
||||
db = get_session_for_user(user_id)
|
||||
try:
|
||||
# Direct DB access to SSOT session
|
||||
session = db.query(OnboardingSession).filter(OnboardingSession.user_id == user_id).first()
|
||||
|
||||
if not session:
|
||||
return {
|
||||
"is_completed": False,
|
||||
@@ -38,7 +63,6 @@ class OnboardingProgressService:
|
||||
|
||||
# Check if onboarding is complete
|
||||
# Consider complete if either the final step is reached OR progress hit 100%
|
||||
# This guards against partial writes where one field persisted but the other didn't.
|
||||
is_completed = (session.current_step >= 6) or (session.progress >= 100.0)
|
||||
|
||||
return {
|
||||
@@ -67,12 +91,26 @@ class OnboardingProgressService:
|
||||
def update_step(self, user_id: str, step_number: int) -> bool:
|
||||
"""Update current step in database."""
|
||||
try:
|
||||
db = SessionLocal()
|
||||
db = get_session_for_user(user_id)
|
||||
try:
|
||||
success = self.db_service.update_step(user_id, step_number, db)
|
||||
if success:
|
||||
logger.info(f"Updated user {user_id} to step {step_number}")
|
||||
return success
|
||||
session = db.query(OnboardingSession).filter(OnboardingSession.user_id == user_id).first()
|
||||
if not session:
|
||||
# Create session if not exists
|
||||
session = OnboardingSession(
|
||||
user_id=user_id,
|
||||
current_step=step_number,
|
||||
progress=0.0,
|
||||
started_at=datetime.utcnow(),
|
||||
updated_at=datetime.utcnow()
|
||||
)
|
||||
db.add(session)
|
||||
else:
|
||||
session.current_step = step_number
|
||||
session.updated_at = datetime.utcnow()
|
||||
|
||||
db.commit()
|
||||
logger.info(f"Updated user {user_id} to step {step_number}")
|
||||
return True
|
||||
finally:
|
||||
db.close()
|
||||
except Exception as e:
|
||||
@@ -82,12 +120,16 @@ class OnboardingProgressService:
|
||||
def update_progress(self, user_id: str, progress_percentage: float) -> bool:
|
||||
"""Update progress percentage in database."""
|
||||
try:
|
||||
db = SessionLocal()
|
||||
db = get_session_for_user(user_id)
|
||||
try:
|
||||
success = self.db_service.update_progress(user_id, progress_percentage, db)
|
||||
if success:
|
||||
session = db.query(OnboardingSession).filter(OnboardingSession.user_id == user_id).first()
|
||||
if session:
|
||||
session.progress = progress_percentage
|
||||
session.updated_at = datetime.utcnow()
|
||||
db.commit()
|
||||
logger.info(f"Updated user {user_id} progress to {progress_percentage}%")
|
||||
return success
|
||||
return True
|
||||
return False
|
||||
finally:
|
||||
db.close()
|
||||
except Exception as e:
|
||||
@@ -97,67 +139,18 @@ class OnboardingProgressService:
|
||||
def complete_onboarding(self, user_id: str) -> bool:
|
||||
"""Mark onboarding as complete in database."""
|
||||
try:
|
||||
db = SessionLocal()
|
||||
db = get_session_for_user(user_id)
|
||||
try:
|
||||
success = self.db_service.mark_onboarding_complete(user_id, db)
|
||||
if success:
|
||||
logger.info(f"Marked onboarding complete for user {user_id}")
|
||||
return success
|
||||
session = db.query(OnboardingSession).filter(OnboardingSession.user_id == user_id).first()
|
||||
if session:
|
||||
session.progress = 100.0
|
||||
session.current_step = 6 # Assuming 6 is complete
|
||||
session.updated_at = datetime.utcnow()
|
||||
db.commit()
|
||||
return True
|
||||
return False
|
||||
finally:
|
||||
db.close()
|
||||
except Exception as e:
|
||||
logger.error(f"Error completing onboarding: {e}")
|
||||
return False
|
||||
|
||||
def reset_onboarding(self, user_id: str) -> bool:
|
||||
"""Reset onboarding progress in database."""
|
||||
try:
|
||||
db = SessionLocal()
|
||||
try:
|
||||
# Reset to step 1, 0% progress
|
||||
success = self.db_service.update_step(user_id, 1, db)
|
||||
if success:
|
||||
self.db_service.update_progress(user_id, 0.0, db)
|
||||
logger.info(f"Reset onboarding for user {user_id}")
|
||||
return success
|
||||
finally:
|
||||
db.close()
|
||||
except Exception as e:
|
||||
logger.error(f"Error resetting onboarding: {e}")
|
||||
return False
|
||||
|
||||
def get_completion_data(self, user_id: str) -> Dict[str, Any]:
|
||||
"""Get completion data for validation."""
|
||||
try:
|
||||
db = SessionLocal()
|
||||
try:
|
||||
# Get all relevant data for completion validation
|
||||
session = self.db_service.get_session_by_user(user_id, db)
|
||||
api_keys = self.db_service.get_api_keys(user_id, db)
|
||||
website_analysis = self.db_service.get_website_analysis(user_id, db)
|
||||
research_preferences = self.db_service.get_research_preferences(user_id, db)
|
||||
persona_data = self.db_service.get_persona_data(user_id, db)
|
||||
|
||||
return {
|
||||
"session": session,
|
||||
"api_keys": api_keys,
|
||||
"website_analysis": website_analysis,
|
||||
"research_preferences": research_preferences,
|
||||
"persona_data": persona_data
|
||||
}
|
||||
finally:
|
||||
db.close()
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting completion data: {e}")
|
||||
return {}
|
||||
|
||||
|
||||
# Global instance
|
||||
_onboarding_progress_service = None
|
||||
|
||||
def get_onboarding_progress_service() -> OnboardingProgressService:
|
||||
"""Get the global onboarding progress service instance."""
|
||||
global _onboarding_progress_service
|
||||
if _onboarding_progress_service is None:
|
||||
_onboarding_progress_service = OnboardingProgressService()
|
||||
return _onboarding_progress_service
|
||||
|
||||
Reference in New Issue
Block a user