사용자 지적(2026-08-30): "결국 노선 위에 위치해야 한다".
관 자리를 정한 선(계획노선 CSV)과 화면에 그려지는 선(B05 최적 경로)은 같은 자리를
지나면서 연장이 다르다 — 실측 350.11m vs 354.83m, 전 구간 이격은 평균 0.03m·최대
1.12m뿐인데 길이가 4.7m 차이난다. pipe_points.json이 누가거리만 저장해서 읽는 쪽이
쥔 선에 따라 같은 값이 3.3~4.3m 미끄러졌고, 노선 지문도 늘 어긋나 B05는 저장된 관을
매번 통째로 버렸다("노선 지문이 달라 저장된 관 지점 4건을 쓰지 않습니다").
- PipePoint에 x·y를 두고 저장 시 채운다(save_pipe_points에 노선을 넘긴다).
- 지문이 달라도 좌표가 있으면 읽는 쪽 노선에 투영해 이월한다(project_pipe_points).
구간(start_m·end_m)은 기준점이 옮겨간 만큼 같이 민다 — 시설 치수는 불변.
- 좌표 없는 구 저장분만 종전대로 버린다.
- B05는 같은 로더(load_pipe_points_file)를 쓴다 — 두 화면의 판정 기준을 하나로.
검증: 프로젝트 5d18ebe3 실데이터 — 좌표 4/4 저장, B05 계획선으로 읽어 4건 전부 이월,
관에서 계획선까지 거리 최대 0.016m(종전 4건 전량 폐기). 누가거리는 +3.45~+4.29m
이동(연장 차이 그대로). pytest 11 passed(투영 이월·구 저장분 폐기 테스트 추가).
`storage/`를 심볼릭 링크·정션으로 두면(워크트리를 나눠 쓰면 실제로 그렇다)
프로젝트 루트는 링크 경로 그대로인데 저장 엔진(resolve_upload_destination·
resolve_chunk_session_dir)은 Path.resolve()로 링크를 따라가 실경로를 만든다.
그래서 chunk_path.relative_to(project_root)가 ValueError로 터졌다.
'...본폴더\storage\1\3\{id}\B03_FileInput\chunks_temp\...\00000000.chunk'
is not in the subpath of '...워크트리\storage\1\3\{id}'
프로젝트 루트와 임시 보관함 루트를 realpath로 돌려주어 기준을 하나로 맞춘다.
경로 검증(상대경로·.. 금지·루트 이탈)은 그대로다.
검증: tmp/tests/test_storage_symlink_root.py 추가 — 링크된 storage에서 반환
경로가 resolve() 결과와 같고 relative_to가 성립하는지. 전체 9 passed.
납품 도면 2장을 B07 도면 목록에 새로 붙였다. 계산은 하지 않는다 —
유토곡선은 B06 확정 시 저장한 산출물(longitudinal_sections.data.mass_haul)을,
유역도는 B04 세부유역 GeoJSON과 도엽 등고선·세류선을 읽어 좌표만 종이 mm로 옮긴다.
- 도면 종류 확장: kind에 mass_haul·watershed 추가(Schema·Api_Fetch·openwebcad
App.types), 12분류 라벨에 연결, 목록에 단장 도면 2건 상시 노출
- 엔진 신규: _Engine_Cad_MassHaul.py(축·곡선·평형선·띠 현·balloon·측점 테이블),
_Engine_Cad_Basin.py(등고선·세류선 배경·노선·유역·구역별 정보표·방위표)
- 척도 상수: 유토곡선 H 1/2,000 · 세로 1mm=50㎥, 유역도 1/6,000 (A1 고정)
- 유토곡선 저장 payload에 띠·잔여의 기하 필드 추가 — 파이썬에 곡선 보간·토량 배분
로직을 복제하지 않기 위해 값을 낳는 쪽(TS 엔진)에서 함께 남긴다
- 유역도 배경은 여러 도엽을 합쳐 받은 뒤 도곽 크기로 절취(clip_line_to_box)
- 배수규격은 소요 관경이 아니라 규격관(recommended_diameter_mm)으로 표기
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
파일을 올리면 지표면 확정 직후 "노선 설계로 이동하세요"를 적고 그 뒤에 초기 설계
체인이 돈다. 그 사이 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로 바뀐 것을 테스트 미반영).
세월교는 배관을 여러 개 묶어 다리 형태로 만든 것이라 배수관과 같은 산식을 쓰되,
그 위에 물넘이가 얹히므로 0.5m를 더 얹는다(2026-08-23 사용자 확정).
물넘이포장은 도로 위에 그대로 만드는 시설이라 들어 올릴 이유가 없다.
- 세월교 Ø1000 → 1.0 + 0.5(토피) + 0.5(물넘이) = 2.0
- 물넘이포장 → 0.0
- 앞서 쓰던 월류 높이(ford_height_m) 기준은 폐기
검증: 계획선 재생성 실측 — 세월교 지점 계획고-지반고 2.00 일치
(배수관 1.50/1.30, BOX 2.50도 유지). pytest 6/6, tsc 통과
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- 종단 상단 표시줄을 법정 판정 3항목으로 축소: 최대 기울기/상한(별표2 Ⅰ.2.라),
절·성토 불균형(Ⅰ.1.나.(4)(다)), 종단곡선 필요 n곳(Ⅰ.2.마 — 대수차 5% 초과인데
곡선이 빠진 변화점). 변화점·종단곡선 개수·기본 곡선길이 L·중복 경고칩은 제거.
- [초기선 복원] → [편집 되돌리기] — 실제 동작은 화면 편집 델타 삭제이지 저장 지점
복귀가 아니다. 툴팁에 동작과 [초기화]와의 차이를 적었다.
- 3D 뷰포트 안내 라벨 삭제(로딩 완료 후 남던 조작법 문구).
- 배수유역 버튼 정비: 유역 다시 나누기 / 선택한 관 삭제 / 배관 배치 초기화 +
각 툴팁에 실제 동작 명시(선택 삭제는 툴팁 자체가 없었다).
- 유토곡선 요약 12칩 → 5칩(절토·성토·잉여|부족·운반·검산). 토질 3종 내역·다짐
환산·블록 수·장거리 운반·사토는 해당 칩 툴팁으로. B05·B06 공용 함수라 동시 반영.
- 설계속도 축 도입(임도는 속도를 낼 수 없는 노선 — 기본 20km/h):
· 임도 종류는 프로젝트 등록값(projects.road_type)을 읽어 B05가 읽기 전용 표시
(main→간선임도, fire→산불진화임도, 그 외→작업임도). 화면에서 고치지 않는다.
· 설계속도 선택(간선·산불진화 20/30/40, 작업 20 고정) 신설 — 종단기울기 상한과
종단곡선 반경이 등급이 아니라 이 값으로 정해진다(지식DB 설계제원_총괄 §6·§7).
· B02 임도 종류 선택지를 현행 3종으로 정리(지선 폐지·계류보전은 사방 구분).
· 백엔드: ROUTE_GRADE_CLASSES 3종+branch 호환, selectable_design_speeds 신설,
resolve_design_speed 신설, legal_grade_limit_pct·resolve_grade_options에
설계속도 인자, sections/context가 road_type 제공(B06도 저장값 승계).
· 계획선 정책은 그릴 때마다 현재 기준으로 동기화 — 저장분의 옛 상한이 남지 않는다.
- 검증: pytest 125 통과(설계속도 12건 신규), tsc, 헤드 브라우저 — 진입 시 상한 9%,
40km/h 전환 시 7% 즉시 반영, 원복 9%, 안내문 '간선임도 · 설계속도 20km/h ·
일반지형', 유토곡선 5칩, 유역 버튼 새 이름·툴팁, 3D 라벨 빈 문자열 확인.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
지시 2 — 배수유역도가 유효직경 다음에 추천을 보여 주고, 초기 전처리 계산이
그 값으로 관 지점을 설정한다:
- recommend_structure(): 유효직경 임계로 구조물·관경을 고른다. D≤1500 배관(규격
스냅 800·1000·1200·1500), 1500<D≤2000 BOX암거 후보, D>2000 세월교 검토.
판정 근거는 유량뿐 — 계곡 횡단경사·하천 차수는 지형 계산이 필요해 뺐고 화면이
"현장 확인"으로 안내한다(2026-08-17 사용자 확정)
- WatershedBasin에 recommended_facility·recommended_diameter_mm·required_area_m2
추가, 응답 payload와 DetailBasin 타입까지 배선
- _apply_recommendations(): 자동 배치 관의 **빈칸에만** 추천을 채운다. 사용자가
놓은 관(source=user)·이미 바꾼 시설·이미 있는 옵션 키는 건드리지 않는다.
carry_facility_attributes 뒤에 얹어 승계값이 덮이지 않게 했다
- 유역 목록 표기: `Ø1234mm → 추천 Ø1500 배관` / `→ BOX암거 후보`
- 추천 관경 목록은 레지스트리 pipe_diameter_mm 선택지와 같아야 한다 — 폼에서 못
고르는 값을 추천하지 않도록 테스트로 묶었다
지시 1 — 물넘이포장·세월교는 지정할 옵션이 거의 없으니 계산값이라도 보여 준다:
- 레지스트리에 ford_width_m(월류 폭) 신설. 기본값 없음 — 지식DB에 폭 근거가 없어
프로그램이 지어내지 않는다
- fordSection(): 설계유량과 폭으로 필요 수심을 되짚는다. 광폭 근사로 출발해 실제
동수반경(R=A/P)으로 수렴시킨다 — 근사값을 그대로 쓰면 통수능이 늘 설계유량에
못 미쳐 판정이 무의미해진다. n=0.017·S=10%는 실무 수리계산서 역산값
(울진1 물넘이·관 시트가 둘 다 S=0.10에서 검산 일치)
- 폼에 월류 폭 한 칸 + 읽기전용 요약(설계유량·필요 수심·필요 단면·유속). 설계유량은
유역 payload에서 폼까지 배선(onPipesChanged → Page → setFacility)
⚠ 실무 시트의 수심 0.27m는 Qd 역산값이 아니라 가정 수심이다(B=20·h=0.27의 통수능
41.2㎥/s ≫ 그 시트 Qd 2.54㎥/s). 실무는 단면을 가정해 통수능을 검증하고, 프로그램은
필요 최소 수심을 낸다 — 화면 문구도 "필요 수심"으로 적었다.
검증: pytest tmp/tests 124 passed·7 skipped(신규 17건), npm run typecheck 무오류,
npm run build 성공. 물넘이 산식은 프론트 전용이라 수치 손검증으로 대신했다 —
Qd=2.54·B=20에서 필요 수심 0.0503m·유속 2.526m/s, 통수능이 2.5400㎥/s로 Qd와 일치.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
지시 1 — 사이드 「종단 설계 기준」 컨테이너를 기본 접힘으로. section()에
collapsed 인자를 더해 초기 is-collapsed만 붙이고 토글은 전역 collapsible이
그대로 맡는다(B06 buildGroup과 같은 패턴).
지시 4 — 배수관 유입·유출 바닥 보호를 "보호공" 한 축으로 개편:
- 구 돌붙임 있음/없음 + 표면처리(찰/메) 두 축 → 돌붙임(찰)/돌붙임(메)/도수로.
도수로·산비탈수로(B4)는 유출부에서 돌붙임 대신 쓰는 공종이라 별도 구조물이
아니라 이 선택지로 받는다(별표2 유출구~원지반 도달 의무)
- "없음" 삭제 — 물이 흐르는 자리라 보호공은 필수(사용자 확정). 기본값은
유입 돌붙임(찰)·유출 돌붙임(메)
- 치수는 배타 — 돌붙임이면 면적(㎡, 기본 10), 도수로면 폭(m, 기본 1.0)
- 저장 키 개명 *_pitching → *_protection, *_pitching_finish 삭제,
*_protection_width_m 신설. 독립 기슭막이(D4)도 같은 축
- 구 저장분은 parse_pipe_points가 읽는 순간 새 키로 옮긴다("없음"은 부위
기본값으로 승격). 폼 write()도 구 키를 폴백으로 읽어 structures.json 몫을 덮는다
- revetment.pitching_area_m2가 기본값과 required를 동시에 갖던 모순도 정리
지시 5 — 700줄 초과 파일 분리(순수 이동 + import 정리, 공개 인터페이스 불변):
- Drainage_Facility 724 → 401 (+_Facility_Fields 354)
- Structures_Panel 753 → 695 (+_Structures_List 113)
- Drainage_Panel 822 → 693 (+_Drainage_Chrome 150, +_Drainage_Interact 159)
- Page 1031 → 911 (+_Page_Helpers 144) — 아직 초과
- Profile_Panel 1152 — 미착수. 두 파일은 가변 클로저 상태를 곳곳에서 직접
읽고 써 상태 승격이 선행돼야 하며, 프론트 테스트가 없어 순수 이동으로는
떼어낼 수 없다. 분리안은 PLAN.md 3-3에 기록
검증: pytest tmp/tests 107 passed·7 skipped(신규 보호공 마이그레이션 6건 포함),
npm run typecheck 무오류, npm run build 성공(374 modules).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
대시보드 삭제 버튼은 지금까지 projects.deleted_at만 찍는 소프트 삭제였다. 배포에서는
그게 맞다 — 사용자가 올린 라이다 원본은 다른 프로젝트에 재활용할 자산이다. 그러나 개발
중에는 프로젝트를 반복 생성·삭제하는데 정리 잡이 없어 수십 GB 원본이 계속 쌓인다.
config_system.py 맨 위에 PROJECT_DELETE_HARD_ENABLED를 두고 갈랐다. 기본값 False라
환경변수를 빠뜨린 배포 환경은 자동으로 안전한 쪽에 선다. True면 projects 행을 실제로
DELETE 하고(자식 테이블은 FK CASCADE로 함께 사라진다) storage/{회사}/{사용자}/{프로젝트ID}/
폴더를 통째로 지운다.
자식 테이블 목록은 코드에 나열하지 않았다. projects.id 참조가 전부 ON DELETE CASCADE라
행 하나면 충분하고, 목록을 복사해 두면 스키마가 바뀔 때 조용히 어긋난다.
순서는 DB 먼저 커밋, rmtree 나중이다. 파일을 먼저 지우면 DB 실패 시 실체 없는 프로젝트가
목록에 남아 화면이 깨진다. 반대면 rmtree가 실패해도 고아 폴더만 남고 정합성은 유지된다.
resolve_stored_project_path()는 끝에서 makedirs를 하므로 삭제에 쓸 수 없다 — 지우기 직전에
폴더를 되살린다. 검증만 하는 resolve_project_root_for_delete()를 따로 뒀고, 저장소 루트 안 ·
세그먼트 정확히 4개 · 마지막 세그먼트가 요청 project_id와 일치를 모두 요구한다. DB의
storage_path가 오염돼도 상위 폴더나 남의 폴더를 지우지 못한다.
하드 삭제 모드에서는 확인 모달 문구를 바꿔 원본까지 사라진다고 알린다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
사용자 결정(2026-08-08): 새 자료를 올리면 재계산해 덮어쓰고, 화면은 불러올 자료가 없으면
대시보드로 보낸다. 그러려면 자료가 갈리는 순간 옛 계산 결과가 남아 있으면 안 된다.
- common_util_project_reset 신설: 단계별 산출물 폴더(B04~B09)와 산출물 DB 레코드
(surface_models·routes·route_points·route_statistics·longitudinal_sections·
cross_sections·structures·quantity_items·outputs·processed_point_cloud)를 함께 지운다.
**B03_FileInput(업로드 원본)은 지우지 않는다** — 같은 파일인지 가리는 중복 검사가 쓴다.
- 직접 업로드 완료·보관함 연결 양쪽에서 정리를 호출하고, 진행 단계도 1단계 이후를
NOT_STARTED로 되돌린다(reset_stages_after_input_change).
- is_analysis_running(): 전처리가 도는 중이면 업로드 세션 생성과 보관함 연결을 409로
막는다. 화면 버튼 잠금은 새로고침·다른 탭으로 우회되므로 서버에도 문을 단다.
- fail_stage 아래 있던 리비전(번호표) 안은 폐기 — 사용자가 "산출물이 없으면 대시보드"
방식으로 정리했다.
곁들여: os.replace 공유 위반 재시도(replace_with_retry)
진행률 파일은 서버가 쓰는 동안 화면이 계속 읽어 WinError 5가 났고, 전처리
structured.npz 교체에서는 같은 이유로 분석이 통째로 죽었다(결함 3의 사망 원인).
짧게 여러 번 다시 시도하도록 바꿨다.
검증(실서버 f45243b3, 계획노선 CSV 재업로드로 자료 교체):
전처리 결과 112개 -> 재분석분만, 노선 2->0, 횡단 20->0,
DB 지표면 15->0 / 노선 1->0 / 횡단 19->0, 업로드 원본 6개는 그대로.
진행 단계가 초기로 돌아가고 재분석이 자동 시작됨.
교체 재시도는 읽는 쪽이 파일을 잡고 있는 상황을 만들어 성공 확인.
ruff format·check 통과.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
라이다 원본은 업로드에 오래 걸려 프로젝트 정보 확정 전에 미리 올릴 수 있어야 한다.
계정에 묶인 임시 보관함을 만들고, 나중에 만든 프로젝트로 자료를 옮겨 쓴다.
저장·DB
- storage/tmp/{user_id}/{batch_id}/ 아래에 프로젝트 저장소와 동일한 구조를 써서
청크 저장·병합 엔진(resolve_upload_destination/merge_upload_chunks)을 그대로 재사용
- 010_temp_upload.sql: temp_upload_batches / temp_upload_files 신설,
upload_sessions.project_id NULL 허용 + temp_batch_id 추가(FK명 조회 후 재생성)
- config: TEMP_UPLOAD_DIR_NAME / TEMP_UPLOAD_RETENTION_DAYS(30) /
TEMP_UPLOAD_CLEANUP_INTERVAL_HOURS(6)
백엔드
- B03_FileInput_Router_Temp.py: 묶음 생성·목록·삭제, 일반/청크 업로드, finalize,
이어올리기 상태 조회, 프로젝트 연결(attach)
- attach: 파일 이동 후 input_files 등록, stage 0 완료, WF1·자동 설계 체인 트리거
- common_util_temp_cleanup.py: 완료 시각 기준 만료분 주기 삭제(서버 시작 시 1회 포함)
프론트엔드
- B01 대시보드 임시 보관함 섹션: 프로젝트 등록과 같은 폼 + 보관 목록.
진행률은 모달이 아니라 리스트 행에 표시, 새로고침 후 이어올리기 지원
- B03 업로드 컨테이너 내부 불러오기 버튼과 선택 모달.
완료된 묶음만 노출하고, 선택 후 업로드를 누르면 이동과 분석으로 이어짐
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- 유토곡선 SVG 상단 여백 padTop 옵션 신설(공용 렌더러), B05는 40px —
범례·기준 버튼 오버레이(top 34px)와 커브 겹침 해소. B06은 기존 10px 유지
- 사이드 패널 컨테이너 공통 클래스(ui-sidebar-section): 외곽선을 본문 보조색
45% 혼합으로 진하게 — 두 테마 모두 흐려 안 보이던 문제. B04/B05/B06 적용
- 하단 패널 리사이즈 끊김 해소: 끌리는 동안 프레임당 경량 높이 동기화
(캐스케이드 계산 + 캔버스/차트 높이만), 무거운 차트·테이블 재구성은
손 뗀 뒤 120ms 디바운스로 1회. 추가 리소스 없음 — 프레임당 비용 감소
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- 세월교 판정: 유효직경이 관 최대 규격(D2000, config) 초과 시 bridge_required
플래그 — 관이 아니라 세월교·물넘이·교량 대상(임도설치규정 제12조: 계류 횡단
구간은 물넘이 포장·교량으로 설계). 유역 제원에 '세월교 검토(Ø계산값)' 표기,
관경 자동 지정 제외. 실측: 41.4ha 계류 유역 D5137 -> 세월교 검토
- 사면 유역 계산식은 유지 — FHWA 유입부 조절식과 교차검증 결과 근접
(1.1ha D862/D839, 2.9ha D1426/D1255)
- 하단 패널: 서브패널이 종단도를 덮지 않고 밀어올림. 접힘=종단 전체,
하나 펼침=그만큼 종단 축소(테이블 높이 고정), 둘 펼침=종단-테이블-유토곡선
순서로 유토곡선 리사이즈 시 종단만 조절
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
바로 위 종단 표고축(…m)과 같은 자리에 서므로 단위가 없으면 어느 축인지 읽히지
않았다. 유토곡선 축 라벨에 ㎥를 붙이고 0선 눈금도 "0㎥"로 표기한다.
.b05-profile__yaxis-inner에 overflow: hidden을 걸어 눈금이 자기 그래프 영역 밖으로
새어 아래 그래프의 축처럼 읽히는 것도 막았다.
확인: 실제 누가토량 범위는 -213.3~0.0㎥인데 보고된 화면 축은 527~544.7m로 표고였다.
번들 검사 결과 유토곡선 전용 sticky 축과 0㎥ 눈금은 최신 빌드에 정상 포함돼 있다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
페이지 간 캐시 공유
- B06_wf3_ProfileCross_Section_Store.ts 신설: 종횡단 상세를 projectId:routeId
키로 들고 있는 모듈 싱글턴 캐시. B05와 B06이 같은 객체 참조를 보므로 B06이
측점 설계를 제자리 갱신하면 B05가 다음 그리기에서 그대로 본다 — 횡단을 고친
뒤 B05 유토곡선이 옛 값으로 그려지던 문제의 원인이 페이지별 개별 fetch였다.
- 두 페이지 진입을 loadSectionDetail()로 통일(동시 호출은 Promise 공유).
- 정본이 다시 쓰이는 조작에 캐시 갱신: 횡단 재생성 → replaceSectionDetail,
계획선 편집 저장 → invalidateSectionDetail.
계획선 호 기하
- 배관 지점(지면선 × 배관 세로선 교점)이 호 위에 오도록 변화점 표고를 반복
보정. 대칭 종단곡선은 꼭짓점을 지나지 않아 중앙종거만큼 어긋나 있었다.
시작점·종점과 각 호, 호와 호 사이는 직선이 접선으로 잇는다.
- 호 반경 기본값을 설계 기준의 종단곡선 최소 반경으로 지정하고 curve_radii
편집 델타에 실어 B05 테이블에서 사용자가 그대로 고칠 수 있게 했다.
유토곡선 Y축
- B05에만 있던 종단용 sticky 표고축 탓에 가로로 훑으면 유토곡선 눈금은 흘러가고
표고축만 남아 Y축이 높이로 읽혔다. createMassHaulChart에 onAxis 콜백을 더해
유토곡선용 sticky 누가토량 축을 따로 고정.
검증: solve 재실행 후 배관 4개 자리에서 계획고=지반고(오차 ≤0.0001m), 호 비겹침
확인. typecheck·vite build·ruff·B03 테스트 통과.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
계획선 1차 로직 (2026-08-03 사용자 확정)
- design_pipe_anchored_profile 신설: 배수유역도가 확정한 배관 배치 측점을
변화점으로 삼아 계획선이 각 배관 자리에서 지면선과 만나도록(계획고=지반고)
시작점→배관1→…→종점을 직선으로 잇고 기본 R을 얹는다. 배관(암거)은 계곡
유하부라 계획선이 그 지점에 붙어야 복토·유입 조건이 성립한다.
- 기존 지반 추종 직선 분할 DP 선형은 2차 폴백으로, 구 균형 최적화 계획선은
3차 폴백으로 강등. 관 지점 파일의 노선 지문이 다르면 배관을 쓰지 않는다.
- 테스트 프로젝트 solve 재실행으로 저장분 재계산 — 변화점이 배관 5개 자리와
일치하고 각 자리 계획고=지반고임을 검증.
유토곡선 UI (사용자 피드백 5건)
- 기준 버튼 라디오화: 횡단/종단 택1로 그 기준 그래프만 전체 영역에 표시
(applyLegendToggle/normalizeVisibleBasis, B05·B06 공통 저장 키 공유).
Y 범위도 표시 중인 기준으로 산정.
- B05 Y축 이중 표시 제거: MassHaulAxis.axisX 신설 — 축 선·눈금을 종단 그래프
축과 같은 자리에 그리고 데이터 매핑은 유지해 측점 세로 정렬 불변.
- 종단↔유토곡선 경계 드래그 리사이저(비율 0.25~0.75, 세션 보존, 기본 1:1).
- B05 범례를 B06과 같은 오버레이 우상단 절대배치로 이동.
- 도면 테이블: 구조물 배치로 갈라진 구배 블록 값을 기본 숨김(윤곽·툴팁만),
해당 측점 선택 시 하이라이트와 함께 표시.
typecheck·vite build·ruff·B03 테스트 통과.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
사용자 피드백 반영(2026-08-03 2차).
유토곡선 일원화
- B05 유토곡선을 B06과 같은 계산(computeMassHaulSeries: 횡단 정식 + 종단 개략
비교)으로 전환. 상세 조회가 미지정 측점에 기본 설계를 즉석 계산해 얹으므로
B05 진입 시점에 이미 정식 곡선 재료가 있다. 표시 토글 sessionStorage 키와
balloon 위치 scope를 B06과 공유해 두 화면이 같은 그림을 유지한다.
- B05 전용 개략 엔진(computeLongitudinalMassHaul) 삭제.
- 유토곡선 펼침 시 종단 그래프:곡선 세로 1:1 분할.
사토장·토취장 자동 선정 삭제
- 임도에는 토취장이 없고 부족분은 설계자가 계획선을 고쳐 맞춘다는 실무 판단에
따라 4옵션(비용/지형안정성/계곡부/임내공간) 엔진·API·UI·config·저장 훅 전부
제거. massHaulPayload/createMassHaulLegend 시그니처 원복.
기본 지반 변경
- 기본 설계 프리뷰·확정 기본값을 토사에서 리핑암 + 예상 암반 경계 0.5m로 변경.
산지 절토는 표토 아래 암이 일반적이라 전량 토사 가정은 물량이 낙관적이다.
발파암은 B06에서 측점별 수정.
계획선 R·선 연결 (배관 구조물 자리)
- 계획선 샘플이 고정 격자로만 평가되어 격자 사이 변화점(배관 측점 승격분)의
모서리를 잘라먹던 결함 수정 — 샘플 집합에 PVI·BVC·EVC를 합집합으로 포함
(프론트 buildAlignment + 서버 build_alignment 동일 규칙). 면적 가중치도
합쳐진 격자로 재계산. 37.3m 변화점 + R=150 수치 검증 통과.
표시 개선
- EP(종점) 잔량 라벨: 곡선 끝점에 "EP {누가토량}㎥" 불투명 판(B05·B06 공통).
- 유토곡선 0선을 붉은 굵은 실선(2.5px)으로, 0 눈금값도 적색.
- 3D 뷰 [측점 가로선] 우측 [측점 라벨] 토글 신설(기본 꺼짐) — 구조물 측점만
"측점번호 구조물명" 스프라이트 표시.
typecheck·vite build·ruff·B03 테스트 통과. 실서버 스모크로 리핑암 기본 프리뷰와
disposal-sites 제거 확인.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
절·성토 균형은 종단 시공계획고로 정해지고 사토장 위치도 계획고를 다시 끌어야
정리되므로, 계획용 유토곡선과 부지 선정을 B05로 옮겼다. B06은 실측 단면적으로
낸 정식 곡선과 확정·B08 인계를 맡는다.
B05 계획 유토곡선
- _UI_Profile_MassHaul.ts: 종단면 패널 안 2차 하단 슬라이드. 펼치면 12행 도면
테이블 자리를 곡선이 대신 차지한다. 곡선 SVG를 종단 그래프와 같은 가로
스크롤러 안 형제로 넣고 MassHaulAxis에 LONG_PAD + originOffset을 넘겨 X축을
종단 chainageMapper와 일치시켰다.
- computeLongitudinalMassHaul(): 횡단 설계가 없는 계획 단계용 개략 엔진. 표준횡단
노반폭을 전 구간 공통으로 물린다. 종단 기반 토량은 국내 기준상 노선계획 개산용
이므로(2026-08-03 조사) 표준단면까지 씌워 정밀화하지 않는다.
사토장·토취장 자동 선정
- B05_wf2_Route_Engine_Disposal.py + POST /route/disposal-sites: 노선 corridor
DEM 격자를 훑어 후보 부지를 추리고 옵션별 점수로 정렬한다. 기준 4가지 —
비용(최단거리+하향 운반), 지형안정성(완경사·계곡 이격), 계곡부(계곡 축 매립),
임내 공간(라이다 수고 기반 공터). 기준값 정의처는 DISPOSAL_SITE_CRITERIA.
- _UI_Profile_Disposal.ts: 범례 줄의 [토량 분배]와 [도형 위치 초기화] 사이에
구분기호와 함께 라디오 토글 4개. 부지 카드에 위치·용량·운반거리와 기준별 근거값,
측점·용량 사용자 정의 편집과 복원. 후보 부족으로 남은 토량은 경고로 알린다.
- 확정 시 종단 정본의 disposal_sites에 심는다(기준 미선택이면 저장분 삭제).
B06 연동
- common_util_mass_haul_sites.ts: 저장된 부지를 정식 곡선의 잔량 위치 기준으로
운반거리를 다시 재 표시하고 mass_haul.disposal_sites로 B08에 넘긴다.
미구현(계획서에 남김): 토취장 토질 판정(지반유형이 B06 산출물), 계곡부 암거 연장.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
B05 계획 유토곡선과 B06 정식 유토곡선이 같은 엔진을 쓰도록 모듈을 옮겼다.
- B06_wf3_ProfileCross_UI_MassHaul{,_Balance,_Balance_View,_Balloon,_Curve,
_Settle,_View}.ts + Style_MassHaul.css → common_util/common_util_mass_haul*.
- common_util_mass_haul_types.ts 신설: GroundType/EarthworkConversion/
HaulEquipmentLimit/BalloonOffsets 정의처를 한 곳으로 모으고 B06 Api_Fetch가
재수출. 엔진 입력은 페이지 API 타입 대신 구조적 부분집합으로 받는다.
- common_util_svg.ts 신설: svgElement/svgText/L/stationLabel/
inferStationInterval 이관, B06 _UI_Section_Common은 재수출로 경로 유지.
- createMassHaulChart에 MassHaulAxis 인자 추가 — 종단 렌더러 상수 의존을
걷어내고 호출한 쪽이 X축(누가거리 최댓값·좌우 여백)을 주입한다.
동작 무변경. npm run typecheck 통과.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
[초기화]가 화면 목록만 자동 배치로 되돌리고 edits/pipe_points.json은 그대로 두어,
페이지를 다시 열면 옛 관이 살아났다. B04와 같은 파일을 보므로 B04에도 그대로 남았다.
- DELETE /{project_id}/drainage/pipe-points 신설: 저장분과 파생물
(04_detailed_basins.geojson)까지 지우고 자동 배치 결과를 돌려준다.
- clear_pipe_points() 헬퍼 추가.
- B05 [초기화] 버튼이 이 경로를 쓴다.
- loadSaved / analyze / resetPipes가 같은 뼈대를 반복해 run() 하나로 묶었다.
검증: 초기화 전 {user:1, stream:1, spacing:2} → 초기화 후 {stream:1, spacing:3},
두 파일 삭제 확인, 재조회도 같은 자동 배치.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
데이터 흐름 문제
- B04 해석 산출물 7개(7.2MB)가 B05로 전량 복사되고 있었고, 관 지점 정본
(pipe_points.json)은 B05가 아예 참조하지 않았다. B04에서 확정한 관이 B05에
보이지 않고 진입할 때마다 자동 배치로 되돌아갔다.
- 사본은 "B05 편집이 원본을 덮어쓴다"를 막으려던 것인데, 외곽선 편집을 제거한
뒤로 B05는 아무것도 저장하지 않아 존재 이유가 사라졌다.
일원화
- B05 전용 모듈 3개 삭제: Engine_Drainage_Store / Engine_Drainage_Basin /
Router_Drainage. 두 화면이 B04_wf1_Surface/drainage/ 한 폴더를 직접 읽는다.
- POST /drainage/basins 폐기 → GET/PUT /drainage/pipe-points ·
POST /drainage/detail-basins 한 벌로 통합. 응답에 route_lonlat ·
main_polygon_lonlat · flow_arrows · upstream_lonlat · inflow_hotspots를 추가해
B05가 그리는 데 필요한 값을 같은 응답으로 받는다.
- 경로 확정 시 관 지점을 B04와 같은 저장소에 커밋한다.
B05 화면
- 유역 폴리곤을 눌러 고르기(지도에서도 선택), [집중유역] 토글, 관 개수·유역 수·
종단 Z 출처 요약 줄 추가.
- 관 마커는 좌클릭으로만 잡는다(우클릭은 메뉴, 가운데 버튼은 팬).
- 유역 목록은 3행까지 보이고 나머지는 스크롤.
종단 테이블 구조물 라인
- 관 매설 지점을 "배관" 구조물로 실체화(origin: "pipe"). 정본은 관 지점 파일이며
목록은 그 투영이라, 어디서 옮기든 pipeEditor 한 경로로 전파된다.
- 테이블 위 세로선을 끌어 이동(배관은 세부유역까지 재산정), 우클릭으로 배관
추가/구조물 삭제.
정리
- B05_wf2_Route/drainage/ 사본과 debug/(삭제된 Subdivide 엔진 검증 스크립트) 삭제.
- config DRAINAGE_B05_DIRNAME 제거.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
B04
- 세부유역을 눌러 고르면 그 유역만 진하게, 나머지는 옅게 물러난다. 관 마커를
눌러도 그 관이 받는 유역이 골라지고, 유역 밖을 누르면 강조가 풀린다.
- 고른 유역의 제원(측점·면적·표고차·유하장)을 상태 줄에 표기한다.
B05
- "배관 편집" 토글을 없애고 마커를 언제나 끌 수 있게 했다. 계획선을 그냥 누르는
것으로는 배관이 생기지 않는다 — 유역을 고르려 할 때마다 배관이 생기기 때문이다.
추가·삭제는 B04와 같은 우클릭 메뉴로 옮겼다.
- 배관을 옮기거나 넣거나 지우면 세부유역을 즉시 다시 나눈다(격자 해석 없음).
- "자동 제안" 버튼 이름을 "초기화"로 바꿨다.
- 노선 유입 강도 색칠을 B04 지도와 같은 색띠로 추가했다(토글 포함).
공용화
- common_util_drainage_detail: 1m 구간별 유입 면적 곡선을 응답에 포함(B04·B05 동일).
- B04_wf1_Surface_UI_FlowRamp.ts: 색띠·정규화·강도선 렌더러를 두 화면이 공유.
- ui_template_context_menu.ts: 지도 우클릭 메뉴를 공용 부품으로 분리.
- B05_wf2_Route_UI_Drainage_Parts.ts 신설로 패널을 700줄 규칙 안으로 되돌림(677줄).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
관 매설 지점을 기준으로 세부 배수유역을 나누는 기능을 B04 2D 지도에 추가한다.
경계는 B04 전처리 격자(03_road_routing.npz)의 road_slot — 1m 셀마다 물이 도달하는
도로 셀 — 을 담당 관으로 라벨링해 그 경계로 잡는다. 종단 Z는 경계를 긋지 않고
"도로 셀이 어느 관으로 흐르는가"만 정한다.
공용 승격 (B04 관리자 화면과 B05 사용자 화면이 같은 결과를 내야 함)
- common_util_drainage_detail.py: 관 보충(9)·세부유역 분할(10) 알고리즘
- common_util_drainage_context.py: 노선·종단 Z·좌표계 입력 준비
- common_util_drainage_pipes.py: 관 지점 정본 저장소(edits/pipe_points.json)
- common_util_route_profile.py: 종단 Z 해석기(계획고 > 경로 정점 > 지표면 > CSV)
- common_util_surface_sampler.py: B05 종횡단 sampler 이동
- B05 _prepare()의 노선 소스를 원청 계획노선 CSV로 정정(B04 격자와 누가거리 정합)
B04 신규 API
- GET /{project_id}/drainage/pipe-points 저장분 조회(없으면 자동 생성)
- POST /{project_id}/drainage/detail-basins 편집 중 목록으로 재분할(저장 안 함)
- PUT /{project_id}/drainage/pipe-points 모델 확정 시 관 지점·세부유역 커밋
B04 화면
- 관 마커 기본/자동/수동 색 구분, 계획선 스냅 드래그 이동
- 계획선 우클릭 "관 매설 추가" / 마커 우클릭 "관 매설 삭제"
- 표시 토글 2그룹(관 매설 / 세부 유역)을 유입 집중점과 분리
- "상세유역 분석" 버튼을 눌렀을 때만 재계산
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- 휠 위 = 축소로 반전, 커서 지점을 축으로 한 dolly를 공용 유틸에서 직접 처리
- 회전 중심을 작은 구로 표시(돌리는 동안만, 화면상 크기 일정, 항상 위에 그림)
- 등고선 렌더 비용 감소: 폴리라인을 주곡선·보조곡선 2덩어리로 병합(드로우콜 424 → 2),
라벨은 카메라가 움직였을 때만 재배치
- common_util_http_cache: 파일 mtime+크기 ETag, If-None-Match 일치 시 304
(preview·contour 적용, 파일이 바뀌면 자동 무효화)
- A00_Common/b_asset_cache: IndexedDB 보관함(키 = projectId|url, 값 = 바이트+ETag).
보관본 즉시 사용 후 백그라운드 재검증, 3D는 보관 바이트를 직접 파싱
- 프로젝트 전환 시 타 프로젝트 보관분 삭제, 대시보드→B그룹 이동 시 확정 모델의
3D 프리뷰·등고선(1.0m) 미리 받기(포인트클라우드 제외)
분석이 30초 걸리는데 B05는 일반 사용자 화면이다. 관리자 확인용 B04에서
한 번 돌려 저장하고, B05는 그 결과를 읽어 관 보충과 세부유역만 처리한다
(2026-07-31 사용자 지시).
노선 원천 변경
- B05 확정 경로 -> B03 업로드 계획 노선 파일(CSV). 분석이 노선 설계보다
먼저 끝나 있어야 하기 때문. 샘플 planned_route_sample_epsg5187.csv 로 검증.
- common_util_route_geometry.py 신설 — RouteVertex/StructureCandidate/누가거리
보간/세류 교차점/계획 노선 CSV 리더. B04와 B05가 같은 표현을 쓰도록 공용화.
열 이름은 대소문자·한글 표기를 함께 받는다(B03이 여러 형식 수용 예정).
B04 (관리자 확인용, 신규)
- Engine_Watershed_{Grid,Stream,Descent,Flow,Expand,Export} — B05에서 git mv
- Engine_Watershed_Analyze.py — 1~8단계 오케스트레이션
- Router_Watershed.py — GET /drainage/primary-region
- UI_Watershed.ts — 2D 지도 GIS 레이어 그룹에 "배수유역" 토글 추가.
격자/화살표/세류망/1차영역/2차유역/기본관을 겹쳐 그린다.
- 저장 위치 B05_wf2_Route/drainage -> B04_wf1_Surface/drainage
- 03_road_routing 단계 추가: B05가 세부유역을 나눌 최소 배열(셀->도로셀 귀속,
유하장, 강도, 도로셀 제원, 셀 표고) + 계획도로선/기본배관/2차유역 기하
B05 (일반 사용자용, 축소)
- Engine_Drainage_Basin.py — B04 산출물 로더 + 관 보충(9) + 측구 라우팅/세부유역(10,11)
- Engine_Drainage.py 는 관경 산정만 남기고 322 -> 27줄
- Router_Drainage.py 509 -> 142줄. POST /drainage/basins 만 남김
- 화살표·격자·강도 띠 렌더 제거. 계획도로선/기본배관/2차유역만 받는다
삭제
- _legacy_watershed/ 4파일 (능선 행진 방식 원본 보관본)
- Engine_Watershed_Basin.py (B04 Analyze + B05 Drainage_Basin 으로 분할)
- GET /drainage/candidates 와 propose_structure_stations (구방식 후보 제안)
E2E 검증 (실데이터)
B04 분석 28.2s -> 저장(geojson 11KB + npz 2.6MB)
B05 로드 + 세부 설계 0.11s <-- 30초가 0.1초로
면적 457,404m2 로 B04 2차 유역과 정확히 일치
관 편집 재산정 0.12s, 관 3개 -> 세부유역 3개, 면적 보존
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>