Files
Aislo/docs/raw/plans/2026-08-02_plan_B04_B05_drainage_unification.md
T
eomsangdonandClaude Opus 5 eb30b774f8 chore(docs): docs 폴더 git 추적 전환 · PLAN·OWNERS 최상위 이관
- `docs/` 를 .gitignore 에서 빼 git 추적 대상으로 전환 (위키·완료 이력·검증 기록)
- `docs/raw/PLAN.md` · `docs/raw/OWNERS.md` 를 저장소 최상위로 이동 후 .gitignore 에 등록
  — 창끼리 공유하되 저장소에는 안 올리는 장부
- 살아 있는 경로 참조 7개 파일 정정 (아카이브 107건은 그때 사실이라 그대로 둠)
- graphify 날짜별 산출물(`docs/wiki/graphify-out/20*/`) 제외 — `graphify update` 가 다시 만듦

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 18:19:07 +09:00

19 KiB

B04/B05 배수유역 일원화 + 종단 테이블 구조물 연동 (2026-08-01 착수)

목표: 배수유역 데이터를 B04 한 곳으로 모으고, B05는 그 데이터를 그대로 받아 같은 것을 그린다. 관 매설 지점은 종단 테이블의 구조물 라인으로도 다뤄진다.

A. 조사 결과 — 현재 데이터 흐름의 문제 (2026-08-01 확인)

항목 실제 상태 문제
해석 산출물 sync_from_b04()B04_wf1_Surface/drainage/ 7개 파일을 B05_wf2_Route/drainage/로 전량 복사 디스크 2배(7.2MB 중복), 갱신 시점이 B05 진입에 묶임
관 지점 정본 B04_.../drainage/edits/pipe_points.json B05는 이 파일을 전혀 참조하지 않음(grep 0건). B04에서 확정한 관이 B05에 안 보이고, B05 진입 시 자동 배치로 되돌아감
API B04 GET/PUT /drainage/pipe-points + POST /drainage/detail-basins, B05 POST /drainage/basins 같은 엔진인데 엔드포인트·응답 모양이 2벌
사본이 생긴 이유 "B05 편집이 B04 원본을 덮어쓴다" 외곽선 편집을 제거(e994330)해 B05는 이제 아무것도 저장하지 않음 → 사본 존재 이유 소멸
고아 데이터 B05_.../debug/(삭제된 Subdivide 엔진 검증 스크립트 4 + PNG 2 + JSON 1) 2026-08-01 삭제 완료
좌클릭 제한 B04는 event.button === 0 확인 후 마커를 잡음. B05는 버튼 검사 없이 pipeEditor.handleDown() 호출 B05에서 우클릭·가운데 클릭에도 마커가 잡힘
유역 목록 높이 .b05-drainage__basins { max-height: 34% } 비율 기반이라 유역 수와 무관하게 잘림
구조물 측점 IrregularStation{id, station, remainder, chainage_m, structure} — 클라이언트 전용, 경로 확정 시에만 백엔드 전달 관 매설 지점과 아무 연결 없음

B. 설계 결정 (사용자 확정)

  1. 저장소: B05 사본 폐지, B04_wf1_Surface/drainage/ 단일화. 두 화면이 같은 폴더·같은 edits/pipe_points.json을 본다.
  2. API: B05 전용 POST /drainage/basins 폐기. 두 화면 모두 GET /drainage/pipe-points · POST /drainage/detail-basins · PUT /drainage/pipe-points 사용.
  3. 종단 테이블 배관: 구조물 목록에 실제 항목으로 등록(이름 "배관"). 단 저장 정본은 pipe_points.json이고 목록의 배관 항목은 그 투영이다 — 관 위치 변경은 어느 쪽에서 시작하든 pipeEditor를 거쳐 한 경로로 전파한다.
  4. 고아 데이터: 1회성 수동 정리. debug/ 삭제 완료, B05 drainage/ 사본은 사본 폐지 구현 마지막에 삭제(먼저 지우면 sync_from_b04가 다시 만든다).
  5. 버튼 형태 유지: B05 배수유역 패널의 버튼 스타일(b05-drainage__analyze 등)은 그대로 두고 기능만 맞춘다.

C. 구현 체크리스트

