Files
Aislo/docs/raw/plans/2026-09-07_plan_completed_items.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

102 KiB
Raw Blame History

완료 항목 이관 — 2026-09-07

무엇 — 상시계획서 docs/raw/PLAN.md 에서 체크가 전부 끝난 절 24개와 그 절들이 낳은 자체검증 기록 3건을 옮긴 것. 계획서가 1,900줄을 넘어 읽기 어려워져 갈랐음.

원문 그대로 — 절 본문은 한 글자도 고치지 않았음. 바꾼 것은 묶는 순서와 구획 이름뿐이고, 페이지·기능별로 다시 모은 뒤 「무엇이 끝났나」 표를 앞에 붙였음.

절 번호는 계획서 것을 그대로 씀 (0-4 · 1-1 · 3-5 …). 계획서에 남은 절들이 「0-3 참조」·「3-2 위에 얹음」처럼 이 번호로 서로를 가리키므로 다시 매기면 끊김.

이관 시점의 계획서 — 남은 항목 21건 (0-1 · 0-2 · 0-5 · 0-6 · 0-7 · 0-8 · 0-11 · 2-1 · 2-5b · 3-6 · 3-7).

앞선 아카이브 — 재구획 직전 원문 2026-09-06_plan_before_regroup.md, 그 전 완료분 2026-09-04_plan_snapshot_before_cleanup.md 및 같은 날짜 완료 아카이브 3개.


무엇이 끝났나 — 한눈에

무엇을 끝냈나 주요 커밋
0-3 노선 자동탐색 입구를 화면에서 내리고, 측점마다 평면 곡선반경 R 을 실어 법정표 위반을 표시만 하게 함
0-4 계산 자리 일원화 — 구조물 기하·폐회로 면적·유토곡선을 Node 진입점 한 벌로 묶어 서버가 초기값·[저장]·[확정]에서 같은 코드로 계산
0-10 계획노선 폴리라인화 — 노선을 세 벌(예상 · 초기 폴리라인 · 수정본)로 나누고, 노선 변경 뒤 재확정 체인이 끊기던 것과 값이 조용히 사라지던 것들을 고침 356b3216 9b93d305 00decc81 eec29a90 53525a1d 674ca827 908c75d5 41852da3 19444149
0-12 계획노선을 「직선 → 곡선 → 직선」으로 — 이어진 꺾임을 한 곡선으로 묶어 예정노선에 가장 가까운 반지름을 고르고, 화면에서 곡선을 지우고 넣고 R 을 못박게 함 db700496 e593378d 9626d9e8 f59932de 56a6d186 43d55f49 20f34390 a0bd9c87
1-1 B01 권한·회사·프로젝트 연결 정비 — 시스템 회사 자동 권한 · 회사 관리(중복조회·지도·정보수정) · 팀원 관리 · 프로젝트 참여자 · B02 등록 화면 · 대시보드 배치
1-2 B01·B02 화면 다듬기 + 시스템 로그 기록 보강(공용 기록 함수·대상 식별자·접속주소·보관 1년) 017_audit_log_detail.sql
2-2 3D 구조물 선택이 B06 횡단 카드로 넘어가는 길 — 코드 결함 없음. 안 되던 진짜 이유는 종단 오버레이가 클릭을 가로챈 것
2-3 종단 그래프 끌기 이동 — 종단 그래프·유토곡선·측점 테이블 세 곳이 함께 움직임
2-4 종단 테이블 12행 → 6행, 높이 상한 204px 신설 (B07 도면 종단도는 12행 유지)
2-5 유토곡선 초기 화면 정리(토량분배 숨김·전체 맞춤) + 최종 누가량을 종단 상단줄로
2-5c 유토곡선 자리 정리 — 총괄값 배지 신설, B06 유토곡선 그래프 제거
2-6 구조물 측점 누가거리 정본 일원화 + B06 종단 구조물 표시를 B05 와 같은 부품으로 통일 67972612
2-7 초기값(초기 스냅샷) 실경로 검증 + initial_design.lock 흐름 확인
3-1 곡선부 노폭 자동 확폭 — 법정 R 표 + 양끝 10m 테이퍼, 유효너비 5m 상한
3-2 구조물 절·성토 면적 반영 + C군 벽(옹벽·돌쌓기·흙막이·큰돌쌓기)을 횡단 제원으로 얹음. 구조물 저장이 통째로 유실되던 버그도 고침 6b76a3f2
3-4 기슭막이 연동을 풀면 그 순간 높이로 굳게 함(다단 포함) 71694163
3-5 다단 벽이 원지반에서 뜨던 것 + 다단·벽 바깥 성토가 물량에 안 잡히던 것 고침 (파급은 사용자가 「그대로 둠」으로 확정) e9ad8cdf
4-1 도각 외부 파일 불러오기(DWG→DXF→도각 JSON) + 자리표 배치 도구
4-2 도면 DXF·DWG 내보내기 + LibreDWG 동봉(GPL 실행파일 호출)
4-3 자리표 다듬기 — 칸(box) · 그림 비율 유지 · 편집 중 실제 값 미리보기
4-4 자리표 후속 — 마우스로 칸 크기 조절 · 기본 도각 칸 정렬 · 도면명 미리보기
5-1 라이다 다중 파일 입력·병합 + 씨닝(지면점은 전부 보존) + 먼 파일 차단
5-2 700줄 분리 뒤 B04 배수유역 결과가 분리 전과 같은지 확인 — 같음 07e6441d
5-3 「LAS 없이 설계」에서 PRJ·TFW 를 필수로 보던 오류

1. 공통 기반 — 노선 데이터와 계산 자리

노선이 무엇인지(데이터 모양)와 계산이 어디서 도는지를 정한 절들. 뒤의 모든 페이지가 이 위에 섬.

0-3. 노선 자동탐색 사용 중단 + 평면 법정 검사 (2026-09-06 사용자 확정)

지금 [최적 경로 계산]은 평면만 보고 노선을 정함. 실제 판단은 평면·종단·횡단·유토곡선을 함께 봐야 하므로 현재 방식은 불필요. 0-2 계획노선 편집이 그 자리를 대신함. 여기서 만드는 측점별 평면 R 계산은 3-1 확폭이 그대로 씀.

  • 좌측 「경로 계산 설정」의 [최적 경로 계산] 입구를 내림(코드는 남기되 화면에서 뺌).
  • 측점마다 평면 곡선반경 R을 구해 횡단 자료에 실음 — 횡단 생성이 이미 노선 폴리라인을 읽으므로 그 자리에서 계산(기존 외접원 반경 함수 재사용).
  • 평면 최소곡선반경 법정표(설계속도·지형별 40→60/40 · 30→30/20 · 20→15/12)는 탐색 제약이 아니라 위반 표시 쪽으로 씀 — 원청 노선·계획노선 모두에 적용.
  • 배향곡선(중심선 R 10m 미만)은 경고만 표시 — 자동 보정·차단 없음(2026-09-06 사용자 확정, 처리 방식은 뒤에 판단).
  • 내각 155° 이상 곡선 생략 — 0-10 에서 넣음(별표2 Ⅰ.2.다, 폴리라인화 규칙의 일부). 이 줄의 옛 「보류」는 코드와 어긋나 여기서 닫음.

0-4. 계산 자리 일원화 — 브라우저에서만 도는 계산 없애기 (2026-09-06 사용자 지시)

문제 — 사용자가 브라우저를 닫으면 계산이 안 돎. 지금 일부 값은 브라우저가 만들어 서버에 저장만 하므로, 그 화면을 한 번도 안 연 프로젝트에는 값이 없다. 초기 결과가 저장되려면 백엔드에서도 같은 계산이 돌아야 함.

현황 조사 (2026-09-06) — 계산이 어디서 도는지 네 갈래로 갈림.

갈래 무엇 지금 상태
ⓐ 파이썬·TS 두 벌 짝 횡단 설계선·단면적 B06_Section_Engine_Design.pycommon_util_cross_design.ts, _Engine_Areas.py_design_areas.ts. 값이 갈리지 않게 주석·테스트로 묶여 있음
브라우저 TS 를 서버 Node 로 그대로 실행 3D 코리도 B05_Profile_Corridor_Node.ts(SSR 번들) 를 B05_Profile_Corridor_Prebuild.py 가 전처리 체인 끝에서 호출해 영구저장. 계산을 다시 짜지 않는 유일한 선례
ⓒ 브라우저 전담 + 서버는 스냅샷 보관 유토곡선 프론트가 계산해 mass_haul 로 보내면 서버는 검산 없이 저장해소됨: 저장 때 서버가 Node 로 정본을 냄. 브라우저는 balloon 위치만 보냄(0-8 ⓕ)
ⓓ 브라우저 전용(서버에 없음) 구조물 기하 — 기슭막이·집수정·다단, 세월교, BOX암거 / 그 위에서 낸 구조물 폐회로 면적(3-2) 서버에는 옵션값만 있고 형상 계산이 없음. B07 도면에도 아직 횡단면도 엔진이 없어 드러나지 않았을 뿐

방안 — ⓑ 방식으로 모은다 (계산은 한 벌, 실행 자리만 둘)

파이썬으로 포팅하면 같은 기하가 두 벌이 되어 화면과 서버가 갈린다(코리도 때 이미 내린 결론). 대신 브라우저가 쓰는 TS 를 Node 진입점으로 감싸 서버가 호출한다.

  • 구조물 기하 + 폐회로 면적을 Node 진입점 하나로 묶음 — B06_Section_Server_Calc_Node.ts(입력 JSON → 출력 JSON, 코리도와 같은 꼴). 화면이 쓰던 「정본만 읽는 제어기 흉내」를 B06_Section_Structure_Layouts.ts 로 빼 B07 도면과 서버가 같은 한 벌을 씀.
  • 전처리 체인 끝(코리도 프리빌드 옆, 초기값 스냅샷 앞)에서 한 번 돌려 영구저장 — B03_FileInput_Service_Chain.py.
  • [저장]·[확정] 때도 같은 진입점으로 서버가 다시 계산 — B06_Section_Router_Confirm.py 커밋 뒤 호출. 사용자 patch 가 먼저 들어간 뒤라 바뀐 벽 위치가 면적에 실림.
  • 유토곡선(ⓒ)도 같은 진입점에 넣음 — 면적 보정을 얹은 뒤 쌓으므로 화면 순서와 같음. balloon 위치(사용자가 끌어 옮긴 화면값)는 서버가 만들지 않고 저장분에서 떼어 도로 붙임.
  • ⓐ 두 벌 짝은 그대로 둠 — 이미 짝이 맞고 테스트가 지킨다. 새로 두 벌을 만들지 않는 것이 이 항목의 핵심.
  • 자체검증(2026-09-06 용화 신규 프로젝트) — 브라우저를 한 번도 안 열고 구조물 면적 15곳·유토곡선이 저장됨(측점 67곳 전부 면적 있음). 화면 유토곡선 절토 5,116.3㎥·성토 15,397.4㎥ = 저장값과 일치.
  • [저장]·[확정] 은 서버가 정본을 계산함 (2026-09-06 저녁 사용자 최종 확정). 그날 낮에 「저장 때 Node 를 돌리지 말고 브라우저 계산으로」 잠깐 뒤집었다가 되돌린 것이니, 옛 커밋·주석에 그 흔적이 보이면 이 줄이 최신임. 이유는 속도가 아니라 보안 — 저장 경로가 브라우저 계산이면 유토 배분·운반거리 코드가 번들에 남아야 해서 화면에서 안 그려도 못 뺌. 서버로 되돌려 그 몫이 빠졌음. 대가로 저장 대기가 늘었고(4.1초까지 갔다가 왕복 묶기로 1.8~3.0초), 기다리는 조작이라 허용. 실측·자리는 0-8 참조(옛 0-9 는 0-8 로 합쳐졌음).

왜 포팅이 아니라 Node 실행인가 — 기하 계산은 화면 그리기와 같은 코드를 쓴다(트림·성토부선·벽 외곽). 파이썬으로 옮기면 화면이 바뀔 때마다 두 곳을 고쳐야 하고, 어긋나면 「그림은 이런데 수량은 저렇다」가 된다. 코리도에서 그 판단을 이미 했고 잘 돌고 있다.

0-10. 계획노선 폴리라인화 — 노선 데이터 모양 정정 (2026-09-06 사용자 확인)

전제가 틀렸음 — 「예상노선 = 폴리라인」이라 보고 0-2·0-7 을 짰는데, 사용자가 실물을 보고 예상노선은 점(포인트)으로 이뤄진 데이터이며 규칙 없는 폴리라인과도 맞지 않는다고 확인함. 그래서 계획노선은 원데이터를 복사한 뒤 폴리라인으로 바꾼 것이어야 하고, 그것이 불변의 초기 데이터가 됨. 유토곡선·3D 에 투영되는 선도, 노드를 잡아 고치는 대상도 이것임.

노선은 이제 세 벌 (옛 「두 벌」 서술은 0-2·0-7 에서 이 절을 참조할 것)

자리 무엇
예상노선 B05_Profile/route/expected_route.csv 자동 체인이 낸 점 묶음. 불변
계획노선 초기본 planned_route_initial.csv 위를 폴리라인화한 것. 불변 초기 데이터
계획노선 planned_route.csv 사용자가 노드를 잡아 고친 수정본

읽는 순서(load_design_route) — 수정본 → 초기 폴리라인 → 예상노선 → 초기값 스냅샷. [초기화] = 수정본을 지워 초기 폴리라인으로 돌아가는 것.

