Files
Aislo/resources/tester/test_349_0j_draft_vs_saved.py
T
eomsangdonandClaude Opus 5.5 23bb885610 fix(계산묶음): 349-0j 노선 초안 미리보기 누가토량 = [저장] 정본 — cross 단계 미교차 넓힘(서버 짝 그대로)
- 대조(검증_sub2 · 2번째 꺾임점 서쪽 20 m · 서버 [저장] 체인을 실제로 돌리고 체인 스스로 되살림 — 최신 노선 · 관 · 파일 그대로 확인):
  미리보기 -11,378.5 ㎥ = 서버 이월 직후(보정 전) -11,391.3 ㎥(측점 · 계획고 · 면적 측점마다 같음) · 정본 -21,249.3 ㎥
  · 갈린 몫 = [확정] 보정 — 미교차 넓힘(표본 20 m → 지표면 끝) -9,531 ㎥ · 관 구조물 면적 -334 ㎥(넓힌 뒤 기준 1,971 ㎥) · 그 밖 +7 ㎥
- logic/B06_Section_Cross_Stage: createCrossStage({ grid }) — 비탈이 표본 끝에서 원지반과 안 만나면 열린 쪽만 +5 m 씩 격자에서 다시 떠 셈(extendUnclosed ↔ 서버 extend_unclosed 짝 · 격자 없으면 안 넓힘)
- logic/B05_Profile_Pipeline_Detail: 초안 상세 설계 = cross 단계 결과 사본(구조물 면적 얹기가 단계 기억을 안 고침)
- 넓힘 + 서버 구조물 면적이면 검증_sub2 초안 정본과 12.5 ㎥ 차이(측점 끝 EP 0.1 ㎡)
- 시험 test_349_0j_draft_vs_saved 8(가짜 · 검증_sub7 — 넓힌 끝 · 면적 · 누가토량 = 서버 엔진 + extend_unclosed · 검증_sub7 넓힘 17곳 -16,639.8 → -30,757.9 = 서버 · 단계 합 65 ms)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QfHKQs7J3hQELKMWQMUJEy
2026-10-16 15:42:14 +09:00

176 lines
6.8 KiB
Python

