fix(종단): 노선 손 고침 [저장] 바뀐 구간 계획고 = 옛 종단 길이 비례(지반 아님) · 손 [저장]이 「종단 반영」 안 덮음 (PLAN 575-2 · 요구 432 · 434 · 435)
- carry_base(파이썬) ↔ carryBase(TS) 짝 — 같은 자리 = 맞춘 옛 누가거리 · 바뀐 자리 = 앞뒤 같은 자리(없으면 시점 0 · 종점 새 연장 ↔ 옛 연장) 사이 새 누가거리 비례로 옛 누가거리 · 높이 = 옛 기준 꼭짓점 · 지반 첫 계획선은 측점 자리만 - 덧 흠 — 손 [저장](as_initial 아님)은 initial_choice with_profile 을 적힌 값 그대로 - 낡은 시험 고침: test_400 · test_344_2 「바뀐 자리 = 지반」 · 「끝 꼭짓점 = 새 지반」 → 옛 종단 비례 · 옛 끝 높이 / test_459 비균형 「옮긴 시설 측점 = 새 원지반」 단언 지움(옛 규칙 · 시설 세움은 옮긴 계획선 높이) · 거울 단언은 그대로 - 새 시험: test_400 바뀐 자리 비례 4 갈래 · test_346_3 손 [저장] 종단 반영 그대로 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -420,22 +420,31 @@ def test_가운데를_고치면_앞은_그대로_뒤는_같은자리_계획고()
|
||||
assert "90.500" not in edits["station_offsets"]
|
||||
assert edits["curve_radii"]["40.000"] == 300.0
|
||||
moved = _kept_plan(case, base_pvi, edits)
|
||||
# 바뀐 자리는 새 지반 첫 계획선 꼭짓점 · 편집 없음
|
||||
# 바뀐 자리는 옛 종단 길이 비례(앞뒤 같은 자리 사이 · 575 B) · 지반 아님 · 편집 없음
|
||||
ground = {round(p["chainage_m"], 3): p["elevation_m"] for p in case["ground_pvi"]}
|
||||
got = {round(p["chainage_m"], 3): p["elevation_m"] for p in base_pvi}
|
||||
assert moved and all(got[round(t, 3)] == ground[round(t, 3)] for t in moved)
|
||||
match = line_matcher(_pts(case, "old_xy"), _pts(case, "new_xy"))
|
||||
anchors = [(t, match(t)) for t in sorted(got) if match(t) is not None]
|
||||
old_s = [p["chainage_m"] for p in case["base_pvi"]]
|
||||
old_z = [p["elevation_m"] for p in case["base_pvi"]]
|
||||
for t in moved:
|
||||
t0, s0 = max(a for a in anchors if a[0] < t)
|
||||
t1, s1 = min(a for a in anchors if a[0] > t)
|
||||
want = float(np.interp(s0 + (s1 - s0) * (t - t0) / (t1 - t0), old_s, old_z))
|
||||
assert got[round(t, 3)] == pytest.approx(want, abs=1e-6), t
|
||||
assert moved and any(got[round(t, 3)] != ground[round(t, 3)] for t in moved)
|
||||
assert not {f"{t:.3f}" for t in moved} & set(edits["station_offsets"])
|
||||
assert min(got) == 0.0 and max(got) == pytest.approx(L_OLD + span["delta_m"], abs=1e-3)
|
||||
|
||||
|
||||
def test_끝을_고치면_끝_꼭짓점은_새_지반():
|
||||
def test_끝을_고치면_끝_꼭짓점은_옛_끝높이():
|
||||
case = CARRY_CASES[1]
|
||||
span, (base_pvi, edits) = _py_carry(case)
|
||||
assert not span["end_fixed"]
|
||||
assert f"{L_OLD:.3f}" not in edits["station_offsets"] # 바뀐 끝 편집은 버림
|
||||
_kept_plan(case, base_pvi, edits) # 앞(90.5 편집 둘레) 계획고 그대로
|
||||
ground_end = case["ground_pvi"][-1]
|
||||
assert base_pvi[-1] == {
|
||||
assert base_pvi[-1] == { # 575 B — 종점 = 옛 종점 높이(새 연장 ↔ 옛 연장 비례)
|
||||
"chainage_m": ground_end["chainage_m"],
|
||||
"elevation_m": ground_end["elevation_m"],
|
||||
"elevation_m": round(case["base_pvi"][-1]["elevation_m"], 6),
|
||||
}
|
||||
|
||||
@@ -508,6 +508,17 @@ def test_균형_종단은_추천_적용만_손_저장은_계획고_옮김(api_wo
|
||||
assert calls[1][0] is not None # 추천 [적용] = 균형 종단 그대로
|
||||
|
||||
|
||||
def test_손_저장은_종단_반영_적힌_값_그대로(api_world, monkeypatch):
|
||||
"""575 덧 흠 — 손 [저장] 본문엔 `balanced_profile` 이 없어 「종단 반영」을 끔으로 덮던 것."""
|
||||
written: list = []
|
||||
monkeypatch.setattr(replan, "write_initial_choice", lambda *a: written.append(a) or {})
|
||||
monkeypatch.setattr(replan, "read_initial_choice", lambda root: {"with_profile": True})
|
||||
initial = {"mode": "earthwork", "with_profile": False}
|
||||
asyncio.run(replan.replan_route(uuid4(), _body(initial=initial))) # 손 [저장]
|
||||
asyncio.run(replan.replan_route(uuid4(), _body(initial=initial, as_initial=True))) # [적용] 끔
|
||||
assert [call[-1] for call in written] == [True, False]
|
||||
|
||||
|
||||
def test_초안_칸이_어긋나면_400(api_world):
|
||||
body = save.RouteSaveRequest(draft={"nodes": [[0, 0], [1, 1]], "curveOn": [True]})
|
||||
response = asyncio.run(replan.replan_route(uuid4(), body))
|
||||
|
||||
@@ -128,6 +128,24 @@ def _plan(base: list[dict], edits: dict) -> tuple[np.ndarray, np.ndarray]:
|
||||
return pvi_s, pvi_z
|
||||
|
||||
|
||||
def _stretched(old_xy, new_xy, base, chainages) -> dict[float, float]:
|
||||
"""575 B 기대값 — 바뀐 측점 = 앞뒤 같은 자리(없으면 시점 · 종점) 사이 누가거리 비례 옛 높이."""
|
||||
match = line_matcher(old_xy, new_xy)
|
||||
hits = {t: match(t) for t in chainages}
|
||||
anchors = [(0.0, 0.0)] + [(t, s) for t, s in hits.items() if s is not None]
|
||||
anchors.append((_length(new_xy), _length(old_xy)))
|
||||
base_s = [n["chainage_m"] for n in base]
|
||||
base_z = [n["elevation_m"] for n in base]
|
||||
out = {}
|
||||
for t, s in hits.items():
|
||||
if s is None:
|
||||
t0, s0 = max((a for a in anchors if a[0] < t), default=anchors[0])
|
||||
t1, s1 = min((a for a in anchors if a[0] > t), default=anchors[-1])
|
||||
s = s0 + (s1 - s0) * (t - t0) / (t1 - t0) if t1 > t0 and s1 > s0 else s0
|
||||
out[t] = round(float(np.interp(s, base_s, base_z)), 6)
|
||||
return out
|
||||
|
||||
|
||||
def _py(name: str) -> tuple[list[dict], dict]:
|
||||
new = CASES[name]
|
||||
base = carry_base(OLD, new, OLD_BASE, _ground_pvi(new))
|
||||
@@ -262,17 +280,19 @@ def test_같은자리_계획고_그대로(name):
|
||||
assert new_z == pytest.approx(old_z, abs=1e-3), (name, t)
|
||||
|
||||
|
||||
def test_먼교점_옮김_앞쪽_편집_누가거리_그대로_바뀐자리만_지반():
|
||||
def test_먼교점_옮김_앞쪽_편집_누가거리_그대로_바뀐자리는_옛종단_비례():
|
||||
kept, moved, base, edits = _same_place_plan("far")
|
||||
# 앞쪽(같은 선 · 같은 측점) 편집 글자 그대로
|
||||
for key in ("60.000", "80.000", "100.000", "300.000"):
|
||||
assert edits["station_offsets"][key] == pytest.approx(OLD_EDITS["station_offsets"][key])
|
||||
assert edits["curve_radii"]["300.000"] == 400.0
|
||||
# 바뀐 자리 = 옮긴 교점 둘레 · 그 측점은 지반 첫 계획선 · 편집 없음(옛 520 · 540 자유지정 버림)
|
||||
# 바뀐 자리 = 옮긴 교점 둘레 · 그 측점은 옛 종단 길이 비례(지반 아님 · 575 B) · 편집 없음
|
||||
# (옛 520 · 540 자유지정 버림)
|
||||
assert moved and min(moved) > 423.0
|
||||
ground = {n["chainage_m"]: n["elevation_m"] for n in _ground_pvi(CASES["far"])}
|
||||
by = {n["chainage_m"]: n["elevation_m"] for n in base}
|
||||
assert all(by[t] == ground[t] for t in moved)
|
||||
want = _stretched(OLD, CASES["far"], OLD_BASE, list(by))
|
||||
assert all(by[t] == pytest.approx(want[t], abs=1e-4) and by[t] != ground[t] for t in moved)
|
||||
assert not any(float(k) in moved for k in edits["station_offsets"])
|
||||
|
||||
|
||||
@@ -286,11 +306,16 @@ def test_시점_늘림_누가거리_투영_새측점_위():
|
||||
assert edits["curve_radii"].get("320.000") == 400.0
|
||||
|
||||
|
||||
def test_끝_늘림_옛끝까지_이어받고_넘는자리만_지반():
|
||||
def test_끝_늘림_옛끝까지_이어받고_넘는자리는_옛_끝높이():
|
||||
kept, moved, base, _ = _same_place_plan("end")
|
||||
old_len = _length(OLD)
|
||||
assert moved and min(moved) > old_len
|
||||
assert max(t for t, _, _ in kept) > old_len - INTERVAL
|
||||
# 넘는 자리 = 마지막 같은 자리 ~ 옛 끝 비례로 늘림 · 새 끝 = 옛 끝 높이(575 B)
|
||||
by = {n["chainage_m"]: n["elevation_m"] for n in base}
|
||||
want = _stretched(OLD, CASES["end"], OLD_BASE, list(by))
|
||||
assert all(by[t] == pytest.approx(want[t], abs=1e-4) for t in moved)
|
||||
assert base[-1]["elevation_m"] == pytest.approx(OLD_BASE[-1]["elevation_m"], abs=1e-4)
|
||||
|
||||
|
||||
def test_스냅처럼_옆으로_밀림_1m_안_이어받고_넘으면_다시():
|
||||
@@ -301,20 +326,24 @@ def test_스냅처럼_옆으로_밀림_1m_안_이어받고_넘으면_다시():
|
||||
assert "300.000" in edits["station_offsets"]
|
||||
|
||||
|
||||
def test_종단_안_고친_프로젝트는_편집_없음_꼭짓점은_옛것_또는_지반():
|
||||
def test_종단_안_고친_프로젝트는_편집_없음_꼭짓점은_옛것_또는_옛종단_비례():
|
||||
empty = {"station_offsets": {}, "curve_radii": {}}
|
||||
new = CASES["far"]
|
||||
assert carry_edits(OLD, new, OLD_BASE, empty, _stations(new), HALF) == empty
|
||||
base = carry_base(OLD, new, OLD_BASE, _ground_pvi(new))
|
||||
old_s = [n["chainage_m"] for n in OLD_BASE]
|
||||
old_z = [n["elevation_m"] for n in OLD_BASE]
|
||||
ground = {n["chainage_m"]: n["elevation_m"] for n in _ground_pvi(new)}
|
||||
match = line_matcher(OLD, new)
|
||||
want = _stretched(OLD, new, OLD_BASE, [n["chainage_m"] for n in base])
|
||||
for node in base:
|
||||
t = node["chainage_m"]
|
||||
s = match(t)
|
||||
want = ground[t] if s is None else round(float(np.interp(s, old_s, old_z)), 6)
|
||||
assert node["elevation_m"] == pytest.approx(want, abs=1e-9)
|
||||
assert node["elevation_m"] == pytest.approx(want[node["chainage_m"]], abs=1e-4)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("name", ["far", "start", "end", "snap"])
|
||||
def test_바뀐자리_옛종단_비례_575B(name):
|
||||
new = CASES[name]
|
||||
base = carry_base(OLD, new, OLD_BASE, _ground_pvi(new))
|
||||
want = _stretched(OLD, new, OLD_BASE, [n["chainage_m"] for n in base])
|
||||
for node in base:
|
||||
# 끝 측점 반올림(mm)으로 기준점 고름이 갈려도 0.1 mm 안
|
||||
assert node["elevation_m"] == pytest.approx(want[node["chainage_m"]], abs=1e-4), name
|
||||
|
||||
|
||||
def test_엇갈려_지나는_선은_같은자리_아님():
|
||||
|
||||
@@ -241,11 +241,6 @@ def test_초안_planline_은_서버와_같은_꼭짓점(tmp_path: Path, balanced
|
||||
old = {n["chainage_m"]: n["elevation_m"] for n in out["carried"]}
|
||||
new = {n["chainage_m"]: n["elevation_m"] for n in out["basePvi"]}
|
||||
assert new[PIPE] == pytest.approx(old[PIPE], abs=1e-9) # 같은 자리 관 = 옛 높이
|
||||
if not balanced: # 옮긴 시설 측점 계획선 = 새 원지반(옛 계획선이 지반 종단)
|
||||
for at in (251.0, 333.0):
|
||||
assert _plan_at(out["basePvi"], alignment["policy"], at) == pytest.approx(
|
||||
_new_ground(at), abs=1e-4
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("balanced", [False, True])
|
||||
@@ -266,10 +261,6 @@ def test_노선_초안_중_마커로_옮긴_관도_서버_차례와_같은_값(t
|
||||
kept = [n for n in out["home"] if abs(n["chainage_m"] - PIPE) >= 0.006]
|
||||
server = _seat_born_nodes(_new_alignment(alignment, out["home"]), kept, [MOVED]) # 관 저장 뒤
|
||||
_same_nodes(out["basePvi"], server, tol=1e-5) # 맞춤 풀이 수렴 폭(0.000001 m) 안팎
|
||||
if not balanced:
|
||||
assert _plan_at(out["basePvi"], alignment["policy"], MOVED) == pytest.approx(
|
||||
_old_ground(MOVED), abs=1e-4
|
||||
)
|
||||
|
||||
|
||||
def test_구조물_초안은_서버_따라감_위에(tmp_path: Path) -> None:
|
||||
|
||||
Reference in New Issue
Block a user