Files
Hyungi Ahn 24142ea605 fix: Codex 리뷰 5건 수정 (critical 1 + high 4)
1. [critical] config.yaml → settings 객체에서 taxonomy 로드 (import crash 방지)
2. [high] ODF 변환: file_path 유지, derived_path 별도 필드 (무한 중복 방지)
3. [high] 법령 분할: 첫 장 이전 조문을 "서문"으로 보존
4. [high] Inbox: review_status 필드 분리 (pending/approved/rejected)
5. [high] 삭제: soft-delete (deleted_at) + worker 방어 + active_documents 뷰
   - 모든 조회에 deleted_at IS NULL 일관 적용
   - queue_consumer: row 없으면 gracefully skip

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 07:15:13 +09:00

88 lines
3.5 KiB
Python

"""documents 테이블 ORM"""
from datetime import datetime
from pgvector.sqlalchemy import Vector
from sqlalchemy import BigInteger, DateTime, Enum, String, Text
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import Mapped, mapped_column
from core.database import Base
class Document(Base):
__tablename__ = "documents"
id: Mapped[int] = mapped_column(BigInteger, primary_key=True)
# 1계층: 원본 파일
file_path: Mapped[str] = mapped_column(Text, unique=True, nullable=False)
file_hash: Mapped[str] = mapped_column(String(64), nullable=False)
file_format: Mapped[str] = mapped_column(String(20), nullable=False)
file_size: Mapped[int | None] = mapped_column(BigInteger)
file_type: Mapped[str] = mapped_column(
Enum("immutable", "editable", "note", name="doc_type"),
default="immutable"
)
import_source: Mapped[str | None] = mapped_column(Text)
# 2계층: 텍스트 추출
extracted_text: Mapped[str | None] = mapped_column(Text)
extracted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
extractor_version: Mapped[str | None] = mapped_column(String(50))
# 2계층: AI 가공
ai_summary: Mapped[str | None] = mapped_column(Text)
ai_tags: Mapped[dict | None] = mapped_column(JSONB, default=[])
ai_domain: Mapped[str | None] = mapped_column(String(100))
ai_sub_group: Mapped[str | None] = mapped_column(String(100))
ai_model_version: Mapped[str | None] = mapped_column(String(50))
ai_processed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
document_type: Mapped[str | None] = mapped_column(String(50))
importance: Mapped[str | None] = mapped_column(String(20), default="medium")
ai_confidence: Mapped[float | None] = mapped_column()
# 3계층: 벡터 임베딩
embedding = mapped_column(Vector(768), nullable=True)
embed_model_version: Mapped[str | None] = mapped_column(String(50))
embedded_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
# 사용자 메모
user_note: Mapped[str | None] = mapped_column(Text)
# ODF 변환
derived_path: Mapped[str | None] = mapped_column(Text) # 변환본 경로 (.derived/)
original_format: Mapped[str | None] = mapped_column(String(20))
conversion_status: Mapped[str | None] = mapped_column(String(20), default="none")
# 승인/삭제
review_status: Mapped[str | None] = mapped_column(String(20), default="pending")
deleted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
# 외부 편집 URL
edit_url: Mapped[str | None] = mapped_column(Text)
# 미리보기
preview_status: Mapped[str | None] = mapped_column(String(20), default="none")
preview_hash: Mapped[str | None] = mapped_column(String(64))
preview_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
# 메타데이터
source_channel: Mapped[str | None] = mapped_column(
Enum("law_monitor", "devonagent", "email", "web_clip",
"tksafety", "inbox_route", "manual", "drive_sync",
name="source_channel")
)
data_origin: Mapped[str | None] = mapped_column(
Enum("work", "external", name="data_origin")
)
title: Mapped[str | None] = mapped_column(Text)
# 타임스탬프
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
)