""" SEO Data models for the "SEO Juice Detector" feature. This implements "Strategie 3: SEO-Daten & Backlinks" from analysis_3.md: "SEO-Agenturen suchen Domains nicht wegen dem Namen, sondern wegen der Power (Backlinks). Wenn eine Domain droppt, prüfst du nicht nur den Namen, sondern ob Backlinks existieren." This is a TYCOON-ONLY feature ($29/month). DATABASE TABLE TO CREATE: - domain_seo_data - Cached SEO metrics for domains Run migrations: alembic upgrade head """ from datetime import datetime from typing import Optional, List from sqlalchemy import String, DateTime, Float, Integer, Text, ForeignKey, Boolean, JSON from sqlalchemy.orm import Mapped, mapped_column from app.database import Base class DomainSEOData(Base): """ Cached SEO data for domains. Stores backlink data, domain authority, and other SEO metrics from Moz API or alternative sources. From analysis_3.md: "Domain `alte-bäckerei-münchen.de` ist frei. Hat Links von `sueddeutsche.de` und `wikipedia.org`." """ __tablename__ = "domain_seo_data" id: Mapped[int] = mapped_column(primary_key=True, index=True) domain: Mapped[str] = mapped_column(String(255), unique=True, nullable=False, index=True) # Moz metrics domain_authority: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) # 0-100 page_authority: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) # 0-100 spam_score: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) # 0-100 # Backlink data total_backlinks: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) referring_domains: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) # Top backlinks (JSON array of {domain, authority, type}) top_backlinks: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True) # Notable backlinks (high-authority sites) notable_backlinks: Mapped[Optional[str]] = mapped_column(Text, nullable=True) # Comma-separated has_wikipedia_link: Mapped[bool] = mapped_column(Boolean, default=False) has_gov_link: Mapped[bool] = mapped_column(Boolean, default=False) has_edu_link: Mapped[bool] = mapped_column(Boolean, default=False) has_news_link: Mapped[bool] = mapped_column(Boolean, default=False) # Estimated value based on SEO seo_value_estimate: Mapped[Optional[float]] = mapped_column(Float, nullable=True) # Data source data_source: Mapped[str] = mapped_column(String(50), default="moz") # moz, ahrefs, majestic, estimated # Cache management last_updated: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) expires_at: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True) # Request tracking fetch_count: Mapped[int] = mapped_column(Integer, default=0) def __repr__(self) -> str: return f"" @property def is_expired(self) -> bool: if not self.expires_at: return True return datetime.utcnow() > self.expires_at @property def seo_score(self) -> int: """Calculate overall SEO score (0-100).""" if not self.domain_authority: return 0 score = self.domain_authority # Boost for notable links if self.has_wikipedia_link: score = min(100, score + 10) if self.has_gov_link: score = min(100, score + 5) if self.has_edu_link: score = min(100, score + 5) if self.has_news_link: score = min(100, score + 3) # Penalty for spam if self.spam_score and self.spam_score > 30: score = max(0, score - (self.spam_score // 5)) return score @property def value_category(self) -> str: """Categorize SEO value for display.""" score = self.seo_score if score >= 60: return "High Value" elif score >= 40: return "Medium Value" elif score >= 20: return "Low Value" return "Minimal"