C-1. 저장소·API 일원화 (선행)

  • B05_wf2_Route_Engine_Drainage_Store.py 삭제 — sync_from_b04()·b05_drainage_dir() 제거
  • B05_wf2_Route_Engine_Drainage_Basin.py 삭제 — 사본 경로 어댑터라 존재 이유가 사라짐
  • B05_wf2_Route_Router_Drainage.py 삭제 + main.py 라우터 등록 제거
  • config_system.pyDRAINAGE_B05_DIRNAME 제거
  • B04 Router_Basins가 B05 요청도 받도록 확인 — 확정 경로가 없어도 동작하므로 추가 조건 불필요(load_drainage_context가 route 없으면 CSV 기준으로 폴백)
  • 저장 시점 정리: B04는 "모델 확정", B05는 "경로 확정"에서 PUT /drainage/pipe-points 호출

C-2. B05 프론트 API 전환

  • B05_wf2_Route_Api_Fetch.tsfetchDrainageBasins/DrainageBasinResponse 제거 → B04 클라이언트(fetchDetailPipePoints·computeDetailBasins·saveDetailPipePoints) 재사용
  • _UI_Drainage_Panel.ts 응답 매핑 교체(pipe_points/basins/strength_profile/main_polygon_lonlat 등)
  • B04가 주는 값 중 B05에 없던 것 반영: 2차 전체 유역 외곽선은 detail.basin_lonlat, 상류 세류망·평균 흐름 화살표는 기존과 동일 키 확인

C-3. B05 UI 일원화 (버튼 형태 유지)

플롯 대상 — 도엽등고선(있음) · 평균 흐름(있음) · 흐름 강도(있음) · 세부유역(있음) · 관 제어(있음) · 유역 선택(목록만 있음)

  • 유역 폴리곤 클릭 선택 추가 — 현재는 목록 행 클릭만 가능. B04와 같은 pointInRing 판정으로 지도에서도 고르고, 유역 밖 클릭 시 해제
  • [집중유역] 토글 추가 (제안 반영) — 유입 집중점 마커. 관을 어디에 둘지 판단하는 근거라 사용자 화면에도 필요. 기본 꺼짐, B04와 같은 규칙
  • 종단 Z 출처·관 개수 요약 표기 (제안 반영) — 세부유역이 갈리는 근거라 화면에서 확인 가능해야 함
  • 관 마커 색을 B04와 같은 기준(기본/자동/수동)으로 통일할지 검토 — 현재 B05는 유역 파스텔색, B04는 생성 사유색. B05는 유역 대응이 중요하므로 현행 유지하고 마커 테두리로만 사유를 구분

C-4. 좌클릭 전용 이동 (B04·B05)

  • B05 pointerdown에서 event.button === 0일 때만 pipeEditor.handleDown() 호출
  • pipeEditor.handleDown() 자체에도 방어 조건은 두지 않고 호출부에서만 거른다(우클릭 메뉴가 같은 좌표 판정을 쓰기 때문)
  • B04는 이미 좌클릭 전용 — 회귀만 확인

C-5. 유역 목록 3행 스크롤

  • .b05-drainage__basinsmax-height: 34%를 행 높이 기준 calc(3 * 행높이 + 패딩)으로 교체. 행 높이는 CSS 변수로 한 곳에 정의

C-6. 종단 테이블 구조물 라인 연동

  • _UI_IrregularStations.ts — 외부에서 "배관" 항목을 통째로 갈아 끼우는 API 추가(setPipeStations(chainages)), 사용자 수동 항목과 구분되는 표식(origin: "pipe" | "user")
  • 관 목록 → 구조물 목록 투영: 유역 산정 결과가 오면 "배관" 항목을 그 누가거리로 재생성
  • 테이블 라인 드래그 이동_UI_Profile_Table.tsis-floating 컬럼에 포인터 드래그 부착. 배관·기타 구조물 모두 이동 가능
    • 배관을 옮기면 → pipeEditor 갱신 → 세부유역 즉시 재산정(지도와 같은 경로)
    • 기타 구조물은 목록 chainage만 갱신
  • 테이블 우클릭 메뉴ui_template_context_menu.ts 재사용. 라인 위: "배관 삭제"/"구조물 삭제", 빈 자리: "배관 추가"
  • 이름은 이번 범위에서 "배관" 고정. 드롭다운·수기 입력은 후속(현재 자유 텍스트 입력 폼은 그대로 둔다)

C-7. 정리·마감

  • B05_wf2_Route/drainage/ 사본 폴더 삭제(C-1 완료 후, 재생성되지 않는 것 확인하고)
  • _Engine_Drainage_Basin.py 주석에 남은 boundary_overrides.json 언급 제거(파일 삭제로 자연 해소)
  • ruff format / prettier / ruff check / tsc --noEmit / vite build
  • 700줄 규칙 확인 — 위험 파일: _UI_Profile_Table.ts(563줄, 드래그·메뉴 추가 예정), _UI_Profile_Panel.ts(690줄, 배선 추가 예정). 초과 시 즉시 분리
  • 서버 기동 검증 후 커밋 → .claude/git-autopush.ps1

