Commit Graph
241 Commits
Author SHA1 Message Date
eomsangdonandClaude Opus 5 1e596846cd feat(B06): 절토 경사 판정 암질을 설계자가 고르게 — 기본 경암
사용자 확정(2026-09-07): 「연암과 경암도 별도의 버튼으로 선택하게 하고 경암을 기본값으로
선택. 리핑을 할지 발파를 할지는 설계자가 선택 필요함.」

**축이 둘이었음 — 섞지 말 것.**
· **암질**(연암·경암) = 별표2 경사 판정의 기준. 설계자가 고름. 기본 **경암**.
· **굴착 공법**(리핑암·발파암, `cut_rock_kind`) = 어떻게 파는가. 수량·단가 몫.
처음에는 공법에서 암질을 유추하려 했는데 **그것이 잘못 세운 문제**였음. 매핑을 걷어내고
암질 선택 하나로 바꿈 — 표준 횡단면 설정 안, 자리는 그대로.

- config 매핑 상수 → `FOREST_ROAD_CUT_SLOPE_ROCK_QUALITY_DEFAULT = "hard_rock"`.
- 판정에서 `cut_rock_kind` 를 뗌. 수량 쪽 쓰임은 그대로 둠.
- 실측(용화 63측점·구간 124개) — 기본값 경암에서 **위반 0건**. config 기본 절토비 1:0.4 가
  경암 범위(0.3~0.8) 안임. 토사 63구간도 0건. **기존 설계·수량 안 바뀜.**
  (연암으로 고르면 61건 — 1:0.4 가 연암 하한 0.5 밖이라 그때는 설계 검토가 필요함.)

시험 8건 — 별표2 값 · 작업임도 제외 · **암질은 설계자가 고르고 기본은 경암** ·
**공법으로 암질을 추론하지 않음** · 구간별 판정 · 소단이 있어도 위반이 안 사라짐 ·
경계값 통과 · 카드에 같은 방식으로 붙는지.

7-3 회귀에 소단 한 줄 더함 — 「소단은 값이 아니라 **기하 입력**이라 계산 뒤에 베껴 붙이면
`berm` 값만 남고 계단이 안 그려진다」. 저장분 소단을 **계산 전에** 읽어 넣는 순서를 지킴.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 16:42:32 +09:00
eomsangdonandClaude Opus 5 a066023da9 merge: sub_laptop_1 되받기 — 저장 흐름 import 충돌 해소
`B06_Section_UI_Page_Persist.ts` 의 import 한 줄만 갈렸음. 양쪽 다 쓰는 이름이라 합침 —
`readByKey`/`writeByKey`(저장소 선택을 한 창구로 모은 것)와 `writeState`(소단 배선)가
같은 파일에서 각각 한 번씩 쓰임. 다른 충돌 없음.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 16:26:57 +09:00
eomsangdonandClaude Opus 5 4e7a4bf787 fix(횡단): 사면 구간 재료를 **그린 경사비**로 가름 — 「토사 경사인데 암」 라벨 없앰
다른 창이 전 측점을 돌려 찾은 것(용화 5601e828 · route 169, 63측점 124구간).
`material: "rock"` 인데 경사비가 1.0(토사)인 구간이 **13개** 나왔음.

까닭 — 재료를 **암반 경계선을 다시 재서** 붙였음. 그런데 무릎을 지난 뒤에도 경계선은
지반을 따라 계속 오르므로, 토사 경사로 그린 구간이 경계 아래로 **되돌아가 있는** 일이
흔함. 그것을 경계로 재면 그린 것과 라벨이 어긋남.

고침 — 재료는 **그 구간을 실제로 그린 경사비**를 따라 붙임(암 경사비에 가까우면 암,
토사 경사비에 가까우면 토사). 두 경사비가 같으면(2단 절토 아님) None.

영향 — 별표2 경사 판정이 갈리던 자리임. 13개가 「암인데 너무 완만」 위반으로 잘못 뜰 수
있었음. 토사 구간 50개는 종전대로 위반 0.

자체검증 — 새 시험 1건: 지반이 가파른 암 지반에서 모든 구간의 재료가 그 경사비와 맞는지
확인(어긋나면 실패). 거울 시험도 통과. 전체 542 passed · 18 skipped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 16:25:46 +09:00
eomsangdonandClaude Opus 5 973128f798 feat(B06): 절토 비탈 법정 기울기 검사 — 별표2 범위 밖이면 카드에 경고
법정 위반 표시가 없던 유일한 자리였음(평면 곡선반경·성토사면 길이는 이미 있음).
절토 경사비는 표준 횡단면 설정에서 오는 입력값일 뿐이라 범위를 벗어나도 아무 표시가 없었음.

- 기준 — 별표2(지식DB `01_임도/02_상세설계/절토_비탈면.md` §1): 경암 1:0.3~0.8 ·
  연암 1:0.5~1.2 · 토사 1:0.8~1.5. **작업임도는 규정 없음 → 검사 제외.**
- 판정은 **구간별 경사**(`cut_slope_segments`)로 함. 소단이 서면 실효 경사가 완만해져
  **위반이 사라진 것처럼** 보임(폭 1.0·간격 2 이면 1:1 이 1:1.71). 그 함정을 시험으로 못박음.
- 표시는 **기존 방식 그대로** — 성토사면·미폐합 경고와 같은 자리·같은 클래스. 새 방식 안 만듦.
- 지반유형(리핑암·발파암) → 별표2 줄 매핑은 법령 근거가 아니라 **프로그램 설정**이라
  표준 횡단면 설정에서 **사용자가 고르게** 함(2026-09-07 사용자 확정). 기본값 리핑암 → 연암 ·
  발파암 → 경암. 표준단면과 함께 저장돼 이미 사용자 값 계통임.
- 기준값은 서버가 컨텍스트로 내려보냄 — 화면에 상수를 복제하지 않음.

시험 7건 — 별표2 값 · 작업임도 제외 · 매핑이 기본값일 뿐 · 구간별 판정 ·
**소단이 있어도 위반이 안 사라짐** · 경계값 통과 · 카드에 같은 방식으로 붙는지.

⚠ 실화면 — 사용자 선택 칸은 섰음(리핑암 연암 · 발파암 경암). 다만 이 프로젝트 저장분
63측점에 `cut_slope_segments` 가 아직 없어(오늘 새로 생긴 키) 경고가 뜨는 것은 못 봄.
재계산이 한 번 돌면 채워짐 — 지금 경고 0건은 맞는 동작임.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 16:20:42 +09:00
eomsangdonandClaude Opus 5 819aa643dc feat(횡단): 좌측 [소단] 패널 — 구간에 놓고 빼기 + 확정 뒤에도 남게 함 (계획서 3-9)
사용자가 소단을 **직접 놓는** 화면을 만들었음. 프로그램이 「붕괴 우려 지역」을 판정하지
않고, 법정 기준 셋 중 무엇도 자동 적용하지 않음(2026-09-07 사용자 확정).

폼은 C군 구간형 구조물과 **같은 꼴** — 기준 측점 + 전·후 거리로 종단 범위를 잡고
폭·간격·기울기를 함께 받음. 기본값 폭 0.5m · 간격(사면길이) 3.0m · 안쪽 2°.
새 모듈 `B06_Section_UI_Berm_Panel.ts` 로 뺐음 — 좌측 페이지가 이미 700줄을 넘어 있어
거기 더 넣지 않고 두 줄만 꽂음(그 파일 분리는 별건).

세션에는 **구간 목록**으로 둠. 측점별로 펴서 저장하면 「어디부터 어디까지 놓았나」를
되짚을 수 없음. 읽는 자리에서 펴고, 서버에는 측점키 dict 로 실어 보냄.

**확정 뒤에도 계단이 남게** — 소단 제원을 설계 결과에 되싣고(`berm`),
`USER_TOUCHED_KEYS` 에 넣어 재계산이 지우지 않게 함. 서버 재계산은 세션값이 없으면
저장분을 씀(`stored_berm`).