곡선 기준은 지식DB (01_임도/02_상세설계/평면선형.md, 별표2 Ⅰ.2.다 — 코드에서는 config_system_design.FOREST_ROAD_PROFILE_CRITERIA["min_plan_radius_m"])

  • 최소곡선반지름 — 설계속도 40: 일반 60 / 특수 40 · 30: 30 / 20 · 20: 15 / 12 (중심선)

  • 배향곡선 중심선 R 10m 이상

  • 내각 155° 이상(교각 25° 이하)이면 곡선 생략 가능

  • 폴리라인화 모듈 common_util/common_util_route_polyline.py (커밋 356b3216)

    • 꺾임점(IP)만 남김 — Douglas-Peucker, 허용오차는 격자 해상도(2m)의 두 배. ⚠ 이 단계가 핵심임. 예상노선은 격자 탐색이 낸 조밀한 점군이라(용화: 1,097m 에 331점 = 약 3.3m 간격) 점마다 곡선을 끼우면 접선 자리가 1.5m 뿐이라 R 이 2~6m 로 뭉개짐. 처음에 이 단계를 빼고 만들었다가 곡선 15곳이 전부 위반으로 나와 드러났음.
    • ② 내각 155° 이상은 생략, 그 아래는 최소곡선반지름으로 원호를 끼움. 자리가 모자라면 반지름을 줄이되 막지 않고 위반으로 표시(사용자 확정: 자동 보정·차단 없이 경고만).
    • 점을 옮기지 않음 — 노드는 원본 자리에 그대로 두고 그 사이에 원호를 넣음.
    • GET /route/plan 이 노드(반지름·내각·위반)까지 함께 돌려줌. POST /route/replan 도 받은 노드로 다시 폴리라인화 — 노드만 옮기면 선이 저절로 규칙을 지키게 하는 것이 이 구조의 목적임.
    • 자체검증(용화, 실화면 API) — 예상노선 331점 → 편집 노드 25개 + 곡선 13곳 (전부 R 12m = 설계속도 20·특수지형 법정 하한, 위반 0) → 계획노선 정점 142개. 응답 180ms. 시험 tmp/tests/test_route_polyline.py 7건(원호가 접선과 맞물리는지 좌표 대조).
  • 모달이 노드를 그리고 잡게 (커밋 9b93d305) — 그려 보이는 선(폴리라인, 원호 포함)과 잡는 점(노드) 을 나눔. 노드에 붙은 반지름·내각·위반을 실었고 위반 노드는 붉게, 상태줄에 곡선 수·기준 R·미달 개수. 노드를 옮기면 폴리라인은 낡은 값이라 지우고 직선으로 미리 보임 — 곡선은 [확인] 때 서버가 같은 규칙으로 다시 끼움(계산 한 벌). 자체검증 — 모달이 노드 28개로 열리고 「노드 28개 · 초기 폴리라인 · 곡선 13곳(R 12m)」. 전에는 폴리라인 정점 142개를 잡았음.

  • 완만한 긴 굴곡이 삼켜지던 것 (커밋 00decc81) — 노드 사이가 100m 를 넘으면 그 구간만 허용오차를 절반으로 낮춰 다시 뽑음(최대 3겹). shapely 대신 색인을 돌려주는 Douglas-Peucker 를 직접 둠(되뽑기가 원본 구간을 꺼내야 함). 새 점은 안 만듦.

    • 자체검증(보조 창, 되뽑기 전후 대조) — 1.1km 둘 노드 25→28 · 최대 간격 82m(상한 안) · 위반 0. 4.5km 노드 20→24, 최대 간격 1,300m 는 실제 직선(그 구간 내각 최소 158°, 원본 점 간격 중앙 3.3m·최대 3m 로 고름).
    • 되뽑기가 곡선 유무를 바꿀 수 있음 — 4.5km 노선에서 노드 자리가 옮겨지며 정확히 155° 였던 두 곳이 155° 위로 올라가 곡선 2 → 0 이 됐음. 별표2 「155° 이상 생략 가능」에 맞으므로 문제는 아니나 알아 둘 것.
  • 🔴 노드를 옮기고 [확인] 하면 재계산이 중간에 끊김 (2026-09-06 보조 창 실측) → 아래 네 갈래 전부 닫힘. 남은 것은 시간뿐 — 끝까지 도는 값이 208~221초라 「0-11. 노선 [확인] 이 3분 반」 으로 옮겨 따로 다룸.

    • 증상 — route/replansuccess 를 돌려주고 화면도 성공으로 보이는데, 체인이 B05 재계산에서 400 으로 중단돼 그 뒤(종횡단 재생성·확정)가 통째로 안 돎. 재계산이 2.1초에 끝난 것으로 먼저 드러남(코리도 사전 생성 하나만 4.3초).
    • 결과가 어긋난 채 굳음route/plan 1,079.4m · pipe-points route_length 1,079.44 (둘 다 새 노선)인데 sections/detail 은 측점 67·끝 1,097.4m(옛 노선). 관 누가거리가 새 노선 기준이라 어느 측점과도 안 맞아 배수관 측점이 9 → 0 이 됐음.
    • 자리 — B03_FileInput_Service_Chain.pysolve_route(project_id, request)JSONResponse(400) 를 돌려주고 그 아래에서 중단 로그만 찍고 끝남.
    • 사유가 안 남던 것부터 고침(커밋 eec29a90) — 상태 코드만 남겨 무엇이 막았는지 못 짚었음. 이제 본문(message)까지 로그에 남음. 보조 창이 재현해 사유를 확인 중.
    • 사유 확정(보조 창 재현) — 「세그먼트 1 (BP → CP1) 경로 탐색 실패: 종단경사 한계(26%)·최소곡선반지름(12m)·회피지역 제약으로 통과 경로가 없습니다」. 자동탐색이 사용자 노선을 다시 푸는 중 막힌 것.
    • 곁가지 둘 먼저 고침(커밋 53525a1d) — ① 실패해도 계획노선 CSV 는 이미 바뀐 뒤라 누를 때마다 어긋남이 쌓였음(1,097.4 → 1,079.4 → 1,064.7m). 쓰기 전에 이전 수정본을 쥐고 있다가 실패하면 되돌림. ② route/replansuccess 를 돌려줘 화면이 성공으로 보였음 — 409 + 사유로 응답. run_redesign_chain 이 중단 사유를 돌려주게 함. 재현 확인(보조 창) — 같은 조작에 409 + 원래 사유, 노선은 1,064.7m 그대로.
    • 근본 원인 고침(커밋 674ca827) — B05_Profile_Engine_AsPlanned.solve_as_planned 신설. 제어점 목록이 곧 노선이고 표고만 지표면 격자에서 뜸. 반환 꼴을 솔버와 맞춰 아래 단계가 그대로 이어짐. 위반은 세되 막지 않음. run_route_designalgorithm="as_planned" 갈래를 내고 재확정 체인이 그것을 씀 — 자동탐색은 이제 초기 업로드에서만 돎(0-3 방향과 맞음). 솔버와 지표 계산이 겹치지만 합치지 않음(솔버는 0-3 으로 접히는 코드).
    • 실화면 재검증(보조 창) — ⓐ 노선 길이 바뀜 측점 67·1,097.4m 고정 → 64·1,079.4m(노선과 일치) 배수관 측점 0 → 7~9 . 「세그먼트 경로 탐색 실패」 로그가 사라졌고 재계산이 2.2초(실패) → 208~221초(끝까지 도는 값)가 됐음.
  • 노선 변경 때 옛 사용자 설계가 덮여 사라지던 것 (커밋 908c75d5) — 다단 구간값 (extra_spans)이 재계산 뒤 0건이 됐음. 이월은 돌고 있었는데 곧바로 덮였음_prepare_drainage_pipes_and_reprofile(refresh=True) 가 측점을 새 노선 기준으로 다시 만들며 방금 얹은 설계를 갈아 끼웠고, 이월이 그 에 있었음. 이월을 측점 재생성 로 옮기고 merge_cross_section_designs 로 묶음. 보조 창이 재검증 중.

  • 최소곡선반지름 조회가 없는 컬럼을 읽던 것 (커밋 eec29a90) — projects 에는 road_type 뿐이고 design_speed_kph·terrain_type 은 없음. 매 요청마다 OperationalError(1054) 가 ERROR 로 남고 기본값 폴백으로 넘어갔음(값은 우연히 맞았음). 임도 종류는 projects.road_type, 설계속도·지형은 워크플로 stage 2 params 에서 읽게 함. 산식은 이미 있던 B05_Profile_Engine_Grade.legal_plan_radius_min_m 을 씀 — 내가 같은 표를 다시 짜 뒀던 것을 지움.

  • 초기 업로드부터 노선 정본이 폴리라인이 되게 (커밋 41852da3) — 폴리라인 파일만 만들고 정작 노선은 원본 점군으로 풀고 있었음. 그래서 첫 업로드 뒤 종횡단 측점· 유토곡선·3D 코리도가 곡선 없는 점군 위에 섰고, 노선을 한 번이라도 편집한 프로젝트만 우연히 폴리라인 기준이었음. 실측(용화 3건) — DB 노선 정점 331·323 vs 초기 폴리라인 145. 편집한 하나만 145. 자동 체인에 2.7 단계를 넣어 초기 폴리라인을 세운 뒤 노선을 다시 읽어 그것을 BP·경유점·EP 로 삼고 algorithm="as_planned" 로 넘김(재확정 체인과 같은 길). 자체검증 ① 2.7 재판독이 145 정점을 돌려주고 BP·EP 좌표가 expected_route.csv 첫·끝 줄과 완전 일치. ② 진짜 solve_route 를 그 입력으로 돌려 노선 연장 1,079.5m · 저장 정점 145 를 얻음 — 사용자가 노드를 고쳐 만든 b269ea34 의 1,079.4m·145 와 같은 값이 다른 경로로 나옴. ③ pytest 409 passed. ⚠ 이미 만든 프로젝트는 그대로임 — 새로 업로드해야 폴리라인 기준이 됨. ⚠ 검증 함정route_main.geojson프로젝트당 한 벌이라 solve_route 를 한 번만 불러도 살아 있는 노선의 파일을 덮음(노선 행은 늘어도 파일은 하나). 위 ②에서 실제로 덮였고, 예상노선 331점을 solve_as_planned 로 다시 떠 331 정점·1,097.4m(노선 행 값과 일치)로 되돌렸음. 살아 있는 프로젝트에 검증용 solve 를 돌리지 말 것.

  • 🔴 [확인]을 누를 때마다 계획노선이 깎이던 것 (커밋 19444149, 2026-09-07) — 보조 창이 재측정 중 발견. 같은 노선으로 [확인]을 되풀이하면 정점이 136 → 134 → 119, 노드가 22 → 21 로 계속 줄었음. 사용자가 아무것도 안 옮겨도 누를 때마다 자기 노선이 뭉개졌음.

    • 원인 — 이미 폴리라인인 것을 다시 단순화. 두 자리 모두 그랬음: ① read_route_plan 이 노드를 계획노선(원호 점이 섞인 폴리라인)에서 되뽑음 ② replan_route 가 화면이 보낸 노드를 또 한 번 단순화해서 씀. 단순화(Douglas-Peucker)는 원본 점군에서 한 번만 돌아야 함.
    • 고친 것 — build_planned_polylinesimplify 갈래 신설(False 면 받은 점을 꺾임점으로 그대로 씀) · 폴리라인을 쓸 때 그것을 낳은 노드도 함께 저장 (planned_route_nodes.csv · planned_route_initial_nodes.csv) · read_route_plan 은 저장된 노드를 그대로 씀(없는 옛 프로젝트만 뽑음) · replan_routesimplify=False.
    • 실측(용화 b269ea34 실제 노선 파일, 4회 왕복) — 옛 방식 노드21/정점115 → 20/115 → 20/113 → 20/115(깎이고 흔들림), 고친 방식 노드21/정점115매 회차 완전히 동일. 시험 tmp/tests/test_route_polyline_idempotent.py 2건. pytest 414 passed.
    • ⚠ 이미 고쳐 둔 프로젝트는 첫 조회에서 한 번만 자리를 잡음(119 → 115) — 노드 파일이 없어 폴리라인에서 한 번 뽑기 때문임. 그 뒤로는 안 움직임.

0-12. 계획노선을 「직선 → 곡선 → 직선」으로 (2026-09-07 사용자 확정)

사용자 확정 원문 — 「계획노선은 직선>곡선>직선 형태의 폴리라인임. 여기서 반지름은 법정 최소 값을 지키되 기존 예정노선에 가까운 폴리라인을 찾는 게 키임. 그 후 사용자가 직선과 곡선의 교차점을 이동하고 필요한 경우 곡선 값을 변경. 단 곡선 반지름값 변경 시 주변 직선 각도 변경 구속. 직선과 곡선 교차점 이동 시에는 직선의 각도와 반지름 값 변경이 되.」

되물어 확정한 것 둘 (2026-09-07)

  • 끄는 점 = 곡선 시작·끝점(직선이 곡선에 닿는 자리, BC·EC). 꺾임점(교각점)이 아님.
  • 반지름은 법정 최소 이상에서 자유 — 「원본에 가장 가까운 값」으로 고름. 상한은 안 둠.

지금과 무엇이 다른가

