Commit Graph
79 Commits
Author SHA1 Message Date
eomsangdonandClaude Opus 5 18c174036b feat(B07): 계획평면도(라이다) — 지표면 음영기복 배경 1차 배치
빈 도각이던 라이다 계획평면도에 지표면 탑뷰 그림을 얹음.

- 확정 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>
2026-09-04 19:00:38 +09:00
eomsangdonandClaude Opus 5 887444fe7c refactor(common_util): 횡단 설계 계산 700줄 초과 분리 — 단면 기하
728줄 한 파일을 둘로 나눔 (계산 불변, 순수 이동).
- `common_util_cross_design.ts` 405줄 — 표준단면 해석·설계선 조립·면적 산출
- `common_util_cross_design_geometry.ts` 333줄 — `SectionGeometry`(노면·측구·사면 설계고)
  와 그것만 쓰는 상수(행진 간격·최대 거리)·`sideRole`·`ResolvedGroup`

파이썬 짝 파일(`B06_Section_Engine_Design.py`)은 손대지 않음 — 미러 관계 유지.

검증: 공용 브라우저 B06 횡단 카드 실측과 서버 계산값 대조 — 측점 0에서
절토 화면 2.50㎡ / 서버 2.4985㎡, 성토 화면 1.85㎡ / 서버 1.8521㎡ 로 일치
(카드 273장 렌더, 콘솔 오류 0). `tsc --noEmit` 통과, tmp/tests 378 passed

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-04 11:30:22 +09:00
eomsangdonandClaude Opus 5 a35c8ff356 feat(B05,B06): 계획선 편집 중 횡단 재계산을 브라우저에서 수행 — 서버 왕복 제거
사용자 조작 중의 계산은 브라우저 안에서 끝나야 함(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 19:58:09 +09:00
eomsangdonandClaude Opus 5 c19af779e4 feat(B06): 횡단 설계 계산 TS 미러 추가 — 브라우저에서 절·성토 면적 산출
사용자 조작 중 계산은 브라우저 안에서 끝나야 함(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>
2026-09-03 19:42:31 +09:00
eomsangdonandClaude Opus 5 e83171b2b3 refactor(공통): 작업 좌표계 폴백을 창구 하나로 통합 + 트림 실패 진단
같은 폴백 사다리가 네 곳에 서로 다른 모양으로 흩어져 있었고, 그중 배수유역 라우터
`_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>
2026-09-03 13:25:20 +09:00
eomsangdon f91d589ed4 Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1 2026-09-03 13:16:14 +09:00
eomsangdonandClaude Opus 5 8393dfd619 fix(B04): 세부유역 최소면적 필터 제거 — 작은 유역이 통째로 안 그려지던 문제
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>
2026-09-03 13:13:00 +09:00
eomsangdonandClaude Opus 5 f1bcee7857 feat(B04): 세부유역 경계를 링 목록(외곽+구멍)으로 넓혀 중첩·빈공간 해소
단일 링 자료형이 곧 중첩·빈공간이었다. 아래 유역이 위 유역을 감싸면 구멍이 사라져 위
유역을 통째로 덮고(합성 실측 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>
2026-09-03 12:40:21 +09:00
eomsangdonandClaude Opus 5 6bca521516 fix(B04): 세부 배수유역 배정을 종단 마루 분수령 기준으로 교체
옛 방식은 한 칸 이웃만 보는 국소 하강이라 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>
2026-09-03 09:43:38 +09:00
eomsangdonandClaude Opus 5 56b9bf5280 feat(B03): 계획노선 shapefile 의 CSV 정본화 + LAS 없는 도엽 서피스 좌표계 교정
- 자동설계 체인이 초기값 스냅샷에 `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>
2026-09-03 08:17:21 +09:00
eomsangdonandClaude Opus 5 0e0dd74e92 feat(B05/B06): 유토곡선 요약줄에 최종 누가토량 칩 신설
평형선·장비 띠·물량 balloon 이 안 생기는 노선에서 그 이유를 화면에서 읽을 수 있게,
곡선 끝의 누가토량을 요약 칩으로 드러냄. 0에 가까울수록 절·성토 균형이며 곡선이
0선을 넘지 않으면 평형선이 서지 않는다는 설명을 툴팁에 담음. 판정·조정은 사용자
몫이라 자동 보정 로직은 두지 않음(2026-09-03 사용자 확정).

B05 종단 패널과 B06 횡단 화면이 같은 함수를 쓰므로 양쪽에 함께 반영됨.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 07:53:32 +09:00
eomsangdonandClaude Opus 5 2e57d29156 fix(B05,B03): 초기 설계 실패 시 초기값 부재 처리 — 실패 마커·안내 메일·[초기화] 거부
증상: 파일 입력 직후 초기 계산값과 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 18:03:18 +09:00
eomsangdonandClaude Opus 5 b1593bce83 feat(B07): 표제란에 로고·서명 자리 신설 + 시행청·담당자 DB 확장
사용자 지시(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>
2026-09-02 08:35:25 +09:00
eomsangdonandClaude Fable 5 1c95290a18 fix(B05): 레거시 기성막이 측점을 관 지점 정본으로 이관
기슭막이가 관 지점 시설로 옮겨간 뒤(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>
2026-09-01 20:18:33 +09:00
eomsangdonandClaude Opus 5 0a1ebcfbb5 fix(B04): 도엽 서피스를 라이다와 같은 원점에 놓고, 유역 저장본에 좌표계를 남긴다
① 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>
2026-09-01 17:52:27 +09:00
eomsangdonandClaude Opus 5 b50559d799 fix(B04): 노선 CSV의 EPSG 라벨 대신 사업지 실좌표계로 화면 좌표를 만든다
상세 배수유역 폴리곤이 지도에 안 그려졌다. 노선 CSV `crs_epsg` 열(라벨 5179)을
좌표계로 써서, 사업지 좌표계(.prj EPSG:5176)로 계산된 격자 산출물을 잘못 역투영한
탓이다 — 유역이 lon 119.79 / lat 23.08(대만 남쪽 바다)로 나가 화면 밖이었다.

`load_design_route()`는 노선을 .prj 좌표계로 재투영하며 `crs_input`만 갱신하고
`epsg` 라벨은 CSV 값 그대로 둔다(2026-08-31 확정). 그 라벨을 좌표계로 쓰던 자리를
모두 `crs_input`(= .prj 좌표계)으로 바꿨다.

- common_util_drainage_context: DrainageContext.epsg(int) → crs(str, pyproj 입력).
  상세 배수유역·관 지점 응답 좌표가 노선 위로 돌아온다.
- B07_DesignDetail_Router_Support: context.crs 그대로 사용.
- SheetSurface.build_sheet_surface_from_route: load_design_route로 노선을 읽어
  도엽 서피스를 라이다 지표면과 같은 좌표계에 만든다(기존엔 5179 격자로 만들어져
  라이다 DTM과 다른 프레임에 놓였다). LAS 없는 WF1 경로는 라벨·좌표가 한 벌이라 그대로.
- Router_Inflow._resolve_epsg: 격자 산출물과 같은 .prj 좌표계로 역투영.

검증: tmp/tests/test_drainage_crs.py 신규(용화 샘플 회귀) + tmp/tests 전체 37 passed.
build_detail 실행 결과 유역 11개 폴리곤이 lon 129.0947~129.0988 / lat 36.8107~36.8127
(노선 위)로 나온다 — 수정 전 같은 좌표는 119.7872 / 23.0817이었다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 17:39:19 +09:00
eomsangdonandClaude Opus 5 f82e60492e fix(security): 프로젝트 경로가 붙는 API가 그 프로젝트의 회사를 대조하게 한다
B03~B07 라우터는 로그인·회사 소속만 확인하고 URL의 project_id가 누구 것인지는 보지
않았다. 프로젝트 id만 알면 남의 회사 자료를 읽고 쓸 수 있었고, B07 도각 저장(PUT)은
그 회사의 공용 양식을 덮어쓴다.

require_project_access를 protected_with_company에 함께 걸어 경로에 project_id가 있는
요청만 한 곳에서 검사한다. 다른 회사면 403, 없는 프로젝트면 404, 시스템 관리자는 통과.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 16:11:24 +09:00
eomsangdonandClaude Opus 5 b014c4c1ec fix(B05): 노선 원천을 한 곳으로 모으고 도엽 서피스를 기본 하나만 만든다
용화 자동 체인이 끝까지 돌았는데 종단 계획선이 PVI 3개 직선으로 나왔다. 관 22개
측점이 92.6~2044.9m 로 트림 전 원본(2,136m) 기준인데 확정 노선은 1,070.4m 라
21개가 노선 밖이었다. 좌표계도 갈려 있었다 — 관은 EPSG 5179(노선 파일), 노선은
5176(프로젝트)에 저장돼 누가거리만 우연히 맞물려 있었다.

원인은 계획노선을 읽는 곳이 흩어져 있는 것이다. 트림·조밀화를 체인 한 곳에만
넣었더니 배수유역·유입·도엽은 원본을 그대로 읽었다.

- load_design_route() 신규 — 읽기·좌표계 변환·트림·조밀화를 한 곳에서 끝낸다.
  설계 계통(체인·배수유역)은 전부 이 함수를 지난다.
- Router_Watershed 도 이 함수를 쓴다. 좌표계가 프로젝트 기준으로 통일돼 관과
  노선이 같은 공간에 놓인다.
- 도엽 서피스는 SHEET_SURFACE_AUTO_METHODS(기본 laplace 1종)만 자동 생성한다.
  여섯 방식을 매번 만들면 WF1 891초 중 655초를 여기서 쓴다. 나머지는 관리자가
  화면에서 고를 때 만든다.

Router_Inflow 는 노선을 EPSG 라벨용으로만 쓰고 기하를 안 써서 제외했다.
Engine_Extent(배경 지도 범위)도 제외 — 지도는 트림 전 전 구간을 덮어야 사용자가
측량 범위 밖을 볼 수 있다.

실측(용화 route 120):
  배수유역 노선   2,136m -> 1,070m   격자 889x1435 -> 545x831
  1차 영역        538,574㎡ -> 263,170㎡
  관 좌표계       EPSG 5179 -> 5176
  관              22개(10개가 노선 밖) -> 11개 전부 노선 안
  종단 PVI        3개 -> 13개, 종곡선 1 -> 11, 불균형 97.0% -> 86.2%
  PVI 측점이 관 측점과 일치한다(반올림 오차 제외).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 15:53:59 +09:00
eomsangdon e4b859766b Merge remote-tracking branch 'origin/feat/b03-route-shapefile' into feat/b07-cover-template
# Conflicts:
#	B03_FileInput/B03_FileInput_Service_Chain.py
2026-09-01 15:34:07 +09:00
eomsangdonandClaude Opus 5 cf14958ea5 fix(B05): 예정노선 정점을 직결 문턱 아래로 좁혀 계획선을 그대로 채택한다
용화 예정노선으로 자동 체인을 돌리자 B05가 400으로 끊겼다.
  세그먼트 1 (BP -> CP1) 경로 탐색 실패: 종단경사 한계(26%)로 통과 경로가 없습니다.

원인은 제어점 간격이다. Solver.py:394 는 제어점 쌍이 direct_link_max_m
(ROUTE_DIRECT_LINK_CELL_FACTOR 2.0 x ROUTE_GRID_RES_M 2.0 = 4.0m) 이하면 격자 탐색
없이 직결해 원청 계획노선을 그대로 보존하고, 경사·곡선 위반은 경고로만 남긴다.

  샘플 CSV  350m / 135점 = 간격 2.6m  -> 대부분 직결   -> 통과(곡선 위반 9건은 경고)
  용화 예정  2136m / 121점 = 간격 17.6m -> 전부 탐색     -> 첫 구간 44.6%에서 실패

기존 프로젝트가 됐던 것은 제약을 만족해서가 아니라 탐색을 타지 않아서였다.

계획노선은 바꿀 수 없는 선이므로(사용자 확정), 같은 직선 위에 점을 더 찍어 간격만
좁힌다. densify_route()는 원래 정점을 모두 남기고 사이만 채운다 — 평면 형상도
연장도 끝점도 그대로다. 문턱과 정확히 같게 두면 부동소수 오차 한 번에 탐색으로
넘어가므로 ROUTE_PLANNED_DENSIFY_SAFETY(0.9)를 곱한다.

실측(용화, classification/dtm/smooth): 트림 후 63정점 -> 조밀화 323정점,
연장 1,070m 불변, 최대 간격 3.50m(문턱 4.0m), 시·종점 동일.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 14:30:56 +09:00
eomsangdon b747609ed8 auto: 2026-09-01 13:46 (ESD_LAPTOP) 2026-09-01 13:46:54 +09:00
eomsangdonandClaude Opus 5 8ac892d97d feat(B04): 계획노선을 3D 최고 표고 평면에 그리고, 지표면 밖 구간은 잘라 낸다
실무 자료 용화.las가 계획노선 2,136m 중 일부만 덮는다. 좌표 문제가 아니라 측량
범위 자체다 — LAS(VLR EPSG 5176)와 정사영상 용화.tif 범위가 서로 일치하고, 위경도로
datum 보정까지 태워도 위도는 완전히 포함되며 경도만 동쪽 431m 초과한다.

노선 3D 표시
- 계획노선을 지표면에 드리우지 않고 데이터 최고 표고(bounds.z_max) 평면에 수평으로
  얹는다. 노선과 측량 범위가 평면상 어디서 어긋나는지 보려는 것이라 지형을 따라
  오르내리면 오히려 판단이 어렵다.
- 색은 2D 지도·B05 배수유역도가 쓰는 routeLineColor()를 그대로 쓴다. 같은 선을 두
  화면에서 다른 색으로 그리면 같은 것인지 알아볼 수 없다.

노선 트림
- trim_route_to_surface(): DtmGridSampler 의 valid_mask 로 판정한다. bounds 사각형이
  아니라 불규칙한 실제 외곽이다. 가장 긴 연속 유효 구간을 남긴다.
- 가장자리 여유 SURFACE_ROUTE_EDGE_TRIM_M(30m)은 잘라 낸 쪽 끝에만 적용한다. 노선
  본래 끝점이 지표면 안이면 깎지 않는다.
- 자르는 자리는 _planned_route_points_in_project_crs() 한 곳이다. 체인의 BP·EP·CP가
  전부 이 함수를 지나므로 여기서 한 번 자르면 하류가 모두 유효해진다.
- 지표면을 못 열면 자르지 않는다. 트림 실패가 설계를 막으면 안 된다.

실측(용화 노선 2,136m):
  csf/dtm/smooth            -> 1,310m (61%)
  classification/dtm/smooth -> 1,070m (50%)
bounds 사각형 기준 추정치 1,400m보다 짧다 — 실제 외곽이 사각형보다 작기 때문이다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 13:46:48 +09:00
eomsangdonandClaude Opus 5 892c33c74e fix(B04): 배수유역·유입·배수 컨텍스트도 shapefile 노선을 읽게 한다
실측(2026-08-31): 업로드 후 화면에서 "계획 노선 CSV를 읽지 못했습니다: ...shp"
경고가 반복됐다. find_planned_route_file 은 shapefile을 우선 돌려주는데 받는 쪽이
아직 read_planned_route_csv 였다 - 확장자 분기가 없는 판독기라 .shp 를 CSV로 열다
실패하고 노선 없이 진행했다.

앞선 커밋에서 Service_Chain, SheetSurface, Router_GIS 는 바꿨으나 아래 세 곳을
놓쳤다(조사 당시 grep 출력이 잘려 목록에서 빠졌다):

- B04_PreProcess_Router_Watershed.py (2곳)
- B04_PreProcess_Router_Inflow.py
- common_util_drainage_context.py

셋 다 read_planned_route 로 바꿨다 - CSV/shapefile을 확장자로 갈라 읽는다.

검증: ruff check 통과, tmp/tests 302 passed (잔여 실패 11건은 기존 실패로
HEAD 사본에서 동일 재현).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 20:14:06 +09:00
eomsangdonandClaude Opus 5 afc404cbc5 fix(B04): 프로젝트 좌표계를 지형 자료와 짝인 PRJ로 고른다
실측 사고(2026-08-31): 실제 업로드에서 자동 설계 체인이 B05 경로 계산 400으로
멈췄다. input/prj/ 에 옛 result.prj(KGD2002 East Belt 2010, FN 600,000)가 남아
있었고, find_project_prj 가 이름 정렬 첫 번째를 골라 새 용화.prj(Korean 1985
Modified East Belt, FN 500,000) 대신 그것을 집었다. 그 결과 노선이 Y로
100,000m 어긋나(로그의 bp y=468228.23, 옳은 값 367920.75) 지표면 밖으로 나갔다.

자료를 다시 올려도 파일명이 다르면 옛 PRJ가 그대로 남는다 - 이름 정렬은 어느
것이 지금 쓰는 것인지 알지 못한다. 그래서 지금 쓰는 지형 자료(LAS/LAZ/TIF/TFW)와
basename이 같은 PRJ를 먼저 찾고, 못 찾으면 가장 최근 것을 쓰고 경고를 남긴다.
폴백 경로에서도 노선 세트 폴더(input/shp/)의 PRJ는 제외한다 - 그건 노선 좌표계다.

검증: 실제 프로젝트 폴더로 확인 - 선택이 용화.prj로 바뀌고 노선 시점이
x=208403.20 y=367920.75 로 TFW 원점(208288.84/368092.28) 안쪽에 앉는다.
tmp/tests/test_route_shapefile_input.py 10개 통과(회귀 시험 추가), 전체 302 passed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 20:10:16 +09:00
eomsangdonandClaude Opus 5 fe72ea041b feat(B03): 계획노선 shapefile 입력과 PRJ 2개 분리를 지원한다
원청 정식 계획노선이 shapefile(UTM-K)로, 지형이 별도 PRJ(동부원점 Bessel)로
들어오는데 입력 경로가 shapefile 확장자를 막고 PRJ를 프로젝트당 1개로 전제했다.

- 업로드 허용에 .shp/.shx/.dbf/.cpg 추가, 한 번에 보낼 파일 수 5 -> 10
- B03_FileInput_Engine_Shapefile: ESRI 규격 직접 파싱(GDAL 미사용). 형제 파일이
  아직 안 왔어도 .shp 하나로 기하를 읽는다. .cpg 내용이 949뿐인 실물을 CP949로
  정규화해 한글 속성을 살린다.
- 노선 판독을 read_planned_route로 일원화(CSV/shapefile), PlannedRoute에
  crs_input 추가 - 변환 입력은 EPSG 코드가 아니라 crs_input_from_prj가 주는
  값(EPSG:n 또는 원문 WKT)이다. 실물 PRJ 2종 모두 to_epsg가 None이다.
- shapefile 세트를 input/shp/ 한 폴더에 모은다(GDAL 요건). 노선 PRJ가 그 안에
  남으므로 지형 PRJ(input/prj/)와 파일명 정렬 운에 기대지 않고 갈린다.
  find_project_prj가 지형 PRJ를 프로젝트 좌표계로 고른다.
- 필수 세트를 노선 1종(csv 또는 shp) + prj + tfw로 완화, shp면 shx/dbf 동반 필수.
- UI: 확장자 단독 슬롯 매칭을 basename 그룹핑으로 바꿔 노선 PRJ와 지형 PRJ가
  같은 슬롯을 다투지 않게 하고, 노선 슬롯이 파일 한 벌을 담아 함께 전송한다.

자체검증: tmp/tests/test_route_shapefile_input.py 9개 통과, tsc --noEmit 통과,
ruff check/format 통과. 전체 스위트 잔여 실패 11건은 HEAD 사본(git archive)에서
동일하게 재현되는 기존 실패다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 19:28:34 +09:00
eomsangdonandClaude Opus 5 0d90595a40 fix(B03/B04): PRJ 좌표계 판별을 문자열 매칭에서 WKT 정본+EPSG 라벨로 바꾼다
get_epsg_from_prj()가 WKT에 항상 있는 false_easting 때문에 EAST 분기가
무조건 걸려 모든 PRJ를 EPSG:5187로 판정했다(표준 WKT 6종 실측). 새 원청
자료의 5176(비표준 TOWGS84)·5179(AUTHORITY 없는 ESRI WKT)가 이 경로로
들어오면 최대 100km 어긋난다.

- common_util/common_util_crs.py 신설: 판별 사다리(pyproj DB 대조 →
  AUTHORITY 태그 → 투영 파라미터 지문)와 변환 입력 정규화. 수평 성분에
  TOWGS84가 박힌 PRJ는 EPSG로 갈아타지 않고 원문 WKT로 변환해 지역 보정을
  보존한다. 수직 성분의 bound(KNGeoid)는 2D 변환에 무관하므로 무시.
- B04 get_epsg_from_prj: 죽은 문자열 분기 제거, 유틸 위임. 시그니처 불변 —
  기존 COMPD_CS 프로젝트는 예전과 같은 EPSG:5187 문자열이 나온다.
- B03 _component_metadata: to_epsg 실패 시 사다리 라벨 보강,
  normalize_crs_metadata: BoundCRS 벗김(TOWGS84 PRJ 수평 탐색 실패 수정).

검증: tmp/tests/test_common_util_crs.py 13건 포함 스위트 29 passed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 16:27:13 +09:00
eomsangdon bfaf32a043 Merge remote-tracking branch 'origin/feat/las-free-sheet-surface' into feat/B07-cad-block-library 2026-08-30 19:58:51 +09:00
eomsangdon 932d85348e fix(배수유역): 관 지점에 좌표를 남겨 어느 노선으로 읽든 선 위에 앉힌다
사용자 지적(2026-08-30): "결국 노선 위에 위치해야 한다".

관 자리를 정한 선(계획노선 CSV)과 화면에 그려지는 선(B05 최적 경로)은 같은 자리를
지나면서 연장이 다르다 — 실측 350.11m vs 354.83m, 전 구간 이격은 평균 0.03m·최대
1.12m뿐인데 길이가 4.7m 차이난다. pipe_points.json이 누가거리만 저장해서 읽는 쪽이
쥔 선에 따라 같은 값이 3.3~4.3m 미끄러졌고, 노선 지문도 늘 어긋나 B05는 저장된 관을
매번 통째로 버렸다("노선 지문이 달라 저장된 관 지점 4건을 쓰지 않습니다").

- PipePoint에 x·y를 두고 저장 시 채운다(save_pipe_points에 노선을 넘긴다).
- 지문이 달라도 좌표가 있으면 읽는 쪽 노선에 투영해 이월한다(project_pipe_points).
  구간(start_m·end_m)은 기준점이 옮겨간 만큼 같이 민다 — 시설 치수는 불변.
- 좌표 없는 구 저장분만 종전대로 버린다.
- B05는 같은 로더(load_pipe_points_file)를 쓴다 — 두 화면의 판정 기준을 하나로.

검증: 프로젝트 5d18ebe3 실데이터 — 좌표 4/4 저장, B05 계획선으로 읽어 4건 전부 이월,
관에서 계획선까지 거리 최대 0.016m(종전 4건 전량 폐기). 누가거리는 +3.45~+4.29m
이동(연장 차이 그대로). pytest 11 passed(투영 이월·구 저장분 폐기 테스트 추가).
2026-08-30 18:48:24 +09:00
eomsangdon b59d0e4f2c fix(공통): storage가 링크일 때 업로드 경로 기준이 어긋나 터지던 것을 고친다
`storage/`를 심볼릭 링크·정션으로 두면(워크트리를 나눠 쓰면 실제로 그렇다)
프로젝트 루트는 링크 경로 그대로인데 저장 엔진(resolve_upload_destination·
resolve_chunk_session_dir)은 Path.resolve()로 링크를 따라가 실경로를 만든다.
그래서 chunk_path.relative_to(project_root)가 ValueError로 터졌다.

  '...본폴더\storage\1\3\{id}\B03_FileInput\chunks_temp\...\00000000.chunk'
  is not in the subpath of '...워크트리\storage\1\3\{id}'

프로젝트 루트와 임시 보관함 루트를 realpath로 돌려주어 기준을 하나로 맞춘다.
경로 검증(상대경로·.. 금지·루트 이탈)은 그대로다.

검증: tmp/tests/test_storage_symlink_root.py 추가 — 링크된 storage에서 반환
경로가 resolve() 결과와 같고 relative_to가 성립하는지. 전체 9 passed.
2026-08-30 18:03:30 +09:00
eomsangdonandClaude Opus 5 a1dc0ee235 feat(B07): 토적도(유토곡선)·유역도(수리집수면적유역도)를 도면으로 낸다
납품 도면 2장을 B07 도면 목록에 새로 붙였다. 계산은 하지 않는다 —
유토곡선은 B06 확정 시 저장한 산출물(longitudinal_sections.data.mass_haul)을,
유역도는 B04 세부유역 GeoJSON과 도엽 등고선·세류선을 읽어 좌표만 종이 mm로 옮긴다.

- 도면 종류 확장: kind에 mass_haul·watershed 추가(Schema·Api_Fetch·openwebcad
  App.types), 12분류 라벨에 연결, 목록에 단장 도면 2건 상시 노출
- 엔진 신규: _Engine_Cad_MassHaul.py(축·곡선·평형선·띠 현·balloon·측점 테이블),
  _Engine_Cad_Basin.py(등고선·세류선 배경·노선·유역·구역별 정보표·방위표)
- 척도 상수: 유토곡선 H 1/2,000 · 세로 1mm=50㎥, 유역도 1/6,000 (A1 고정)
- 유토곡선 저장 payload에 띠·잔여의 기하 필드 추가 — 파이썬에 곡선 보간·토량 배분
  로직을 복제하지 않기 위해 값을 낳는 쪽(TS 엔진)에서 함께 남긴다
- 유역도 배경은 여러 도엽을 합쳐 받은 뒤 도곽 크기로 절취(clip_line_to_box)
- 배수규격은 소요 관경이 아니라 규격관(recommended_diameter_mm)으로 표기

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-30 13:01:39 +09:00
eomsangdonandClaude Fable 5 6e195afb69 feat(B07,B08): 워크플로 순서 교환 — 상세설계를 수량산출 앞으로
횡단설계(B06) 다음을 상세설계 → 수량산출 → 설계도서 순으로 재배열하고,
폴더 번호가 흐름과 일치하도록 이름을 맞바꾼다.

- B08_DesignDetail → B07_DesignDetail, B07_Quantity → B08_Quantity
  (파일 접두어·식별자·라우트·locale 키 전량 스왑)
- STAGE_KEYS 4=DESIGN_DETAIL, 5=QUANTITY 스왑 + 라우터 stage 리터럴 교체
- CAD 마운트 /b08-cad → /b07-cad (main.py·vite proxy·iframe URL),
  openwebcad Toolbar 라벨 B07로 수정 후 재빌드
- 유지: openwebcad postMessage 프로토콜 aislo:b08:*·패키지명(내부 식별자)
- 기존 프로젝트 storage 폴더 rename + project_manifest 갱신,
  DB project_workflow_stages stage_no 4↔5 행 스왑 완료

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-29 16:16:46 +09:00
eomsangdon f99f1f68d3 feat(B03,B04,B05): 초기 설계가 도는 동안 B05·B06 진입을 막는다
파일을 올리면 지표면 확정 직후 "노선 설계로 이동하세요"를 적고 그 뒤에 초기 설계
체인이 돈다. 그 사이 B05에 들어가 만지면 반쯤 계산된 값을 편집하게 되고, 그 편집이
섞인 채 초기값 스냅샷이 찍힌다. 계산이 끝날 때까지 준비 화면(B11)에서 기다리게 한다.

- initial_snapshot: 마커(initial_design.lock) 생성·해제·조회와 스냅샷 무효화,
  편집 정본 제거를 한곳에 둔다. 마커는 스냅샷 4트리 밖(프로젝트 루트)이라 복원에
  딸려 들어가지 않는다.
- 체인 두 곳(자동·재설계): 시작에 마커, finally에서 해제. 재설계 체인은 끝에 스냅샷을
  무효화한다 — 사용자 입력을 유지한 채 재계산하므로 그 결과를 찍으면 가짜 초기값이
  된다. 다음 [초기화]가 폴백을 타며 새 초기값을 만들고 그때 촬영한다.
- /surface/status: 마커가 있으면 in_progress + initial_design으로 답한다. stage 1이
  COMPLETE면 진행률 파일을 무시하는 기존 분기를 건드리지 않으려고 별도 확인이다.
- 진입 게이트(routeAfterPreloadCheck)와 B11이 그 상태를 보고 기다린다. 상태 조회가
  실패하면 막지 않고, B11 대기는 20분 상한을 둔다.
- [초기화] 폴백: 체인 전에 structures.json과 drainage/edits를 지운다. 남기면 구조물
  측점이 사용자 편집분에서 다시 파생돼 초기값이 오염된다(2026-08-29 실측).

테스트 4건 추가. 249 passed·8 failed(기슭막이 이관 때 생긴 기존 실패, 변경 전 동일).
2026-08-29 15:09:09 +09:00
eomsangdon 08c0dc3228 feat(B05,B06): 자동저장을 걷어내고 초기값 스냅샷으로 초기화한다
CLAUDE.md 5장(조작·데이터 흐름 정책)을 코드에 반영한다. 조작분은 세션에만 쌓고
영구저장은 [저장]·[확정]에서만 하며, [초기화]는 재계산이 아니라 초기값 복원이다.

자동저장 폐지
- B06 조정창 기준벽 구간값: 800ms 디바운스 PUT을 없애고 세션(b06:culvertopt)에
  담는다. flushCulvertOptions()를 B06 [저장]·[확정]과 B05 [저장]이 부른다.
- B05 구조물: 조작 즉시 PUT + 서버 재조회로 화면을 덮어쓰던 것을 세션
  (b05:structures) 적재로 바꾼다. 저장 전에도 고르고 지울 수 있도록 식별자를
  crypto.randomUUID()로 미리 발급한다(서버는 빈 값일 때만 새로 발급).
- B05에서 만지고 B06에서 확정하는 경로를 위해 flushPendingStructures()를 공용화.

초기값 스냅샷
- common_util_initial_snapshot: 자동설계 체인 성공 직후 정본 파일 4트리와
  routes+자식 4표를 initial_snapshot/에 뜬다. 이후 읽기 전용.
- reset_route_design: 스냅샷이 있으면 DELETE와 같은 트랜잭션에서 행을 되세우고
  파일을 되돌린다. 없으면 종전 재계산 폴백. 응답에 restored를 더한다.

조작 응답
- 등고선 재적용·B06 진입 정합·[모두 적용]에서 전체 화면 오버레이 제거.
- 재계산이 design을 갈아끼울 때 extra_spans를 보존한다(다른 조작값과 동일).

테스트: tmp/tests/test_initial_snapshot.py 5건 추가. 245 passed·8 failed(기존
실패 — 기슭막이 이관 때 placement가 interval→point로 바뀐 것을 테스트 미반영).
2026-08-29 11:15:29 +09:00
eomsangdonandClaude Opus 5 f916aa52a1 feat(B05,B06): 독립 기슭막이를 배수관 시설 종류로 이관
독립 기슭막이(구조물 정본 D군 구간형)를 배수관 측점(pipe_points)의 시설 종류
"revetment"로 옮긴다. 관을 숨긴(hidden_pipe) 배관 세트로 얹혀 컴퓨트·렌더·패널·
연동·경사·3D·수량이 배관 경로를 그대로 탄다.

- common_util_drainage_pipes: PIPE_FACILITY_REVET 추가, 여유고 0
- B06_Section_Engine_Culvert: _revet_set/_revet_side — 관 숨김 세트 생성
- B06_Section_Router: attach_revetments 제거(관 세트에 통합)
- B05_Profile_Engine_Sections: 기슭막이는 시작·기준·종료 세 측점 심기
- B05_Profile_Structure_Types.json: revetment placement=point, managed_by=pipe_points,
  설치 측에 "양쪽" 추가
- B05_Profile_UI_Drainage_Facility: 기슭막이 옵션 폼(설치 측·형태·높이·길이·전후·단 수)
- B06_Section_UI_Cross_Culvert_Geom: 관 숨김 시 관경 하한·역경사 클램프·유출 가드 해제
- B06_Section_UI_Cross_Culvert_Wire: restrictToSide — 설치 측(좌/우)만 남기기
- B06_Section_UI_Cross_View: 옛 D군 경로 이중그리기 가드, appliedAdjust 되받기
- B06_Section_UI_Cross_Wall: 기슭막이 벽 공용 기하·그리기 층 신설
- B05_Profile_UI_Corridor_Structures: 3D도 같은 규칙(관 실린더 숨김, 관통 컷 없음)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 19:09:28 +09:00
eomsangdonandClaude Opus 5 ea55ad1c73 fix(B05): 세월교 요구 여유 = 배수관 산식 + 물넘이 몫, 물넘이포장은 여유 없음
세월교는 배관을 여러 개 묶어 다리 형태로 만든 것이라 배수관과 같은 산식을 쓰되,
그 위에 물넘이가 얹히므로 0.5m를 더 얹는다(2026-08-23 사용자 확정).
물넘이포장은 도로 위에 그대로 만드는 시설이라 들어 올릴 이유가 없다.

- 세월교 Ø1000 → 1.0 + 0.5(토피) + 0.5(물넘이) = 2.0
- 물넘이포장 → 0.0
- 앞서 쓰던 월류 높이(ford_height_m) 기준은 폐기

검증: 계획선 재생성 실측 — 세월교 지점 계획고-지반고 2.00 일치
(배수관 1.50/1.30, BOX 2.50도 유지). pytest 6/6, tsc 통과

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 14:19:57 +09:00
eomsangdonandClaude Opus 5 e1d0d6dda3 feat(B05): 계획 종단선이 횡단배수 최소고를 자동 확보
계획선의 변화점(PVI)은 배수유역이 확정한 배관 배치 측점이다. 그 자리 계획고를
지반고와 같게 두면 관·구체가 들어갈 자리가 없어, 시설 제원만큼 들어 올린다.

- common_util_drainage_pipes: facility_clearance_m()/pipe_anchor_clearances() 신설
  (정본 산식) — 배수관 관경+토피 0.5, BOX암거 구체높이+토피 0.5,
  물넘이·세월교는 좌측 패널이 설계유량으로 산출한 월류 높이(ford_height_m)
- Engine_Sections: 관 지점에서 시설 여유를 함께 읽어(_load_pipe_anchors) 전달
- Engine_Grade_Profile: 앵커 목표 표고 = 지반고 + 시설 여유
- Profile_MinCover(화면 경고): 세월교·물넘이를 월류 높이 기준으로 추가.
  백엔드가 정본이고 이쪽은 편집 중 즉시 경고용 사본 — 값 일치는 테스트로 잠금

검증: 재생성한 계획선의 배관 측점 실측 — Ø1000 +1.50 / 세월교 +0.30 /
BOX +2.50 / Ø800 +1.30 (전부 요구 여유와 일치), 화면 경고 소거 확인.
pytest 10/10, tsc·ruff 통과

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 14:12:38 +09:00
eomsangdonandClaude Fable 5 8812657022 feat(B05/B02): 표시 정보 정비 + 설계속도 축 도입
- 종단 상단 표시줄을 법정 판정 3항목으로 축소: 최대 기울기/상한(별표2 Ⅰ.2.라),
  절·성토 불균형(Ⅰ.1.나.(4)(다)), 종단곡선 필요 n곳(Ⅰ.2.마 — 대수차 5% 초과인데
  곡선이 빠진 변화점). 변화점·종단곡선 개수·기본 곡선길이 L·중복 경고칩은 제거.
- [초기선 복원] → [편집 되돌리기] — 실제 동작은 화면 편집 델타 삭제이지 저장 지점
  복귀가 아니다. 툴팁에 동작과 [초기화]와의 차이를 적었다.
- 3D 뷰포트 안내 라벨 삭제(로딩 완료 후 남던 조작법 문구).
- 배수유역 버튼 정비: 유역 다시 나누기 / 선택한 관 삭제 / 배관 배치 초기화 +
  각 툴팁에 실제 동작 명시(선택 삭제는 툴팁 자체가 없었다).
- 유토곡선 요약 12칩 → 5칩(절토·성토·잉여|부족·운반·검산). 토질 3종 내역·다짐
  환산·블록 수·장거리 운반·사토는 해당 칩 툴팁으로. B05·B06 공용 함수라 동시 반영.
- 설계속도 축 도입(임도는 속도를 낼 수 없는 노선 — 기본 20km/h):
  · 임도 종류는 프로젝트 등록값(projects.road_type)을 읽어 B05가 읽기 전용 표시
    (main→간선임도, fire→산불진화임도, 그 외→작업임도). 화면에서 고치지 않는다.
  · 설계속도 선택(간선·산불진화 20/30/40, 작업 20 고정) 신설 — 종단기울기 상한과
    종단곡선 반경이 등급이 아니라 이 값으로 정해진다(지식DB 설계제원_총괄 §6·§7).
  · B02 임도 종류 선택지를 현행 3종으로 정리(지선 폐지·계류보전은 사방 구분).
  · 백엔드: ROUTE_GRADE_CLASSES 3종+branch 호환, selectable_design_speeds 신설,
    resolve_design_speed 신설, legal_grade_limit_pct·resolve_grade_options에
    설계속도 인자, sections/context가 road_type 제공(B06도 저장값 승계).
  · 계획선 정책은 그릴 때마다 현재 기준으로 동기화 — 저장분의 옛 상한이 남지 않는다.
- 검증: pytest 125 통과(설계속도 12건 신규), tsc, 헤드 브라우저 — 진입 시 상한 9%,
  40km/h 전환 시 7% 즉시 반영, 원복 9%, 안내문 '간선임도 · 설계속도 20km/h ·
  일반지형', 유토곡선 5칩, 유역 버튼 새 이름·툴팁, 3D 라벨 빈 문자열 확인.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 19:46:03 +09:00
eomsangdonandClaude Opus 5 a9c46b9711 feat(B05): 배수 추천 구조물·관경 자동 설정 + 물넘이·세월교 개략 단면
지시 2 — 배수유역도가 유효직경 다음에 추천을 보여 주고, 초기 전처리 계산이
그 값으로 관 지점을 설정한다:
- recommend_structure(): 유효직경 임계로 구조물·관경을 고른다. D≤1500 배관(규격
  스냅 800·1000·1200·1500), 1500<D≤2000 BOX암거 후보, D>2000 세월교 검토.
  판정 근거는 유량뿐 — 계곡 횡단경사·하천 차수는 지형 계산이 필요해 뺐고 화면이
  "현장 확인"으로 안내한다(2026-08-17 사용자 확정)
- WatershedBasin에 recommended_facility·recommended_diameter_mm·required_area_m2
  추가, 응답 payload와 DetailBasin 타입까지 배선
- _apply_recommendations(): 자동 배치 관의 **빈칸에만** 추천을 채운다. 사용자가
  놓은 관(source=user)·이미 바꾼 시설·이미 있는 옵션 키는 건드리지 않는다.
  carry_facility_attributes 뒤에 얹어 승계값이 덮이지 않게 했다
- 유역 목록 표기: `Ø1234mm → 추천 Ø1500 배관` / `→ BOX암거 후보`
- 추천 관경 목록은 레지스트리 pipe_diameter_mm 선택지와 같아야 한다 — 폼에서 못
  고르는 값을 추천하지 않도록 테스트로 묶었다

지시 1 — 물넘이포장·세월교는 지정할 옵션이 거의 없으니 계산값이라도 보여 준다:
- 레지스트리에 ford_width_m(월류 폭) 신설. 기본값 없음 — 지식DB에 폭 근거가 없어
  프로그램이 지어내지 않는다
- fordSection(): 설계유량과 폭으로 필요 수심을 되짚는다. 광폭 근사로 출발해 실제
  동수반경(R=A/P)으로 수렴시킨다 — 근사값을 그대로 쓰면 통수능이 늘 설계유량에
  못 미쳐 판정이 무의미해진다. n=0.017·S=10%는 실무 수리계산서 역산값
  (울진1 물넘이·관 시트가 둘 다 S=0.10에서 검산 일치)
- 폼에 월류 폭 한 칸 + 읽기전용 요약(설계유량·필요 수심·필요 단면·유속). 설계유량은
  유역 payload에서 폼까지 배선(onPipesChanged → Page → setFacility)

⚠ 실무 시트의 수심 0.27m는 Qd 역산값이 아니라 가정 수심이다(B=20·h=0.27의 통수능
41.2㎥/s ≫ 그 시트 Qd 2.54㎥/s). 실무는 단면을 가정해 통수능을 검증하고, 프로그램은
필요 최소 수심을 낸다 — 화면 문구도 "필요 수심"으로 적었다.

검증: pytest tmp/tests 124 passed·7 skipped(신규 17건), npm run typecheck 무오류,
npm run build 성공. 물넘이 산식은 프론트 전용이라 수치 손검증으로 대신했다 —
Qd=2.54·B=20에서 필요 수심 0.0503m·유속 2.526m/s, 통수능이 2.5400㎥/s로 Qd와 일치.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 19:18:28 +09:00
eomsangdonandClaude Opus 5 77f833bb25 feat(B05): 보호공 개편·종단 기준 기본 접힘·700줄 파일 분리 3건
지시 1 — 사이드 「종단 설계 기준」 컨테이너를 기본 접힘으로. section()에
collapsed 인자를 더해 초기 is-collapsed만 붙이고 토글은 전역 collapsible이
그대로 맡는다(B06 buildGroup과 같은 패턴).

지시 4 — 배수관 유입·유출 바닥 보호를 "보호공" 한 축으로 개편:
- 구 돌붙임 있음/없음 + 표면처리(찰/메) 두 축 → 돌붙임(찰)/돌붙임(메)/도수로.
  도수로·산비탈수로(B4)는 유출부에서 돌붙임 대신 쓰는 공종이라 별도 구조물이
  아니라 이 선택지로 받는다(별표2 유출구~원지반 도달 의무)
- "없음" 삭제 — 물이 흐르는 자리라 보호공은 필수(사용자 확정). 기본값은
  유입 돌붙임(찰)·유출 돌붙임(메)
- 치수는 배타 — 돌붙임이면 면적(㎡, 기본 10), 도수로면 폭(m, 기본 1.0)
- 저장 키 개명 *_pitching → *_protection, *_pitching_finish 삭제,
  *_protection_width_m 신설. 독립 기슭막이(D4)도 같은 축
- 구 저장분은 parse_pipe_points가 읽는 순간 새 키로 옮긴다("없음"은 부위
  기본값으로 승격). 폼 write()도 구 키를 폴백으로 읽어 structures.json 몫을 덮는다
- revetment.pitching_area_m2가 기본값과 required를 동시에 갖던 모순도 정리

지시 5 — 700줄 초과 파일 분리(순수 이동 + import 정리, 공개 인터페이스 불변):
- Drainage_Facility 724 → 401 (+_Facility_Fields 354)
- Structures_Panel 753 → 695 (+_Structures_List 113)
- Drainage_Panel 822 → 693 (+_Drainage_Chrome 150, +_Drainage_Interact 159)
- Page 1031 → 911 (+_Page_Helpers 144) — 아직 초과
- Profile_Panel 1152 — 미착수. 두 파일은 가변 클로저 상태를 곳곳에서 직접
  읽고 써 상태 승격이 선행돼야 하며, 프론트 테스트가 없어 순수 이동으로는
  떼어낼 수 없다. 분리안은 PLAN.md 3-3에 기록

검증: pytest tmp/tests 107 passed·7 skipped(신규 보호공 마이그레이션 6건 포함),
npm run typecheck 무오류, npm run build 성공(374 modules).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 18:19:46 +09:00
eomsangdonandClaude Opus 5 1d07633260 feat(B05): 구조물 컨테이너 병합 1단계 — 계곡 통과 시설·구간 기준점·phase 분리
PLAN 2026-08-17 「B05 구조물 컨테이너 병합」 백엔드. 배관 정본은 유역 계산의
입력이라 저장소를 옮기지 않고 제자리 확장한다.

- PipePoint: facility(배관/BOX암거/물넘이/세월교)·start_m/end_m(기준점 앞뒤
  구간)·options(세월교 관 종류/크기/수량) 추가. 구 파일은 배관·폭 미지정으로
  읽히고 기본값은 저장 시 생략된다(하위 호환). 교량은 임도용이 아니라 없다.
- carry_facility_attributes: 세부유역 계산기는 chainage만 다뤄 재구성 목록이
  전부 기본 배관이 된다 — 원본에서 종류·구간·옵션을 되붙인다. B04 basins
  라우터의 재구성 지점에 적용하고 응답·GeoJSON에도 시설 정보를 싣는다.
- StructureInstance: 구간형 chainage_m = 기준점(마킹 위치) 허용, 시작≤기준≤종료
  검증, 미지정 시 시점으로 채움(기존 저장분 호환). anchor_m = 기준점.
- 레지스트리: 옵션 phase 필드(b05/detail) 신설. required 32건을 detail로 이동
  — 필수 원칙은 유지하고 강제 시점만 B06/B07로 미룬다(B05 = 유무·종류·위치
  단계, 2026-08-17 사용자 확정). BOX암거·물넘이·세월교(표시용, managed_by=
  pipe_points)와 A6 노출형 횡단수로·A7 개거(수동) 5종 추가, 총 37종.
- Repository: phase=detail 옵션은 B05 저장에서 필수 강제 제외. b05 필수는
  기존대로 강제.

tmp/tests 88건 통과 (신규: pipe facility 16·span anchor 10·registry 정책 재편).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 08:54:47 +09:00
eomsangdonandClaude Fable 5 226d452b48 fix(drainage): 등우선 커버리지 여유 30km→10km 축소 (사용자 지시)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 18:56:39 +09:00
eomsangdonandClaude Fable 5 69df533350 feat(drainage): 확률강우량 본토 오류 개선 — WAMIS 관측소 방식 도입, 제주는 등우선 유지
- common_util_wamis_station.py 신규: 관측소 목록/확률강우량 xlsx 수급(프로젝트별 영구저장),
  최근접+반경 10km 후보 중 100년/24hr 최대 지점 선정(Aislo 자체 로직), 후보표 기록(성과품용)
- 강우강도 이원화: Mononobe형(기본, 실무 방식 rt=(R24/24)(24/T)^0.557) + General형 적합 옵션
  — Mononobe는 b=0 General형과 동일해 하류 idf_intensity/size_pipe 무수정
- _build_rainfall_sync: 계획노선 좌표로 제주/본토 분기, 본토 실패 시 예외 전파(제주 폴백 금지)
- value_at: 등우선 커버리지(제주+30km) 밖 좌표 차단 — 조용히 틀린 값 재발 방지
- config: WAMIS_STATION_RADIUS_M/DRAINAGE_RAINFALL_STATION_DIRNAME/DRAINAGE_RAINFALL_IDF_METHOD
- 검증: 제주 등우선 96값 정상, 본토 차단, 울진 관측소 R24=305.83, Mononobe 검산 실무 일치

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 18:51:25 +09:00
eomsangdonandClaude Opus 5 fbab749ec4 feat(B01): 프로젝트 삭제에 개발용 하드 삭제 스위치 추가
대시보드 삭제 버튼은 지금까지 projects.deleted_at만 찍는 소프트 삭제였다. 배포에서는
그게 맞다 — 사용자가 올린 라이다 원본은 다른 프로젝트에 재활용할 자산이다. 그러나 개발
중에는 프로젝트를 반복 생성·삭제하는데 정리 잡이 없어 수십 GB 원본이 계속 쌓인다.

config_system.py 맨 위에 PROJECT_DELETE_HARD_ENABLED를 두고 갈랐다. 기본값 False라
환경변수를 빠뜨린 배포 환경은 자동으로 안전한 쪽에 선다. True면 projects 행을 실제로
DELETE 하고(자식 테이블은 FK CASCADE로 함께 사라진다) storage/{회사}/{사용자}/{프로젝트ID}/
폴더를 통째로 지운다.

자식 테이블 목록은 코드에 나열하지 않았다. projects.id 참조가 전부 ON DELETE CASCADE라
행 하나면 충분하고, 목록을 복사해 두면 스키마가 바뀔 때 조용히 어긋난다.

순서는 DB 먼저 커밋, rmtree 나중이다. 파일을 먼저 지우면 DB 실패 시 실체 없는 프로젝트가
목록에 남아 화면이 깨진다. 반대면 rmtree가 실패해도 고아 폴더만 남고 정합성은 유지된다.

resolve_stored_project_path()는 끝에서 makedirs를 하므로 삭제에 쓸 수 없다 — 지우기 직전에
폴더를 되살린다. 검증만 하는 resolve_project_root_for_delete()를 따로 뒀고, 저장소 루트 안 ·
세그먼트 정확히 4개 · 마지막 세그먼트가 요청 project_id와 일치를 모두 요구한다. DB의
storage_path가 오염돼도 상위 폴더나 남의 폴더를 지우지 못한다.

하드 삭제 모드에서는 확인 모달 문구를 바꿔 원본까지 사라진다고 알린다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 21:56:17 +09:00
eomsangdonandClaude Opus 5 37b4d41e0b feat(B03): 자료 교체 시 옛 산출물 정리 + 분석 중 업로드 차단 (E2E 결함 3·6 서버측)
사용자 결정(2026-08-08): 새 자료를 올리면 재계산해 덮어쓰고, 화면은 불러올 자료가 없으면
대시보드로 보낸다. 그러려면 자료가 갈리는 순간 옛 계산 결과가 남아 있으면 안 된다.

- common_util_project_reset 신설: 단계별 산출물 폴더(B04~B09)와 산출물 DB 레코드
  (surface_models·routes·route_points·route_statistics·longitudinal_sections·
  cross_sections·structures·quantity_items·outputs·processed_point_cloud)를 함께 지운다.
  **B03_FileInput(업로드 원본)은 지우지 않는다** — 같은 파일인지 가리는 중복 검사가 쓴다.
- 직접 업로드 완료·보관함 연결 양쪽에서 정리를 호출하고, 진행 단계도 1단계 이후를
  NOT_STARTED로 되돌린다(reset_stages_after_input_change).
- is_analysis_running(): 전처리가 도는 중이면 업로드 세션 생성과 보관함 연결을 409로
  막는다. 화면 버튼 잠금은 새로고침·다른 탭으로 우회되므로 서버에도 문을 단다.
- fail_stage 아래 있던 리비전(번호표) 안은 폐기 — 사용자가 "산출물이 없으면 대시보드"
  방식으로 정리했다.

곁들여: os.replace 공유 위반 재시도(replace_with_retry)
  진행률 파일은 서버가 쓰는 동안 화면이 계속 읽어 WinError 5가 났고, 전처리
  structured.npz 교체에서는 같은 이유로 분석이 통째로 죽었다(결함 3의 사망 원인).
  짧게 여러 번 다시 시도하도록 바꿨다.

검증(실서버 f45243b3, 계획노선 CSV 재업로드로 자료 교체):
  전처리 결과 112개 -> 재분석분만, 노선 2->0, 횡단 20->0,
  DB 지표면 15->0 / 노선 1->0 / 횡단 19->0, 업로드 원본 6개는 그대로.
  진행 단계가 초기로 돌아가고 재분석이 자동 시작됨.
  교체 재시도는 읽는 쪽이 파일을 잡고 있는 상황을 만들어 성공 확인.
ruff format·check 통과.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 19:41:51 +09:00
eomsangdonandClaude Opus 5 1f119f9845 fix(B03): 직접 업로드 마지막 파일 404 + 실패 기록이 실패하던 문제 (E2E 결함 1·4)
결함 1 — 업로드 세션이 인증 세션을 가리고 있었다
- finalize_project_upload가 Depends(verify_session)로 받은 session을 같은 이름으로
  덮어써, str(session["role"])이 업로드 세션 행에서 role을 찾다 KeyError를 냈다.
  KeyError는 LookupError 하위라 404 {"message": "'role'"}로 나가고 로그도 안 남았다.
- 업로드 세션 변수를 upload_session으로 분리(청크 업로드·finalize·상태 조회 3곳).
- B03_FileInput_Router_Errors.lookup_error_response() 신설: 조회 실패만 404,
  KeyError·IndexError는 500 + 예외 로그. 두 라우터의 LookupError 처리 13곳에 적용.
  batch 단위 엔드포인트에는 batch_id를, 프로젝트 단위에는 project_id를 로그 필드로 준다.

결함 4 — fail_stage가 예외 문자열을 그대로 넣어 UPDATE가 죽었다
- project_workflow_stages.message는 varchar(255)인데 PermissionError 메시지는 300자를
  넘겨 DataError로 실패했고, 단계가 FAILED로 못 가 화면이 영영 "분석 중"이었다.
- 200자로 자르고 말줄임표를 붙인다. 원문은 호출부 로그에 남는다.

검증(실서버, 신규 프로젝트 f45243b3에 표본 5개 직접 청크 업로드):
  finalize 성공 11건 / "'role'" 오류 0건, 마지막 파일 complete_upload=true도 성공.
  이어서 WF1 자동 분석이 시작됨(stage 0 COMPLETE, stage 1 IN_PROGRESS) — 종전에는
  예외가 스케줄링 앞에서 터져 자동 분석이 아예 걸리지 않았다.
ruff format·check 통과.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 19:18:42 +09:00
eomsangdonandClaude Fable 5 17834d8189 feat(B01,B03): 프로젝트 생성 전 임시 보관함 (temp upload)
라이다 원본은 업로드에 오래 걸려 프로젝트 정보 확정 전에 미리 올릴 수 있어야 한다.
계정에 묶인 임시 보관함을 만들고, 나중에 만든 프로젝트로 자료를 옮겨 쓴다.

저장·DB
- storage/tmp/{user_id}/{batch_id}/ 아래에 프로젝트 저장소와 동일한 구조를 써서
  청크 저장·병합 엔진(resolve_upload_destination/merge_upload_chunks)을 그대로 재사용
- 010_temp_upload.sql: temp_upload_batches / temp_upload_files 신설,
  upload_sessions.project_id NULL 허용 + temp_batch_id 추가(FK명 조회 후 재생성)
- config: TEMP_UPLOAD_DIR_NAME / TEMP_UPLOAD_RETENTION_DAYS(30) /
  TEMP_UPLOAD_CLEANUP_INTERVAL_HOURS(6)

백엔드
- B03_FileInput_Router_Temp.py: 묶음 생성·목록·삭제, 일반/청크 업로드, finalize,
  이어올리기 상태 조회, 프로젝트 연결(attach)
- attach: 파일 이동 후 input_files 등록, stage 0 완료, WF1·자동 설계 체인 트리거
- common_util_temp_cleanup.py: 완료 시각 기준 만료분 주기 삭제(서버 시작 시 1회 포함)

프론트엔드
- B01 대시보드 임시 보관함 섹션: 프로젝트 등록과 같은 폼 + 보관 목록.
  진행률은 모달이 아니라 리스트 행에 표시, 새로고침 후 이어올리기 지원
- B03 업로드 컨테이너 내부 불러오기 버튼과 선택 모달.
  완료된 묶음만 노출하고, 선택 후 업로드를 누르면 이동과 분석으로 이어짐

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-08 12:55:14 +09:00
eomsangdonandClaude Fable 5 ec9943417c feat(B07,B09): B07_Quantity 셸 페이지 신설 + B09_Estimation 접두사 정리 + 단계 순서 반영
- B09_wf6_Estimation -> B09_Estimation (폴더·파일·라우트 키/슬러그)
- B07_Quantity_UI_Page.ts 신설: 워크플로우 셸 + 좌측 [확정] 버튼
  (renderPendingWorkflow에 leftPanel 옵션 추가로 공용 셸 재사용)
- B07_Quantity_Router.py 신설: POST /api/projects/{id}/quantity/confirm
  -> complete_stage(4) 전이만 수행(본문 미구현), main.py 등록
- STAGE_KEYS 재정의: FILE_INPUT/PREPROCESS/PROFILE/SECTION/QUANTITY/DESIGN_DETAIL/ESTIMATION
- 스텝바 라벨·아이콘 4↔5 스왑(수량산출이 4차, 상세설계가 5차), A02 소개 문구 스왑
- 라우터 테이블에 b07-quantity 등록(플레이스홀더 제거), locale 키 5종 추가

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-08 10:15:07 +09:00
eomsangdonandClaude Fable 5 2a248ff742 refactor(B07,B08): 도면 코드 B08_DesignDetail 이동 + 수량 슬롯 B07_Quantity 확보
- B07_wf4_DesignDetail(8파일+openwebcad) -> B08_DesignDetail로 git mv (이력 보존)
- B08_wf5_Quantity -> B07_Quantity (구 수량 백업 zip 보관 폴더)
- 파생 문자열 일괄 전환: /b07-cad -> /b08-cad 정적 서빙, CAD 레이어 b07-* -> b08-*,
  postMessage aislo:b07:* -> aislo:b08:*, 패키지명 aislo-b08-cad, CSS .b08-*,
  라우트 키/슬러그(B08_DESIGN_DETAIL/b08-design-detail, B07_QUANTITY/b07-quantity)
- 상세설계 워크플로우 stage 4 -> 5 (확정/무효화 전이 3곳), 확정 완료 시 B09로 이동
- WORKFLOW_STEP_ROUTES 순서 재배열: index4=수량(B07), index5=상세설계(B08)
- [임시] B08 이동 테스트 버튼 제거, openwebcad dist 재빌드
- 주석 의미 정렬: 수량 인계 주석 B08->B07, 도면 참조 B07->B08

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-08 10:10:10 +09:00
eomsangdonandClaude Fable 5 54954a05e5 refactor(B05,B06): B05_wf2_Route -> B05_Profile, B06_wf3_ProfileCross -> B06_Section 동시 개명
- 한몸으로 동작하는 두 페이지라 한 커밋으로 처리 (상호 참조 다수)
- B05 37파일 + B06 20파일 접두사 개명 (git mv, 이력 보존)
- 참조 치환 91파일: import 경로, 라우트 슬러그(b05-profile/b06-section),
  라우트 키(B05_PROFILE/B06_SECTION), B03 자동 체인, storage 상수, pyproject 제외 경로
- 로직 변경 없음. typecheck·백엔드 import 검증 통과

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-08 10:03:11 +09:00
eomsangdonandClaude Fable 5 f7528a4aa4 refactor(B04): B04_wf1_Surface -> B04_PreProcess 전면 개명
- 폴더·내부 파일 51개 접두사 개명 (git mv, 이력 보존)
- 저장소 전체 참조 치환 67파일: import 경로, 라우트 슬러그(b04-preprocess),
  라우트 키(B04_PREPROCESS), storage 경로 상수, locale, SQL 주석
- 로직 변경 없음 (기계적 치환). typecheck·백엔드 import 검증 통과

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-08 10:01:36 +09:00