# PROGRESS.md — abcvideo 진행 상태

> 이 파일은 각 단계의 진행 상태를 기록합니다.
> 에이전트는 작업 시작/완료 시 반드시 이 파일을 업데이트합니다.

---

## 현재 상태 요약

- 마지막 완료 단계: 구조물 시설종별(1종/2종/3종) 표시 필터 (2026-06-19)
- 직전 완료 단계: 폴더 임포트 v2.0 데이터 형상 전용화 (2026-06-19)
- 블로커: FFmpeg 미설치 (HLS/프레임 기능은 FFmpeg 설치 후 동작)
- 검증 결과: VERIFICATION.md 참조 (모든 항목 통과, V3-3 RAF 버그 1건 수정 완료)
- v2.0 전환: geoData.ts/types/geo.ts/geoStore.ts/StationBar.tsx 수정, client 빌드 통과

---

## 단계별 진행 기록

### 단계 1: 프로젝트 구조 세팅
- 상태: ✅ 완료 (2026-03-24)
- 완료 항목:
  - 루트 package.json (npm workspaces)
  - shared/src/types.ts (공유 타입)
  - server/ 스캐폴드 + tsx dev 환경
  - client/ Vite + React 18 + TypeScript + Tailwind
  - storage/ 디렉토리 구조
  - npm install 성공 (514 packages)
  - server TypeScript 컴파일 에러 없음
  - GET /api/health 응답 확인
- 미완료/이슈:
  - better-sqlite3 ^12.8.0으로 업그레이드 (Node 24 prebuilt binary 문제)
- 다음 단계 참고:
  - server/package.json에 better-sqlite3 ^12.8.0 사용 중
  - client Vite dev는 아직 미확인 (브라우저 확인 필요)
  - server/src/app.ts는 최소 스캐폴드 — 단계 2에서 확장

### 단계 2: 백엔드 서버
- 상태: ✅ 완료 (2026-03-24)
- 완료 항목:
  - server/src/config.ts — 환경변수 기반 설정
  - server/src/services/ffmpeg.ts — FFprobe/FFmpeg spawn 래퍼 (probeVideo, runFFmpeg, getVideoFps 등)
  - server/src/services/streaming.ts — Range Request 스트리밍 (10MB 청크, 1MB highWaterMark)
  - server/src/services/storage.ts — better-sqlite3 초기화, Annotation CRUD, 임시파일 정리
  - server/src/middleware/security.ts — pathTraversalGuard, validateMimeType, securityHeaders
  - server/src/routes/stream.ts — GET /api/stream/:videoId
  - server/src/routes/hls.ts — POST /convert, GET /status, GET /progress(SSE), GET /index.m3u8, GET /:segment
  - server/src/routes/frame.ts — GET /api/frame/:videoId?time=|frame=
  - server/src/routes/upload.ts — 청크 업로드 (init/chunk/complete/status), @tus/server 대신 자체 구현
  - server/src/routes/meta.ts — GET /api/videos, GET /api/meta/:videoId, DELETE /api/videos/:videoId
  - server/src/routes/annotations.ts — CRUD + export(vtt/srt/json/csv)
  - server/src/app.ts — 전체 통합, 헬스체크, 에러 핸들러
  - server/.env — 환경변수 파일
  - server/tsconfig.json — rootDirs 수정 (shared 타입 접근 허용)
  - GET /api/health, /api/videos, /api/annotations/:videoId 정상 응답 확인
- 미완료/이슈:
  - @tus/server 2.3.0이 ESM-only("type":"module")라 CommonJS 서버와 호환 불가 → 자체 청크 업로드 구현으로 대체
  - FFmpeg 시스템 PATH 미등록 상태 → HLS 변환/프레임 추출은 FFmpeg 설치 후 동작
- 다음 단계 참고:
  - 청크 업로드 클라이언트는 POST /api/upload/init → POST /api/upload/chunk (X-Upload-Id, X-Chunk-Index 헤더, application/octet-stream body) → POST /api/upload/complete 순서 사용
  - HLS 변환 완료 후 /api/hls/:videoId/index.m3u8 로 플레이리스트 접근
  - 서버는 포트 3001에서 실행 중

