- 열쇠 = 관 목록 · 노선(평면 · 종단 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
95 lines
3.9 KiB
Python
95 lines
3.9 KiB
Python
"""342-3 배수유역 지문 · 낡음 — 분석 노선 지문이 지금 노선과 다르면 세부 유역을 안 나눔.
|
|
|
|
- 지문: 관 자리 지문은 차례와 무관 · 분석 저장분 지문은 파일이 바뀔 때만 다시 읽음 · 없으면 None.
|
|
- 낡음: 관 목록이 있으면 관 자리만(세부 유역 없음 · 시설 속성 그대로 · 까닭 글) · 없으면 409.
|
|
- 같으면 그대로 나눔(_build).
|
|
"""
|
|
|
|
import asyncio
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
from uuid import uuid4
|
|
|
|
from fastapi.responses import JSONResponse
|
|
|
|
from B04_PreProcess import B04_PreProcess_Router_Basins as basins
|
|
from common_util import common_util_drainage_pipes as pipes_mod
|
|
from common_util.common_util_drainage_context import DrainageContext
|
|
from common_util.common_util_drainage_pipes import PipePoint, pipe_signature, route_signature
|
|
from common_util.common_util_route_geometry import RouteVertex
|
|
|
|
VERTICES = [RouteVertex(0.0, 0.0, 100.0, 0.0), RouteVertex(300.0, 0.0, 90.0, 300.0)]
|
|
|
|
|
|
def _context() -> DrainageContext:
|
|
return DrainageContext(stored_path="x", project_root=Path("."), vertices=list(VERTICES))
|
|
|
|
|
|
def test_pipe_signature_ignores_order_and_noise():
|
|
assert pipe_signature([10.0, 120.0]) == pipe_signature([120.0, 10.001])
|
|
assert pipe_signature([10.0, 120.0]) != pipe_signature([10.0, 121.0])
|
|
|
|
|
|
def test_analysis_signature_reads_saved_and_follows_file(tmp_path, monkeypatch):
|
|
monkeypatch.setattr(pipes_mod, "drainage_dir", lambda stored: tmp_path)
|
|
assert pipes_mod.analysis_route_signature("x") is None # 저장분 없음
|
|
path = tmp_path / "00_watershed_response.json"
|
|
path.write_text(json.dumps({"status": "success"}), "utf-8")
|
|
assert pipes_mod.analysis_route_signature("x") is None # 옛 저장분 = 지문 없음
|
|
path.write_text(json.dumps({"route_signature": "2-abc"}), "utf-8")
|
|
os.utime(path, (1, 1))
|
|
assert pipes_mod.analysis_route_signature("x") == "2-abc"
|
|
|
|
|
|
def _patch(monkeypatch, analysis_signature, stored):
|
|
async def load(project_id):
|
|
return _context(), ""
|
|
|
|
monkeypatch.setattr(basins, "load_drainage_context", load)
|
|
monkeypatch.setattr(basins, "analysis_route_signature", lambda stored_path: analysis_signature)
|
|
monkeypatch.setattr(basins, "load_pipe_points", lambda *args: stored)
|
|
built = SimpleNamespace(called=False)
|
|
|
|
def build(stored_path, context, points):
|
|
built.called = True
|
|
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
|
|
|
|
|
|
def test_stale_keeps_pipes_without_basins(monkeypatch):
|
|
stored = [
|
|
PipePoint(chainage_m=120.0, source="stream", facility="ford_bridge"),
|
|
PipePoint(chainage_m=40.0, source="user"),
|
|
]
|
|
built = _patch(monkeypatch, None, stored) # 옛 저장분
|
|
context, detail, points, saved, stale = asyncio.run(
|
|
basins._resolve(uuid4(), None, use_stored=True)
|
|
)
|
|
assert stale and saved and not built.called
|
|
assert detail.basins == []
|
|
assert [point.chainage_m for point in points] == [40.0, 120.0]
|
|
assert points[1].facility == "ford_bridge"
|
|
payload = basins._payload(uuid4(), context, detail, points, saved, stale)
|
|
assert payload["stale"] == basins.STALE_MESSAGE
|
|
assert payload["route_signature"] == route_signature(VERTICES)
|
|
assert payload["pipe_signature"] == pipe_signature([40.0, 120.0])
|
|
|
|
|
|
def test_stale_without_pipes_is_409(monkeypatch):
|
|
_patch(monkeypatch, "다른-노선", None)
|
|
result = asyncio.run(basins._resolve(uuid4(), None, use_stored=True))
|
|
assert isinstance(result, JSONResponse) and result.status_code == 409
|
|
|
|
|
|
def test_same_signature_splits(monkeypatch):
|
|
built = _patch(monkeypatch, route_signature(VERTICES), None)
|
|
*_, stale = asyncio.run(basins._resolve(uuid4(), None, use_stored=True))
|
|
assert built.called and not stale
|