Files
Aislo/resources/tester/test_m02_table_fill.py
T
eomsangdonandClaude Opus 5.5 abd0d4b126 fix(M02): 구조물집계표 묶음 열은 값마다 한데 · 빈 설계값 머리는 「높이 미입력」 꼴 · 계산 열 설계값 딱지 뗌 (브레인 최종 확인)
- 관공 등 묶음 열 차례 = 관종·관경마다 관매설 · 커플링 짝 — 머리 합치기가 값마다 묶임
- 머리틀은 맨 자리표 + 값꼴(H={} · T={}cm · Φ{}) · 빈 값은 빈값 글 · 열 id 는 그대로(빈 값 -)
- 포장 A · 수축줄눈 · 커플링 · 큰돌쌓기 면적 = 바인딩 대신 펼침틀(묶음 · 머리틀 · 식틀)
- 식틀 자리 값이 비면 식을 빼고 설명에 적음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015LuapLYqN1GGFD8Y1PStD5
2026-09-25 12:00:54 +09:00

213 lines
9.7 KiB
Python

"""M02 구조물 집계표 채우기 — 펼침 열 · 측점 줄 · 관 줄 나눔 · 손 값 지킴 (임시 프로젝트)."""
from __future__ import annotations
import json
from pathlib import Path
from typing import Any
import pytest
from config import config_system
from M02_MasterTemplete import M02_Table_Fill as fill
MASTER = config_system.PROJECT_ROOT / "resources/master_template/table/구조물집계표.json"
def _master() -> dict[str, Any]:
return json.loads(MASTER.read_text(encoding="utf-8"))
@pytest.fixture
def project(tmp_path: Path) -> Path:
root = tmp_path / "proj"
route = root / "B05_Profile/route"
route.mkdir(parents=True)
structures = [
{
"type_id": "masonry_dry",
"chainage_m": 85,
"start_m": 80,
"end_m": 90,
"options": {"height_m": 2.0, "length_m": 10},
},
{
"type_id": "masonry_wet",
"chainage_m": 80,
"start_m": 80,
"end_m": 90,
"options": {"height_m": 2.5, "length_m": 10},
},
{
"type_id": "pavement_concrete",
"chainage_m": 205,
"start_m": 200,
"end_m": 230,
"options": {"length_m": 30, "width_m": 3, "thickness_cm": 20, "joint_spacing_m": 5},
},
{"type_id": "position_sign", "chainage_m": 300},
{
"type_id": "boulder_masonry",
"chainage_m": 325,
"start_m": 320,
"end_m": 330,
"options": {"height_m": 2.5, "length_m": 10, "bond": "메쌓기", "stone_cm": "40~60"},
},
]
for index, item in enumerate(structures):
item.setdefault("structure_id", f"st{index}")
item.setdefault("placement", "interval" if "start_m" in item else "point")
(route / "structures.json").write_text(
json.dumps({"revision": 1, "structures": structures}, ensure_ascii=False), encoding="utf-8"
)
edits = root / "B04_PreProcess/drainage/edits"
edits.mkdir(parents=True)
points = [
{"chainage_m": 85.05, "source": "user", "options": {"pipe_diameter_mm": "1000"}},
{"chainage_m": 85.05, "source": "user", "options": {"pipe_kind": "흄관"}},
{"chainage_m": 173.09, "source": "user", "facility": "ford_bridge", "options": {}},
]
(edits / "pipe_points.json").write_text(json.dumps({"points": points}), encoding="utf-8")
return root
def _filled(project: Path, document: dict[str, Any] | None = None) -> dict[str, Any]:
return fill.fill_table(project, document or _master(), {85.0: 14.0})
def _col(doc: dict[str, Any], col_id: str) -> dict[str, Any]:
return next(column for column in doc["열"] if column["id"] == col_id)
def test_펼침_열은_설계에_쓰인_값만_차례대로(project: Path) -> None:
doc = _filled(project)
ids = [column["id"] for column in doc["열"]]
assert [i for i in ids if i.startswith("ms|")] == ["ms|찰쌓기|2.5", "ms|메쌓기|2.0"]
assert _col(doc, "ms|메쌓기|2.0")["머리"] == ["돌쌓기", "메쌓기", "H=2.0"]
assert [i for i in ids if i.startswith("pp_len|")] == [
"pp_len|흄관|1000",
"pp_len|파형강관|1000",
]
assert not any(i.startswith("rw|") for i in ids) # 옹벽 없음 → 열 없음
assert "rv" not in ids and "ps" in ids # 펼침 마스터 칸은 빠지고 고정 열은 남음
# 관 유입·유출 기슭막이는 레지스트리 기본값(찰 · 메 · 높이 없음)으로 열이 섬 · id 는 설계 값
assert "rv|돌쌓기(찰)|-" in ids and "rv|돌쌓기(메)|-" in ids
assert _col(doc, "rv|돌쌓기(찰)|-")["머리"] == ["돌기슭막이", "찰쌓기", "높이 미입력"]
assert _col(doc, "fb|1000|-")["머리"] == ["세월교", "Φ1000×련수 미입력", None]
assert not any(i.startswith("pg_in|") for i in ids) # 날개벽·집수정 형식 없음 → 관보호공 없음
def test_묶음_열은_값마다_한데(project: Path) -> None:
ids = [column["id"] for column in _filled(project)["열"]]
assert [i for i in ids if i.startswith("pp_")] == [
"pp_len|흄관|1000",
"pp_cp|흄관|1000",
"pp_len|파형강관|1000",
"pp_cp|파형강관|1000",
]
assert [i for i in ids if i.startswith("pv_")] == [
f"pv_{k}|20" for k in ("b", "l", "w", "a", "jt")
]
def test_계산_열은_설계값_딱지_없음(project: Path) -> None:
master = _master()
assert not [c["id"] for c in master["열"] if c.get("식") and c.get("바인딩")]
doc = _filled(project)
for col_id in ("pv_a|20", "pv_jt|20", "pp_cp|파형강관|1000", "bm_area|메쌓기|40~60|2.5"):
column = _col(doc, col_id)
assert "바인딩" not in column and column["펼침틀"]["원열"]["id"] == col_id.split("|")[0]
assert _col(doc, "pp_cp|파형강관|1000")["머리"] == ["관공", "파형강관 Φ1000", "커플링밴드"]
# 다시 저장 · 채워도 같은 열(계산 열도 원열로 되돌림)
again = fill.fill_table(project, fill.strip_design(doc), {85.0: 14.0})
assert [c["id"] for c in again["열"]] == [c["id"] for c in doc["열"]]
def test_계산_열_식은_같은_묶음_열로_바뀌고_빈_변수면_안_셈(project: Path) -> None:
doc = _filled(project)
assert _col(doc, "pv_a|20")["식"] == 'IF([pv_l|20]>0,[pv_b|20]*[pv_l|20]+[pv_w|20],"")'
cp = _col(doc, "pp_cp|파형강관|1000")["식"]
assert "[pp_len|파형강관|1000]" in cp and "[$파형강관_1본_m]" in cp
assert "식" not in _col(doc, "pp_cp|흄관|1000") # 흄관 1본 길이 빈칸 → 계산 안 함
assert _col(doc, "bm_area|메쌓기|40~60|2.5")["식"] == (
'IF([bm_len|메쌓기|40~60|2.5]>0,[bm_len|메쌓기|40~60|2.5]*2.5,"")'
)
def test_측점_줄_같은_측점은_한_줄_관은_나눔(project: Path) -> None:
doc = _filled(project)
rows = {row["id"]: row for row in doc["줄"]}
assert doc["줄"][0]["고정"] == "전구간"
interval = rows["s80.00~90.00"]["값"]
assert interval["sta"] == "NO.4~NO.4+10"
assert interval["ms|찰쌓기|2.5"] == 10 and interval["ms|메쌓기|2.0"] == 10
first, second = rows["s85.05"]["값"], rows["s85.05#1"]["값"]
assert first["sta"] == second["sta"] == "NO.4+5.05"
assert first["pp_len|파형강관|1000"] == 14.0 and "pp_len|흄관|1000" not in first
assert second["pp_len|흄관|1000"] == 14.0 # 한 측점 횡단 관 연장은 하나 — 두 관이 같이 씀
assert first["rv|돌쌓기(찰)|-"] == second["rv|돌쌓기(찰)|-"] == 10
assert rows["s300.00"]["값"]["ps"] == 1
assert [row["값"].get("no") for row in doc["줄"][1:]] == [str(i) for i in range(1, 8)]
assert rows["s200.00~230.00"]["식"]["pv_jt|20"].count("/5)") == 1 # 줄눈 간격 5 박음
assert doc["알림"] == []
bare = fill.fill_table(project, _master(), {})
assert bare["알림"] == ["관 연장 없음 2곳 — B06 횡단 설계 전이면 빈칸"]
assert not any("pp_len" in key for row in bare["줄"] for key in row["값"])
def test_손_값과_전구간_줄은_다시_채워도_지킴(project: Path) -> None:
doc = _filled(project)
doc["줄"][0]["값"]["ps"] = 3
next(row for row in doc["줄"] if row["id"] == "s300.00")["값"]["h_intake"] = 2
doc["줄"].append({"id": "u1", "값": {"sta": "NO.9", "h_rockfall": 4}, "손": True})
doc["줄"].append({"id": "s999.00", "값": {"sta": "NO.49+19", "h_waste": 1.5}})
again = _filled(project, doc)
rows = {row["id"]: row for row in again["줄"]}
assert rows["all"]["값"]["ps"] == 3
assert rows["s300.00"]["값"]["h_intake"] == 2
assert rows["u1"]["값"]["h_rockfall"] == 4
assert rows["s999.00"]["손"] is True and rows["s999.00"]["값"]["h_waste"] == 1.5
assert [c["id"] for c in again["열"]] == [c["id"] for c in doc["열"]] # 두 번 펼치지 않음
def test_측점_표기() -> None:
assert fill.station_label(0) == "NO.0"
assert fill.station_label(38) == "NO.1+18"
assert fill.station_label(59.999) == "NO.3"
assert fill.station_label(2413.5) == "NO.120+13.5"
def test_저장은_양식과_손_값만_다시_채우면_제자리(project: Path) -> None:
master = _master()
doc = _filled(project)
rows = {row["id"]: row for row in doc["줄"]}
rows["s300.00"]["값"]["h_intake"] = 2
rows["s85.05#1"]["값"]["memo"] = "흄관 확인"
doc["줄"][0]["값"]["ps"] = 3
saved = fill.strip_design({**doc, "결과": {"계산": {}}})
assert [c["id"] for c in saved["열"]] == [
c["id"] for c in master["열"]
] # 펼침 규칙 열로 되돌림
assert "알림" not in saved and "결과" not in saved
assert saved["줄"] == [
{"id": "all", "값": {"sta": "전구간", "ps": 3}, "고정": "전구간"},
{"id": "s85.05#1", "값": {"sta": "NO.4+5.05", "memo": "흄관 확인"}},
{"id": "s300.00", "값": {"sta": "NO.15", "h_intake": 2}},
]
again = {row["id"]: row for row in _filled(project, saved)["줄"]}
assert again["s300.00"]["값"]["h_intake"] == 2 and again["s300.00"]["값"]["ps"] == 1
assert again["s85.05#1"]["값"]["memo"] == "흄관 확인"
assert again["s85.05#1"]["값"]["pp_len|흄관|1000"] == 14.0
def test_측점이_없어진_손_값은_맨_아래_비고_줄로(project: Path) -> None:
saved = fill.strip_design(_filled(project))
saved["줄"].append({"id": "s300.00", "값": {"sta": "NO.15", "h_intake": 2}})
saved["줄"].append({"id": "s555.00", "값": {"sta": "NO.27+15", "h_intake": 1}})
doc = _filled(project, saved)
last = doc["줄"][-1]
assert last["id"] == "s555.00" and last["손"] is True
assert last["값"] == {"sta": "NO.27+15", "h_intake": 1, "memo": "설계에서 측점 없어짐"}
kept = fill.strip_design(doc)
assert kept["줄"][-1]["값"]["h_intake"] == 1 # 다음 저장에도 남음