Files
hyungi 0a7402b327 feat(study): 공부 암기노트 Phase 1 — card_extract 추출 파이프라인 (순수 additive)
study_memo_cards 추출 파이프라인 + 버전키 폴러 + needs_review 컬럼. 운영 SR 코드(session_finalize/quiz_selection) 무수정.

- migrations 287~298: study_memo_cards/_evidence/_jobs/_progress(P1 휴면)·study_reminders·study_topics.focused_at·study_questions needs_review 3컬럼. dedup PARTIAL UNIQUE(deleted_at IS NULL).
- 워커: in-process RAG gather → MLX {cards} → 카드 가드(정량=evidence 원문 등장·cue/cloze 누출·dedup) → supersede 구버전 retire → append. 별 consumer 로 기존 study_queue 격리.
- 폴러 study_card_enqueue: 버전키 NOT EXISTS(source_version) 멱등 + ai_explanation_generated_at NOT NULL 가드 + per-poll LIMIT(thundering-herd).
- 검증: 실 prod 스키마 덤프 위 12 마이그 적용 OK + dedup/supersede/active-unique 기능 7/7 PASS + 정규화 util 15/15.

plan: PKM plans/2026-06-05-study-memo-card-p1-plan.html

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 21:33:12 +09:00

39 lines
1.9 KiB
SQL

-- 287_study_memo_cards.sql
-- 공부 암기노트 Phase 1: 추출 플래시카드 본체 (FK 트리 루트).
--
-- 출처(source_kind): question (P1 활성) / subject_note / document (P3 예약).
-- 포맷(format): qa (cue->fact) / cloze (빈칸). 강한 enum 미사용 — read-time 매핑.
-- needs_review DEFAULT true: 생성물이라 추출 직후 검토 대기 (study_questions 의 false 와 반대).
-- source_generated_at: 추출 당시 study_questions.ai_explanation_generated_at — stale 판정/버전 핀.
-- source_question_id 만 nullable + ON DELETE CASCADE (문제 물리삭제 시 카드 동반삭제;
-- 단 study_questions 는 soft-delete 만이라 실전은 정정/삭제 훅이 needs_review 마킹).
-- 인용(evidence) 은 별 테이블 study_memo_card_evidence (append-only).
CREATE TABLE IF NOT EXISTS study_memo_cards (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
study_topic_id BIGINT NOT NULL REFERENCES study_topics(id) ON DELETE CASCADE,
source_kind VARCHAR(40) NOT NULL,
source_question_id BIGINT REFERENCES study_questions(id) ON DELETE CASCADE,
source_subject_note_id BIGINT,
format VARCHAR(20) NOT NULL,
cue TEXT NOT NULL,
fact TEXT NOT NULL,
cloze_text TEXT,
extra JSONB,
source_generated_at TIMESTAMPTZ,
dedup_hash VARCHAR(64) NOT NULL,
needs_review BOOLEAN NOT NULL DEFAULT true,
flagged_at TIMESTAMPTZ,
flagged_by VARCHAR(40),
model VARCHAR(120),
generated_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ
);