5da94213ec
안전 자료실 plan safety-library-1 A-1 (r3 계약 반영): - documents 3컬럼 (TEXT+CHECK, nullable additive) + law→jurisdiction NOT NULL 구조 강제 - legal_acts 단일 레지스트리(워치리스트 겸, watermark·repeal_detected_at 포함) - legal_meta 최소형 (version_key 합성형 UNIQUE, 전 버전 pending 적재 계약) - partial 인덱스 2 + family 인덱스 + paper DOI partial UNIQUE (doi=서지 단일 보유 계약) - ephemeral PG16 스모크: 12파일 적용 + CHECK/UNIQUE 계약 6종 검증 PASS Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
74 lines
3.8 KiB
Python
74 lines
3.8 KiB
Python
"""legal_acts / legal_meta 테이블 ORM — 법령 레지스트리(워치리스트 겸) + 버전 위성
|
|
|
|
plan: safety-library-1 (migrations 346~347).
|
|
- legal_acts = 폴링 순회 대상 목록이 곧 테이블 (news_sources 패턴의 법령판).
|
|
KOSHA GUIDE(비법령)·KGS Code(watch-폴더 단독 트랙)는 비대상.
|
|
- legal_meta = 법령 문서 1버전(또는 별표·해석례 1건)당 1행, documents 1:0..1 위성.
|
|
version_status 전이는 statute_collector 의 일일 잡이 유일한 코드 지점
|
|
(전 버전 pending 적재 → 잡이 승격·supersede·repeal 을 한 트랜잭션 처리).
|
|
"""
|
|
|
|
from datetime import date, datetime
|
|
|
|
from sqlalchemy import BigInteger, Boolean, Date, DateTime, ForeignKey, Text, UniqueConstraint
|
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
|
|
from core.database import Base
|
|
|
|
|
|
class LegalAct(Base):
|
|
__tablename__ = "legal_acts"
|
|
|
|
# 'kr-law:{법령ID}' / 'us-cfr:29-1910' 형식. KGS 는 시드 비대상 (R3-M5).
|
|
family_id: Mapped[str] = mapped_column(Text, primary_key=True)
|
|
# 어댑터 상수 고정값 — 파싱 결과에서 추론 금지 (코어가 적재 직전 assert)
|
|
jurisdiction: Mapped[str] = mapped_column(Text, nullable=False)
|
|
# statute(법률) / decree(시행령) / rule(시행규칙·부령) / admin_rule(고시·예규) / code(법정 위임 상세기준)
|
|
law_level: Mapped[str] = mapped_column(Text, nullable=False)
|
|
title: Mapped[str] = mapped_column(Text, nullable=False)
|
|
title_ko: Mapped[str | None] = mapped_column(Text)
|
|
# 법률 → 시행령 → 시행규칙 계층
|
|
parent_family_id: Mapped[str | None] = mapped_column(ForeignKey("legal_acts.family_id"))
|
|
# 법령ID / CFR part / CELEX / e-Gov law_id 등 소스 고유 식별자
|
|
native_id: Mapped[str] = mapped_column(Text, nullable=False)
|
|
# 'law.go.kr' / 'ecfr' / 'cellar' / 'egov_v2' / 'leg_gov_uk'
|
|
source_api: Mapped[str] = mapped_column(Text, nullable=False)
|
|
# 시드 26개 전부 true — '우선순위'는 정렬일 뿐 watch 제외 아님 (R3-B1)
|
|
watch: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
|
|
poll_cycle: Mapped[str] = mapped_column(Text, nullable=False, default="daily")
|
|
# 변경이력 폴링 워터마크 — 파싱 검증 통과 후에만 영속
|
|
watermark: Mapped[str | None] = mapped_column(Text)
|
|
# 어댑터는 폐지 감지 마킹만, repealed 전이는 일일 잡 (R3-M3)
|
|
repeal_detected_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=datetime.now
|
|
)
|
|
updated_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=datetime.now, onupdate=datetime.now
|
|
)
|
|
|
|
|
|
class LegalMeta(Base):
|
|
__tablename__ = "legal_meta"
|
|
__table_args__ = (
|
|
# 버전 dedup 구조 강제 — annex 는 version_key='MST|별표N' 합성형 (R3-M4)
|
|
UniqueConstraint("family_id", "law_doc_kind", "version_key", name="uq_legal_meta_version"),
|
|
)
|
|
|
|
document_id: Mapped[int] = mapped_column(
|
|
BigInteger, ForeignKey("documents.id", ondelete="CASCADE"), primary_key=True
|
|
)
|
|
family_id: Mapped[str] = mapped_column(
|
|
ForeignKey("legal_acts.family_id"), nullable=False
|
|
)
|
|
# primary(본문) / annex(별표·서식) / interpretation(해석례)
|
|
law_doc_kind: Mapped[str] = mapped_column(Text, nullable=False, default="primary")
|
|
version_key: Mapped[str] = mapped_column(Text, nullable=False)
|
|
promulgation_date: Mapped[date | None] = mapped_column(Date)
|
|
effective_date: Mapped[date | None] = mapped_column(Date)
|
|
# pending → current → superseded / repealed. 전이는 일일 잡 단일 지점, KST 기준.
|
|
version_status: Mapped[str] = mapped_column(Text, nullable=False, default="pending")
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=datetime.now
|
|
)
|