지금 바꿀 것
반지름 모든 곡선이 법정 하한 고정(R 12m) 구간마다 예정노선과 가장 덜 벌어지는 R(≥ 하한)
잡는 점 꺾임점(노드) 곡선 시작·끝점
꺾임점을 옮기면 양쪽 각도·R 다시 계산 (그대로 두되 손잡이는 곡선 끝점)
R 을 바꾸면 없음 주변 직선 각도 고정 — 접선점만 미끄러짐
  • ① 반지름 피팅 (커밋 db700496) — 꺾임점 하나마다 원호를 끼우는 방식으로는 안 되는 것이 실측으로 드러남. R 을 키울수록 오히려 원본에서 멀어졌음(평균 1.56 → 1.89m). 한 곡선은 한 원호여야 하므로 이어진 꺾임을 묶는 것이 먼저였음.

    • 고친 것 셋 — ⓐ 같은 쪽으로 도는 꺾임을 한 곡선으로 묶고 앞뒤 직선을 늘려 만나는 자리를 교각점으로 씀(_curve_runs) ⓑ 그 안에서 법정 하한 이상·예정노선과 평균 벗어남이 가장 작은 R 을 고름(_fit_radius_m) ⓒ 묶는 것이 손해면 도로 쪼갬(_split_wide_runs — 평균이 나빠지거나 한 자리라도 8m 넘게 벌어지면).
    • 별표2 의 155° 를 「묶은 뒤 전체 교각」에 적용하도록 자리를 옮김. 조각마다 적용하면 완만한 곡선이 쪼개졌음(내각 156·138·154° 세 꺾임이 갈려 가운데 하나만 곡선). 쪼갤 때도 다시 검사함 — 안 하면 펴진 자리(내각 159°)에 곡선이 생겼음.
    • 실측(흉내낸 매끄러운 곡선) — 원본 R 25 → 25.1m · 40 → 39.8m · 80 → 78.2m 로 되찾음 (벗어남 0.090.44m). 전에는 전부 R 12m 로 뭉개져 1.32.7m 벌어졌음.
    • 실측(용화 실제 노선) — 중립. 노드 28 · 곡선 13 · 정점 145 · 위반 0 · 벗어남 최대 3.57m 평균 1.077 → 1.081m. 반지름만 12~28m 로 갈림. 격자 탐색이 낸 지그재그라 급한 꺾임이 대부분이고, 그런 자리는 하한이 이미 최선이라 그럼.
    • 시험 tmp/tests/test_route_polyline_curve_fit.py 4건.
  • ② 직선·곡선 성분을 정본에 남김 (커밋 e593378d) — RouteCurve 신설(교각점· 반지름·접선길이·내각·곡선 시작점·끝점·대신하는 꺾임점 구간·위반). planned_route_curves.json 으로 저장하고 GET /route/plancurves 로 내려보냄. 반지름을 바꿔도 교각점은 안 움직임 — 그것이 「주변 직선 각도 구속」의 뜻임.

  • 곡선 생략 규칙을 쓰지 않음 (커밋 9626d9e8, 2026-09-07 사용자 지시) — 「내각 155 이상이면 곡선 생략은 반영하지 말자. 사용자가 계획 평면 노선을 수정할 때 문제가 될 것 같아.」 곡선이 있다 없다 하면 잡는 손잡이도 있다 없다 하고, 조금 폈다는 이유로 곡선이 사라지면 되돌릴 길이 없음. 이제 꺾이는 자리에는 늘 곡선. 실측(용화) — 곡선 13 → 26곳, 정점 145 → 181, R 1228 → 12199m, 내각 109~177°. 벗어남은 그대로(최대 3.57m · 평균 1.081 → 1.102m). ⚠ 평면 R 을 읽는 자리(확폭 3-1, 구조물 2-6)는 곡선 개수가 늘어난 것을 감안할 것.

  • 곡선·직선 지우고 더하기 + 반지름 못박기 (커밋 f59932de, 사용자 지시 「r과 직선 삭제나 추가가 있어야 하지 않을까?」) — 서버 쪽 완료. 편집을 꺾임점 목록 + 자리마다 곡선 켬끔 + 반지름 셋으로 표현함: 직선 삭제·추가 = 점을 빼거나 더하기 · 곡선 삭제·추가 = curve 끄고 켜기 · 반지름 변경 = radius_m 못박기. POST /route/replan 이 정점마다 그 둘을 받음. 편집값이 오면 묶지 않음(사용자가 곡선 하나로 본 것을 임의로 합치면 손잡이가 사라짐). 시험 tmp/tests/test_route_polyline_edit.py 7건 — 그중 「반지름을 바꿔도 교각점은 안 움직인다」가 사용자가 말한 「주변 직선 각도 구속」 을 못박음.

  • ④ 화면에 곡선 제어 붙임 (커밋 56a6d186 + 43d55f49) — 곡선 시작·끝점을 네모로 표시(곡선 범위를 눈으로 봄) · 꺾임점을 누르면 고른 자리가 되고 아래 편집줄이 그 곡선을 만짐 · R 입력칸(값 넣으면 못박고 [반지름 자동]으로 되돌림) · [곡선 지우기]/[곡선 넣기](지운 자리는 가운데 빈 동그라미) · [확인]이 정점마다 curve·radius_m 을 함께 보냄. 노드 삽입·삭제 때 편집값 목록도 같은 자리에서 밀어 줌 — 안 그러면 뒤 노드의 R·켬끔이 한 칸씩 어긋남.

    • 실화면 확인(용화, 20번째 꺾임점) — 고름 「자동 · 내각 155° · 법정 하한 12m」 → 곡선끔 「곡선 없음 — 직선이 그대로 꺾입니다」(상태줄 「곡선 지움 1곳」) → 곡선켬 「자동」 → R 40 「값 지정」(「R 지정 1곳」) → 자동 되돌림까지 넷 다 동작.
    • ⚠ 함정 — 처음엔 단추가 캔버스 밑에 깔려 클릭이 가로채였음(intercepts pointer events). 세로 flex 에서 캔버스가 flex:1 이라 새 줄이 눌린 것. 바닥 단추줄처럼 flex: none + z-index: 1 을 줘야 함.
  • ③ 곡선 시작·끝점을 끌면 직선 각도와 R 이 함께 바뀜 (커밋 20f34390) — 셈은 서버의 반대 방향임(서버: 교각점·R → 접선점 / 화면: 끈 접선점 → 교각점·R). 끈 쪽 직선만 돌리고 반대쪽은 옛 교각점을 지나는 그대로 두어, 두 직선이 만나는 자리가 새 교각점이 되고 R = |새 교각점 끈 자리| / tan(교각/2).

    • 두 벌이 아니라 짝tmp/tests/test_route_polyline_handle_drag.py 3건이 왕복을 지킴. 화면이 낸 (교각점, R) 을 서버 셈에 넣으면 끈 자리가 1µm 안으로 되돌아옴. 접선점을 직선 위에서만 밀면 교각점이 안 움직이고 R 만 커지는 것도 확인.
    • 손잡이를 속 빈 네모(5px) 로 키움 — 3.5px 채운 네모는 선·노드와 색이 같아 눈에도 안 띄고 집기도 어려웠음. 곡선을 지운 자리에는 손잡이도 없음.
    • 실화면 확인 — 손잡이를 끌자 「14번째 꺾임점 · R 16.6 · 값 지정」. 서버가 곡선 성분 22개(예: R 44.3m, 노드 1~3을 한 곡선으로)를 내려줌.
    • 또 옛 코드로 도는 서버에 속았음(오늘 두 번째) — 응답에 curves 키 자체가 없었는데 상태줄은 「곡선 15곳」으로 보여 정상처럼 읽혔음(다른 값으로 세는 폴백이 있었음). 화면 검증 전에 응답에 새 필드가 실제로 있는지부터 볼 것.
  • ⑤ 법정 하한 미달은 막지 않고 붉게 표시 — 기존 규칙 그대로 섬. 위반이 붙은 자리는 모달에서 붉은 동그라미로 보이고, 곡선을 지운 자리는 가운데가 빈 동그라미로 구분됨.

  • 자체검증 — 예상노선과의 벗어남을 피팅 전후로 수치 대조했고(위 ① 참조), 편집 왕복이 제자리인지도 시험 셋으로 못박음: test_route_polyline_idempotent(깎임 없음) · test_route_polyline_handle_drag(손잡이 왕복 1µm) · test_route_polyline_apex_roundtrip(교각점으로 갈아 끼워도 선이 1mm 안에서 같음).

  • 한 번 편집하면 반지름이 하한으로 눌리던 것 고침 (커밋 a0bd9c87) — 실화면에서 잡음. 곡선 하나만 지워도 나머지가 R 12~199m → 전부 12m 로 되돌아갔음. 편집은 「꺾임점 하나 = 곡선 하나」인데 서버가 묶은 곡선의 교각점은 원본 꺾임점 중 어느 것도 아니라, 손대는 순간 묶음이 흩어져 각 자리가 하한으로 떨어졌음. 모달이 열 때 묶인 구간을 그 교각점 하나로 갈아 끼우게 함 — 그려지는 선은 똑같고 이제 손대도 안 흩어짐. 실화면 확인: 되돌린 뒤 곡선 26곳(R 최대 198.9m) → 곡선 하나 지우고 [확인] → 25곳, R 42.5·51·198.9m 그대로.

  • [확인] 한 번 = 약 90초 (오늘 아침 293초에서 줄어든 값. 0-11 참조)

  • 곡선 반지름을 노드마다 손대게 할지 — 사용자 확정: 손대게 함. 위 0-12 로 옮김. 사용자가 특정 곡선만 크게 하고 싶을 수 있음(0-2 의 「곡선(R) 성분 편집」과 같은 항목).

  • 초기 폴리라인을 자동 체인에서 세움(커밋 fe14ad02) — 확인(보조 창): 파일을 지우고 _ensure_initial_polyline 을 부르면 794ms 에 재생성, 옛 파일과 바이트 동일. 브라우저를 한 번도 안 열고 생김.

  • 단순화 허용오차 확인 — 표본 5개(1.1km 셋 · 4.5km 하나 · 노선 없는 둘)로 4m + 노드 간격 상한 100m 로 닫음.


2. B01·B02 — 대시보드·프로젝트 등록

1-1. B01 대시보드 권한·회사·프로젝트 연결 정비 (2026-09-06 사용자 협의)

확정 — ① 시스템 관리 회사는 개발사 자기 회사 한 곳뿐이며 새로 만들 일 없음. 그 회사 소속이면 역할을 고를 것 없이 전원 시스템관리자 ② 고객사는 회사 생성자가 회사관리자, 나머지는 일반사용자 ③ 타사 사용자 공유는 보류 ④ 도면에는 한 사람 이름만 나가되 설계 과정의 보조 인원은 여러 명일 수 있어 참여자 목록을 따로 둠.

1) 시스템 회사 자동 권한

시스템 회사 = .env 의 관리자 계정이 소속된 회사(현재 캐미팩토리). 표시 칸이나 설정 화면은 만들지 않음.

  • 서버 시작 때 그 회사 소속 전원을 시스템관리자로 맞춤 (main.py 의 관리자 이메일 한 줄 옆).
  • 가입 승인·팀원 등록으로 그 회사에 들어오면 시스템관리자 부여, 다른 회사는 일반사용자 (B01_Dashboard_Repository.py process_join_request, _Repository_Members.py 팀원 등록).
  • 회사에서 빠지면 일반사용자로 복귀 — 현행 그대로임을 확인만.
  • 견본 계정·역할 칸은 그대로 둠 (2026-09-06 사용자 확정).
  • 자체검증 — 소속 4명의 역할 변화를 DB 로 확인.

2) 회사 관리

  • 회사 생성 모달에 중복 조회 — 사업자등록번호·회사명으로 기존 회사를 찾아 보이고, 있으면 「가입 신청」으로 유도.
  • 입력 순서 = 사업자등록번호 > 회사명 > 대표자명 > 회사 로고 > 주소.
  • 주소 입력 + 지도 미리보기 — 이미 쓰는 VWorld 키를 서버 경유로 재사용(새 지도 서비스 도입 없음).
  • 회사 정보 수정 화면·API 신설 — 회사관리자는 자기 회사, 시스템관리자는 전체.
  • 자체검증 — 같은 사업자번호로 생성 시도 시 안내가 뜨는지, 수정값이 도면 표제란·로고에 반영되는지 실화면 확인.

3) 사용자(팀원) 관리

  • 「팀원 추가」를 계정 대신 만들기 → 소속 없는 기존 가입자 검색·선택 으로 교체. 다른 회사 소속자는 목록에서 제외. (지금은 임의 비밀번호로 계정을 만들어 당사자가 로그인 못 함)
  • 아직 가입 안 한 사람은 안내 메일만 발송.
  • 입력 순서 = 이름 > 직급 > 이메일 > 부서.
  • 기본정보 폼과 사용자 수정 폼을 한 벌로 통일 (배치는 조절 가능).
  • 회사관리자에게 역할 변경 허용 — 자기 회사 한정, 본인 강등 포함. 마지막 회사관리자 이탈 방지(count_company_admins)는 유지, 시스템관리자는 전역 제어.
  • 비활성화·삭제 구현 — 회사관리자도 자기 회사 팀원 삭제까지 가능, 시스템관리자는 전역.
  • 가입요청 반복 신청 방어 — 이미 소속이 있는 사용자가 「회사 찾기」로 재신청하면 지금은 소속이 풀리고 승인대기로 떨어짐(B01_Dashboard_Repository.py join_company). 소속자에게는 신청 자체를 막을 것.
  • 가입 요청 목록과 사용자 관리 목록의 중복 표시 정리.
  • 자체검증 — 소속자가 재신청해도 소속 유지, 마지막 관리자 강등 거부, 권한 밖 호출은 백엔드가 403 인지 확인.

4) 프로젝트 권한·담당자

  • 일반사용자도 회사 프로젝트 전체 열람(수정 불가) — 지금은 자기가 만든 것만 보임(list_user_projects).
  • 회사관리자에게 자기 회사 프로젝트 삭제 허용 (지금은 시스템관리자만).
  • 참여자 표 신설 — 프로젝트에 여러 명을 붙임. 만든 사람이 기본 참여자.
  • 참여자면 일반사용자도 그 프로젝트 수정 가능 — 백엔드 _can_edit_project 와 프론트 헬퍼를 같이 고침.
  • 도면 표제란 3역할(과업책임자·분야별책임자·설계자)은 각 1명 유지.
  • 자체검증 — 일반사용자 계정으로 남의 프로젝트 열람 가능·수정 403, 참여자로 넣은 뒤 수정 성공을 실화면으로 확인.

5) 프로젝트 생성 화면 (B02)

  • 취소 버튼 추가 — 대시보드로 복귀.
  • 프로젝트명 자동 조합 — 사업연도 + 사업지역 + 임도종류 + 수기 입력값을 한 줄로 만들어 그대로 저장.
  • 예상 연장 칸 삭제 — 대신 「노선 종료 누가거리(m)」 제목에 (종료 − 시작) 길이를 표기.
  • 「연도 기번(표지)」·「사업량(표지)」 입력칸 삭제 — 위 두 값과 중복. 도면 표제란·표지는 프로젝트명·노선 연장에서 끌어 쓰게 잇고 B07 출력 영향 확인.
  • 담당자 선택의 「+ 신규 등록…」 제거 — 계정 등록 후 고르는 길만 남김.
  • 담당자 기본값 = 만든 사람.
  • 자체검증 — 저장된 이름·연장·표제란 값이 도면에 그대로 나오는지 실화면 확인.

6) 대시보드 배치·문구

  • 「시스템 로그」를 맨 아래로 — 지금은 기본정보·보안 칸이 그 뒤에 붙어 있음.
  • 서명 고르는 칸 문구 정리 — 「○○에 물리기」·「올리고 선택」이 무슨 뜻인지 읽히게.
  • 자체검증 — 실화면 스크린샷으로 순서·문구 확인.

1-2. B01·B02 후속 — 화면 다듬기 (2026-09-06 사용자 지시)

실화면을 훑어 나온 것들을 그 자리에서 고침. 다섯 건 모두 공용 브라우저(8001·5174)에서 수치·스크린샷으로 확인.

B02 프로젝트 등록

  • 프로젝트명을 고칠 수 있는 칸으로 — 「저장될 이름」 미리보기 줄을 없애고 입력 칸 하나로 통합. 사업연도·사업지역·임도종류로 자동으로 차되 사용자가 사이나 끝에 글을 넣거나 자동 글자를 고칠 수 있음.
  • 자동 조합이 끊기던 것 고침 — 문자열을 찾아 바꾸던 방식이라 자동 글자 사이에 한 글자만 끼워 넣어도 연동이 통째로 끊겼음(실측: 「울진군 금강송면」의 「금강송면」 앞 삽입). 글자마다 주인을 기억하는 방식(B02_ProjRegister_UI_Name.ts)으로 다시 만듦.
  • 회사 로고 칸 삭제 — 프로젝트가 이미 회사에 매여 있고, 표제란은 COALESCE(프로젝트, 회사) 로 회사 로고를 씀.
  • 비고를 맨 마지막 항목으로 이동.
  • 제목·버튼 줄을 공용 템플릿(createGeneralLayout·ui-general-block__actions)으로 전환, 화면 폭은 종전 720px 유지.