⚠ 소단은 사용자 조작값이면서 **기하 입력**이라 다른 사용자 키와 다름 — 계산 뒤에 키만
베껴 붙이면 설계선·면적은 계단 없이 나오고 `berm` 값만 남아 서로 어긋남. 그래서 단측점
설계 경로는 저장분을 **계산 전에** 읽도록 순서를 바꿨고, 포장 강제·세월교 노면 하강
재계산 경로에도 저장분 소단을 실었음. `extra_spans` 가 그렇게 빠져 있던 자리와 같음.

자체검증 — 새 시험 2건(저장분만으로 같은 설계가 나오는지 · 값이 없거나 손상되면 None).
전체 541 passed · 18 skipped. TS 타입 검사·ruff 통과.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 16:19:32 +09:00
eomsangdon d48302898f Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1 2026-09-07 16:09:54 +09:00
eomsangdonandClaude Opus 5 061d550e55 feat(횡단): 소단을 미리보기·재계산에 실어 화면에 계단이 서게 함 (계획서 3-9)
계단이 값에만 있던 것을 화면까지 연결함.

배선 — 세션 열쇠 `berm`(측점키 → 폭·간격·기울기)을 등록표에 두고,
`readBermSession` 으로 읽어 ① 브라우저 재계산(`refreshCrossDesigns`)과
② 서버 미리보기(`cross-design/preview` 의 `berms`) 양쪽에 실음.
암 경계선 오프셋과 같은 길이라 「계획선을 고치면 계단이 사라지는」 일이 없음.

실화면 확인(8001·5174, `/api/health` `stale:false`) — 측점 4120.0m 에 소단을 놓고
계획고를 한 칸 올렸다 내려 전 구간 재계산을 태움.
· 폭 0.5m · 간격 3.0m → 절토 6.84 → 8.10㎡ (토사 2.42→3.24 · 암 4.42→4.86)
· 폭 1.0m · 간격 2.0m → 절토 6.84 → 12.96㎡, 횡단도에 **계단이 눈으로 보임**
· 소단을 안 놓은 옆 측점(4100.0m)은 3.77㎡ 그대로 — 놓은 곳만 달라짐
· 되돌린 뒤 6.84㎡ 로 복귀. [저장]·[확정] 안 눌렀으므로 정본은 그대로.

`cut_slope_segments` 신설 — 절토 사면을 경사 구간별로 쪼갠 목록(파이썬·TS 짝).
법정 경사 검사가 읽을 값임(다른 창 요청). 소단이 서면 사면 전체를 하나로 재는
「실효 경사」가 완만해져 **위반이 사라진 것처럼** 보이므로(폭 1.0·간격 2 이면 설계 1:1 이
실효 1:1.71), 검사는 소단을 뺀 구간 자체를 봐야 함. 실측 — 소단을 놓아도 구간별 경사비는
1.0 그대로 나옴. 평탄부(소단)와 지반 만난 뒤 구간은 싣지 않음.

자체검증 — 거울 시험에 사면 구간 대조를 더해 파이썬·TS 일치 확인.
전체 539 passed · 18 skipped. TS 타입 검사 통과.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 15:28:16 +09:00
eomsangdonandClaude Opus 5 5a6617f278 fix(횡단): 소단이 암 경계 아래로 되돌아가면 다시 암 경사로 그림 (계획서 3-9)
배분 창 지적으로 확인한 자리 — 미룰 수 없는 쪽이었음.

무엇이 문제였나 — 소단은 평탄한데 암 경계선은 지반을 따라 올라감. 지반이 1:1 이면
폭 0.5m 소단 하나를 지나는 동안 경계는 0.5m 오르고 소단은 2°(0.017m)만 오름. 그래서
설계선이 경계 아래로 **되돌아 들어가는 일이 흔함**. 무릎을 한 번만 꺾는 종전 규칙으로는
그 구간을 **암인데 토사 경사로** 그려 절토가 조용히 커짐 — 경고도 안 뜸.

고침 — `multi_knee` 를 둬서 경계를 오갈 때마다 꺾게 함. **소단이 있을 때만** 켬.
소단이 없을 때 켜면 지금 측점들의 설계가 같이 바뀌기 때문임(실측: 물결 경계 0.0016m).
기본값은 거짓이라 소단을 안 쓰는 프로젝트는 그대로임.

자체검증
- 새 시험 1건 — 경계를 뚫고 올라갔다가 소단마다 뒤처져 되돌아 들어가는 지형에서
  한 번만 꺾는 결과와 갈리고, 되돌아 들어간 구간이 암 경사(가파름)라 같은 거리에서
  더 높이 올라가는 것을 확인.
- 거울 시험에 되돌아 들어가는 경우를 한 줄 더해 파이썬·TS 일치 확인(12건).
- 전체 539 passed · 18 skipped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 14:58:18 +09:00
eomsangdonandClaude Opus 5 e43ad18f97 feat(횡단): 절토 사면에 소단(계단) 기하를 넣음 — 파이썬·TS 짝 (계획서 3-9)
사용자 확정(2026-09-07)에 따라 소단을 **자동 적용하지 않고 사용자가 놓는 것**으로 만듦.
이 커밋은 그 기하 한 벌이고, 화면 폼은 다음 단계임.

새 짝 모듈 `common_util_cross_berm.py` · `.ts` — 절토 사면 꼭짓점을 만듦.
암 경계 무릎과 소단이 한 목록에 함께 들어가고, `breakpoints` 가 그 꼭짓점을 설계선에
실어 도면·면적·유토곡선·3D 가 계단을 그대로 봄.

기본값 폭 0.5m · 간격(사면길이) 3.0m · 안쪽 기울기 2°.
· 폭·간격은 별표2 범위(사면길이 2~3m마다 · 폭 50~100㎝) 안에서 가장 적게 파는 조합임.
  기본값은 되돌리기 쉬운 쪽이어야 함 — 더 넣는 것은 폼에서 한 번이지만 이미 판 것을
  되돌리면 전 측점을 다시 계산해야 함. 실효 경사로도 그러함(경사 1:1 기준)
  폭 0.5·간격 3 → 1:1.24 / 폭 1.0·간격 3 → 1:1.47 / 폭 1.0·간격 2 → 1:1.71.
· 기울기 2°는 사용자 확정값이고 법령·교본 근거가 없어 지식DB 에 적지 않음(사용자 지시).

무릎은 종전대로 **한 번만** 꺾음. 여러 번 꺾게 풀면 소단이 없는 지금 측점 설계도 같이
바뀌므로 별건으로 미룸(실측: 물결 경계에서 0.0016m 차이, 경계를 다시 만나면 구조가 달라짐).

자체검증
- 거울 시험 10건 `tmp/tests/test_cross_berm_mirror.py` — 소단 켠 5경우 포함해 파이썬·TS
  꼭짓점이 1e-9 안에서 일치. 간격이 수평이 아니라 사면길이 기준인 것, 평탄부가 2° 기운 것,
  소단이 없으면 옛 무릎 방식과 완전히 같은 것까지 확인.
- 면적 시험 6건 `tmp/tests/test_cross_berm_area.py` — 절토 55.115 → 78.785㎡(기본값),
  폭·간격이 물량에 단조로 반영, 소단 모서리가 설계선에 실림, 소단을 안 주면 설계선까지 동일.
- ⚠ 어림식 두 번을 시험이 잡아냄. 「늘어난 면적 = 폭 × 그 위 높이」는 틀림(11.797 vs
  7.770㎡) — 설계선이 밀리면 지반과 만나는 점도 함께 밀려 절토가 더 길어짐. 「폭만큼 밀림」도
  틀림 — 참값은 「그 아래 소단 개수 × 폭」임. 시험은 그 참값으로 잼.
- 기존 거울 시험(`test_b06_cross_design_mirror.py`) 10건 그대로 통과 — 리팩터가 값을
  안 바꿨다는 증거임. 전체 537 passed · 18 skipped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 14:54:18 +09:00
