| """ |
| Action Plan Generator |
| Automatically generates actionable SEO and GEO recommendations |
| """ |
|
|
| from typing import Dict, List |
| import json |
|
|
| def generate_action_plan_from_audit(audit: Dict) -> Dict: |
| """ |
| Generate comprehensive action plan from audit results |
| Returns prioritized, actionable tasks |
| """ |
| pages = audit.get('pages', []) |
| geo_score = audit.get('geo_score', 0) |
| ai_visibility = audit.get('ai_visibility', {}) |
| |
| actions = [] |
| |
| |
| actions.extend(_generate_critical_actions(audit, pages)) |
| |
| |
| actions.extend(_generate_seo_actions(pages)) |
| |
| |
| actions.extend(_generate_content_actions(pages)) |
| |
| |
| actions.extend(_generate_ai_visibility_actions(ai_visibility, geo_score)) |
| |
| |
| actions.extend(_generate_technical_actions(pages)) |
| |
| |
| priority_order = {'critical': 0, 'high': 1, 'medium': 2, 'low': 3} |
| actions.sort(key=lambda x: priority_order.get(x['priority'], 4)) |
| |
| |
| total_actions = len(actions) |
| critical_count = len([a for a in actions if a['priority'] == 'critical']) |
| high_count = len([a for a in actions if a['priority'] == 'high']) |
| |
| return { |
| 'ok': True, |
| 'actions': actions, |
| 'summary': { |
| 'total': total_actions, |
| 'critical': critical_count, |
| 'high': high_count, |
| 'medium': len([a for a in actions if a['priority'] == 'medium']), |
| 'low': len([a for a in actions if a['priority'] == 'low']) |
| }, |
| 'estimated_time': _estimate_total_time(actions), |
| 'estimated_impact': _estimate_total_impact(actions) |
| } |
|
|
|
|
| def _generate_critical_actions(audit: Dict, pages: List[Dict]) -> List[Dict]: |
| """Generate critical priority actions""" |
| actions = [] |
| |
| |
| empty_pages = [p for p in pages if p.get('word_count', 0) < 100] |
| if empty_pages: |
| actions.append({ |
| 'id': 'critical_empty_content', |
| 'type': 'content', |
| 'priority': 'critical', |
| 'title': f'محتوى فارغ في {len(empty_pages)} صفحة', |
| 'description': 'صفحات بدون محتوى كافٍ لن تظهر في نتائج البحث أو الذكاء الاصطناعي', |
| 'task': f'أضف محتوى تفصيلي (600+ كلمة) لـ {len(empty_pages)} صفحة', |
| 'impact': 'عالي جداً', |
| 'effort': 'متوسط', |
| 'timeline': f'{len(empty_pages) * 2} ساعة', |
| 'pages_affected': [p.get('url') for p in empty_pages[:5]] |
| }) |
| |
| |
| pages_without_schema = [p for p in pages if not _has_schema(p)] |
| if len(pages_without_schema) == len(pages): |
| actions.append({ |
| 'id': 'critical_no_schema', |
| 'type': 'technical', |
| 'priority': 'critical', |
| 'title': 'لا يوجد Schema Markup', |
| 'description': 'Schema Markup ضروري للظهور في نتائج الذكاء الاصطناعي', |
| 'task': 'أضف Organization و FAQPage Schema لجميع الصفحات', |
| 'impact': 'عالي جداً', |
| 'effort': 'منخفض', |
| 'timeline': '2 ساعة', |
| 'code_example': 'استخدم schema_generator.py' |
| }) |
| |
| return actions |
|
|
|
|
| def _generate_seo_actions(pages: List[Dict]) -> List[Dict]: |
| """Generate SEO-related actions""" |
| actions = [] |
| |
| |
| pages_without_meta = [p for p in pages if not _has_meta_description(p)] |
| if pages_without_meta: |
| actions.append({ |
| 'id': 'seo_meta_descriptions', |
| 'type': 'seo', |
| 'priority': 'high', |
| 'title': f'Meta Description مفقود في {len(pages_without_meta)} صفحة', |
| 'description': 'Meta descriptions تحسن نسبة النقر في نتائج البحث', |
| 'task': f'أضف meta description مُحسّن لـ {len(pages_without_meta)} صفحة', |
| 'impact': 'عالي', |
| 'effort': 'منخفض', |
| 'timeline': f'{len(pages_without_meta) * 15} دقيقة', |
| 'pages_affected': [p.get('url') for p in pages_without_meta[:5]], |
| 'tool': 'استخدم meta_generator.py' |
| }) |
| |
| |
| pages_without_h1 = [p for p in pages if not _has_h1(p)] |
| if pages_without_h1: |
| actions.append({ |
| 'id': 'seo_h1_tags', |
| 'type': 'seo', |
| 'priority': 'high', |
| 'title': f'H1 مفقود في {len(pages_without_h1)} صفحة', |
| 'description': 'H1 tag ضروري لمحركات البحث لفهم موضوع الصفحة', |
| 'task': f'أضف H1 واضح ومُحسّن لـ {len(pages_without_h1)} صفحة', |
| 'impact': 'عالي', |
| 'effort': 'منخفض', |
| 'timeline': f'{len(pages_without_h1) * 10} دقيقة', |
| 'pages_affected': [p.get('url') for p in pages_without_h1[:5]] |
| }) |
| |
| |
| titles = [p.get('title', '') for p in pages] |
| duplicate_titles = [t for t in titles if titles.count(t) > 1] |
| if duplicate_titles: |
| actions.append({ |
| 'id': 'seo_duplicate_titles', |
| 'type': 'seo', |
| 'priority': 'medium', |
| 'title': f'{len(set(duplicate_titles))} عنوان مكرر', |
| 'description': 'العناوين المكررة تضعف أداء SEO', |
| 'task': 'اجعل كل عنوان صفحة فريد ووصفي', |
| 'impact': 'متوسط', |
| 'effort': 'منخفض', |
| 'timeline': '1 ساعة' |
| }) |
| |
| return actions |
|
|
|
|
| def _generate_content_actions(pages: List[Dict]) -> List[Dict]: |
| """Generate content-related actions""" |
| actions = [] |
| |
| |
| thin_pages = [p for p in pages if 100 < p.get('word_count', 0) < 300] |
| if thin_pages: |
| actions.append({ |
| 'id': 'content_thin_pages', |
| 'type': 'content', |
| 'priority': 'medium', |
| 'title': f'محتوى ضعيف في {len(thin_pages)} صفحة', |
| 'description': 'الصفحات ذات المحتوى القليل تحصل على ترتيب أقل', |
| 'task': f'وسّع المحتوى إلى 600+ كلمة في {len(thin_pages)} صفحة', |
| 'impact': 'متوسط', |
| 'effort': 'متوسط', |
| 'timeline': f'{len(thin_pages) * 1.5} ساعة', |
| 'pages_affected': [p.get('url') for p in thin_pages[:5]] |
| }) |
| |
| |
| pages_without_entities = [p for p in pages if len(p.get('entities', [])) < 3] |
| if len(pages_without_entities) > len(pages) * 0.5: |
| actions.append({ |
| 'id': 'content_entities', |
| 'type': 'content', |
| 'priority': 'medium', |
| 'title': 'محتوى غير منظم - لا كيانات معرفية', |
| 'description': 'الذكاء الاصطناعي يحتاج كيانات واضحة (أسماء، أماكن، منتجات)', |
| 'task': 'أضف أسماء محددة، أرقام، وأماكن في المحتوى', |
| 'impact': 'عالي', |
| 'effort': 'متوسط', |
| 'timeline': '3 ساعات', |
| 'example': 'بدلاً من "نقدم خدمات ممتازة" → "نقدم خدمات SEO في الرياض منذ 2020"' |
| }) |
| |
| return actions |
|
|
|
|
| def _generate_ai_visibility_actions(ai_visibility: Dict, geo_score: int) -> List[Dict]: |
| """Generate AI visibility improvement actions""" |
| actions = [] |
| |
| if geo_score < 50: |
| actions.append({ |
| 'id': 'ai_low_geo_score', |
| 'type': 'ai_visibility', |
| 'priority': 'high', |
| 'title': f'درجة GEO منخفضة ({geo_score}/100)', |
| 'description': 'موقعك غير مرئي في محركات الذكاء الاصطناعي', |
| 'task': 'نفّذ جميع التوصيات لرفع درجة GEO', |
| 'impact': 'عالي جداً', |
| 'effort': 'عالي', |
| 'timeline': '2-4 أسابيع', |
| 'steps': [ |
| 'أضف محتوى تفصيلي 600+ كلمة', |
| 'أضف Schema Markup', |
| 'أضف FAQ Schema', |
| 'حسّن الكيانات المعرفية', |
| 'أضف روابط خارجية موثوقة' |
| ] |
| }) |
| |
| mentions = ai_visibility.get('mentions', 0) |
| if mentions == 0: |
| actions.append({ |
| 'id': 'ai_no_mentions', |
| 'type': 'ai_visibility', |
| 'priority': 'high', |
| 'title': 'لا يوجد ذكر في الذكاء الاصطناعي', |
| 'description': 'علامتك التجارية غير معروفة لمحركات الذكاء الاصطناعي', |
| 'task': 'بناء حضور رقمي قوي', |
| 'impact': 'عالي', |
| 'effort': 'عالي', |
| 'timeline': '4-8 أسابيع', |
| 'steps': [ |
| 'انشر محتوى عالي الجودة بانتظام', |
| 'احصل على روابط من مواقع موثوقة', |
| 'شارك في المنتديات والمجتمعات', |
| 'أنشئ ملفات تعريف في الدلائل الرئيسية' |
| ] |
| }) |
| |
| return actions |
|
|
|
|
| def _generate_technical_actions(pages: List[Dict]) -> List[Dict]: |
| """Generate technical SEO actions""" |
| actions = [] |
| |
| |
| actions.append({ |
| 'id': 'technical_mobile', |
| 'type': 'technical', |
| 'priority': 'medium', |
| 'title': 'فحص التوافق مع الموبايل', |
| 'description': 'تأكد من أن الموقع متجاوب على جميع الأجهزة', |
| 'task': 'اختبر الموقع على أجهزة مختلفة وأصلح المشاكل', |
| 'impact': 'عالي', |
| 'effort': 'متوسط', |
| 'timeline': '1-2 أيام', |
| 'tool': 'استخدم mobile_checker.py' |
| }) |
| |
| |
| actions.append({ |
| 'id': 'technical_speed', |
| 'type': 'technical', |
| 'priority': 'low', |
| 'title': 'تحسين سرعة الموقع', |
| 'description': 'المواقع السريعة تحصل على ترتيب أفضل', |
| 'task': 'ضغط الصور، تفعيل التخزين المؤقت، تصغير CSS/JS', |
| 'impact': 'متوسط', |
| 'effort': 'متوسط', |
| 'timeline': '2-3 أيام' |
| }) |
| |
| return actions |
|
|
|
|
| def _has_schema(page: Dict) -> bool: |
| """Check if page has Schema markup""" |
| html = page.get('html', '') |
| return 'application/ld+json' in html or '@type' in html |
|
|
|
|
| def _has_meta_description(page: Dict) -> bool: |
| """Check if page has meta description""" |
| html = page.get('html', '') |
| import re |
| return bool(re.search(r'<meta\s+name=["\']description["\']', html, re.IGNORECASE)) |
|
|
|
|
| def _has_h1(page: Dict) -> bool: |
| """Check if page has H1 tag""" |
| headings = page.get('headings', []) |
| for h in headings: |
| if isinstance(h, dict) and h.get('level') == 1: |
| return True |
| elif isinstance(h, str) and h.startswith('h1'): |
| return True |
| return False |
|
|
|
|
| def _estimate_total_time(actions: List[Dict]) -> str: |
| """Estimate total time to complete all actions""" |
| total_hours = 0 |
| for action in actions: |
| timeline = action.get('timeline', '') |
| if 'ساعة' in timeline or 'hour' in timeline: |
| import re |
| match = re.search(r'(\d+(?:\.\d+)?)', timeline) |
| if match: |
| total_hours += float(match.group(1)) |
| elif 'دقيقة' in timeline or 'minute' in timeline: |
| import re |
| match = re.search(r'(\d+)', timeline) |
| if match: |
| total_hours += float(match.group(1)) / 60 |
| elif 'يوم' in timeline or 'day' in timeline: |
| import re |
| match = re.search(r'(\d+)', timeline) |
| if match: |
| total_hours += float(match.group(1)) * 8 |
| elif 'أسبوع' in timeline or 'week' in timeline: |
| import re |
| match = re.search(r'(\d+)', timeline) |
| if match: |
| total_hours += float(match.group(1)) * 40 |
| |
| if total_hours < 8: |
| return f'{int(total_hours)} ساعة' |
| elif total_hours < 40: |
| return f'{int(total_hours / 8)} يوم' |
| else: |
| return f'{int(total_hours / 40)} أسبوع' |
|
|
|
|
| def _estimate_total_impact(actions: List[Dict]) -> str: |
| """Estimate total impact of completing all actions""" |
| high_impact = len([a for a in actions if a.get('impact') in ['عالي جداً', 'عالي', 'high']]) |
| |
| if high_impact > 5: |
| return 'عالي جداً - تحسين كبير متوقع' |
| elif high_impact > 2: |
| return 'عالي - تحسين ملحوظ متوقع' |
| else: |
| return 'متوسط - تحسين تدريجي' |
|
|