B01 대시보드

  • 진행도(%) 열 삭제 — 워크플로 배지가 같은 것을 보여 줌. 상태 문자열로 따로 세던 서버 계산도 제거(배지는 project_workflow_stages 표가 근거라 둘이 어긋나 「0% 인데 횡단설계까지 켜짐」이 나왔음).
  • 시스템 로그 표 정비이메일 · 동작 · 대상 · 일시. 관리 버튼 열과 같은 문구(「관리」)를 돌려 쓰던 것을 가르고, 대상(resource_type·resource_id)을 새로 보이며, 일시는 날짜/시각 두 줄(아랫줄 작은 글씨라 행 높이 불변).
  • 표 관리 버튼 한 줄 — 작은 버튼 + 줄바꿈 금지, 폭이 모자라면 표가 가로 스크롤. 프로젝트 행 105px → 61px, 사용자 행 55px.
  • 기본정보 = 로그인 본인 화면 — 이메일 라벨을 「팀원 이메일」에서 「이메일」로, 본인 서명 칸을 사용자 수정 모달과 같은 부품으로 추가.
  • 제목·여백을 공용 템플릿으로 통일(역할 배지는 제목 줄 오른쪽), 헤더의 사용자명·로그아웃 버튼이 붙어 보이던 것 간격 부여.

시스템 로그에 더 낼 수 있는 것 (2026-09-06 조사)

  • 대상 번호(resource_id)는 칸이 있으나 기록할 때 NULL 로 넣는 자리가 많음 — 번호까지 보이려면 기록 쪽을 고쳐야 함.
  • IP·브라우저(ip_address·user_agent)는 표에 칸만 있고 기록을 안 함(INSERT 에서 빠짐). 보안 로그로 쓰려면 기록부터 붙일 것.

시스템 로그 기록 보강 (2026-09-06 사용자 확정 — 완료)

  • 기록을 공용 함수 common_util_audit.record_audit() 한 곳으로 모음 — 여섯 자리에 흩어져 있던 raw INSERT 제거.
  • 대상 식별자 칸 신설 (db_management/017_audit_log_detail.sql, 적용 완료) — 옛 resource_id 는 INT 라 프로젝트 UUID 를 못 담아 늘 비어 있었음. 프로젝트 생성·수정·삭제, 회사 생성이 대상을 남김.
  • 접속 주소·브라우저 기록 — 라우터가 요청을 넘기고, 프록시 뒤에서는 X-Forwarded-For 첫 주소를 씀.
  • 보관 기간 1년 (AUDIT_LOG_RETENTION_DAYS 기본 365일) — 임시 보관함 정리 루프에 얹어 함께 지움. 종전에는 지우는 코드가 아예 없어 무한히 쌓였음.
  • 자체검증 — 실화면에서 프로젝트 생성·하드삭제를 돌려 로그 두 줄이 프로젝트 e1040640… · 127.0.0.1 · 09:54 / 09:55 로 남는 것 확인. 시험 tmp/tests/test_common_util_audit.py 6건.

자체검증 (2026-09-06, 보조 워크트리 8001·5174 공용 브라우저)

계획서에서는 이 기록이 「## 2. B05 종단설계」 머리에 놓여 있었으나, 내용은 전부 B01·B02(권한·회사·팀원·프로젝트 등록) 검증이라 여기로 옮겼음.

  • 시스템 회사 자동 권한 — 서버 재시작 뒤 DB 확인: 캐미팩토리 소속 4명 전원 SYSTEM_ADMIN(견본 계정 포함, 사용자 확정대로 예외 없음).
  • 대시보드 순서 — 실화면 섹션이 리소스 현황 · 프로젝트 · 임시 보관함 · 사용자 관리 · 가입 요청 · 회사 관리 · 기본정보 · 보안 · 시스템 로그 로, 시스템 로그가 맨 아래.
  • 회사 정보 수정 — 회사 관리 표의 「수정」에서 모달이 뜨고 칸 순서가 사업자등록번호 · 회사명 · 대표자명 · 회사 로고 · 주소.
  • 주소 지도 — 「강원특별자치도 춘천시 중앙로 1」 입력 후 「지도 확인」: 타일 9장 모두 로드, 표식이 강원도청 자리(스크린샷 tmp/browser/shots/1205_map.png).
  • 팀원 등록 — 「엄상」 검색 결과 없음(소속 있는 사람이 제외됨을 확인), 한 글자면 「두 글자 이상」 안내. 「가입 안내 메일 보내기」 단추 존재.
  • 사용자 관리 행 동작 — 수정 · 역할 변경 · 사용자 삭제 3종 노출.
  • 가입 재신청 방어 — 소속 있는 계정으로 POST /api/dashboard/user/company/join 호출 시 409, 이후 /mecompany_id·status 그대로(예전에는 소속이 풀렸음).
  • 프로젝트 참여자 — 「도엽등고 테스트」 수정 모달에 생성자(엄상돈)가 체크된 상태로 뜨고, 박동철을 더해 저장 → DB project_members 2행 확인.
  • 참여자만 바꿔 저장하면 「프로젝트를 찾을 수 없습니다」로 튕기던 문제(값이 하나도 안 바뀌면 rowcount 0)를 고침 — 재시도 시 「저장되었습니다」.
  • B02 등록 화면 — 예상 연장·연도 기번·사업량 칸 사라짐, 취소 단추 생김, 담당자 3칸 기본값이 만든 사람(엄상돈), 프로젝트명 미리보기 2026 정선군 간선임도 가리왕산지구, 종료 칸 제목 노선 종료 누가거리 (m) — 연장 2140.0m(2260120).
  • 테스트 — ./venv/Scripts/python.exe -m pytest tmp/tests/ -q 186 passed, 1 skipped. 새 테스트 tmp/tests/test_b01_permissions.py 가 참여자 수정권한·시스템 회사 역할 규칙을 확인.
  • 미검증 — 일반사용자·회사관리자 계정의 실사용 확인은 DB에 시스템 회사 하나뿐이라 못 함(고객사 계정이 생기면 확인 필요). 마지막 관리자 강등·삭제 차단도 같은 이유로 코드 경로만 확인.
  • 고객사 흐름 실검증 (2026-09-06 추가) — 임시 회사 「검증용 임시사」와 계정 3개(회사관리자·일반사용자·무소속)를 만들어 실제 로그인(OTP 메일 수신)으로 확인한 뒤 전부 삭제함. 결과: ① 회사관리자 화면은 자기 회사 프로젝트만 보이고 수정·삭제 둘 다 됨 ② 일반사용자는 회사 프로젝트 2건을 모두 보되 참여자인 1건에만 「수정」이 뜨고, 남의 프로젝트 수정·삭제·팀원등록·회사수정·사용자삭제는 백엔드가 403 ③ 마지막 관리자 강등 409, 본인 계정 삭제 409, 다른 관리자를 세운 뒤에는 강등 200 ④ 남의 회사 정보 수정 403, 다른 회사 소속자를 팀원으로 붙이기 409 ⑤ 팀원 빼기 → 후보 목록 등장 → 다시 붙이기 성공 ⑥ 무소속 계정의 가입 신청 → 관리자 승인 → 목록에서 사라지고 팀원으로 편입(일반사용자 역할 유지) ⑦ 계정 비활성화 뒤 로그인 403, 계정 삭제 뒤 로그인 401 ⑧ 가입 안내 메일 실제 수신 확인.
  • 코드 재검토 반영 — 저장소 740줄을 회사 부분 분리로 508줄까지 내림(700줄 규칙), 초대 메일 본문 HTML 이스케이프, 지도 타일 z/x/y 범위 검증, 회사명·사업자번호 중복 시 409, 값 변경 없는 회사 수정이 404 로 튕기던 문제 수정.

3. B05 — 종단설계

2-2. 3D 구조물 선택이 B06 횡단도에 안 뜬다 (2026-09-05 사용자 지시)

선행: 0-1 (넘김값을 ② 초안 한 자리로 흡수한 뒤 그 위에서 고침 — 지금 임시 키 위에서 고치면 두 번 일함).

넘김 자체는 서 있다 — 3D 클릭이 aislo:structure-pick:{프로젝트} 세션 한 칸에 {측점, 부재키}를 적고(B05_Profile_UI_Page.tswireStructurePick), B06 진입 때 그 값으로 카드를 열게 돼 있다(B06_Section_UI_Page_Structure_Pick.ts). 실제로는 안 열린다. 의심 지점은 세 곳이다.

측점 대조 허용오차가 0.01m로 좁다 — 3D가 준 누가거리가 소수점 아래에서 조금만 달라도 그 측점을 못 찾고 조용히 끝난다. 같은 파일의 다른 대조는 0.05m를 쓴다. ② 목록·폼에서 고르는 길(B05_Profile_UI_Page.tsonPipeFacilitySelect)이 같은 칸에 측점만 적어 부재키를 지운다 — 3D로 고른 직후 선택 동기화가 이 길을 타면 카드는 열려도 조정창이 안 열린다. ③ 복원이 진입당 한 번(pickRestored 빗장)인데, 부르는 곳이 둘(구조물 패널 적재 뒤 · 횡단 그림 그린 뒤)이라 이른 쪽이 빗장을 걸어 늦게 온 자료로는 다시 안 돈다.

두 화면은 한 페이지처럼 움직여야 하므로 선택·하이라이트 연결선을 끊지 않게 조심할 것.

결론(2026-09-06 실측) — 코드 결함 없음. 넘김은 처음부터 서 있었음. 안 되던 진짜 이유는 클릭이 캔버스에 닿지 않은 것임. 클릭 자리의 최상단 DOM 이 캔버스가 아니라 종단 오버레이의 SVG rect 였고, 이벤트가 window 캡처에만 뜨고 캔버스 리스너는 하나도 안 불렸음 — pickAt 이 아예 안 돌았음. 종단 오버레이를 접고 같은 좌표를 누르니 한 번에 통과했음.

  • 실화면 3D 클릭 — 세션 칸 aislo:draft:structure-pick:{프로젝트}{"at":85.18405,"key":"outlet"} 이 적힘(기대한 꼴 그대로).
  • B06 진입 — 카드 64개 중 그 측점 카드(4+5.2 = 85.2m 배수관) 1개가 선택된 채로 뜸. 버려지는 단계 없음. 의심 지점 ①②③ 은 전부 해당 없음.
  • 코리도 그룹 레이캐스트도 정상 — 그룹 안 첫 메쉬corridor-structure:revet:0 (at 85.18405 · key outlet · visible:true). 가리는 메쉬도 안 보이는 메쉬도 없었음. 「안 보이는 메쉬 건너뛰기」 수정은 넣지 않음.

검증 함정 — 다음에 3D 클릭을 잴 때 반드시 지킬 것. __corridorScene.project() 가 주는 좌표는 캔버스 좌표계라 DOM 이 덮고 있는지를 안 봄. 클릭 직전에 document.elementFromPoint(x, y) === document.querySelector('canvas') 를 확인하거나, 종단 오버레이 핸들(.ui-workflow-overlay__handle, 텍스트 「종단」)을 먼저 접을 것. 이 함정을 몰라 「3D 픽 고장」으로 두 번 오진했음(지표면 가림 가설·blocked() 가설 둘 다 폐기).

2-3. 종단 그래프 팬 이동 — 좌클릭 끌기·휠 버튼 끌기

가로 이동이 이미 한 벌로 모여 있음(B05_Profile_UI_Profile_Wheel.ts). 여기에 끌기를 더하면 이것을 쓰는 세 곳(종단 그래프·유토곡선·측점 테이블)이 한 번에 같이 얻음.

  • 같은 파일에 포인터 끌기 이동 추가 — 진행 중이던 관성 이동은 끌기 시작 시 중단.
  • 그래프 위 조작 부품에서 시작한 끌기는 제외 — 계획고 편집 버튼(.b05-profile-edit__btn)·구조물 표식(B05_Profile_UI_Structures_Marks.ts)이 좌클릭을 이미 씀.
  • 끌기와 클릭 구분 — 몇 px 넘게 움직인 뒤에만 이동으로 보고, 그 경우 클릭(측점 선택·선택 해제)은 취소.
  • 끄는 동안 커서 모양 변경, 창 밖에서 손을 떼도 풀리게 함.
  • 실화면 확인(2026-09-06) — 측점 테이블에서 180px 끌자 세 곳 scrollLeft 가 0 → 180 으로 함께 움직임. 유토곡선에서 150px 더 끌자 셋 다 330 으로 맞물림. 측점 선택·계획고 편집은 그대로(같은 화면에서 계획고 ▲ 두 번 눌러 값이 바뀜).

2-4. 종단 테이블 행 정리 — 목적은 화면 자리 확보

지금 12행 = 구배 3행(L·H·S) + 측점값 7행 + 곡선 2행. 자리는 B05_Profile_UI_Profile_Table.ts 한 곳(SEGMENT_ROWS·buildStationRows).

  • 뺄 6행 — 구배 L, 구배 H, 절토고, 성토고, 누가거리, 거리.
  • 남길 6행 — 구배 S, 계획고, 지반고, 측점(사용자 확정: 유지), 곡선 L, 곡선 R.
  • B07 도면 종단도는 현행 12행 유지 — 도면 표는 별도 코드(B07_DesignDetail_Engine_Cad_Table.py)라 이 변경이 닿지 않음을 확인만 함(코드 확인 완료, 실제 도면 출력으로 재확인).
  • 행이 줄어든 만큼 그래프가 넓게 쓰이는지, 글자 크기·높이가 자동으로 맞는지 실화면 확인.
  • 높이 상한 신설(2026-09-06 사용자: 「최대값 제한, 레이아웃 제어는 현재 유지」) — 6행 × 행 30px + 24px = 204px. 리사이저 상한·패널 비례 연동·세션 복원 어느 경로로 들어와도 이 값을 넘지 않게 한 곳(clampHeight)에서 막음. 끌어 줄이는 것은 그대로(하한 140px).
    • 자체검증 — 화면 실측 167px(상한 이하), 6행 모두 읽힘. 12행 시절 저장값(300px)이 남아 있어도 리사이저 복원이 상한으로 깎임.

2-5. 유토곡선 — 초기 화면 정리 + 최종 누가량을 종단 상단줄로

두 일이 같은 파일(B05_Profile_UI_Profile_MassHaul.ts)을 만지므로 한 항목으로 묶음. 순서는 (가) → (나).

