"""Professional GEO Report Generator — white-label HTML + PDF.""" import json, os from pathlib import Path from datetime import datetime def _get_branding() -> dict: """Load white-label settings; fall back to Moharek defaults.""" try: settings_path = Path(os.environ.get('OUTPUT_DIR', './output')) / 'settings.json' if settings_path.exists(): s = json.loads(settings_path.read_text()) if s.get('agency_name'): return { 'name': s.get('agency_name', 'Moharek GEO'), 'logo': s.get('agency_logo', '/logo.webp'), 'color': s.get('primary_color', '#10b981'), 'footer': s.get('report_footer', ''), } except Exception: pass return {'name': 'Moharek GEO', 'logo': '/logo.webp', 'color': '#10b981', 'footer': ''} def build_html_report(job_dir: str) -> str: p = Path(job_dir) audit, analysis, recs = {}, {}, {} for fname, target in [('audit.json','audit'),('analysis.json','analysis'),('recommendations.json','recs')]: path = p / fname if path.exists(): try: data = json.loads(path.read_text(encoding='utf-8')) if target == 'audit': audit = data elif target == 'analysis': analysis = data elif target == 'recs': recs = data except Exception: pass brand = _get_branding() pages = audit.get('pages', []) org = audit.get('org_name') or (pages[0].get('title') if pages else 'Website') url = audit.get('url') or (pages[0].get('url') if pages else '') geo = analysis.get('geo_score') or {} score = geo.get('score', 0) status = geo.get('status', 'N/A') breakdown = geo.get('breakdown', {}) actions = recs.get('actions', []) if isinstance(recs, dict) else [] per_page = recs.get('per_page', []) if isinstance(recs, dict) else [] date_str = datetime.utcnow().strftime('%Y-%m-%d') score_color = '#10b981' if score >= 75 else '#f59e0b' if score >= 40 else '#ef4444' accent = brand['color'] def bar(val, max_val=20): pct = min(100, int((val / max_val) * 100)) return f'
' total_issues = sum(len(pg.get('issues', [])) for pg in per_page) total_pages = len(pages) page_rows = '' for pg in per_page[:20]: issues = pg.get('issues', []) color = '#ef4444' if len(issues) > 1 else '#f59e0b' if issues else '#10b981' icon = '🔴' if len(issues) > 1 else '🟡' if issues else '🟢' page_rows += f'''
{pg.get("title") or pg.get("url","")}
{pg.get("url","")}
{icon} {"، ".join(issues) if issues else "✅ لا مشاكل"} ''' action_rows = '' for i, a in enumerate(actions[:10], 1): text = a.get('text', a) if isinstance(a, dict) else a priority = a.get('priority', 'MEDIUM') if isinstance(a, dict) else 'MEDIUM' p_color = '#ef4444' if priority == 'HIGH' else '#f59e0b' if priority == 'MEDIUM' else '#10b981' action_rows += f''' {i} {priority} {text} ''' # ── Logo / branding header ── logo_html = f'{brand[' footer_text = brand['footer'] or f'تم إنشاء هذا التقرير بواسطة {brand["name"]}' html = f''' تقرير GEO — {org}
{logo_html} {brand["name"]}
{date_str}
{score}%

تقرير GEO — {org}

{url}

تاريخ التقرير: {date_str}  ·  الحالة: {status}

الصفحات المحللة: {total_pages}  ·  المشاكل المكتشفة: {total_issues}

📊 تفصيل درجة GEO

{''.join(f"""
{label} {val}/20
{bar(val)}
""" for label, val in [ ('جودة العناوين', breakdown.get('headings', 0)), ('كثافة المحتوى', breakdown.get('density', 0)), ('الكيانات الدلالية', breakdown.get('entities', 0)), ('أسئلة FAQ', breakdown.get('faq', 0)), ('الظهور في الذكاء الاصطناعي', breakdown.get('ai_visibility', 0)), ])}

💡 خطة العمل ({len(actions)} توصية)

{'

لا توجد توصيات — شغّل تحليل الذكاء الاصطناعي أولاً.

' if not actions else f''' {action_rows}
#الأولويةالإجراء المطلوب
'''}

🔍 تحليل الصفحات ({total_pages} صفحة)

{'

لا توجد بيانات صفحات.

' if not per_page else f''' {page_rows}
الصفحةالحالةالمشاكل
'''}
{'

🤖 الظهور في الذكاء الاصطناعي

' + _render_ai_vis(audit.get('ai_visibility', {})) + '
' if audit.get('ai_visibility') else ''}
''' return html def _render_ai_vis(ai_vis: dict) -> str: if not ai_vis or not ai_vis.get('enabled'): reason = ai_vis.get('reason', 'بيانات الظهور غير متاحة') if ai_vis else 'غير مفعّل' return f'

{reason}

' results = ai_vis.get('results', []) rows = ''.join(f''' {r.get("query","")} {'✅ موجود' if r.get('mentioned') else '❌ غائب'} ''' for r in results) return f'{rows}
الاستعلامالنتيجة
' def try_render_pdf(html: str, out_path: Path) -> bool: # Try weasyprint first try: from weasyprint import HTML HTML(string=html).write_pdf(str(out_path)) return True except ImportError: pass except Exception: pass # Fallback: try pdfkit (wkhtmltopdf) try: import pdfkit pdfkit.from_string(html, str(out_path)) return True except Exception: pass return False