"""news_sources 테이블 ORM""" from datetime import datetime from sqlalchemy import Boolean, DateTime, Enum, Integer, String, Text from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy.orm import Mapped, mapped_column from core.database import Base class NewsSource(Base): __tablename__ = "news_sources" id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] = mapped_column(String(100), nullable=False) country: Mapped[str | None] = mapped_column(String(10)) feed_url: Mapped[str] = mapped_column(Text, nullable=False) feed_type: Mapped[str] = mapped_column(String(20), default="rss") category: Mapped[str | None] = mapped_column(String(50)) language: Mapped[str | None] = mapped_column(String(10)) enabled: Mapped[bool] = mapped_column(Boolean, default=True) last_fetched_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=datetime.now ) # ── A-3 (plan crawl-24x7-1) 레지스트리 증축 — migration 319 ── # fetch_method: rss / rss+page / sitemap+page / page / api / signal-only fetch_method: Mapped[str] = mapped_column(String(20), default="rss") # fulltext_policy: none(현행) / page(기사 페이지 fetch 후 4-tier 승격) / feed-full(피드 본문이 전문) fulltext_policy: Mapped[str] = mapped_column(String(20), default="none") # NULL=공개, 값=구독 세션 키 (B-3 Playwright 어댑터 슬롯) auth_profile: Mapped[str | None] = mapped_column(String(50)) # 소스별 차등 폴링 (NULL=전역 6h 사이클) poll_interval_minutes: Mapped[int | None] = mapped_column(Integer) # 조건부 GET 워터마크 — 서버가 준 값 그대로 저장·재전송 (A-1) etag: Mapped[str | None] = mapped_column(Text) last_modified: Mapped[str | None] = mapped_column(Text) # CDN ETag 회전 대비 콘텐츠 해시 변경감지 병행 (A-1) feed_content_hash: Mapped[str | None] = mapped_column(String(64)) # 추출 실패 잦은 소스의 site-specific CSS selector (A-2) selector_override: Mapped[dict | None] = mapped_column(JSONB) # rdf / table-strip / gn-redirect / skip-video 등 파서 특이 케이스 (B-5) parser_quirk: Mapped[str | None] = mapped_column(String(30)) # 채널 — 'news'(다이제스트/브리핑 대상) / 'crawl'(도메인 재료, 0-5 (a)) — migration 324. # documents.source_channel 로 전파, crawl 채널은 embed/chunk 30일 게이트 미적용. # documents 와 동일 PG enum 재사용 (Document 모델과 값 목록 동기 유지). source_channel: Mapped[str] = mapped_column( Enum("law_monitor", "devonagent", "email", "web_clip", "tksafety", "inbox_route", "manual", "drive_sync", "news", "memo", "voice", "hermes", "crawl", name="source_channel"), default="news", )