- schema: MathUnit enum (common/prob_stat/calculus/geometry) - User: focusUnits(Json), bojHandle(VarChar32) - ProblemSet: mathUnit optional + unique(year,examType,subjectName,mathUnit) - migration: phase10_math_units_and_ps - scripts: wipe-non-math (dry/apply), kice-import refactor (math only, 30문항을 공통22 + 확통/미적/기하 8씩으로 분할 → 2년×4 = 8 ProblemSet) - parsing strategy/ocr-fix/seed/me/study-logs: 비수학 참조 전면 제거 - data/kice, backend/uploads/problems: 비수학 폴더 삭제 - Plans.md: Phase 10 계획 확정 Phase 10A DoD 통과 — prisma validate/format OK, nest build OK, kice-import dry-run 2년 각 46문항 확인, wipe dry-run 8 set/260 problems 확인.
38 KiB
ReLoop v2 — Plans.md (Phase 7 active)
작업 히스토리 (Phase 0~6 완료) 는 .claude/archive/plans-phase0-6.md 로 이동. 이 파일은 현재 활성 Phase 만 다룬다.
Context
- Repo:
/home/erang/reloop-v2(backend NestJS+Prisma, frontend Next.js 14 + styled-components) - Deploy:
reloop.nabomhalang.co.kr/reloop-api.nabomhalang.co.kr(mailu-dev VM PM2, HEADcc946e4) - DB: MariaDB on 10.10.10.146:33006,
reloop_v2 - Persona forgetting curve 운영 중, 2nd pass review APPROVE
Phase 7 — KICE 실데이터 + 사용자 문제집 추가 + 12 페이지 재디자인 (styled-components)
목적: 2025/2026학년도 수능 실제 PDF (mailu-dev /home/dev/reloop-v2/data/kice/, 290MB) 를 텍스트 파싱해서 ProblemSet 카탈로그로 import 하고, 사용자가 원하는 문제집을 자기 목록에 추가해 개별 문항을 풀 수 있게 한다. 동시에 사용자 제공 디자인 12종을 styled-components 로 재현해 프론트엔드를 재구축한다. 수식이 많은 수학/과탐은 이미지 crop 필요성 때문에 Phase 8 별도 트랙으로 분리.
기술 결정 (사용자 확정분)
| # | 결정 포인트 | 확정 내용 |
|---|---|---|
| D1 | CSS 스택 | styled-components 유지. Tailwind 도입 안 함. 디자인 파일의 Tailwind 클래스를 styled-components CSS 로 1:1 변환. 디자인 토큰은 기존 theme.ts 에 확장 |
| D2 | PDF 파싱 방식 | pdf-parse + 정규식 기반 본문 파싱. 문제 본문·선택지·정답을 DB 에 저장해 우리 화면 디자인으로 직접 렌더. PDF Viewer 임베드 안 함 |
| D3 | 과목 범위 (MVP) | 텍스트 과목만: 국어 / 영어 / 한국사 / 사회탐구(생활과 윤리 1과목). 2025+2026 × 4 = 8 ProblemSet, 약 240 문항. 수식이 벡터인 수학/과탐은 Phase 8 (별도 이미지 crop 파이프라인) |
| D4 | 정답률 데이터 | MVP 는 baseCorrectRate: null 허용. EBSi 오답률 엑셀 수동 import 는 별도 트랙 |
| D5 | 사용자 문제집 모델 | UserProblemSet join (userId, problemSetId, addedAt, archivedAt?). 카탈로그는 공유, 추가는 개인. ProblemSet 전체를 큐에 bulk insert 하지 않고, 사용자가 개별 Problem 을 "풀기" 할 때 StudyLog 생성 |
평가원 PDF 파싱 — 휴리스틱 설계
평가원 기출 PDF 는 포맷이 매우 일관적이라 LLM 없이 정규식 + 룰베이스로 70~90% 파싱 가능. 실패 케이스만 import 스크립트에서 needs_review=true flag 로 표시하고 나중에 수동 보정.
문항 분할 규칙:
- 문항 시작:
/\n?\s*(\d{1,2})\.\s+/— 1~45 번 - 선택지 시작:
/[①②③④⑤]/— 원문자 5 종 - 공통 지문:
/\[(\d+)~(\d+)\]/— "[1~3]" 형식. 해당 범위 문항은 같은PassageFK 공유 - 정답표 파싱:
/(\d{1,2})번?\s*[①②③④⑤1-5]/또는 표 구조 raw text
Problem 모델 확장:
bodyText Text— 문제 본문 (선택지 제외)choices Json—{"1":"...","2":"...","3":"...","4":"...","5":"..."}answerNumber Int?— 1~5passageId Int?— 공통 지문 FK (별도Passage테이블)needsReview Boolean @default(false)— 파싱 엣지케이스 표시
Phase 7A — KICE 데이터 파이프라인 (텍스트 파싱)
| Task | 내용 | DoD | Depends | Status |
|---|---|---|---|---|
| 7A.1 | Prisma 스키마 확장: ProblemSet 에 audioUrls Json?. Problem 에 bodyText String? @db.Text, choices Json?, answerNumber Int?, passageId Int?, needsReview Boolean @default(false). 새 모델 Passage (공통 지문). examRound 는 기존 examType 과 중복이라 제외 |
prisma migrate dev --name kice_text_fields 성공, @prisma/client 재생성 |
- | cc:완료 [e2e3e4b] |
| 7A.2 | backend/scripts/kice-import.ts 스캐너 — data/kice/{year}/{과목}/*.pdf 순회, 생활과 윤리 특수 경로 처리, 영어 mp3 수집, dry-run 출력 |
pnpm cli:kice-import --dry-run 이 2025/2026 전체 파일을 tree 로 출력 |
7A.1 | cc:완료 [35dcf82] |
| 7A.3 | 정답표 파서: pdftotext -layout + 홀/짝 분리 + /(\d{1,2})\s*[①②③④⑤]/ + 원문자→1..5 정규화. 2025 는 이미지 PDF 라 0 bytes → answerNumber null + needsReview |
2026 4과목 정답 추출 완료, 2025 는 OCR 필요 flag | 7A.2 | cc:완료 [35dcf82] |
| 7A.4 | 문제지 파서 — 본문 분할: pdftotext -raw (2단 컬럼 physical order), /^\d{1,2}\.\s+/ monotonic 체크, ①..⑤ 위치로 본문/선택지 분할 |
2026 4과목 각 45문항 전부 본문+5 선택지 분리 | 7A.2 | cc:완료 [35dcf82] |
| 7A.5 | 공통 지문 파서: /\[\s*(\d+)\s*[~~-]\s*(\d+)\s*\]/ 감지 → Passage 레코드 생성 → 범위 내 Problem 에 passageId FK 부착 |
2026 국어/영어에서 26개 passage 생성, 각 Passage 에 2+문항 연결 | 7A.4 | cc:완료 [35dcf82] |
| 7A.6 | 파싱 실패 가드: bodyText <10 chars, choices!=5, 빈 choice → needsReview=true | 2026 영어 5 needsReview (듣기 문항), 2025 전체 130 needsReview (OCR 대상) | 7A.4, 7A.5 | cc:완료 [35dcf82] |
| 7A.7 | ProblemSet + Problem upsert: (year, examType, subjectName) upsert, Passage delete+recreate, Problem upsert (problemSetId_number), audioUrls 영어만 | 로컬 DB: 9 ProblemSet / 260 Problem / 26 Passage, 재실행 멱등성 확인 | 7A.3, 7A.6 | cc:완료 [35dcf82] |
| 7A.8 | seed.ts 의 DEMO_PROBLEMSET(2024 수학) 블록 제거 + 2024 수학 ProblemSet DB DELETE + seed 재실행 검증 |
seed.ts DEMO_PROBLEMSET 0건, seed 재실행 에러 없음, ProblemSet.count() 는 kice import 만 | 7A.7 | cc:완료 [35dcf82] |
제외 (Phase 8 별도 트랙): 수학·과탐·직업탐구·제2외국어 — 수식/도표 비율 높아서 텍스트 파싱 부적합. 이미지 crop 또는 Vision API 파이프라인이 필요해 Phase 7 에 포함하지 않음.
Phase 7B — 사용자 문제집 추가 플로우
| Task | 내용 | DoD | Depends | Status |
|---|---|---|---|---|
| 7B.1 | Prisma: UserProblemSet 모델 (userId, problemSetId, addedAt, archivedAt DateTime?, @@unique([userId, problemSetId]), @@index([userId])) |
migration 통과, User/ProblemSet 양방향 relation 정의됨 | 7A.1 | cc:TODO |
| 7B.2 | backend me/problem-sets module: GET /api/me/problem-sets (내 추가분 list), POST /api/me/problem-sets ({ problemSetId }), DELETE /api/me/problem-sets/:id (soft archive) |
데모 계정으로 3개 API 모두 200/201/204 응답. 중복 추가 시 409 | 7B.1 | cc:TODO |
| 7B.3 | frontend lib/api.ts 에 UserProblemSet, addProblemSet, listMyProblemSets, removeProblemSet 타입/헬퍼 추가 |
타입 에러 없이 컴파일, stats 페이지에서 호출 가능 | 7B.2 | cc:TODO |
| 7B.4 | frontend /study 페이지 탭 재설계: [내 문제집 / 카탈로그 / 직접 입력] 3 탭. "카탈로그" 에 전체 ProblemSet 목록 + 각 카드에 "+ 내 목록에 추가" 버튼. "내 문제집" 에 추가한 것만 표시 |
카탈로그에서 카드 클릭 → 내 목록 탭에 즉시 반영. 중복 클릭 방지 | 7B.3 | cc:TODO |
| 7B.5 | "내 문제집" 탭 상세: 선택한 ProblemSet → 문항 리스트 → 각 문항 "풀기" 버튼 → 기존 /study/form?problemId=N 으로 이동해 결과 입력 |
데모 계정에서 2026 수학 → 15번 클릭 → 결과 기록 → 복습 큐 진입까지 전 플로우 통과 | 7B.4 | cc:TODO |
Phase 7C — 디자인 토큰 확장 + styled-components primitives 증축
기존 theme.ts + primitives.tsx 를 유지하고 디자인 파일의 토큰/컴포넌트만 이식한다. Tailwind 도입 없음.
| Task | 내용 | DoD | Depends | Status |
|---|---|---|---|---|
| 7C.1 | frontend/src/styles/theme.ts 확장 — 새 컬러 (bgDeep #0F0F14, surfaceDeep #15151C, surfaceHover #1A1A24, borderSoftAlpha rgba(255,255,255,0.06), brandGradient 함수), shadow (glowIndigo/glowViolet/cardElevated), keyframes(fadeInUp/float/slideToast/shimmer), font.display (Inter) |
theme.color.bgDeep 등 5+ 신규 토큰 접근 가능, 기존 토큰과 공존 |
- | cc:완료 [ba4d6e4] |
| 7C.2 | Phosphor Icons React 도입 — pnpm add @phosphor-icons/react. components/ui/Icon.tsx 어댑터 (name 매핑 + size/weight props) |
<Icon name="squares-four" weight="regular" size={20} /> 가 디자인 파일의 <i class="ph ph-squares-four"> 와 시각적으로 동일 |
7C.1 | cc:완료 [0d2303a] |
| 7C.3 | Inter + JetBrains Mono 웹폰트 — next/font/google 로 Pretendard(유지) 외에 Inter, JetBrains Mono 로딩. theme.font.display / theme.font.mono 바인딩, FOUT 없음 |
3종 웹폰트가 production HTML 에 load | 7C.1 | cc:완료 [140a7c3] |
| 7C.4 | components/ui/primitives.tsx 증축 — 새 variants: Button $variant="white" (흰색 CTA, 디자인 1의 "지금 시작"), Card $variant="glow" (radial glow), KbdHint (키보드 단축키 pill), StatCard (대형 숫자 카드 레이아웃), AppShellLayout |
5+ 신규 export, TypeScript 통과 | 7C.1 | cc:완료 [b9874ad] |
| 7C.5 | components/ui/Toast.tsx 신규 — slideToast 애니메이션, undo 버튼 + 원형 progress ring (2초 타이머). showToast({ message, onUndo }) 헬퍼 |
toast 호출 시 bottom-center 슬라이드 인, 2초 후 자동 dismiss 또는 undo 클릭 시 콜백 | 7C.1, 7C.2 | cc:완료 [eb10dfe] |
Phase 7D — 페이지 재디자인 (12종)
| Task | 내용 | DoD | Depends | Status |
|---|---|---|---|---|
참조 디자인 파일: /home/erang/reloop-v2/.claude/archive/designs/design-NN.html (Tailwind 기반 mockup, styled-components 로 1:1 이식). Tailwind 도입하지 않음 — 클래스를 styled-components CSS 로 변환. |
| Task | 내용 | DoD | Depends | Status |
|---|---|---|---|---|
| 7D.1 | AppShell (사이드바) — design-01.html 의 aside.w-64 레이아웃 (256px, 다크 bgDeep, indigo radial glow blur-80px, backdrop-blur-xl). 로고 indigo→violet gradient + Phosphor ph-infinity, nav 5 항목(대시보드/복습/학습 기록/통계/과목 및 태그) Phosphor icons, 활성 라우트 bg-white/5 + border-white/5, 하단 user card (아바타 + nickname + plan badge + caret 드롭다운). 모바일에서는 BottomNav 로 대체 |
기존 SideNav.tsx 교체, /dashboard 진입 시 새 사이드바 렌더, 활성 라우트 강조, 로그아웃 클릭 동작 |
7C.5 | cc:완료 [d35fc08] |
| 7D.2 | Landing (/) — design-02.html 1:1 이식. nav + hero "잊기 직전에 딱 한 번 더" + screenshot mockup + features 3카드 + how-it-works 4단계 + stats 카운터 + pricing teaser + FAQ + footer. SSR/하이드레이션 (로그인 상태면 /dashboard 리다이렉트는 middleware 쿠키) |
로그아웃 상태에서 / 200, 모든 섹션 렌더, 로그인 사용자는 /dashboard 리다이렉트 |
7C.5 | cc:완료 [0605ba8] |
| 7D.3 | Auth (/login + /register) — design-04.html 이식. 단일 카드 UI, 로그인/회원가입 탭 토글, 이메일/비번/닉네임 필드, 에러 shake 애니메이션, "로그인 없이 체험하기" = 데모 계정 자동 로그인 |
두 경로 모두 같은 컴포넌트 사용 (URL path 로 초기 탭 결정), 기존 auth flow 유지 + 에러 UI | 7C.5 | cc:완료 [750378c] |
| 7D.4 | Pricing (/pricing) — design-03.html 이식. 월/연 토글, Free/Pro(가장 인기)/School 3 카드, "모든 플랜 공통 4 기능", 상세 기능 비교 테이블(3 섹션 구조), FAQ 4개, 바닥 CTA |
토글 동작 시 가격 숫자 전환, 테이블 모바일 숨김, FAQ 아코디언 | 7C.5 | cc:완료 [92e054c] |
| 7D.5 | Dashboard filled (/dashboard) — design-01.html 우측 main 영역 이식. 헤더 (오늘 N개 복습 대기 + streak 뱃지 ph-fill ph-fire), 3 통계 카드 (오늘 큐 / 주간 정답률 + sparkline / 마스터리 원형 SVG 34%), "곧 내 것이 되는 영역" 리스트, "최근 학습" 리스트, 적응형 학습 코치 배너. 실제 API (/dashboard/summary) 연동 |
데모 계정 로그인 후 /dashboard 에서 실데이터 렌더, 마스터리 원형 SVG + streak 숫자 정확 |
7D.1 | cc:완료 [eeb772a] |
| 7D.6 | Dashboard empty (/dashboard) — design-08.html 이식. Welcome + 3단계 체크리스트. 조건: subjects.length === 0 && studyLogs.length === 0 에 자동 전환 |
신규 계정 (/register 직후) 은 empty 상태 렌더, 과목 1개 이상 생기면 filled 로 | 7D.5 | cc:완료 [e79c4d8] |
| 7D.7 | Review 세션 (/review) — design-05.html 이식. 집중 모드 헤더 (뒤로 + ReLoop 로고 + 집중 모드 뱃지), Day N 제목 + 진행률 바 + 키보드 힌트, 카드 스택 (3 layer stacked), 과목 뱃지 + 회차 + 예측 P%, 메모 블록, 4 액션 버튼 (틀림/부분/맞음/스킵) 색상별, Toast "결과 기록됨 + undo 2s" |
2026 큐에서 카드 전체 처리 + undo 동작 + 단축키 1/2/3/S | 7D.1 | cc:완료 [fdc3e4f] |
| 7D.8 | Study History (/study/history) — design-10.html 5 컬럼 테이블 (항목/과목·태그/날짜·시간/결과/자신감) 이식. 검색 + 과목 필터 + 날짜 필터 + 결과 탭. 페이지네이션 (20건/페이지). design-12.html 는 참고용 대안 |
/api/study-logs 전체 목록이 테이블 렌더, 필터 동작, 빈 상태 UI |
7D.1 | cc:완료 [46c6a45] |
| 7D.9 | Subjects & Tags (/subjects) — design-07.html 이식. 상단 tabs (전체/과목별), 과목 섹션 카드 (색상 dot + 이름 + 마스터리 %), 각 섹션 안에 태그 리스트 (P-value + bar + 수정/보관 액션) |
/subjects 에서 실제 데이터 렌더, 태그 편집 인라인, 보관 soft archive 동작 | 7D.1 | cc:완료 [0850507] |
| 7D.10 | Stats (/stats) — design-11.html 이식. 학습 히트맵 (주×월 grid) + 과목별 정답률 bar 리스트 + 마스터리 P-value 성장 곡선 (SVG path). 기존 /api/stats/* + /api/stats/mastery-path 활용. design-06.html 는 참고용 대안 |
3 패널 전부 실데이터 렌더, 곡선 mastery-path 응답 기반 | 7D.1 | cc:완료 [0e61f0e] |
| 7D.11 | Settings/Profile (/profile) — design-09.html 이식. 계정 + Pro plan 카드, 학습 목표 슬라이더, 알고리즘 민감도 3 카드, 3 toggle 알림, 연속 학습 달력 (7 열 heatmap), 데이터 export / 진행 초기화 / 로그아웃 |
모든 설정이 /me/profile PATCH 와 연동, 변경사항 저장 시 즉시 반영 |
7D.1 | cc:완료 [22f3be2] |
| 7D.12 | Study 폼 (/study) — 디자인 제외 영역. 기존 기능 유지 + Phase 7B 의 "내 문제집" 탭 추가는 7B 에서. 이 태스크는 기존 탭들을 새 theme/primitives 로 시각적 통일만 |
기존 플로우 유지, 새 theme 컬러/폰트 적용 | 7C.5 | cc:완료 [22ee6ab] |
Phase 7F — 7D 피드백 리비전 (용어 순화 + 모의고사 + 모바일)
사용자 피드백 3건을 정리한다.
| Task | 내용 | DoD | Depends | Status |
|---|---|---|---|---|
| 7F.1 | 수학 용어 순화 — UI 전반에서 P, P-value, P=0.85, P ≥ 0.9 같은 수학 표기를 제거하고 "예상 정답률", "숙련도", "마스터 기준(90%)" 같은 자연어로 교체. 변경 대상: dashboard/stats/review/subjects/profile/dashboard-empty 등 모든 UI 텍스트. 내부 변수명/API 필드는 유지, 화면에 표시되는 문자열만 변경 |
모든 페이지에서 "P=" / "P-value" / "P ≥" 리터럴 0건, 의미 동등한 자연어로 대체 | 7D.12 | cc:완료 [cbc3af2] |
| 7F.2 | 모의고사 학습 플로우 — /study 의 "새 학습 기록" 화면에 "문제집에서 풀기" 탭 추가. 탭에서 Phase 7A 로 import 된 KICE ProblemSet 을 선택 → 모의고사 인터페이스 (시험지 느낌의 문제 네비게이션, 카운트다운 타이머, 1-5 선택지 버튼, 표시/건너뛰기, 답안지 확인 모드, 제출 후 각 문항 자동 채점 + StudyLog 일괄 생성). 과목별 실제 시험 시간 매핑 (국어 80분/영어 70분/한국사·탐구 30분) |
데모 계정으로 2026 국어 선택 → 타이머 동작 → 문항 번호 클릭 네비 → 답 체크 → 제출 → 각 Problem 당 StudyLog row 생성 + /dashboard 의 "최근 학습" 에 즉시 반영 | 7A.7, 7D.12 | cc:완료 [31d62fb] |
| 7F.4 | 파서 리팩토링 + segmentation 수정 — backend/scripts/kice-import.ts 의 파싱 로직을 backend/src/problem-sets/parsing/ 모듈로 추출 (범용 설계, 전략 기반). 페이지 헤더/푸터 strip (제N교시, 홀수형/짝수형, 2026학년도 대학수학능력시험 문제지 N, 페이지 번호, 저작권 문구), 문제 경계 정확도 (다음 \d{1,2}\.\s+ OR 다음 [N~M] OR EOF 중 최소값), passage 헤더 포함 ([1~3] 다음 글을 읽고… 를 본문 안에 보존), ⓐⓑⓒⓓⓔ 각주 마커 유지. 재임포트 후 샘플 문항 수동 검증. 향후 업로드 엔드포인트에서 재사용 가능한 순수 함수 시그니처 (buffer 입력) |
2026 국어/영어/한국사/생활과윤리 재임포트 후 needsReview ≤ 5% (듣기 외), 문항 3번 선택지 안에 다른 지문/헤더 오염 0건, passage 첫 줄 "다음 글을 읽고…" 포함 | 7A.7 | cc:완료 [c9935cf] |
| 7F.5 | OCR fallback (Vision API) — 7F.4 이후에도 needsReview 로 남는 엣지케이스 문항만 타겟. backend/scripts/ocr-fallback.ts 스크립트가 pdftoppm 으로 해당 페이지 crop 렌더 → Claude Vision API 로 구조화 JSON 추출 → Problem 레코드 업데이트. 2025 4과목 답안지 (이미지 PDF) 도 이 경로로 처리 |
2026 4과목 needsReview 0건, 2025 4과목 답안지 OCR 결과로 answerNumber 전부 채움 | 7F.4 | cc:완료 [dcf1eb6] |
| 7F.3 | 모바일 반응형 스윕 — 12 개 신규 페이지 전부 모바일 브레이크포인트 (theme.breakpoint.tablet 이하) 대응. 사이드바 숨김 + BottomNav 노출, 그리드 스택, 테이블 카드 레이아웃 전환, 폰트 크기 축소, 패딩 축소. 특히 /dashboard 3카드 1열, /pricing 3카드 1열, /study/history 테이블 → 카드 리스트, /stats 3패널 1열, /review 카드 스택 풀스크린, /subjects 태그 리스트 간격 축소, 모의고사 (7F.2) 인터페이스 모바일 터치 최적화 |
iPhone SE (375×667) 뷰포트에서 12 페이지 전부 가로 스크롤 없음, 주요 버튼 터치 영역 44px+, 모바일에서 사이드바 숨김 확인 | 7F.2 | cc:완료 [80b3e98] |
Phase 7G — 사용자 피드백 리비전
| Task | 내용 | DoD | Depends | Status |
|---|---|---|---|---|
| 7G.1 | IA 재구성 + 모바일 로그아웃 — /exams 신규 top-level 라우트로 문제집 카탈로그 분리. SideNav 에 "문제집" 항목 추가, /study 폼에서 "문제집에서 풀기" 탭 제거. 시험 페이지 우상단 "집중 모드" 토글 버튼 (ON=풀스크린 / OFF=AppShell 포함). /login /register 상단 "← 홈으로" 링크. 모바일에서 로그아웃/프로필 접근 (BottomNav 에 프로필 탭 또는 상단 메뉴) |
/exams 진입 가능, study 폼 문제집 탭 제거, 집중모드 토글 동작, 로그인에서 홈 이동, 모바일 로그아웃 접근 가능 | 7F.5 | cc:완료 [47a70f2] |
| 7G.2 | 시험 UX 전면 개선 — 네비 4버튼 (이전/건너뛰기/다음/항목이동) 전부 노출, 시험 종료는 ⋮ 메뉴로. 데스크톱/태블릿 문제 패널 드래그 리사이즈 (localStorage). 모바일 탭 시 확대 모달. 자동 채점 제출 시 Subject+Tag 자동 생성/매칭 보장. Problem.imageUrl 렌더 (7H.1 의존). Subject+Tag 자동 생성 강화: topic 필드 → Tag |
4 버튼 동작, 시험 종료 숨김, 데스크톱 리사이즈, 모바일 확대, 제출 후 과목/태그 자동 생성 확인 | 7G.1 | cc:완료 [7c5cdf4] |
| 7G.3 | StudyLog 상세 + 복습 파이프라인 검증 — /study-logs/[id] 신규 상세 라우트 (문항 재열람: 본문/선택지/정답/내 답/메모). 대시보드 "최근 학습" + 학습 기록 행 클릭 → 상세 이동. 모의고사 제출 → ReviewSchedule 생성 플로우 검증 + 오늘 복습 큐 진입 확인. 첫 시도 S₀ 시드 조정 (baseCorrectRate 반영) |
문항 상세 재열람, 모의고사 제출 후 24h 내 복습 큐 등장, 초기 복습 간격 난이도 반영 | 7G.2 | cc:완료 [9068fcb] |
| 7G.4 | 랜딩 리파인 + D-day 동기화 + 애니메이션 — Hero typing effect (3-5개 격려 문구 rotation), 수능 D-day 실시간 계산 (고정 상수 = 2026-11-13, 매일 자동 갱신), 전역 소프트 애니메이션 (카드 hover/scroll fade-in via Intersection Observer), dashboard/review/stats 문구 동기부여 톤 재작성 | Typing effect 동작, D-day 매일 자동 갱신, 스크롤 애니메이션 부드러움, 톤 개선 | 7C.5 | cc:완료 [feb1a62] |
| 7G.5 | 통계 히트맵 리사이즈 + 테마드 폼 컴포넌트 + 과목 페이지 수정 — 통계 히트맵 50% 축소 + 요일 라벨 + 클릭 시 해당일 드로어. components/ui/Select.tsx + DatePicker.tsx (다크 테마). /study/history 필터/profile 셀렉터/subjects 정렬 전부 새 컴포넌트로 교체. 과목 페이지 모바일 탭 버튼 세로 글씨 수정 → horizontal scroll pill |
히트맵 축소 + 기능, 모든 드롭다운 다크 테마, 과목 탭 가로 정렬 | 7D.10 | cc:완료 [e332d27] |
| 7G.6 | 프로필 + 아바타 + 온보딩 — Backend: User.avatarUrl, POST /me/avatar multipart (backend/uploads/avatars/). Frontend /profile 아바타 업로드 UI. /onboarding 플로우 보강 (현재/목표 등급, 중점 과목, 수능 연도, 페르소나). 신규 가입 후 자동 진입. 프로필 드롭다운 모바일 접근 |
아바타 업로드 + 저장 + URL 반환, 온보딩 완전 동작, 프로필 드롭다운 모바일 접근 가능 | 7G.1 | cc:완료 [a5dab35] |
Phase 7H — 수학 + 이미지 기반 문제 지원
| Task | 내용 | DoD | Depends | Status |
|---|---|---|---|---|
| 7H.1 | 수학 데이터 + 스키마 + 이미지 파이프라인 — mailu-dev 에서 2025/2026 수학 PDF rsync. Prisma 확장 Problem.imageUrl String?, Problem.pageImageUrl String? + migration. parsing/strategies/math.ts — pdftoppm 으로 per-problem PNG 렌더. 이미지는 backend/uploads/problems/{year}/{subject}/{number}.png. kice-import --subject=수학 동작, 답안지 OCR fallback 재사용 |
2025/2026 수학 각 30문제 import 완료, imageUrl 채움, 답안 자동 채점 | 7F.4, 7F.5 | cc:완료 [acc35e9] |
| 7H.2 | 프론트 이미지 렌더 + 시험 UI 통합 — Problem 타입에 imageUrl 추가. 시험/복습 UI: imageUrl 있으면 <img> 메인. 사진/표 포함 문제도 동일 경로. /uploads static 서빙 NestJS |
수학 문항 시험 화면에서 정상 렌더, 이미지 포함 문제 시각 표시 | 7H.1, 7G.2 | cc:완료 [8a91ab4] |
Phase 8 — 판매 준비 (production ready)
| Task | 내용 | DoD | Depends | Status |
|---|---|---|---|---|
| 8.1 | PWA + SEO 메타 — manifest.webmanifest, 앱 아이콘 세트 (192/512 + apple-touch), theme-color, per-route generateMetadata 로 OG 태그, robots.txt, sitemap.xml |
/ 설치 배너, OG 미리보기, robots/sitemap 서빙 |
7G.6 | cc:완료 [7ba7b4a] |
| 8.2 | 법적 페이지 + 404/500 + 에러 바운더리 — /privacy, /terms, /contact 페이지 (footer 링크). app/not-found.tsx, app/error.tsx, app/global-error.tsx 테마드 페이지 |
4 경로 렌더, 404/500 테마드 | 7G.4 | cc:완료 [eb0e07b] |
| 8.3 | 보안 + rate limit + 환경 하드닝 — helmet 강화, CORS 화이트리스트, @nestjs/throttler 전역 rate limit (분당 60), 데모 계정 평문 비밀번호 stdout 제거, .env 템플릿 정리 |
로그인 스팸 60/분 초과 429, seed 평문 노출 0 | 7G.6 | cc:완료 [c36978c] |
Phase 7I — 2차 피드백 리비전 (이미지 우선 + UX)
| Task | 내용 | DoD | Depends | Status |
|---|---|---|---|---|
| 7I.1 | 전 과목 per-problem 이미지 crop — pdftotext -bbox 로 문제 번호 Y좌표 추출 → pdftoppm -x -y -W -H 으로 문제별 PNG crop → uploads/problems/{year}/{subject}/{number}.png 저장. 모든 과목 적용. 텍스트 파싱은 보조, 이미지가 주 표시. 재임포트 후 320 문항 전부 imageUrl 보유 |
2025/2026 × 5과목 재임포트, 320 problems 전부 imageUrl, 수학 포함 정상 | 7H.1 | cc:완료 [e76e14e] |
| 7I.2 | chosenAnswer + 개별 풀기 + 프로필 통합 — (a) StudyLog.chosenAnswer Int? migration. 제출 시 저장. (b) /solve/[studyLogId] 개별 문제 풀기 라우트. (c) /profile + /settings 통합: 단일 페이지 탭 |
chosenAnswer 저장 + detail 표시, 개별 풀기, 프로필 단일 페이지 | 7I.1 | cc:완료 [4c61b87] |
| 7I.4 | 3차 피드백 일괄 — (a) 집중모드 기본 OFF (review+exam localStorage 기본값 false). (b) /profile 등급 슬라이더 네이티브 HTML → 테마드 커스텀 슬라이더. (c) 문제 표시 이미지 ONLY (텍스트 파싱 제거, 선택지도 이미지). 수학 등 OMR 스타일 1-5 버블 (식 표기 대신). (d) 이미지 가운데 정렬. (e) 국어 지문+문제 세트: passage 이미지 crop 도 생성 + 시험 UI 에서 지문 이미지 먼저 표시. (f) /pricing 연결 (로그인 후 접근). (g) 영어 듣기 오디오 재생기 (mp3 서빙 + <audio> UI). (h) / 메인 typing effect 속도 줄임 + 긴 타이틀 overflow 수정 + 전반 애니메이션 속도 감속 |
모든 항목 동작 확인 | 7I.3 | cc:완료 [d14fde3] |
| 7I.3 | UI 일괄 수정 — (a) 복습 집중모드 토글. (b) 통계 3패널 재배치. (c) 시험 간격/그림자/긴 지문. (d) SideNav 프로필 고정. (e) 배경 통일 | 복습 토글, 통계 깔끔, 시험 가독성, SideNav 고정, 배경 일관 | 7I.2 | cc:완료 [59f6652] |
Phase 9 — 학교/학원 관리 (School Plan)
목적: 선생님(관리자)이 학급을 만들고, 학생을 초대하고, 문제집을 과제로 배정하고, 학생별 진도/성적을 추적할 수 있는 기관 관리 기능. Pricing 의 "School" 플랜에 대응.
| Task | 내용 | DoD | Depends | Status |
|---|---|---|---|---|
| 9.1 | 스키마 + 백엔드 API (Organization/Class/Assignment) | 엔드포인트 200, seed, migration | 8.3 | cc:완료 [f4b80d3] |
| 9.2 | 선생님 대시보드 + 학급 + 초대 (9.5 통합) | 대시보드/학급/초대/역할 분기 | 9.1 | cc:완료 [bed5b80] |
| 9.3 | 과제 CRUD + 제출 현황 UI | 과제 생성→풀기→현황 전 플로우 | 9.2 | cc:완료 [8ebd419] |
| 9.4 | 학생 진도 + 리포트 (3 차트) | 학생 상세, 학급 리포트 렌더 | 9.3 | cc:완료 [f9c8b9a] |
Phase 10 — KICE 비수학 제거 + Solved.ac PS 통합 + 온보딩 재설계
목적: 저작권 리스크(비수학 KICE 기출)를 제거하고, 수능 수학 4단원(공통/확통/미적/기하) + Solved.ac 기반 PS 두 가지 학습 트랙만 남긴 구조로 재편한다. 사용자는 온보딩에서 수학 수능 / PS / 자격증 / 어학 / 취미 중 목표를 선택하고, 수학 수능 선택 시 세부 단원을, PS 선택 시 solved.ac 핸들을 연결한다. Solved.ac 풀이 이력은 자동 sync 되어 StudyLog 로 복습 큐에 진입한다.
기술 결정 (Phase 10)
| # | 결정 포인트 | 확정 내용 |
|---|---|---|
| P10-D1 | 수학 단원 모델 | enum MathUnit { common, prob_stat, calculus, geometry } — common = 수학Ⅰ+Ⅱ, 선택 3종(확률과 통계/미적분/기하). ProblemSet.mathUnit MathUnit? 으로 nullable 추가 |
| P10-D2 | KICE 30문항 단원 분할 | 공통 22문항 (1 |
| P10-D3 | 비수학 데이터 처리 | Dev DB 에서 ProblemSet.subjectName NOT IN ('수학') 에 연결된 Problem/StudyLog/ReviewSchedule/AssignmentSubmission cascade delete. 되돌리기 불가 — 마이그레이션 전 mysqldump 백업 필수 |
| P10-D4 | Solved.ac 통합 깊이 | (c) 풀: 검색+북마크 + 핸들 자동 sync. SolvedAcClient 는 https://solved.ac/api/v3 공개 API 사용(인증 불필요), user/show + user/problem_stats + user/top_100 조합으로 bulk import |
| P10-D5 | PS → StudyLog 매핑 | PsProblem 별도 테이블에 캐시. StudyLog.problemId 는 KICE Problem FK 라 재사용 불가 → StudyLog.psProblemId Int? 추가. subject 는 자동 생성되는 시스템 Subject "PS" 로 묶음 |
| P10-D6 | 온보딩 GoalType | 'math-suneung' | 'ps' | 'certificate' | 'language' | 'none'. User.focusUnits Json? 추가(수학 단원 멀티셀렉트 저장), User.bojHandle String? 추가 |
| P10-D7 | 기존 focusSubjects | focusSubjects는 그대로 유지하되 수학 선택지만 남김. 자격증/어학/취미 트랙은 자유 입력 유지 |
| P10-D8 | 학교/학원 기능 | Phase 9 Assignment 는 수학 ProblemSet 에 대해서만 작동. 스키마 변경 없음 |
Phase 10A — 스키마 + KICE 비수학 제거
| Task | 내용 | DoD | Depends | Status |
|---|---|---|---|---|
| 10A.1 | Prisma 스키마: MathUnit enum, ProblemSet.mathUnit MathUnit?, User.bojHandle String? @db.VarChar(32), User.focusUnits Json?. migration phase10_math_units_and_ps 생성 |
prisma migrate dev --name phase10_math_units_and_ps 성공, @prisma/client 재생성, 기존 행 NULL 기본값 |
- | cc:TODO |
| 10A.2 | 비수학 데이터 wipe 스크립트 backend/scripts/wipe-non-math.ts — ProblemSet where subjectName != '수학' 연쇄 삭제(Assignment/AssignmentSubmission, StudyLog, ReviewSchedule, Problem, Passage 포함). 실행 전 mysqldump 백업을 backups/ 에 저장 |
dry-run 시 삭제 대상 수 출력, --apply 시 실제 삭제 후 ProblemSet count where subjectName='수학' 만 남음 |
10A.1 | cc:TODO |
| 10A.3 | kice-import.ts 리팩터: TARGET_SUBJECTS = ['수학'], 30문항을 1ProblemSet.title 은 YYYY학년도 수능 수학 (공통) 형태 |
pnpm cli:kice-import --dry-run --year=2026 시 common 22 + 확통 8 + 미적 8 + 기하 8 = 46 문항 리포트 |
10A.2 | cc:TODO |
| 10A.4 | backend/src/problem-sets/parsing/strategies/kice.ts 및 관련 OCR 프롬프트에서 비수학 분기 제거. shared.ts·ocr-fix.ts·seed.ts 에서 비수학 과목 참조 삭제 |
grep -rE "국어|영어|한국사|생활과 윤리|사회탐구" backend/src backend/scripts 결과 0 |
10A.3 | cc:TODO |
| 10A.5 | data/kice/{2025,2026}/{국어,영어,한국사,사회탐구}/ 삭제 + .gitignore 정리. backend/uploads/problems/{year}/{비수학 과목}/ 도 삭제 |
디스크에 수학 PDF + 수학 이미지 crop 만 남음 | 10A.4 | cc:TODO |
Phase 10B — Solved.ac PS 모듈
| Task | 내용 | DoD | Depends | Status |
|---|---|---|---|---|
| 10B.1 | Prisma: model PsProblem { id Int, bojId Int @unique, title, level Int, tags Json, acceptedUserCount Int?, averageTries Float?, updatedAt }, model PsBookmark { userId, psProblemId, createdAt @@unique([userId,psProblemId]) }, StudyLog.psProblemId Int? FK 추가. migration phase10_ps_tables |
prisma migrate dev 성공, 관계 매핑 검증 |
10A.1 | cc:TODO |
| 10B.2 | backend/src/ps/solved-ac.client.ts — axios 인스턴스(https://solved.ac/api/v3, User-Agent 명시, 타임아웃 10s, 지수 백오프 3회). 메서드: searchProblems(query, page), getProblem(bojId), getUser(handle), getUserSolvedProblems(handle, page). 내부 rate-limit (초당 3req) |
단위 테스트: mocked axios 로 4 메서드 응답 파싱 정상 | 10B.1 | cc:TODO |
| 10B.3 | PsModule + PsService + PsController — endpoints: GET /api/ps/search?q=&level=&page=, GET /api/ps/problems/:bojId, POST /api/ps/bookmarks { bojId }, DELETE /api/ps/bookmarks/:bojId, GET /api/ps/bookmarks, POST /api/ps/sync { bojHandle? } (핸들 없으면 User.bojHandle 사용). JWT guard + throttler |
Postman/curl 6 endpoint 200, sync 실행 시 PsProblem upsert + 시스템 Subject "PS" 생성 + StudyLog bulk insert, 멱등성 확인(중복 sync 시 같은 BOJ 문제 StudyLog 추가 생성 안 함) | 10B.2 | cc:TODO |
| 10B.4 | Sync 멱등성 저장소: PsSyncState { userId @unique, lastSyncedAt, lastSolvedCount } 추가해 마지막 sync 이후 새로 맞힌 문제만 가져옴. 초기 sync 는 top_100 기준 상위 100개로 제한(과도한 StudyLog 폭주 방지) |
1차 sync 후 PsSyncState.lastSolvedCount 저장, 2차 sync 는 차분만 insert |
10B.3 | cc:TODO |
Phase 10C — 온보딩/프론트 재구성
| Task | 내용 | DoD | Depends | Status |
|---|---|---|---|---|
| 10C.1 | frontend/src/app/onboarding/page.tsx — GoalType 옵션 라벨/설명 교체. math-suneung 선택 시 4 단원 체크박스 그룹 노출, ps 선택 시 bojHandle 인풋(정규식 검증 ^[a-zA-Z0-9_-]{3,20}$) 노출 |
각 GoalType 분기 UI 정상, 저장 후 /auth/me 응답에 focusUnits 또는 bojHandle 반영 |
10B.1 | cc:TODO |
| 10C.2 | frontend/src/lib/api.ts + MeUser 타입: bojHandle, focusUnits: MathUnit[], GoalType 유니언 갱신. api/me PATCH payload 확장 |
타입 에러 0, API PATCH 200 | 10C.1 | cc:TODO |
| 10C.3 | 전역 과목 리스트 정리 — subjects/page.tsx, exams/page.tsx, review/page.tsx, review/history/page.tsx, dashboard, school/* 에서 비수학 하드코딩 참조 제거. exam/shared.ts::getExamDurationSeconds → 수학 100분 고정. SideNav 에서 국어/영어/한국사 링크 제거 |
grep -rE "국어|영어|한국사|생활과 윤리" frontend/src = 0 |
10C.2 | cc:TODO |
| 10C.4 | frontend/src/app/ps/ 신규 섹션 — page.tsx(검색 + 필터 + 북마크 버튼), bookmarks/page.tsx(북마크 목록), sync/page.tsx(핸들 동기화 버튼 + 상태). BottomNav/SideNav 에 PS 진입점 추가 |
4 경로 200 + search/bookmark/sync e2e 수동 검증 통과 | 10C.2 | cc:TODO |
| 10C.5 | landing/pricing/dashboard 카피 업데이트 — "수능 국어/영어…" 문구를 "수학 수능 + PS" 로 교체, 아카이브 design HTML 과 docs 에서도 혼동 없도록 정리 |
grep -rE "국어|영어|한국사|생활과 윤리" frontend/src docs README.md = 0 (아카이브 .claude/archive/ 는 제외) |
10C.3 | cc:TODO |
Phase 10D — Dev 배포 + 검증
| Task | 내용 | DoD | Depends | Status |
|---|---|---|---|---|
| 10D.1 | 로컬 커밋 + push → Gitea hanarang/reloop-v2 master. commit 단위는 10A / 10B / 10C 별로 나눔 |
git push 성공, tea pr list 없이 master 직접 반영 |
10C.5 | cc:TODO |
| 10D.2 | ssh dev@10.10.10.169 → cd ~/reloop-v2 && git pull. backend 에서 mysqldump reloop_v2 > backups/phase10-pre.sql 실행 후 pnpm --filter backend exec prisma migrate deploy → pnpm --filter backend build |
마이그레이션 적용됨, dist/main.js 빌드 성공, DB 백업 존재 | 10D.1 | cc:TODO |
| 10D.3 | 비수학 wipe 스크립트 운영 실행 — node backend/dist/scripts/wipe-non-math.js --apply. 이어서 pnpm cli:kice-import --year=2025 && --year=2026 로 수학 8 ProblemSet 재생성 |
DB 에 수학 8 ProblemSet / 92 problems, 비수학 0 | 10D.2 | cc:TODO |
| 10D.4 | Frontend 빌드 + standalone copy + pm2 reload reloop-web reloop-api --update-env |
PM2 status online, curl https://reloop-api.nabomhalang.co.kr/health 200, curl https://reloop.nabomhalang.co.kr 200 |
10D.3 | cc:TODO |
| 10D.5 | 스모크 테스트 — 온보딩 math-suneung 플로우 + 단원 선택 저장, ps 플로우 + 더미 BOJ 핸들 sync, /ps/search 검색, /exams 에 수학 4단원 표시, /review 에 비수학 흔적 없음 |
5 시나리오 수동 통과 스크린샷 또는 로그 캡처 | 10D.4 | cc:TODO |
Phase 10 범위 / 비 범위
포함:
- KICE 비수학 과목(국어/영어/한국사/생활과 윤리) 코드·데이터·DB 전량 제거
- 수학 4단원 분할(common/prob_stat/calculus/geometry) + 재import
- Solved.ac 기반 PS 모듈 (검색/북마크/핸들 자동 sync → StudyLog)
- 온보딩 재설계 (
math-suneung세부 단원 /psbojHandle) - Dev 배포 + DB 백업/마이그레이션/스모크
제외 (별도 트랙):
- Solved.ac 인증 API(로그인 연동) — 공개 API 로 충분
- PS 문제 로컬 렌더링 — BOJ 링크 아웃바운드 유지
- 수학 30문항 → 단원 분할 비율 조정(정밀 매핑표 필요 시 별도 Phase)
- 기존 사용자 데이터 마이그레이션 보전(비수학 StudyLog 는 cascade delete)
- i18n / 영어 UI / PWA
- 이 Phase 에서는 새 UI 디자인 토큰 변경 없음
실행 모드
- 전량
harness-work --codex위임, Lead 세션이 codex helper 를 통해 Task 단위로 구현/검증 - 배포(10D)는 ssh + PM2 수동 단계 포함 → Lead 세션이 Bash 로 직접 실행
- DB 파괴적 작업(10A.2, 10D.3) 실행 전 mysqldump 백업 필수
범위 / 비 범위
포함:
- KICE 2025/2026 × 5 과목 = 10 ProblemSet import
- UserProblemSet 추가/조회/보관 API + UI
- Tailwind 도입 + 디자인 토큰 이식
- 12 페이지 재디자인 (AppShell 포함)
제외 (Phase 8 이후):
- 2025/2026 탐구 전 과목 (46+46) 전체 import — 먼저 5과목 패턴 확립 후 Phase 8 자동 확장
- 정답률/등급컷 데이터 수동 입력 — EBSi 엑셀 별도 트랙
- 실제 결제 연동 (PortOne/Toss)
- Pro tier 실제 gating 강제 (P6-F5 TODO 주석 유지)
- 문제지 PDF 이미지/수식 OCR
- PWA manifest / offline 지원
- 학교 Pro 전용 기능 (선생님 대시보드)
실행 모드 제안
전체 35+ 태스크 → Breezing 자동 선택되지만 Phase 별로 나눠 순차 실행:
- 먼저 Phase 7C (Tailwind 도입) — 5 태스크, 다른 Phase 의 전제
- Phase 7A (KICE 파이프라인) — 7 태스크, backend 중심
- Phase 7B (UserProblemSet) — 5 태스크, backend + 최소 frontend
- Phase 7D (12 페이지 재디자인) — 12 태스크, 가장 무거움. 서브 Phase 로 더 쪼개 가능
- Phase 7E (배포+감사) — 3 태스크
사용자 컨펌 대기 항목:
- D1 ~ D5 기술 결정 승인
- Phase 7D 재디자인을 하나의 세션에서 12개 전부 할지, 서브 Phase (7D-a 사이드바+dashboard / 7D-b auth+landing+pricing / 7D-c review+study+history / 7D-d subjects+stats+profile) 로 쪼갤지
Execution
Mode: Breezing (명시 플래그 --breezing) — 2 Wave
Wave 1: 6개 QA 병렬 Worker (정적 분석)
Wave 2: 5개 fix Cluster 병렬 Worker + Lead 리뷰 + Cluster별 commit
Lead: 본 세션 (claude-opus-4-6[1m])
Workers: claude-code-harness:task-worker (Wave 2, write 권한)
AI Residuals: clean (TODO/FIXME/Claude/ChatGPT/Copilot 어느 것도 소스 없음)