eomsangdonandClaude Opus 5 8a0a8f18e3 feat(화면): 화면 배치를 계정에 저장 — PC 를 바꿔도 따라오게
사용자 승인(2026-09-07). 앞서 취향을 localStorage 로 옮겨 **탭·재시작** 문제는 풀었고,
이 변경은 그 위에 **다른 PC 에서도 같은 배치**를 얹음. 사용자가 노트북·데스크톱 두 대를 오감.

- `db_management/018_user_ui_prefs.sql` — 사용자당 한 줄, `prefs` JSON 한 칸.
  칸을 나누면 취향이 늘 때마다 마이그레이션이 또 필요해 한 칸에 담음.
- `GET/PUT /api/dashboard/me/ui-prefs` — 배치 값만. **설계값은 안 담음**(문자열만 받음).
- 로그인이 확인된 첫 순간에 한 번 받아 로컬 위에 얹고, 취향이 바뀌면 1.5초 모아 올림.
  서버가 없거나 못 읽으면 **로컬 값으로 그대로 돔**(계획서 원문).

⚠ 같은 함정을 또 밟을 뻔했음 — 키만 아는 자리가 저장소를 직접 고르면 **올려보내기도 안 걸림**.
그래서 `storageOf` 를 없애고 **읽기·쓰기 창구 하나**(`readByKey`/`writeByKey`)로 모음.
저장소 선택과 서버 올려보내기가 그 한 곳에만 있음. 그물도 그 이름으로 갱신.

**DB 는 아직 적용하지 않았음** — 표가 없으면 API 가 실패하고 화면은 로컬 값으로 도는 것이
정상 동작임. 적용 시점은 다른 창들과 맞춘 뒤.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 14:50:31 +09:00
eomsangdonandClaude Opus 5 159e2a5226 refactor(B06): 등록표 키를 쓰는 자리가 저장소를 직접 고르지 않게
오늘 같은 계통이 두 번 났음 — 키는 등록표에서 받는데 **저장소는 제 맘대로** 고르는 자리.
· `rockb` — 키를 날문자열로 만들어 읽는 쪽이 늘 빈 값(`f8fafd23`)
· 화면 취향 — 키는 맞았는데 저장소를 직접 골라 **절반만** 옮겨짐(`9db84d9d`)

남은 두 자리(암 경계선 저장·표시 반폭)도 `storageOf` 를 거치게 함. 지금은 둘 다 초안이라
세션이 맞지만, 통이 바뀌면 조용히 갈릴 자리였음.

그물도 넓힘(`test_session_keys_registered.py`) — 등록표 키를 쓰는 파일이 저장소를 이름으로
직접 고르면 깨짐. 고르는 곳은 `b_page_state` 하나. 앱 전역 값(`CURRENT_PROJECT_ID_KEY`)은
등록표 밖이라 제외하고 이유를 적어 둠. **훑어 보니 남은 위반 0곳.**

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 14:41:38 +09:00
eomsangdonandClaude Opus 5 72e50b3edd fix(B06): 저장된 표준 횡단면이 새 탭에서 화면에 안 서던 것
표준단면 편집값은 sessionStorage 에만 살아 **탭을 새로 열면** 사라졌음. 그때 브라우저는
config 기본값으로, 서버는 저장분으로 계산해 **같은 측점이 갈렸음**. 표준단면은 모든 측점의
횡단 모양을 정하므로 면적·유토곡선·수량까지 그대로 흐름.

- `sections/context` 가 저장분을 **한 칸 더** 실어 보냄(`stored_standard_cross_section`).
  기존 `standard_cross_section`(config 기본값)은 그대로 둠 — 옛 화면 안 깨짐, 마이그레이션 없음.
- 브라우저는 두 화면이 함께 지나는 한 자리(`fetchSectionContext`)에서 **세션이 비었을 때만**
  그 값으로 세움. 그 탭에서 고친 값이 있으면 안 건드림(초안 우선).

실측(용화 5601e828 · route 169):
- 고치기 전 — 저장분 암반 횡단경사 **5%** · 측구 상단폭 **0.9m** 인데
  화면이 받는 값은 **3%** · **0.69m**(config 기본값)였음. 응답에 저장분 칸 자체가 없었음.
- 고친 뒤 — 응답에 0.9·5 가 실리고, **빈 새 탭**에서 B06 을 열면 세션이
  `rock.ditch.top_width_m=0.9` · `rock.cross_slope_pct.max=5` 로 섬(검증 탭은 닫음).

시험 `test_b06_stored_standard_reaches_browser.py` 3건 — 칸이 따로 있고 기본은 None ·
라우터가 저장분을 실음 · 브라우저가 세션이 빈 경우에만 세움(두 갈래 모두).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 11:47:27 +09:00
eomsangdonandClaude Opus 5 f8fafd2300 fix(B06): 암 경계선 오프셋을 읽는 쪽이 옛 키를 보던 것
쓰는 쪽은 `stateKey("rockb", …)` = `aislo:draft:rockb:{p}:{r}` 에 쓰는데, 읽는 쪽
`readRockBoundarySession` 이 옛 키 `b06:rockb:{p}:{r}` 를 날문자열로 읽고 있었음.
`writeStateRaw` 는 옛 키를 안 쓰므로 **읽는 쪽이 늘 빈 값**을 받았음.

그래서 계획선을 고쳐 횡단을 다시 계산할 때(`B06_Section_Cross_Refresh`) 사용자가 옮긴
암 경계선이 안 실려 나가 **토사/암 나눔이 기본값으로 되돌아갔음** — 수량이 갈리는 자리.
[저장] payload 는 메모리 Map 을 써서 저장 자체는 무사했음.

- 읽는 쪽을 등록표(`readState("rockb", …)`)로 바꿈. 옛 키 만드는 함수는 지움.
- 실화면 실측(용화 5601e828 · route 169) — 경계선 -0.5 → -0.8m 로 옮기니
  새 키에 `{"0.00":-0.8}` 가 쌓이고 **옛 키는 아예 없었음**(고치기 전 근거).
  고친 뒤 새로고침해도 값·표기 그대로.

시험 `tmp/tests/test_session_keys_registered.py` 3건 — 등록표 밖 날문자열 세션 키를
쓰면 깨지는 그물 + 이 자리 못박기. **훑어 보니 다른 날문자열 키는 0개**로,
`rockb` 가 유일한 구멍이었음.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 11:31:07 +09:00
eomsangdon 05c4a2ef65 Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1 2026-09-07 11:15:25 +09:00
eomsangdonandClaude Opus 5 b8f0f7ad9d fix(B06): 사용자가 켠 2단 절토가 재계산에서 사라지던 것
엔진 둘이 결과 딕셔너리에 **엔진이 실제 적용했는지**를 담아 저장했고, 그 값이 다음
재계산에 인자로 되먹여졌음. 그래서 토사 측점처럼 2단을 못 쓰는 자리에서 사용자가 켜 두면
저장값이 False 로 바뀌고, 그 False 가 다시 인자가 되어 **켬이 영구히 사라졌음.**

- `two_stage_slope` 를 **받은 요청값 그대로** 내보내게 함(파이썬·TS 짝 두 줄).
- 「실제로 적용됐나」를 읽는 곳은 코드 전체에 없음(전수 확인) — 읽는 쪽 여섯은 모두
  사용자 설정으로 씀. 2단 사면이 그려지는 근거는 이 값이 아니라 설계선 기하임.
- 저장 형식 안 바뀜. 옛 저장분도 그대로 읽힘.

시험 `test_b06_two_stage_slope_kept.py` 4건 — 토사에서 켬 유지 · 되먹임 왕복 ·
끔도 유지 · 「2단은 설계선이 그린다」(암은 기하가 갈리고 토사는 같음) 확인.
거울 시험은 기존 `test_b06_cross_design_mirror.py` 가 그대로 잡음.

같은 모양인 `ditch_enabled` 은 손대지 않음 — 그쪽은 저장값을 「측구 없음」 판정으로
읽는 곳이 4군데(횡단 맞춤·횡단 그리기·B07 도면·3D 카브)라 처방이 다름.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 10:57:09 +09:00
eomsangdonandClaude Opus 5 744059aea1 refactor(B06): 사용자 값 목록을 한 벌로 — 손나열 두 곳 제거
새 사용자 값을 더할 때 **네 곳을 다 고쳐야** 했고, 하나만 빠지면 그 값이 저장에서
조용히 사라졌음(`extra_spans` 실사고 `b6941bd2`). 나열을 걷어 목록 한 벌로 모음.

