분석이 30초 걸리는데 B05는 일반 사용자 화면이다. 관리자 확인용 B04에서
한 번 돌려 저장하고, B05는 그 결과를 읽어 관 보충과 세부유역만 처리한다
(2026-07-31 사용자 지시).
노선 원천 변경
- B05 확정 경로 -> B03 업로드 계획 노선 파일(CSV). 분석이 노선 설계보다
먼저 끝나 있어야 하기 때문. 샘플 planned_route_sample_epsg5187.csv 로 검증.
- common_util_route_geometry.py 신설 — RouteVertex/StructureCandidate/누가거리
보간/세류 교차점/계획 노선 CSV 리더. B04와 B05가 같은 표현을 쓰도록 공용화.
열 이름은 대소문자·한글 표기를 함께 받는다(B03이 여러 형식 수용 예정).
B04 (관리자 확인용, 신규)
- Engine_Watershed_{Grid,Stream,Descent,Flow,Expand,Export} — B05에서 git mv
- Engine_Watershed_Analyze.py — 1~8단계 오케스트레이션
- Router_Watershed.py — GET /drainage/primary-region
- UI_Watershed.ts — 2D 지도 GIS 레이어 그룹에 "배수유역" 토글 추가.
격자/화살표/세류망/1차영역/2차유역/기본관을 겹쳐 그린다.
- 저장 위치 B05_wf2_Route/drainage -> B04_wf1_Surface/drainage
- 03_road_routing 단계 추가: B05가 세부유역을 나눌 최소 배열(셀->도로셀 귀속,
유하장, 강도, 도로셀 제원, 셀 표고) + 계획도로선/기본배관/2차유역 기하
B05 (일반 사용자용, 축소)
- Engine_Drainage_Basin.py — B04 산출물 로더 + 관 보충(9) + 측구 라우팅/세부유역(10,11)
- Engine_Drainage.py 는 관경 산정만 남기고 322 -> 27줄
- Router_Drainage.py 509 -> 142줄. POST /drainage/basins 만 남김
- 화살표·격자·강도 띠 렌더 제거. 계획도로선/기본배관/2차유역만 받는다
삭제
- _legacy_watershed/ 4파일 (능선 행진 방식 원본 보관본)
- Engine_Watershed_Basin.py (B04 Analyze + B05 Drainage_Basin 으로 분할)
- GET /drainage/candidates 와 propose_structure_stations (구방식 후보 제안)
E2E 검증 (실데이터)
B04 분석 28.2s -> 저장(geojson 11KB + npz 2.6MB)
B05 로드 + 세부 설계 0.11s <-- 30초가 0.1초로
면적 457,404m2 로 B04 2차 유역과 정확히 일치
관 편집 재산정 0.12s, 관 3개 -> 세부유역 3개, 면적 보존
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
61 lines
2.1 KiB
Python
61 lines
2.1 KiB
Python
import json
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
from uuid import UUID
|
|
|
|
from B03_FileInput.B03_FileInput_Router import (
|
|
_missing_required_file_types,
|
|
_write_stage_metadata,
|
|
)
|
|
from B03_FileInput.B03_FileInput_Schema import UploadedFileResult
|
|
|
|
|
|
class B03RouterHelperTest(unittest.TestCase):
|
|
def test_required_file_types_include_planned_route(self) -> None:
|
|
self.assertEqual(
|
|
_missing_required_file_types({"las", "prj", "tfw"}),
|
|
["csv"],
|
|
)
|
|
self.assertEqual(
|
|
_missing_required_file_types({"csv", "laz", "prj", "tfw"}),
|
|
[],
|
|
)
|
|
|
|
def test_stage_metadata_preserves_existing_files(self) -> None:
|
|
project_id = UUID("acb9170b-9ac8-49b3-82a0-51cfa32bb42d")
|
|
with tempfile.TemporaryDirectory() as temporary_dir:
|
|
stage_root = Path(temporary_dir)
|
|
(stage_root / "metadata.json").write_text(
|
|
json.dumps(
|
|
{
|
|
"project_id": str(project_id),
|
|
"files": [
|
|
{
|
|
"original_filename": "terrain.las",
|
|
"relative_path": "B03_FileInput/input/las/terrain.las",
|
|
}
|
|
],
|
|
}
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
route = UploadedFileResult(
|
|
input_file_id=100,
|
|
original_filename="planned_route.csv",
|
|
file_type="csv",
|
|
relative_path="B03_FileInput/input/csv/planned_route.csv",
|
|
size_bytes=1000,
|
|
metadata={"purpose": "planned_route", "epsg": 5187},
|
|
)
|
|
|
|
_write_stage_metadata(stage_root, project_id, [route])
|
|
|
|
payload = json.loads((stage_root / "metadata.json").read_text(encoding="utf-8"))
|
|
self.assertEqual(len(payload["files"]), 2)
|
|
self.assertEqual(payload["files"][1]["metadata"]["purpose"], "planned_route")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|