C-8. 구현 중 추가된 분리 (700줄 규칙)

작업 중 세 파일이 한계를 넘어 분리했다.

파일 전 → 후 분리 대상
_UI_Drainage_Panel.ts 787 → 690 _UI_Drainage_Render.ts(캔버스 렌더러), _UI_Drainage_Parts.ts(토글 줄)
_UI_Profile_Panel.ts 729 → 669 _UI_Profile_Data.ts(종단 자료 어댑터)
_UI_Page.ts 702 → 683 세션 캐시 헬퍼를 _Api_Fetch.ts로 이동
_UI_Profile_Table.ts 563 → 577 구조물 라인은 _UI_Profile_Structures.ts 신설로 흡수

D. 검증 기준

  1. B04에서 관을 옮기고 확정 → B05 진입 시 같은 위치에 같은 개수의 관과 같은 세부유역이 보인다.
  2. B05에서 관을 옮기면 지도·유역 목록·종단 테이블 배관 라인이 동시에 움직인다.
  3. 종단 테이블에서 배관 라인을 끌면 지도의 관 마커와 세부유역이 따라 바뀐다.
  4. storage/.../B05_wf2_Route/drainage/가 다시 생기지 않는다.
  5. B05 지도에서 우클릭·가운데 클릭으로는 관이 잡히지 않는다.
  6. 유역이 4개 이상이면 목록에 스크롤이 생기고 3행까지만 보인다.

D-1. 실행 검증 결과 (2026-08-01, 서버 기동 상태)

확인 항목 결과
통합 엔드포인트 GET/PUT /drainage/pipe-points · POST /drainage/detail-basins · GET /drainage/primary-region · GET /drainage/road-inflow
POST /drainage/basins(B05 전용) 404 — 폐기 확인
통합 응답 필드 pipe_points 4 · basins 4 · inflow_hotspots 10 · flow_arrows 377 · upstream_lonlat 16 · main_polygon_lonlat 122점 · strength_profile · z_source누락 없음
B05 사본 재생성 삭제 후 API 호출해도 다시 생기지 않음
품질 게이트 ruff check 통과 · tsc --noEmit 통과 · vite build 성공
700줄 규칙 배수유역·종단 관련 파일 전부 통과(최대 _UI_Drainage_Panel.ts 690줄)

화면 조작(테이블 라인 드래그·우클릭, 유역 클릭 선택, 3행 스크롤)은 브라우저에서 사용자 확인이 필요하다.

F. 사용자 확인 후 후속 조치 (2026-08-01, 즉시 수정)