### 단계 3: 기본 플레이어
- 상태: ✅ 완료 (2026-03-24)
- 완료 항목:
  - client/src/types/player.ts — VideoSource, PlayerState 타입
  - client/src/store/playerStore.ts — Zustand 플레이어 상태 스토어
  - client/src/store/annotationStore.ts — 주석 Zustand 스토어
  - client/src/hooks/useVideoPlayer.ts — Video.js 8 초기화, loadLocalFile, loadServerStream, switchToHls
  - client/src/components/player/VideoPlayer.tsx — forwardRef + VideoPlayerHandle API
  - client/src/components/player/HlsConversionStatus.tsx — SSE 진행률 바
  - client/src/components/sidebar/VideoList.tsx — 서버 영상 목록
  - client/src/App.tsx — 전체 레이아웃 통합
  - npm install: video.js@^8.23.0, @videojs/http-streaming, hls.js@^1.6.15, interactjs@^1.10.27, subsrt-ts@^2.0.3
  - TypeScript 에러 없음, 빌드 성공 (dist 1.47MB)
- 미완료/이슈:
  - interact.js 패키지명 오류 → interactjs (하이픈 없음) 으로 설치
  - 빌드 경고: chunk > 500kB (video.js 특성상 정상, 운영환경 분할 불필요)
- 다음 단계 참고:
  - VideoPlayer는 forwardRef<VideoPlayerHandle> 사용 — App에서 playerRef.current?.loadServerStream() 호출
  - hls.js backBufferLength: 30 설정 완료
  - MemoOverlay는 interactjs 사용 (import from 'interactjs')

### 단계 4: 프레임 추출
- 상태: ✅ 완료 (2026-03-24)
- 완료 항목:
  - client/src/utils/frameCapture.ts — 단일 Canvas 재사용, captureFrame, downloadDataUrl
  - client/src/components/player/FrameCaptureButton.tsx — Shift+S 캡처 버튼
  - VideoPlayer에 캡처 통합 (getVideoElement → captureFrame → downloadDataUrl)
- 미완료/이슈: 서버 FFmpeg 정확 추출(로컬 파일 정확 추출 시)은 미구현 — Canvas 즉시 미리보기만 구현
- 다음 단계 참고: (없음)

### 단계 5: 단위 이동
- 상태: ✅ 완료 (2026-03-24)
- 완료 항목:
  - client/src/hooks/useFrameStep.ts — requestVideoFrameCallback FPS 감지, stepForward/stepBackward
  - client/src/hooks/useKeyboard.ts — CLAUDE.md 키보드 단축키 전체 구현
  - 프레임/초/장면 단위 이동 완성 (Space/←/→/J/L/,/./[/]/F/M/+/-/Shift+S/Shift+M/0-9)
- 미완료/이슈: (없음)
- 다음 단계 참고: (없음)

### 단계 6: 텍스트 오버레이
- 상태: ✅ 완료 (2026-03-24)
- 완료 항목:
  - client/src/hooks/useAnnotations.ts — 주석 CRUD API 연동
  - client/src/components/overlay/MemoOverlay.tsx — interactjs 드래그, x%/y% 좌표
  - client/src/components/sidebar/AnnotationPanel.tsx — 자막/메모 탭, 내보내기(VTT/SRT/JSON/CSV)
  - client/src/components/AddAnnotationModal.tsx — 주석 추가 모달
  - client/src/utils/timecode.ts — secondsToTimecode, timecodeToSeconds, frameToSeconds, secondsToFrame
- 미완료/이슈:
  - WebVTT track 요소를 통한 자막형 렌더링은 미구현 (주석 저장/표시는 완성, track 연동은 단계 7)
- 다음 단계 참고:
  - 자막형 WebVTT track 연동은 단계 7 UI/UX 통합 시 추가 가능
  - 내보내기는 /api/annotations/:videoId/export?format=vtt|srt|json|csv 백엔드 API 사용

### 검증 (VERIFICATION)
- 상태: ✅ 완료 (2026-03-24)
- 완료 항목:
  - VERIFICATION.md 작성 및 전 항목 검증 실행
  - V1 정적 검증: server/client tsc 에러 없음, 빌드 성공
  - V2 서버 API: 헬스체크/영상목록/업로드/주석 CRUD/내보내기/보안 모두 통과
  - V3 코드 품질: 10/10 통과 (MemoOverlay RAF 버그 발견 및 수정)
  - V4 파일 구조: 19개 필수 파일 전부 존재 확인
- 수정 사항:
  - `VideoPlayerHandle`에 `getVideoElement()` 추가 (VideoPlayer.tsx)
  - App.tsx RAF 루프 추가 → MemoOverlay `currentTime` 60fps 업데이트