- 확정·저장 payload 조립(`_Router_Confirm.py`) — `if patch_item.X is not None:` 11개를
  스키마 통째 덤프 + 최상위 None 걷기로 바꿈. 새 필드가 저절로 실림.
  중첩 None 은 남김(기슭막이 4축 `d: null` 은 「자동」이라는 뜻).
- 브라우저 payload(`_Section_Store.ts`) — `put("X", design.X)` 10줄을 목록 순회로.
- `_Cross_Refresh.ts` 에 `USER_TOUCHED_KEYS` 를 내보내고 `PRESERVED_KEYS` 는
  거기에 상태 둘을 더해 만들게 함. 서버 목록과 짝임을 머리에 적음.

전수 조사 결과 `design` 키는 세 갈래였음 — ① 재계산 인자로 되먹여 사는 것 8개
② 목록으로 베껴야 사는 것 12개 ③ 순수 계산값 24개. 위험한 칸은 ② 하나뿐임.

시험 3건 추가:
- `test_b06_design_key_split.py` — 사용자 값이 ①·② 어디에도 안 걸리면 깨짐(불변식).
  지금 코드는 통과 = 알려지지 않은 구멍 없음.
- `test_b06_recompute_keeps_user_values.py` — 포장 재계산 뒤 ② 값 보존·① 인자 전달.
- `test_b06_user_touched_keys.py` — 손나열이 되살아나면 깨지는 검사 둘 추가.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 10:49:29 +09:00
eomsangdonandClaude Opus 5 09442ae165 fix(B06): 단이 못 섰다는 안내를 조정창 밖 경로에서도 띄움
3-7 (2026-09-07). 안내는 이미 있었으나 `activeRevet` 이 그 벽일 때만 떠서, 좌측 「구조물
배치」 폼처럼 **조정창을 안 거친 경로**로 단 수를 바꾸면 개수만 조용히 잘렸음. 사용자는
[+]를 눌러도 아무 일이 없는 것으로 보임.

- 조건을 없애고 **자리(측점·측·세워진 단 수)마다 한 번만** 내도록 바꿈. 다시 그릴 때마다
  뜨면 시끄럽고, 결과가 달라지면(지형·조작) 키가 달라져 다시 알림.
- 요청대로 다 선 자리는 기록을 지워, 나중에 또 모자라면 다시 알림.
- 문구는 기존 `B06_Cross_Extra_Limit` 그대로 — 새 문구를 만들지 않음(중복 토스트 방지).

앞서 `Station_Controls.syncCount` 에 같은 안내를 붙였다가 **기존 것과 겹쳐 두 줄로 뜨는 것**을
실화면에서 보고 되돌렸음. 안내는 이 한 자리에만 둠.

tsc 통과 · 시험 508 통과.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 10:04:22 +09:00
eomsangdonandClaude Opus 5 023c0d545a fix(B06): 독립 기슭막이에도 좌우 이동 안내 + 하한 알림 조건 좁힘
- 독립 기슭막이(D군) 툴팁은 **따로 만들어져** 있어 「지형에 막힘」조차 안 떴음.
  배관 벽과 같은 두 줄(지형 막힘 · 각도 하한)을 같은 문구로 붙임.
- 기준벽 경로(`computeRevetmentLayout`)에도 `shiftFloorM` 을 남김 —
  하한이 `1.2 × 상하 내림 − 자동 자리`라 d 2.6m 자리에서 1.0m 요청이 삼켜졌음.
- 하한 알림은 **사용자가 실제로 값을 넣었을 때만** 뜨게 좁힘. 요청이 0이면
  하한은 그냥 기본 자리라 모든 벽에 뜨면 소리만 됨.
- 문구를 두 경로에 맞게 고침 — 「상하 내림의 1.2배」가 아니라
  「성토선 각도(1:1.2)를 지키려면 그만큼은 나가 있어야 함」.
- 시험 3건(`test_b06_extra_shift_floor.py`) — 하한 적용·알림·요청 없을 때 침묵.

미확인 — 실화면에서 알림이 뜨는 것까지는 못 봄. 지금 프로젝트(용화)의 카드가
전부 링크 카드라 좌우 이동 조작 자체가 먹지 않아 조건을 세우지 못했음.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 10:02:03 +09:00
eomsangdonandClaude Opus 5 ec153bc2e8 fix(B06): 다단 기슭막이 좌우 이동이 말없이 안 먹던 까닭 알림
「좌우 1.0m 를 넣어도 안 움직인다」의 원인은 지형이 아니라 **선반 하한**이었음 —
좌우 이동은 선반 길이가 음수가 되지 않게 `1.2 × 상하 내림`까지 따라 나가므로,
저장값이 d 2.6m 이면 하한이 3.12m 라 1.0m 요청은 아무 변화도 못 냄. 그런데도
`shiftBlockedM`(지형에 막힌 양)은 0 이라 까닭을 알 길이 없었음.

- `shiftFloorM` 추가 — 요청이 하한에 눌려 통째로 무시된 경우 그 하한을 남김.
- 벽 툴팁에 한 줄 — 하한 값과 「그 아래로 넣으면 안 움직임」을 적음.
  지형 탓(`shiftBlockedM`)과 자리를 나눠 두 까닭이 안 섞이게 함.
- 시험 `tmp/tests/test_b06_extra_shift_floor.py` — 화면 코드를 그대로 컴파일해
  Node 로 돌림. 좌우 0.5m 요청이 d 2.0m 아래에서 2.4m 로 밀리고 그 값이 알려지는 것 확인.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 09:49:14 +09:00
eomsangdonandClaude Opus 5 136965c472 fix(B06): 알약 레인이 종단 패널 높이를 부풀리던 것
④(2026-09-07)로 붙인 레인이 `chartWrap` 안에 서는데, 그래프 높이를 정할 때 잰 값에서
레인 몫을 빼지 않아 되먹임이 생겼음 — 잰 값이 커지면 그래프를 키우고, 그러면 다음 회차에
더 커짐. 실측 패널이 **16,664px** 까지 부풀어 카드·알약을 누를 수 없었음.

레인 높이(`STRUCTURE_LANE_HEIGHT_PX`)를 빼서 한 번에 수렴하게 함.
자체검증 — 패널 242px · 그래프 228px · 레인 42px · 알약 10개. tsc 통과.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 09:07:09 +09:00
eomsangdonandClaude Opus 5 679726124b feat(B06): 종단 구조물 표시를 B05 와 통일 — 같은 알약 레인
PLAN 2-5 (2026-09-07 사용자 지시 4 「구조물 표시 통일」). B06 종단에는 구조물 마크가
아예 없었고 배수관·세월교만 다른 계통으로 그려졌음.

- B05 가 쓰는 **같은 부품**(`buildStructureLane`)을 B06 종단 그래프 아래에 붙임.
  이름·중복 번호·툴팁·약호가 전부 B05 규칙 그대로 따라옴.
- 종단 차트가 `toX`(누가거리 → px)를 내보내 레인이 그래프와 같은 자리를 씀.
- 좌측 「구조물 배치」가 목록을 받을 때마다 `setStructureMarks` 로 넘김(구조물 정본 +
  관 정본). 관을 가상 구조물로 바꾸는 `pipesToStructureMarks` 는 B05 안에 있던 것을
  공용 자리(`B05_Profile_Api_Structures`)로 올려 **한 벌만** 둠.
- 알약을 누르면 그 자리에 가장 가까운 측점 카드를 고름(좌측 목록 클릭과 같은 규칙).
  끌기는 B05 몫이라 여기서는 자리만 보임(배수유역 재분할이 걸림).
- 검증 훅 `window.__b06Marks` 추가(`__corridorBuild` 등과 같은 용도).

