Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016PSXsvyDxjNmufteN5bqmt
132 lines
4.8 KiB
Python
132 lines
4.8 KiB
Python
"""노면공 포장 제원(88) — 파이썬·TS 짝 거울 시험.
|
|
|
|
`common_util/common_util_structure_pavement.py`(서버 · 저장분) 와 `.ts`(브라우저 · 초안)가
|
|
같은 입력에 같은 `section.pavement` 를 내는지 대조. 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"
|
|
|
|
from common_util.common_util_structure_pavement import ( # noqa: E402
|
|
attach_pavement_specs,
|
|
pavement_specs_from,
|
|
)
|
|
|
|
STRUCTURES = [
|
|
{
|
|
"type_id": "pavement_concrete",
|
|
"placement": "interval",
|
|
"chainage_m": None,
|
|
"start_m": 100.0,
|
|
"end_m": 160.0,
|
|
"options": {"pave_spec": "콘크리트", "height_m": 0.25, "widening_off": "120-140, x-1,,"},
|
|
},
|
|
{
|
|
# 뒤집힌 구간 · 두께 빔 · 사양 빔 — 두 벌 다 작은 값을 시작으로 · null 로.
|
|
"type_id": "gravel_surfacing",
|
|
"placement": "interval",
|
|
"start_m": 150.0,
|
|
"end_m": 130.0,
|
|
"options": {"pave_spec": "", "height_m": "0.3"},
|
|
},
|
|
{"type_id": "pavement_concrete", "start_m": None, "end_m": None, "options": {}},
|
|
{"type_id": "masonry_wet", "start_m": 0.0, "end_m": 500.0, "options": {}},
|
|
{
|
|
"type_id": "pavement_concrete",
|
|
"chainage_m": 300.0,
|
|
"options": {"pave_spec": "아스콘", "height_m": 0.05, "widening_off": "290-310"},
|
|
},
|
|
]
|
|
TYPE_IDS = ["pavement_concrete", "gravel_surfacing"]
|
|
CHAINAGES = [99.97, 100.0, 119.99, 130.0, 145.0, 160.02, 160.05, 300.0, 400.0]
|
|
|
|
_RUNNER = """
|
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
import { attachPavementSpecs, pavementSpecsFrom } from "./common_util_structure_pavement.js";
|
|
const input = JSON.parse(readFileSync(process.argv[2], "utf8"));
|
|
const sections = input.chainages.map((chainage_m) => ({ chainage_m }));
|
|
attachPavementSpecs(sections, pavementSpecsFrom(input.structures, new Set(input.type_ids)));
|
|
writeFileSync(process.argv[3], JSON.stringify(sections));
|
|
"""
|
|
|
|
|
|
def _python_result() -> list[dict]:
|
|
sections = [{"chainage_m": value} for value in CHAINAGES]
|
|
attach_pavement_specs(sections, pavement_specs_from(STRUCTURES, set(TYPE_IDS)))
|
|
return sections
|
|
|
|
|
|
def _ts_result(tmp_path: Path) -> list[dict]:
|
|
out = tmp_path / "js"
|
|
subprocess.run( # noqa: S603 — 고정 실행 파일
|
|
[
|
|
"node",
|
|
str(TSC),
|
|
str(PROJECT_ROOT / "common_util" / "common_util_structure_pavement.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, "type_ids": TYPE_IDS, "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_pavement_specs_mirror():
|
|
with tempfile.TemporaryDirectory() as workdir:
|
|
ts = _ts_result(Path(workdir))
|
|
py = _python_result()
|
|
assert ts == py
|
|
by = {section["chainage_m"]: section.get("pavement") for section in py}
|
|
# 범위 밖 · 경계 오차(0.02 m) 안
|
|
assert by[99.97] is None and by[160.05] is None and by[400.0] is None
|
|
assert [entry["type_id"] for entry in by[100.0]] == ["pavement_concrete"]
|
|
assert by[160.02][0]["thickness_m"] == 0.25
|
|
# 확폭 끈 구간 120~140 (±0.02) — 끔 · 못 읽는 조각은 버림
|
|
assert by[119.99][0]["widening_on"] is False
|
|
assert by[100.0][0]["widening_on"] is True
|
|
# 겹친 범위 = 둘 다 · 뒤집힌 구간 정렬 · 문자 두께 · 빈 사양 = null
|
|
assert [entry["type_id"] for entry in by[145.0]] == ["pavement_concrete", "gravel_surfacing"]
|
|
gravel = by[145.0][1]
|
|
assert (gravel["start_m"], gravel["end_m"]) == (130.0, 150.0)
|
|
assert gravel["thickness_m"] is None and gravel["spec"] is None
|
|
# 기준 측점만 있는 옛 항목 = 그 측점 한 점
|
|
assert by[300.0] == [
|
|
{
|
|
"type_id": "pavement_concrete",
|
|
"spec": "아스콘",
|
|
"thickness_m": 0.05,
|
|
"start_m": 300.0,
|
|
"end_m": 300.0,
|
|
"widening_on": False,
|
|
}
|
|
]
|