# B03/B05 초기값 스냅샷 기반 초기화 완료 아카이브 - 완료일: 2026-08-29 - 상태: 사용자 검증 완료 - 검증: `docs/raw/verification/2026-08-29_verify_initial_snapshot_reset.md` - 검증 방식: 사용자 실무 검증 채택, Codex 2차 교차검증 생략 ## 완료 범위 ### 초기값 스냅샷 기반 [초기화] (2026-08-29 구현 완료 — 검증 대기) 배경 — 현행 [초기화]는 **복원이 아니라 재생산**이다. `DELETE FROM routes` 뒤 `run_auto_design_chain()`을 처음부터 다시 돌린다(`B05_Profile_Router.py:608`). 초기값을 콕 집어 부를 수 없는 이유는 두 가지다. - `routes`에 초기값/변경값 구분이 없다(`001_create_schema.sql:284` — `status`는 DRAFT/CONFIRMED/ARCHIVED뿐). - [저장]·[확정]이 **새 행을 만들지 않고 최신 행을 제자리 갱신**한다 (`confirm_latest_route` → `confirm_route(connection, latest["id"])`). 종단·횡단 정본 파일도 덮어쓴다. 초기 상태가 물리적으로 남지 않는다. - 게다가 현행 초기화는 DB만 지우므로 `structures.json`·`edits/pipe_points.json`은 살아남아, 재계산 결과가 **초기값과도 다르다**(구조물 측점이 편집분에서 파생). 사용자 확정(2026-08-29) — 초기연산 결과를 **초기값으로 영구 보존**하고, [초기화]는 그 초기값을 되돌려 놓는다. 재계산하지 않는다. #### 설계 - **스냅샷 시점**: `run_auto_design_chain()` 성공 직후 1회 (`B03_FileInput_Service_Chain.py` 5)단계 뒤, `return` 직전). 재계산 체인 (`run_redesign_chain`)에서는 만들지 않는다 — 초기값은 최초 1회뿐. - **저장 위치**: 프로젝트 저장소 아래 `initial_snapshot/` (stage 폴더와 형제, `PROJECT_STORAGE_LAYOUT_V2`에는 넣지 않는다 — 워크플로 단계가 아니다). - **파일 스냅샷**(그대로 복사): `B05_Profile/route/` · `B06_Section/longitudinal/` · `B06_Section/cross_sections/` · `{배수유역}/edits/` (`common_util_drainage_pipes.edits_dir`). - **DB 스냅샷**: `routes` 1행 + FK 자식 4개(`route_points`·`route_statistics`· `longitudinal_sections`·`cross_sections`)를 JSON 덤프로 같은 폴더에 남긴다. 스키마상 route_id를 참조하는 표는 이 4개가 전부다(`001_create_schema.sql:539~556`). - **복원(=[초기화])**: `DELETE FROM routes` → 덤프에서 `routes` 1행 INSERT(새 id) → 자식 4표를 새 route_id로 INSERT → 파일 스냅샷을 위 4개 경로에 덮어쓰기 → `prune_corridor_files()`로 주인 없는 코리도 정리. **자동 설계 체인 호출 없음.** - **폴백**: 스냅샷이 없으면(옛 프로젝트) 지금의 재계산 경로를 그대로 탄다. #### 체크리스트 - [X] `common_util/common_util_initial_snapshot.py` 신설 — `save_initial_snapshot()` · `load_initial_snapshot()` · `has_initial_snapshot()`. 파일 복사와 JSON 덤프 한 벌만 담는다(700줄 제한 여유). - [X] `B03_FileInput_Service_Chain.run_auto_design_chain()` — 성공 반환 직전 `save_initial_snapshot()` 호출. 실패는 **비치명적**(로그만) — 체인 규약과 동일. - [X] `B05_Profile_Router.reset_route_design()` — 스냅샷이 있으면 복원 경로, 없으면 기존 재계산 경로. 응답 스키마(`RouteResetResponse`)는 유지하고 `restored: bool`만 더한다. - [X] 프론트 `B05_Profile_Api_Fetch.RouteResetResponse`에 `restored` 추가. 복원이면 분석용 긴 타임아웃이 필요 없으나, 폴백을 위해 타임아웃은 그대로 둔다. - [X] 세션 캐시 정리는 현행 `resetDesign()` 그대로 둔다(이미 B05·B06 양쪽을 비운다). - [X] **정본 파일 덮어쓰기 확인** — 복원이 `structures.json`·`edits/pipe_points.json`을 스냅샷본으로 되돌리는지 테스트로 못박는다. 현행 초기화가 이 둘을 남겨 두는 것이 **초기값 오염의 실체**다(2026-08-29 사용자 확인). - [X] 테스트 `tmp/tests/test_initial_snapshot.py` — ① 저장 후 파일·덤프 존재 ② 편집·저장 뒤 초기화하면 route 행 수 1, 종단 정본 파일이 스냅샷과 동일 ③ 스냅샷 없는 프로젝트는 재계산 폴백. - [X] 화면 실측(2026-08-29) — 아래 「화면 실측」 절 참조. #### 확정 (2026-08-29 사용자) - **자동저장은 폐지**한다. 사용자 조작분은 세션 캐시에 담고 [저장]·[확정]에서만 영구저장소에 쓴다 — 별도 항목 「자동저장 폐지…」 참조. - 스냅샷은 사용자 첫 접근 **이전**에 찍히고 그 뒤 읽기 전용이므로, 자동저장이 남아 있든 없든 초기값 자체는 오염되지 않는다. - 현재 조작 속도는 불편하지 않다. 성능이 아니라 **초기값 보전**이 이 작업의 목적이다. 구현 메모 — 복원은 `DELETE FROM routes`와 **같은 트랜잭션** 안에서 끝낸다(지우기만 하고 실패하면 경로가 없는 프로젝트가 된다). 파일 복원은 트랜잭션 밖 `asyncio.to_thread`. 스냅샷 파일 트리는 폴더명을 `B05_Profile__route`처럼 평탄화해 담는다. - [X] 자체검증(2026-08-29) — `tmp/tests/test_initial_snapshot.py` **5 passed**: ① 파일 4트리 + DB 덤프 촬영 ② 촬영 뒤 정본을 고쳐도 스냅샷 불변(읽기 전용 층) ③ 복원이 편집된 `pipe_points.json`을 초기값으로 되돌리고 스냅샷에 없던 파일은 남기지 않음 ④ 자식 4표가 **새 route_id**로 다시 들어가고 `id` 열은 비워짐 ⑤ 스냅샷 없으면 None(재계산 폴백). ruff format·check 통과. 실제 DB·화면 왕복은 미검증 — 공용 브라우저 확인 필요. ## 화면 실측 — 진짜 초기값 재생산 겸 검증 (2026-08-29, 사용자 승인 후 실행) 배경: 검증 대상 프로젝트(`c1bb453f…`)는 기능 추가 **전에** 만들어져 `initial_snapshot/`이 없었고, [저장]이 정본을 덮어써 진짜 초기값이 남아 있지 않았다. 자동설계 체인은 계획노선 CSV + config 기본값으로 도는 결정적 절차이므로 **편집 정본만 걷어내고 다시 돌리면** 원래 나왔어야 할 값이 나온다 — 종전 [초기화]가 그걸 못 하던 이유가 정확히 `structures.json`· `edits/pipe_points.json`을 안 지운 것이었다. 절차 1. 백업 — `tmp/backup_before_initial/`에 storage 152파일 + `routes`·자식 4표 덤프 (스크립트 `tmp/reconstruct_initial.py`, `backup`/`show` 단계). 2. 편집 정본 2개 삭제 — 자동 권한 검사가 파일 삭제·이동을 막아 **사용자가 직접** 지웠다. 3. 공용 브라우저에서 B05 [초기화] 클릭(확인 모달 → 스냅샷이 없으므로 재계산 폴백). 결과 | 항목 | 초기값(route 102) | 직전 작업본(route 101) | |---|---|---| | 연장 | 350.11m | 350.11m (계획노선 CSV가 같으니 동일) | | 횡단 측점 | 23 | 25 | | 관 지점 | 4 | 6 | | 구조물 | 0 (`structures.json` 없음) | 0 | 줄어든 측점 2 · 관 2가 곧 사용자가 손댔던 몫이다. 초기 관 배치는 전부 자동 산출이다 — 4+4.3 배수관(spacing) · 7+9.7 세월교(stream) · 10+0.9 BOX암거(spacing) · 13+15.7 배수관(spacing). **체인이 스냅샷을 스스로 떴다** — 따로 부르지 않았다. `initial_snapshot/db.json`에 routes 1 · route_points 135 · route_statistics 1 · longitudinal_sections 1 · cross_sections 23(route 102 · CONFIRMED · 350.11m), 파일 4트리(`cross_*.json` 23개 포함). DB 값과 일치한다. 토스트 "초기 계산 상태로 되돌렸습니다." 확인, 10초 안에 완료. 남은 것 - **복원 경로(`restored=true`)는 아직 미실측**이다. 이번 클릭은 스냅샷이 **없던** 마지막 폴백 실행이었고, 그 실행이 초기값을 만들면서 동시에 촬영했다. 이제 스냅샷이 있으므로 다음 [초기화]가 복원 경로를 탄다 — 그때 route 행 재삽입·정본 파일 되돌리기·응답 `restored`를 실측한다. - 현재 작업본 = 초기값(route 102). 직전 설계(route 101)는 `tmp/backup_before_initial/` 에만 있다. 되살릴지 초기값 상태로 둘지는 사용자 판단.