자체검증 — 실화면 B06 종단에 레인 1줄·알약 10개(「배수관 1~8 · 세월교 1~2」)가 B05 와
같은 표기로 뜸. 훅 값 `{structures:10, types:36}`. tsc 통과.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 08:20:59 +09:00
eomsangdonandClaude Opus 5 a811286e52 feat(B06): 구조물 전체를 B06 에서 넣게 (B군 종단배수·F군 생태/녹화 열림)
PLAN 3-6 (2026-09-07 사용자 지시 5 「A군뿐 아니라 구조물 전체를 넣을 수 있어야 함」).

원인은 「B06 이 A군만 받는다」가 아니라 **레지스트리의 `enabled:false`** 였음 — B군 6종·
F군 2종·G군 1종이 목록에서 통째로 빠져 있었고, 두 화면이 같은 목록을 쓰므로 B05·B06 이
똑같이 A/C/D/E/G/기타 여섯 군만 보였음. 레지스트리 주석(2026-08-17)이 그 셋을 「B05
선택지에서 빼고 **B06 개별 횡단도 옵션으로 재사용**」이라 적어 둔 자리임.

- `fetchStructureTypes(includeDisabled)` 로 갈래를 둠. B06 만 참으로 부름 — B05 는 종전대로.
- 함께: 3-5 ⓑ 되돌릴 자리 주석(단 2개 이상일 때만 이어 붙이는 한 줄) 표시.

자체검증 — 공용 브라우저에서 두 화면 군 목록 대조:
  B06 A·**B**·C·D·E·**F**·G·기타 (8) / B05 A·C·D·E·G·기타 (6, 그대로)
  B06 에서 B군 6종(측구·산마루측구·소단측구·도수로·절토사면 배수로·맹암거)과
  F군 2종(야생동물 이동통로·비탈면 녹화)이 실제로 목록에 뜸. tsc 통과.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 07:57:15 +09:00
eomsangdonandClaude Opus 5 e9ad8cdfaf fix(B06): 다단 기슭막이 성토부선을 면적 트림에 넣음 — 단 수가 물량에 반영
PLAN 3-5 ⓑ (2026-09-07 사용자 확정 「옹벽도 수량이 변경되어야 함」).

그동안 면적 폐회로의 바깥 경계가 **기준벽 사면(노견~벽 상단)** 까지만이었고 그 바깥은
원지반으로 봐서 면적 0 이었음. 그래서 단을 몇 개 올려도 절·성토가 한 자리도 안 변했음.

- 화면이 그리는 선(`outletFill.segments`·`basinFill.segments`)을 트림 사면에 이어 붙임.
  「면적은 실제로 그려지는 설계선을 따른다」는 기존 원칙과 같은 자리로 맞춘 것.
- 그리지 않는 `cut` 갈래(벽이 원지반에 묻혀 성토가 없는 자리)는 뺌 — 화면과 같은 규칙.

실측(용화 route 165, 새 트림 대 옛 트림):
  81.77m  0단 성토 8.4247 → 10.9027(+2.478) · 1단 이상 10.6743(+2.250) · 절토 불변
  244.14m 지형이 단을 거부(0단) · 성토 3.3492 → 4.0511(+0.702) · 절토 불변
  423.55m 벽이 묻힌 자리(cut) · 성토 3.6145 그대로 · 변화 없음
→ 단이 서면 성토가 줄고(10.90 → 10.67), 단이 안 서는 자리도 **벽 바깥 성토가 이제
  잡힘**. 후자는 단과 무관한 변화라 보고에 따로 적었음.

파이썬 짝은 없음 — 구조물 트림 면적은 TS 한 벌이고 서버는 그것을 Node 로 돌림
(`B06_Section_Engine_Areas.py` 는 구조물 없는 표준 계산). tsc 통과·시험 500 통과.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 07:25:28 +09:00
eomsangdon 9ded6fe6c6 Merge remote-tracking branch 'origin/main_laptop_1' into sub_laptop_1 2026-09-07 04:57:03 +09:00
eomsangdonandClaude Opus 5 b6941bd28d fix(B06): 재계산이 다단 구간값을 지우던 것 — 사용자 조작값 목록을 한 벌로
같은 「사용자 조작값」 목록이 세 곳에 흩어져 있었고 서로 달랐음.
브라우저(B06_Section_Cross_Refresh.ts PRESERVED_KEYS)는 extra_spans 를
살렸는데 서버 두 곳은 안 살렸음 —

  - B06_Section_Router_Design._USER_TOUCHED_KEYS (포장 강제 재계산 ·
    세월교 노면 하강 재계산이 씀)
  - B06_Section_Router 안의 인라인 사본 (단측점 설계 갱신)

그래서 포장 구간이나 세월교가 앉은 측점이 다시 계산되면 사용자가 넣은
다단 구간값이 조용히 사라졌음.

_USER_TOUCHED_KEYS 를 공개 이름 USER_TOUCHED_KEYS 로 바꾸고 extra_spans 를
넣음. 라우터의 인라인 사본을 지우고 그 한 벌을 씀.

시험 tmp/tests/test_b06_user_touched_keys.py 3건 신설 — 서버 목록에
extra_spans 가 있는지, 서버와 브라우저 목록이 같은지(status ·
pavement_suggested 는 부르는 쪽이 붙이므로 제외), 라우터에 사본이
남지 않았는지. 목록이 다시 갈라지면 시험이 깨짐.

pytest 412 passed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 04:50:43 +09:00
eomsangdonandClaude Opus 5 48c856ea8d fix(계측): B06 확정 마크가 다른 함수에 정의되던 것 바로잡음
`marks` 정의가 `save_sections` 에 들어가고 사용은 `confirm_sections` 에 남아
`NameError: name 'marks' is not defined` 로 [확인] 재계산이 500 으로 끝났음
(노선은 자동 롤백돼 자료 손상 없음). 정의를 쓰는 함수 안으로 옮기고
`save_sections` 의 잘못 들어간 두 줄 제거. 계측 항목은 그대로.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 04:50:08 +09:00
eomsangdonandClaude Opus 5 4da04929c4 perf(계측): 재확정 체인 병목 추적용 단계 마크 추가
PLAN 0-10. 8000 에서 1회 실측 결과 재확정 체인 292.9s 중 배수유역 분석이 285.0s(97%)로
나와, 그 안쪽을 가르기 위한 마크만 추가. 로직 불변.

- B06 확정: 조회 / 기본설계 계산 / 확정 저장 / 서버 재계산 / 측구 역반영
- 서버 재계산 내부: 상세+DB 병렬 조회 / 포장·세월교 보정 / Node 번들 / 정본 저장
- 배수유역 분석 내부: _prepare(도엽 읽기·좌표변환) / preview_stages / 응답 만들기·저장
- preview_stages: 1차 영역 / 격자 확장·흐름 판정 / 흐름 강도 / 외곽선·기본 관·화살표

`_log_steps` 는 체인 모듈 것을 그대로 씀(지연 임포트로 순환 회피).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 03:20:39 +09:00
eomsangdonandClaude Opus 5 71694163de fix(B06): 벽을 안 그리는 카드에 [연동] 버튼이 뜨던 것
링크 카드는 소유 측점의 벽을 빌려 그리는데, 빌릴 벽이 없는 카드에도 버튼이 떠 눌러도
아무 일이 없었음 — revetlink 에 detached 만 쌓였음(보조 창 실측: 0·20·40·60·80m 카드).
linkState 가 drawnWallKeys().length 도 함께 보게 함.

계획서 3-4 실화면 확인도 함께 마침(공용 브라우저, 용화):
- 벽이 있고 연동이 켜진 카드 820.00m 에서 [연동]을 끄자 revetx 에 820.00:outlet h=1.8 이
  적힘 — 푸는 순간의 높이가 그 측점 값으로 굳음.
- 그 뒤 소유 측점 821.62m 높이를 4.2m 로 올려도 820.00m 은 1.8m 그대로 — 따로 놂.
※ 앞서 안 굳는 것으로 본 것은 측정 오류였음. 패널이 카드마다 하나씩 67개인데 첫 번째
   패널만 봤고, 벽이 없는 카드를 골랐음(보조 창이 가려냄).

