perf(배수유역): 342-3 세부 유역 저장본 — 열쇠가 같으면 다시 나누지 않음(부록 4 R15)
- 열쇠 = 관 목록 · 노선(평면 · 종단 Z) · 분석 산출물(03 road_routing · 01 primary_region) · 강우표 · 구조물 정의 자국 - GET · POST · DELETE pipe-points = 메모리 최근 4벌 + 파일 한 벌(drainage/04_detail_cache.pkl · [저장] 때 GET 열쇠로 씀) - [저장](PUT)은 늘 정본으로 다시 셈 · 관 목록은 사본으로 돌려줌 - 시험 test_342_3_detail_cache(같은 열쇠 재사용 · 입력 바뀌면 다시 셈 · 다시 켜도 파일로 읽힘) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XWh5wNNycMkbwMpACz1rte
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
"""342-3 세부 유역 저장본(부록 4 R15) — 열쇠가 같으면 다시 나누지 않음.
|
||||
|
||||
- 열쇠 = 관 목록 · 노선(평면 · 종단 Z) · 분석 산출물 · 강우표 · 구조물 정의 자국.
|
||||
- 같은 열쇠 = _build 안 부름 · 관 목록은 사본 · 무엇이든 바뀌면 다시 셈.
|
||||
- [저장] 뒤 파일 한 벌 — 서버를 다시 켜도(메모리 비움) GET 열쇠로 읽힘.
|
||||
"""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
from B04_PreProcess import B04_PreProcess_Router_Basins as basins
|
||||
from common_util.common_util_drainage_context import DrainageContext
|
||||
from common_util.common_util_drainage_detail import DrainageDetail
|
||||
from common_util.common_util_drainage_pipes import PipePoint
|
||||
from common_util.common_util_route_geometry import RouteVertex
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def setup(tmp_path, monkeypatch):
|
||||
monkeypatch.setattr(basins, "drainage_dir", lambda stored: tmp_path)
|
||||
basins._DETAIL_CACHE.clear()
|
||||
calls = SimpleNamespace(count=0)
|
||||
|
||||
def build(stored_path, context, points):
|
||||
calls.count += 1
|
||||
chainages = [p.chainage_m for p in points] if points else [50.0]
|
||||
return DrainageDetail(), [PipePoint(chainage_m=c, source="stream") for c in chainages]
|
||||
|
||||
monkeypatch.setattr(basins, "_build", build)
|
||||
(tmp_path / "03_road_routing.npz").write_bytes(b"grid")
|
||||
context = DrainageContext(
|
||||
stored_path="x",
|
||||
project_root=Path("."),
|
||||
vertices=[RouteVertex(0.0, 0.0, 100.0, 0.0), RouteVertex(300.0, 0.0, 90.0, 300.0)],
|
||||
)
|
||||
return tmp_path, context, calls
|
||||
|
||||
|
||||
def test_same_key_reuses_and_copies(setup):
|
||||
_, context, calls = setup
|
||||
points = [PipePoint(chainage_m=120.0, source="user")]
|
||||
_, first = basins._build_cached(context, points)
|
||||
first[0].x = 999.0 # 저장이 좌표를 채워도 저장본은 그대로
|
||||
_, second = basins._build_cached(context, points)
|
||||
assert calls.count == 1
|
||||
assert second[0].x is None
|
||||
|
||||
|
||||
def test_any_input_change_recomputes(setup):
|
||||
tmp_path, context, calls = setup
|
||||
points = [PipePoint(chainage_m=120.0, source="user")]
|
||||
basins._build_cached(context, points)
|
||||
basins._build_cached(context, [PipePoint(chainage_m=121.0, source="user")]) # 관 자리
|
||||
context.vertices[1].z = 91.0 # 계획고(종단 Z) → 관 깊이
|
||||
basins._build_cached(context, points)
|
||||
routing = tmp_path / "03_road_routing.npz"
|
||||
routing.write_bytes(b"grid-2") # 분석 다시 돎
|
||||
os.utime(routing, ns=(1, 1))
|
||||
basins._build_cached(context, points)
|
||||
assert calls.count == 4
|
||||
|
||||
|
||||
def test_saved_file_survives_restart(setup):
|
||||
tmp_path, context, calls = setup
|
||||
stored = [PipePoint(chainage_m=120.0, source="stream")]
|
||||
detail, carried = basins._build(context.stored_path, context, stored)
|
||||
basins._save_detail_cache(context, stored, detail, carried)
|
||||
assert (tmp_path / basins.DETAIL_CACHE_FILENAME).exists()
|
||||
basins._DETAIL_CACHE.clear() # 서버 다시 켬
|
||||
_, again = basins._build_cached(context, stored)
|
||||
assert calls.count == 1
|
||||
assert [p.chainage_m for p in again] == [120.0]
|
||||
@@ -57,6 +57,9 @@ def _patch(monkeypatch, analysis_signature, stored):
|
||||
return SimpleNamespace(pipes=[], basins=["유역"]), points or []
|
||||
|
||||
monkeypatch.setattr(basins, "_build", build)
|
||||
monkeypatch.setattr(
|
||||
basins, "_build_cached", lambda context, points: build("x", context, points)
|
||||
)
|
||||
return built
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user