### 기능 확장: 카메라 파라미터 보정 패널 (2026-03-26)
- 상태: ✅ 완료
- 완료 항목:
  - client/src/utils/geoProjection.ts — CameraParams 인터페이스 추가, projectPoint 시그니처 변경
    - yawOffset(number) → params(CameraParams) 로 통합
    - Roll 회전 수학 추가 (Rodrigues, fwd 축 기준): right_r = right0·cosR − up0·sinR
    - 주점 오프셋(cx0, cy0) 핀홀 투영에 적용
    - 초점 배율(focalScale) 적용: f_eff = focalLen × focalScale
    - pxRaw/pyRaw 필드 추가 (클램프 전 원본값, FOV 이탈 감지용)
    - DEFAULT_CAMERA_PARAMS export
  - client/src/components/overlay/StationOverlay.tsx — 파라미터 패널 UI 전면 개편
    - ParamRow 컴포넌트: 슬라이더 + 직접 수치 입력(Enter/blur 커밋) 양방향 동기화
    - 자세 보정 섹션: Yaw(-180~+180°), Pitch(-45~+45°), Roll(-45~+45°)
    - 내부표정 섹션: f×(0.3~3.0배율), cx₀(-0.5~+0.5), cy₀(-0.5~+0.5)
    - 현재 유효값 실시간 표시: yaw_eff, pitch_eff, f_eff(mm), hFOV(°)
    - 측점 Canvas 마커 구현: 다이아몬드 ◆ + 측점명 레이블 + 거리(m)
    - FOV 이탈 측점 → 가장자리 방향 화살표(drawEdgeArrow)
    - POI 마커: + 심볼 + 레이블
    - 지면 가이드선: 측점 → 화면 하단 점선
    - 나침반 HUD: hFOV를 focalScale 반영하여 동적 계산
    - 초기화 버튼, 드론 경로 체크박스
  - 빌드 성공: tsc + vite build 에러 없음 (2026-03-26)
- 발견 사항:
  - SRT gb_roll=0.0 (전 프레임 고정) → Roll 보정은 이 영상에서 효과 없으나 다른 영상 대비 구조 완성
  - 자기 편각(-8°) 보정이 yawOffset 슬라이더의 주된 용도
  - focalScale로 실효 FOV를 조정할 수 있어 정밀 보정 가능
- 미완료/이슈:
  - GCP 클릭 기반 자동 Resection 미구현 (yaw/pitch 자동 계산)
  - 파라미터 설정값 영속 저장(localStorage) 미구현

### 기능 확장: 지리정보 오버레이 + 측점 검증 (2026-03-25)
- 상태: ✅ 완료
- 완료 항목:
  - server/src/services/geoMatch.ts — 블렌더 방식 월드 ENU 좌표계 구현
    - geoToEnu(): 위경도 → ENU(m), 기준점 위도로 cos(lat) 고정 (전 프레임 일관성)
    - projectEnu(): 상대 ENU 벡터 + 카메라 회전 → 핀홀 투영
    - project3D(): geoToEnu + projectEnu 래퍼, origin 파라미터 지원
    - findFramesForPoi / findPoisForFrame — frames[0]를 월드 원점으로 전달
    - getDroneFrames() 신규 export
  - server/src/routes/geo.ts — yawOffset 쿼리 파라미터, GET /api/geo/frames 엔드포인트
  - client/src/utils/geoProjection.ts — 서버와 동일한 블렌더 방식 ENU 투영, ref 파라미터 추가
  - client/src/components/overlay/StationOverlay.tsx — yaw 보정 슬라이더(-180~+180°), 드론 경로 오버레이, pathFrames[0] 원점 전달
  - client/src/components/geo/StationVerify.tsx — 측점 목록 + 클릭 검증 패널 (신규)
  - client/src/App.tsx — 우측 패널에 "측점" 탭 추가
  - server/client 빌드 모두 에러 없음 확인
- 발견 사항:
  - cos(lat) 보정을 각 프레임 위도가 아닌 기준점(frame 0) 위도로 고정해야 전 구간 일관성 보장
  - CSV yaw/pitch/roll = 짐벌(카메라) 방향 (pitch=-29.8°는 짐벌 고정 틸트, roll=0은 짐벌 보정)
  - frame 1791 기준 158K300: px=70.1% (사용자 관측 ~73%와 일치)
- 미완료/이슈:
  - Z값(표고) 미적용 — 측점 z값이 ~0m이라 수직 오차 존재, 실제 표고 데이터 확보 후 보정 필요
  - GCP 캘리브레이션 UI 미구현 (클릭으로 yaw/pitch 오프셋 자동 계산)

