Files
Aislo/resources/tester/test_b06_structure_walls_mirror.py
T
eomsangdonandClaude Opus 5.5 bbd6254892 feat(구조물표): 322-1 부품 상자 · 표 읽기 — 목록 · 측점 · 카드 이름 · 형상 고르기 · 3D 몸체 · 알약 띠가 종류 키 · 군 글자 · 자리꼴 · managed_by 대신 구조물 표(catalog) 한 줄을 읽음 · 코드 숨김 목록 걷음 · ★ 넷 + 관 공식 한 벌
- 부품 상자 common_util_structure_parts.json(갈래마다 부품 · 읽는 칸 · 자리꼴 · 정본 · 42종 처음값) · 표 읽기 common_util_structure_catalog.ts / .py 짝(정의 반영 ⊕ 처음값 ⊕ 부품 끔) · 거울 시험 test_322_1_catalog_mirror
- 목록: pickGroups 군 차례 = 구조물군 열쇠 · 페이지 거르기 listPick(B05 both · B06 both + b06) · HIDDEN_PICK_GROUPS · HIDDEN_PICK_TYPES · pickable 걷음(옛 대피 셋 asRoad 그대로)
- 측점: structureStationMarks · pipeStationMarks · is_station_planting_type · resolve_extra_stations = 표 횡단도(anchor · span3 · none)
- 카드 이름: cardTitle / card_title 한 공식(빈칸 = 그 종류 정의 기본값) · 표시 이름이 정의 관종도 앎
- 형상: 벽 = shape wall(형태 = 형상칸.form · 물빼기 = 형상칸.weep) · 포장 = shape pavement(「사용 중」 거르기 없앰) · 맹암거 · 사토 · 관 지점 시설 세트 = shape
- 3D 몸체 = 그림 자료 주인 종류의 표 3D 부품 · 알약 띠 = 표 자리꼴 구간(관 지점 시설은 알약만)
- ★1 노출형 · 개거 횡단도 none · ★2 쇄석 화면에서도 포장층 · ★3 기슭막이 표 자리꼴 interval(정의 파일 그대로) · ★4 코드 숨김 10종 list hidden · ★5 관 빈칸 D800 → 정의 기본 D1000
- 옛 시험: 도움 파일 로더 27개가 표 모듈 공용 한 벌(helper_322_catalog_module) 씀 · 기대값 고침(272_1 · b05_extra_stations · roundtrip · 314 · 52_7 · walls 거울 · wall_drain_pipe)

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

194 lines
7.1 KiB
Python