시험 409 통과·17 건너뜀, typecheck 통과.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 01:37:59 +09:00
eomsangdonandClaude Opus 5 eec29a90df fix(B05,B06): 최소곡선반지름 조회가 없는 컬럼을 읽던 것 · 재계산 중단 사유 로그
보조 창이 노선 편집 [확인] 실측에서 둘을 잡음.

1. projects 에 design_speed_kph / terrain_type 컬럼이 없음(있는 것은 road_type 뿐).
   매 요청마다 OperationalError(1054) 가 ERROR 로그로 남고 기본값 폴백으로 넘어갔음.
   - 임도 종류는 projects.road_type, 설계속도·지형은 워크플로 stage 2 params 에서 읽음.
   - 산식은 이미 있던 B05_Profile_Engine_Grade.legal_plan_radius_min_m 를 씀 —
     내가 같은 표를 다시 짜 두었던 것을 지움(중복 제거).
2. 재확정 체인이 solve_route 400 으로 끊길 때 상태 코드만 남겨 원인을 못 짚었음.
   본문(사유)까지 로그에 남김. 노선 편집 [확인]이 조용히 끊겨 배수유역·관은 새 노선으로
   가고 종횡단만 옛 노선에 남는 어긋남이 굳었던 자리임.

recompute 의 상세 조회와 DB 두 건도 asyncio.gather 로 묶음(원격 DB 왕복 약 12ms/질의).

시험 409 통과·17 건너뜀.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 00:37:53 +09:00
eomsangdonandClaude Opus 5 4df5d0a7ea perf(B06): 카드 기하를 두 번 계산하던 것을 한 번으로
행 높이를 재는 1차와 실제 그리는 2차가 같은 측점의 crossPlotMetrics 를 각각 계산했음.
측점 67곳 기준 3.2ms/장이라 진입에서 draw 자기 시간 213ms 의 대부분이었음
(보조 창 CPU 프로파일).

- crossPlotMetrics 를 crossPlotBase(강제 높이 전) + crossPlotFromBase(높이만 얹기)로
  가름. 기존 시그니처는 둘을 잇는 감싸개로 유지.
- Section_View 1차에서 base 를 만들어 2차 카드에 그대로 넘김.
- 1차 반폭도 카드가 실제로 쓰는 값(effectiveCardHalfWidth, 자동 줌아웃 포함)으로 맞춤 —
  전에는 1차가 원래 반폭으로 재 카드·행 높이 기준이 어긋났음.
- 쓰이지 않게 된 crossCardNaturalHeight 와 그 재수출 삭제.

자체검증(공용 브라우저) — 카드 67장·SVG 67개 그대로, 같은 행 카드 높이 통일 유지
(rowsUniform true), 높이 범위 292~1,119px. 시험 400 통과·17 건너뜀, typecheck 통과.
효과 수치는 보조 창의 격리된 프로젝트에서 3회 중앙값으로 재기로 함(내 창은 편차가 큼).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-06 22:54:12 +09:00
eomsangdonandClaude Opus 5 c0bfcba34b feat(B05,B06): 유토 배분을 서버로 옮기고 브라우저는 미리 받아 쓰게 함
배분·운반거리 산식(_balance 523 + _settle 235 = 758줄)을 브라우저 번들에서 빼기 위한
배선. 계산은 여전히 한 벌 — 서버가 같은 TS 를 Node 로 돈다(CLAUDE.md 5장).

- POST /projects/{id}/sections/{route_id}/haul-plan 신설
  (B06_Section_Router_HaulPlan.py). 브라우저가 낸 누가토량 결과를 받아 배분만 돌려줌.
  화면이 쓰는 꼴 그대로 내보내 그리기 코드를 안 건드림. 표시 전용 — 정본은 저장 때 따로.
- B05_Profile_Api_HaulPlan.ts 신설 — 편집이 멈추면(400ms) 조용히 받아 두는 선반입기.
  늦게 온 응답은 버림(최신 요청만 채택). 못 받아도 곡선 자체는 그대로 보임.
- common_util_mass_haul.massHaulPayload 가 배분을 값으로 물던 것을 끊음(extra 인자).
  이 커플링 때문에 화면에서 안 불러도 번들에 남았음.
- 저장 경로는 곡선도 배분도 안 만듦 — 서버가 정본을 내므로 balloon 위치만 보냄.
- 죽은 파일 B06_Section_UI_Section_View_MassHaul.ts(236줄) 삭제.

자체검증(공용 브라우저, 용화 route 149) — 엔드포인트 200 · 196ms.
서버 배분과 브라우저 computeHaulPlan 결과가 JSON 문자열까지 동일(blocks 1 · steps 2 ·
spoil/borrow/hauled/transferred/fill_total 전부 일치). 시험 400 통과·17 건너뜀.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-06 22:47:22 +09:00
eomsangdonandClaude Opus 5 1abb456ef9 perf(B06): 카드 넘침 정리를 한 프레임에 몰아 강제 레이아웃 67회를 1회로
카드마다 requestAnimationFrame(reflow) 을 걸어 한 프레임에 67번 돌았고, 매번
쓰기 뒤 읽기라 강제 레이아웃이 67회 났음. 한 번의 강제 레이아웃이 그때까지 들어간
카드 전부를 다시 재므로 뒤로 갈수록 비쌌음(보조 창 CPU 프로파일: 자기 시간 1위).

- reflow 를 reset(쓰기) / measure(읽기) / apply(쓰기) 세 토막으로 가름.
- 모듈 단위 scheduleReflow 가 카드를 모아 한 프레임에서 전부 reset -> 전부 measure ->
  전부 apply 순으로 돌림. 강제 레이아웃 프레임당 1회.
- ResizeObserver 는 창 크기 변경 때 그 카드만 큐에 다시 넣음(첫 호출 건너뛰기 유지).

자체검증(공용 브라우저) — B06 진입 2,330~2,864ms -> 1,994/2,088ms,
긴 작업 합 1,853 -> 1,604/1,728ms. 동작 확인: 카드를 200px 로 좁히면 버튼 3개가
... 패널로 옮겨지고 되돌리면 복귀(정상 폭에서는 scrollWidth = clientWidth = 937 로
넘침 없음). typecheck 통과.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-06 22:32:17 +09:00
eomsangdonandClaude Opus 5 5967d64b63 perf(B05): 측구 방향 역반영을 스레드 한 번 + 쓰기 한 문장으로 묶음
route/confirm 이 측점마다 종단 정본을 다시 열고(13.8ms) 행마다 DB 에 썼음(24.5ms).
건당 38.3ms 라 30건이면 1.15초, 67건이면 2.57초로 측점 수에 선형으로 늘었음
(보조 창 서버 내부 측정).

- 다시 계산할 측점을 먼저 고른 뒤 asyncio.to_thread 를 **한 번만** 돌림. 그 안에서
  종단 정본과 포장 제안표·세월교 하강표를 한 번 읽어 측점마다 돌려 씀.
- read_cross_design_inputs 에 preloaded 인자 추가(종단 경로·내용·포장 제안표).
  경로 검증은 resolve_longitudinal_path 로 떼어 재사용.
- 쓰기는 merge_cross_section_designs 한 문장.

자체검증(공용 브라우저 [저장] 3회) — route/confirm 470~834ms -> 324/351/356ms.
버튼 전체 대기 2,624~3,415ms -> 1,826/2,766/2,979ms.
(오늘 누적: 4,137ms -> 1,826~2,979ms) 시험 400 통과·17 건너뜀.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-06 22:27:41 +09:00
eomsangdonandClaude Opus 5 0b30f5b035 perf(B05,B06): 지형 표고를 레이캐스팅 대신 격자 색인으로 · 카드 버튼줄 레이아웃 스래싱 제거
B05 진입이 간헐적으로 13~14초 멎던 원인을 CDP 프로파일로 잡음. 13,537ms 중
getVertexPosition 3,267 + intersectTriangle 3,178 + checkGeometryIntersection 3,012
+ _computeIntersections 2,198 = 11.6초가 three.js 레이캐스팅이었음.