### 지리정보 오버레이 — Python advanced_tuner_v2 포팅 (2026-03-30)
- 상태: ✅ 완료
- 완료 항목:
  - geoProjection.ts 재구현: R_b2w=Rz(-yaw)*Rx(pitch)*Ry(roll), R_w2c=R_align@R_b2w.T (Python 동치)
  - pitch/roll 절대값 → 오프셋 방식 (Python spn_pitch/roll 기본값 0과 동일)
  - sensorH 기본값 20.25mm (=36×9/16, 16:9 영상 기준, 기존 24mm 수직 스케일 오류 수정)
  - CameraParams에 offX/offY/offZ 추가 (Python off_x/y/z 동치)
  - geoMatch.ts: center.csv 224점 로더 추가 (getCenterlinePoints)
  - GET /api/geo/centerline 엔드포인트 추가
  - StationOverlay: center.csv 중심선 빨간 선분 시각화 (Python 방식), Catmull-Rom next6 스플라인 제거
  - 파라미터 패널: 위치 보정(offX/Y/Z), 센서 크기(senW/H) 슬라이더 추가
  - DEFAULT_CAMERA_PARAMS = Python 기본값 (모든 오프셋 0, focal=24, sensor=36)
- 다음 단계 참고:
  - off_x/y/z 슬라이더로 GPS 위치 오차 보정 필요
  - swap_xy 옵션 미구현 (Python에는 있음, 필요시 추가)

### 단계 7: UI/UX 통합
- 상태: ✅ 완료 (2026-03-24)
- 완료 항목:
  - client/src/components/ErrorBoundary.tsx — React 에러 바운더리 (오류 발생 시 복구 UI)
  - client/src/components/HelpOverlay.tsx — 키보드 단축키 도움말 오버레이
  - client/src/main.tsx — ErrorBoundary로 App 래핑
  - client/src/hooks/useKeyboard.ts — ? 키 (Shift+Slash) → onToggleHelp 콜백 추가
  - client/src/components/player/VideoPlayer.tsx — onToggleHelp prop 수신 및 useKeyboard에 전달
  - client/src/App.tsx — showHelp 상태 + HelpOverlay 렌더링 통합
  - README.md — 프로젝트 설치/실행/단축키/환경변수 문서 작성
  - TypeScript 타입 에러 없음, 프로덕션 빌드 성공 (1,480 kB / gzip 453 kB)
- 미완료/이슈:
  - 빌드 경고: chunk > 500kB (video.js 특성상 정상)
- 다음 단계 참고: 전체 7단계 완료

### 기능 확장: 구조물 시설종별(1종/2종/3종) 표시 필터 (2026-06-19)
- 상태: ✅ 완료
- 목표: 구조물 시설종별에 따라 재생바(StationBar)/패널(RoutePanel) 표시 필터. 플레이어에 1/2/3종 체크박스, 기본 1종☑2종☑3종☐, localStorage 지속.
- 완료 항목:
  - client/src/types/geo.ts: RouteStructure 에 grade?: string 추가(시설종별 원문).
  - client/src/utils/geoData.ts (parseStructures): pushFrom spec 에 gradeFallback 추가. 교량 시설종별 idx1, 터널 idx5 를 헤더명('시설종별') indexOf 우선 + 폴백으로 읽어 trim, 빈 값이면 undefined. 구교(06)는 gradeFallback 미지정 → grade undefined.
  - client/src/store/settingsStore.ts (신설): zustand + persist('ghivideo.settings'). gradeFilter 기본 {1종:true,2종:true,3종:false}, setGradeFilter 액션. KNOWN_GRADES, isGradeVisible(공유 표시규칙) export. merge 로 누락 키 기본값 보충.
  - 표시규칙 isGradeVisible: grade 없음 || KNOWN 미포함('기타'/'인상' 등) → 항상 표시, 1/2/3종은 gradeFilter[grade]===true 일 때만.
  - client/src/stationbar/StationBar.tsx: storeStructuresRaw 를 isGradeVisible 로 필터(useMemo, gradeFilter 구독) → structureMarks 가 필터된 목록 사용.
  - client/src/components/overlay/RoutePanel.tsx: filteredPois 에 isGradeVisible 조건 추가(동일 규칙, gradeFilter 구독).
  - client/src/components/player/VideoPlayer.tsx: 좌하단 컨트롤 그룹(배속 행 인근)에 "시설등급" 라벨 + 1/2/3종 체크박스 3개. showVideoControls && geoLoaded 일 때 노출. setGradeFilter 바인딩(즉시 토글).
- 실데이터 검증(하행)회덕-대전조차장 v2.0, 좌표필터 적용 후):
  - 교량 12건: 시설종별 3종 ×10, 기타 ×2 ("인상" 1건은 깨진 좌표로 이미 필터됨)
  - 터널 2건: 시설종별 2종 ×2
  - 구교 9건: 시설종별 컬럼 없음 → grade undefined(항상 표시)
  - 기본값(1종☑2종☑3종☐) 적용 시: 3종 교량 10건 숨김, 기타 교량 2건·터널 2건(2종)·구교 9건 표시.