"""PLAN 349-0j — 노선 초안 미리보기 누가토량 대 [저장] 정본.
갈린 자리(검증_sub2 · 2번째 꺾임점 서쪽 20 m · 서버 체인을 실제로 돌리고 체인 스스로 되살림):
미리보기 = 서버 이월 직후(보정 전)와 측점 · 계획고 · 면적이 같음(-11,378.5 대 -11,391.3 ㎥)
· [확정] 보정 중 미교차 넓힘(표본 20 m → 지표면 끝) -9,531 ㎥ · 관 구조물 면적 -334 ㎥(넓힌 뒤 기준 1,971 ㎥).
· cross 단계에 설계 지형 격자를 넘기면 미교차 측점을 서버와 같은 규칙(`extendUnclosed` ↔
`extend_unclosed`)으로 넓힘 — 넓힌 끝 · 면적 · 누가토량이 서버 엔진과 같음
· 격자를 안 넘기면(지금 묶음) 넓힘 없음 — 서버와 갈림
· 초안 상세의 설계 = cross 단계 결과의 사본(구조물 면적 얹기가 단계 기억을 안 고침)
짝: `helper_349_0j_draft_vs_saved.cjs` · 지형 · 노선 · 관 틀 = `test_346_2_pipeline_cross_page.py`.
"""
from __future__ import annotations
import json
import subprocess
import sys
import tempfile
from pathlib import Path
import pytest
HERE = Path(__file__).resolve().parent
ROOT = HERE.parents[1]
for path in (ROOT, HERE):
if str(path) not in sys.path:
sys.path.insert(0, str(path))
from B05_Profile.B05_Profile_Engine_Grade import resolve_grade_options # noqa: E402
from B05_Profile.B05_Profile_Engine_Grade_Profile import ( # noqa: E402
design_ground_following_profile,
)
from B06_Section.B06_Section_Engine_Design import compute_cross_design # noqa: E402
from B06_Section.B06_Section_Engine_SampleExtend import extend_unclosed # noqa: E402
from common_util.common_util_surface_sampler import DtmGridSampler, dtm_grid_bytes # noqa: E402
from config.config_system_design import STANDARD_CROSS_SECTION # noqa: E402
from test_346_2_pipeline_cross_page import ( # noqa: E402
INTERVAL_M,
MIN_RADIUS_M,
ONES,
_fake,
_line,
_pipes,
_real,
_saved_detail,
_sections,
_types,
)
pytestmark = pytest.mark.skipif(
not (ROOT / "config" / "node_modules" / "typescript").is_dir(),
reason="프론트엔드 의존성 미설치",
)
def _server(ts: dict, sampler) -> list[dict]:
"""[저장] 정본 길 — 같은 노선 서버 측점 · 지반 → 엔진 설계 → 미교차 넓힘(확정 보정)."""
pipes = [
{"id": s["key"].split(":", 1)[1], "chainage_m": s["chainage"], "source": "user"}
for s in ts["stations"]
if s["key"].startswith("pipe:")
]
data = _sections(ts["line"], sampler, pipes, ts["curves"])
out = []
for a, b in zip(ts["stations"], data["cross_sections"]):
assert abs(a["chainage"] - b["chainage_m"]) < 1e-3
def compute(samples, a=a, b=b):
return compute_cross_design(
samples,
a["elevation"],
ground_type="ripping_rock",
section_mode=f"{b.get('uphill_side') or 'left'}_cut",
plan_radius_m=b.get("plan_radius_m"),
curve_outer_side=b.get("curve_outer_side"),
curve_widening_m=b.get("curve_widening_m"),
)
design = compute(b["samples"])
wide = extend_unclosed(b["samples"], b["frame"], compute, sampler)
if wide is not None and (wide[1]["left_m"] or wide[1]["right_m"]):
design = compute(wide[0])
line = design["design_line"]
out.append(
{
"chainage": b["chainage_m"],
"cut": design["cut_area_m2"],
"fill": design["fill_area_m2"],
"lineEnds": [line[0]["offset_m"], line[-1]["offset_m"]],
}
)
return out
def _final(rows) -> float:
nets = [(r["chainage"], r["cut"] - r["fill"]) for r in rows]
return sum((n0 + n1) / 2 * (c1 - c0) for (c0, n0), (c1, n1) in zip(nets, nets[1:]))
@pytest.fixture(scope="module", params=[_fake, _real], ids=["가짜", "검증_sub7"])
def r(request) -> dict:
with tempfile.TemporaryDirectory() as folder:
npz, nodes = request.param(Path(folder))
line = _line(nodes)
sampler = DtmGridSampler.from_npz(npz)
pipes = _pipes(line)
saved = _sections(line, sampler, pipes)
options = resolve_grade_options("trunk", terrain_type="normal", paved=False, stored=None)
policy = design_ground_following_profile(saved["longitudinal"], options)[0]["policy"]
job = {
"nodes": nodes,
"line": line,
"minRadius": MIN_RADIUS_M,
"policy": policy,
"types": _types(),
"interval": INTERVAL_M,
"pipes": pipes,
"standard": STANDARD_CROSS_SECTION,
"conversion": ONES,
"detail": _saved_detail(saved),
"haulDelayMs": 20,
"drags": [{"index": 1, "dx": -20.0, "dy": 0.0}],
}
path = Path(folder) / "job.json"
path.write_text(json.dumps(job), encoding="utf-8")
grid = Path(folder) / "grid.bin"
grid.write_bytes(dtm_grid_bytes(npz))
proc = subprocess.run( # noqa: S603 — 고정 실행 파일
["node", str(HERE / "helper_349_0j_draft_vs_saved.cjs"), str(path), str(grid)],
capture_output=True,
text=True,
encoding="utf-8",
timeout=180,
)
assert proc.returncode == 0, proc.stderr
out = json.loads(proc.stdout)
out["server"] = _server(out["wide"], sampler)
out["kind"] = request.param.__name__
return out
def test_격자를_넘기면_서버와_같은_넓힘_면적(r):
wide, server = r["wide"], r["server"]
assert wide["outcome"] == "done"
assert len(wide["stations"]) == len(server)
for a, b in zip(wide["stations"], server):
assert a["lineEnds"] == pytest.approx(b["lineEnds"]), a["key"]
assert abs(a["cut"] - b["cut"]) < 0.05 + 0.01 * b["cut"], a["key"]
assert abs(a["fill"] - b["fill"]) < 0.05 + 0.01 * b["fill"], a["key"]
def test_미리보기_누가토량_서버_정본과_같음(r):
want = _final(r["server"])
got = r["wide"]["final"]
assert got is not None
assert abs(got - want) <= max(1.0, abs(want) * 0.005), (got, want)
def test_격자_없으면_넓힘_없음(r):
plain, wide = r["plain"], r["wide"]
widened = [
s["key"] for s in wide["stations"] if s["lineEnds"] and max(map(abs, s["lineEnds"])) > 20.01
]
assert all(
s["lineEnds"] is None or max(map(abs, s["lineEnds"])) <= 20.01 for s in plain["stations"]
)
if r["kind"] == "_real": # 실제 지형 — 미교차가 있어 넓힘이 누가토량을 바꿈
assert widened
assert abs(plain["final"] - wide["final"]) > 1.0
else:
assert plain["final"] == pytest.approx(wide["final"]) or widened
def test_초안_상세_설계는_사본(r):
assert r["wide"]["copied"] is True