# 2026-09-03 완료 항목 이관 > 사용자가 완료 항목의 위치 정리를 요청했으며, 요청에 따라 위키 관리자의 2차 교차검증과 별도 검증보고서는 생략했다. > `docs/raw/PLAN.md`에서 완료 체크 항목과 그 하위 원인·조치·자체검증 설명을 이관했다. ### 2026-09-03 화면 개선 (메인 창 · B05·B06 담당) - [X] **I형 집수정 배관이 횡단도에 삼각형으로 그려짐** (사용자 항목 4). - 원인 — `B06_Section_UI_Cross_Culvert_Solve.ts` `pipeAxisSolver.corners()` 가 관 끝단 두 꼭짓점을 마감면(I형 벽 안쪽 변) 선분 안으로 `clampToFace` 하는데, 원지반이 벽 상단 위라 관 시작점이 벽 상단에 붙으면 위·아래 교점이 **같은 끝점으로 접혀** 사각형이 삼각형이 됨. 13+4.1 실측 — 관 폴리곤 `[[594.6,418.7],[338.9,537.3],[348.5,505.4],[594.6,418.7]]` (1번=4번 꼭짓점 동일), 집수정 벽 상단 `y=419` = 관 시작점 `y=418.7`. - 조치 — 자른 끝단 길이가 관경보다 짧으면(비스듬히 자르면 항상 관경 이상) 접힌 것으로 보고 **축 직각 마감(fallback)** 으로 되돌림. 편집 단일 창구인 `corners()` 한 곳만 수정. - 자체검증 — 공용 브라우저 B06(8+17.3·13+4.1 등 8개 단면) 실측: 수정 전 유입측 끝단 길이 `0.0 px`(세월교 1건 제외 전부), 수정 후 `18.7~58.4 px` 로 전부 살아남. `npm run typecheck` 통과, prettier 무변경. - **2차 수정 (2026-09-03 사용자 지적 「측벽과 배관의 형상이 교차하고 있어야 함」)** — 1차 수정은 삼각형만 없앴을 뿐 관이 벽 위에 얹혀 벽과 만나지 않았음. ㄴ형과 대조해 (관이 바닥판 윗면에서 시작해 벽 안에 물림) 원인 확정: I형 관 하단이 벽면×원지반 교점인데 원지반이 벽 상단보다 높으면 교점이 상단으로 밀림. **교점을 벽 상단에서 관경+여유(0.5m) 아래로 제한**해 관이 벽을 관통하게 함. 벽을 키우는 안은 벽 기울기(1:0.3)로 base 가 계류측으로 밀려 벽이 7.5m 까지 자라 폐기. - 2차 자체검증 — I형 8개 단면 관·벽 세로 겹침 `18.1~56.4 px`(수정 전 0). ㄴ형 대조는 13+4.1 을 조정창에서 ㄴ형으로 바꿔 확인 후 세션값 원복. pytest 370 passed·17 skipped. - 육안검증 기록(아티팩트): https://claude.ai/code/artifact/c4ef706c-c3d8-461c-ae54-6c376980c043 - [X] **유토곡선 세부 항목이 리로딩 중 보였다가 사라짐** (사용자 항목 3) — 원인 규명 완료. - 재현(공용 브라우저 30ms 폴링) — 2.0초 그림: 절토 6,908 / 성토 42,050 / 운반 5,571㎥, 평형선 6·장비 띠 9·balloon 12. 3.5초 그림: 절토 3,704 / 성토 14,881 / 운반 0㎥, 평형선 1·띠 0·balloon 1. - 원인 — `B06_Section_UI_Page.ts:285 reconcileStaleDesigns()` 가 저장분 횡단 설계 중 현재 종단 계획선과 어긋난 측점을 일괄 재계산해 제자리 교체함. 첫 그림은 **재계산 전 저장분**(20m 구간당 성토 860㎥ — 임도에 불가능한 값), 둘째가 정본. 캐시 덮어쓰기· 숨김·토글 문제 아님(범례는 두 시점 모두 「토량 분배」 켜짐). - 벌룬이 정본 상태에서 안 생기는 이유 — 성토 14,881 vs 절토 3,704(4배)라 누가토량 곡선이 0선을 한 번도 넘지 않아 평형선 계단이 서지 않음. 극값 가지치기 기준(범위×2%) 탓이 아님 — 실무 6건은 같은 기준으로도 극값이 14~53개 남음. - 실무 원본 대조(`유토곡선.MASS` 6건 파싱) — 최종 누가토량 **+49 ~ +324㎥**, 0선 교차 3~17회. 용화는 **−11,094㎥ · 교차 0회**. 실무 `EARTH.DAT` 헤더 6건 전부 무대 20.0 / 도자 **60.0** m (현행 구현은 20/70). - 조치 — 사용자 확정(2026-09-03 「절·성토 불균형 판정은 사용자 몫, 자동 조정 금지」)에 따라 **진단만 노출**: 유토곡선 요약줄에 「최종 누가토량」 칩 신설(툴팁에 균형 의미와 평형선 미생성 사유). B05·B06 공용 함수라 양쪽에 함께 뜸. - 지식DB — [유토곡선_토량배분](../../resources/knowledge/technical_info/01_임도/03_계산정보/유토곡선_토량배분.md) 신설(실무 6건 관측표·장비 한계거리 후보·근거 공백 명시), 미결 20·21 등재. - 자체검증 — 공용 브라우저 실측: 요약 칩이 `절토(자연) 3,704.6㎥ · 성토 14,881.8㎥ · 토취 11,094.6㎥ · 최종 누가토량 −11,094.6㎥ · 운반토량 계 0㎥ · 운반·성토 검산 −3,787㎥` 로 표시됨. `npm run typecheck` 통과, prettier 통과. - [X] **옛 계획고 유토곡선 노출 차단 + 사면 미교차 경고** (2026-09-03 사용자 확정). - 「새 값만 보여주기」 — 저장 횡단이 지금 계획선과 어긋나면(재계산 예약) 유토곡선을 그리지 않고 안내만 띄움. B05·B06 양쪽 적용(`staleDesignChainages` 공용 판정). - 「사면 미교차는 경고로 대체」 — 사면이 계산 반폭(±20m) 끝까지 원지반과 안 만나 면적이 잘린 측점에 카드 경고. 판정은 면적을 내는 엔진 한 곳(`B06_Section_Engine_Design`)이 `slope_unclosed` 로 내림 — 화면·수량·도면이 같은 기준을 봄. 계산은 무수정. - 실측 근거 — 용화 65측점 중 **56개가 낡음**, 계획고 차 −9.36~+5.83m(예: 0+20 저장 875.36 → 현재 870.26, 저장분 성토 단면 85.9㎡ / 43+0 지점 256.4㎡). - 자체검증 — 공용 브라우저: 2.6초 「재계산 중」 안내만(옛 값 미노출), 4.4초 정본 표시, 사면 미교차 경고 13/65측점. `pytest 366 passed·17 skipped`, typecheck·prettier 통과. - [X] **B05·B06 유토곡선 일원화** (2026-09-03 사용자 지시 「하나의 데이터를 두 페이지에 보여 주는 기능이니 로직·템플릿이 다르면 일원화」). 갈림 3곳을 모두 한 벌로. - **보는 노선** — B05는 최신 경로(`get_latest_route`), B06은 최신 **확정** 경로만 열어 노선을 다시 탐색한 프로젝트에서 서로 다른 노선을 봤음. 실측: B05 route 126(DRAFT) / B06 route 125(CONFIRMED), 같은 20m 측점 계획고 870.26 ↔ 875.36 · 성토 4.82㎡ ↔ 85.9㎡. → `get_workflow_route_context()`(최신 경로) 신설, B06 화면 context가 사용. 납품 도면(B07)이 쓰는 확정 전용 창구는 그대로 둠. - **횡단 재계산 창구** — 호출이 두 벌이라 인자가 갈림(B06만 표준 단면값·암 경계 오프셋 전달, 보존 사용자값 2개 ↔ 7개). → `B06_Section_Cross_Refresh.refreshCrossDesigns()` 하나로 통합, 세션 편집값은 저장소에서 직접 읽어 패널 없는 B05도 같은 값을 보냄. - **낡음 판정** — 「옛 암 2단계 필드 누락」 조건이 B06 페이지에만 있어 B05는 재계산을 건너뜀(실측 절토 합 228.52㎡ ↔ 270.12㎡). → 공용 `staleDesignChainages()` 안으로 이관. - 자체검증 — 공용 브라우저 실측: 두 화면 모두 route 126, 요약줄 문자열 **완전 일치** (`절토(자연) 4,526.8㎥ · 성토 14,881.8㎥ · 토취 10,213.3㎥ · 최종 누가토량 −10,213.3㎥ · 운반토량 계 0㎥ · 운반·성토 검산 −4,669㎥`). `pytest 370 passed·17 skipped` (일원화 소스검사 4건 신설 `tmp/tests/test_masshaul_single_source.py`, 기존 stale 규칙 테스트 1건은 조건 이관에 맞춰 갱신), typecheck·prettier·ruff 통과. ### 초기 계산 실패 처리 후속 - [X] `initial_design.failed` 마커 — **백필·재확정 적용 둘 다 안 함**(2026-09-03 사용자 결정). 새 실패만 마커를 남기는 현행 유지. ### LAS 없는 설계 — 곡선 경고 판정 후속 - [X] 지표면 종류별 계곡 시설 차이 — **원인 조사 완료**(2026-09-03). - 시설 종류는 유효직경 하나로 갈린다: 1,500mm 초과 = BOX암거, 2,000mm 초과 = 세월교, 그 이하는 규격 스냅(800·1000·1200·1500). 임계 유량 실측 — BOX `3.711 ㎥/s`, D800 `1.056 ㎥/s`. - 저장 산출물 대조(같은 노선, 두 번의 LAS 해석): **200.9m 유역이 임계 위에 아슬아슬**하다 — 면적 28,907㎡ → D `1,529.3mm`, 29,209㎡ → D `1,537.3mm`. 임계 1,500mm 를 **2%** 차이로 넘는다. 면적이 1~2%만 작아지면 그대로 관(D1500)으로 내려간다. 같은 LAS 해석끼리도 면적이 1.0% 흔들린다(격자·트림 차이). - 반면 관측된 **BOX → 관D800** 은 그 흔들림으로 설명되지 않는다. D800 이 되려면 설계유량이 3.5배(면적 기준 약 1/3.5) 줄어야 한다 — 표고 해상도 차이가 아니라 **유역 분할 자체가 달라진 것**이다. 같은 관측에서 4번째 시설 자리가 275.71m → 297.71m 로 옮겨진 것이 그 신호(관 위치가 바뀌면 유역 귀속이 통째로 재배분된다). - 149.7m 유역은 414,106㎡ · D `4,719mm` 로 세월교 판정 — 관측과 일치. ### B08 CAD 후속 - [X] 도각 편집 안내 띠 — **이미 해결돼 있음**(2026-09-02 사이드바 액션 1행으로 이동). 2026-09-03 화면 실측: 띠는 `.b07-drawing-actions` 안(사이드바 하단 y=1243)에 평소 숨김 상태로 있어 CAD 리본과 겹칠 수 없음. 코드 변경 없음. - [X] **SVG 서명 — CAD 벡터로 변환**(2026-09-03 사용자 결정). `_Engine_Cad_Svg` 신설(path M·L·H·V·C·Q·S·T·Z, 곡선 16분할, viewBox 비율 유지·y 뒤집기). 래스터는 종전대로 ImageEntity. - **화면 검증 불가**(2026-09-03) — 회사 자산 폴더(`storage/1/assets/`)의 서명·로고가 **전부 PNG**라 SVG 경로를 탈 자산이 하나도 없다. 단위 검증 (`tmp/tests/test_b07_signature_vector.py` 5건)만 선 상태. 화면으로 보려면 SVG 서명을 한 장 올려야 하므로 **사용자 판단 대기**(시험용 SVG 업로드 여부). ### B08 횡단도·장 배치 후속 - [x] **장 단위 확정 흐름 실측**(2026-09-03) — 용화 검증본 `d6997dd9` 3장(`cross_s00220m`)을 실제로 확정해 결함 3건을 찾아 고침(커밋 `4fdbcaf0`). - ① 수량표 역추출이 측점을 구분하지 않음 — `_table_values_from_cells()` 가 도면의 표를 전부 훑어 **마지막 값**을 모든 측점에 기록(실측: 4측점 전부 지반고 `837.21`, 도면에는 840.87/840.33/836.45/837.21). 표 엔티티 id `uuid5(_ENTITY_NS, f"{drawing_id}:qtable")` 로 그 측점 표만 읽고, 못 찾으면 옛 도면 폴백. - ② 계획고가 통째로 빔 — 장 배치 입력 원본에 계획고가 없어 21항목 중 지반고 1개만 채워짐. `_quantity_table(source, design)` 이 횡단 설계의 `design_elevation_m` 도 보게 함. - ③ 확정 해제(invalidate) 404 — 도면 목록을 `designs` 없이 만들어 장 나눔이 달라짐. 목록·확정과 같은 인자를 쓰게 함. - 자체검증(백엔드 재시작 후 실동작) — invalidate `404 → 200`, 표 값이 측점마다 다름 (`ground=840.87 planned=840.87 fill=0.00` · `ground=831.72 planned=836.64 fill=4.92`), `manifest.json.quantity_tables` 5측점(220·240·260·264·280) 각각 4/21 항목 채움 (나머지 17칸은 CAD 에서 사용자가 채우는 자리). 시험 확정은 전부 해제해 원상복구 (확정 남은 도면 `[]`). pytest 383 passed·17 skipped. - 관찰(결함 아님) — 확정하면 설계 재계산으로 **장 나눔이 바뀜**(3장 220~264m → 220~280m). 확정 시점의 장 구성을 고정할지는 판단 대기. - [X] **돌쌓기 해칭 — 경계로 잘라 싣기**(2026-09-03 사용자 결정). 수확기가 `g[clip-path]` 를 통째로 건너뛰어 형태 해칭이 CAD 에 하나도 안 실렸다. 선분–폴리곤 교차로 안쪽 조각만 남기고 클립 안 원은 36각으로 펴서 자른다. - **화면 검증에서 2차 결함 발견·수정**(2026-09-03, 커밋 `0f5b4e4d` — 자동 폴백 커밋에 묶여 메시지가 `auto:` 로 남음). 클립 건너뛰기만 고쳤을 뿐 수확기 selector 가 `polygon, polyline, line, circle, text` 라 **`rect` 를 아예 안 봤다**. 돌쌓기 돌·돌망태 칸은 `cell()` 이 `rect` + `rotate(벽기울기)` 로 그리므로 주 표현이 통째로 빠졌다. 실측(용화 route 126 B06 화면) — 형태 해칭 80개 = `line` 31 + **`rect` 49**(61%), 264.06 카드는 `rect` 4(돌쌓기(메) 4켜) + `line` 1. - 조치 — selector 에 `rect` 추가, `rectPoints()` 로 네 모서리 점열 변환(`rotate(각도 cx cy)` 적용, `rx` 무시). 회전을 빼면 기운 돌이 벽 폴리곤을 벗어나 클립에 전부 잘린다. - 자체검증 — 3장(220~280m) CAD 구조물 엔티티 **14 → 22개**(돌 8 = 벽 2매 × 4켜). 돌 상자가 켜마다 가로 1mm 씩 밀려 벽 경사를 따름(`-30,-168,-25,-163` → `-34,-154,-29,-149`). `tmp/tests/test_cad_rect_harvest.mjs` 4건, pytest 383 passed·17 skipped, typecheck 통과. - [X] **장 선택 시 정보 패널 표기 정리**(2026-09-03). 장에는 측점 단위 값이 없는데 제목이 「측점 3장 (220~264m)」이었다 — 제목을 「장」으로 바꾸고 안내 문구를 넣었다. 판정은 도면 id(`cross_s…`)로 한다(`chainage_m` 은 장에도 첫 측점 값이 실려 옴). ### B08 토적도·유역도 후속 - [X] **토적도 축척 = 길이별 자동**(2026-09-03 사용자 결정). `auto_scale_h()` — 후보 1:500~1:6,000 중 A1 가용 폭 700mm 에 드는 가장 큰 그림. 실측 40m→1:500 · 용화 1,106m→ 1:2,000 · 3,465m→1:5,000. 세로는 종이 1mm=50㎥ 고정. - [x] **유역 정보표 측점 표기 통일**(2026-09-03, 커밋 `e29e3cab`) — 토적도와 같은 `n+ 0.0` 형식으로 맞춤. `station_plus_label()` 을 `_Engine_Cad` 로 공용 이관해 두 도면이 한 함수를 씀. 자체검증 — `tmp/tests/test_b07_masshaul_basin.py` 갱신·통과. **화면 실측 완료**(2026-09-03, 「S자유역검증」 `8879c53b` 유역도) — 정보표 48개 전부 `(1) 4+ 18.3` · `(2) 9+ 16.1` … `(48) 221+ 9.9` 형식. - [X] **납품 도면 표기 8행 전부 반영**(2026-09-03 사용자 결정) — 종무대 · `Q= ` 한 칸 · 계단형 지시선 · 평형선·경계현 빨강 · `120+ 0.0` · 표 눈금(정규 빨강·추가 회색) · 행 이름 3.2mm 자간 · `0+16.90`. - 자체검증(2026-09-03, 「구조물검증-LAS」 `2f940d8a` 토적도 엔티티 실측) — **8행 중 6행 확인**: `Q= 4491.46M3`(한 칸) · 계단형 지시선 `(301.9,-99.8)→(291.8,-99.8)→(291.8,0)`(수평 뒤 수직) · 측점 `0+ 0.0`~`17+ 10.1` · 표 눈금 빨강 18 / 회색 5(회색 5 = 추가 측점 5개와 일치) · 행 라벨 `누 가 토 량`·`측 점` 3.2mm 자간 · `M.N= 0+0.00`(소수 2자리). 축척은 `SCALE H=1:600 V=1:50,000` 로 자동 선택. - **남은 2행은 사례 없음** — 종무대(장비 이름)와 평형선·경계현 빨강은 이 프로젝트에 운반 블록이 없어 도면에 그려지지 않음. 코드 상수는 `BALANCE_COLOR = BAND_CHORD_COLOR = "#ff4d4d"` 로 확인. 아래 「운반 블록이 있는 실노선에서…」 항목에서 함께 볼 것. | 항목 | 납품 도면 | 현재 | | -------------- | -------------------- | --------------- | | 장비 이름 | 종무대 | 무대 | | 값 표기 | `Q= 162.41M3` | `Q=162.41M3` | | 지시선 | 계단형·모서리 접점 | 직선·아래 중앙 | | 평형선·경계현 | 빨강 | 흰색·노랑 | | 측점 | `120+ 0.0` | `No.120` | | 표 눈금 | 정규 빨강·추가 회색 | 없음 | | 행 라벨 | 큰 글씨·자간 벌림 | 2.2mm 본문 크기 | | M.N 소수 | `0+16.90` | `0+16.9` | ### B05 종단 계획선 개편 — 검증 후속 (2026-09-02 laptop-main 화면 검증에서 발견) 화면 검증 자체는 완료(실측 수치는 `docs/raw/plans/2026-09-02_plan_user_verified_completed_items_2.md` 「B05 종단 계획선 편집」). 아래 4건은 그 과정에서 나온 **미해결**이다. 완료된 최소고 가드와 구조물 위치 되돌리기는 `docs/raw/plans/2026-09-02_plan_B05_profile_edit_followup_completed.md`로 이동했다. - 취소 — ~~규칙 측점은 선택이 안 돼 방향키 상하도 안 먹는다~~ **오검(2026-09-02 재확인)**. `_Profile_Panel.selectStation` 은 같은 측점을 다시 누르면 선택을 푸는 토글이고, 앞선 검증이 240.000 을 두 번 눌러 해제된 상태에서 ArrowUp 을 보낸 것이었다. 한 번만 누르면 `b06-chart__station--selected` 가 붙고 ArrowUp 이 오프셋 0.1 을 만든다. 규칙 측점도 정상 — 코드 변경 없음. - [X] **`shiftSegment`·`shiftMovingPoints` — 유지**(2026-09-03 사용자 결정). 정리하지 않음. 새 [쉬프트]는 `shiftStraightRuns` 를 쓴다. `_Profile_Alignment.ts` 두 함수의 파일 밖 참조 0건. 「기하 함수는 남김」 방침이라 유지 중 — 되쓸 계획이 없으면 정리 대상(사용자 확인 후). - 참고 — **`/route/confirm`([저장])은 계획선을 재산출하지 않는다.** 저장 경로는 `_Router_Lifecycle.py` 가 기존 `longitudinal.json` 에 상단측 변경분만 합쳐 다시 쓴다. 새 초기 계획선을 보려면 **[경로 계산]**(`/route/solve`)이 필요하다 — 옛 프로젝트를 열어 기본이 안 바뀐 것처럼 보이는 것은 이 때문이고 결함이 아니다. ### B05/B06 구조물 3D 판단 대기 완료된 패치·마스크 수정은 `docs/raw/plans/2026-09-02_plan_user_verified_completed_items.md`로 이관했다. 2D 면적·유토곡선 산식은 분리 보류이므로 건드리지 않는다. - [X] **BOX 날개 사이 — 현행(원지반) 유지**(2026-09-03 사용자 결정). 코드 변경 없음. - [X] **세월교 좌측 접근선 — 패치로 덮음**(2026-09-03 사용자 결정). 접근선(노견 → 벽 상단)은 벽보다 위라 덮어도 관 구멍·L자 통로를 막지 않는다. 벽 전면·유로는 종전대로 리본에 담지 않는다. 접근선 줄을 `patchPoints` 앞에 담고 `fillRuns` 에 한 장으로 실어 「구간 1줄 = 리본 1장」 규칙을 지킨다. - 자체검증(2026-09-03, 용화 route 126) — `fordSilhouettes()` 를 실제 세월교 2측점 (177.33m·358.92m)에 돌려 **첫 리본이 노견에서 시작**함을 확인: 우측 `(-2, 851.18)` = `road_edges.right`, 좌측 `(2, 851.30)` = `road_edges.left`, 면마다 `fillRuns` 2장 (접근선 1 + 성토 1). 3D 픽셀 육안은 카메라를 그 측점으로 몰아야 해 미실시 — 리본 데이터로 판정함. ### B05/B06 700줄 제한 위반 정리 - [X] `B06_Section_UI_Section_View.ts` 751 → **689줄** — 유토곡선 조립을 `_MassHaul` 로 분리. - [X] `B06_Section_UI_Cross_View.ts` 784 → **698줄** — 카드 제목·하단행을 `_Cross_Card_Chrome`, 실효 반폭 계산을 `_Cross_View_Metrics`, 줌 상태 맵을 `_Cross_View_Zoom` 으로. - [X] `B05_Profile_UI_Profile_Panel.ts` — 현재 **699줄**로 제한 안(별도 작업 불필요). - [X] `B05_Profile_UI_Structures_Panel.ts` 1,078 → **1,024줄** — 폼 뼈대를 `_Structures_Form` 으로 분리(1차). - 자체검증 — 분리 3건의 **화면 실동작 확인**(2026-09-03, 용화 route 126): B06 횡단 카드 65장 전부 header·footer 생성(`_Cross_Card_Chrome`), 유토곡선 패널 표시 (`.b06-masshaul`, `_Section_View_MassHaul`), B05 구조물 배치 폼 본문이 `grid → position-row → divider → grid(옵션) → 시설 서브폼 → 메모 → [추가][삭제][리셋]` 순서로 조립되고 목록 11건 표시(`_Structures_Form`). ### B04 세부유역 중첩·빈공간 — 현행 반영 여부 확인 (2026-09-03, 보조폴더) 사용자 지시: 로직은 그대로 두고, **S자 노선에서 아래 세부유역이 위 세부유역을 포함하는 경우를 현행이 반영하는지 확인만** 할 것. - [X] **확인 결과 — 셀 단계는 반영, 내보내기 단계에서 소실**. `assemble_basins`(`common_util/common_util_drainage_detail.py:508`)의 라벨 격자는 셀마다 관 하나만 배정하므로 포함 관계(도넛)가 정확히 살아 있고, `polygonize_labels`(`B04_PreProcess/B04_PreProcess_Engine_Watershed_Flow.py:526`)도 구멍을 가진 폴리곤을 낸다. 그러나 `largest_ring:563`이 **외곽 링 하나만** 뽑고 `WatershedBasin.boundary_xy`(`:107`)·API `polygon_lonlat` (`B04_PreProcess_Router_Basins.py:164,195`)가 **단일 링 자료형**이라 구멍·조각이 실릴 자리 자체가 없음. 즉 화면에서는 아래 유역이 위 유역을 통째로 덮음. **검증**(`tmp/tests/test_basin_boundary_holes.py`, 1m 격자 합성 사례 3건, 3 passed): ① 도넛 — 갇힌 위 유역 256㎡ 전체가 아래 유역 링에 중첩 ② 조각 — 한 관 유역이 두 조각(225㎡+144㎡)이면 작은 144㎡ 소실 = 빈공간 ③ 유역별 독립 simplify(2.0m) — 들쭉날쭉한 공유 경계에서 중첩 18.2㎡ · 틈 35.2㎡ (전체 6400㎡ 대비 0.8%). 규칙적 계단 경계에서는 재현 안 됨(양쪽이 같게 단순화). - [X] **후속 완료(2026-09-03)**: 자료형을 링 목록으로 넓힘. `polygon_parts()` 신설(조각마다 [외곽, 구멍...], 넓은 조각 먼저) · `WatershedBasin.boundary_parts` 로 교체하고 `boundary_xy`(가장 넓은 조각의 외곽) · `boundary_rings`(편 링 목록)는 파생 속성으로 남겨 옛 소비처 무손상 · API 에 `polygon_rings_lonlat` 추가 · 저장 GeoJSON 은 구멍을 가진 Polygon, 조각이 여럿이면 MultiPolygon · 캔버스 3곳(B04 유역화면 채움·선택, B05 배수유역도 채움·선택)은 even-odd 로 한 번에 채우고 `pointInRings()` 로 판정(구멍 안을 눌러도 바깥 유역이 잡히지 않음). 중복이던 지역 `pointInRing` 은 삭제하고 공용 것으로 통일. **검증**: 단위 5건 신규(`tmp/tests/test_basin_boundary_rings.py` — 도넛 구멍 보존, 조각 2개 보존, 파생 속성, 빈 경계, **조립 경로 전체에서 유역 하나가 링 2개**), 전체 168 passed. 브라우저 — API 가 22개 유역에 링 목록을 실어 보냄(경계 없는 1개 제외), 캔버스 even-odd 실측(가운데 알파 0 / 고리 255, nonzero 는 255)로 구멍이 실제로 뚫림. 1차 전체 배수유역(분수령)은 `largest_ring` 그대로 — 외곽선 한 줄이 맞는 자리. - [X] **S자 노선 실사례 검증(2026-09-03, 사용자 지시)**: 용화 지형 위에 스위치백 3구간 합성 노선(CSV, 정점 439·연장 4,528m)을 만들어 신규 프로젝트로 자동 체인까지 완주. 결과 관 48개·세부유역 48개. 도넛은 1건 나왔으나 1㎡짜리 simplify 부스러기였고, **진짜 빈공간의 원인은 최소면적 필터**로 드러남 — 100㎡ 미만 유역은 폴리곤이 아예 안 만들어져 화면에서 통째로 사라졌음(용화 22개 중 1개, S자 48개 중 1개). - [X] **최소면적 필터 제거(세부유역 한정)**: `assemble_basins` 가 `polygonize_labels(..., min_area_m2=0.0)` 로 부름. 실측 면적 오차 용화 5.76%→1.86%, S자 3.56%→1.42%, 경계 없는 유역 1개→0개, 좌표점은 583→622·806→829 로 거의 그대로. 1차 전체 배수유역·다른 호출부의 기본값(100㎡)은 손대지 않음. 전체 168 passed. - [X] **단순화 허용오차 2.0m → 1.0m** (2026-09-03 사용자 결정, 메인 창 경유). 격자가 1m(`DRAINAGE_GRID_SIZE_M`)라 경계가 1m 계단이므로 허용오차를 격자 한 칸에 맞춤 — 형상 손실이 격자 오차 안으로 들어감. 「공유 경계를 함께 단순화」(라벨 경계를 선분 그래프로 한 번만 단순화)는 작업량 대비 우선순위가 밀려 이번엔 채택 안 함. **실측**(min_area=0 상태에서 비교): | 허용오차 | 좌표점(용화/S자) | 면적오차(용화/S자) | 유역끼리 중첩(용화/S자) | 틈(용화/S자) | | -------------------- | ----------------------- | ----------------------- | ----------------------- | ----------------------- | | 2.0m(옛) | 622 / 829 | 1.86% / 1.42% | 1,043㎡ / 1,373㎡ | 1,184㎡ / 2,579㎡ | | **1.0m(채택)** | **1,749 / 1,987** | **1.01% / 0.67%** | **243㎡ / 373㎡** | **369㎡ / 668㎡** | | 0.5m | 10,215 / 12,294 | 0.00% / 0.00% | 148㎡ / 129㎡ | 296㎡ / 259㎡ | | 0.0m | 12,403 / 14,558 | 0.00% | 0㎡ | 0㎡ | 0.5m 이하는 1m 계단 점을 못 지워 좌표점만 6배로 튐. 브라우저 실측도 같은 값 (API 좌표점 용화 1,750 · S자 1,987, 경계 없는 유역 0개), 확대해도 계단이 도드라지지 않음. 테스트 159 passed(B07 항목 아래 별도 기록한 1건 제외). - [X] **병합 뒤 깨진 로컬 테스트 복구**(2026-09-03): `tmp/tests/test_b07_masshaul_basin.py` 가 사라진 `MM_H` 를 import 해 수집 단계에서 전체 실행이 막혔음. 메인 창이 알려 준 대로 고침 — `auto_scale_h()` 로 축척을 얻고(시험 노선 40m → 1:500, `MM_H = 1000/500`), 표기 6곳을 현행으로(`종무대`·`Q= 600.00M3`·`L= 18.50M`·`EA= 400.00M3`· `M.N= 1+10.00`·`SCALE H=1:500 V=1:50,000`). 축척 규칙을 굳히는 시험도 추가 (40m→500 · 1,106m→2,000 · 3,465m→5,000 · 길이 0이면 설정 기본값 2,000). `tmp/tests` 는 git 밖이라 커밋 없음. 전체 **169 passed**. ### B03 계획노선 shapefile — 변환 CSV 정본화 (2026-09-03, 보조폴더) 사용자 확답: 프론트엔드는 **올린 파일 그대로 표시**, 백엔드는 **CSV 로 변환해 사용**하고 그 CSV 를 **초기값(`initial_snapshot/`) 정본**으로 남김. 좌표는 **사업지 좌표계 + 트림·조밀화 후**(2026-09-03 사용자 확정). 현행 조사 결과 — 이미 되어 있는 것(작업 불필요): 슬롯 라벨 `계획노선 도형`(CSV 표기 아님) · `.csv`/`.shp` 동시 수용 · shx/dbf/cpg/route_prj 슬롯 · shp 판독기(`B03_FileInput_Engine_Shapefile.py`) · shp 우선 선택 (`find_planned_route_file`) · 좌표계 변환·트림·조밀화 단일 창구 (`common_util_route_geometry.py:215 load_design_route`). - [X] 초기값 CSV 기록 — `common_util_initial_snapshot.py` 에 `planned_route.csv`(sequence,x,y) 를 `save_initial_snapshot()` 이 함께 씀. 값은 체인 2단계가 이미 만든 `points`(사업지 좌표계·트림·조밀화 후)를 그대로 받음 — 재계산·서피스 샘플러 재구축 없음. - [X] 체인 연결 — `B03_FileInput_Service_Chain.py` 스냅샷 블록이 `points` 를 넘김. - [X] 정본 사용 — `load_design_route()` 는 **surface_params 가 주어진 호출에 한해** 초기값 CSV 가 있으면 그것을 읽고 반환(재판독·재투영·트림 생략). `surface_params=None` 호출(도엽 범위 산출 `B04_PreProcess_Engine_SheetSurface.py:492`)은 트림 전 원본이 필요하므로 종전 경로 유지. - [X] 무효화 규약 확인 — 지표면·노선이 바뀌면 `discard_initial_snapshot()` 이 폴더째 지우므로 CSV 도 함께 사라져 자동으로 shp 재판독 경로로 되돌아감. - [X] **자체검증 완료** (2026-09-03, 보조폴더 8001·5174) ① 단위 4건 `tmp/tests/test_design_route_csv_master.py` — CSV 왕복 좌표 일치, 정본 우선, 트림 전 호출은 정본 미사용, 스냅샷 폐기 시 경로 닫힘. 전체 158 passed. ② 실사용 경로 — 공용 브라우저에서 프로젝트 신규 등록 → 용화 예정노선 shapefile 5개 + 지형 PRJ·TFW 업로드 → [LAS 없이 설계] 체크 → 업로드 → 자동 체인 완료. `initial_snapshot/planned_route.csv` 생성됨(643행). 정본 경로와 원본 shp 경로를 맞대 보니 **정점 643개 동일, 최대 좌표차 0.00007m, 연장 2136.2m 동일**. B05 화면에 종단 계획선·배수유역도(관 22개·세부유역 22개)가 정상 표시. ③ 프론트 표시 — 슬롯 카드가 올린 파일명을 그대로 보임(`대상지_일월.용화.2.2.shp` 외 4개), 라벨은 `계획노선 도형`. ### LAS 없는 설계 — 도엽 서피스가 노선 원본 좌표계로 만들어지던 문제 (2026-09-03, 보조폴더) **증상**: shapefile 노선(EPSG:5179) + 지형 PRJ(EPSG:5176)로 [LAS 없이 설계]를 돌리면 자동 체인이 `노선 트림: 노선 전체가 지표면 밖입니다` 로 중단됨 — 실측 재현. **원인**: `B04_PreProcess_Engine_SheetSurface.py:503 run_sheet_surface_analysis` 만 `read_planned_route()` 로 노선을 **원본 좌표 그대로** 읽어 도엽 서피스를 5179 자리에 만들고 (실측: 서피스 x 1,141,951~1,143,784), 설계 계통 `load_design_route()` 는 노선을 사업지 좌표계 5176(x 208,402~209,637)으로 옮기므로 두 자리가 어긋나 트림이 노선을 통째로 지움. 같은 파일의 `build_sheet_surface_from_route()` 는 이미 `load_design_route()` 를 쓰고 있어 LAS 경로에서는 나지 않던 문제. - [X] `run_sheet_surface_analysis()` 도 `load_design_route()` 로 읽도록 창구 통일, 좌표계는 `planned.crs_input or project_epsg_from_prj()` 를 그대로 사용 (EPSG 라벨 대신 PRJ 원문 WKT 가 실려 TOWGS84 보정이 보존됨). - [X] 도엽 확보(`download_geodata`)의 PRJ 탐색 폴더를 노선 세트(`input/shp/`)에서 지형 PRJ 폴더(`input/prj/`)로 교정 — 노선 PRJ 가 잡히던 자리. - [X] **검증**: 수정 후 같은 자료로 재실행 → 체인 완주(노선 643점·2136.2m, 관 22개, 세부유역 22개). 전체 테스트 158 passed. ### B03 [파일 업로드] 가 잠긴 채 남던 문제 (2026-09-03, 보조폴더) **증상**: 필수 파일을 다 골랐는데도 [파일 업로드] 가 비활성. 새로고침하거나 파일을 하나 더 고르면 풀림. **원인**(첫 추정 정정): 프로젝트 ID 캐시가 아니었음 — 파일 선택 경로가 `detectPausedUploads()` 에서 ID를 다시 읽어 스스로 풀림. 진짜 자리는 **[LAS 없이 설계] 토글**. 이 토글이 LAS 카드의 필수 여부를 바꾸는데(`isSlotRequired`), change 핸들러가 `updateUploadButton()` 을 부르지 않아 버튼 판정이 옛 상태로 남음. **파일을 먼저 고르고 토글을 나중에 켜는 순서**에서만 재현돼 처음엔 ID 문제로 보였음. - [X] `lasFreeCheck` change 핸들러에 `updateUploadButton()` 추가 (4줄). - [X] **검증**(공용 브라우저, 저장된 토글 표시를 지워 첫 진입 상태로 재현): ① 파일 7개만 고른 상태 → 비활성 + "필수 카드를 모두 채우세요 … LAS/LAZ …" ② [LAS 없이 설계] 켠 직후 → **즉시 활성**, 오류 문구 사라짐 ③ 다시 끄면 → 즉시 비활성 + 같은 안내. typecheck 통과. ### B04 세부 배수유역 — 종단 높낮이 흐름 반영 (2026-09-03, 보조폴더) 사용자 재정의(2026-09-03): "세밀유역" 표현은 접고, **배수유역 = 횡단배수 지점으로 들어오는 유역**. 빠진 것은 **종단 계획노선의 높낮이에 따른 흐름** — 용화에서 낮은 도로측 유역이 높은 쪽으로 흐르는 것처럼 표현됨. **진단(용화 실측, 프로젝트 `shp정본검증2`)**: `assign_road_cells_to_pipes` 의 내리막 추적이 한 칸 이웃만 보는 국소 하강이라 1m 간격 계획고의 미세 요철에 걸려 멈춤 — 측점 2,138개 중 **1,898개(88.8%)가 저점에 갇힘**. 갇힌 측점은 흐름이 아니라 **누가거리 최근접**으로 관에 붙어, 도로 셀 11,312개 중 **4,886개(43.2%)가 자기보다 높은 관에 배정**(최대 6.82m). 추적이 성사된 측점의 오르막은 0.8%(최대 0.09m)라 추적식 자체는 정상. - [X] 배정 규칙 교체 — **관 사이 마루(구간 최고점)가 분수령**. 1차원에서 물은 마루를 넘지 못하므로 마루 왼쪽은 앞 관, 오른쪽은 뒤 관, 첫 관 앞·마지막 관 뒤는 그 관이 받음. 미세 요철에 면역 (사용자 확정). - [X] 관 없는 저점 126개는 **그대로 둠** (사용자 확정) — 대부분 계획고 미세 요철이지 진짜 사그가 아니고, 마루 분할이 자동으로 무시함. - [X] **자체검증** ① 단위 5건 `tmp/tests/test_ditch_ridge_assignment.py` — 마루가 경계, ±3cm 톱니에도 배정 불변, 사그는 낮은 쪽으로, 첫·끝 구간, 관 순서 보존. 전체 163 passed. ② 실측 재계산: 오르막 배정 **43.2% → 17.6%**, 최대 상승 6.82m → 4.15m. ③ 잔여 1,993셀은 전부 **사그에 고였다가 더 낮은 장벽(=배정된 관) 쪽으로 넘치는** 경우임을 확인(어긋남 0개) — 물리적으로 옳은 배정이라 남겨 둠. ④ 화면: B05 배수유역도 [유역 다시 나누기] 후 유역 면적이 실제로 바뀜 (관1 1.35ha → 1.07ha, 관3 2,545㎡ → 3,661㎡, 관4 23.34ha 세월교 제안). ### B04 지표면 3D 뷰어 방위 콤파스 (2026-09-03, 보조폴더) 사용자 확인: 대상은 **B04 지표면 3D 뷰어** (B03 에는 3D 화면이 없음). - [X] `B04_PreProcess_UI_Compass.ts` 신설 — 카메라 오프셋으로 도북 각도를 내는 작은 모듈. 뷰어 좌표 규약이 `x, 높이, -y` 라 세계 북쪽은 `-z`, 화면 회전각은 `atan2(offset.x, offset.z)`. 뷰어 파일이 이미 946줄이라 별도 파일로 뺌(700줄 제한). - [X] `B04_PreProcess_UI_TerrainViewer.ts` 배선 3줄 — 뷰어 우하단(축척 막대 반대편)에 얹고 애니메이션 루프에서 각도 갱신(0.5° 미만 변화는 건너뜀). 지표면이 보일 때만 표시. - [X] CSS — `.b04-surface__compass*` (테마 토큰 사용, 라이트·다크 공용). - [X] **자체검증**: `npm run typecheck` 통과. 공용 브라우저에서 ① [상단] 시점 회전각 `matrix(1,0,0,1,0,0)` = 0° (바늘이 화면 위 = 북쪽, 북쪽 위인 2D 지도와 같은 방향) ② 캔버스를 220px 드래그하니 `matrix(-0.78152,-0.62388,…)` = 218.6° 로 카메라와 함께 회전 ③ 상단·하단 두 3D 뷰어 각각에 하나씩(총 2개) 표시. 검증 후 시점은 기본(사시도) 으로 되돌려 둠. ### 프로젝트 작업 좌표계 일원화 (2026-08-31 신규) - [X] **작업 좌표계 = 지형 PRJ 로 확정**(2026-09-03 사용자 결정 — 서피스 격자가 모델좌표의 주인이라 현행 유지). 「노선 .prj 우선」은 노선 UTM-K / 지형 동부원점 Bessel 로 실제 좌표계가 달라 서피스·격자를 통째로 재투영해야 해 폐기. - [X] **폴백 사다리를 창구 하나로 통합** — `common_util_crs.resolve_project_crs()` 신설. ① 노선 crs_input ② 파일 라벨(원본 좌표를 읽는 자리만) ③ 지형 PRJ ④ DB epsg ⑤ EPSG:5186. `_route_center_lonlat` 이 CSV 라벨을 그대로 변환에 쓰던 자리를 이 사다리로 교체. `tmp/tests/test_project_crs_resolution.py` 5건 신설. - [X] **트림 실패 진단** — 지표면 트림이 노선을 통째로 지우면 노선·지표면 bbox 를 함께 로그에 남긴다(겹침 0이면 좌표계, 일부 겹치면 측량 범위 문제). - [ ] ~~`planned.epsg or 5186` 폴백이 여섯 군데 흩어져 있다~~ (완료 — 위 항목): `B04_PreProcess_Engine_SheetSurface.py:467·496`, `B04_PreProcess_Router_Watershed.py:186·253`, `B04_PreProcess_Router_Inflow.py:84`, `common_util_drainage_context.py:98`. `B04_PreProcess_Engine_Extent.py:147-152`, `B04_PreProcess_Engine_GisVector.py:148`도 5186 기본값.