⚠ **뿌리** — `tmp/` 는 창 사이에 안 건너감(실측 확인: 상대 창이 놓은 `tmp/_sync_probe.txt` 가 시간을 두고 두 번 봐도 안 보임). 그래서 **정본(등록부 스키마)만 건너가고 그것을 읽는 시험은 안 건너가** 오늘 두 번, 같은 시험이 **연 창은 통과·받은 창은 실패**가 됐음. - `tmp/tests/*` 를 `resources/tester/` 로 **복사**(127 파일). 내용은 **한 줄도 안 고침** - `tmp/tests` 는 **남겨 둠** — 되돌릴 자리(사용자 지시) - 실행: `./venv/Scripts/python.exe -m pytest resources/tester/ -q` 옮기기 전과 **같은 수**: 617 통과 / 22 건너뜀 / 실패 0 ⇒ 이제 시험·예외·까닭이 **정본과 함께** 움직임. 오늘 세운 「예외는 정본 스키마에」와 짝임. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
146 lines
5.6 KiB
Python
146 lines
5.6 KiB
Python
"""포장 구간 정본 + 물넘이포장 횡단 스펙 — 2026-08-28 사용자 확정 스펙.
|
|
|
|
- 포장은 **사용자가 구간으로 지정**한다(구조물 정본 G군). 종단경사 자동 판정은 더 이상
|
|
포장을 켜지 않고 경고(`pavement_suggested`)로만 남는다.
|
|
- 물넘이포장은 노면을 월류 폭만큼 판 자리다 — 범위 안 모든 횡단도에 붙고, 그 범위는
|
|
언제나 포장이다.
|
|
"""
|
|
|
|
import json
|
|
|
|
from B06_Section.B06_Section_Engine_Culvert import (
|
|
FORD_PAVEMENT_DEFAULT_WIDTH_M,
|
|
attach_culvert_sets,
|
|
load_culvert_sets,
|
|
)
|
|
from B06_Section.B06_Section_Router_Design import pavement_ranges, paved_at
|
|
|
|
|
|
def _write_pipe_points(root, points):
|
|
edits = root / "B04_PreProcess" / "drainage" / "edits"
|
|
edits.mkdir(parents=True, exist_ok=True)
|
|
(edits / "pipe_points.json").write_text(
|
|
json.dumps({"route_signature": "무관", "points": points}, ensure_ascii=False),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
|
|
def _write_structures(root, structures):
|
|
route_dir = root / "B05_Profile" / "route"
|
|
route_dir.mkdir(parents=True, exist_ok=True)
|
|
(route_dir / "structures.json").write_text(
|
|
json.dumps({"revision": 1, "structures": structures}, ensure_ascii=False),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
|
|
FORD = {
|
|
"chainage_m": 100.0,
|
|
"source": "user",
|
|
"facility": "ford_pavement",
|
|
"options": {"ford_width_m": 6.0, "ford_height_m": 0.4, "ford_slope_pct": 3.0},
|
|
}
|
|
|
|
|
|
# ── 물넘이 스펙 ────────────────────────────────────────────────────────────
|
|
|
|
|
|
def test_ford_pavement_becomes_its_own_spec(tmp_path):
|
|
_write_pipe_points(tmp_path, [FORD])
|
|
(spec,) = load_culvert_sets(tmp_path).values()
|
|
assert spec["type"] == "ford_pavement"
|
|
assert spec["span_m"] == 6.0
|
|
assert spec["depth_m"] == 0.4
|
|
assert spec["slope_pct"] == 3.0
|
|
|
|
|
|
def test_ford_pavement_without_values_keeps_default_width_and_no_depth(tmp_path):
|
|
"""깊이는 지어내지 않는다 — 없으면 None이라 화면이 파임을 그리지 않는다."""
|
|
_write_pipe_points(tmp_path, [{"chainage_m": 50.0, "facility": "ford_pavement"}])
|
|
(spec,) = load_culvert_sets(tmp_path).values()
|
|
assert spec["span_m"] == FORD_PAVEMENT_DEFAULT_WIDTH_M
|
|
assert spec["depth_m"] is None
|
|
assert spec["slope_pct"] is None
|
|
|
|
|
|
def test_ford_pavement_attaches_to_every_section_in_span(tmp_path):
|
|
"""월류 폭 6m → 기준측점 ±3m 안의 측점 전부에 붙는다(다른 시설은 소유 측점 한 곳)."""
|
|
_write_pipe_points(tmp_path, [FORD])
|
|
sections = [{"chainage_m": value} for value in (94.0, 97.5, 100.0, 102.5, 106.0)]
|
|
attach_culvert_sets(tmp_path, sections)
|
|
attached = [s["chainage_m"] for s in sections if "ford_pavement" in s]
|
|
assert attached == [97.5, 100.0, 102.5]
|
|
assert all("culvert" not in s and "ford" not in s for s in sections)
|
|
|
|
|
|
# ── 포장 구간 ──────────────────────────────────────────────────────────────
|
|
|
|
|
|
def test_pavement_range_comes_from_group_g_structure(tmp_path):
|
|
_write_structures(
|
|
tmp_path,
|
|
[
|
|
{
|
|
"type_id": "pavement_concrete",
|
|
"chainage_m": 200.0,
|
|
"start_m": 180.0,
|
|
"end_m": 230.0,
|
|
"placement": "interval",
|
|
}
|
|
],
|
|
)
|
|
assert pavement_ranges(tmp_path) == [(180.0, 230.0)]
|
|
|
|
|
|
def test_ford_pavement_range_is_always_paved(tmp_path):
|
|
_write_pipe_points(tmp_path, [FORD])
|
|
ranges = pavement_ranges(tmp_path)
|
|
assert ranges == [(97.0, 103.0)]
|
|
assert paved_at(100.0, ranges) is True
|
|
assert paved_at(97.0, ranges) is True
|
|
assert paved_at(96.9, ranges) is False
|
|
|
|
|
|
def test_other_groups_are_not_pavement(tmp_path):
|
|
_write_structures(
|
|
tmp_path,
|
|
[
|
|
{
|
|
"type_id": "revetment",
|
|
"chainage_m": 100.0,
|
|
"start_m": 100.0,
|
|
"end_m": 115.0,
|
|
"placement": "interval",
|
|
}
|
|
],
|
|
)
|
|
assert pavement_ranges(tmp_path) == []
|
|
|
|
|
|
def test_missing_truth_files_are_not_an_error(tmp_path):
|
|
assert pavement_ranges(tmp_path) == []
|
|
|
|
|
|
# ── 포장 판정 규칙 ─────────────────────────────────────────────────────────
|
|
|
|
|
|
def test_outside_range_keeps_user_value_and_defaults_unpaved():
|
|
"""경사 제안은 더 이상 포장을 켜지 않는다 — 구간 밖은 사용자 값(없으면 비포장)."""
|
|
ranges = [(10.0, 20.0)]
|
|
assert paved_at(30.0, ranges) is False
|
|
assert paved_at(30.0, ranges, True) is True
|
|
assert paved_at(30.0, ranges, False) is False
|
|
|
|
|
|
def test_inside_range_overrides_user_off_switch():
|
|
"""구간 안은 강제 포장 — 물넘이는 콘크리트 노면이고 지정 구간은 자동 반영이다."""
|
|
assert paved_at(15.0, [(10.0, 20.0)], False) is True
|
|
|
|
|
|
# ── 독립 기슭막이 ─────────────────────────────────────────────────────────
|
|
#
|
|
# 2026-08-28 이관으로 기슭막이는 구조물 정본 D군을 떠나 관 지점 시설이 됐다. 전용 엔진
|
|
# (`B06_Section_Engine_Revetment`)은 호출자가 사라져 2026-09-01 삭제했다 — 벽은 관 세트
|
|
# (`B06_Section_Engine_Culvert._revet_set`)가 만든다. 그쪽 검증은
|
|
# `test_revet_span_source.py`에 있다.
|