(가) 초기 화면은 곡선만 — 토량분배 숨김 (B05·B06 공용)

기본 켜짐 목록은 common_util/common_util_mass_haul.tsMASS_HAUL_DEFAULT_VISIBLE(횡단 곡선)에 토량분배 키를 더한 형태이고, 그 더하는 자리가 B05(B05_Profile_UI_Profile_MassHaul.ts)·B06(B06_Section_UI_Section_View_Panel.ts) 두 곳임. 세션 저장 키는 두 화면 공용.

  • 두 파일의 기본 목록에서 토량분배를 뺌.
  • 세션 저장 키 판(b06:masshaul-visible-v4)을 올림 — 안 올리면 이미 켜 둔 브라우저가 옛 값을 그대로 씀. (0-1 키 규칙이 먼저 서면 그 규칙의 새 키로 바로 감)
  • 범례에서 켜면 평형선·운반 블록이 예전대로 나오는지 확인 — 켠 뒤 분배 도형 80개 확인.

(다) 토량분배를 켜면 곡선 전체를 한 화면에 (2026-09-06 사용자 지시)

평형선·운반 블록은 노선 전체를 놓고 읽는 그림이라, 종단 그래프에 맞춘 가로 스크롤 안에서는 한 번에 못 봄. 분배를 켠 동안만 X·Y 비율을 버리고 컨테이너에 맞춤.

  • 분배가 켜져 있으면 곡선 폭 = 스크롤러 안쪽 폭(가로 스크롤 없음), Y 는 전 구간 기준.
  • 끄면 예전대로 종단과 맞물린 가로 스크롤·보이는 구간 Y 로 복귀.
    • 자체검증 — 켠 상태 스크롤 내용폭 867 = 보이는 폭 867(스크롤 없음), 끈 상태 4,600 vs 867(예전대로). 분배 도형 80개, 종단 기준 곡선 선택 상태 유지.
  • 전체 맞춤일 때 측점 세로선 숨김 — 67개가 촘촘히 서서 곡선을 덮기만 하고 자리도 못 읽음(2026-09-06 사용자 지시). 끄면 다시 나옴.
  • 전체 맞춤일 때 좌우 여백 축소 — 종단과 맞물릴 필요가 없으므로 반 칸 들여쓰기 (originOffset)를 빼고 우측 여백을 12px 로 줄임. 곡선이 폭을 다 씀.
    • 자체검증 — 측점선 0개(끄면 66개), 곡선 오른쪽 끝이 화면 끝까지 닿음.
  • 상단 여백 축소 — 요약 막대가 종단 상단줄로 올라간 뒤에도 범례가 옛 자리(34px)에 떠 있어 위가 비었음. 범례 4px, 곡선 상단 여유 40 → 30px(범례 높이 24 + 여유).
    • 자체검증 — 범례가 오버레이 위에서 5px, 높이 24px. 곡선이 그만큼 위로 올라옴.

(나) 최종 누가량을 종단 상단줄로

지금 요약 막대(절토·성토·잉여/부족·최종 누가토량·운반·검산)는 유토곡선 오버레이 안에 있어 패널을 접으면 사라짐. 계획선을 만지며 봐야 하는 값이므로 종단 상단줄로 올림.

  • 종단 상단줄(B05_Profile_UI_Profile_Balance.ts)의 「최대 기울기」 오른쪽에 최종 누가토량 항목 추가 — 값 출처는 유토곡선 계산 결과(result.points 마지막 누계)이며 토량분배 켜짐과 무관하게 나옴((가)와 충돌 없음).
  • 유토곡선 오버레이의 요약 막대 행 삭제.
  • 삭제되는 나머지 값(절토·성토·잉여/부족·운반·검산)은 새 항목의 툴팁으로 내림 — 지금 요약 막대가 세부 내역을 툴팁에 두는 방식과 같음.
  • 종단 상단줄 높이를 조금 높임(값이 한 줄에 들어오게).
  • 유토곡선 펼침 손잡이를 오버레이 형태로 바꿈(그래프 자리를 먹지 않게).
  • 요약 막대·손잡이가 비운 만큼 유토곡선 그래프 상하 높이를 넓게 씀.
  • B06 화면의 요약 막대는 그대로 둠(같은 공용 함수를 쓰므로 B05 쪽만 떼어 내게 손댈 것).
  • 실화면 확인(2026-09-06) — 유토곡선 패널을 접어도 종단 상단줄에 「누가토량 547.8㎥」 가 그대로 남음. 계획고를 두 번 올리자 547.8 → 528.1㎥, 「편집 측점」 6 → 7 로 따라 바뀜.

2-5c. 유토곡선 자리 정리 — 총괄값 배지 + B06 곡선 제거 (2026-09-06 사용자 지시)

  • 기준은 횡단 고정 — 총괄값 계열을 범례 선택과 무관하게 횡단 기준으로 뽑음 (종단 기준 곡선은 뒤에 없앨 예정이라는 사용자 확정).
  • 최종 누가토량 배지 신설(common_util_mass_haul_badge.ts) — 종단 그래프 좌측 상단에 겹쳐 뜸. 세부(절토·성토·잉여/부족·기준)는 툴팁.
  • B05 상단줄에서 「누가토량」 항목을 빼고 배지로 옮김. 유토곡선 패널은 그대로 유지.
  • B06 유토곡선 그래프·범례·요약줄 삭제 — 그 높이를 종단면도가 다 씀. 같은 배지가 총괄값을 보여 줌.
    • 자체검증 — B06: 배지 「누가토량 −5,595.5㎥」, 범례·요약줄 없음, 종단 그래프가 남은 높이 사용(228px), 횡단 카드가 그만큼 위로 올라옴. B05: 배지 「누가토량 −6,842.6㎥」, 상단줄에는 누가토량 없음(최대 기울기·편집 측점만).
    • 배지 자리는 Y축 폭만큼 밀어 축 라벨과 겹치지 않게 함(--mass-haul-badge-left 86px), B05 는 상단 도구줄 아래로 한 줄 내림(--mass-haul-badge-top 34px).
  • 뒤이은 변화 (2026-09-06 저녁, 0-8) — 이 절 뒤에 유토곡선 구조가 한 번 더 바뀌었으니 이 절만 읽고 판단하지 말 것:
    • 배분·운반거리는 브라우저가 계산하지 않음. 서버 /haul-plan 이 내고 화면은 편집이 멈추면 받아 둠(B05_Profile_Api_HaulPlan). 보안 목적임.
    • 죽은 파일 B06_Section_UI_Section_View_MassHaul.ts 삭제됨 — B06 곡선 제거의 뒷정리.
    • 접힘일 때는 횡단 기준만 적분함(computeMassHaulSeries 기준 인자).

2-6. 구조물 측점 누가거리 정본 일원화 — 남은 작업

저장 정밀도와 화면 정본 맞춤 완료분은 정리 전 원문 스냅샷에 보존했다.

  • 겹치면 구조물 측점만 남긴다.
    • 그래프 측점 목록에서 구조물 측점과 0.1m 안에 겹치는 규칙 측점을 제외한다 — 공용 유틸 dropStationsNear(B05_Profile_Util_Station.ts), 종단 그래프와 3D 측점 띠가 같은 규칙을 씀.
    • 구조물 측점 라벨 실화면 확인(2026-09-06) — 종단 그래프에 「찰쌓기 1~4」·「옹벽」· 「배수관 1」 이 이름표와 함께 섬.
    • 선택 연동 확인 — 그래프의 「옹벽」 마크를 누르자 마크가 선택되고 사이드 목록의 「5+15.0~6+5.0 옹벽」 도 함께 선택되며 폼에 기준측점 5 가 실림. 측점 테이블 열 강조는 없음(구조물 구간은 정규 측점 열이 아님 — 표시 규칙은 사용자 판단).
    • B06 종단 구조물 표시를 B05 와 통일 (2026-09-07 사용자 동의, 보조 창 커밋 67972612) — B05 가 쓰는 같은 부품(buildStructureLane)을 B06 종단 아래에 붙임. 실화면에서 알약 10개(배수관 18 · 세월교 12)가 B05 와 같은 이름·번호로 뜸. 관을 가상 구조물로 바꾸는 함수는 B05 안에 갇혀 있던 것을 공용으로 올려 한 벌만 둠. 알약을 누르면 그 측점 카드가 열림. 끌기는 B05 몫으로 남김(배수유역 재분할이 걸림).
    • 유토곡선이 구조물 초안까지 반영하는지 확인(2026-09-06) — 같은 화면에서 두 곡선을 계산해 대조: 초안 벽 포함 9,286.7㎥, 서버 저장분만 10,288.7㎥(차이 1,002㎥). 앞서 「값이 안 변한다」고 본 553.4㎥ 는 종단 기준(개략) 곡선이라 횡단 면적과 무관했던 것 — 횡단 기준으로 바꾸면 초안이 반영됨.

2-7. 초기값(초기 스냅샷) 후속 — 실경로 검증

배수유역 스냅샷 범위 확대·옛 스냅샷 호환·3D 코리도 보존 구현과 단위검증은 정리 전 원문 스냅샷에 보존했다. 노선 변경 게이트 설계는 0-2 로 옮김.

  • 확인(2026-09-06 용화 신규 프로젝트) — 파일 업로드부터 체인 완주까지 화면에서 돌렸고 initial_snapshot/(노선·종횡단·배수유역·코리도·DB덤프)이 생성됨.
  • initial_design.lock 흐름 확인(2026-09-06 보조 창, 공용 브라우저) — 락을 세우면 surface/statusin_progress·initial_design 이 되고 단계바 [종단설계] 클릭 시 #/b11-loading 으로 튕김(문구·0% 표시). 락을 지우면 약 2초 만에 #/b05-profile 로 자동 복귀(3초 폴링과 맞음). ⚠ 단계바 .ui-workflow-layout__step0번이 대시보드라 종단설계는 3번임.

4. B06 — 횡단설계

3-2 가 「구조물이 서면 면적이 달라진다」는 자리를 만들고, 3-4·3-5 가 그 위에 구조물 형상을 얹는 순서임.

3-1. 곡선부 노폭 자동 확폭 (평면 R 기준) — 새 기능

선행: 0-3 (측점별 평면 R 계산). 0-2 계획노선이 바뀌면 R·확폭도 같이 다시 섬.

노폭 = 도로 중심의 차도 폭(노견 아님). 지금 코드에 곡선부 확폭이 없음 — 차도 폭은 규격 프리셋 상수 하나로 전 측점 동일(B06_Section_Engine_Design.pyroad_width_m). 기존 「노폭 연장」은 기슭막이가 밖으로 나간 만큼 노견이 따라 늘어나는 별개 계산임.

근거 (2026-09-06 지식DB·실무 원본 확인)

  • 법령(별표2 .2.나.(4) = 평면선형.md §3) — 확폭량은 평면 곡선반경 R 로만 정해지며 표값은 하한:
R(m) 10~13 13~14 14~15 15~18 18~20 20~25 25~30 30~40 40~45 45 이상
확폭(m) 2.25 2.00 1.75 1.50 1.25 1.00 0.75 0.50 0.25 없음
  • 유효너비 3.5m 이상이고 확폭 구간은 최대 5m까지. 배향곡선지는 8m 이상.
  • 실무 원본 대조 — 울진·대흥 2공구 수량서 「확폭」 시트: IP별 R → 표값 W 그대로, 면적 = ((L1+L2)/2)×W 사다리꼴이고 전이는 양끝 각 10m. 울진·소광 「포장확폭」 시트: 측점별 확폭폭 × 구간거리 로 면적 산출. 대피소는 두 곳 모두 별도 줄로 측점 범위·면적 직접 입력.

확정 사항 (2026-09-06 사용자)

  • 확폭 방향 = 곡선 바깥쪽 편측 — 회전 시 차량이 밀리는 쪽 기준. (참고: 일반 도로설계기준은 후륜 내륜차로 안쪽 확폭이며, 임도 법령·교본에는 방향 규정이 없음. 도면 실물로 재확인할 여지 있음)

  • 전이 = 양끝 각 10m 테이퍼, 확폭 유지 구간은 곡선 구간(BC~EC). 측점별 확폭폭을 갖는 방식으로 넣으면 실무 사다리꼴과 같은 결과가 됨.

  • 위 표로 확폭량 W 를 정함(경계는 「이상~미만」, 45m 이상 없음, 표값은 하한) — R 은 0-3 이 실어 준 값을 씀.

  • 곡선 구간 앞뒤 10m 테이퍼로 확폭폭을 0→W→0 으로 잇고, 측점별 값(curve_widening_m)으로 저장 — 설계는 반경 표값이 아니라 이 값을 우선 씀(파이썬·TS 짝 모두). 측점 간격 20m 에서는 중간 측점이 없어 화면 보간이 대신 이어 줌.

  • 차도 폭 = 규격 폭 + 확폭(바깥쪽 편측)으로 측점별 적용 — 노견·측구·사면은 그 바깥에 그대로 밀림.

  • 확폭 뒤 유효너비가 5m를 넘지 않게 제한하고, 넘으면 경고 표시.

  • 횡단도에 노폭 값 라벨 표시(확폭 걸린 측점을 눈으로 확인 가능하게).

  • 3D 코리도는 설계선(road_edges·carriageway_edges)을 그대로 읽으므로 확폭이 자동으로 실림. 수량 쪽은 B08 재작업 대기 — 그때 carriageway_width_m(= 3.0 + 확폭)를 쓰면 됨.

  • 대피소·차돌림곳은 자동계산에 들어가지 않음 — 확폭은 평면 곡선반경만 보므로 구조적으로 제외됨. 별도 구간 입력은 뒤에 정함.

  • 파이썬·브라우저 두 벌 계산이 한 세트이므로 같이 고치고 거울 테스트를 돌림.

  • 실화면 확인(2026-09-06 용화 신규 프로젝트) — 31곳 확폭, 67곳 중 64곳이 표값과 정확히 일치, 나머지 3곳은 테이퍼로 생긴 값(0.40·0.07·0.19m). 유효너비 5m 상한도 걸림(R12.2 → 표값 2.25 대신 2.00). 카드 라벨 「노폭 5.00m (규격 3.00 + 확폭 2.00)」.

3-2. 구조물 절·성토 면적 반영 (2026-09-05 사용자 착수 지시)

연결: 3-4·3-5 가 구조물 형상을 바꾸므로, 그 결과가 이 면적 계산으로 들어와야 함 — 면적 반영 자리를 먼저 만들고 둘을 그 위에 얹는 순서가 맞음.

