7cd8cfde0a
A-3 migrations 319-323 (news_sources 9컬럼 + source_channel 'crawl' + process_stage 'fulltext' + source_health) A-1 조건부 GET(ETag/Last-Modified 그대로 재전송)+콘텐츠 해시 변경감지, A-4 politeness 코어(per-domain 직렬+robots+정직UA), A-2+A-7 fulltext_worker(4-tier 재사용·NAS crawl_raw gzip 보존·격하 경로·03:40 reconcile 안전망), A-5 circuit breaker(3/10 임계, enabled 미터치), A-6 포털 전재 2차 dedup(제목+3일, 12자 게이트). 기존 소스 fulltext_policy='none' 기본 = 무회귀. plan crawl-24x7-1, 예외 박제 crawl-24x7-exec1-20260610.md
46 lines
2.3 KiB
Python
46 lines
2.3 KiB
Python
"""news_sources 테이블 ORM"""
|
|
|
|
from datetime import datetime
|
|
|
|
from sqlalchemy import Boolean, DateTime, 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 등 파서 특이 케이스 (B-5)
|
|
parser_quirk: Mapped[str | None] = mapped_column(String(30))
|