diff --git a/B04_PreProcess/B04_PreProcess_Router_Basins.py b/B04_PreProcess/B04_PreProcess_Router_Basins.py index 20429f91a..bbbb4973c 100644 --- a/B04_PreProcess/B04_PreProcess_Router_Basins.py +++ b/B04_PreProcess/B04_PreProcess_Router_Basins.py @@ -91,7 +91,7 @@ def _build( return detail, carried -# 관 보호공 · 유입 집수정 높이 칸 — 새 관 기본 = 관경 + 0.5(139-1) +# 관 보호공 · 유입 집수정 높이 칸 — 새 관 기본 = 관경 + 0.5(139-1) · 유입 흙막이만 + 0.7(211-1) PIPE_HEIGHT_KEYS = ("inlet_revet_height_m", "outlet_revet_height_m", "inlet_basin_height_m") @@ -130,7 +130,10 @@ def _apply_new_defaults( if diameter_m > 0: for key in PIPE_HEIGHT_KEYS: if key in keys: - options.setdefault(key, pipe_wall_min_height_m(diameter_m)) + side = ( + "inlet" if key == "inlet_revet_height_m" else "" + ) # 유입 흙막이 + 0.7(211-1) + options.setdefault(key, pipe_wall_min_height_m(diameter_m, side)) if point.facility == "ford_bridge" and "ford_height_m" not in options: flow = (flows or {}).get(round(point.chainage_m, 2)) height = ford_height_m(flow, float(options.get("ford_width_m") or 0)) diff --git a/B06_Section/B06_Section_Engine_Culvert.py b/B06_Section/B06_Section_Engine_Culvert.py index 24ce66c42..4f50a3c08 100644 --- a/B06_Section/B06_Section_Engine_Culvert.py +++ b/B06_Section/B06_Section_Engine_Culvert.py @@ -78,19 +78,23 @@ REVET_FACE_SLOPE = 0.3 # `test_b06_pipe_wall_height_mirror`. REVET_HEIGHT_STEP_M = 0.1 # 높이 눈금 REVET_EMBED_DEPTH_M = 0.5 # 근입 깊이 +# 관 보호공 **유입** 흙막이 빈 칸 기본 = 관경(올림) + 0.7 — 유출 · 집수정은 근입 0.5 그대로(211-1). +# ⚠ TS 짝 `common_util_culvert_sets.INLET_REVET_ADD_M` · `_Cross_Culvert_Const.INLET_REVET_ADD_M`. +INLET_REVET_ADD_M = 0.7 # 옛 정의 기본 높이(10-6 표 · 관경 무관) — 관경 > 1.0 관에 이 값이면 관 윗면보다 낮은 벽이라 # 읽을 때 관경 기준 값으로 봄(139-1). OLD_PIPE_WALL_DEFAULT_M = 1.5 -def pipe_wall_min_height_m(diameter_m: float) -> float: +def pipe_wall_min_height_m(diameter_m: float, side: str = "") -> float: """관 벽 **순수 높이**(근입 포함) 기본값 = 관경(0.1m 로 올림) + 근입 0.5 — 벽 윗면 = 관 윗면(139-1). - 관 · 유입 집수정 높이 칸의 새 기본값이자 빈 칸 값. + 관 · 유입 집수정 높이 칸의 새 기본값이자 빈 칸 값. `side="inlet"`(유입 흙막이) = + 0.7(211-1). """ raw = float(diameter_m) steps = math.ceil(raw / REVET_HEIGHT_STEP_M - 1e-9) - return round(steps * REVET_HEIGHT_STEP_M + REVET_EMBED_DEPTH_M, 3) + add = INLET_REVET_ADD_M if side == "inlet" else REVET_EMBED_DEPTH_M + return round(steps * REVET_HEIGHT_STEP_M + add, 3) def pipe_wall_height_m(stored: float | None, diameter_m: float) -> float | None: @@ -211,10 +215,11 @@ def _side_spec( spec["basin_form"] = str(options.get("inlet_structure") or "") or None return spec - # 높이를 안 적었으면 **관경 기준 높이**(관경 + 0.5 · 139-1) · 옛 1.5 도 관경 > 1.0 이면 그 값. + # 높이를 안 적었으면 **관경 기준 높이**(관경 + 0.5 · 유입 + 0.7 · 139-1 · 211-1) · + # 옛 1.5 도 관경 > 1.0 이면 관경 + 0.5(저장 값 해석 · 안 바뀜). height = pipe_wall_height_m(_number(options.get(f"{side}_revet_height_m"), None), diameter_m) if height is None: - height = pipe_wall_min_height_m(diameter_m) + height = pipe_wall_min_height_m(diameter_m, side) length = _number( options.get(f"{side}_revet_length_m"), None, diff --git a/B06_Section/B06_Section_UI_Cross_Culvert_Const.ts b/B06_Section/B06_Section_UI_Cross_Culvert_Const.ts index 673bfeaaf..a6ac1bf2b 100644 --- a/B06_Section/B06_Section_UI_Cross_Culvert_Const.ts +++ b/B06_Section/B06_Section_UI_Cross_Culvert_Const.ts @@ -71,6 +71,10 @@ export function pipeWallThicknessM(kind: string | null, diameterM: number): numb */ export const REVET_EMBED_DEPTH_M = 0.5; +/** 관 보호공 **유입** 흙막이 빈 칸 기본 순수 높이 = 관경(올림) + 0.7(211-1) — 유출은 근입 0.5 그대로. + * ⚠ 파이썬 짝 `INLET_REVET_ADD_M` · 세트 짝 `common_util_culvert_sets.INLET_REVET_ADD_M`(거울 시험). */ +export const INLET_REVET_ADD_M = 0.7; + /* 기슭막이 높이는 형태와 무관하게 **사용자 입력 그대로** — 형태별 높이 한계·원지반 맞춤 * 자동 자르기 없음. 형태가 바꾸는 것은 안 무늬뿐. 높이 기준은 **순수 높이**(벽 바닥~상단, * 근입 0.5m 포함). 단 하나 — 벽 윗면은 노면 위로 올라오지 않음(`buildRevetWallGeometry`). */ @@ -138,7 +142,7 @@ export function revetWallSpec( // 관경 기준 높이는 기본값만 — 하한은 그림이 서는 값뿐(54-51 · 사용자가 고름). const preferred = hiddenPipe ? (spec.revet_height_m ?? autoHeightM ?? REVET_EMBED_DEPTH_M) - : (spec.revet_height_m ?? pipeWallMinPureHeight(diameterM)); // 칸 값 → 그림(139-1) + : (spec.revet_height_m ?? pipeWallMinPureHeight(diameterM, spec.role)); // 칸 값 → 그림(139-1) const pureHeight = Math.max(adjust.h ?? preferred, WALL_MIN_PURE_HEIGHT_M); return { form, @@ -155,10 +159,12 @@ export const PIPE_CONNECT_GRADE = Math.tan((3 * Math.PI) / 180); /** * 배관 기슭막이 관 하부 여유(m) — 관 invert 아래 벽 바닥까지의 깊이 = 근입 0.5. * **순수 높이 기본 = 관경 + 0.5**(땅속 근입 0.5 포함 벽 전체 높이 · 벽 윗면 = 관 윗면 · - * 10-6 표 Ø1000 = 1.5 · 139-1). 칸을 비우면 이 값. + * 10-6 표 Ø1000 = 1.5 · 139-1 · 유입 흙막이 = 관경 + 0.7 · 211-1). 칸을 비우면 이 값. */ -export function pipeWallMinPureHeight(diameterM: number): number { - return revetTargetHeight(diameterM) + REVET_EMBED_DEPTH_M; +export function pipeWallMinPureHeight(diameterM: number, side = ""): number { + return ( + revetTargetHeight(diameterM) + (side === "inlet" ? INLET_REVET_ADD_M : REVET_EMBED_DEPTH_M) + ); } /** 벽 순수 높이 하한(m) — 0 이하는 그림이 안 섬. 관경 · 근입 하한 없음(54-51). */ export const WALL_MIN_PURE_HEIGHT_M = 0.1; diff --git a/B08_Quantity/B08_Quantity_Router_Summary.py b/B08_Quantity/B08_Quantity_Router_Summary.py index 3ac853296..104bcc4ac 100644 --- a/B08_Quantity/B08_Quantity_Router_Summary.py +++ b/B08_Quantity/B08_Quantity_Router_Summary.py @@ -187,11 +187,13 @@ def _revets( sides: list[tuple[str, str | None]], empty_height: float | None, names: dict[str, str], + inlet_empty_height: float | None = None, ) -> None: """흙막이 — 유입 · 유출(흙막이일 때 · `sides`) + 흙막이 n(container_rows 가 선 단까지만 냄). 저장 키 = 필수는 `{쪽}_revet_{칸}_m` · 선택 흙막이는 `soil_add_{n}_{칸}`(명세 인스턴스 키) · - 빈 높이 = `empty_height`(None 이면 「미입력」 열) · 머리 글은 관 정의 이름(공용 열). + 빈 높이 = `empty_height`(None 이면 「미입력」 열 · 관 유입 쪽만 `inlet_empty_height` · 211-1) · + 머리 글은 관 정의 이름(공용 열). """ sources = [(f"{side}_revet_", "_m", side, combo) for side, combo in sides] sources += [ @@ -203,7 +205,9 @@ def _revets( form = str(options.get(f"{head}form") or "") height = _num(options.get(f"{head}height{unit}")) if height is None: - height = empty_height + height = ( + inlet_empty_height if container == "inlet" and inlet_empty_height else empty_height + ) height_text = fill._label("height_m", height) sheet.add( at, @@ -588,7 +592,8 @@ def pipe_summary( ) wall_options = {**options, **moved} wall = pipe_wall_min_height_m(diameter / 1000) if diameter else None - _revets(sheet, at, wall_options, links, sources, wall, names) + wall_in = pipe_wall_min_height_m(diameter / 1000, "inlet") if diameter else None + _revets(sheet, at, wall_options, links, sources, wall, names, wall_in) _chutes(sheet, at, options, links, names) out = sheet.result() diff --git a/common_util/common_util_culvert_sets.ts b/common_util/common_util_culvert_sets.ts index be2df5118..e1a92706a 100644 --- a/common_util/common_util_culvert_sets.ts +++ b/common_util/common_util_culvert_sets.ts @@ -51,12 +51,15 @@ export const BOX_COVER_M = 0.5; export const REVET_HEIGHT_STEP_M = 0.1; export const REVET_EMBED_DEPTH_M = 0.5; export const OLD_PIPE_WALL_DEFAULT_M = 1.5; +/** 관 보호공 유입 흙막이 빈 칸 기본 = 관경(올림) + 0.7(211-1) — 유출 · 집수정은 근입 0.5 그대로. */ +export const INLET_REVET_ADD_M = 0.7; -/** 관 벽 순수 높이(근입 포함) 기본값 — 파이썬 `pipe_wall_min_height_m`. */ -export function pipeWallMinHeightM(diameterM: number): number { +/** 관 벽 순수 높이(근입 포함) 기본값 — 파이썬 `pipe_wall_min_height_m`. `side="inlet"` = + 0.7. */ +export function pipeWallMinHeightM(diameterM: number, side = ""): number { const raw = diameterM; const steps = Math.ceil(raw / REVET_HEIGHT_STEP_M - 1e-9); - return round(steps * REVET_HEIGHT_STEP_M + REVET_EMBED_DEPTH_M, 3); + const add = side === "inlet" ? INLET_REVET_ADD_M : REVET_EMBED_DEPTH_M; + return round(steps * REVET_HEIGHT_STEP_M + add, 3); } /** 관 벽 · 유입 집수정 높이 칸 읽기 — 옛 기본 1.5 이고 관경 > 1.0 이면 관경 기준 값(139-1) · @@ -71,7 +74,8 @@ export function pipeWallHeightM(stored: number | null, diameterM: number): numbe return stored; } -/** 관 보호공 · 유입 집수정 높이 칸 — 새 관 기본 = 관경 + 0.5(139-1 · 서버 짝 `PIPE_HEIGHT_KEYS`). */ +/** 관 보호공 · 유입 집수정 높이 칸 — 새 관 기본 = 관경 + 0.5(139-1) · 유입 흙막이만 + 0.7(211-1) + * (서버 짝 `PIPE_HEIGHT_KEYS`). */ export const PIPE_HEIGHT_KEYS = [ "inlet_revet_height_m", "outlet_revet_height_m", @@ -86,12 +90,14 @@ export function fillPipeHeights( const diameterM = Number(values.pipe_diameter_mm) / 1000; if (!(diameterM > 0)) return; for (const key of PIPE_HEIGHT_KEYS) - if (keys.includes(key)) values[key] ??= pipeWallMinHeightM(diameterM); + if (keys.includes(key)) + values[key] ??= pipeWallMinHeightM(diameterM, key === "inlet_revet_height_m" ? "inlet" : ""); } -/** 배관 관 보호공 높이 칸(유입 · 유출 · 유입 집수정 = 항목 `height`) — 관경 + 0.5 규칙(139-1). +/** 배관 관 보호공 높이 칸(유입 · 유출 · 유입 집수정 = 항목 `height`) — 관경 + 0.5 규칙(139-1) · + * 유입 흙막이 + 0.7(211-1). * `previousMm` 없음 = 읽을 때(옛 1.5 · 관경 > 1.0 옮김) · 있음 = 관경을 바꿈(옛 관경 규칙 값 · 옛 1.5 - * 칸만 새 규칙 값으로 · 손 값 · 빈 칸은 그대로). */ + * 칸만 새 규칙 값으로 · 손 값 · 빈 칸은 그대로 · 유입은 옛 + 0.5 값도 새 + 0.7 로 따라감). */ export function followPipeHeights( state: Record> | undefined>, diameterMm: string, @@ -99,7 +105,13 @@ export function followPipeHeights( ): void { const d = Number(diameterMm) / 1000; if (!(d > 0)) return; - const prev = previousMm ? pipeWallMinHeightM(Number(previousMm) / 1000) : null; + const prevOf = (id: string): Array => { + if (!previousMm) return []; + const p = Number(previousMm) / 1000; + return id === "inlet" + ? [pipeWallMinHeightM(p, id), pipeWallMinHeightM(p)] + : [pipeWallMinHeightM(p)]; + }; for (const id of ["inlet", "outlet"]) { const values = state[id]?.[0]; const raw = values?.height ?? ""; @@ -108,8 +120,10 @@ export function followPipeHeights( const moved = previousMm === undefined ? pipeWallHeightM(now, d) - : [prev, OLD_PIPE_WALL_DEFAULT_M].some((v) => v !== null && Math.abs(now - v) < 1e-9) - ? pipeWallMinHeightM(d) + : [...prevOf(id), OLD_PIPE_WALL_DEFAULT_M].some( + (v) => v !== null && Math.abs(now - v) < 1e-9, + ) + ? pipeWallMinHeightM(d, id) : now; if (moved !== null && moved !== now) values.height = String(moved); } @@ -170,7 +184,7 @@ function sideSpec( const height = pipeWallHeightM(num(options[`${side}_revet_height_m`], null), diameterM) ?? - pipeWallMinHeightM(diameterM); + pipeWallMinHeightM(diameterM, side); const length = num(options[`${side}_revet_length_m`], null); const form = text(options[`${side}_revet_form`]); const before = num(options[`${side}_revet_before_m`], null); diff --git a/resources/tester/test_139_1_pipe_height_follow.py b/resources/tester/test_139_1_pipe_height_follow.py index 978ac0f69..ddc9e2c0f 100644 --- a/resources/tester/test_139_1_pipe_height_follow.py +++ b/resources/tester/test_139_1_pipe_height_follow.py @@ -59,13 +59,16 @@ def test_화면_높이_규칙(tmp_path: Path) -> None: assert run.returncode == 0, run.stderr[-2000:] got = json.loads(result.read_text(encoding="utf-8")) - for key in ("inlet_revet_height_m", "outlet_revet_height_m", "inlet_basin_height_m"): + for key in ("outlet_revet_height_m", "inlet_basin_height_m"): assert got["fresh"][key] == pytest.approx(1.7), key # Φ1200 → 1.7 + assert got["fresh"]["inlet_revet_height_m"] == pytest.approx(1.9) # 유입 흙막이 + 0.7(211-1) assert "inlet_revet_height_m" not in got["noPipe"] # 관경 없음 = 안 채움 assert got["read"]["inlet"][0]["height"] == "1.7" # 옛 1.5 · Φ1200 → 1.7 assert got["read"]["outlet"][0]["height"] == "1.6" # 손 값 그대로 assert got["small"]["inlet"][0]["height"] == "1.5" # Φ800 의 1.5 는 그대로 - assert got["change"]["inlet"][0]["height"] == "2" # 옛 규칙 값(1.7) → 새 규칙(2.0) + assert ( + got["change"]["inlet"][0]["height"] == "2.2" + ) # 옛 + 0.5 값(1.7) → 유입 새 규칙(1.5 + 0.7) assert got["change"]["outlet"][0]["height"] == "2.2" # 손 값 그대로 - assert got["old"]["inlet"][0]["height"] == "1.3" # 옛 1.5 → Φ800 규칙 + assert got["old"]["inlet"][0]["height"] == "1.5" # 옛 1.5 → Φ800 유입 규칙(0.8 + 0.7) assert got["old"]["outlet"][0]["height"] == "" # 빈 칸 그대로 diff --git a/resources/tester/test_211_1_inlet_revet_height.py b/resources/tester/test_211_1_inlet_revet_height.py new file mode 100644 index 000000000..50fb28551 --- /dev/null +++ b/resources/tester/test_211_1_inlet_revet_height.py @@ -0,0 +1,219 @@ +"""211-1 관 보호공 **유입** 흙막이 빈 칸 기본 높이 = 관경(0.1 올림) + 0.7 — 유출 · 집수정은 + 0.5 그대로. + +못박는 것 + - 파이썬 `pipe_wall_min_height_m(d, "inlet")` · 새 관 기본값(`_apply_new_defaults`) · 관 세트 스펙 · B08 집계 빈 높이. + - TS 짝 `buildCulvertSets` ↔ 파이썬 `_culvert_set`(유입 · 유출 스펙 같은 값) · 횡단도 `revetWallSpec`. + - 저장된 높이는 그대로 · 유출 · 집수정 무변 · 흙막이 아닌 시설(숨은 관)은 본체 높이 그대로. +""" + +from __future__ import annotations + +import json +import subprocess +from pathlib import Path + +import pytest + +from B04_PreProcess import B04_PreProcess_Router_Basins as basins +from B05_Profile.B05_Profile_Structures_Schema import structure_type_map +from B06_Section import B06_Section_Engine_Culvert as culvert +from B08_Quantity import B08_Quantity_Router_Summary as summary +from common_util.common_util_drainage_pipes import PipePoint +from M02_MasterTemplete import M02_Item_Snapshot as snapshots +from M02_MasterTemplete.StructOptions import M02_SO_Definition as definition + +ROOT = Path(__file__).resolve().parents[2] +TSC = ROOT / "config" / "node_modules" / "typescript" / "bin" / "tsc" + + +def _revet(side: str, height: float | None = None) -> dict: + found = { + f"{side}_type": "기슭막이", + f"{side}_revet_form": "돌쌓기(메)", + f"{side}_revet_before_m": 5, + f"{side}_revet_after_m": 5, + } + if height is not None: + found[f"{side}_revet_height_m"] = height + return found + + +def test_파이썬_기본_높이_유입만_0_7(): + h = culvert.pipe_wall_min_height_m + assert h(0.8, "inlet") == 1.5 and h(0.8, "outlet") == 1.3 + assert h(1.2, "inlet") == 1.9 and h(1.2) == 1.7 # 쪽 안 줌 = 옛 값(집수정) + assert h(1.0, "inlet") == 1.7 and h(1.0, "outlet") == 1.5 + assert h(0.45, "inlet") == 1.2 # 0.5 로 올림 + 0.7 + + +def test_새_관_기본값_유입_흙막이만_0_7(): + fresh = PipePoint(chainage_m=10.0, source="stream", options={"pipe_diameter_mm": 1200}) + basins._apply_new_defaults([fresh]) + assert fresh.options["inlet_revet_height_m"] == pytest.approx(1.9) + assert fresh.options["outlet_revet_height_m"] == pytest.approx(1.7) + assert fresh.options["inlet_basin_height_m"] == pytest.approx(1.7) + # 이미 적은 높이(추천 · 손 값)는 그대로 · 나머지 칸만 채움 + kept = PipePoint( + chainage_m=20.0, + source="stream", + options={"pipe_diameter_mm": 1200, "inlet_revet_height_m": 1.2}, + ) + basins._apply_new_defaults([kept]) + assert kept.options["inlet_revet_height_m"] == 1.2 + assert kept.options["outlet_revet_height_m"] == pytest.approx(1.7) + + +def test_관_세트_빈_높이_유입_0_7_저장값_그대로(): + blank = culvert._culvert_set({"pipe_diameter_mm": 1200, **_revet("inlet"), **_revet("outlet")}) + assert blank["inlet"]["revet_height_m"] == pytest.approx(1.9) + assert blank["outlet"]["revet_height_m"] == pytest.approx(1.7) + stored = culvert._culvert_set( + {"pipe_diameter_mm": 1200, **_revet("inlet", 1.2), **_revet("outlet", 2.4)} + ) + assert stored["inlet"]["revet_height_m"] == 1.2 and stored["outlet"]["revet_height_m"] == 2.4 + # 옛 기본 1.5 · 관경 > 1.0 = 저장 값 해석(139-1) — 관경 + 0.5 그대로 + legacy = culvert._culvert_set({"pipe_diameter_mm": 1200, **_revet("inlet", 1.5)}) + assert legacy["inlet"]["revet_height_m"] == pytest.approx(1.7) + + +_RUNNER = """ +import { readFileSync, writeFileSync } from "node:fs"; +import { buildCulvertSets } from "./common_util_culvert_sets.js"; +const input = JSON.parse(readFileSync(process.argv[2], "utf8")); +const sets = buildCulvertSets(input.points, input.registry); +writeFileSync(process.argv[3], JSON.stringify([...sets.entries()])); +""" + +_WALL_RUNNER = """ +import { writeFileSync } from "node:fs"; +import { revetWallSpec } from "./B06_Section_UI_Cross_Culvert_Const.js"; +const pick = (spec, hidden, auto) => revetWallSpec(spec, {}, hidden, 1.2, auto).pureHeight; +const base = { structure: "기슭막이" }; +writeFileSync(process.argv[2], JSON.stringify({ + inlet: pick({ ...base, role: "inlet" }, false), + outlet: pick({ ...base, role: "outlet" }, false), + stored: pick({ ...base, role: "inlet", revet_height_m: 1.2 }, false), + hidden: pick({ ...base, role: "inlet" }, true, 2.0), +})); +""" + + +def _tsc(source: Path, out: Path) -> None: + subprocess.run( # noqa: S603 — 고정 실행 파일 · 타입 오류가 있어도 JS 는 나옴 + [ + "node", + str(TSC), + str(source), + "--outDir", + str(out), + "--module", + "esnext", + "--target", + "es2022", + "--moduleResolution", + "bundler", + "--skipLibCheck", + "--ignoreConfig", + ], + cwd=str(ROOT), + capture_output=True, + ) + + +@pytest.mark.skipif(not TSC.is_file(), reason="프론트엔드 의존성 미설치") +def test_TS_세트_거울(tmp_path: Path) -> None: + points = [ + { + "chainage_m": 20.0, + "facility": "pipe", + "options": {"pipe_diameter_mm": 1200, **_revet("inlet"), **_revet("outlet")}, + }, + { + "chainage_m": 40.0, + "facility": "pipe", + "options": {"pipe_diameter_mm": "800", **_revet("inlet", 1.2), **_revet("outlet")}, + }, + { + "chainage_m": 60.0, + "facility": "pipe", + "options": {"pipe_diameter_mm": 1200, **_revet("inlet", 1.5)}, # 옛 1.5 + }, + ] + registry = { + type_id: {o.key: o.default for o in entry.options} + for type_id, entry in structure_type_map().items() + if type_id in ("pipe", "ford_bridge", "ford_pavement", "box_culvert") + } + out = tmp_path / "js" + _tsc(ROOT / "common_util" / "common_util_culvert_sets.ts", out) + (out / "runner.mjs").write_text(_RUNNER, encoding="utf-8") + payload, result = tmp_path / "in.json", tmp_path / "out.json" + payload.write_text(json.dumps({"points": points, "registry": registry}), encoding="utf-8") + run = subprocess.run( # noqa: S603 + ["node", str(out / "runner.mjs"), str(payload), str(result)], + capture_output=True, + text=True, + ) + assert run.returncode == 0, run.stderr[-2000:] + got = {at: spec for at, spec in json.loads(result.read_text(encoding="utf-8"))} + for point in points: + want = culvert._culvert_set(point["options"]) + spec = got[point["chainage_m"]] + for side in ("inlet", "outlet"): + assert spec[side].get("revet_height_m") == want[side].get("revet_height_m"), point + assert got[20.0]["inlet"]["revet_height_m"] == pytest.approx(1.9) + assert got[20.0]["outlet"]["revet_height_m"] == pytest.approx(1.7) + assert got[40.0]["inlet"]["revet_height_m"] == 1.2 # 저장 값 그대로 + assert got[40.0]["outlet"]["revet_height_m"] == pytest.approx(1.3) + assert got[60.0]["inlet"]["revet_height_m"] == pytest.approx(1.7) # 옛 1.5 → 관경 + 0.5 + + +@pytest.mark.skipif(not TSC.is_file(), reason="프론트엔드 의존성 미설치") +def test_횡단도_벽_기본_높이(tmp_path: Path) -> None: + out = tmp_path / "js" + _tsc(ROOT / "B06_Section" / "B06_Section_UI_Cross_Culvert_Const.ts", out) + built = next( + out.rglob("B06_Section_UI_Cross_Culvert_Const.js") + ) # 가져온 파일 따라 경로가 깊어짐 + runner = built.parent / "runner.mjs" + runner.write_text(_WALL_RUNNER, encoding="utf-8") + result = tmp_path / "out.json" + run = subprocess.run( # noqa: S603 + ["node", str(runner), str(result)], capture_output=True, text=True + ) + assert run.returncode == 0, run.stderr[-2000:] + got = json.loads(result.read_text(encoding="utf-8")) + assert got["inlet"] == pytest.approx(1.9) # Φ1200 + 0.7 + assert got["outlet"] == pytest.approx(1.7) # 무변 + assert got["stored"] == 1.2 # 칸 값 그대로 + assert got["hidden"] == 2.0 # 숨은 관(세월교 · 박스) = 본체 높이 그대로 + + +def test_B08_집계_빈_높이_유입만_0_7(tmp_path: Path) -> None: + combo = {"layer": "system", "owner": None, "key": "SS000011", "name": "횡단배수 배관"} + root = tmp_path / "proj" + edits = root / "B04_PreProcess/drainage/edits" + edits.mkdir(parents=True) + point = { + "chainage_m": 1433.0, + "source": "user", + "options": {"pipe_diameter_mm": 800, **_revet("inlet"), **_revet("outlet")}, + "summary_item": combo, + "group_items": {"gnbf9m9": combo, "g9lq9uk": combo}, + } + (edits / "pipe_points.json").write_text( + json.dumps({"points": [point]}, ensure_ascii=False), encoding="utf-8" + ) + designs = [ + { + "chainage_m": 1433.0, + "design": {"pipe_length_m": 18, "inlet_basin": None, "extra_walls": []}, + } + ] + names = definition.read()["종류"]["pipe"]["이름"] + linked = {owner: {} for owner in snapshots.assignments(root)} + out = summary.pipe_summary(root, designs, names, 6.0, linked=linked) + ids = [c["id"] for c in out["columns"] if c["id"].startswith("pipe:revet")] + assert ids == ["pipe:revet|돌쌓기(메)|1.3", "pipe:revet|돌쌓기(메)|1.5"], ( + ids + ) # 유출 1.3 · 유입 1.5 diff --git a/resources/tester/test_b05_new_structure_defaults.py b/resources/tester/test_b05_new_structure_defaults.py index b85ce87ae..d6a60d202 100644 --- a/resources/tester/test_b05_new_structure_defaults.py +++ b/resources/tester/test_b05_new_structure_defaults.py @@ -34,8 +34,8 @@ def test_새_관만_기본값_빈_키에만(): fresh = PipePoint(chainage_m=10.0, source="stream", options={"pipe_diameter_mm": 800}) basins._apply_new_defaults([fresh]) # 추천(먼저 들어간 값)이 이김 · 집수정 모양 L 도 실제 값(54-39) - # 관 보호공 · 유입 집수정 높이 = 관경 + 0.5(139-1 · Φ800 → 1.3) - heights = dict.fromkeys(basins.PIPE_HEIGHT_KEYS, 1.3) + # 관 보호공 · 유입 집수정 높이 = 관경 + 0.5(139-1 · Φ800 → 1.3) · 유입 흙막이만 + 0.7(211-1 → 1.5) + heights = {**dict.fromkeys(basins.PIPE_HEIGHT_KEYS, 1.3), "inlet_revet_height_m": 1.5} expected = {**_defaults("pipe"), "pipe_diameter_mm": 800, "inlet_structure": "L", **heights} assert fresh.options == expected assert fresh.options["pipe_kind"] == "파형강관" and fresh.options["inlet_type"] == "집수정" @@ -66,7 +66,8 @@ def test_원본에_있던_관은_빈칸_그대로(monkeypatch): by = {p.chainage_m: p for p in points} assert by[100.0].options is None # 옛 빈칸 그대로 # 새로 보충된 관 = 기본값 + 집수정 모양 L(54-39) - heights = dict.fromkeys(basins.PIPE_HEIGHT_KEYS, 1.5) # 기본 관경 1000 → 1.5(139-1) + # 기본 관경 1000 → 1.5(139-1) · 유입 흙막이 1.7(211-1) + heights = {**dict.fromkeys(basins.PIPE_HEIGHT_KEYS, 1.5), "inlet_revet_height_m": 1.7} assert by[300.0].options == {**_defaults("pipe"), "inlet_structure": "L", **heights} diff --git a/resources/tester/test_b06_pipe_wall_height_mirror.py b/resources/tester/test_b06_pipe_wall_height_mirror.py index 75707760c..ad8b2b0fa 100644 --- a/resources/tester/test_b06_pipe_wall_height_mirror.py +++ b/resources/tester/test_b06_pipe_wall_height_mirror.py @@ -26,7 +26,7 @@ def _ts_const(source: str, name: str) -> float: def test_상수_셋이_TS_와_같다() -> None: - for name in ("REVET_HEIGHT_STEP_M", "REVET_EMBED_DEPTH_M"): + for name in ("REVET_HEIGHT_STEP_M", "REVET_EMBED_DEPTH_M", "INLET_REVET_ADD_M"): assert getattr(culvert, name) == _ts_const(TS, name) == _ts_const(SETS, name), name assert culvert.OLD_PIPE_WALL_DEFAULT_M == _ts_const(SETS, "OLD_PIPE_WALL_DEFAULT_M") @@ -35,9 +35,16 @@ def test_TS_식_모양이_그대로다() -> None: """식을 바꾸면 여기서 잡힘 — 파이썬 식도 함께 고칠 것.""" assert "const raw = diameterM;" in TS and "const raw = diameterM;" in SETS assert "Math.ceil(raw / REVET_HEIGHT_STEP_M - 1e-9) * REVET_HEIGHT_STEP_M" in TS - assert "return revetTargetHeight(diameterM) + REVET_EMBED_DEPTH_M;" in TS - # 그림이 칸 값을 읽음(139-1) — 배관 기본 = 칸 → 관경 기준 - assert "(spec.revet_height_m ?? pipeWallMinPureHeight(diameterM))" in TS + assert ( + 'revetTargetHeight(diameterM) + (side === "inlet" ? INLET_REVET_ADD_M : REVET_EMBED_DEPTH_M)' + in ( + " ".join(TS.split()) # 줄바꿈 · 들여쓰기 무시(포맷터가 접음) + ) + .replace("( ", "(") + .replace(" )", ")") + ) + # 그림이 칸 값을 읽음(139-1) — 배관 기본 = 칸 → 관경 기준(유입 + 0.7 · 211-1) + assert "(spec.revet_height_m ?? pipeWallMinPureHeight(diameterM, spec.role))" in TS # 옛 1.5 옮김 — TS 세트 짝 식 assert "Math.abs(stored - OLD_PIPE_WALL_DEFAULT_M) < 1e-9 &&" in SETS assert "diameterM > 1.0 + 1e-9" in SETS