보류를 푼다. 지금 면적은 구조물이 없다고 보고 낸 값이고, 그 뒤 구조물이 들어오면 면적이 갱신돼야 한다. 상세 원인·실측값은 정리 전 원문 스냅샷에 보존했다.

코드 확인 결과 (2026-09-05) — 면적은 지반선과 설계선의 차이만 사다리꼴로 적분한다(B06_Section_Engine_Areas.pycommon_util/common_util_cross_design_areas.ts, 두 벌 한 세트). 이 계산은 구조물이 있는지조차 모른다. 구조물 형상을 깎는 일은 3D 쪽에서만 하고 있다(B05_Profile_UI_Corridor_Carve.ts 등) — 그 결과가 면적으로 돌아오지 않는다. 그래서 「구조물이 먹은 만큼 면적을 고치는」 자리를 새로 만들어야 한다. 설계선을 세우는 B06_Section_Engine_Design.py가 이미 671줄이라 700줄 여유가 없다 — 새 로직은 별도 파일로 뺄 것.

  • 기준 확정(2026-09-06 사용자) — 구조물 자체 면적을 빼는 것이 아니라, 지반선과 실제로 그려지는 설계선이 이루는 폐회로의 넓이다. 그리는 쪽 트림(designTrim)을 그대로 받아 계산하므로 시설 종류(기슭막이·BOX·세월교)를 가리지 않는다.
  • 브라우저 계산에 반영 — common_util_cross_structure_areas.ts 신설, 벽면을 수직으로 끊어 단차에서 없는 면적이 생기지 않게 함. 확인: 전구간 100㎡ · 벽에서 끊음 6.25㎡ · 구조물선 포함 20㎡.
  • 서버에도 같은 값이 서게 함 — 파이썬 포팅이 아니라 0-4 의 Node 진입점으로 해결. 전처리 체인·[저장]·[확정]에서 서버가 같은 TS 를 돌려 정본에 얹는다. 카드를 안 그린 측점도 값이 선다.
  • 확인(2026-09-06) — 카드 버튼을 바꾸면 브라우저가 전 측점을 다시 계산하고, [저장] 때 면적·유토곡선이 함께 정본에 실림(DB 대조 완료).
  • 파이썬 짝은 만들지 않기로 함 — 기하는 화면 그리는 코드와 한 벌이어야 해서 Node 실행을 택함(0-4). 거울 테스트 대상이 아님.
  • 남은 확인 — 구조물을 새로 추가했을 때 면적표·유토곡선이 같이 움직이는지 (2026-09-06 실화면).
    • 결과: 안 움직임. 좌측 「구조물 배치」로 C군(돌쌓기(찰)·옹벽)을 넣고 [저장]까지 해도 그 측점 절토계(4+0.0 = 5.05㎡)와 최종 누가토량(553.4㎥)이 그대로임.
    • 까닭: 면적을 만드는 구조물 폐회로는 측점 카드의 횡단 설계값(revet_adjust· extra_wall_counts·inlet_structure 등)에서 나오고, structures.json 목록은 그 계통에 실리지 않음. 즉 두 「구조물」이 서로 다른 자리에 있음.
  • 사용자 확정(2026-09-06): 반영한다. 좌측 목록의 C군 벽(옹벽·돌쌓기·흙막이·큰돌쌓기)을 횡단 제원으로 얹어 도면·면적·유토곡선이 함께 보게 함.
    • 방법 — 독립 기슭막이가 쓰던 section.revetment 제원 자리에 그대로 얹음. 그러면 기하· 설계선 트림·폐회로 면적·3D 가 손대지 않고 따라옴(옛 attach_revetments 를 C군으로 되살림).
    • 서버 B06_Section_Engine_Structures_Wall.py ↔ 브라우저 common_util_structure_walls.ts (거울 테스트 tmp/tests/test_b06_structure_walls_mirror.py). 서버는 저장분을, 브라우저는 아직 저장 안 한 초안을 얹음 — 조작 중에도 즉시 보이게.
    • 설치 측은 C군 폼에 칸이 없어 성토가 나는 쪽으로 자동(양측 절토면 벽을 세우지 않음).
    • 형태는 종류로 이음(돌쌓기(찰)/(메) 그대로, 옹벽→콘크리트, 흙막이→통나무·목재틀, 큰돌쌓기→돌쌓기(메)) — 형태가 높이 한계·두께를 정하기 때문.
    • 자체검증 — 40m 에 돌쌓기(찰)을 넣자 그 측점 성토 5.81 → 5.02㎡(0.79). 절토 측점 (120m, 우측 절토)에서는 값이 그대로 — 성토 사면이 없어 벽이 면적을 바꾸지 않음(정상). 초안 3건을 브라우저가 얹어 면적 행 15 → 18개. 거울 테스트 통과.
  • 화면 배선 확인(2026-09-06 실화면) — 구조물을 넣는 즉시 그 측점 카드와 유토곡선이 함께 움직임. 5+0.0 성토 10.43 → 7.88㎡, 3+0.0 성토 8.06 → 5.73㎡ · 최종 누가토량 553.4 → 546.4㎥. 저장은 누르지 않았음(조작 중 계산은 브라우저 몫).
    • 고친 것 — 유토곡선은 카드보다 먼저 계산돼 처음에는 카드만 바뀌고 곡선이 옛 면적으로 남았음. 구조물이 바뀌면 면적을 먼저 다시 얹고 그리게 순서를 바로잡음.
  • 설치 측을 사용자가 고르게 함 (2026-09-07 사용자 확정 「폼 선택은 가능하게 반영하고 나중에 선택 비활성화로 하자」, 보조 창 커밋 6b76a3f2) — C군 5종(옹벽·돌쌓기 찰/메· 흙막이·큰돌쌓기)에 「설치 측: 자동(성토 쪽)·좌·우」 칸을 넣음. 기하는 이미 그 값을 보고 있었고(Cross_Revetment.ts:168) 칸만 없던 것. 함께 옵션에 enabled 를 둬 회색으로 잠그는 구조를 만듦 — 잠기면 필수 검사에서도 빠짐. ⚠ 그 기본값 「자동(성토 쪽)」이 「근거 없는 기본값 금지」 시험에 걸려, 지금까지의 동작을 잇는 값이라는 근거를 적어 허용 목록에 넣었음.
  • 구조물 저장이 통째로 유실되던 버그 수정 (2026-09-06 실화면에서 드러남) — B05 화면이 진입 때 받은 판번호로 저장해, 그 사이 정본이 한 번이라도 바뀌면 409 로 거절되고 실패 처리가 세션 초안까지 지워 넣은 구조물이 사라졌음. 판번호를 쓰기 직전에 다시 받고, 실패해도 초안을 남기도록 고침.
    • 자체검증 — 고치기 전: 구조물 추가 → [저장] → 정본 0건(사라짐). 고친 뒤: 추가 → [저장] → 정본 1건 → 2건(판번호 3 → 4 → 5), 세션 초안은 저장 성공 뒤에만 비워짐.

3-4. 기슭막이 연동 해제 시 높이도 분리

연결: 3-2 위에 얹음(높이가 갈리면 면적도 갈림).

지금 연동 해제는 위치(4축 조작값)만 그 측점 것으로 갈림. 높이는 두 곳에서 오는데 ⓐ 구조물 옵션에 저장된 높이(연동 구간 공용) ⓑ 측점별 조정값 — 해제해도 그 측점에서 높이를 직접 만지기 전에는 ⓐ를 계속 따라감(B06_Section_UI_Cross_Culvert_Wire.tsdetachedAdjusts, B06_Section_UI_Cross_Culvert_Const.tsrevetWallSpec).

  • 연동을 푸는 그 순간의 실제 높이를 그 측점 값으로 굳힘 — 값이 튀지 않으면서 이후에는 따로 놀게 함.
  • 다단(추가 기슭막이) 높이도 같은 규칙으로 분리.
  • 단 수·형태 유지 확인(2026-09-06 수치) — 연동 측점 260m 은 소유 측점 264.06m 과 같은 벽 1개·다단 2개·형태 돌쌓기(메)·높이 1.30m. revet_link_detached 를 켜도 그대로 — 해제하면 다단이 통째로 사라지던 옛 문제는 재발하지 않음.
  • 실화면 확인 완료 — 굳히기 정상(2026-09-06 공용 브라우저, 용화)
    • 벽이 있고 연동이 켜진 카드 820.00m 에서 [연동]을 끄자 세션 revetx820.00:outlet {h: 1.8} 이 적힘 — 푸는 순간의 높이가 그 측점 값으로 굳음.
    • 그 뒤 소유 측점 821.62m 높이를 4.2m 로 올려도 820.00m 은 1.8m 그대로 — 따로 놂. 3-4 의 본 취지가 성립함.
    • 측정 방법 두 가지를 틀리면 「안 굳는다」로 보임(둘 다 실제로 겪음): ① 조정창 .b06-structure-panel카드마다 하나씩 67개 — 첫 번째만 보면 영영 is-hidden. 그 카드 안의 패널(card.querySelector)을 볼 것. ② 벽을 안 그리는 카드를 고르면 drawnWallKeys() 가 비어 굳히기 루프가 0회 돎. card.querySelector('.b06-chart__culvert-revet-hit') 로 먼저 거를 것.
    • 조정창 여는 법 — 판정면을 scroll_into_view_if_needed() 로 화면에 올린 뒤 그냥 클릭. 카드를 미리 고를 필요 없음.
  • 벽을 안 그리는 카드에 [연동] 버튼이 뜨던 것(커밋 71694163) — 눌러도 아무 일이 없고 revetlinkdetached 만 쌓였음. linkStatedrawnWallKeys().length 도 함께 보게 함.

3-5. 연계(추가) 기슭막이가 성토사면과 만나는 자리 안 맞음 · 노견 확장 없음 (버그)

연결: 3-2 위에 얹음(노견이 늘면 면적이 바뀜).

2026-09-06 상태 — 코드만 보고는 고칠 자리를 못 좁힘. 추가 벽은 노견이 아니라 관 하단 꼭짓점에서 1:1.2 로 내려오는 성토부선을 타므로, 노폭 연장이 안 걸리는 것이 잘못인지 아닌지가 화면 실측 없이는 안 갈림. 화면 재현 뒤 수리로 미룸.

확인 결과 — 노견을 밖으로 늘리는 계산(B06_Section_UI_Cross_Culvert_Solve.tsfillSlopeOf)은 배관 기준벽(유입·유출)에만 걸림. 추가 기슭막이(다단)는 B06_Section_UI_Cross_Culvert_Extra.ts가 따로 세우며 그 길을 타지 않음.

  • 증상을 수치로 잼(2026-09-06) — 추가 벽 바닥이 원지반에서 떠 있었음. 배수관 측점에 다단 1을 세워 재니 floatGapM = 264.06m 0.394m · 731.31m 0.873m · 821.62m 0.427m · 914.93m 0.122m. 성토부선과 벽 상단은 원래 정확히 만남 (성토선이 벽 topJoint 로 끝나게 만들어짐) — 어긋난 것은 아래쪽이었음.

  • 원인 가름 — 노견 확장(fillSlope.roadWideningM)은 이 측점들에서 기준벽도 0 이라 원인이 아님. 추가 벽은 상단을 1:1.2 성토선에 붙이고 키는 요청값 그대로 쓰는데, 지형이 그만큼 안 나오면 바닥이 뜬 채로 남았음(코드 주석도 「뜨면 경고」로 두고 있었음).

  • 고침 — 자리는 그대로 두고 키를 지반까지 맞춤: 지반이 낮으면 키우고(형태 높이 한계까지), 높으면 줄임(최소 높이까지). 되받기 값도 조정된 높이로 나감.

    • 자체검증 — 같은 측점 재측정: 264.06 0.394 → 0.000m(키 1.00 → 1.39), 821.62 0.427 → 0.000, 914.93 0.122 → 0.000, 1002.24 0.000. 731.31 은 0.873 → 0.373 으로 줄되 남음 — 돌쌓기(메) 높이 한계(2.0m)에 걸린 자리라 예전처럼 경고를 띄우는 것이 맞음.
    • 테스트 390 통과.
  • 3D·횡단도·면적이 한 벌인지 코드로 확인(2026-09-06) — 셋 다 computeCulvertLayout 한 함수를 지남: 3D B05_Profile_UI_Corridor_Structures.ts:25, 횡단도 B06_Section_UI_Cross_Culvert_Wire.ts:8, 면적 structureAreaRowscomputeStoredLayouts. 구조적으로 갈릴 수 없음.

  • ⓐ 다단 벽이 원지반에 앉는지 — 통과(2026-09-06 보조 창, Node 로 저장 경로 실행)

    • 270.43m — 단 2개 실제로 섬(extra_wall_counts {outlet:2} 그대로 반영). 벽1 바닥 826.291 · 높이 0.967 · 뜸 0, 벽2 바닥 825.265 · 높이 0.512 · 뜸 0. 끝 성토 0.912/0.861/0.117m 로 addable=false(더 못 놓음).
    • 85.18m — 단 0개인데 정상. 끝구간 갈래가 cut = 시작점이 원지반 아래(매몰)라 관문 ①에 걸림. 그 측점은 다단 대상이 아님.
    • ⚠ 앞서 「단 수는 저장됐는데 벽이 0개」로 본 보고는 오측(조정창을 안 거치고 API 로만 심은 뒤 다른 자리를 봤음). 배선·기하 모두 정상임.
  • 🔴 다단이 면적(물량)을 안 바꾸던 것 — 고침 (2026-09-07 사용자 지시 「옹벽도 수량이 변되어야함」, 보조 창 커밋 e9ad8cdf)

    • 고친 것 — 면적 폐회로의 바깥 경계가 기준벽 사면까지만이라 그 바깥은 원지반으로 보고 0 이었음. 화면이 그리는 선(outletFill.segments·basinFill.segments)을 트림 사면에 이어 붙임. 「면적은 실제로 그려지는 설계선을 따른다」는 기존 원칙과 같은 자리로 맞춘 것. 그리지 않는 cut 갈래(벽이 원지반에 묻힌 자리)는 화면과 같게 뺐음.
    • 전후 표(용화 route 165) — 81.77m 성토 8.4247 → 10.9027(단 0) · 단 1개면 10.6743 (단이 서면 성토가 줄어듦 — 벽이 흙을 대신 받으니 맞는 방향) · 244.14m 3.3492 → 4.0511 · 423.55m 변화 0(벽이 묻힌 자리). 절토는 전부 불변.
    • 81.77m 은 지형이 1단까지만 허락해 2·3 요청이 같은 값임(관문 두 개) — 결함 아님.
    • 파이썬 짝 없음 — 구조물 트림 면적은 TS 한 벌이고 서버가 Node 로 돌림.
  • 🔴 그 파급 — 사용자 확정: 그대로 둠(2026-09-07 「1번 ok」). 되돌리지 않음. 아래는 판단 근거로 남김.

    • 같은 수정으로 단이 하나도 안 서는 측점의 성토도 늘어남(81.77m +2.48㎡ · 244.14m +0.70㎡ · 벽이 묻힌 423.55m 은 0). 이유는 같음 — 기준벽 바깥으로 이어지는 성토부선이 화면에는 그려지는데 면적에는 안 들어가 있었음.
    • 관점 ⓐ 정정임 — 벽 바깥 흙쌓기는 실제 물량인데 빠져 있었음(과소 산정). 관점 ⓑ 범위가 넓음 — 배관·세월교가 선 모든 측점의 성토가 늘어 견적 금액이 오름. 사용자 지시는 「옹벽도 수량이 변경되어야 함」이었고 이 변화는 옹벽 단 수와 무관함.
    • 두 창 모두 ⓐ 가 맞다고 보나 금액에 바로 닿아 사용자 확인이 필요함.
    • 되돌리려면 「단이 2개 이상일 때만 이어 붙이기」로 좁힐 수 있으나 그러면 화면과 면적이 다시 어긋남. 되돌릴 한 줄 자리는 Cross_Culvert_Geom.tsextendTrimSlope 위 주석에 박아 뒀음.
  • 아래는 고치기 전의 조사 기록 —

    • 실측(보조 창) — 같은 자료로 extra_wall_counts 만 2 → 0 으로 바꿔 structureAreaRows 를 두 번 돌리니 차이 0.0000. 270.43m 절토 1.0246/성토 7.5471, 85.18m 절토 5.1737/성토 0.7124.
    • 원인 — 면적은 designTrim 으로 잘린 폐회로에서 나오는데, 그 트림 경계는 Cross_Culvert_Geom.ts:632 에서 기준벽(유입·유출)의 성토 사면(fillSlopeOf)으로만 잡힘. 다단의 성토 구간(extras.segments)은 트림에 안 실림. 그래서 단이 몇 개든 폐회로가 같음.
    • 판단이 필요한 이유 — 다단이 서면 성토면이 계단으로 바뀌므로 실제 토량은 달라짐. 3-2 의 원칙(「구조물이 서면 폐회로가 달라진다」)대로면 면적도 바뀌어야 함. 다만 이 값은 B08 수량·B09 견적으로 그대로 나가는 물량이라, 임의로 바꾸면 견적이 조용히 달라짐. 사용자 확인 뒤 고칠 것.
    • 고칠 자리는 좁음 — 트림을 기준벽 사면 대신 다단이 있으면 그 구간을 이어 붙인 선으로 잡으면 됨(trimMaxSlope.pointsextras.segments 로 이어 만들기). 3D·그리기·수량이 같은 트림을 쓰므로 셋이 함께 움직임.

