Merge remote-tracking branch 'origin/sub_laptop_7' into sub_laptop_1
This commit is contained in:
@@ -153,10 +153,10 @@
|
||||
"label": "집수정 높이",
|
||||
"input": "number",
|
||||
"unit": "m",
|
||||
"default": 1.5,
|
||||
"default": null,
|
||||
"required": false,
|
||||
"phase": "detail",
|
||||
"default_basis": "사용자 확정 2026-10-06 배관 기본값 표(docs/raw/사용자요구/2026-10-05_수정요구.md)"
|
||||
"default_basis": "새 관 = 관경 + 0.5 m(땅속 근입 0.5 포함 벽 전체 높이 · 벽 윗면 = 관 윗면 · Ø1000 = 1.5 = 10-6 배관 기본값 표) — 화면 · 서버가 관경으로 채움 · 관경 바꾸면 따라감 · 옛 1.5 이고 관경 > 1.0 이면 읽을 때 관경 + 0.5(139-1)"
|
||||
},
|
||||
{
|
||||
"key": "inlet_revet_form",
|
||||
@@ -205,11 +205,11 @@
|
||||
"label": "유입 흙막이 높이",
|
||||
"input": "number",
|
||||
"unit": "m",
|
||||
"default": 1.5,
|
||||
"default": null,
|
||||
"required": false,
|
||||
"phase": "detail",
|
||||
"empty_means": "비우면 관경 기준 최소 높이(관경 + 여유 0.5 를 0.1m 로 올림 + 근입 0.5)로 그리고 셈 — 횡단도·구조물도·표가 같은 값 · 최소라 수량은 미확정(금액 밖) · 2026-09-14 브레인 판정(옛 기본 2.5 걷음)",
|
||||
"default_basis": "사용자 확정 2026-10-06 배관 기본값 표(docs/raw/사용자요구/2026-10-05_수정요구.md)"
|
||||
"empty_means": "비우면 관경 기준 높이(관경을 0.1m 로 올림 + 근입 0.5 · 벽 윗면 = 관 윗면)로 그리고 셈 — 횡단도·구조물도·표가 같은 값(139-1)",
|
||||
"default_basis": "새 관 = 관경 + 0.5 m(땅속 근입 0.5 포함 벽 전체 높이 · 벽 윗면 = 관 윗면 · Ø1000 = 1.5 = 10-6 배관 기본값 표) — 화면 · 서버가 관경으로 채움 · 관경 바꾸면 따라감 · 옛 1.5 이고 관경 > 1.0 이면 읽을 때 관경 + 0.5(139-1)"
|
||||
},
|
||||
{
|
||||
"key": "outlet_type",
|
||||
@@ -269,11 +269,11 @@
|
||||
"label": "유출 흙막이 높이",
|
||||
"input": "number",
|
||||
"unit": "m",
|
||||
"default": 1.5,
|
||||
"default": null,
|
||||
"required": false,
|
||||
"phase": "detail",
|
||||
"empty_means": "비우면 관경 기준 최소 높이(관경 + 여유 0.5 를 0.1m 로 올림 + 근입 0.5)로 그리고 셈 — 횡단도·구조물도·표가 같은 값 · 최소라 수량은 미확정(금액 밖) · 2026-09-14 브레인 판정(옛 기본 2.5 걷음)",
|
||||
"default_basis": "사용자 확정 2026-10-06 배관 기본값 표(docs/raw/사용자요구/2026-10-05_수정요구.md)"
|
||||
"empty_means": "비우면 관경 기준 높이(관경을 0.1m 로 올림 + 근입 0.5 · 벽 윗면 = 관 윗면)로 그리고 셈 — 횡단도·구조물도·표가 같은 값(139-1)",
|
||||
"default_basis": "새 관 = 관경 + 0.5 m(땅속 근입 0.5 포함 벽 전체 높이 · 벽 윗면 = 관 윗면 · Ø1000 = 1.5 = 10-6 배관 기본값 표) — 화면 · 서버가 관경으로 채움 · 관경 바꾸면 따라감 · 옛 1.5 이고 관경 > 1.0 이면 읽을 때 관경 + 0.5(139-1)"
|
||||
},
|
||||
{
|
||||
"key": "revet_foundation",
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
/* 139-2 연동 풀림 굳힘 헬퍼 — 화면 TS(`_Page_Link_Session.freezeLinkedWalls` · `_Cross_Culvert_Wire`)를
|
||||
* 그 자리에서 트랜스파일해 Node 로 돌림. 입력 = 73-4 픽스처(박스 352.14 흙막이 · 옆 측점 340 · 360).
|
||||
* 짝 = `test_139_2_link_freeze.py`.
|
||||
*/
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
|
||||
|
||||
const ROOT = path.join(__dirname, "..", "..");
|
||||
const cache = new Map();
|
||||
const noop = new Proxy({}, { get: () => () => "" });
|
||||
// 조정창 모듈이 불러올 때 문서 이벤트를 검 — 기하엔 안 쓰임
|
||||
globalThis.document = { addEventListener: () => undefined };
|
||||
globalThis.window = globalThis;
|
||||
|
||||
function loadTs(file) {
|
||||
const full = path.resolve(file);
|
||||
if (cache.has(full)) return cache.get(full);
|
||||
if (full.endsWith(".json")) {
|
||||
const data = JSON.parse(fs.readFileSync(full, "utf8"));
|
||||
cache.set(full, data);
|
||||
return data;
|
||||
}
|
||||
const js = ts.transpileModule(fs.readFileSync(full, "utf8"), {
|
||||
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
||||
}).outputText;
|
||||
const box = { exports: {} };
|
||||
cache.set(full, box.exports);
|
||||
const localRequire = (request) => {
|
||||
// 화면 전용(토스트 · 글 · 설정) — 기하엔 안 쓰임
|
||||
if (request.startsWith("@ui/") || request.startsWith("@config/")) return noop;
|
||||
if (request.startsWith("@util/"))
|
||||
return loadTs(resolve(path.join(ROOT, "common_util", request.slice(6))));
|
||||
return loadTs(resolve(path.join(path.dirname(full), request)));
|
||||
};
|
||||
new Function("exports", "module", "require", js)(box.exports, box, localRequire);
|
||||
cache.set(full, box.exports);
|
||||
return box.exports;
|
||||
}
|
||||
const resolve = (base) =>
|
||||
[base, `${base}.ts`].find((p) => fs.existsSync(p) && fs.statSync(p).isFile()) ?? `${base}.ts`;
|
||||
|
||||
const B06 = (f) => path.join(ROOT, "B06_Section", f);
|
||||
const { freezeLinkedWalls } = loadTs(B06("B06_Section_UI_Page_Link_Session.ts"));
|
||||
const { computeCardCulvert, culvertLinkFor } = loadTs(B06("B06_Section_UI_Cross_Culvert_Wire.ts"));
|
||||
|
||||
const sections = JSON.parse(
|
||||
fs.readFileSync(path.join(__dirname, "fixtures", "b06_73_4_body_guard_sections.json"), "utf8"),
|
||||
).cross_sections;
|
||||
const at = (c) => sections.find((s) => Math.abs(s.chainage_m - c) < 0.6);
|
||||
|
||||
// 제어기 흉내 — 측점:역할 → 4축(세션 맵과 같은 꼴)
|
||||
const stored = new Map();
|
||||
const keyOf = (c, role) => `${c.toFixed(2)}:${role}`;
|
||||
const ZERO = { x: 0, d: null, h: null, m: null };
|
||||
const revetOffset = {
|
||||
adjustFor: (s, role) => ({ ...ZERO, ...(stored.get(keyOf(s.chainage_m, role)) ?? {}) }),
|
||||
storedAdjustFor: (s, role) => stored.get(keyOf(s.chainage_m, role)) ?? null,
|
||||
update: (c, role, patch) =>
|
||||
stored.set(keyOf(c, role), { ...ZERO, ...(stored.get(keyOf(c, role)) ?? {}), ...patch }),
|
||||
reset: (c, role) => stored.delete(keyOf(c, role)),
|
||||
syncApplied: () => undefined,
|
||||
selectedFor: () => null,
|
||||
highlightFor: () => null,
|
||||
select: () => undefined,
|
||||
};
|
||||
const extraWalls = { countFor: () => 0, consumeEqualize: () => false };
|
||||
let linked = true;
|
||||
const revetLink = { linkedFor: () => linked, followGradeFor: () => true };
|
||||
const controls = { revetOffset, extraWalls, revetLink };
|
||||
|
||||
const round = (v) => Math.round(v * 1000) / 1000;
|
||||
const shape = (s) => {
|
||||
const link = culvertLinkFor(s, sections, () => null);
|
||||
const layout = computeCardCulvert(
|
||||
s,
|
||||
s.samples,
|
||||
null,
|
||||
revetOffset,
|
||||
undefined,
|
||||
extraWalls,
|
||||
link,
|
||||
revetLink,
|
||||
);
|
||||
return (layout?.walls ?? []).map((w) => ({
|
||||
role: w.role,
|
||||
points: w.points.map((p) => [round(p.offset), round(p.elevation)]),
|
||||
}));
|
||||
};
|
||||
|
||||
const owner = at(352.14);
|
||||
const card = at(360);
|
||||
// 소유 측점 벽을 조금 옮겨 둠 — 옆 카드가 물려받는 자리가 자동 자리가 아니게
|
||||
revetOffset.update(owner.chainage_m, "outlet", { x: 0.7, h: 2.3 });
|
||||
const linkedShape = shape(card);
|
||||
freezeLinkedWalls(card, sections, controls);
|
||||
const frozen = Object.fromEntries(
|
||||
[...stored].filter(([k]) => k.startsWith("360.00:")).map(([k, v]) => [k.slice(7), v]),
|
||||
);
|
||||
linked = false;
|
||||
const detachedShape = shape(card);
|
||||
// 푼 뒤 소유 측점을 또 옮김 — 푼 카드는 안 따라감
|
||||
revetOffset.update(owner.chainage_m, "outlet", { x: 2.0 });
|
||||
const afterOwnerMove = shape(card);
|
||||
linked = true; // 대조 — 연동이면 소유 측점 이동을 따라감(시험이 실제로 재는지)
|
||||
const linkedAfterMove = shape(card);
|
||||
process.stdout.write(
|
||||
JSON.stringify({ linkedShape, frozen, detachedShape, afterOwnerMove, linkedAfterMove }),
|
||||
);
|
||||
@@ -0,0 +1,71 @@
|
||||
"""139-1 관 보호공 높이 = 관경 + 0.5 — 화면 쪽(새 관 기본값 · 폼 읽기 옮김 · 관경 따라감).
|
||||
|
||||
- B05 `optionDefaults` → `fillPipeHeights`: 새 관 높이 칸 셋(유입 · 유출 흙막이 · 유입 집수정) = 관경 + 0.5.
|
||||
- 폼 `followPipeHeights`(둘 다 `common_util_culvert_sets.ts`): 읽을 때 옛 1.5 · 관경 > 1.0 → 관경 + 0.5 · 관경을 바꾸면 옛 규칙 값 ·
|
||||
옛 1.5 칸만 따라감(손 값 · 빈 칸 그대로).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
TSC = ROOT / "config" / "node_modules" / "typescript" / "bin" / "tsc"
|
||||
|
||||
_RUNNER = """
|
||||
import { writeFileSync } from "node:fs";
|
||||
import { fillPipeHeights, followPipeHeights } from "./common_util_culvert_sets.js";
|
||||
const keys = ["pipe_diameter_mm", "inlet_revet_height_m", "outlet_revet_height_m", "inlet_basin_height_m"];
|
||||
const fresh = { pipe_diameter_mm: 1200 }; fillPipeHeights(fresh, keys);
|
||||
const noPipe = {}; fillPipeHeights(noPipe, keys);
|
||||
const st = (d, i, o) => ({ pipe: [{ diameter: d }], inlet: [{ height: i }], outlet: [{ height: o }] });
|
||||
const read = st("1200", "1.5", "1.6"); followPipeHeights(read, "1200");
|
||||
const small = st("800", "1.5", ""); followPipeHeights(small, "800");
|
||||
const change = st("1200", "1.7", "2.2"); followPipeHeights(change, "1500", "1200");
|
||||
const old = st("1000", "1.5", ""); followPipeHeights(old, "800", "1000");
|
||||
writeFileSync(process.argv[2], JSON.stringify({ fresh, noPipe, read, small, change, old }));
|
||||
"""
|
||||
|
||||
|
||||
@pytest.mark.skipif(not TSC.is_file(), reason="프론트엔드 의존성 미설치")
|
||||
def test_화면_높이_규칙(tmp_path: Path) -> None:
|
||||
out = tmp_path / "js"
|
||||
subprocess.run( # noqa: S603 — 고정 실행 파일
|
||||
[
|
||||
"node",
|
||||
str(TSC),
|
||||
str(ROOT / "common_util" / "common_util_culvert_sets.ts"),
|
||||
"--outDir",
|
||||
str(out),
|
||||
"--module",
|
||||
"esnext",
|
||||
"--target",
|
||||
"es2022",
|
||||
"--skipLibCheck",
|
||||
"--ignoreConfig",
|
||||
],
|
||||
cwd=str(ROOT),
|
||||
capture_output=True,
|
||||
)
|
||||
(out / "runner.mjs").write_text(_RUNNER, encoding="utf-8")
|
||||
result = tmp_path / "out.json"
|
||||
run = subprocess.run( # noqa: S603
|
||||
["node", str(out / "runner.mjs"), str(result)], capture_output=True, text=True
|
||||
)
|
||||
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"):
|
||||
assert got["fresh"][key] == pytest.approx(1.7), key # Φ1200 → 1.7
|
||||
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"]["outlet"][0]["height"] == "2.2" # 손 값 그대로
|
||||
assert got["old"]["inlet"][0]["height"] == "1.3" # 옛 1.5 → Φ800 규칙
|
||||
assert got["old"]["outlet"][0]["height"] == "" # 빈 칸 그대로
|
||||
@@ -0,0 +1,55 @@
|
||||
"""139-2 관보호공 연동 풀림 — 옆 측점 벽 0.1 m 걸음 · 푸는 순간 4축 굳힘 · 다시 연동 = 지움.
|
||||
|
||||
화면 TS 를 그 자리에서 돌린 값(`helper_139_2_link_freeze.cjs` · 73-4 픽스처 박스 352.14 흙막이 · 옆 측점 360).
|
||||
"""
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
HELPER = Path(__file__).parent / "helper_139_2_link_freeze.cjs"
|
||||
NODE_MODULES = ROOT / "config" / "node_modules"
|
||||
|
||||
pytestmark = pytest.mark.skipif(not NODE_MODULES.is_dir(), reason="프론트엔드 의존성 미설치")
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def out() -> dict:
|
||||
done = subprocess.run( # noqa: S603 — 고정 실행 파일
|
||||
["node", str(HELPER)], cwd=str(ROOT), capture_output=True, text=True, encoding="utf-8"
|
||||
)
|
||||
assert done.returncode == 0, done.stderr
|
||||
return json.loads(done.stdout)
|
||||
|
||||
|
||||
def test_푸는_순간_4축이_차고_그림은_그대로(out: dict) -> None:
|
||||
assert set(out["frozen"]) == {"inlet", "outlet"}
|
||||
for adjust in out["frozen"].values():
|
||||
assert isinstance(adjust["x"], (int, float))
|
||||
assert isinstance(adjust["d"], (int, float)) # d 가 차서 자동 자리 · 정수 맞춤 안 탐
|
||||
assert adjust["h"] > 0 and adjust["m"]
|
||||
assert out["frozen"]["outlet"]["h"] == pytest.approx(2.3) # 소유 측점 높이 그대로
|
||||
assert out["detachedShape"] == out["linkedShape"] # 튐 없음
|
||||
|
||||
|
||||
def test_푼_뒤엔_소유_측점_이동을_안_따라감(out: dict) -> None:
|
||||
assert out["afterOwnerMove"] == out["detachedShape"]
|
||||
assert out["linkedAfterMove"] != out["linkedShape"] # 대조 — 연동이면 따라감
|
||||
|
||||
|
||||
def test_한_길_걸음_다시_연동_지움() -> None:
|
||||
stc = (ROOT / "B06_Section" / "B06_Section_UI_Page_Station_Controls.ts").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
vs = (ROOT / "B06_Section" / "B06_Section_UI_Cross_View_Structure.ts").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
# 카드 [연동] · 좌측 [연동] 모두 제어기 setLinked 로 — 풀면 굳힘 · 걸면 지움
|
||||
assert "if (linked) dropLinkedAdjusts(chainageM);" in stc
|
||||
assert "freezeLinkedWalls(section" in stc
|
||||
assert "setLinked: (linked) => ctx.revetLink?.setLinked(ctx.section.chainage_m, linked)" in vs
|
||||
# 풀린 옆 측점 카드 = 0.1 m 걸음
|
||||
assert "(ctx.isLinked && ctx.revetLink?.linkedFor(ctx.section) === false)" in vs
|
||||
@@ -141,10 +141,10 @@ def test_흙막이_형태_높이마다_길이(root):
|
||||
# 집수정 유입은 흙막이로 안 셈 · 유출만
|
||||
assert _cell(out, 1567, "pipe:revet|돌쌓기(찰)|2.0") is None
|
||||
assert _cell(out, 1567, "pipe:revet|돌쌓기(메)|2.0") == 10
|
||||
# 빈 높이 = 횡단도 관경 최소(Φ800 → 1.8) · 정의 기본 1.5 아님 · 빈 형태 = 미입력
|
||||
assert _cell(out, 1790, "pipe:revet|-|1.8") == 10
|
||||
head = next(c for c in out["columns"] if c["id"] == "pipe:revet|-|1.8")
|
||||
assert head["group"][1:] == ["미입력", "H=1.8"]
|
||||
# 빈 높이 = 횡단도 관경 기준(Φ800 → 1.3 · 139-1) · 빈 형태 = 미입력
|
||||
assert _cell(out, 1790, "pipe:revet|-|1.3") == 10
|
||||
head = next(c for c in out["columns"] if c["id"] == "pipe:revet|-|1.3")
|
||||
assert head["group"][1:] == ["미입력", "H=1.3"]
|
||||
|
||||
|
||||
def test_돌붙임은_형식_면적이_다_있을_때만(root):
|
||||
|
||||
@@ -34,7 +34,9 @@ def test_새_관만_기본값_빈_키에만():
|
||||
fresh = PipePoint(chainage_m=10.0, source="stream", options={"pipe_diameter_mm": 800})
|
||||
basins._apply_new_defaults([fresh])
|
||||
# 추천(먼저 들어간 값)이 이김 · 집수정 모양 L 도 실제 값(54-39)
|
||||
expected = {**_defaults("pipe"), "pipe_diameter_mm": 800, "inlet_structure": "L"}
|
||||
# 관 보호공 · 유입 집수정 높이 = 관경 + 0.5(139-1 · Φ800 → 1.3)
|
||||
heights = dict.fromkeys(basins.PIPE_HEIGHT_KEYS, 1.3)
|
||||
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"] == "집수정"
|
||||
box = PipePoint(chainage_m=20.0, source="stream", facility="box_culvert")
|
||||
@@ -64,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)
|
||||
assert by[300.0].options == {**_defaults("pipe"), "inlet_structure": "L"}
|
||||
heights = dict.fromkeys(basins.PIPE_HEIGHT_KEYS, 1.5) # 기본 관경 1000 → 1.5(139-1)
|
||||
assert by[300.0].options == {**_defaults("pipe"), "inlet_structure": "L", **heights}
|
||||
|
||||
|
||||
def test_월류_폭_기본값은_등록부_엔진과_같음():
|
||||
|
||||
@@ -466,11 +466,13 @@ def test_pipe_accessory_options_defined():
|
||||
"outlet_pitching_area_m2",
|
||||
):
|
||||
assert gone not in options, f"pipe.{gone}는 보호공으로 합쳐졌다"
|
||||
# 기본 치수 — 길이 10m · 높이 1.5(10-6 배관 기본값 표 · 54-36 · 근거 default_basis).
|
||||
# 기본 치수 — 길이 10m · 높이 = 관경 + 0.5(정의 고정값 없음 · 화면 · 서버가 관경으로 채움 · 139-1).
|
||||
for side in ("inlet", "outlet"):
|
||||
assert options[f"{side}_revet_length_m"].default == 10
|
||||
assert options[f"{side}_revet_height_m"].default == 1.5
|
||||
assert options[f"{side}_revet_height_m"].default is None
|
||||
assert "관경 + 0.5" in options[f"{side}_revet_height_m"].default_basis
|
||||
assert "관경 기준" in options[f"{side}_revet_height_m"].empty_means
|
||||
assert options["inlet_basin_height_m"].default is None
|
||||
# 유무·길이·관경·유입 구조 = b05, 형태·치수 제원 = detail.
|
||||
for key in ("inlet_type", "inlet_revet_length_m", "outlet_revet_length_m"):
|
||||
assert options[key].phase == "b05", f"pipe.{key}는 b05 phase"
|
||||
|
||||
@@ -43,14 +43,14 @@ def test_empty_options_have_no_members():
|
||||
|
||||
|
||||
def test_saved_values_drive_members():
|
||||
"""고른 값만 부재 — 빈 높이 = 관경 기준 최소(Ø1000 → 2.0) · 빈 형태 = 없음."""
|
||||
"""고른 값만 부재 — 빈 높이 = 관경 기준(Ø1000 → 1.5 · 139-1) · 빈 형태 = 없음."""
|
||||
spec = _culvert_set({"inlet_type": "집수정", "inlet_structure": "L", "outlet_type": "기슭막이"})
|
||||
assert spec["inlet"]["basin_form"] == "L"
|
||||
assert spec["inlet"]["basin_height_m"] is None
|
||||
part = spec["outlet"]
|
||||
assert part["structure"] == "기슭막이"
|
||||
assert part["revet_form"] is None
|
||||
assert part["revet_height_m"] == 2.0
|
||||
assert part["revet_height_m"] == 1.5
|
||||
assert part["face_slope"] == REVET_FACE_SLOPE
|
||||
# 옛 보호공(apron) 칸은 걷음 — 유출 보호는 도수로/돌붙임 띠(90-2)
|
||||
assert "apron_length_m" not in part
|
||||
@@ -63,7 +63,7 @@ def test_basin_inlet_skips_revet_and_apron():
|
||||
assert "revet_height_m" not in spec["inlet"]
|
||||
# 유출측은 그대로 세트를 갖춘다.
|
||||
assert spec["outlet"]["structure"] == "기슭막이"
|
||||
assert spec["outlet"]["revet_height_m"] == 2.0 # 빈 높이 = 관경 기준 최소
|
||||
assert spec["outlet"]["revet_height_m"] == 1.5 # 빈 높이 = 관경 기준(139-1)
|
||||
|
||||
|
||||
def test_string_diameter_and_custom_height():
|
||||
@@ -75,6 +75,29 @@ def test_string_diameter_and_custom_height():
|
||||
assert spec["outlet"]["revet_height_m"] == 1.5
|
||||
|
||||
|
||||
def test_old_default_moves_for_large_pipe():
|
||||
"""옛 기본 1.5 · 관경 > 1.0 = 관경 + 0.5 로 읽음(139-1) · 관경 ≤ 1.0 · 다른 값은 그대로."""
|
||||
big = _culvert_set(
|
||||
{
|
||||
"pipe_diameter_mm": 1200,
|
||||
"inlet_type": "집수정",
|
||||
"inlet_basin_height_m": 1.5,
|
||||
"outlet_type": "기슭막이",
|
||||
"outlet_revet_height_m": 1.5,
|
||||
}
|
||||
)
|
||||
assert big["outlet"]["revet_height_m"] == 1.7
|
||||
assert big["inlet"]["basin_height_m"] == 1.7
|
||||
small = _culvert_set(
|
||||
{"pipe_diameter_mm": 800, "outlet_type": "기슭막이", "outlet_revet_height_m": 1.5}
|
||||
)
|
||||
assert small["outlet"]["revet_height_m"] == 1.5
|
||||
hand = _culvert_set(
|
||||
{"pipe_diameter_mm": 1500, "outlet_type": "기슭막이", "outlet_revet_height_m": 1.6}
|
||||
)
|
||||
assert hand["outlet"]["revet_height_m"] == 1.6
|
||||
|
||||
|
||||
def test_chute_thickness_cm_to_m():
|
||||
"""도수로/돌붙임(90-2) — 구조를 고르면 `chute` · 두께 cm → m · 빈 두께 30 cm · 안 고르면 없음."""
|
||||
assert "chute" not in _culvert_set({"chute_add_1_thickness": 40})
|
||||
|
||||
@@ -23,13 +23,14 @@ import { buildOutletExtras } from "./B06_Section_UI_Cross_Culvert_Extra.js";
|
||||
const spec = { revet_form: "찰쌓기", revet_height_m: 1.5 };
|
||||
const pipe = (h) => revetWallSpec(spec, { h, m: null }, false, 0.8).pureHeight;
|
||||
const own = (h) => revetWallSpec(spec, { h, m: null }, true, 0).pureHeight;
|
||||
const blank = revetWallSpec({ revet_form: "찰쌓기" }, { h: null, m: null }, false, 0.8).pureHeight;
|
||||
const extras = buildOutletExtras({
|
||||
start: { offset: 10, elevation: 95 }, startBottomElevation: 94.5, outward: 1,
|
||||
groundAt: () => 80, limitOffset: 40,
|
||||
adjusts: [{ x: 0, d: 0, h: 0.3, m: null }],
|
||||
});
|
||||
writeFileSync(process.argv[2], JSON.stringify({
|
||||
pipeLow: pipe(0.7), pipeAuto: pipe(null), pipeZero: pipe(0),
|
||||
pipeLow: pipe(0.7), pipeAuto: pipe(null), pipeBlank: blank, pipeZero: pipe(0),
|
||||
ownLow: own(0.3), ownNeg: own(-1),
|
||||
extraCount: extras.walls.length,
|
||||
extraPure: extras.walls.map((w) => w.topBack.elevation - w.bottomBack.elevation),
|
||||
@@ -74,7 +75,8 @@ def test_높이는_입력_그대로_0이하만_멈춤(tmp_path: Path) -> None:
|
||||
got = json.loads(result.read_text(encoding="utf-8"))
|
||||
|
||||
assert got["pipeLow"] == pytest.approx(0.7), "관 흙막이가 관경 기준 최소로 되돌아감"
|
||||
assert got["pipeAuto"] == pytest.approx(1.8), "빈 칸 기본값(관경 + 1.0) 틀림"
|
||||
assert got["pipeAuto"] == pytest.approx(1.5), "그림이 칸 값(1.5)을 안 읽음(139-1)"
|
||||
assert got["pipeBlank"] == pytest.approx(1.3), "빈 칸 기본값(관경 + 0.5 · 139-1) 틀림"
|
||||
assert got["pipeZero"] == pytest.approx(0.1), "0 이하가 그림 하한에서 안 멈춤"
|
||||
assert got["ownLow"] == pytest.approx(0.3), "흙막이 0.3 이 근입 기준 최소로 되돌아감"
|
||||
assert got["ownNeg"] == pytest.approx(0.1), "음수 높이가 그림 하한에서 안 멈춤"
|
||||
|
||||
@@ -26,18 +26,33 @@ def _ts_const(source: str, name: str) -> float:
|
||||
|
||||
|
||||
def test_상수_셋이_TS_와_같다() -> None:
|
||||
for name in ("REVET_FREEBOARD_M", "REVET_HEIGHT_STEP_M", "REVET_EMBED_DEPTH_M"):
|
||||
for name in ("REVET_HEIGHT_STEP_M", "REVET_EMBED_DEPTH_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")
|
||||
|
||||
|
||||
def test_TS_식_모양이_그대로다() -> None:
|
||||
"""식을 바꾸면 여기서 잡힘 — 파이썬 식도 함께 고칠 것."""
|
||||
assert "const raw = diameterM + REVET_FREEBOARD_M;" in TS
|
||||
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
|
||||
# 옛 1.5 옮김 — TS 세트 짝 식
|
||||
assert "Math.abs(stored - OLD_PIPE_WALL_DEFAULT_M) < 1e-9 &&" in SETS
|
||||
assert "diameterM > 1.0 + 1e-9" in SETS
|
||||
|
||||
|
||||
def test_관경별_최소_높이() -> None:
|
||||
assert culvert.pipe_wall_min_height_m(1.0) == 2.0
|
||||
assert culvert.pipe_wall_min_height_m(0.8) == 1.8
|
||||
assert culvert.pipe_wall_min_height_m(1.2) == 2.2
|
||||
def test_관경별_기본_높이() -> None:
|
||||
"""관경 + 0.5 = 땅속 근입 0.5 포함 벽 전체 높이 · 벽 윗면 = 관 윗면(139-1 · 10-6 표 Ø1000 = 1.5)."""
|
||||
assert culvert.pipe_wall_min_height_m(1.0) == 1.5
|
||||
assert culvert.pipe_wall_min_height_m(0.8) == 1.3
|
||||
assert culvert.pipe_wall_min_height_m(1.2) == 1.7
|
||||
assert culvert.pipe_wall_min_height_m(1.5) == 2.0
|
||||
|
||||
|
||||
def test_옛_기본_옮김() -> None:
|
||||
assert culvert.pipe_wall_height_m(1.5, 1.2) == 1.7
|
||||
assert culvert.pipe_wall_height_m(1.5, 1.0) == 1.5
|
||||
assert culvert.pipe_wall_height_m(1.6, 1.5) == 1.6
|
||||
assert culvert.pipe_wall_height_m(None, 1.2) is None
|
||||
|
||||
Reference in New Issue
Block a user