- terrainElevation 이 점마다 Raycaster 로 지형 높이를 찾고 있었음(한 번 쏠 때마다
  삼각형 전수 훑기). 마커·측점선이 점마다 부르는 자리라 곱해짐. 이미 있던
  TerrainHeightIndex(격자 색인)로 돌리고, 색인도 지형과 한 벌로 보관(빌드 O(삼각형)).
  같은 교훈을 2026-08-23 비탈 투영에서 겪어 색인을 만들어 뒀는데 이 함수만 옛 길이었음.
- B06 카드 버튼줄 reflow 의 레이아웃 스래싱 제거 — 한 칸 옮길 때마다 scrollWidth 를
  다시 읽어 매번 강제 레이아웃을 냈음(카드 67장마다). 읽기/쓰기를 갈라 폭을 한 번만
  재고 옮길 개수를 계산. ResizeObserver 첫 호출도 건너뜀(rAF 와 겹쳐 두 번 돌았음).
- get_section_context·get_section_detail 의 순차 읽기를 asyncio.gather 로 묶음
  (원격 DB 왕복 약 12ms/질의).

자체검증(공용 브라우저 4왕복) — B05 진입 14,708/1,060/14,841 -> 1,269/806/726/746ms.
급등 사라짐. B06 진입 2,884 -> 2,330~2,864ms(긴 작업 합 2,313 -> 1,853).
시험 400 통과·17 건너뜀, typecheck 통과.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-06 22:22:08 +09:00
eomsangdonandClaude Opus 5 d650d705d1 perf(B06): 측점 설계 저장을 한 문장으로 묶어 원격 DB 왕복 제거
측점마다 SELECT+UPDATE 두 왕복을 냈고 DB 가 원격이라 왕복 하나가 약 12ms.
22행이면 670ms 이고 측점 수에 선형으로 늘었음(보조 창 서버 내부 측정).

- B06_Section_Repository_Bulk.merge_cross_section_designs 신설 — 노선 측점을 한 번에
  읽고, 파이썬에서 chainage 를 맞춰 JSON 을 합친 뒤
  `UPDATE ... SET data = CASE id ...` 한 문장으로 되돌려 씀(없는 행은 다중 INSERT).
  행 수와 무관하게 왕복 두 번. Repository 가 685줄이라 파일을 나눔(700줄 한계).
- 부르는 자리 셋을 묶음으로 바꿈 — _apply_section_edits 의 기본설계·측점 patch 두 루프,
  _recompute 의 보정·면적 두 루프.

자체검증(공용 브라우저 [저장] 3회) — sections/save 3,593ms -> 2,205 / 1,870 / 2,547ms.
버튼 전체 대기 4,137ms -> 2,624~3,415ms. 진행 표시는 2~3ms 만에 뜸.
시험 tmp/tests/test_b06_bulk_designs.py 5건(같은 JSON 결과·왕복 두 번·1cm 허용오차·
행 없을 때 INSERT·빈 목록은 왕복 0). 전체 400 통과·17 건너뜀.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-06 22:00:28 +09:00
eomsangdonandClaude Opus 5 3cd717d8f6 feat(B06): [저장]·[확정] 정본을 서버가 다시 계산하도록 되돌림
2026-09-06 저녁 사용자 확정. 이유는 속도가 아니라 보안 — 저장 경로가 브라우저 계산이면
유토 배분·운반거리 코드(_balance 523 + _settle 235 = 758줄)가 번들에 남아야 해서
화면에서 안 그려도 뺄 수 없음. 서버가 정본을 내면 그 몫이 번들에서 빠짐.
대가는 저장 대기 850ms -> 약 980ms(Node 131ms) — 기다리는 조작이라 허용.

- 저장·확정이 편집을 커밋한 **뒤** recompute_server_side 를 부름(전에는 편집 앞에서
  Node 없이 보정만). 바뀐 벽·측점이 면적·유토곡선에 실림.
- run_node 갈래 제거 — 이제 부르는 자리가 둘 다 Node 를 돎(체인·저장).
  쓰이지 않게 된 enforce_stored_designs 래퍼 삭제.
- 코리도 열쇠 질의: hash 를 isinstance 로 좁힘. 파이썬에서 곧바로 부르면 Query 기본값
  객체가 들어오는데 truthy 라 열쇠 없는 호출이 통째로 stale 로 떨어졌음(보조 창 지적).

시험 395 통과·17 건너뜀.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-06 21:20:55 +09:00
eomsangdonandClaude Opus 5 c97e949792 fix(B06): 서버가 못 뜨던 import 수정 — 확정 라우터의 옛 재수출 경로
`B06_Section_Router_Confirm` 이 `_compute_default_designs` 를 `B06_Section_Router`
에서 들여왔는데, 커밋 1d4568b9 가 그 재수출을 지우면서 서버가 아예 안 떴음.
원본 모듈 `B06_Section_Router_Design.compute_default_designs` 에서 곧바로 들여옴.

시험 390건이 이 경로를 안 타서 못 잡았으므로 tmp/tests/test_app_imports.py 추가 —
`main` 을 들여와 라우터 27개가 전부 import 되는지 본다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-06 21:12:41 +09:00
eomsangdonandClaude Opus 5 165b281fe1 fix(B05,B06): 배수관 편집을 세션 초안에 쌓아 B06 저장에도 실리게 함
관 추가·이동·삭제가 B05 패널 메모리에만 있어, B06 으로 넘어가 [저장]·[확정]하면 그
편집이 사라졌음(대응표 조사에서 드러남).

- 등록표에 `pipes` 초안(프로젝트 범위)을 되살림
- 배치가 바뀌는 순간 목록을 초안에 담고, 내보내기는 `flushPendingPipes` 한 창구로 모음
- B05 [임시저장]·B06 [저장]·[확정] 모두 이 창구를 지남 (자동저장 없음)

검증: 초안으로 관 11 → 10개 저장·초안 비움, 되돌려 11개 복구. 테스트 390 통과.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-06 20:25:12 +09:00
eomsangdonandClaude Opus 5 81a86fcc0d fix(B06): 저장 뒤 상세 캐시를 버려 새 구조물 벽이 붙게 함
구조물 초안이 저장으로 정본이 되면, 화면이 들고 있던 상세는 초안 기준으로 벽을 얹은
사본이라 새로 저장된 구조물의 벽이 다음 조회까지 안 붙었음(8+0.0 흙막이 실측).

검증: 캐시를 비우면 벽 5 → 7개로 붙음. 테스트 390 통과.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-06 20:20:08 +09:00
eomsangdonandClaude Opus 5 d529ebd170 feat(B06): 지형에 막힌 좌우 이동을 벽 툴팁으로 알림
추가 기슭막이를 옆으로 옮겨도 안 움직이는 측점이 있어(실측 1.0m 요청에 0.00~0.18m),
막는 규칙(원지반 매몰 금지)이 되돌린 양을 벽에 남기고 툴팁에 적음.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-06 20:13:47 +09:00
eomsangdonandClaude Opus 5 89820848ec fix(B06): 추가 기슭막이 바닥을 원지반에 앉힘
연계(추가) 기슭막이가 성토사면과 안 맞는다는 지적(3-5)을 수치로 재니, 어긋난 곳은
접점이 아니라 바닥이었음 — 상단은 1:1.2 성토선에 붙는데 키는 요청값 그대로라
지형이 모자라면 바닥이 떴음(실측 0.12~0.87m).

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

검증: 264.06 0.394 → 0.000m(키 1.00 → 1.39), 821.62 0.427 → 0.000, 914.93 0.122 → 0.000.
731.31 은 0.873 → 0.373 으로 줄되 남음(돌쌓기(메) 높이 한계 2.0m — 경고 유지가 맞음).
테스트 390 통과.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-06 20:03:28 +09:00
eomsangdonandClaude Opus 5 818042e45c feat(B05,B06): 최종 누가토량 배지 + B06 유토곡선 그래프 제거
사용자 지시(2026-09-06): 유토곡선은 자리를 많이 먹는데 정작 늘 봐야 하는 값은 마지막
지점 누가토량 하나임. 기준은 횡단으로 고정(종단 기준 곡선은 뒤에 삭제 예정).

