1842f27d89
- 채널 인지화: news_sources.source_channel(324, documents enum 재사용) → 문서 생성 정체성(_doc_identity)·embed/chunk 30일 게이트(crawl=전량 색인)· extract 후속 override(crawl→classify, preview 스킵) 분기. - B-2 Guardian Open Platform: API 디스패치(호스트 분기, 미지 호스트=명시 실패) + show-fields=bodyText 전문 어댑터. fixture live 박제 + call-shape 테스트. - B-3 구독지: playwright-fetcher 격리 컨테이너(동시 1·요청당 브라우저·storage_state ro mount) + politeness 사람속도(30-60s) 브라우저 경로 + fulltext 인증 라우팅 (내용 기반 probe 게이트·relogin_requested 소비=open-스킵보다 앞·본문 페이월 마커 게이트) + source_health probe 컬럼(325) + 세션 박제 스크립트(맥북용). - C-2 KOSHA: 3 API live 검증·fixture 박제(board/attach/guide) — 재해사례 daily diff +첨부 PDF/HWP→extract 파이프라인, GUIDE 일일 cap 점진 백필(silent cap 금지 로그). 키는 URL 직결합(재인코딩 함정 회피). daily 06:40 KST. - C-3 정적 코퍼스: National Board 86 + TWI job-knowledge 153 일괄 CLI(멱등·politeness ·crawl_raw 보존·fulltext_worker 승격 필드 규약 동일). - C-1/C-5 시드(326): 전 URL live 검증 — UK HSE(feed-full)/안전신문/고용노동부 3종 (rss/*.do)/OSHA/EU-OSHA(후보)/SEP/1000-Word(feed-full)/Doing Philosophy/Aeon/Psyche (skip-video quirk). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
45 lines
2.3 KiB
Python
45 lines
2.3 KiB
Python
"""source_health 테이블 ORM (A-5, plan crawl-24x7-1)
|
|
|
|
news_sources 와 1:1. 소스별 fetch 성공/실패 기록 + circuit breaker 상태.
|
|
silent skip 누적 방지의 가시성 기반 — A-8 헬스 패널이 읽는다.
|
|
"""
|
|
|
|
from datetime import datetime
|
|
|
|
from sqlalchemy import BigInteger, Boolean, DateTime, ForeignKey, Integer, String, Text
|
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
|
|
from core.database import Base
|
|
|
|
|
|
class SourceHealth(Base):
|
|
__tablename__ = "source_health"
|
|
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
source_id: Mapped[int] = mapped_column(
|
|
Integer, ForeignKey("news_sources.id", ondelete="CASCADE"), nullable=False
|
|
)
|
|
consecutive_failures: Mapped[int] = mapped_column(Integer, default=0)
|
|
total_fetches: Mapped[int] = mapped_column(BigInteger, default=0)
|
|
total_failures: Mapped[int] = mapped_column(BigInteger, default=0)
|
|
last_success_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
last_error: Mapped[str | None] = mapped_column(Text)
|
|
last_error_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
last_fetch_items: Mapped[int | None] = mapped_column(Integer)
|
|
# 200 인데 entries 0 인 연속 fetch 횟수 (304/해시동일은 미집계 — 피드 부패 신호 전용)
|
|
empty_streak: Mapped[int] = mapped_column(Integer, default=0)
|
|
# closed(정상) / open(연속 실패 → 지수 backoff) / disabled(임계 초과, 수동 복구 대상)
|
|
circuit_state: Mapped[str] = mapped_column(String(10), default="closed")
|
|
circuit_opened_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
updated_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=datetime.now
|
|
)
|
|
|
|
# ── B-3 구독 세션 상태 계약 — migration 325 ──
|
|
# 쓰기 1종 플래그: A-8 버튼이 기록만, 어댑터가 소비(수동 half-open).
|
|
# 소비 위치 = open-스킵 분기보다 앞 (r5 함정 고정 — 데드 버튼 방지).
|
|
relogin_requested: Mapped[bool] = mapped_column(Boolean, default=False)
|
|
# 내용 기반 probe 결과 (시간 기반 만료 판정 금지 — 페이월 안내문 silent corruption 차단)
|
|
last_probe_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
last_probe_ok: Mapped[bool | None] = mapped_column(Boolean)
|