5. B07 — 상세설계(도면)

4-1. 도각 외부 파일 불러오기 — DWG 받기 + 자리표 배치 (2026-09-06 사용자 지시로 방식 변경)

지금은 도각을 캐드 화면에서 직접 고쳐 회사 도각으로 저장하는 길만 있음(B07_DesignDetail_UI_FrameEdit.ts → 회사 도각 storage/{회사}/templates/, 프로그램 기본 도각은 resources/template_2dDrawing/00_template_A1.json). 여기에 외부 파일 불러오기를 더함. 고객이 내는 도각은 DWG 일 확률이 높으므로 DWG 를 받아 변환해 들이고, 프로그램 값이 들어갈 자리는 자동으로 알아맞히지 않고 사용자가 아이콘·라벨을 놓아 지정함(2026-09-06 사용자 제안 — 자동 연계 규칙을 만들 필요가 없어 훨씬 단순함).

검토 결과 (2026-09-06) — 제안이 지금 구조와 정확히 맞음

  • 자리표 방식이 이미 서 있음. 기본 도각의 글자 24개 중 12개가 {{도면명}}·{{공사명}}·{{과업책임자}}·{{설계일자}}·{{용역회사}}·{{시행청}}·{{도면번호}}·{{분야별책임자}}·{{설계자}}·{{축척_A1}}·{{축척_A3}} 같은 자리표 토큰이고, 그림 4개는 {{회사로고}}·서명 자리임. 출력 때 B07_DesignDetail_Engine_Template.py 의 치환 한 곳이 값을 채움(_fill_placeholders). 사용자가 자리표를 놓기만 하면 연계는 이미 끝난 상태 — 새 연계 로직 불필요.
  • DXF 읽기 라이브러리가 이미 깔려 있음ezdxf 1.4.4. 기본 도각 JSON 자체가 source: 00_templete_A1.dxf 로 남아 있어 DXF → 도각 JSON 변환을 이미 한 번 거친 결과물임. 같은 변환을 코드로 세우면 됨.
  • DWG 는 그대로 못 읽음 — 비공개 형식이라 ezdxf 도 DXF 만 읽음. 무료 외부 변환기(ODA File Converter)를 서버에 설치해 DWG→DXF 로 바꾼 뒤 읽는 것이 유일한 길. 변환기가 없으면 「DXF 로 저장해 올려 주세요」 안내로 떨어짐(폴백).
  • 도각 JSON 의 요소는 Text · Line · PolyLine · Point · Image 5종. DXF 에서 이 5종만 추리면 됨.

할 일

  • 서버에 ODA File Converter 를 두고 DWG → DXF 변환을 붙임 — 없으면 DXF 만 받고 안내 문구를 띄우는 폴백.
  • DXF → 도각 JSON 변환Text · Line · PolyLine · Point 를 추리고 좌표·도면층을 그대로 옮김. 지원 밖 요소(치수·해치·블록)는 어떻게 처리할지 정함(선으로 풀지, 버릴지).
  • 불러온 도각을 회사 도각 저장 경로(storage/{회사}/templates/)에 얹는 흐름을 잇고, 저장 즉시 다음 도면부터 쓰이게 함(캐시가 수정 시각을 키로 쓰므로 자동).
  • 자리표 배치 도구 — 넣을 수 있는 값 목록(위 12종 + 로고·서명)을 아이콘·라벨로 보이고, 도면 위 원하는 자리에 끌어다 놓게 함. 놓은 뒤 위치·크기·정렬·글자크기를 조절.
  • 놓인 자리표는 화면에서 무슨 값인지 이름표로 보이고, 저장할 때 {{키}} 토큰으로 굳음 — 출력은 기존 치환 엔진이 그대로 처리.
  • 요소 선택 시 회색 외곽선 표시.
  • 자리표를 안 놓은 값은 빈칸으로 남김(지금 규칙 그대로 — 남의 값이 남지 않음).
  • 실화면 확인 — DWG 불러오기 → 자리표 배치 → 저장 → 다음 도면 출력에 값이 채워지는지.

자체검증 (2026-09-06, 보조 워크트리 8001·5174 공용 브라우저)

  • DXF 변환 — 견본 도각(테두리·표제란·글자·원·블록·해치)을 만들어 불러옴. 선·폴리선·글자·점은 그대로 들어오고, 원은 선분으로 펴지고, 블록은 낱개로 풀리고, 해치는 빠짐(의도대로).
  • DWG — LibreDWG(dwg2dxf) 로 확정 (2026-09-06 약관 확인 뒤 변경). ODA File Converter 는 비회원 무료 사용이 비상업 용도 한정이라 상용인 이 프로그램에는 못 씀(상업 사용은 ODA 회원 가입 = 연 수천만 원대). LibreDWG 는 GPL 이지만 별도 실행 파일로만 호출하므로 이 프로그램 코드에 의무가 번지지 않음. 읽는 범위는 R2018 까지이며, 그 이후 형식·변환 실패·변환기 미설치는 「2018 DWG 또는 DXF 로 저장해 달라」 안내로 떨어짐. 변환기 경로는 .envLIBREDWG_DWG2DXF_PATH (없으면 PATH 에서 찾음). 판정·안내 경로는 단위 테스트 4건으로 확인(tmp/tests/test_b07_frame_import_dwg.py) — 실제 DWG 변환은 변환기 설치 후 확인 필요.
  • 자리표 패널 — 캐드 안 오른쪽 아래에 뜸(2026-09-06 사용자 지시로 부모 사이드바에서 옮김). 도각 편집으로 도면을 실었을 때만 보이고, 단추 18개(글자 14 · 그림 4) 확인.
  • 배치 → 저장 — 「공사명」·「회사로고」를 놓고 [완료]. 회사 도각 파일에 {{공사명}} 글자와 {{회사로고}} 그림이 그대로 저장됨(엔티티 10개).
  • 도면 반영 — 종단면도를 다시 받아 보니 도각 글자가 검증용 외부도각 · 회사명 자리 · 카드미리보기_검증_sub 로, 자리표가 프로젝트명으로 채워짐. 남은 {{토큰}} 없음, 로고 그림 자리도 값으로 치환됨.
  • 그림 자리표는 값이 없으면 화면에 아무것도 안 보이던 문제를 고침 — 자리표일 때는 테두리와 이름을 늘 그림(출력물에는 안 실림).
  • 글자를 집으면 회색 점선 외곽선이 생기도록 고침.
  • 검증에 쓴 회사 도각은 원본으로 되돌려 놓음(검증 전 백업 → 복구 확인, 엔티티 51개).
  • 테스트 — pytest tmp/tests/ -q 186 passed, 1 skipped.

4-2. 도면 내보내기 (DXF·DWG) + LibreDWG 동봉 (2026-09-06 사용자 지시)

불러오기의 반대 방향. 캐드 「출력」 리본에 DXF 내보내기 · DWG 내보내기 를 더함. 파일은 서버가 만든다 — 캐드가 도면을 부모에 넘기면 부모가 서버에 맡기고 내려받는다.

  • 내보내기 엔진 — 도면 JSON 을 ezdxf 로 DXF 로 씀(선·폴리선·글자·점·원·호, 도면층 이름 유지). 그림(Image)은 DXF 가 외부 파일을 가리키는 방식이라 제외하고 몇 개를 뺐는지 알림.
  • DWG 내보내기 — LibreDWG dxf2dwg 를 별도 프로세스로 호출(.envLIBREDWG_DXF2DWG_PATH). 변환기가 없으면 「DXF 로 내려받으라」 안내.
  • 한글 파일 이름 — 헤더에 그대로 실으면 서버가 500 으로 떨어지던 것을 고침(영문 이름 + UTF-8 이름 함께 전달).
  • 도각·내보내기 엔드포인트를 B07_DesignDetail_Router_Frame.py 로 분리(700줄 제한).
  • LibreDWG 동봉 완료 (2026-09-06 사용자 확정 — 저장소에 같이 둠). 실제 파일은 메인 워크트리 B07_DesignDetail/openwebcad/tools/libredwg/ 에 두고 보조 워크트리는 같은 자리에 링크(junction) 로 잡음 — 33MB 를 한 벌만 둠. 담은 것은 dwg2dxf.exe·dxf2dwg.exe·libredwg-0.dll·부속 dll 3개 + GPL 전문(COPYING) + 출처·교체법 README. 파이썬 바인딩은 제외(import 하면 GPL 이 번짐). 변환기 찾는 순서는 .env → 동봉본 → PATH.
  • DWG 왕복 시 한글이 깨지던 것 고침 — 옛 DXF 는 한글을 유니코드 escape 로 적으므로 불러올 때 되돌리고, DWG 로 갈 DXF 는 R2004 로 만듦.

자체검증 (2026-09-06)

  • 종단면도(1)을 캐드 「출력 → DXF 내보내기」로 내려받음 — 335KB, 도형 1,833개(선 1,490 · 글자 305 · 원 29 · 점 8 · 호 1), 도면층 b08-ground·b08-design·b08-long-grid·b08-long-table·b08-frame 그대로. 표제란 값은 채워진 상태이고 {{토큰}} 남지 않음.
  • 화면 안내 — 「DXF 로 내보냈습니다. 그림 3개는 담기지 않았습니다.」(로고·서명 3개)
  • DWG 내보내기 — 변환기가 없어 400 과 함께 「DXF 로 내려받아 주십시오」 안내 확인.
  • DWG 실검증 (동봉 뒤) — 캐드 「출력 → DWG 내보내기」로 종단도(1)을 받음(106KB, 형식 2000). 그 파일을 다시 불러오니 선 1,490 · 글자 302 · 폴리선 30 · 점 8 로 들어오고 한글(곡선·측점·거리·추가거리·지반고)이 깨지지 않음.
  • 테스트 — pytest tmp/tests/ -q 191 passed, 1 skipped (DWG 왕복 검증 1건 추가).

4-3. 자리표 다듬기 — 칸 크기·그림 비율·값 미리보기 (2026-09-06 사용자 지시)

  • 글자 자리표에 칸(box) 을 둠 — 칸 한가운데에 가로·세로 가운데 맞춤으로 글자가 섬. 고른 자리표의 칸 크기를 캐드 패널에서 가로·세로(mm) 로 고침.
  • 그림 자리표는 칸을 늘려도 비율을 지켜 안에 들어감(남는 자리는 비움) — 로고·서명이 찌그러지지 않음.
  • 도각 편집 중에는 자리표에 실제 값을 보여 줌(공사명·용역회사·담당자·로고·서명). 저장값은 {{키}} 토큰 그대로 — 값이 아니라 연결을 저장한다.
  • 내보낸 DXF 에서도 칸이 있는 글자는 가운데 맞춤으로 나감.

자체검증 (2026-09-06)

  • 서버가 편집용 값을 내려주는 것 확인 — 공사명=카드미리보기_검증_sub · 용역회사=캐미팩토리 · 설계자=엄상돈 · 회사로고/서명=그림 자료.
  • 화면 — 「공사명」 자리표에 카드미리보기_검증_sub 가 그대로 보임(토큰이 아니라 값), 칸 안 가운데 정렬. 칸 크기를 60×10 → 120×24 → 200×20 으로 바꿔 즉시 반영됨.
  • 그림 — 「회사로고」 자리표를 32×16 에서 160×20 으로 늘려도 로고는 작게 가운데 그대로(비율 유지) 확인.
  • 편집을 [취소] 로 빠져나온 뒤 회사 도각 파일은 손대지 않은 상태(엔티티 51개, 토큰 유지) 확인.
  • 테스트 — pytest tmp/tests/ -q 191 passed, 1 skipped.

