사용자 확인(2026-09-06) — 예상노선은 폴리라인이 아니라 점 묶음이고 규칙 없는
폴리라인과도 맞지 않음. 그래서 계획노선은 원데이터를 복사해 폴리라인으로 바꾼 것이어야
하고 그것이 불변의 초기 데이터가 됨. 곡선 기준은 지식DB(별표2 I.2.다) 값을 씀.
- common_util_route_polyline.py 신설 — 점 묶음을 폴리라인으로.
① Douglas-Peucker 로 꺾임점(IP)만 남김. 예상노선은 격자 탐색이 낸 조밀한 점군이라
(용화: 1,097m 에 331점) 점마다 곡선을 끼우면 접선 자리가 1.5m 뿐이라 R 이 2~6m 로
뭉개짐. 허용오차는 격자 해상도(2m)의 두 배.
② 내각 155도 이상은 곡선 생략(별표2), 그 아래는 최소곡선반지름으로 원호를 끼움.
자리가 모자라면 반지름을 줄이되 막지 않고 위반으로 표시(사용자 확정: 경고만).
③ 점은 옮기지 않음 — 노드는 원본 자리에 그대로 두고 그 사이에 원호를 넣음.
- planned_route_initial_path 신설 — 세 벌 구조(예상노선 점 묶음 / 초기 폴리라인 /
수정본). load_design_route 읽는 순서에 초기 폴리라인을 예상노선보다 앞에 끼움.
- 최소곡선반지름은 임도 종류·설계속도·지형으로 고름
(FOREST_ROAD_PROFILE_CRITERIA[min_plan_radius_m], 못 읽으면 가장 완화된 조건).
- GET /route/plan 이 노드(반지름·내각·위반)까지 함께 돌려줌 — 노드만 옮기면 선이
저절로 규칙을 지키게 하는 것이 목적. POST /route/replan 도 받은 노드로 다시 폴리라인화.
자체검증(용화, 실화면 API) — 예상노선 331점 -> 편집 노드 25개 + 곡선 13곳
(전부 R 12m = 설계속도 20·특수지형 법정 하한, 위반 0) -> 계획노선 정점 142개. 응답 180ms.
단순화 전에는 곡선 15곳이 R 2.75~6.51m 로 전부 위반이었음.
시험 tmp/tests/test_route_polyline.py 7건(원호가 접선과 맞물리는지 좌표로 대조).
전체 407 통과·17 건너뜀.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
배분·운반거리 산식(_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>
DB 가 원격이라 질의 하나가 곧 왕복 12ms 임. 서로 기다릴 이유가 없는 읽기를
한 커넥션에서 순차로 내면 그 왕복이 그대로 더해짐.
- config_db.run_with_connection: 저장소 함수를 자기 커넥션으로 돌려 gather 로
묶을 수 있게 하는 공용 헬퍼. 정의를 한 곳에 둠(drainage_context 의 _query 는
이 함수를 가리키는 이름으로 정리).
머리에 경고 적음 — 순서가 필요한 쓰기는 이걸로 묶으면 트랜잭션이 깨짐.
- B04_PreProcess_Router_Watershed._prepare: 저장경로·좌표계·지표면확정값
세 건을 gather 로. 약 24ms 절약.
common_util_auth_repository.py:318(decide_join_request)은 FOR UPDATE + 순서 있는
UPDATE 라 묶지 않음 — 읽기 블록만 대상.
자체검증 — 관 드래그 왕복 305 -> 259ms(최소 193), 관 11 · 유역 11 로 값 동일.
전체 484 통과.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
인증이 걸린 모든 요청이 지나는 자리인데 요청마다 UPDATE + commit 이 나갔음.
DB 가 원격이라 그 둘이 곧 왕복 20ms 임.
실측 — verify_session 34.4ms = SELECT 10.8 + UPDATE 10.4 + commit 9.5.
쓰기를 1분 간격으로 낮춘 뒤 13.6ms(최소 9.4).
last_activity_at 은 유휴 판정에만 쓰는 값이고 그 판정이 4시간 단위라
초 단위 정확도가 필요 없음. 간격은 SESSION_ACTIVITY_WRITE_INTERVAL_SECONDS
(기본 60, .env 로 조정).
화면 한 번 여는 데 API 가 약 180번 나가므로 그만큼 곱해짐.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
계산 결과는 화면에 나가도 된다는 방침이라, 남는 위험은 입력을 바꿔가며 출력을
긁어 모으는 것임. 사람이 화면을 쓰는 속도에는 한계가 있음 — 화면 한 번 여는 데
API 가 약 180회 나가므로 한 시간에 2만 회를 넘으면 사람이 아니라고 봄.
- 막지 않음. 고객 화면을 끊을 위험이 있고 어디서 끊을지는 실제 사용 기록을 본 뒤
정할 일임. 지금은 시스템 로그(보관 1년)에 RATE_ANOMALY 한 줄만 남김.
- 세는 값은 메모리(세션·시간대별)라 요청당 DB 비용 0. 상한을 넘은 그 순간에만
세션 주인을 한 번 조회해 기록하고, 같은 시간대에는 다시 안 남김.
- 지난 시간대 기록은 버려 사전이 무한정 자라지 않게 함.
- 상한은 API_CALL_HOURLY_LIMIT(기본 20000, .env 로 조정).
빌드 껍데기는 이미 단단해 손대지 않음 — 실측 확인: 배포 산출물에 소스맵 0개,
식별자 뭉개짐(import{$ as e,C as t,...}), computeCrossDesign 등 원본 함수명
검색 결과 0건. 난독화 도구 추가는 얻는 것 대비 비용이 큼.
시험 tmp/tests/test_call_volume_watch.py 5건. 전체 484 통과.
실서버 확인 — 정상 사용 흐름에서 감시 로그 0건.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
DB·파일에는 뜻 없는 자릿수가 붙어 있고(1.4000000000000001 꼴) 서버는 응답을
전혀 압축하지 않고 있었음. B05 한 번 진입에 118MB 수신(2026-09-06 실측).
- main.py: 압축 미들웨어(1KB 이상). 3D 예상형상(/corridor)은 예외 —
숫자 배열이라 절반만 줄면서 압축에 493ms 듦(18.6->9.4MB).
- common_util_json: round_floats(value, digits) + LONLAT_DIGITS 7(1.1cm) ·
METRE_DIGITS 6. 저장 파일은 그대로 두고 내려보낼 때만 줄임.
브라우저가 다시 계산에 넣는 값(종횡단 지반선)은 줄이면 안 된다고 머리에 경고 적음.
- 도엽 GeoJSON: 자릿수만 줄인 표시용 사본(.display.geojson)을 한 번 만들어 서빙.
원본이 새로 깔리면 자동 재생성, 실패하면 원본 그대로.
등고선 도엽 65.9->36.8MB, 압축까지 11.3MB.
- 배수유역 응답: 위경도 7자리. 151->100KB, 압축 22KB.
ruff 통과, 테스트 390 통과·17 건너뜀.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
perf(B04): 배수유역 세부 계산의 헛일 제거 — 282→154ms
DB 가 원격(dsm.chemifactory.com)이라 질의 하나가 곧 왕복 12ms 임을 실측.
순차 6건 130ms 중 대부분이 왕복 대기였음.
- context: 서로 기다릴 이유가 없는 질의를 두 묶음으로 asyncio.gather
(묶음마다 자기 커넥션 — 풀 최대 20 이라 여유). 계획노선 읽기·지표면
샘플러 열기도 같이 보냄.
- 격자 산출물(npz 3.1MB): 관을 옮길 때마다 다시 읽던 것을 파일 자국
(수정시각·크기) 열쇠로 재사용. 분석이 다시 돌면 자국이 바뀌어 저절로 새로 읽음.
자체검증 — 용화 프로젝트(관 11·유역 11) 5회 중앙 282→154ms.
같은 입력에 응답 전체가 한 글자도 안 달라짐(JSON 정렬 비교).
전체 테스트 390 통과·17 건너뜀.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@
사용자 지시(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 사용자 확정: 반영해야 함).
- 독립 기슭막이가 쓰던 `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>
- 기록을 `common_util_audit.record_audit()` 한 곳으로 모음 — 여섯 자리에 흩어져 있던
raw INSERT 제거
- 대상 식별자 칸 신설(`017_audit_log_detail.sql`, 적용 완료) — 기존 `resource_id` 는 INT 라
프로젝트 UUID 를 못 담아 늘 NULL 이었음. 프로젝트 생성·수정·삭제·회사 생성이 대상을 남김
- 접속 주소·브라우저 기록 — 라우터가 요청을 넘겨 주고, 프록시 뒤에서는 X-Forwarded-For 우선
- 보관 기간 `AUDIT_LOG_RETENTION_DAYS` 기본 365일, 임시 보관함 정리 루프에 얹어 함께 정리
- 화면: 시스템 로그 표에 대상·접속 주소 열 추가(대상은 UUID 앞 8자만)
자체검증 — 프로젝트 생성·하드삭제를 실화면에서 돌려 두 줄 모두
`프로젝트 e1040640… · 127.0.0.1 · 2026-09-06 09:54/09:55` 로 남는 것 확인.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
노선을 예상노선(원본)·계획노선(수정본) 두 벌로 나눔 (PLAN 0-7 사용자 확정).
- 예상노선 정본 `B05_Profile/route/expected_route.csv` 신설 — 자동 체인이 한 번
씀. 초기값 스냅샷 안에도 같은 CSV 가 있으나 그 폴더는 재확정 체인이 지우므로
스냅샷 밖에 한 벌 둠.
- 계획노선 수정본 `B05_Profile/route/planned_route.csv` — 설계 계통(`load_design_route`)이
수정본 → 예상노선 → 스냅샷 순으로 읽음. 노선 초기화는 수정본을 지우는 것.
- `GET /route/plan` 두 노선 정점 반환(모달이 점선·실선으로 그림).
- `POST /route/replan` 고친 노선을 수정본에 쓰고(조밀화) 재확정 체인 재사용 —
배수유역 다시 → 관 새로 → 계획선·종횡단 재생성 → 옛 측점 설계 누가거리 이월.
- `POST /route/replan/reset` 수정본 삭제 후 같은 재계산.
- 횡단배수 지점은 노선이 바뀌면 저장분을 버리고 새로 계산(사용자 확정).
구조물은 프로젝트 정본이라 옛 측점값 그대로 남음.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- 전체 맞춤(토량 분배) 보기는 측점 세로선을 그리지 않음 — 67개가 촘촘히 서서
곡선을 덮기만 하고 자리도 못 읽음. 공용 차트에 `hideStations` 축 옵션 신설,
분배를 끄면 예전대로 나옴.
- 같은 보기에서 좌우 여백 축소 — 종단과 맞물릴 필요가 없으므로 반 칸 들여쓰기
(originOffset)를 빼고 우측 여백 12px. 곡선이 폭을 다 씀.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
별표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>
B05·B06 계산을 서버·브라우저 양쪽에서 돌 수 있게 하는 작업의 1단계.
배수관 세트(배관·세월교·BOX암거·물넘이포장·독립 기슭막이) 제원이 서버에만
있어 브라우저는 스펙 칸을 하나씩 옮겨 적는 부분 사본으로 버티고 있었다.
- common_util_culvert_sets.ts 신설 — B06_Section_Engine_Culvert.py 의 짝.
매 상세 조회마다 도는 자리라 Node 왕복 대신 짝을 택함(CLAUDE.md 5장 ①).
- 거울 테스트 추가 — 다섯 시설을 태워 측점별 딕셔너리째 대조.
- 흩어진 임시 산식 3곳(관경÷1000 · BOX 구체 길이 · 날개벽 바닥판 연장)을
이 한 벌로 바꿈.
검증: tsc --noEmit 통과, pytest 387 passed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
feat(B06): 구조물 면적·유토곡선 서버 계산 자리 마련 + 상단측 저장 누락 수정
계산 자리 일원화(CLAUDE.md 5장) — 브라우저에서만 돌던 두 계산을 서버가 같은 TS 로
한 번 더 돌려 정본에 얹음. 파이썬 포팅 금지(기하가 두 벌이 되면 그림과 수량이 갈림).
- B06_Section_Server_Calc_Node.ts 신설 — 구조물 폐회로 면적 계산 후 그 위에서
유토곡선을 쌓음(화면과 같은 순서). balloon 위치는 서버가 만들지 않음.
- B06_Section_Structure_Layouts.ts 신설 — 정본만 읽는 제어기 흉내를 B07 도면에서
떼어 공용화. B07·서버가 같은 한 벌을 씀.
- common_util_node_bundle.py 신설 — 번들 빌드·실행 배관 공용화(코리도도 이걸 씀).
- 전처리 체인(초기값 스냅샷 앞)·[저장]·[확정]에서 서버 재계산 호출.
- 상단측(측구 방향) 변경이 B05 [임시저장]에만 실리던 것을 B06 [저장]·[확정]에도
실음 — flushUphillOverrides.
- 죽은 세션 등록 항목 pipes 제거(읽는 곳도 쓰는 곳도 없었음).
검증: tsc --noEmit 통과, pytest 386 passed, tmp/tests/test_b06_server_calc_node.mjs 통과.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@
2026-09-06 사용자 확정 — 구조물 **자체 면적을 빼는 것이 아니라**, 지반선과 실제로
그려지는 설계선이 이루는 폐회로의 넓이다. 기존 계산은 표준 설계선만 보아 구조물이
있는지조차 몰랐다.
- 그리는 쪽이 이미 만든 트림(`designTrim`)을 그대로 받아 「실제로 그려지는 설계선」을
세운다 — 트림 안쪽은 설계선, 바깥은 구조물 성토부선, 그것도 없으면 원지반(면적 0).
그림과 면적이 같은 입력을 쓰므로 둘이 갈리지 않는다.
- 벽면은 **수직**이라 트림 경계 바로 바깥에 점을 하나 더 찍는다. 안 넣으면 사다리꼴이
단차를 비스듬히 이어 붙여 없는 면적이 생겼다(실측 6.25㎡ → 7.5㎡).
- 절토는 암 경계선이 있으면 기존 규칙대로 토사/암으로 나눈다.
- 확인(node) — 전구간 100㎡ · 벽에서 끊음 6.25㎡ · 구조물선 포함 20㎡.
한계: 서버는 아직 구조물 기하를 몰라 이 보정이 **브라우저 계산에만** 실린다. 저장·확정
뒤 서버 재계산에서는 표준 값으로 돌아간다 — 서버 반영은 남은 작업(계획서 3-2).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-06 사용자 확정. 별표2 Ⅰ.2.나.(4) 확폭표(R 10~45m → 2.25~0.25m)를 측점별
평면 곡선반경에 물려 차도 폭을 넓힌다.
- 확폭 방향은 **곡선 바깥쪽 편측** — 노선 폴리라인의 외적 부호로 회전 방향을 보고
바깥쪽을 정한다(좌회전이면 우측). 측점 기록에 `curve_outer_side` 로 실린다.
- 차도 반폭을 좌·우로 나눠 들어 한쪽만 넓어지게 함. 확폭이 0이면 예전과 같은 대칭
단면이다. 노견·측구·사면은 그 바깥으로 그대로 밀린다.
- 확폭을 더한 유효너비는 법정 상한 5m 에서 자른다(규격 3.0m 면 최대 2.0m 까지).
- 계산 짝을 함께 고침 — 파이썬 `compute_cross_design` 과 브라우저 `computeCrossDesign`,
표는 양쪽에 두되 짝임을 주석으로 못 박음. 확폭 입력은 측점 기록에서 뽑는 헬퍼
하나로 9개 호출부(횡단·확정·B07 도면)에 같은 값이 가게 함.
- 횡단도에 노폭 라벨 — 확폭이 걸리면 「노폭 4.5m (규격 3.0 + 확폭 1.5)」로 적는다.
- 확인: 표 경계·편측 적용·5m 상한·회전 방향 판정 5건(pytest) + 브라우저 표 15건 일치.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
사용자 보고(2026-09-04) — 배수관 2 자리에서 계획선이 수직으로 꺾이고 최대 기울기가
**155,791 %**. 원인은 「가까운 점」이 아니라 **한 구조물이 층마다 다른 누가거리를 든 것**:
횡단 측점 264.054626 · 종단 정본 변화점 264.055 · 배수 정본 파일 **264.06**(cm 반올림).
사이드 목록이 든 264.06 으로 계획고를 편집하면 정본 옆 5mm 자리에 변화점이 하나 더 서고,
둘 사이 종단곡선이 mm 로 쭈그러들어 기울기가 거리 0 에 가까운 값으로 나뉘었음.
2026-09-03 에 ▲▼ 버튼 경로만 막아 뒀고(같은 증상 11,858%), 측점 테이블·도구 경로가 남아
다시 터진 것.
- **저장**(`common_util_drainage_pipes.py`) — 누가거리를 좌표(x·y)와 같은 **밀리미터 기준**으로
남김(cm 반올림 제거). 종단 정본 변화점(3자리)과 값이 어긋나지 않음.
- **화면**(`_UI_Profile_Panel.ts`) — 구조물 측점 목록이 들어오는 **한 자리**에서 종단 정본
측점(0.1m 안)으로 누가거리를 갈아 끼움. 버튼·테이블·도구 **모든 편집 경로**가 함께 막힘.
이미 cm 로 저장된 옛 프로젝트도 이 경로로 안전해짐.
자체검증(공용 브라우저 임시 탭):
① 264 부근 그래프 측점이 **264.055 하나**로 모임(고치기 전 264.06 이 따로 있었음)
② 그 구조물 측점 계획고를 8칸 올려도 **수직 꺾임 0건**, 계획선 점 수 1,208 불변,
최대 기울기 66.0 → 183.5%(8칸 올린 만큼의 정상값. 고치기 전 155,791%)
③ 전체 시험 381 통과·17 건너뜀.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
사용자 보고(2026-09-04) — 「종단 그래프 왔다갔다하면 Y축 값이 비는 경우가 있음」.
세로창을 변환으로 옮기면서 눈금을 **배율이 크게 바뀔 때만** 다시 만들게 해 뒀는데,
창이 위아래로 **이동만** 하면 배율은 그대로라 눈금이 한쪽으로 쓸려 나가고 그 자리가 빔.
- 새 창에 있어야 할 눈금 수와 화면에 남는 수가 다르면 눈금을 다시 만들게 함(요소 20개
안팎이라 값싸다). 배율 문턱 판정은 그대로 둠.
자체검증(공용 브라우저 임시 탭): 확대 4칸 뒤 좌우로 6번(+900·+900·−600·+1400·−1800·+2400px)
오가며 눈금이 플롯을 덮는지 측정 — 눈금 6~11개가 항상 이어지고 **위·아래 빈틈 최대 37px**
(플롯 높이 329px, 창 여유 20%에 해당). 고치기 전에는 플롯 절반이 비었음.
전체 시험 381 통과·17 건너뜀.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- 완료 카드에서도 [파일 선택]을 보임. 올리는 중에만 감춤. 교체 확인 모달과
교체 업로드 규칙은 이미 있던 것을 그대로 씀.
- 업로드 관문이 전처리 1단계 IN_PROGRESS 를 무조건 「분석 중」으로 읽어,
자동 확정이 보류된 프로젝트는 영영 새 자료를 받지 못했음. 전처리 진행 파일이
awaiting_confirmation·completed·failed 로 끝나 있으면 받도록 고침.
실제로 도는 중(analyzing)이면 그대로 막음.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
사용자 확정(2026-09-04) — 「버티기 + 부드럽게」. 누가토량 곡선은 표고와 달리 가팔라,
급한 구간이 창에 들어오면 세로 폭이 한 칸에 228 → 1,019㎥(4.5배)로 튀었음(실측).
- **버티기** — 곡선이 지금 창 안에 들어오고 창을 55% 넘게 채우면 창을 아예 안 건드림.
- **부드럽게** — 바꿔야 할 때도 한 번에 안 가고 다시 그릴 때마다 남은 만큼 20%씩 좁힘
(약 0.15초에 걸쳐 미끄러짐). 아직 안 끝났으면 다음 프레임에 한 걸음 더.
- 여유를 5% → **20%** 로 넓혀 곡선이 위아래 끝에 딱 붙지 않게 함(종단과 같은 값).
- 곡선 계산(`computeMassHaulSeries`)을 입력 객체 단위로 아껴 둠 — 스크롤 중에는 입력이
그대로인데 그릴 때마다 다시 계산해 프레임을 먹었음.
자체검증(공용 브라우저 임시 탭):
① 한 칸씩 8번 굴려 세로 폭 257·257·257·257 → 1,031 … : **4칸은 아예 그대로**(버티기)
② 창이 바뀌는 순간 궤적 257 → 711(42ms) → 882(80ms) → 1,030(151ms): 계단이 아니라 미끄러짐
③ 휠 20칸 기준 최대 프레임 27.4 → **24.0ms**, 33ms 초과 **0건**(고치기 전보다 나아짐)
④ B06 최대 프레임 21.2ms, 33ms 초과 0건 ⑤ 전체 시험 381 통과·17 건너뜀.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
사용자 보고(2026-09-04) — 「0선이 갑자기 위로 넘어가고 그 영역 채움색이 눈금 상한을 넘어
표현됨」. 보이는 구간에 세로를 맞추면서 0이 창 밖으로 나가는데, 유토곡선에는 **자르는
영역(clip)이 없어** 0선과 면 채움색이 범례·토글 버튼 위까지 그대로 칠해짐. 종단 그래프는
진작 자르고 있었는데 유토곡선만 빠져 있었음.
- 그래프 칸(축 오른쪽 · 상단 여백 아래)을 자르는 겹 둘을 둠 — 측점 세로선이 면과 곡선
**사이**에 서야 해서 앞뒤로 나눔(그리는 순서 유지).
- 면·곡선·0선·EP 표식·선택 강조·말풍선·토량 분배를 그 겹 안으로 옮김.
- 0선은 창 안에 있을 때만 그리고 눈금에도 그때만 실음 — 밖으로 나갔는데 실으면 좌측
고정 축 맨 위에 남의 값과 겹쳐 찍힘(사용자 확정: 0선은 나가도 됨).
- 눈금 글자는 축 **왼쪽**이라 자르는 겹에 넣지 않음(넣으면 사라짐).
자체검증(공용 브라우저 임시 탭): 확대 4칸 + 휠 25칸으로 0을 창 밖으로 보낸 상태에서
① 자르는 겹 2개 확인 ② 0선·0눈금 사라짐, 눈금 5개 전부 음수(겹쳐 찍힌 0 없음)
③ 영역 캡처로 면 채움색이 그래프 칸 위 경계에서 정확히 멈추는 것 확인.
전체 시험 381 통과·17 건너뜀.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
사용자 지적(2026-09-04) — 「유토곡선도 반영해야지. B06 페이지도 있잖아. 같은 로직일 텐데
왜 반영이 안 된 거야」. 그리는 쪽(종단 렌더러)은 공용이라 이미 바뀌어 있었고, **스크롤을
받아 창을 옮기는 쪽이 화면마다 따로**라 B05 만 연결돼 있던 것.
- 세로 창 모듈을 `common_util_chart_ywindow.ts` 로 옮겨 B05·B06 이 함께 씀.
좌측 고정 축은 표식(`b05-profile__yaxis--ywindow`)이 붙은 것만 옮김 — B06 은 한 컨테이너에
종단·유토곡선 축이 함께 살아 남의 축까지 옮기면 안 됨.
- 세로 과장(B06 조절값)을 각인·반영 — 예전에는 과장이 1일 때만 대상이었음.
- 유토곡선은 변환이 아니라 **곡선만 다시 그림**(스크롤마다). 곡선 위 말풍선·EP 표·측점
점처럼 세로로 늘어나면 안 되는 것이 많아, 그것만 따로 옮기는 값이 다시 그리는 값보다 큼
(유토 SVG 234개 = 종단의 1/3).
- B06 상단 패널을 통째로 다시 그리던 스크롤 처리를 걷어내고, 종단은 변환·유토곡선은
제자리 교체로 바꿈. 700줄 제한에 맞춰 종단 조립을 `_View_Chart.ts`(116줄)로,
유토곡선 조립기를 `_View_MassHaul.ts` 로 분리(727 → 718줄).
자체검증(공용 브라우저 임시 탭):
① B05 유토곡선 눈금이 스크롤을 따라 4.3㎥ → −1,348.8㎥ 로 이동(예전에는 안 따라옴)
② B06 종단 눈금 830/840/850m → 824/826/828m, 유토곡선 68.2㎥ → −3,375.1㎥ 로 동시 이동
③ B06 최대 프레임 **35.9 → 23.5ms**, 33ms 초과 1 → **0건**(중앙값 16.7ms 유지)
④ B05 최대 프레임 27.4ms, 33ms 초과 0건 ⑤ 전체 시험 381 통과·17 건너뜀.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
사용자 확정(2026-09-04) — 「다른 로직과 동일하게: 전처리 계산 마지막에 연산 후
영구저장, 진입 시 로딩, 조작은 캐시, 저장·확정 때 저장」. 예전에는 사용자가 B05에
처음 들어간 그 순간 브라우저가 17MB 규모를 만들어 첫 진입이 느렸음.
계산은 **재구현하지 않음** — 브라우저가 쓰는 TS 빌더(8,209줄)를 Node 로 그대로 돌림.
- `B05_Profile_UI_Corridor_Envelope.ts` 신설 — 저장 형식·버전 해시·직렬화를
`_UI_Corridor.ts` 에서 그대로 떼어 브라우저·서버 공용으로 둠(내용 불변).
- `B05_Profile_Corridor_Node.ts` 신설 — 입력 JSON 을 받아 저장본을 내놓는 진입점.
`npm run build:corridor` 로 번들(335kB), `npm run build` 에 물림.
- `B05_Profile_Corridor_Prebuild.py` 신설 — 상세·노선점을 모아 Node 실행 후 저장.
번들이 TS 원본보다 낡으면 스스로 다시 만듦(두 그림이 갈라지는 것을 막는 장치).
- 체인 마지막(스냅샷 직전)에 호출. 실패는 비치명적 — 저장본이 없으면 브라우저 폴백.
- 초기값 — 스냅샷이 코리도를 `initial_corridor.json` 으로 함께 뜨고, [초기화] 복원 때
**새 route id** 이름으로 되돌림(복원이 새 번호를 만들기 때문).
- `window.__corridorSource` 디버그 훅 — 저장본을 썼는지 다시 만들었는지 화면 밖 확인용.
자체검증: 전체 시험 381 통과·17 건너뜀(옛 테스트 2건은 이름·경로 변경에 맞춰 갱신).
실경로 — 용화 프로젝트(route 139)로 사전 생성 성공, 브라우저가 그 저장본을
`source: "stored"`, 해시 `377c2a84` 로 **그대로 채택**(재빌드 0회, 전송 278ms).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- 계획노선 사용 범위: B02 등록에 시작·종료 누가거리 두 칸 추가, B01 수정 모달에서도
변경. projects.route_start_m·route_end_m 신설(015_route_range.sql).
load_design_route 가 범위 절단 → 서피스 트림 순서로 적용. 시작 >= 종료는 화면·서버
양쪽에서 차단. 비우면 전 구간으로 종전과 같음.
- 서피스 절단 여유 기본값 30m → 3m (SURFACE_ROUTE_EDGE_TRIM_M).
- B04 지도·B05 배수유역도 줌 상한을 「화면 폭 20m」 기준으로 계산(고정 8배·16배 폐지).
4배를 넘으면 배경 그림 흐림 보간 해제.
- 계획선 위 측점 눈금·번호 표기(측점번호+잔여거리). 관 마커와 겹치면 반대쪽으로 밀고,
되꺾임 구간에서 라벨이 겹치면 건너뜀. 그리기 코드는 두 화면 공용.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
빈 도각이던 라이다 계획평면도에 지표면 탑뷰 그림을 얹음.
- 확정 DTM 격자를 도곽 범위로 잘라 음영기복 PNG 를 만들고 Image 엔티티로 실음
(북서 315도·고도 45도, 한 변 최대 1,600 px). 점구름 4,900만 점을 그대로
그리지 않음.
- 어느 지표면을 쓸지는 1단계 확정값을 따름 — DrainageContext 에 surface_params
를 실어 전달.
- 축척·도곽·장 나눔은 계획평면도와 같음(1/1,200) — 노선이 같은 자리에 섬.
- entities_bbox 가 꼭짓점 배열(points)을 세도록 고침. 세지 않으면 그림이 도곽
계산에서 통째로 빠짐.
검증(용화_LAS): 콘텐츠 726.1x487.2 mm ≤ A1 작도영역, 그림 범위 안에 노선이
완전히 들어감, 음영기복 준비 0.4초·자료 214 KB. 능선·계곡이 눈으로 구분됨.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- 스냅샷 대상이 B04_PreProcess/drainage/edits 에서 drainage 폴더 통째로 넓어짐
(00_watershed_response ~ 04_detailed_basins 포함). 관을 옮겨 다시 나뉜 세부유역이
초기화로 되돌아가지 않던 구멍을 막음
- 용량 실측: 배수유역 8.6MB, 스냅샷 전체 3.9MB → 약 12MB
- 옛 스냅샷(edits 만 촬영) 호환 — 그 경우 예전처럼 관 지점 편집분만 복원
- 3D 코리도는 제외 — 스냅샷 시점(체인 직후)에 아직 없는 값이라 별도 판단 필요
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- 워크플로 상태 응답에 project_name 추가. 좌측 제목 패널 같은 행 오른쪽 끝에
프로젝트 이름 표기(길면 말줄임, 전체는 툴팁). 이름표를 오버레이 쪽에 두어
레이아웃을 직접 조립하는 B03 까지 여섯 화면이 한 곳으로 반영됨.
- fetchProjectWorkflowState 가 {status, workflow_state} 껍데기를 벗기도록 수정.
- 계획노선을 「업로드한 CSV」로 적은 주석을 「계획노선(정본)」으로 정리.
업로드 판정이 .csv 개수만 세어 shapefile 을 막던 것도 .shp 포함으로 맞춤.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- 줌 버튼을 셋(줌인·줌아웃·초기화)으로 줄임. 세로 배율·창 이동 버튼 제거
- 배율 1 = 기본값이자 축소 한계 — 한계에 닿은 버튼은 흐리게 죽임
- 세로는 보이는 누가거리 구간의 지반·계획선 범위로 자동(공통 함수
windowElevationRange, B05·B06 종단이 함께 씀). 스크롤이 멈춘 뒤 0.16초에 갱신
- 계획고 편집 버튼을 누르고 있는 동안 Y 축 고정, 손을 떼면 다시 맞춤
- 유토곡선 Y 도 같은 창 기준(전 구간 ±200㎥ 고정 해제)
- B06 종단이 쓰던 공통 Y 스케일(calculateYScale) 제거 — 횡단 카드는 원래 안 쓰던 값
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
사용자 조작 중의 계산은 브라우저 안에서 끝나야 함(2026-09-03 사용자 확정). 계획고가
바뀔 때마다 전 측점 횡단 계산이 `POST …/cross-design/preview` 로 나가 왕복이 조작
속도를 지배했음.
- `refreshCrossDesigns` 를 로컬 계산으로 교체. 계획고는 저장 선형(`base_pvi`)에 편집
델타를 얹어 TS `buildAlignment` 로 여기서 풂 — 호출부(B05·B06) 시그니처는 그대로라
단일 창구가 유지됨. 서버 프리뷰는 선형 저장분이 없는 옛 데이터 폴백으로만 남음.
- 표준단면 config 기본값·암 경계 기본 오프셋을 `sections/context` 응답에서 세션에 기억
(`rememberStandardDefaults`·`rememberRockBoundaryDefault`). 프론트에 config 수치를
복제하지 않으면서 패널 없는 B05 도 같은 값으로 계산함.
- 디바운스 250 → 60ms — 왕복이 사라져 손을 떼는 즉시 유토곡선이 따라옴.
- 낡음 판정을 `hasStaleDesigns` 로 바꿔 첫 건에서 중단하고, 계획선 유효 샘플을
샘플 배열당 한 번만 걸러 이진탐색(그리기 프레임당 2.33ms → 0.07ms 실측).
- 파이썬 엔진 2파일·프리뷰 라우터에 「TS 짝 파일과 한 벌」 경고 블록 추가.
검증: `tmp/tests/test_b06_cross_design_mirror.py` 10건 — 토사/암 2단계/L형 측구/포장/
세월교 하강/사면 미닫힘 등에서 파이썬과 TS 결과가 면적·설계선까지 일치. 전체 183건 통과.
`tsc --noEmit` 통과. 실측: route 127(측점 128) 전 측점 로컬 재계산 7.5ms.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
사용자 조작 중 계산은 브라우저 안에서 끝나야 함(2026-09-03 사용자 확정). 계획선을
만질 때마다 전 측점 횡단 계산이 서버로 나가 조작 속도를 왕복이 지배했음.
- `common_util_cross_design_areas.ts` — `B06_Section_Engine_Areas.py` 미러
(사다리꼴 적분 + 절토 토사/암반 분리).
- `common_util_cross_design.ts` — `B06_Section_Engine_Design.py` 미러
(표준횡단 설계선 구성 + 단면적). config 수치는 복제하지 않고
`sections/context.standard_cross_section` 을 입력으로 받음.
두 파일 머리에 짝 파일·회귀 테스트를 가리키는 경고 블록을 둠 — 한쪽만 고치면 두 화면
값이 갈림. 아직 어느 화면도 이 모듈을 부르지 않음(다음 커밋에서 연결).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
같은 폴백 사다리가 네 곳에 서로 다른 모양으로 흩어져 있었고, 그중 배수유역 라우터
`_route_center_lonlat` 은 노선 CSV 의 `crs_epsg` **라벨**을 그대로 변환에 썼다(라벨과
실좌표계가 다른 사례 실측 — 2026-09-01 용화 라벨 5179 / 실제 5176).
`common_util_crs.resolve_project_crs()` 신설 — 사다리는 ① 노선 crs_input ② 파일 라벨
(원본 좌표를 읽는 자리만) ③ 지형 PRJ(작업 좌표계 정본) ④ DB epsg ⑤ EPSG:5186.
작업 좌표계는 **지형 PRJ 우선으로 확정**(2026-09-03 사용자 결정 — 서피스 격자가 모델좌표의
주인이라 현행 유지). `project_epsg_from_prj()` 도 이 창구로 위임.
더해 지표면 트림이 노선을 통째로 지울 때 노선·지표면 bbox 를 함께 로그에 남긴다 —
"겹치지 않습니다" 만으로는 좌표계 문제인지 측량 범위 문제인지 갈리지 않았다.
검증 — `tmp/tests/test_project_crs_resolution.py` 5건 신설(사다리 4·진단 1),
전체 375 passed·17 skipped, ruff format 무변경.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
100㎡ 미만 유역은 `polygonize_labels` 의 최소면적 필터에 걸려 폴리곤이 아예 만들어지지
않았다. 유역 목록에는 있는데 지도에는 없는 상태 — 실측에서 용화 22개 중 1개(64㎡),
S자 48개 중 1개가 그랬다. 링 목록이 조각을 싣게 된 뒤로는 버릴 이유가 없다.
실측: 폴리곤 면적 오차(셀 면적 대비 평균) 용화 5.76%→1.86%, S자 3.56%→1.42%,
경계 없는 유역 1개→0개. 좌표점은 583→622 · 806→829 로 거의 그대로다.
1차 전체 배수유역 등 다른 호출부의 기본값(100㎡)은 그대로 둔다.
S자 실사례 검증: 용화 지형 위에 스위치백 3구간 합성 노선(정점 439·연장 4,528m)으로
신규 프로젝트를 만들어 자동 체인까지 완주 — 관 48개·세부유역 48개, 경계 없는 유역 0개.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
단일 링 자료형이 곧 중첩·빈공간이었다. 아래 유역이 위 유역을 감싸면 구멍이 사라져 위
유역을 통째로 덮고(합성 실측 256㎡ 전량), 한 관의 유역이 두 조각이면 작은 쪽이 사라져
빈공간이 됐다(144㎡).
- `polygon_parts()` 신설 — 조각마다 [외곽 링, 구멍 링...], 넓은 조각부터.
- `WatershedBasin.boundary_parts` 로 교체. `boundary_xy`(가장 넓은 조각의 외곽)와
`boundary_rings`(편 링 목록)는 파생 속성이라 옛 소비처는 그대로 동작한다.
- API 에 `polygon_rings_lonlat` 추가. 저장 GeoJSON 은 구멍을 가진 Polygon, 조각이
여럿이면 MultiPolygon.
- 캔버스 4곳(B04 유역화면 채움·선택, B05 배수유역도 채움·선택)은 even-odd 로 한 번에
채우고 `pointInRings()` 로 판정 — 구멍 안을 눌러도 바깥 유역이 잡히지 않는다.
중복이던 지역 `pointInRing` 은 삭제하고 공용 것으로 통일.
검증: 단위 5건 신규(도넛 구멍 보존·조각 2개 보존·파생 속성·빈 경계·조립 경로 전체에서
링 2개), 전체 168 passed, typecheck 통과. 브라우저 — API 가 유역 22개에 링 목록을 실어
보내고, 캔버스 even-odd 실측(가운데 알파 0 / 고리 255)으로 구멍이 실제로 뚫린다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
옛 방식은 한 칸 이웃만 보는 국소 하강이라 1m 간격 계획고의 미세 요철에 걸려 멈췄다.
용화 실측: 측점 2,138개 중 1,898개(88.8%)가 저점에 갇혀 흐름이 아니라 누가거리
최근접으로 관에 붙었고, 그 결과 도로 셀 43.2%가 자기보다 높은 관에 배정됐다
(최대 6.82m 오르막).
이웃한 두 관 사이의 최고점(마루)을 분수령으로 삼아 왼쪽은 앞 관, 오른쪽은 뒤 관이
받게 바꿈. 1차원에서 물은 마루를 넘지 못하므로 물리적으로 정확하고 미세 요철에 면역.
첫 관 앞·마지막 관 뒤는 그 관이 받는다. 관 순서는 호출자가 준 그대로 보존.
검증: 단위 5건 신규(전체 163 passed), 실측 오르막 배정 43.2% → 17.6%(최대 4.15m),
잔여 1,993셀은 전부 사그 저류 후 더 낮은 장벽으로 넘치는 경우(어긋남 0).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- 자동설계 체인이 초기값 스냅샷에 `planned_route.csv` 를 함께 남김. 값은 체인이
이미 만든 정점(사업지 좌표계·트림·조밀화 후)이라 재계산 없음.
- `load_design_route()` 는 surface_params 가 주어진 호출에 한해 그 CSV 정본을 읽음.
트림 전 원본이 필요한 호출(도엽 범위)은 종전 경로 유지. 지표면·노선이 바뀌면
`discard_initial_snapshot()` 이 폴더째 지워 경로가 저절로 닫힘.
- `run_sheet_surface_analysis()` 가 노선을 원본 좌표계로 읽어 도엽 서피스를 딴 자리에
만들던 문제 교정 — `load_design_route()` 로 창구 통일, 도엽 확보 PRJ 탐색도 지형
PRJ 폴더로 교정. shapefile 노선(5179) + 지형 PRJ(5176) 조합에서 트림이 노선을
통째로 지우던 원인.
검증: tmp/tests 158 passed(신규 4건 포함), 공용 브라우저 실사용 경로로 용화 shapefile
한 벌 업로드 → 자동 체인 완주 → CSV 정본 643점, 원본 경로와 최대 좌표차 0.00007m.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
평형선·장비 띠·물량 balloon 이 안 생기는 노선에서 그 이유를 화면에서 읽을 수 있게,
곡선 끝의 누가토량을 요약 칩으로 드러냄. 0에 가까울수록 절·성토 균형이며 곡선이
0선을 넘지 않으면 평형선이 서지 않는다는 설명을 툴팁에 담음. 판정·조정은 사용자
몫이라 자동 보정 로직은 두지 않음(2026-09-03 사용자 확정).
B05 종단 패널과 B06 횡단 화면이 같은 함수를 쓰므로 양쪽에 함께 반영됨.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
증상: 파일 입력 직후 초기 계산값과 B05 [초기화] 결과가 다름.
원인
- 자동설계 체인이 5단계까지 전부 성공해야만 `save_initial_snapshot()` 호출.
중간에 깨지면 `initial_snapshot/` 미생성 → [초기화]가 복원 대신 재계산 폴백.
- 재계산 폴백의 지표면 기준이 체인과 다름 — 체인은 stage 1 확정값
(`get_surface_confirmation_params`), [초기화]는 config 기본값
(`surface_confirmation_defaults`). 실측: 프로젝트 stage1 `classification`/5m 대
config `csf`/1m.
- 체인이 깨져도 WF1 은 초기 분석 완료 메일을 그대로 발송.
수정 (2026-09-02 사용자 확정 — 부분 결과는 분석 안 됨과 다르지 않으므로 부분
스냅샷은 만들지 않음)
- `initial_design.failed` 마커 신설 — 프로젝트 루트(스냅샷 4트리 밖), 실패 사유 기록.
체인 진입 시 옛 마커 제거, 실패 5지점 + 스냅샷 저장 실패에서 기록.
- WF1 이 마커를 읽어 완료 메일 대신 `send_initial_design_failed_email()` 발송
(관리자 주소 `ADMIN_EMAIL`, 없으면 주소 없이 안내).
- B05 [초기화]: 스냅샷 있으면 복원, 실패 마커 있으면 409 `initial_design_failed`
로 거부(DELETE 앞에서 반환 — 데이터 무변경), 옛 프로젝트만 종전 재계산 폴백.
- 재계산 폴백의 지표면 기준을 `get_surface_confirmation_params()` 로 통일.
자체검증: `pytest tmp/tests/ -q` 366 passed / 14 skipped / 0 failed (+4).
공용 브라우저 실측 — 실패 프로젝트 [초기화] 409 응답·`routes` 행 무변경 확인.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
사용자 지시(2026-09-02) — 로고·서명을 임의로 만들어 표제란에 넣고, 위치·크기는
도각을 고치면 따라오게 하며, 시행청·과업책임자 등은 DB를 넓혀 채울 것.
도각(표제란)
- `00_template_A1.json` 에 그림 자리 2개 추가 — 회사 로고는 용역회사 칸 왼쪽
(316~348 × 20~36mm), 설계자 서명은 설계 칸 아래(638~680 × 17.5~25.5mm).
자리·크기가 **템플릿 좌표로만** 정해지므로 도각을 고치면 그대로 따라옴.
- `_fill_placeholders` 가 그림 자리(`imageData`)의 `{{키}}` 도 채움. 값을 못 구하면
빈 문자열을 남기지 않고 **엔티티째 제거** — 빈 값은 CAD 가 깨진 그림으로 그림.
- `_transform_entity` 가 `points` 배열도 옮김. 그림(로고·서명)과 띠(Hatch)가 이 키를
쓰는데 여태 변환 대상이 아니라 도각을 옮기면 제자리에 남았음.
DB (`011_title_block.sql`, 전부 ADD COLUMN·NULL 허용)
- `projects` — `client_org`(시행청), `pm_user_id`·`field_lead_user_id`·`designer_user_id`
- `companies.logo_path` · `users.signature_path` — 그림은 파일로 두고 경로만 담음
(기존 `storage_path`·`input_files` 와 같은 방식)
- FK 는 걸지 않음 — 사유를 파일 머리말에 적음(소프트 삭제·LEFT JOIN·4환경 공유).
배선
- `_title_block_fields` 가 시행청·과업책임자·분야별책임자·설계자(배정 없으면 소유자)와
로고·서명 data URL 까지 실어 보냄.
- `read_stored_asset()` 신설 — `storage/` 기준 상대 경로의 **파일**을 읽음.
`resolve_stored_project_path()` 는 폴더를 만드는 프로젝트 루트용이라 파일에 못 씀.
CAD 결함 1건 (이번 작업에서 드러남)
- `screenCanvas.drawController.drawImage` 가 `(width, height)` 를 좌표처럼 변환해
화면 오프셋과 y 뒤집기가 섞여 들어갔음 — 그림이 제 자리를 벗어나고 비율이 무너짐.
크기는 배율만 곱하고 자리는 세계 중심으로 잡도록 고침(SVG 컨트롤러와 같은 방식).
- 검증 창구 `__aisloCad.screen(x, y)` 추가 — 세계→화면 좌표. 그림·글자가 제 자리에
그려졌는지 픽셀로 판정할 때 씀.
검증: `pytest tmp/tests/ -q` 135 passed / 0 failed(그림 자리 3건 신규),
`npx vitest run` 87 passed / 0 failed, `check-types`·`build` 통과.
화면 실측(5174, wdw): 표준도 API Text 24개 `{{` 잔존 0, Image 2개가 도각 좌표
(316,20)-(348,36)·(638,18)-(680,26)에 실림. 캔버스 픽셀 판정 — 두 자리 모두 배경색
외 픽셀이 그려짐(로고 88px·서명 64px), 로고 파랑(20,70,140)이 슬롯 안에서만 검출.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
기슭막이가 관 지점 시설로 옮겨간 뒤(f916aa52) 이관 경로만 옛 스펙에 남아
`POST /route/structures/migrate` 가 400 으로 통째 실패했음 — 저장소가
`managed_by` 타입을 거절하는데 이관은 계속 구조물로 만들었기 때문. 같은 요청에
실린 대피소·기타까지 하나도 이관되지 않았음. 호출부가 B05 진입 경로라 레거시
프로젝트는 진입할 때마다 실패했음.
- B05_Profile_Structures_Migration: 결과를 MigrationPlan(구조물 / 관 시설)으로 나눔.
managed_by 타입은 레지스트리 기본값(사용자 확정분)을 승계한 PipePoint 로 만듦 —
구간은 기준측점 ∓ 전/후
- B05_Profile_Structures_Router: 관 시설분을 pipe_points.json 에 덧붙임. 관 지점
파일이 없으면 그 몫만 미루고 이력도 남기지 않음(원천 측점이 남아 재이관됨)
- common_util_drainage_pipes: read_pipe_points_file · append_pipe_points_file ·
pipe_points_path_in 신설 — 저장 당시 노선 지문 보존
- 관 시설에서 투영된 "기슭막이" 라벨이 되이관돼 "기타" 로 굳던 경로도 함께 막음
- B06_Section_Engine_Revetment 삭제 — 호출자 0건(벽은 관 세트가 만듦)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
① 3D 서피스 겹쳐보기 어긋남 — `write_glb()`는 넘겨받은 상자의 중심을 원점으로 삼는데
모델마다 자기 상자를 넘겨 도엽 서피스와 라이다 지표면이 다른 원점에 섰다
(2f940d8a 실측: 수평 7.85m·높이 1.28m). 도엽 서피스가 라이다 포인트 상자
(`structured.npz`, 화면 `setReferenceBounds`와 같은 값)를 화면 원점으로 쓰게 했다.
표고 격자 상자(`bounds`)는 절취 범위 그대로 두고 `scene_bounds`를 npz에 따로 남긴다 —
등고선 API도 이 값을 먼저 써서 등고선이 메시 위에 얹힌다. 라이다 없는 사업지는 기준이
자기뿐이라 종전대로 자기 상자를 쓴다.
② 세부유역 저장본 좌표계 — `04_detailed_basins.geojson`에 변환에 쓴 좌표계를
`crs_input`으로 남기고, B07 유역도가 그 값으로 되돌린다. 기록이 없는 옛 저장본은
노선 CSV의 EPSG 라벨로 쓰였으므로 그 라벨로 되돌린다(경고 로그 + 재확정 안내).
검증: tmp/tests 42 passed (신규 test_scene_origin_and_basin_crs.py 5건).
패치 코드로 도엽 서피스를 다시 만들어 GLB 정점 상자를 대조 —
sheet [-426.58, -68.95, -386.41]~[411.42, 71.52, 379.59],
csf [-172.12, -44.06, -199.69]~[184.11, 3.22, 165.72] 로 같은 원점.
수정 전 sheet는 [-419.5, -70.24, -383.0]~[418.5, 70.24, 383.0] (자기 중심)였다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>