# 이관 계획서 (Archived Plan) - **이관 일자**: 2026-07-18 - **대상 작업**: B04 전처리를 old 버전 방식으로 정렬 (2026-07-18) - **상태**: 완료 및 검증 완료로 활성 계획에서 이관 --- ### 배경 old 버전(`0_old/main.py` + `0_old/utils/`)은 업로드 직후 구조화→필터 4종→모델 15종→등고선 캐시→VWorld/GIS를 전부 사전 처리하고 문제없이 동작했다. 본 프로그램(B04)은 계산 코어를 그대로 포팅했으나 old의 3가지 장치가 빠졌고 등고선 캐시 간격이 어긋나 있었다: 1. 지면 마스크 영구 저장(`mask_*.npy`) 없음 → 매 분석·미리보기마다 CSF/PMF 재계산 2. `structured.npz` 존재 시 스킵 로직 없음 → 구조화 무조건 재실행 3. 엔진 계층 로그 0건 + 백그라운드 트리거에 진행률 콜백 미전달 → 침묵 상태로 수 분 소요 4. 등고선 사전 캐시 5.0m vs 프론트 요청 1.0m 불일치(작업 트리에서 1.0m로 수정됨) + on-demand 재계산이 `adaptive_contour_grid_resolution`(old에 없음)을 사용 방향: **최대한 old 방식을 따른다. old에 없는 신규 장치는 삭제한다. 문제가 생기면 그때 변경한다.** ### 저장 위치 매핑 (old → 본 프로그램) | old (`instance/{proj}/`) | 본 프로그램 (`{project_root}/B04_wf1_Surface/`) | |---|---| | `structured.npz` | `processed/structured.npz` (기존 동일) | | `mask_{filter}.npy` | `processed/mask_{filter}.npy` **(신규 저장)** | | `terrain_models/*` (모델·manifest·등고선) | `models/*` (기존 동일) | | `vworld_*`, GIS geojson | `processed/*` (기존 동일) | ### 완료 구현 체크리스트 **A. 마스크 영구 저장·재사용** — `B04_wf1_Surface_Engine.py` - [x] A-1. `run_surface_analysis`: 필터별 `processed/mask_{filter}.npy` 존재+길이 일치 시 `np.load(mmap_mode="r")` 재사용, 없으면 계산 후 `np.save`. `force=True`면 재계산 - [x] A-2. `cache_ground_points`: mask 미전달 시 `build_ground_masks` 대신 저장된 `mask_{filter}.npy` 우선 로드 (없을 때만 필터 실행 후 저장) **B. 구조화 스킵 + 입력 세대 검증** — `B04_wf1_Surface_Engine.py` - [x] B-1. `structured.npz` 존재 && `force=False`면 `structurize_las` 스킵 (old `upload_files` 스킵 로직과 동일 원리) - [x] B-2. 입력 LAS 정체성(파일명+크기+mtime)을 structured.npz(또는 sidecar json)에 기록, 불일치 시 구조화·마스크·모델 캐시를 force 취급으로 무효화 — old는 업로드마다 새 프로젝트 폴더라 이 문제가 없었으나 본 프로그램은 같은 폴더를 재사용하므로 필수 **C. 로깅·진행률 복원** (old의 print 진행 표시를 logger로 대체) - [x] C-1. `run_surface_analysis`: 단계별 시작/완료 `logger.info` + 소요시간(구조화/필터별/모델빌드/VWorld/GIS/총계) - [x] C-2. VWorld·GIS 다운로드 `except: pass` → `logger.warning`으로 실패 사유 기록 (old는 print로 출력했음) - [x] C-3. `build_all_terrain_models` 호출 시 progress reporter 연결 (`_report` 래핑, 70~90% 구간 배분) - [x] C-4. B03 `trigger_wf1_analysis_and_email`: `on_progress` 콜백 전달 → `write_surface_progress`로 progress.json 갱신 (업로드 자동 전처리도 진행률 노출) **D. 등고선 old 정합** — `B04_wf1_Surface_Engine_Contour.py`, `B04_wf1_Surface_Router_Contour.py`, `B04_wf1_Surface_Engine_Pipeline.py` - [x] D-1. `adaptive_contour_grid_resolution` 삭제, on-demand 재계산도 old처럼 config 고정 격자(`SURFACE_CONTOUR_GRID_RESOLUTION_M=1.0`) 사용 - [x] D-2. NURBS 스플라인 `s=0` → old 공식 `s=len(control_x)*len(control_y)*0.01` 복귀 (추출 로직을 old와 동일화; `CONTOUR_EXTRACTOR_VERSION=4`는 유지해 기존 캐시 전면 재생성) - [x] D-3. Pipeline 캐시 재사용 분기: dtm/tin은 `_smooth_` 등고선 파일 존재도 검사해 누락 시 재생성 (프론트 기본값이 스무딩 ON이므로) - [x] D-4. 사전 캐시 간격 1.0m 유지 (작업 트리 수정분 유지 — old config와 동일) - [x] D-5. 모델 (재)빌드 직전 해당 stem의 `contour_{filter}_{method}*.json` 전부 삭제 + 라우터 캐시 검사에 "모델 npz mtime > 등고선 캐시 mtime → 재계산" 추가 — 같은 경로를 세대 구분 없이 재사용하므로 구세대 등고선 잔존 방지 (외부 검토 지적 채택) - [x] D-6. `SURFACE_MODEL_PRECOMPUTE` 순서를 `dtm` 우선으로 변경 — TIN 등고선 사전 캐시가 `dtm_{filter}.npz` footprint를 참조하는데 tin이 먼저 빌드되면 footprint 미적용으로 캐시됨. old도 tin 우선이라 동일 결함이 있었으나 저비용 개선이므로 채택 (외부 검토 지적 채택) **E. 이상한 것 삭제·정리** - [x] E-1. `run_surface_analysis`의 3중 `.prj` glob(`**/*.prj` 재귀 포함) 제거 → B03 입력 폴더에서 명시적 1회 탐색 - [x] E-2. 재분석 시 해당 프로젝트의 `surface_models` 기존 행 삭제 후 INSERT — 실측 결과 한 프로젝트에 121행 누적, 전부 동일 파일 경로를 가리키며 디스크에는 일부만 존재(중단된 분석). 화면은 이 목록의 첫 매치를 선택하므로 "파일 없는 모델 선택 → 프리뷰/등고선 404"가 미반영 증상의 1차 원인 중 하나 (외부 검토 실측 채택) **F. config 파라미터 old 값 복귀** — `config/config_system.py` (변경 시 config_signature 변경 → 모델 캐시 전면 재빌드됨) - [x] F-1. `SURFACE_IMPLICIT_MAX_POINTS_PER_TILE` 20000→10000, `SURFACE_IMPLICIT_SMOOTHING` 0.5→0.1 - [x] F-2. `SURFACE_TIN_MAX_INPUT_POINTS` 200000→500000, `SURFACE_MAX_PREVIEW_VERTICES` 120000→500000 - [x] F-3. `SURFACE_MESHFREE_MAX_MODEL_POINTS` 300000→500000, `SURFACE_MESHFREE_POINT_RADIUS_M` 0.5→0.15 **G. 프론트 경합 보완 (소규모)** — `B04_wf1_Surface_UI_TerrainViewer.ts` - [x] G-1. GLTF/PLY 로더 콜백에 요청 세대 토큰 가드 추가 — 선택 변경 후 늦게 도착한 이전 메쉬가 scene에 겹쳐 남는 문제 차단 (등고선 fetch에는 이미 가드 있음) **H. 코딩 후 실측 테스트에서 발견·해결한 추가 문제 (2026-07-18 오후)** - [x] H-1. **TIN/meshfree 등고선 수 분 소요·행(Hang)의 진짜 원인 = scipy 버전**: venv의 scipy 1.13.1에서 `scipy.spatial.Delaunay.transform`(무게중심 변환 지연 계산)이 97만 심플렉스 기준 **114~280초** 소요 (실측). old 환경은 scipy 1.18.0(`0_old/requirements.txt`, UTF-16 인코딩 주의)이라 빨랐음. old 데이터를 현재 venv에 넣어도 동일하게 느린 것으로 교차 검증 완료 → 데이터·코드 문제 아님. **조치**: venv scipy를 1.16.3(현재 numpy 1.26.4와 호환되는 최신)으로 업그레이드, `requirements.txt` 핀 1.13.1→1.16.3 갱신. transform 15.8초로 단축, TIN 등고선 1개당 25~27초. 완전한 old 동등(scipy 1.18)을 위해서는 numpy 2.x 마이그레이션 필요 → Backlog 참조 - [x] H-2. 진단 과정에서 `_grid_axes`를 float64로 바꿨다가 **오진으로 판명되어 old와 동일한 float32로 원복** (H-1이 진짜 원인; 최종 작업 트리에서 이 함수는 무변경) - [x] H-3. **분석 중 서버 프로세스 침묵 소멸의 원인 = uvicorn 자동 리로드**: `.env`의 `DEBUG=True` → `main.py:268`의 `reload=DEBUG` 활성화 → 분석 도중 .py 파일이 저장되면 백그라운드 분석 스레드째 재시작(2회 재현 확인). **조치**: `.env` `DEBUG=False`로 변경 + 사유 주석 추가 - [x] H-4. **전처리 전체 재개 실측 검증 완료** (프로젝트 `storage/1/3/acb9…`, cloud_merged.las 606만 지면점): 15개 모델 + 등고선 캐시 21개 + VWorld 3종 + GIS 벡터 6종 전부 생성, manifest `completed`, 실패 0, **총 433초** (구조화 스킵·마스크 3종 캐시 재사용 각 0.03초·dtm 1종 캐시 재사용 상태 기준. 전체 신규 계산 시 구조화 ~2분 추가 예상). A·B·C·D 캐시/스킵/로그/진행률 로직 동작 확인됨 ### 완료된 백로그 (Completed Backlog) - [x] **TIN coverage 마스크 rasterio.features.rasterize 대체 검토** (2026-07-18): `B04_wf1_Surface_Engine_Contour.py`의 `_tin_face_coverage_mask` 함수 내에서 shapely `union_all`과 `intersects_xy` 연산의 오버헤드를 줄이기 위해 rasterio의 `rasterize` 방식으로 전환 완료. (Delaunay transform 병목 해소와 병행해 성능 체감 효과 확보)