da4a2e81c3
개념문서(가스기사 289) 소비 표면 개선 1단계. /study 허브를 데일리 랜딩으로.
- 마이그 381 study_concept_progress (개념 SR, sr_schedule 공용, documents FK 없음=락 회피)
- concept_curriculum 서비스 + /api/study (curriculum·today-concepts·concepts/{id}/read)
- read 상태 정본 = document_reads (is_read 컬럼 아님), mark_read=회독+SR 입고
- 문제풀이 표면 무접촉·additive
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
47 lines
2.0 KiB
Python
47 lines
2.0 KiB
Python
"""study_concept_progress — 사용자 × 개념문서 단위 간격반복(SR) 진행 (이론공부 홈).
|
||
|
||
문제 SR(study_question_progress)의 개념(이론)판. '개념문서' = documents 한 건(가스기사 태그).
|
||
회독(첫 read) → 복습 큐 진입, 이후 회독마다 sr_schedule 산술(1·3·7·14·졸업) 공용 전진.
|
||
concept_doc_id 는 documents.id 를 가리키나 FK 미설정 — hot 테이블(documents) 락 회피(clause_study 선례).
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from datetime import datetime
|
||
|
||
from sqlalchemy import BigInteger, DateTime, ForeignKey, SmallInteger, UniqueConstraint
|
||
from sqlalchemy.orm import Mapped, mapped_column
|
||
|
||
from core.database import Base
|
||
|
||
|
||
class StudyConceptProgress(Base):
|
||
__tablename__ = "study_concept_progress"
|
||
__table_args__ = (
|
||
UniqueConstraint(
|
||
"user_id", "concept_doc_id", name="uq_concept_progress_user_doc"
|
||
),
|
||
)
|
||
|
||
id: Mapped[int] = mapped_column(BigInteger, primary_key=True)
|
||
user_id: Mapped[int] = mapped_column(
|
||
BigInteger, ForeignKey("users.id", ondelete="CASCADE"), nullable=False
|
||
)
|
||
study_topic_id: Mapped[int] = mapped_column(
|
||
BigInteger, ForeignKey("study_topics.id", ondelete="CASCADE"), nullable=False
|
||
)
|
||
# documents.id 참조 — FK 없음(락 회피). 개념문서 삭제 시 고아 행은 read 집계에서 자연 제외.
|
||
concept_doc_id: Mapped[int] = mapped_column(BigInteger, nullable=False)
|
||
|
||
# 복습 큐 (sr_schedule 공용): stage 0~3 = 1·3·7·14일, 4 = 졸업(due_at NULL)
|
||
review_stage: Mapped[int | None] = mapped_column(SmallInteger)
|
||
due_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
||
last_read_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
||
|
||
created_at: Mapped[datetime] = mapped_column(
|
||
DateTime(timezone=True), default=datetime.now, nullable=False
|
||
)
|
||
updated_at: Mapped[datetime] = mapped_column(
|
||
DateTime(timezone=True), default=datetime.now, onupdate=datetime.now, nullable=False
|
||
)
|