화면을 보고 내려온 지시 3건. 구조물 조작의 자리를 테이블에서 그래프로 옮겼다.

  • 테이블 세로선 제거 — 구조물 위치는 그래프 측점선이 이미 보여 주므로 테이블에까지 얹으면 같은 것을 두 번 그리는 셈. _UI_Profile_Structures.ts의 라인 레이어와 관련 CSS를 걷어내고 우클릭 메뉴만 남겼다(mountStructureLinesmountStructureMenu).
  • 드래그 위치 이동 — 종단 그래프의 구조물 측점선을 끌어 옮긴다. createLongitudinalProfile()onDragStation 인자를 추가하고, kind === "irregular" 측점선에만 끌기를 붙였다(attachStationDrag, 좌클릭 전용·3px 이하는 고르기). 화면 x → 누가거리 역변환은 inverseOf().
  • 좌측 사이드 패널 편집 전파 — 구조물 폼에서 "배관" 항목을 고치거나 지우면 그래프·배수유역도가 함께 갱신된다. DrainagePanel.setPipeChainages() 신설, Page의 applyIrregularStations()에서 origin === "pipe" 항목만 뽑아 넘긴다.
    • 되먹임 고리 차단: reconcilePipes()가 현재 목록과 같으면 null을 돌려 아무 일도 하지 않는다(관 목록 → 구조물 목록 → 관 목록으로 돌아오는 순환이 여기서 끊긴다). 생성 사유(기본/자동/수동)는 자리로 맞춰 이어 붙인다.
  • 손으로 넣은 "배관"도 관 지점 정본을 따르게 — 사이드바 폼으로 이름을 "배관"이라 적어 넣은 항목은 origin: "user"라 [초기화]로 지워지지 않고 배수유역도와도 어긋난 채 남았다. isPipeStation()(origin이 pipe이거나 이름이 "배관")으로 판정을 통일해 목록 교체·이동·삭제·선택 동기화가 모두 같은 규칙을 쓴다.
  • 첫 진입 시 배수유역도가 비어 보이던 문제 — 대시보드에서 곧장 들어오면 패널이 배치되기 전(0×0)에 fitToRoute()가 돌아 엉뚱한 배율이 굳었다(새로고침하면 보이던 이유). 크기가 2px 미만이면 맞춤을 미뤘다가 첫 배치 때 다시 맞춘다(fitPending).
  • 관을 옮겨도 옛 자리 세로선이 남던 문제(진짜 원인) — 구조물 측점이 두 곳에 있었다. ① 화면 정본인 사이드바 목록(irregularStations), ② 경로 확정 때 종단 정본 파일에 병합된 kind: "irregular" 측점(_Router_Confirm._merge_irregular_into_longitudinal). 그래프(normalizedLongitudinal(...).stations)와 3D(renderStationLines)가 둘을 겹쳐 그려서, 관을 옮기면 ①만 따라가고 ②는 확정 당시 자리에 그대로 남았다. 앞선 isPipeStation() 수정이 ①만 다뤄 증상이 남아 있었다.
    • 확인: storage/1/3/{project}/B06_wf3_ProfileCross/longitudinal/longitudinal.json배관 94.97 / 149.73 / 200.92 / 275.71이 저장돼 있었고, 현재 자동 배치는 84.3 / 149.73 / 200.92 / 275.71 — 화면에 남던 유령이 94.97이다.
    • 수정: 그래프·3D 모두 종단 정본의 kind === "irregular"를 걷어내고 사이드바 목록만 얹는다(_UI_Profile_Panel.draw(), _UI_Page.renderStationLines()). 저장분은 진입 시 restoreSections()가 목록으로 되살리고, 관 항목은 배수유역도가 setPipeStations()로 정본(pipe_points.json)에 맞춰 갈아 끼운다. 두 저장소는 [모델 확정] 시점에 다시 합류한다(confirm()savePipes()confirmRoute(irregular_stations) 순으로 보낸다).
  • 2D 화면 휠 방향 반전(전 페이지 통일) — 휠을 당기면 확대, 밀면 축소. B04 2D 지도(_UI_MapViewer.ts)·B05 배수유역도(_UI_Drainage_Panel.ts)·B06 횡단도(_UI_Cross_View.ts)는 deltaY < 0deltaY > 0으로, B07 CAD는 input-controller.tszoomScreen(evt.deltaY)zoomScreen(-evt.deltaY)로 뒤집었다(openwebcad 계산식은 손대지 않는다 — 부호만 넘긴다). 커서 고정 보정은 모두 그대로. 3D 뷰어(_UI_Camera.ts)는 지시 밖이라 손대지 않았다.
    • B07은 별도 번들이라 npm run build:b07-cad 재실행이 필요하다(수행 완료).
  • 측점 선택 하이라이트가 표를 가리던 문제 — 하이라이트 열이 덮는 자리의 규칙 측점 값 셀을 hideCoveredCells()로 통째로 숨겨(visibility: hidden), 비정규 측점을 고를 때마다 좌우 최대 2개 열의 값이 사라졌다. 표는 그대로 두고 선택된 값만 위에 얹도록 그 함수와 .is-covered CSS를 걷어냈다(2026-08-02 사용자 지시).
  • 하이라이트 열의 계획고 추가 강조 삭제 — 계획고만 자주색 테두리·글자색으로 따로 강조돼 같은 열의 다른 값과 달라 보였다. 평상시에는 다른 셀과 같은 모습이고, 고칠 수 있다는 표시는 hover·focus에서만 나타나게 했다(.b05-profile-table__irregular-input). 직접 입력 기능은 그대로.
  • [초기화]가 저장분을 남기던 문제 — 화면만 자동 배치로 되돌리고 edits/pipe_points.json은 그대로 두어, 다시 들어오면 옛 관이 살아났다(사용자 보고). DELETE /{project_id}/drainage/pipe-points 신설 — 저장분과 파생물(04_detailed_basins.geojson)까지 지우고 자동 배치 결과를 돌려준다. B05 [초기화] 버튼이 이 경로를 쓴다.
    • 확인: 초기화 전 {user:1, stream:1, spacing:2} · 초기화 후 {stream:1, spacing:3} · 두 파일 삭제됨 · 재조회도 같은 자동 배치.
  • B05 유역 영역 클릭 선택 복구(회귀 수정)pointerdown에서 basinClickStart를 두는 코드가 실제로는 들어가지 않아, 클릭 후보가 항상 null이 되어 pickBasinAt()이 한 번도 불리지 않았다. 같은 편집에 묶여 있던 B05 좌클릭 전용 제한(C-4) 도 함께 누락돼 있었다(포맷 변경 뒤 문자열이 어긋나 치환이 적용되지 않은 것). 둘 다 넣고 적용 여부를 grep으로 확인했다.
  • B04 집중유역 오버레이를 최상단으로FlowStrengthOverlay.draw()를 강도 색칠만 남기고, 집중점 마커·유입 외곽선은 새 drawTop()으로 떼어 세부유역·관 마커 뒤에 그린다. 켰을 때 무엇에도 가리지 않아야 고를 수 있다.
  • 패널 크기를 바꿔도 지도 배율 고정 — 하단 패널·우측 배수유역 패널을 늘리면 computeMapRect()가 지도를 뷰포트에 다시 맞춰(contain) 함께 확대돼, 방금 보던 자리를 다시 찾아야 했다. 화면 변환 계수(ax·bx)가 그대로가 되도록 배율·오프셋을 되계산해 늘린 만큼 더 넓은 범위를 보여 준다(preserveViewOnResize()·observeViewportSize() in _Drainage_Parts.ts).
  • 흐름 강도 색띠 범례(게이지 바) — 지도 우측에 세로로 세운다. B04 2D 지도·B05 배수유역도 공용(createFlowLegend() in _UI_FlowRamp.ts, ui_template_flow_legend.css). 색은 화면과 같은 색띠, 눈금은 같은 로그 정규화(expm1(t·log1p(max)))로 되돌려 적는다 — 다른 규칙으로 그리면 범례가 오독을 만든다. 제목은 "흐름강도".
  • 배관 누가거리 라벨 가독성 — 흰 테두리(--map-halo)를 깔고 그 위에 글자를 얹는다. --map-* 토큰은 다크 테마에서 바뀌지 않으므로 색만 바꿔서는 위성사진·다크 배경에서 묻힌다.
  • 선택 3자 동기화 — 배수유역 영역 ↔ 종단 그래프 세로선 ↔ 좌측 구조물 폼(+3D 마커)이 서로를 갱신한다. _UI_Selection.ts 신설로 네 곳의 선택 경로를 한 모듈에 모으고, isSyncing 가드로 재진입을 막는다. 배수유역 강조는 origin === "pipe" 항목에만 붙는다(다른 구조물에는 대응 유역이 없다).
    • DrainagePanel: 유역 강조를 바꾸는 자리를 selectBasin() 하나로 모으고, 밖에서 고를 통로 selectBasinByChainage()와 알림 onBasinSelected를 냈다.
  • 유역 서클 번호를 최상단 오버레이로 — 채움과 함께 그리던 것을 떼어내 맨 마지막에 얹는다. B04는 drawBasinNumbers() 호출을 관 마커 뒤로 옮겼고, B05는 drawFilledRing()에서 라벨을 빼고(drawRingBadge()·ringCenterOnScreen() 신설) 배관 마커 뒤에 그린다. 등고선·화살표·관 마커에 가리지 않는다.
  • 우클릭 메뉴도 그래프로 이동 — 배관 추가·삭제를 테이블에서 떼어 종단 그래프 칸(.b05-profile__chart)에 붙였다. 구조물 조작(이동·추가·삭제)이 전부 한 자리에 모인다. 측점선 근처면 "배관/구조물 삭제", 빈 자리면 "배관 추가"(선 대신 화면 거리로 판정). 테이블은 값만 보여 준다.
  • 700줄 규칙 유지를 위한 추가 분리: reconcilePipes()·fetchDrainageLayers()·fitViewToRoute()_Drainage_Parts.ts, 오버레이 도형(drawFilledRing·drawRingBadge·drawRidgeRing·drawUpstreamLines·ringCenterOnScreen) → B04_wf1_Surface_UI_MapOverlays.ts 신설, 선택 동기화 → _UI_Selection.ts 신설.

E. 미결 (이번 범위 밖)

  1. 구조물 이름 드롭다운/수기 입력 — 이번엔 "배관" 고정. 그 외 미결은 '향후 작업'에 있다.

J. 이번 세션 커밋 목록