"""C군 벽 제원 — 파이썬·TS 짝 거울 테스트.
`B06_Section_Engine_Structures_Wall`(서버, 저장분 기준)과
`common_util/common_util_structure_walls.ts`(브라우저, 미저장 목록 기준)가 같은 입력에
같은 제원을 내는지 대조한다(CLAUDE.md 5장 — 짝을 두면 거울 테스트 필수).
TS 는 프로젝트 tsc 로 옮겨 **실제 코드 그대로** 돌린다(배수관 세트 거울 테스트와 같은 방식).
"""
import json
import subprocess
import sys
import tempfile
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
TSC = PROJECT_ROOT / "config" / "node_modules" / "typescript" / "bin" / "tsc"
STRUCTURES = [
{
"structure_id": "abc",
"type_id": "masonry_wet",
"placement": "interval",
"chainage_m": 40.0,
"start_m": 35.0,
"end_m": 45.0,
"options": {"height_m": 2.5, "length_m": 10, "before_m": 5, "after_m": 5},
},
{
"structure_id": None,
"type_id": "retaining_wall",
"placement": "interval",
"chainage_m": 120.0,
# 뒤집힌 구간 — 두 벌 다 작은 값을 시작으로 정렬해야 한다.
"start_m": 130.0,
"end_m": 110.0,
"options": {"height_m": 3.0, "side": "우", "tiers": 2},
},
{
"structure_id": "no-range",
"type_id": "soil_guard",
"placement": "interval",
"chainage_m": 200.0,
"start_m": None,
"end_m": None,
"options": {},
},
{
# 178-3 「추가 단」 상자 둘(둘째 상자 높이 빈 값 · 글 값은 수 아님)
"structure_id": "tiers",
"type_id": "soil_guard",
"placement": "interval",
"chainage_m": 260.0,
"start_m": 250.0,
"end_m": 270.0,
"options": {
"form": "돌망태",
"height_m": 2.0,
"tiers": 5,
"tier_2_form": "",
"tier_2_height": "1.5",
"tier_1_form": "콘크리트",
"tier_1_height": 2.0,
},
},
]
NAMES = {"masonry_wet": "돌쌓기(찰)", "retaining_wall": "옹벽", "soil_guard": "흙막이"}
CHAINAGES = [20.0, 40.0, 45.02, 60.0, 115.0, 200.0]
_RUNNER = """
import { readFileSync, writeFileSync } from "node:fs";
import { attachWallSpecs, wallSpecsFrom } from "./common_util_structure_walls.js";
const input = JSON.parse(readFileSync(process.argv[2], "utf8"));
const specs = wallSpecsFrom(input.structures, new Map(Object.entries(input.names)));
const sections = input.chainages.map((chainage_m) => ({ chainage_m }));
attachWallSpecs(sections, specs);
writeFileSync(process.argv[3], JSON.stringify({ specs, sections }));
"""
def _python_result() -> dict:
from B06_Section.B06_Section_Engine_Structures_Wall import (
_wall_form,
attach_wall_structures,
tier_extras,
)
specs = []
for item in STRUCTURES:
name = NAMES.get(item["type_id"])
start, end = item["start_m"], item["end_m"]
if not name or start is None or end is None:
continue
options = item["options"] or {}
extras = tier_extras(item["type_id"], options)
specs.append(
{
"structure_id": item["structure_id"],
"type_id": item["type_id"],
"name": name,
"start_m": float(min(start, end)),
"end_m": float(max(start, end)),
"anchor_m": float(item["chainage_m"]),
# 빈 형태 = 구조물 표 형상칸.form(322-1 · 옛 `_FORM_BY_TYPE`)
"form": options.get("form") or _wall_form(item["type_id"]),
"height_m": options.get("height_m"),
"side": options.get("side"),
# 기초 축(2026-09-09 신설) — 저장 칸과 같은 글자. 초안 경로에서 빠지면
# 터파기가 안 그려져 「그림이 값에 안 따라간다」가 된다.
"foundation": options.get("foundation"),
# 178-3 단 수 = 1 + 「추가 단」 상자 수 · 상자 없으면 옛 tiers
"tiers": len(extras) + 1 if extras else options.get("tiers"),
"extras": extras,
"lift_m": options.get("lift_m"),
"shift_m": options.get("shift_m"),
# 전면 기울기 판정 칸(2026-09-14 B5) — 저장 원본 그대로.
"face_role": options.get("face_role"),
"face_slope_ratio": options.get("face_slope_ratio"),
}
)
# 얹기 규칙은 같은 함수를 쓸 수 없으므로(정본 파일을 읽는다) 여기서 같은 규칙으로 흉내낸다.
sections = [{"chainage_m": value} for value in CHAINAGES]
for section in sections:
for spec in specs:
if spec["start_m"] - 0.02 <= section["chainage_m"] <= spec["end_m"] + 0.02:
section["revetment"] = spec
break
assert callable(attach_wall_structures)
return {"specs": specs, "sections": sections}
def _ts_result(tmp_path: Path) -> dict:
out = tmp_path / "js"
subprocess.run( # noqa: S603 — 고정 실행 파일
[
"node",
str(TSC),
str(PROJECT_ROOT / "common_util" / "common_util_structure_walls.ts"),
"--outDir",
str(out),
"--module",
"esnext",
"--target",
"es2022",
"--moduleResolution",
"bundler",
"--ignoreConfig",
],
cwd=str(PROJECT_ROOT),
check=True,
capture_output=True,
)
(out / "runner.mjs").write_text(_RUNNER, encoding="utf-8")
payload = tmp_path / "input.json"
result = tmp_path / "output.json"
payload.write_text(
json.dumps({"structures": STRUCTURES, "names": NAMES, "chainages": CHAINAGES}),
encoding="utf-8",
)
subprocess.run( # noqa: S603
["node", str(out / "runner.mjs"), str(payload), str(result)],
cwd=str(PROJECT_ROOT),
check=True,
capture_output=True,
)
return json.loads(result.read_text(encoding="utf-8"))
def test_wall_specs_mirror():
with tempfile.TemporaryDirectory() as workdir:
ts = _ts_result(Path(workdir))
py = _python_result()
assert len(ts["specs"]) == len(py["specs"]) == 3
assert ts["specs"] == py["specs"]
tiered = ts["specs"][2]
assert tiered["tiers"] == 3
assert tiered["extras"] == [
{"form": "콘크리트", "height_m": 2.0},
{"form": None, "height_m": None},
]
assert ts["specs"][1]["extras"] == [] # 상자 없는 옹벽 = 옛 tiers 2 그대로
assert ts["specs"][1]["tiers"] == 2
ts_marks = [
(s["chainage_m"], (s.get("revetment") or {}).get("type_id")) for s in ts["sections"]
]
py_marks = [
(s["chainage_m"], (s.get("revetment") or {}).get("type_id")) for s in py["sections"]
]
assert ts_marks == py_marks
# 구간 밖 측점에는 안 붙는다 / 경계 오차(0.02m) 안은 붙는다.
assert dict(ts_marks)[20.0] is None
assert dict(ts_marks)[45.02] == "masonry_wet"