실무 자료 용화.las가 계획노선 2,136m 중 일부만 덮는다. 좌표 문제가 아니라 측량
범위 자체다 — LAS(VLR EPSG 5176)와 정사영상 용화.tif 범위가 서로 일치하고, 위경도로
datum 보정까지 태워도 위도는 완전히 포함되며 경도만 동쪽 431m 초과한다.
노선 3D 표시
- 계획노선을 지표면에 드리우지 않고 데이터 최고 표고(bounds.z_max) 평면에 수평으로
얹는다. 노선과 측량 범위가 평면상 어디서 어긋나는지 보려는 것이라 지형을 따라
오르내리면 오히려 판단이 어렵다.
- 색은 2D 지도·B05 배수유역도가 쓰는 routeLineColor()를 그대로 쓴다. 같은 선을 두
화면에서 다른 색으로 그리면 같은 것인지 알아볼 수 없다.
노선 트림
- trim_route_to_surface(): DtmGridSampler 의 valid_mask 로 판정한다. bounds 사각형이
아니라 불규칙한 실제 외곽이다. 가장 긴 연속 유효 구간을 남긴다.
- 가장자리 여유 SURFACE_ROUTE_EDGE_TRIM_M(30m)은 잘라 낸 쪽 끝에만 적용한다. 노선
본래 끝점이 지표면 안이면 깎지 않는다.
- 자르는 자리는 _planned_route_points_in_project_crs() 한 곳이다. 체인의 BP·EP·CP가
전부 이 함수를 지나므로 여기서 한 번 자르면 하류가 모두 유효해진다.
- 지표면을 못 열면 자르지 않는다. 트림 실패가 설계를 막으면 안 된다.
실측(용화 노선 2,136m):
csf/dtm/smooth -> 1,310m (61%)
classification/dtm/smooth -> 1,070m (50%)
bounds 사각형 기준 추정치 1,400m보다 짧다 — 실제 외곽이 사각형보다 작기 때문이다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
LAS 없이 설계한 프로젝트에서 WF1이 sheet_laplace로 자동 확정한 직후 체인이
config 기본값(csf/dtm)으로 B05 경로 계산을 걸어 404로 끊겼다.
ERROR 자동 설계 체인 중단(B05 경로 계산 실패): status=404
stage 1 스냅샷(get_surface_confirmation_params)을 읽어 실제 확정값을 쓴다.
B05 초기화가 부르는 경로도 같은 함수라 함께 고쳐진다.
검증: 프로젝트 5d18ebe3(LAS 없이 도엽등고선으로 생성)에서 체인 재실행 —
B05 route_id=112 연장 354.84m, 횡단 19개, 배수유역 관 4개·세부유역 8개까지
완주. 확정 선택값 {"source_filter": "sheet_laplace", "method": "dtm",
"smooth": true} 그대로 사용.
파일을 올리면 지표면 확정 직후 "노선 설계로 이동하세요"를 적고 그 뒤에 초기 설계
체인이 돈다. 그 사이 B05에 들어가 만지면 반쯤 계산된 값을 편집하게 되고, 그 편집이
섞인 채 초기값 스냅샷이 찍힌다. 계산이 끝날 때까지 준비 화면(B11)에서 기다리게 한다.
- initial_snapshot: 마커(initial_design.lock) 생성·해제·조회와 스냅샷 무효화,
편집 정본 제거를 한곳에 둔다. 마커는 스냅샷 4트리 밖(프로젝트 루트)이라 복원에
딸려 들어가지 않는다.
- 체인 두 곳(자동·재설계): 시작에 마커, finally에서 해제. 재설계 체인은 끝에 스냅샷을
무효화한다 — 사용자 입력을 유지한 채 재계산하므로 그 결과를 찍으면 가짜 초기값이
된다. 다음 [초기화]가 폴백을 타며 새 초기값을 만들고 그때 촬영한다.
- /surface/status: 마커가 있으면 in_progress + initial_design으로 답한다. stage 1이
COMPLETE면 진행률 파일을 무시하는 기존 분기를 건드리지 않으려고 별도 확인이다.
- 진입 게이트(routeAfterPreloadCheck)와 B11이 그 상태를 보고 기다린다. 상태 조회가
실패하면 막지 않고, B11 대기는 20분 상한을 둔다.
- [초기화] 폴백: 체인 전에 structures.json과 drainage/edits를 지운다. 남기면 구조물
측점이 사용자 편집분에서 다시 파생돼 초기값이 오염된다(2026-08-29 실측).
테스트 4건 추가. 249 passed·8 failed(기슭막이 이관 때 생긴 기존 실패, 변경 전 동일).
CLAUDE.md 5장(조작·데이터 흐름 정책)을 코드에 반영한다. 조작분은 세션에만 쌓고
영구저장은 [저장]·[확정]에서만 하며, [초기화]는 재계산이 아니라 초기값 복원이다.
자동저장 폐지
- B06 조정창 기준벽 구간값: 800ms 디바운스 PUT을 없애고 세션(b06:culvertopt)에
담는다. flushCulvertOptions()를 B06 [저장]·[확정]과 B05 [저장]이 부른다.
- B05 구조물: 조작 즉시 PUT + 서버 재조회로 화면을 덮어쓰던 것을 세션
(b05:structures) 적재로 바꾼다. 저장 전에도 고르고 지울 수 있도록 식별자를
crypto.randomUUID()로 미리 발급한다(서버는 빈 값일 때만 새로 발급).
- B05에서 만지고 B06에서 확정하는 경로를 위해 flushPendingStructures()를 공용화.
초기값 스냅샷
- common_util_initial_snapshot: 자동설계 체인 성공 직후 정본 파일 4트리와
routes+자식 4표를 initial_snapshot/에 뜬다. 이후 읽기 전용.
- reset_route_design: 스냅샷이 있으면 DELETE와 같은 트랜잭션에서 행을 되세우고
파일을 되돌린다. 없으면 종전 재계산 폴백. 응답에 restored를 더한다.
조작 응답
- 등고선 재적용·B06 진입 정합·[모두 적용]에서 전체 화면 오버레이 제거.
- 재계산이 design을 갈아끼울 때 extra_spans를 보존한다(다른 조작값과 동일).
테스트: tmp/tests/test_initial_snapshot.py 5건 추가. 245 passed·8 failed(기존
실패 — 기슭막이 이관 때 placement가 interval→point로 바뀐 것을 테스트 미반영).
계획선 1차 로직(design_pipe_anchored_profile)은 저장된 관 지점을 변화점으로
쓰는데, 자동 설계 체인이 그 파일을 만들어 주지 않았다. 그래서 초기 계획선이
2차 폴백(지반 추종 직선 분할)으로 산출돼 관 자리 틸팅·R이 통째로 빠지고,
B05/B06에 들어와도 배수유역도가 비어 있었다.
- _prepare_drainage_pipes_and_reprofile() 신설: 배수유역 분석 → 기본 관 지점
확정 저장 → 배관 정착 계획선으로 종횡단 재생성. 실패해도 체인을 멈추지 않는다.
- run_auto_design_chain: B05 경로 확정 다음, B06 확정 전에 호출.
- run_redesign_chain: 노선이 바뀌었으므로 refresh=True로 호출 — 저장된 배수유역
분석 응답은 노선 변경을 스스로 알지 못한다.
검증(테스트 프로젝트 fc86f247, 노선 350m): 관 4개(계류 1 + 간격 보충 3) 확정 후
종횡단 재생성 → PVI 6개(BP + 관 4자리 + EP)가 관 측점과 일치하고 각 자리에
R(1108.57/180.79/450.0/580.97m)이 붙는 것을 확인. 반영 전에는 관과 무관한
지반 추종 변화점이었다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
같은 업로드 한 번에 업로드 완료 메일과 지표면 분석 완료 메일이 두 통 도착했다.
사용자가 실제로 화면을 볼 수 있는 시점은 초기 설계(B04 전처리 + B05 종단 + B06 횡단)까지
끝난 때 하나뿐이므로 그 시점에 통합 메일 한 통만 보낸다.
- send_initial_analysis_complete_email 신설: 지표면 요약 + 노선 연장/측점 수 +
종단설계 화면 링크. 자동 체인 미실행/실패 시 전처리 요약만 싣고 안내 문구 전환
- run_auto_design_chain이 요약(route_id/length_m/cross_section_count) 반환
- WF1 서비스: 체인 실행 -> 통합 메일 순으로 재배치
- 업로드 직후 메일 발송 호출 제거(일반·청크 두 경로). 발송 함수는 프로젝트 생성 전
임시 보관함 안내용으로 남겨 두고 주석으로 용도 명시
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
자동 체인 상태 전이 분리:
- confirm_latest_route/confirm_sections에 mark_stage_complete 플래그 추가
- 자동 체인(신규·재확정)은 데이터만 확정하고 stage 2·3을 IN_PROGRESS로 남김
(스텝바 기존 노란 테두리 = 계산됨·확정 전 상태로 재사용)
- B06 [확정]이 stage 2+3 동시 완료 + 경로 상태 CONFIRMED 보강 후 B07 수량 이동
B05 좌측 패널 개편:
- 포인트 팔레트 + 임도 기준·옵션을 경로 계산 설정 한 컨테이너로 병합,
[최적 경로 계산] 버튼을 컨테이너 내부로 이동 (가끔 쓰는 무거운 재계산)
- 하단 액션 행 [초기화][임시저장][횡단 이동]으로 교체, 경로 단독 확정 폐지
- [임시저장] = 관로 + 계획선 델타 + 비정규 측점·상단측 저장(mark_stage_complete=false)
- [횡단 이동]/B06 [종단 이동] = 저장 없이 페이지 이동만 (B05·B06 캐시 공유 구조 유지)
- [초기화] = POST /route/reset 신설: 기존 경로 삭제 후 계획노선 CSV 기본값으로
자동 체인 재실행(파일입력 직후 상태 복원), 분석용 타임아웃 적용
B06 액션 행: [종단 이동][임시저장][확정] 3버튼 구성, locale 키 정비
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- 12: run_redesign_chain — B04 재확정 시 사용자 입력(stage 2 params) 유지한 채
새 지표면 기준 B05 재계산·확정, 옛 측점별 설계·표준단면 설정을 chainage 매칭
이월 후 B06 확정. 경로 없으면 신규 자동 체인 폴백. B04 confirm에 백그라운드 결선
- 13: 구간 이동 글리프 ⇧⇩ → 속 찬 ⬆︎⬇︎(21×17·굵게), 규칙·비정규 측점 버튼
최소 간격 20px 캐스케이드 배치로 근접 측점 ▲▼ 겹침 해소
- 14: 유토곡선 손잡이를 표준 삼각형 손잡이(64×24, 바닥 중앙)로 통일, 캡션 제거,
'유토곡선 펼치기/접기' 툴팁
- 15: 유토곡선 스크롤러 세로 휠 → 가로 이동(종단 그래프와 동기)
Playwright 검증: 버튼 27개 최소 간격 20px, 글리프 ⬆︎ 21×17, 표준 손잡이 64×24,
휠 스크롤 0→400 종단 동기, 콘솔 오류 0. typecheck·ruff 통과.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
10. 재접속 현황·완료 표시·재업로드 경고:
- GET /projects/{id}/upload-overview 신설 — 완료 파일 목록(input_files 정본),
중단 청크 세션(진행률), 필수 파일·WF1 분석 완료 여부
- 진입 시 서버 정본으로 슬롯 카드 표시(serverUploaded), localStorage는 보조로 강등
- 파일 재선택 전에도 중단 세션 이어올리기 안내 배너
- 전체 완료 배지 + 완료 슬롯 재업로드 시 교체 확인 모달(승인 시에만 진행)
- 필수 슬롯 검증: 서버 업로드분 있으면 충족 — 단일 파일 교체 업로드 허용
9. 자동 설계 체인 연장 (B03_FileInput_Service_Chain.py):
- WF1 자동 확정 후 같은 백그라운드 태스크에서 ① 계획노선 CSV 기반 B05 기본 경로
계산(solve) ② 경로 확정(stage 2) ③ B06 기본 횡단 설계 확정(stage 3)까지 진행
- 수동 이력 보호: 프로젝트에 경로가 이미 있으면 건너뜀
- 단계별 실패 격리: 실패 단계에서 멈추고 로그·workflow 상태로만 기록
- AUTO_DESIGN_CHAIN_ENABLED config 플래그(기본 True)
typecheck·ruff·B03 unittest(7건) 통과.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>