- 총괄값 계열을 범례 선택과 무관하게 **횡단 기준**으로 뽑음
- 공용 배지 `common_util_mass_haul_badge.ts` — 종단 그래프 좌측 상단 오버레이,
  세부 내역은 툴팁
- B05: 상단줄의 「누가토량」 항목을 배지로 옮김(유토곡선 패널은 그대로 유지)
- B06: 유토곡선 그래프·범례·요약줄 삭제, 그 높이를 종단면도가 전부 사용

검증: B06 배지 −5,595.5㎥·범례 없음·종단 228px, B05 배지 −6,842.6㎥·상단줄에서 제거.
테스트 390 통과.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-06 19:44:59 +09:00
eomsangdonandClaude Opus 5 1136fff0ea fix(B06): 구조물 초안·면적을 상세 저장소 한 곳에서 얹음
페이지마다 따로 얹으니 새로고침 뒤 세션 스냅샷이 저장분 기준으로 되돌아갔음
(초안 벽 3건이 화면에서 사라짐). 캐시·세션·새 요청 어느 길로 들어오든 지나는
공유 저장소에서 ① 저장 안 한 구조물 벽을 얹고 ② 구조물 면적을 다시 계산하게 모음.

검증: 새로고침 뒤에도 벽 5건(저장 2 + 초안 3)이 붙고 3+0.0 성토 8.06 → 5.73㎡ 유지.
테스트 390 통과.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-06 19:18:58 +09:00
eomsangdonandClaude Opus 5 155641a782 fix(B06): 구조물 추가 시 유토곡선도 같이 갱신
유토곡선은 카드보다 먼저 계산돼, 구조물을 넣으면 카드 면적만 바뀌고 최종 누가토량은
옛 값으로 남았음. 구조물이 바뀌면 면적을 먼저 다시 얹고 그리게 순서를 바로잡음.

검증: 3+0.0 성토 8.06 → 5.73㎡, 최종 누가토량 553.4 → 546.4㎥ (저장 전, 즉시).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-06 19:04:50 +09:00
eomsangdonandClaude Opus 5 1d4568b92f feat(B06): 구조물 목록의 C군 벽을 횡단 면적에 반영
좌측 「구조물 배치」로 넣은 옹벽·돌쌓기 같은 벽이 지금까지 측점만 심고 기하가 없어
절·성토 면적이 그대로였음(2026-09-06 사용자 확정: 반영해야 함).

- 독립 기슭막이가 쓰던 `section.revetment` 제원 자리에 그대로 얹음 — 기하·설계선
  트림·폐회로 면적·3D 가 손대지 않고 따라옴.
- 서버 `B06_Section_Engine_Structures_Wall.py`(저장분) ↔ 브라우저
  `common_util_structure_walls.ts`(미저장 초안) 짝 + 거울 테스트.
- 설치 측 칸이 없는 C군은 성토가 나는 쪽으로 자동 배치, 양측 절토면 세우지 않음.
- 구조물 목록이 바뀌면 B06 이 초안을 얹어 다시 그림.

검증: 40m 돌쌓기(찰) 추가 시 그 측점 성토 5.81 → 5.02㎡. 테스트 390 통과.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-06 18:59:55 +09:00
eomsangdonandClaude Opus 5 1cf47f39b2 fix(B06): 임시저장 엔드포인트가 깨져 있던 것 수정
포장·세월교 보정 헬퍼를 저장 시점으로 옮기면서 `@router.post(.../save)`
데코레이터와 `save_sections` 사이에 끼워 넣어, 라우트가 헬퍼(None 반환)에
붙어 있었다. 저장이 500(ResponseValidationError)으로 떨어졌다.

헬퍼를 데코레이터 앞으로 옮겨 라우트를 되돌렸다.

검증(용화 신규 프로젝트, 노선 145): 저장 200, 카드에서 고른 지반유형이
DB `cross_sections.data.design.ground_type` 에 반영되고 면적도 브라우저
계산값으로 갱신됨.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-06 16:11:40 +09:00
eomsangdonandClaude Opus 5 9da3bce1bf fix(B06): 횡단 카드 버튼이 서버에 바로 저장하던 것을 캐시로 바꿈
사용자 확정(2026-09-06): 조작은 캐시에 쌓이고 [저장]·[확정]에서만 영구저장소에
나간다. 카드 버튼(지반유형·단면유형·측구·포장·2단 비탈)은 누를 때마다
POST cross-design 이 나가 서버가 계산하고 바로 저장하고 있었다.

- 세션 초안 crossdesign 신설(B06_Section_Cross_Design_Session) — 선택값만 담는다.
- 계산은 브라우저 재계산 창구 하나로 돌린다(B05·B06 공용).
- 재계산 때 세션 선택이 정본보다 우선 — 새로고침 뒤에도 고른 값이 남는다.
- [저장]·[확정] patch 에 선택값 + 전 측점 면적을 실어 보낸다. 카드 버튼을 바꾸면
  구조물이 없는 측점 면적도 달라지므로 구조물 측점만 보내면 수량이 어긋난다.
- 구조물 목록 주석 정정(이미 초안 방식인데 옛 주석만 남아 있었다).

검증: tsc --noEmit 통과, pytest 389 passed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-06 15:14:44 +09:00
eomsangdonandClaude Opus 5 8e3c1f91fc perf(B05/B06): 진입 시 중복 서버 호출 두 건 제거
- 확정 지표면: 목록(surface/models)으로 id 를 찾고 3D 단계에서 surface/confirmed
  를 또 부르던 것을 확정 응답 한 번으로 합침. 모델 id·범위가 그 응답에 다 있다.
- 종횡단 설정값(sections/context): ④ 계산 결과로 보고 세션에 담음. B05·B06 을
  오갈 때마다 다시 묻지 않는다. 노선 캐시를 버리는 자리에서 함께 버린다.

검증: tsc --noEmit 통과, pytest 389 passed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-06 15:07:47 +09:00
eomsangdonandClaude Opus 5 bdbefd9472 feat(B06): 곡선부 확폭에 앞뒤 10m 테이퍼 추가
별표2 는 확폭량 표만 주고 붙이는 방식이 없어, 곡선 시·종점에서 폭이 뚝 끊겼다.
곡선 앞뒤 CURVE_WIDENING_TAPER_M(10m) 구간에서 0 → W 로 잇는다.

- 측점 생성 때 확폭량을 한 번에 계산해 curve_widening_m 으로 저장. 테이퍼는
  이웃 측점을 봐야 하므로 측점 단위 계산으로는 낼 수 없다.
- 설계 엔진(파이썬·TS 짝)이 저장된 확폭량을 우선 쓰고, 없으면 반경 표값으로
  되돌아간다 — 옛 저장분도 그대로 돈다.

검증: pytest 389 passed(테이퍼 경계값 테스트 2건 추가), tsc --noEmit 통과.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-06 15:02:58 +09:00
eomsangdonandClaude Opus 5 dd61754705 refactor(B06): 구조물 면적·유토곡선을 브라우저 계산으로 되돌림
사용자 확정(2026-09-06): 조작 중과 [저장]·[확정]의 계산은 브라우저 몫이고,
서버는 초기값을 만들 때만 같은 코드를 Node 로 돌린다.

- 면적 산출을 B06_Section_Structure_Layouts 로 빼 Node 진입점과 브라우저가
  같은 한 벌을 쓰게 함(structureAreaRows / applyStructureAreaRows).
- [저장]·[확정]이 카드를 그리지 않은 측점까지 면적을 계산해 cross_patch 로
  보냄. 유토곡선도 그 위에서 쌓음.
- 서버는 저장 때 Node 를 돌리지 않음 — 포장 구간·세월교 노면 하강 보정만
  남기고, 그 보정은 편집분을 얹기 전에 돌게 순서를 바꿈.

검증: tsc --noEmit 통과, pytest 387 passed, Node 진입점 스모크 통과.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-06 14:59:31 +09:00