""" Outline Optimizer - AI-powered outline optimization and rebalancing. Optimizes outlines for better flow, SEO, and engagement. """ from typing import List from loguru import logger from models.blog_models import BlogOutlineSection class OutlineOptimizer: """Optimizes outlines for better flow, SEO, and engagement.""" async def optimize(self, outline: List[BlogOutlineSection], focus: str, user_id: str) -> List[BlogOutlineSection]: """Optimize entire outline for better flow, SEO, and engagement. Args: outline: List of outline sections to optimize focus: Optimization focus (e.g., "general optimization") user_id: User ID (required for subscription checks and usage tracking) Returns: List of optimized outline sections Raises: ValueError: If user_id is not provided """ if not user_id: raise ValueError("user_id is required for outline optimization (subscription checks and usage tracking)") outline_text = "\n".join([f"{i+1}. {s.heading}" for i, s in enumerate(outline)]) optimization_prompt = f"""Optimize this blog outline for better flow, engagement, and SEO: Current Outline: {outline_text} Optimization Focus: {focus} Goals: Improve narrative flow, enhance SEO, increase engagement, ensure comprehensive coverage. Return JSON format: {{ "outline": [ {{ "heading": "Optimized heading", "subheadings": ["subheading 1", "subheading 2"], "key_points": ["point 1", "point 2"], "target_words": 300, "keywords": ["keyword1", "keyword2"] }} ] }}""" try: from services.llm_providers.main_text_generation import llm_text_gen optimization_schema = { "type": "object", "properties": { "outline": { "type": "array", "items": { "type": "object", "properties": { "heading": {"type": "string"}, "subheadings": {"type": "array", "items": {"type": "string"}}, "key_points": {"type": "array", "items": {"type": "string"}}, "target_words": {"type": "integer"}, "keywords": {"type": "array", "items": {"type": "string"}} }, "required": ["heading", "subheadings", "key_points", "target_words", "keywords"] } } }, "required": ["outline"], "propertyOrdering": ["outline"] } optimized_data = llm_text_gen( prompt=optimization_prompt, json_struct=optimization_schema, system_prompt=None, user_id=user_id ) # Handle the new schema format with "outline" wrapper if isinstance(optimized_data, dict) and 'outline' in optimized_data: optimized_sections = [] for i, section_data in enumerate(optimized_data['outline']): section = BlogOutlineSection( id=f"s{i+1}", heading=section_data.get('heading', f'Section {i+1}'), subheadings=section_data.get('subheadings', []), key_points=section_data.get('key_points', []), references=outline[i].references if i < len(outline) else [], target_words=section_data.get('target_words', 300), keywords=section_data.get('keywords', []) ) optimized_sections.append(section) logger.info(f"✅ Outline optimization completed: {len(optimized_sections)} sections optimized") return optimized_sections else: logger.warning(f"Invalid optimization response format: {type(optimized_data)}") except Exception as e: logger.warning(f"AI outline optimization failed: {e}") logger.info("Returning original outline without optimization") return outline def rebalance_word_counts(self, outline: List[BlogOutlineSection], target_words: int) -> List[BlogOutlineSection]: """Rebalance word count distribution across sections.""" total_sections = len(outline) if total_sections == 0: return outline # Calculate target distribution intro_words = int(target_words * 0.12) # 12% for intro conclusion_words = int(target_words * 0.12) # 12% for conclusion main_content_words = target_words - intro_words - conclusion_words # Distribute main content words across sections words_per_section = main_content_words // total_sections remainder = main_content_words % total_sections for i, section in enumerate(outline): if i == 0: # First section (intro) section.target_words = intro_words elif i == total_sections - 1: # Last section (conclusion) section.target_words = conclusion_words else: # Main content sections section.target_words = words_per_section + (1 if i < remainder else 0) return outline