- 빌드: nvm use 20 → npm run build -w client → tsc + vite 에러 0 (기존 chunk>500kB 경고만).
- 미완료/후속:
  - 이 데이터엔 1종 구조물이 없어 1종 체크박스 효과는 다른 데이터에서 검증 필요(구조는 완성).
  - StationOverlay(영상 위 측점 마커)는 structures 미사용이라 필터 미적용 — 요구범위(재생바/패널)대로.
- 상세 기록: docs/history/2026-06-19_시설종별-필터.md

### 기능 전환: 폴더 임포트 v2.0 데이터 형상 전용화 (2026-06-19)
- 상태: ✅ 완료
- 목표: 폴더 선택 시 v2.0 형상만 로딩. v1.0(POI_위경도값/측점_위경도값/center.csv) 지원 코드 제거.
- 완료 항목:
  - client/src/types/geo.ts:
    - DirectionChange 타입 신설(station/from/to/atSeconds)
    - RouteStructure 에 lengthM?, category? 추가
    - FolderGeoData 에 structures: RouteStructure[], directionChanges: DirectionChange[] 추가
  - client/src/utils/geoData.ts (전면 재작성, v2.0 전용):
    - 인코딩 자동 감지: decodeBytes(buf) — 앞 3바이트 EF BB BF → UTF-8, 아니면 EUC-KR, 잔여 BOM 제거
    - readCsv(file) 시그니처 단순화(인코딩 인자 제거)
    - parseStations: building/01)측점.csv → 측점 + 방향전환점 추출(비고 정규식)
    - parsePois: <base>_POI.csv(UTF-8) + building/02)지장물.csv(EUC-KR) 병합
    - parseStructures: 03)교량(bridge)+04)터널(tunnel)+06)구교(bridge/category='구교') → RouteStructure[], 위경도 범위(33~39/124~132) 밖 깨진 행 필터
    - mergeStructures: route.json structures 를 name 매칭으로 CSV 구조물에 override/augment(선택적 보정 레이어)
    - buildCenterlineFromStations: v2.0엔 center.csv 없음 → 측점 mileage 정렬 폴리라인을 중심선으로 생성
    - v1.0 코드 제거: POI_위경도값/측점_위경도값/타원체고 식별, center.csv 로더, encoding 하드코딩 분기
  - client/src/store/geoStore.ts: structures, directionChanges 적재
  - client/src/stationbar/StationBar.tsx: 구조물 마커를 geoStore.structures(CSV 유래) 우선으로 전환(기존 routeMeta.structures 대체). 위치 로직(station→lat/lon→이름매칭→mileage)은 lat/lon 지원으로 그대로 활용.
- 실데이터 검증(하행)회덕-대전조차장 데이터(v2.0)):
  - 인코딩: 드론/POI/05)출입문/06)구교=UTF-8(BOM), 01~04=EUC-KR (BOM 자동감지로 일괄 처리)
  - directionChanges 2건: 157K930 상행→하행@125s, 158K340 하행→상행@75s
  - 측점 47개, POI 137개(<base>_POI 114 + 지장물 23), 구조물 교량 12(깨진 1행 필터)+터널 2+구교 9
- 빌드: nvm use 20 → npm run build -w client → tsc + vite 에러 0 (기존 chunk>500kB 경고만)
- 미완료/후속과제:
  - 05)출입문번호.csv: 운영데이터로 재생바 아이콘이 과도해져 기본 표시 제외. 파싱 자체를 스킵(type='door' 보관 비용 대비 효용 낮음, Timeline 렌더는 bridge/tunnel/station만). 추후 필요 시 parseDoors 추가.
  - directionChanges 와이어링: 타입/스토어까지 적재 완료. StationBar barSegments 방향전환은 현재 연속 체이니지(chain) 자동 판정 로직을 유지(directionChanges 로 강제 분할은 미적용). chain 자동판정이 영상과 일치하므로 회귀 위험을 피해 후속과제로 둠.
- 다음 단계 참고:
  - geoStore.structures/directionChanges 가 신규 노출됨 — 후속 소비처에서 활용 가능
  - StationOverlay/StationVerify/RoutePanel/RouteInfoOverlay 는 stations/pois/routeMeta 만 사용하므로 변경 불필요(검증 완료)
  - 상세 기록: docs/history/2026-06-19_v2.0-import전환.md
