Files

60 lines
2.5 KiB
Python
Raw Permalink Normal View History

"""Feed model."""
from datetime import datetime, timezone
from sqlalchemy import Boolean, DateTime, Integer, JSON, String, Text
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.models.base import Base, TimestampMixin, UUIDMixin
class Feed(Base, UUIDMixin, TimestampMixin):
"""RSS feed source."""
__tablename__ = "feeds"
url: Mapped[str] = mapped_column(String(2048), unique=True, nullable=False, index=True)
title: Mapped[str | None] = mapped_column(String(512), default="")
description: Mapped[str | None] = mapped_column(Text, default="")
category: Mapped[str | None] = mapped_column(String(128), default="")
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False, index=True)
fetch_interval_minutes: Mapped[int] = mapped_column(Integer, default=60, nullable=False)
priority: Mapped[int] = mapped_column(Integer, default=5, nullable=False)
parser_config: Mapped[dict] = mapped_column(JSON, default=dict, nullable=False)
proxy_policy: Mapped[str] = mapped_column(String(32), default="auto", nullable=False)
# Fetch statistics
last_fetch_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
last_fetch_status: Mapped[str | None] = mapped_column(String(32), default="")
last_error: Mapped[str | None] = mapped_column(Text, default="")
error_type: Mapped[str | None] = mapped_column(String(64), default="")
success_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
fail_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
article_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
raw_articles: Mapped[list["RawArticle"]] = relationship(
"RawArticle", back_populates="feed", cascade="all, delete-orphan"
)
def health_status(self, now: datetime | None = None) -> str:
"""Compute feed health status."""
if now is None:
now = datetime.now(timezone.utc)
total = self.success_count + self.fail_count
if total == 0:
return "unknown"
success_rate = self.success_count / total
days_since = None
if self.last_fetch_at:
days_since = (now - self.last_fetch_at).days
if success_rate >= 0.9 and (days_since is None or days_since <= 7):
return "healthy"
if success_rate >= 0.5 and (days_since is None or days_since <= 7):
return "warning"
return "unhealthy"
def __repr__(self) -> str:
return f"<Feed {self.title or self.url}>"