4-4. 자리표 후속 — 마우스 크기조절·기본 도각 정렬·도면명 미리보기 (2026-09-06 사용자 지시)

  • 마우스로 칸 크기 조절 — 칸이 있는 글자와 그림에 네 모서리 손잡이를 붙이고, 모서리를 끌면 칸이 따라 커지고 작아짐. 캐드 본래의 「집었다 놓기」 그립은 도각 칸 맞추기에 손이 많이 가서 끌기를 따로 받음. 다른 도면 요소(그림)도 같은 조작을 씀.
  • 기본 도각(AISLO 정본)도 칸 기준 정렬 — 표제란 선에서 칸을 계산해 글자 24개 전부에 칸을 부여하고, 글자를 칸 한가운데(가로·세로)로 옮김.
  • 도면명·도면번호 미리보기 — 서버가 담지 않는 값(도면마다 다름)이라 편집 직전에 보던 도면의 이름·번호를 견본으로 넘김.
  • 복제(그립 편집)해도 미리보기 값이 유지되게 고침 — 예전에는 모서리를 한 번 만지면 자리표가 다시 {{도면명}} 으로 보였음.
  • 캐드 index.html 을 캐시하지 않게 함 — 빌드해도 브라우저가 옛 화면을 들고 있던 문제(검증 중 두 번 겪음).

자체검증 (2026-09-06)

  • 자리표 「도면명」을 놓으니 종단도(1) 로 보임(토큰이 아니라 실제 도면명), 칸 안 가운데 정렬.
  • 모서리 손잡이 5개(네 모서리 + 가운데)가 화면에 뜨는 것을 픽셀로 확인.
  • 오른쪽 위 모서리를 끌어 60×10 → 90.4×20.3 mm 로 바뀌고 패널 숫자도 따라 움직임.
  • 편집을 [취소] 로 나온 뒤 회사 도각 파일은 그대로(엔티티 51개).
  • 테스트 — pytest tmp/tests/ -q 191 passed, 1 skipped.

6. B03·B04 — 입력·전처리

5-1. 라이다 다중 파일 입력·병합 (2026-09-06 사용자 확정)

지금 상태 — 지형 파일은 한 장만 받고(정확히 1개 검사), 전처리는 그 한 장의 점을 통째로 메모리에 올림(B04_PreProcess_Engine_Structurize.py). 실측 환산(용화 1.6GB = 4,900만점, 85점/㎡)으로 30GB 2장이면 9.2억점·메모리 29GB, 50GB 3장이면 15억점·49GB — 병합 이전에 한 장 30GB 부터 못 돌림.

방침 — 원본은 전부 받아 그대로 두고 병합해 전처리함. 노선 버퍼 클리핑은 하지 않음(배수유역이 노선 밖 집수구역을 봐야 함). 대신 좌표가 서로 멀리 떨어진 파일은 업로드에서 막음 — 다른 사업지 파일이 섞여 들어오면 합친 범위가 통째로 어긋남.

방법 — 읽어 들이는 입구에서 격자 대표점(0.5m 칸 최저점)만 남겨 여러 장을 한 벌로 합침. 설계가 쓰는 격자가 1m(지면필터 2m·CSF 천 1.5m)라 0.5m 는 설계보다 촘촘해 결과 표고는 사실상 같음. 뒤 단계(지면필터·모델·등고선·배수)는 받는 형식이 그대로라 손대지 않음. 점이 임계(1억점) 아래면 지금처럼 전부 그대로 씀 — 작은 자료의 결과는 바뀌지 않음.

  • 지형 파일 개수 제한을 풂 — 정확히 1개1장 이상, 여러 장을 한 카드에 담음.
  • 업로드 때 각 파일 머리글의 범위만 읽어 서로 5km 넘게 떨어지면 거부하고 어느 파일이 떨어져 있는지 알림 (임도는 길어도 2~3km — 2026-09-06 사용자 확정).
  • 구조화 엔진을 여러 파일 병합으로 바꿈 — 합친 범위·합친 점, 겹치는 구간은 자연히 하나로 남음.
  • 점이 임계(1억점)를 넘으면 씨닝을 켬 — 지면 분류점은 전부 남기고 나머지만 0.5m 칸 최저점으로 줄임. 칸 크기·임계는 설정값(SURFACE_THIN_*).
  • WF1 자동 전처리와 B04 재분석이 프로젝트 지형 파일 전부를 대상으로 돌게 연결함.
  • B03 화면에서 지형 카드에 담긴 파일 목록이 보이게 함 (「용화_서편.las 외 1장」).

자체검증 (2026-09-06, 보조 워크트리 8001·5174 공용 브라우저)

용화 원본(4,900만점)을 좌·우 도엽으로 쪼갠 두 장(724MB + 865MB)을 실제 화면에서 올려 자동설계까지 완주시키고, 기존 한 장짜리 프로젝트와 수치를 맞대 봄. 씨닝은 임계를 낮춰(설정값) 강제로 켠 상태로 돌림.

  • 지면점 완전 보존 — 저장 점 수는 49,006,663 → 2,491,089(5.1%)로 줄었으나 지면 분류점은 1,140,716개로 한 점도 줄지 않음.
  • DTM 동일 — 스무딩 전 격자 645×896의 표고가 모든 칸에서 차이 0.0000m. 유효 칸은 232,271 → 232,361로 가장자리 90칸이 오히려 넓어짐. 스무딩 격자만 평균 9mm 차이(99%가 5.7cm 이내, 0.1m 초과 315칸) — 넓어진 가장자리가 전역 스플라인에 퍼진 결과.
  • 뒷단 결과 동일 — 노선 1,097.4m(기존 1,097m), 배수관 11개, 세부유역 11개, 자동설계 체인 「B05·B06 기본값 확정」까지 완료.
  • 먼 파일 차단 — 60km 옮긴 파일을 섞으니 업로드가 400으로 막히고 화면에 「지형 파일 「다른사업지.las」의 좌표가 다른 파일과 … 떨어져 있습니다」가 뜸. 파일은 저장되지 않고 되돌려짐.
  • 시험 tmp/tests/test_b04_structurize_merge.py 9건 — 병합·씨닝 최저점·지면점 보존·5km 경계(3km 통과·6km 거부).

5-2. 700줄 제한 분리 — 남은 확인

15개 분리와 초과 파일 0건 결과는 docs/raw/plans/2026-09-04_plan_700line_split.md에 보존했다.

  • B04 배수유역 라우터 쓰기 경로는 다음 재분석 시 분리 전과 결과가 같은지 확인 (2026-09-07) — 같음. 분리 전(2026-08-30 저장분)과 분리 후(오늘 재분석)의 00_watershed_response.json 을 대조: 키 22개 그대로, 빠짐·늘어남 없음, schema_version 3 동일, grid·flow·expansion 하위 키도 그대로. 단계 산출물 7개(1차 영역 geojson·npz, 흐름방향 npz, 도로귀속 geojson·npz, 세부유역 geojson, manifest)도 모두 새로 쓰임. 분리 커밋은 07e6441d(2026-09-04).

5-3. LAS 없이 설계 — PRJ·TFW 필수 검사 오류 (2026-09-06 발견)

증상 — 「LAS 없이 설계」를 켜면 화면은 지형 자료(포인트클라우드·좌표계·래스터)를 필수에서 빼 업로드 버튼이 열리는데, 서버가 prj·tfw 를 무조건 필수로 봐 업로드가 막힘(실행 확인: las_free 상태에서 노선만 올리면 필수 입력 파일이 없습니다: prj, tfw).

원인 — ① 서버의 필수 목록(_REQUIRED_FILE_TYPES)에 「LAS 없이 설계」 예외가 없음 ② 검사 자리가 둘인데 PRJ 를 세는 법이 다름 — 업로드 요청 때는 확장자만 봐서 노선 PRJ 를 지형 PRJ 로 오인하고, 저장 뒤 완료 검사 때는 노선/지형을 갈라 셈. 같은 파일이 한 번은 통과, 한 번은 누락으로 갈림.

  • LAS 없이 설계면 지형 한 벌(포인트클라우드·좌표계·래스터)을 필수에서 면제함.
  • 업로드 요청 검사도 완료 검사와 같은 기준으로 노선 PRJ 와 지형 PRJ 를 갈라 세게 통일함.
  • 업로드 현황의 「필수 충족」 판정이 LAS 없이 설계에서도 맞게 나오게 함 — stage 0 을 마쳤으면 충족으로 봄.

자체검증 (2026-09-06)

  • 공용 브라우저에서 「LAS 없이 설계」를 켜고 노선 shapefile 한 벌(shp·shx·dbf·cpg·prj)만 올려 업로드 통과, 도엽등고선 서피스 생성으로 진입해 지표면 모델 자동 확정까지 완료. 고치기 전에는 같은 자리에서 필수 입력 파일이 없습니다: prj, tfw 로 막혔음.
  • 좌표계는 노선 PRJ 로 떨어지는 것을 확인(지형 PRJ 가 없을 때 find_project_prj 가 노선 세트 PRJ 를 집음).
  • 시험 tmp/tests/test_b03_las_free_required.py 5건 — 노선만/CSV만 통과, 노선 PRJ 가 지형 PRJ 를 대신하지 못함, 일반 업로드는 종전대로 지형 한 벌 요구.

7. 자체검증 기록

절마다 붙은 검증 외에, 계획서 「참고」에 따로 서 있던 실측 기록들.

자체검증 기록 — 2026-09-06 (공용 브라우저 실측)

프로젝트 5cff3920… 노선 143(측점 66곳, 구조물 측점 11곳)에서 확인. 저장은 요청을 가로채 실제로 쓰지 않고 payload 만 봤다(사용자 미저장 편집이 남아 있어서).

무엇 방법 결과
진입 요청 수 리로드하며 /api/ 요청 집계 B05 첫 진입 20 → 18건(그중 지형·지도 자료 8건, 핵심 API 10건). B06→B05 이동 7건 — 목표(10건 이하) 충족
중복 호출 같은 집계 auth/session 4 → 1, workflow-state 2 → 1, 구조물 이관 POST 매 진입 → 1회만, sections/context·지표면 목록 → 사라짐(세션 캐시)
카드 버튼이 서버에 안 쓰는가 B06 첫 카드 「토사」 클릭 후 요청 감시 API 요청 0건. 버튼은 즉시 활성, 세션 초안 aislo:draft:crossdesign:{프로젝트}:143{"0.00":{ground_type:"soil",…}} 기록. 원래 값(리핑암)으로 되돌리고 초안도 지움
[저장]이 무엇을 보내는가 sections/143/save 가로채 payload 검사 측점 66곳 전부 patch, 그중 66곳에 면적 4종(cut/fill/cut_soil/cut_rock), 유토곡선 포함, 사용자 조작(revet_adjust)도 함께. 예: 85.59m — 절토 3.912㎡ · 성토 4.8478㎡ · 토사 1.0144㎡ · 암 2.8975㎡
저장 뒤 화면 가로챈 뒤 상태 확인 잠금 오버레이 없음, 초안(revetx·std-cross) 그대로 남음 — 실패해도 조작이 사라지지 않음

자체검증 — 신규 프로젝트(용화) 2026-09-06

기존 프로젝트로는 확폭을 볼 수 없어(저장본이 기능 이전 것) 새 프로젝트를 만들어 용화 자료(LAS 1.55GB + 지형 PRJ/TFW/TIF + 계획노선 shapefile)를 화면에서 올리고 자동설계 체인을 끝까지 돌렸다. 프로젝트 5601e828…, 노선 145, 연장 1,097m, 측점 67곳.

무엇 결과
초기값 저장(체인) 서버 로그 서버 재계산: route_id=145 설계 보정 0곳, 구조물 면적 15곳, 유토곡선 갱신브라우저를 한 번도 안 열고 값이 저장됨. 코리도 16.1MB 도 사전 생성. initial_snapshot/ 생성 확인
측점 면적 67곳 전부 면적 있음(빈 곳 0)
곡선부 확폭 31곳에 확폭. 표(별표2)와 대조 — R43.0→0.25 · R29.1→0.75 · R24.9→1.00 · R21.5→1.00 · R19.8→1.25 · R16.1→1.50 · R14.7→1.75 · R13.4→2.00. 67곳 중 64곳이 표값과 정확히 일치
확폭 테이퍼 나머지 3곳이 테이퍼로만 생긴 값 — 634.0m 0.400 · 731.3m 0.066 · 1002.2m 0.194 (그 측점 반경으로는 0). 곡선 앞뒤 10m 안에 든 구조물 측점들
확폭 상한 R12.2 측점은 표값 2.25 이지만 유효너비 5m 상한에 걸려 2.00 적용 — 화면 라벨 「노폭 5.00m (규격 3.00 + 확폭 2.00)」
화면 라벨 67개 카드 전부 노폭 라벨. 3.25 / 3.75 / 4.75 / 4.50 / 4.25 / 4.00 / 3.50 / 5.00 / 3.40 / 3.07 / 3.19
유토곡선 화면값 절토(자연) 5,116.3㎥ · 성토 15,397.4㎥ = 저장값(2,251.30+2,865.04=5,116.34 / 15,397.44) 일치
카드 버튼 「발파암」 클릭 → 서버 요청 0건, 세션 초안에 기록 → [저장] → DB ground_type=blasting_rock + 면적이 브라우저 계산값으로 갱신

이 과정에서 잡은 버그 — 포장·세월교 보정 헬퍼를 저장 시점으로 옮기며 @router.post(.../save) 데코레이터와 save_sections 사이에 끼워 넣어, 라우트가 헬퍼(None 반환)에 붙어 임시저장이 500 으로 떨어지고 있었음. 헬퍼를 데코레이터 앞으로 옮겨 수정(커밋 1cf47f39). 화면 검증이 아니었으면 못 잡았을 자리.

아직 못 본 것 — 구조물 측점 겹침 제거. 이 프로젝트도 구조물 측점과 20m 격자의 최소 거리가 1.08m 라 0.1m 겹침이 없음. 단위 테스트로만 확인.

화면으로 확인 못 한 것 (이유 있음)

  • 구조물 측점 겹침 제거 — 이 프로젝트에서 구조물 측점과 20m 격자의 최소 거리가 0.94m 라 0.1m 겹침이 아예 없음. 로직은 단위 테스트(tmp/tests/test_b05_station_merge.mjs)로 확인.
  • 곡선부 확폭·테이퍼 — 이 프로젝트 저장본이 확폭 기능 이전에 만들어져 측점에 plan_radius_m·curve_widening_m 이 아예 없음(0곳). 값은 횡단을 다시 생성해야 생기는데 그 조작은 사용자 설계를 지우므로 하지 않음. 산식은 단위 테스트 7건으로 확인. 새 프로젝트에서 확인 필요.