Files
reloop-v2/Plans.md
reloop d892d8298b feat(phase10A): KICE 비수학 제거 + 수학 단원 분리
- 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 확인.
2026-04-14 18:27:00 +09:00

38 KiB
Raw Blame History

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, HEAD cc946e4)
  • 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]" 형식. 해당 범위 문항은 같은 Passage FK 공유
  • 정답표 파싱: /(\d{1,2})번?\s*[①②③④⑤1-5]/ 또는 표 구조 raw text

Problem 모델 확장:

  • bodyText Text — 문제 본문 (선택지 제외)
  • choices Json{"1":"...","2":"...","3":"...","4":"...","5":"..."}
  • answerNumber Int? — 1~5
  • passageId Int? — 공통 지문 FK (별도 Passage 테이블)
  • needsReview Boolean @default(false) — 파싱 엣지케이스 표시

Phase 7A — KICE 데이터 파이프라인 (텍스트 파싱)

Task 내용 DoD Depends Status
7A.1 Prisma 스키마 확장: ProblemSetaudioUrls Json?. ProblembodyText 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.tsUserProblemSet, 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 이미지 croppdftotext -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문항 (122) + 선택 8문항 × 3단원 (2330 각 세트). 즉 year 당 수학 ProblemSet = 4개(common/prob_stat/calculus/geometry), problems = 22+8+8+8 = 46. 2년치 × 4 = 8 ProblemSet 총 92 problems
P10-D3 비수학 데이터 처리 Dev DB 에서 ProblemSet.subjectName NOT IN ('수학') 에 연결된 Problem/StudyLog/ReviewSchedule/AssignmentSubmission cascade delete. 되돌리기 불가 — 마이그레이션 전 mysqldump 백업 필수
P10-D4 Solved.ac 통합 깊이 (c) 풀: 검색+북마크 + 핸들 자동 sync. SolvedAcClienthttps://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.tsProblemSet 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문항을 122 = common, 2330 = 선택 × 3 단원으로 split 하여 4 ProblemSet 생성. ProblemSet.titleYYYY학년도 수능 수학 (공통) 형태 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.169cd ~/reloop-v2 && git pull. backend 에서 mysqldump reloop_v2 > backups/phase10-pre.sql 실행 후 pnpm --filter backend exec prisma migrate deploypnpm --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 세부 단원 / ps bojHandle)
  • 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 별로 나눠 순차 실행:

  1. 먼저 Phase 7C (Tailwind 도입) — 5 태스크, 다른 Phase 의 전제
  2. Phase 7A (KICE 파이프라인) — 7 태스크, backend 중심
  3. Phase 7B (UserProblemSet) — 5 태스크, backend + 최소 frontend
  4. Phase 7D (12 페이지 재디자인) — 12 태스크, 가장 무거움. 서브 Phase 로 더 쪼개 가능
  5. 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 어느 것도 소스 없음)