Merge remote-tracking branches 'origin/sub_laptop_3' and 'origin/sub_laptop_5' into main_laptop_1
This commit is contained in:
@@ -2,19 +2,18 @@
|
||||
|
||||
- 옛 추천 모달 · 좌측 「노선 추천」 칸 · [적용](`as_initial`) 화면 부름 없음(서버 길 · 시험은 남김).
|
||||
- 미리보기 = 저장본 넷(영선 · 토공 · 기울기 · 비중) 하나만 · 끔 · 다시 셈(`recompute`) 없음.
|
||||
- 지형 칩 → `POST /route/design-terrain` — 노선 그대로(옛 = 새 선) · 초기값 폴더 안 씀 · 같은 지형 = 그대로.
|
||||
- 지형 칩 → `POST /route/design-terrain` — 벌 맞바꿈(765-4 · `swap_design_set`) ·
|
||||
같은 지형 = 그대로 · `force` = 전처리 다시(765-5).
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import re
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from uuid import uuid4
|
||||
|
||||
from B05_Profile import B05_Profile_Router_Replan as replan
|
||||
from B05_Profile import B05_Profile_Router_TerrainSwitch as switch
|
||||
from B06_Section import B06_Section_Router_DesignSave as design_save
|
||||
from common_util import common_util_save_guard as guard
|
||||
from common_util import common_util_design_sets as sets
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
B05 = ROOT / "B05_Profile"
|
||||
@@ -60,22 +59,25 @@ def test_지형_칩_확인창_새_길() -> None:
|
||||
assert "rebuildAfterRouteSave(projectId)" in src
|
||||
|
||||
|
||||
def _run(monkeypatch, now: str, want: str):
|
||||
def _run(monkeypatch, now: str, want: str, force: bool = False):
|
||||
"""칩 요청 — 맞바꿈 · 전처리 다시가 받은 값만 잰다(본체 = test_765_design_sets)."""
|
||||
calls: dict = {}
|
||||
line = [[0.0, 0.0], [10.0, 0.0]]
|
||||
|
||||
async def paths(_pid):
|
||||
return Path("root"), "stored"
|
||||
|
||||
async def recompute(*args):
|
||||
calls["args"] = args
|
||||
return {"route_id": 7}
|
||||
async def swap(pid, root, target):
|
||||
calls["swap"] = (root, target)
|
||||
if target == now:
|
||||
return {"changed": False, "design": now}
|
||||
return {"changed": True, "design": target, "built": False}
|
||||
|
||||
async def rows(*_a, **_k):
|
||||
return {}
|
||||
async def switch_terrain(_c, _pid, terrain):
|
||||
calls["switch"] = terrain
|
||||
return 9
|
||||
|
||||
async def restore(*_a):
|
||||
calls["restored"] = True
|
||||
async def chain(pid, model_id):
|
||||
calls["chain"] = model_id
|
||||
|
||||
class Pool:
|
||||
def acquire(self):
|
||||
@@ -88,38 +90,31 @@ def _run(monkeypatch, now: str, want: str):
|
||||
|
||||
return Ctx()
|
||||
|
||||
async def params(*_a):
|
||||
return {}
|
||||
|
||||
def storage(root, scope):
|
||||
calls["scope"] = scope
|
||||
return SimpleNamespace(discard=lambda: None)
|
||||
import B03_FileInput.B03_FileInput_Service_SurfaceChange as change
|
||||
|
||||
monkeypatch.setattr(replan, "_project_paths", paths)
|
||||
monkeypatch.setattr(replan, "_planned_line", lambda _root: line)
|
||||
monkeypatch.setattr(replan, "_recompute", recompute)
|
||||
monkeypatch.setattr(design_save, "_restore", restore)
|
||||
monkeypatch.setattr(guard, "snapshot_rows", rows)
|
||||
monkeypatch.setattr(guard, "snapshot_storage", storage)
|
||||
monkeypatch.setattr(sets, "swap_design_set", swap)
|
||||
monkeypatch.setattr(sets, "switch_terrain", switch_terrain)
|
||||
monkeypatch.setattr(change, "run_surface_change_chain", chain)
|
||||
monkeypatch.setattr(switch, "get_db_pool", lambda: Pool())
|
||||
monkeypatch.setattr(switch, "get_surface_confirmation_params", params)
|
||||
monkeypatch.setattr(switch, "design_terrain", lambda _p: now)
|
||||
body = switch.DesignTerrainRequest(terrain=want)
|
||||
body = switch.DesignTerrainRequest(terrain=want, force=force)
|
||||
result = asyncio.run(switch.switch_route_design_terrain(uuid4(), body))
|
||||
return result, calls, line
|
||||
return result, calls
|
||||
|
||||
|
||||
def test_지형_바꿈_노선_그대로_초기값_안씀(monkeypatch) -> None:
|
||||
result, calls, line = _run(monkeypatch, "las", "sheet")
|
||||
def test_지형_바꿈_벌_맞바꿈(monkeypatch) -> None:
|
||||
result, calls = _run(monkeypatch, "las", "sheet")
|
||||
assert result["changed"] is True and result["design"] == "sheet"
|
||||
_pid, _root, _stored, balanced, terrain, lines, initial = calls["args"]
|
||||
assert balanced is None and terrain == "sheet" and initial is False
|
||||
assert lines == (line, line) # 계획고 옮김 없음 · 노선 안 씀
|
||||
assert guard.INITIAL_SCOPE[0] not in calls["scope"]
|
||||
assert "restored" not in calls
|
||||
assert calls == {"swap": (Path("root"), "sheet")} # 전처리 다시 아님
|
||||
|
||||
|
||||
def test_같은_지형이면_그대로(monkeypatch) -> None:
|
||||
result, calls, _ = _run(monkeypatch, "sheet", "sheet")
|
||||
result, calls = _run(monkeypatch, "sheet", "sheet")
|
||||
assert result == {"status": "success", "design": "sheet", "changed": False}
|
||||
assert "args" not in calls and "scope" not in calls
|
||||
assert "chain" not in calls
|
||||
|
||||
|
||||
def test_force_는_전처리_다시(monkeypatch) -> None:
|
||||
result, calls = _run(monkeypatch, "las", "las", force=True)
|
||||
assert result["preprocessed"] is True and result["design"] == "las"
|
||||
assert calls == {"switch": "las", "chain": 9} # 맞바꿈 아님 · 두 벌 다시
|
||||
|
||||
@@ -347,14 +347,10 @@ def test_지형_바꿈_force_같은_지형도_다시_셈(monkeypatch) -> None:
|
||||
|
||||
from B05_Profile import B05_Profile_Router_TerrainSwitch as switch
|
||||
|
||||
request = switch.DesignTerrainRequest
|
||||
assert request(terrain="las").force is False
|
||||
monkeypatch.setattr(
|
||||
switch, "DesignTerrainRequest", lambda terrain: request(terrain=terrain, force=True)
|
||||
)
|
||||
result, calls, line = base._run(monkeypatch, "las", "las")
|
||||
assert result["changed"] is True and calls["args"][4] == "las"
|
||||
assert calls["args"][5] == (line, line) # 노선 그대로
|
||||
assert switch.DesignTerrainRequest(terrain="las").force is False
|
||||
result, calls = base._run(monkeypatch, "las", "las", force=True)
|
||||
# LAS 갈음 뒤 = 전처리 다시(765-5 · 두 벌 · B05~B09 초기화)
|
||||
assert result["changed"] is True and calls["switch"] == "las" and calls["chain"] == 9
|
||||
|
||||
|
||||
# ── 637-3 B05 화면 ──────────────────────────────────────────────────────────
|
||||
@@ -367,8 +363,8 @@ def test_B05_칩_옆_올리기_확인창_force_덮임_막음() -> None:
|
||||
assert "const lidarPath = `/projects/${projectId}/route/lidar`;" in ui
|
||||
assert "root.append(chip.root, upload.button, circle.root);" in ui # 칩 옆 · 서클
|
||||
assert "createProgressCircle({ size: 16, inline: true })" in ui
|
||||
# 끝 = 칩 다시 · 못 덮음 = 막고 알림 · 이미 라이다 = force 다시 셈 · 확인창 거쳐서만
|
||||
assert 'await change("las", design === "las");' in ui
|
||||
# 끝 = 칩 다시 · 못 덮음 = 막고 알림 · 전처리 다시(force · 765-5) · 확인창 거쳐서만
|
||||
assert 'await change("las", true);' in ui
|
||||
assert "if (uncovered != null) return warnUncovered();" in ui
|
||||
assert 'if (next === "las" && uncovered != null) {' in ui
|
||||
assert "JSON.stringify({ terrain: next, force })" in ui
|
||||
|
||||
@@ -244,6 +244,8 @@ _ALLOWED = {
|
||||
"B05_Profile/B05_Profile_Router_Replan.py",
|
||||
# 라이다 확정값만 적어 둠 · 설계 지형 그대로(637-2)
|
||||
"B05_Profile/B05_Profile_Router_Lidar.py",
|
||||
# 지형 칩 = 벌 맞바꿈 · LAS 나중 올리기 뒤 전처리 다시(765-4 · 5 · 요구 605-1 · 606-1)
|
||||
"B05_Profile/B05_Profile_Router_TerrainSwitch.py",
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
"""764-1 추천 측점 맞춤 — 덜 나쁜 자리 고를 때 원지반 기울기 늘어남 · 옮긴 거리를 따짐(요구 606-2).
|
||||
|
||||
./venv/Scripts/python.exe -m pytest resources/tester/test_764_1_snap_grade.py -q
|
||||
|
||||
· 가까운 측점 자리가 비탈(기울기 늘어남)이면 다음 측점 자리로 — 옛 순위는 가까운 측점 · 토공을
|
||||
앞세워 비탈 자리를 골랐음.
|
||||
· 모든 자리가 크게 망치면 그중 가장 덜 망치는 자리(곡선 가운데 = 측점은 지킴).
|
||||
· 판(`ALGO_VERSION`)이 다른 저장본은 꺼내지 않음.
|
||||
"""
|
||||
|
||||
import json
|
||||
import math
|
||||
|
||||
import pytest
|
||||
|
||||
from B05_Profile.B05_Profile_Engine_RouteInitial_Station import (
|
||||
STATION_M,
|
||||
curve_mc_chainages,
|
||||
off_station_count,
|
||||
snap_curves_to_stations,
|
||||
)
|
||||
from B05_Profile.B05_Profile_Engine_RouteInitial_Store import (
|
||||
ALGO_VERSION,
|
||||
read_stored_any,
|
||||
store_dir,
|
||||
)
|
||||
from common_util.common_util_route_polyline import build_planned_polyline
|
||||
|
||||
RADIUS = 12.0
|
||||
# MC ≈ 109.5 m — 가까운 측점 100 · 다음 120
|
||||
POINTS = [(0.0, 0.0), (110.0, 0.0), (230.0, 90.0)]
|
||||
|
||||
|
||||
class _Slope:
|
||||
"""직접 잼 흉내 — 늘 못 지남(덜 나쁜 자리만 남음) · 곡선 가운데가 x < 112 면 비탈 쪽.
|
||||
|
||||
토공은 비탈 쪽이 더 작게(옛 순위가 비탈을 고르도록 유혹)."""
|
||||
|
||||
def __init__(self, steep: float, flat: float) -> None:
|
||||
self.steep, self.flat = steep, flat
|
||||
|
||||
def rise(self, local, reference) -> float:
|
||||
arc = local[1:-1]
|
||||
return self.steep if sum(x for x, _ in arc) / len(arc) < 112.0 else self.flat
|
||||
|
||||
def judge(self, local, reference):
|
||||
return False, (False, 0.0, -1000.0 * self.rise(local, reference))
|
||||
|
||||
def ok(self, local, reference) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def _snap(guard) -> float:
|
||||
outline = build_planned_polyline(
|
||||
POINTS,
|
||||
min_radius_m=RADIUS,
|
||||
simplify=False,
|
||||
curve_flags=[True] * 3,
|
||||
radii=[None, 40.0, None],
|
||||
)
|
||||
(mc,) = curve_mc_chainages(outline)
|
||||
assert abs(mc - 100.0) < abs(mc - 120.0) # 가까운 측점 = 비탈 쪽
|
||||
snapped = snap_curves_to_stations(outline, RADIUS, guard=guard)
|
||||
assert snapped is not None and off_station_count(snapped) == 0
|
||||
(placed,) = curve_mc_chainages(snapped)
|
||||
return round(placed / STATION_M) * STATION_M
|
||||
|
||||
|
||||
def test_less_bad_spot_avoids_slope() -> None:
|
||||
"""가까운 측점 자리가 기울기를 크게 늘리면 다음 측점 자리."""
|
||||
assert _snap(_Slope(steep=0.30, flat=0.0)) == 120.0
|
||||
|
||||
|
||||
def test_all_ruined_takes_least_ruin() -> None:
|
||||
"""모든 자리가 크게 망치면 그중 가장 덜 망치는 자리 · MC 는 측점에."""
|
||||
assert _snap(_Slope(steep=0.20, flat=0.40)) == 100.0
|
||||
assert _snap(_Slope(steep=0.40, flat=0.20)) == 120.0
|
||||
|
||||
|
||||
@pytest.mark.parametrize("version, found", [(ALGO_VERSION - 1, False), (ALGO_VERSION, True)])
|
||||
def test_other_version_not_read(tmp_path, version: int, found: bool) -> None:
|
||||
folder = store_dir(tmp_path)
|
||||
folder.mkdir(parents=True)
|
||||
data = {"version": version, "source": "s", "pool": "p", "request": {}, "result": {}}
|
||||
(folder / "k.json").write_text(json.dumps(data), encoding="utf-8")
|
||||
got = read_stored_any(tmp_path, "p", lambda source: "k")
|
||||
assert (got is not None) is found
|
||||
|
||||
|
||||
def test_ruin_threshold_constants() -> None:
|
||||
from B05_Profile.B05_Profile_Engine_RouteInitial_Station import RUIN_GAP_M, RUIN_RISE
|
||||
|
||||
assert RUIN_RISE == pytest.approx(0.05) and math.isclose(RUIN_GAP_M, STATION_M / 2)
|
||||
@@ -517,3 +517,53 @@ def test_비우기에_묶음_포함(tmp_path: Path) -> None:
|
||||
removed = purge_output_folders(tmp_path, keep_preprocess=True)
|
||||
assert SETS_DIR in removed and not (tmp_path / SETS_DIR).exists()
|
||||
assert (tmp_path / "B04_PreProcess" / "models").is_dir()
|
||||
|
||||
|
||||
# ── 765-6 초기값 사본 = 캐시 뺌 · 복제가 묶음 안 지표면 id 고침 ────────────
|
||||
|
||||
|
||||
def test_초기값_사본은_배수_캐시를_빼고_복원은_캐시를_안_지움(tmp_path: Path) -> None:
|
||||
from common_util import common_util_initial_snapshot as snap
|
||||
|
||||
_seed_files(tmp_path)
|
||||
drainage = tmp_path / "B04_PreProcess" / "drainage"
|
||||
(drainage / "rainfall_stations").mkdir()
|
||||
(drainage / "rainfall_stations" / "a.json").write_text("st", encoding="utf-8")
|
||||
target = tmp_path / "initial_snapshot" / "B04_PreProcess__drainage"
|
||||
snap._save_drainage(drainage, target)
|
||||
saved = sorted(p.name for p in target.iterdir())
|
||||
assert saved == ["01_primary_region.geojson", "edits", "manifest.json"] # 캐시 없음
|
||||
|
||||
# 작업본이 바뀜 · 캐시는 새로 지어짐 · 옛 사본(캐시 듦) 꼴도 섞음
|
||||
(drainage / "01_primary_region.geojson").write_text("edited", encoding="utf-8")
|
||||
(drainage / "04_detailed_basins.geojson").write_text("new", encoding="utf-8")
|
||||
(drainage / "descent_ab_cd.npz").write_text("d2", encoding="utf-8")
|
||||
(target / "00_sheet_metric.pkl").write_text("old-cache", encoding="utf-8")
|
||||
snap._restore_drainage(target, drainage)
|
||||
live = {p.name: p for p in drainage.iterdir()}
|
||||
assert live["01_primary_region.geojson"].read_text(encoding="utf-8") == "{}"
|
||||
assert "04_detailed_basins.geojson" not in live # 초기값에 없던 노선 몫 = 지움
|
||||
assert live["descent_ab_cd.npz"].read_text(encoding="utf-8") == "d2" # 캐시 그대로
|
||||
assert live["00_sheet_metric.pkl"].read_text(encoding="utf-8") == "s" # 옛 사본 캐시 안 덮음
|
||||
assert (live["rainfall_stations"] / "a.json").is_file()
|
||||
assert (live["edits"] / "pipe_points.json").is_file()
|
||||
|
||||
|
||||
def test_복제가_묶음_안_지표면_id_고침(tmp_path: Path) -> None:
|
||||
from common_util.common_util_project_copy import _fix_design_sets
|
||||
|
||||
db = _seed_db()
|
||||
_seed_files(tmp_path)
|
||||
asyncio.run(sets.park_active_set(_Conn(db), tmp_path, PID, FP))
|
||||
bundle = tmp_path / SETS_DIR / "las"
|
||||
corridor = bundle / "files" / "B05_Profile" / "corridor" / "corridor_0011.json"
|
||||
report = _fix_design_sets(tmp_path, {5: 905})
|
||||
assert sorted(report["las"]) == ["db.json", "meta.json", "sampling"]
|
||||
dump = json.loads((bundle / "db.json").read_text(encoding="utf-8"))
|
||||
assert {r["surface_model_id"] for r in dump["routes"]} == {905}
|
||||
assert [r["id"] for r in dump["routes"]] == [10, 11] # 노선 id 는 꺼낼 때 새로
|
||||
assert sets.read_meta(tmp_path, "las")["model_id"] == 905
|
||||
sampling = bundle / "files" / "B06_Section" / "sampling.json"
|
||||
assert json.loads(sampling.read_text(encoding="utf-8")) == {"surface_model_id": 905}
|
||||
assert corridor.is_file() # 코리도는 안 건드림
|
||||
assert sets.bundle_ready(tmp_path, "las", FP) # 지문은 id 를 안 봄
|
||||
|
||||
Reference in New Issue
Block a user