fix(b05,b06,b08): 기슭막이 벽 높이를 관경 기준 한 값으로 · 폼은 기본값을 값으로 안 보냄
- 한 벽을 두고 값이 셋(등록부·표 H2.5 · 구조물도 H2.5 · 횡단도 관경 기준)이던 결함 — 근거 있는 관경 기준으로 맞춤 - 관 벽 높이 안 적으면 관경 + 여유 0.5 를 0.1m 로 올리고 근입 0.5 더함(Ø800 1.8 · Ø1000 2.0 · Ø1500 2.5) - 파이썬(B06 엔진)·TS(세트 스펙·횡단 상수) 거울 시험 · 등록부 기본 2.5 걷고 empty_means - 최소값이라 미확정·금액 밖은 그대로 - B05 벽 칸(관 유입·유출 · 독립 기슭막이)은 비워 두고 회색 제안값 · [제안값 넣기] 누른 때만 값 — 저장 한 번에 미확정이 확정으로 바뀌던 길을 막음 - 전체 시험: 받은 덤프 적재 커밋 몫 2개만 빨강(보고함) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016VBGFXB9AbJBwXP19z75Qq
This commit is contained in:
@@ -134,9 +134,10 @@
|
||||
"label": "유입 기슭막이 높이",
|
||||
"input": "number",
|
||||
"unit": "m",
|
||||
"default": 2.5,
|
||||
"default": null,
|
||||
"required": false,
|
||||
"phase": "detail"
|
||||
"phase": "detail",
|
||||
"empty_means": "비우면 관경 기준 최소 높이(관경 + 여유 0.5 를 0.1m 로 올림 + 근입 0.5)로 그리고 셈 — 횡단도·구조물도·표가 같은 값 · 최소라 수량은 미확정(금액 밖) · 2026-09-14 브레인 판정(옛 기본 2.5 걷음)"
|
||||
},
|
||||
{
|
||||
"key": "outlet_type",
|
||||
@@ -186,9 +187,10 @@
|
||||
"label": "유출 기슭막이 높이",
|
||||
"input": "number",
|
||||
"unit": "m",
|
||||
"default": 2.5,
|
||||
"default": null,
|
||||
"required": false,
|
||||
"phase": "detail"
|
||||
"phase": "detail",
|
||||
"empty_means": "비우면 관경 기준 최소 높이(관경 + 여유 0.5 를 0.1m 로 올림 + 근입 0.5)로 그리고 셈 — 횡단도·구조물도·표가 같은 값 · 최소라 수량은 미확정(금액 밖) · 2026-09-14 브레인 판정(옛 기본 2.5 걷음)"
|
||||
},
|
||||
{
|
||||
"key": "revet_foundation",
|
||||
|
||||
@@ -59,9 +59,9 @@ import {
|
||||
WING_IN_KEYS,
|
||||
WING_OUT_KEYS,
|
||||
} from "./B05_Profile_UI_Drainage_Facility_Fields";
|
||||
import { pipeWallMinHeightM } from "../common_util/common_util_culvert_sets";
|
||||
|
||||
// 유입구 형식 목록은 700줄 한계로 `_Fields` 로 옮김(2026-09-14) — 부르던 곳은 그대로 여기서.
|
||||
export type { InletStructureKind };
|
||||
export type { InletStructureKind }; // 목록은 700줄 한계로 `_Fields` 로 옮김 — 부르던 곳은 그대로
|
||||
|
||||
/** 시설 확장 정보 한 건 — 관 지점(chainage)에 얹힌다. 배관(pipe)도 부속 옵션을
|
||||
* 가지면 저장한다. */
|
||||
@@ -235,9 +235,11 @@ export function createFacilityOptionsForm(
|
||||
const basinRows = [
|
||||
grid(labeled("형식", basinForm), labeled("길이 (m)", stepper(basinLength, 1))),
|
||||
];
|
||||
const height = (): string => String(pipeWallMinHeightM(Number(pipeDiameter.value) / 1000));
|
||||
const inletRevet = createRevetmentFields(INLET_REVET_KEYS, {
|
||||
...REVET_COMMON_DEFAULTS,
|
||||
form: "돌쌓기(찰)",
|
||||
height,
|
||||
});
|
||||
// 첫 행 = [구조][형태] — 구조에 따라 집수정 형태와 기슭막이 형태가 갈아 끼워진다
|
||||
// (2026-08-17 사용자 지시 3).
|
||||
@@ -260,6 +262,7 @@ export function createFacilityOptionsForm(
|
||||
const outletRevet = createRevetmentFields(OUTLET_REVET_KEYS, {
|
||||
...REVET_COMMON_DEFAULTS,
|
||||
form: "돌쌓기(메)",
|
||||
height,
|
||||
});
|
||||
outletGroup.body.append(
|
||||
grid(labeled("구조", outletType), outletRevet.formField),
|
||||
|
||||
@@ -201,16 +201,20 @@ export interface RevetmentKeys {
|
||||
after?: string;
|
||||
}
|
||||
|
||||
/** 부위별 기본 구성 — 형태는 유입 돌쌓기(찰)·유출 돌쌓기(메), 길이 10m·높이 2.5m. */
|
||||
/** 부위별 **제안값** — 형태는 유입 돌쌓기(찰)·유출 돌쌓기(메), 길이 10m. 관 벽 높이는 관경 기준
|
||||
* (함수로 받아 볼 때마다 셈).
|
||||
* ⚠ 제안값은 **칸에 채우지 않고 회색 글씨로만** 보임 — 사용자가 적거나 [제안값 넣기]를 누른 때만
|
||||
* 값이 됨(2026-09-14 브레인 판정 「기본값을 몰래 확정으로 바꾸지 않는다」). 칸에 채워 두면 저장이
|
||||
* 그 값을 적어 **미확정이 저장 한 번에 확정으로 바뀜** — 사용자가 한 일이 없는데 금액이 섬. */
|
||||
export interface RevetmentDefaults {
|
||||
form: string;
|
||||
length: string;
|
||||
height: string;
|
||||
height: string | (() => string);
|
||||
before: string;
|
||||
after: string;
|
||||
}
|
||||
|
||||
/** 부위와 무관한 공통 기본 치수 — 길이 10m(전 5·후 5)·높이 2.5m. */
|
||||
/** 부위와 무관한 공통 제안 치수 — 길이 10m(전 5·후 5)·높이 2.5m(독립 기슭막이 · 관 벽은 관경 기준). */
|
||||
export const REVET_COMMON_DEFAULTS = {
|
||||
length: "10",
|
||||
height: "2.5",
|
||||
@@ -284,26 +288,30 @@ export function createRevetmentFields(
|
||||
keys: RevetmentKeys,
|
||||
defaults: RevetmentDefaults,
|
||||
): RevetmentFields {
|
||||
// ⚠ 제안값은 **회색 글씨(placeholder)로만** — 칸은 비워 둠(`RevetmentDefaults` 주석).
|
||||
const suggestHeight = (): string =>
|
||||
typeof defaults.height === "function" ? defaults.height() : defaults.height;
|
||||
const form = document.createElement("select");
|
||||
form.replaceChildren(...REVET_FORMS.map((value) => new Option(value, value)));
|
||||
form.value = defaults.form;
|
||||
const length = keys.length ? numberInput("0.1") : null;
|
||||
if (length) length.value = defaults.length;
|
||||
const height = numberInput("0.1");
|
||||
height.value = defaults.height;
|
||||
form.replaceChildren(
|
||||
new Option(`제안 ${defaults.form}`, ""),
|
||||
...REVET_FORMS.map((value) => new Option(value, value)),
|
||||
);
|
||||
const length = keys.length ? numberInput("0.1", "0", `제안 ${defaults.length}`) : null;
|
||||
const height = numberInput("0.1", "0", `제안 ${suggestHeight()}`);
|
||||
// 관 벽 높이 제안은 관경을 따라감 — 칸을 볼 때마다 다시 셈.
|
||||
height.addEventListener("focus", () => (height.placeholder = `제안 ${suggestHeight()}`));
|
||||
// 기준측점 전/후(2026-08-23 사용자) — 구조물 패널(D군)과 같은 연동:
|
||||
// 전 + 후 = 길이, 나중에 고친 쪽이 살아남고 반대쪽이 재배분된다.
|
||||
const before = keys.before && length ? numberInput("0.1") : null;
|
||||
const after = keys.after && length ? numberInput("0.1") : null;
|
||||
if (before) before.value = defaults.before;
|
||||
if (after) after.value = defaults.after;
|
||||
const before = keys.before && length ? numberInput("0.1", "0", `제안 ${defaults.before}`) : null;
|
||||
const after = keys.after && length ? numberInput("0.1", "0", `제안 ${defaults.after}`) : null;
|
||||
let lastSplitEdit: "before" | "after" = "before";
|
||||
const readNumber = (input: HTMLInputElement | null): number => {
|
||||
const value = input ? Number.parseFloat(input.value) : Number.NaN;
|
||||
return Number.isFinite(value) && value >= 0 ? value : 0;
|
||||
};
|
||||
const syncSplit = (source: "length" | "before" | "after"): void => {
|
||||
if (!length || !before || !after) return;
|
||||
// 길이가 비었으면 전/후를 0 으로 채우지 않음 — 빈 칸이 「0 으로 적음」이 되면 안 됨.
|
||||
if (!length || !before || !after || length.value === "") return;
|
||||
if (source !== "length") lastSplitEdit = source;
|
||||
const total = readNumber(length);
|
||||
const keep = lastSplitEdit === "after" ? after : before;
|
||||
@@ -319,10 +327,25 @@ export function createRevetmentFields(
|
||||
const beforeField = before ? labeled("기준측점 전 (m)", stepper(before, 0.5)) : null;
|
||||
const afterField = after ? labeled("기준측점 후 (m)", stepper(after, 0.5)) : null;
|
||||
|
||||
// [제안값 넣기] — **누른 때만** 빈 칸에 제안값을 넣음(적은 칸은 안 건드림) · 층따기 단추와 같은 모양.
|
||||
const fill = document.createElement("button");
|
||||
fill.type = "button";
|
||||
fill.className = "b05-structure__suggest";
|
||||
fill.textContent = "제안값 넣기";
|
||||
fill.addEventListener("click", () => {
|
||||
if (!form.value) form.value = defaults.form;
|
||||
if (length && !length.value) length.value = defaults.length;
|
||||
if (!height.value) height.value = suggestHeight();
|
||||
if (before && !before.value) before.value = defaults.before;
|
||||
if (after && !after.value) after.value = defaults.after;
|
||||
form.dispatchEvent(new Event("change"));
|
||||
});
|
||||
|
||||
// 형태는 그룹 첫 행(구조 옆)에 따로 놓이므로 여기 행에서는 뺀다.
|
||||
const rows = [
|
||||
grid(...[lengthField, heightField].filter((item): item is HTMLLabelElement => item !== null)),
|
||||
...(beforeField && afterField ? [grid(beforeField, afterField)] : []),
|
||||
grid(fill),
|
||||
];
|
||||
|
||||
return {
|
||||
@@ -330,13 +353,15 @@ export function createRevetmentFields(
|
||||
rows,
|
||||
lengthInput: length,
|
||||
write(options) {
|
||||
// 저장된 값만 칸에 — 없으면 비워 두고 제안값은 회색 글씨로(몰래 채우지 않음).
|
||||
const text = (key: string): string =>
|
||||
options[key] !== undefined ? String(options[key]) : "";
|
||||
form.value = text(keys.form) || defaults.form;
|
||||
if (length && keys.length) length.value = text(keys.length) || defaults.length;
|
||||
height.value = text(keys.height) || defaults.height;
|
||||
if (before && keys.before) before.value = text(keys.before) || defaults.before;
|
||||
if (after && keys.after) after.value = text(keys.after) || defaults.after;
|
||||
form.value = text(keys.form);
|
||||
if (length && keys.length) length.value = text(keys.length);
|
||||
height.value = text(keys.height);
|
||||
height.placeholder = `제안 ${suggestHeight()}`;
|
||||
if (before && keys.before) before.value = text(keys.before);
|
||||
if (after && keys.after) after.value = text(keys.after);
|
||||
syncSplit("length");
|
||||
},
|
||||
read(target) {
|
||||
|
||||
@@ -74,6 +74,26 @@ APRON_THICKNESS_M = 0.45
|
||||
# 1:0.3~0.5의 급한 쪽. 지식DB 근거가 있는 값이라 교차 참조 표시가 붙지 않는다.
|
||||
REVET_FACE_SLOPE = 0.3
|
||||
|
||||
# ── 관 벽 최소 높이 — ⚠ TS 짝: `B06_Section_UI_Cross_Culvert_Const.ts`
|
||||
# (`REVET_FREEBOARD_M`·`REVET_HEIGHT_STEP_M`·`REVET_EMBED_DEPTH_M`·`pipeWallMinPureHeight`).
|
||||
# 횡단도가 관 벽을 그리는 그 높이 — 구조물 표·구조물도 그림도 이 값을 씀(2026-09-14 브레인 판정
|
||||
# 「그림 둘이 같은 벽을 다르게 그리면 결함 · 근거 있는 관경 기준으로 맞춤」). 거울 시험
|
||||
# `test_b06_pipe_wall_height_mirror`.
|
||||
REVET_FREEBOARD_M = 0.5 # 관 상단 ~ 벽 상단 여유고(잠정 · 토피 0.5 와 같은 값)
|
||||
REVET_HEIGHT_STEP_M = 0.1 # 높이 눈금
|
||||
REVET_EMBED_DEPTH_M = 0.5 # 근입 깊이
|
||||
|
||||
|
||||
def pipe_wall_min_height_m(diameter_m: float) -> float:
|
||||
"""관 벽 **순수 높이**(근입 포함) 최소값 — 관경 + 여유고를 0.1m 로 올린 뒤 근입을 더함.
|
||||
|
||||
⚠ 「최소」다 — 실제 설계값은 더 클 수 있어 이 값으로 선 수량은 미확정으로 둠(금액 밖).
|
||||
"""
|
||||
raw = float(diameter_m) + REVET_FREEBOARD_M
|
||||
steps = math.ceil(raw / REVET_HEIGHT_STEP_M - 1e-9)
|
||||
return round(steps * REVET_HEIGHT_STEP_M + REVET_EMBED_DEPTH_M, 3)
|
||||
|
||||
|
||||
# (2026-08-20 사용자 확정) 배관은 **수평도 가능** — 그림에서 경사를 강제로 만들지
|
||||
# 않는다. config `DRAINAGE_PIPE_SLOPE_DEG`(10도)는 유효직경 수리계산 전용으로 남는다.
|
||||
|
||||
@@ -139,7 +159,9 @@ def _number(value: Any, fallback: float | None) -> float | None:
|
||||
return fallback
|
||||
|
||||
|
||||
def _side_spec(options: dict[str, Any], defaults: dict[str, Any], side: str) -> dict[str, Any]:
|
||||
def _side_spec(
|
||||
options: dict[str, Any], defaults: dict[str, Any], side: str, diameter_m: float = 1.0
|
||||
) -> dict[str, Any]:
|
||||
"""유입("inlet")·유출("outlet") 한쪽의 부속 제원.
|
||||
|
||||
구조가 집수정이면 기슭막이·보호공을 만들지 않는다 — 집수정 단면은 후속 작업이라
|
||||
@@ -164,9 +186,10 @@ def _side_spec(options: dict[str, Any], defaults: dict[str, Any], side: str) ->
|
||||
)
|
||||
return spec
|
||||
|
||||
# 높이를 안 적었으면 **횡단도가 그리는 관경 기준 최소 높이**(2026-09-14 · 옛 기본 2.5 걷음).
|
||||
height = _number(
|
||||
options.get(f"{side}_revet_height_m"),
|
||||
_number(defaults.get(f"{side}_revet_height_m"), None),
|
||||
_number(defaults.get(f"{side}_revet_height_m"), pipe_wall_min_height_m(diameter_m)),
|
||||
)
|
||||
length = _number(
|
||||
options.get(f"{side}_revet_length_m"),
|
||||
@@ -208,10 +231,11 @@ def _culvert_set(options: dict[str, Any] | None) -> dict[str, Any]:
|
||||
values.get("pipe_diameter_mm"), _number(defaults.get("pipe_diameter_mm"), 1000.0)
|
||||
)
|
||||
kind = values.get("pipe_kind") or defaults.get("pipe_kind")
|
||||
diameter_m = round((diameter_mm or 1000.0) / 1000.0, 3)
|
||||
return {
|
||||
"type": "pipe",
|
||||
"pipe_kind": str(kind) if kind else None,
|
||||
"diameter_m": round((diameter_mm or 1000.0) / 1000.0, 3),
|
||||
"diameter_m": diameter_m,
|
||||
"min_cover_m": MIN_PIPE_COVER_M,
|
||||
# 관 기슭막이 기초 축 — **유입·유출 한 칸으로** 받는다(2026-09-09). 한쪽만 기초유로
|
||||
# 하는 일이 드물어 폼을 둘로 늘리지 않았다. 실무가 갈라 쓰면 그때 나눌 것.
|
||||
@@ -219,8 +243,8 @@ def _culvert_set(options: dict[str, Any] | None) -> dict[str, Any]:
|
||||
str(values.get("revet_foundation") or defaults.get("revet_foundation") or "").strip()
|
||||
or None
|
||||
),
|
||||
"inlet": _side_spec(values, defaults, "inlet"),
|
||||
"outlet": _side_spec(values, defaults, "outlet"),
|
||||
"inlet": _side_spec(values, defaults, "inlet", diameter_m),
|
||||
"outlet": _side_spec(values, defaults, "outlet", diameter_m),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -208,12 +208,15 @@ REVET_ROLES = (
|
||||
("inlet", "유입부 기슭막이", "유입 칸 벽"),
|
||||
("outlet", "유출부 기슭막이", "유출 칸 벽"),
|
||||
)
|
||||
#: ⚠ 「횡단도와 같은 값」이 아님 — B06 은 관 벽 높이를 **관경 기준 최소 높이**로 따로 그림
|
||||
#: (`revetWallSpec`), 독립 기슭막이도 저장 높이가 없으면 근입 깊이로 그림. 형태·길이만 같은 기본값.
|
||||
#: 관 벽 높이를 안 적었으면 **횡단도가 그리는 관경 기준 최소 높이**(B06 `pipe_wall_min_height_m`,
|
||||
#: TS 짝 `pipeWallMinPureHeight`)로 섬 — 등록부·구조물도·횡단도가 한 값(2026-09-14 브레인 판정).
|
||||
#: ⚠ 「최소」라 실제 설계값이 더 클 수 있어 **미확정·금액 밖은 그대로**.
|
||||
NOTE_DEFAULT_WALL = (
|
||||
"⚠ 시설 지점에 {filled} 을 안 적어 등록부 기본값으로 섰음 — 미확정이라 금액에 안 들어감"
|
||||
" · 횡단도 벽 높이는 관경 기준으로 따로 그려져 다를 수 있음"
|
||||
"⚠ 시설 지점에 {filled} 을 안 적어 기본값으로 섰음 — 미확정이라 금액에 안 들어감"
|
||||
)
|
||||
#: 꼬리 — 관 벽은 횡단도와 같은 높이 · 독립 기슭막이는 아직 갈림(관경이 없음).
|
||||
NOTE_WALL_HEIGHT_PIPE = " · 높이는 횡단도와 같은 관경 기준 최소 높이(실제 설계값은 더 클 수 있음)"
|
||||
NOTE_WALL_HEIGHT_OWN = " · ⚠ 횡단도는 저장 높이가 없으면 근입 0.5m 만 그림 — 표 높이와 다름"
|
||||
#: 금액에서 뺄 까닭(2026-09-14 브레인 판정) — 줄·표·그림은 서되 내역 합에는 안 듦.
|
||||
UNCONFIRMED_WALL = "벽 치수({filled})가 시설 지점에 없어 기본값으로 섰음 — 적으면 금액이 섬"
|
||||
NOTE_SIDE_UNKNOWN = (
|
||||
@@ -234,9 +237,13 @@ def _num_or_zero(value: Any) -> float:
|
||||
|
||||
|
||||
def _revet_values(
|
||||
options: dict[str, Any], role: str, defaults: dict[str, Any], legacy: bool
|
||||
options: dict[str, Any],
|
||||
role: str,
|
||||
defaults: dict[str, Any],
|
||||
legacy: bool,
|
||||
min_height_m: float | None = None,
|
||||
) -> tuple[dict[str, Any], list[str]]:
|
||||
"""벽 한쪽 제원과 등록부 기본값으로 채운 칸 — B06 세트 스펙(`_side_spec`)과 같은 채움."""
|
||||
"""벽 한쪽 제원과 기본값으로 채운 칸 — 등록부 기본값 · 관 벽 높이는 관경 기준 최소 높이."""
|
||||
values: dict[str, Any] = {}
|
||||
filled: list[str] = []
|
||||
for name, label in REVET_FIELDS:
|
||||
@@ -245,12 +252,23 @@ def _revet_values(
|
||||
value = options.get(name)
|
||||
if _blank(value):
|
||||
value = defaults.get(name if legacy else f"{role}_revet_{name}")
|
||||
if value is None and name == "height_m" and min_height_m is not None:
|
||||
value = min_height_m
|
||||
label = f"{label}(관경 기준)"
|
||||
if label and value is not None:
|
||||
filled.append(f"{label} {value}")
|
||||
values[name] = value
|
||||
return values, filled
|
||||
|
||||
|
||||
def _pipe_diameter_m(options: dict[str, Any], defaults: dict[str, Any]) -> float:
|
||||
"""관경(m) — 저장값, 없으면 등록부 기본(B06 세트 스펙과 같은 채움)."""
|
||||
raw = options.get("pipe_diameter_mm")
|
||||
if _blank(raw):
|
||||
raw = defaults.get("pipe_diameter_mm")
|
||||
return _num_or_zero(raw) / 1000.0 or 1.0
|
||||
|
||||
|
||||
def facility_structures(points: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
"""계곡 통과 시설(`pipe_points.json`) → 원단위 전개가 읽는 구조물 줄 (A1, 2026-09-14).
|
||||
|
||||
@@ -260,6 +278,7 @@ def facility_structures(points: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
⚠ 집수정·기슭막이 터파기·되메우기는 전개 성분(`destination: earthwork`)이라 토공집계로만 감.
|
||||
"""
|
||||
from B05_Profile.B05_Profile_Structures_Schema import structure_type_map
|
||||
from B06_Section.B06_Section_Engine_Culvert import pipe_wall_min_height_m
|
||||
from B08_Quantity.B08_Quantity_Engine_StructureSummary import pipe_row_id
|
||||
from B08_Quantity.B08_Quantity_Engine_UnitQuantity import attachments_of, child_row
|
||||
|
||||
@@ -304,11 +323,15 @@ def facility_structures(points: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
)
|
||||
rows.extend(children)
|
||||
walls = []
|
||||
min_height = (
|
||||
None if legacy else pipe_wall_min_height_m(_pipe_diameter_m(options, wall_defaults))
|
||||
)
|
||||
for role, pipe_label, own_label in roles:
|
||||
values, filled = _revet_values(options, role, wall_defaults, legacy)
|
||||
values, filled = _revet_values(options, role, wall_defaults, legacy, min_height)
|
||||
notes = list(inlet_notes) if role == "inlet" else []
|
||||
if filled:
|
||||
notes.append(NOTE_DEFAULT_WALL.format(filled=" · ".join(filled)))
|
||||
tail = NOTE_WALL_HEIGHT_OWN if legacy else NOTE_WALL_HEIGHT_PIPE
|
||||
notes.append(NOTE_DEFAULT_WALL.format(filled=" · ".join(filled)) + tail)
|
||||
walls.append([role, own_label if legacy else pipe_label, values, notes, False, filled])
|
||||
side = str(options.get("side") or "") if legacy else ""
|
||||
if side in ("좌", "우"):
|
||||
|
||||
@@ -46,6 +46,18 @@ export const FORD_WALL_THICKNESS_M = 0.2;
|
||||
export const FORD_DEFAULT_WIDTH_M = 10.0;
|
||||
export const FORD_PAVEMENT_DEFAULT_WIDTH_M = 5.0;
|
||||
export const BOX_COVER_M = 0.5;
|
||||
/** 관 벽 최소 높이 — 횡단도 `B06_Section_UI_Cross_Culvert_Const.ts` 와 같은 값(거울 시험이 셋을 대조).
|
||||
* 높이를 안 적은 관 벽은 이 값으로 섬(2026-09-14 브레인 판정 · 옛 등록부 기본 2.5 걷음). */
|
||||
export const REVET_FREEBOARD_M = 0.5;
|
||||
export const REVET_HEIGHT_STEP_M = 0.1;
|
||||
export const REVET_EMBED_DEPTH_M = 0.5;
|
||||
|
||||
/** 관 벽 순수 높이(근입 포함) 최소값 — 파이썬 `pipe_wall_min_height_m`. */
|
||||
export function pipeWallMinHeightM(diameterM: number): number {
|
||||
const raw = diameterM + REVET_FREEBOARD_M;
|
||||
const steps = Math.ceil(raw / REVET_HEIGHT_STEP_M - 1e-9);
|
||||
return round(steps * REVET_HEIGHT_STEP_M + REVET_EMBED_DEPTH_M, 3);
|
||||
}
|
||||
|
||||
/** 시설 종류 — 파이썬 `common_util_drainage_pipes` 의 상수와 같은 문자열. */
|
||||
const FACILITY_PIPE = "pipe";
|
||||
@@ -82,6 +94,7 @@ function sideSpec(
|
||||
options: Record<string, unknown>,
|
||||
defaults: Record<string, unknown>,
|
||||
side: string,
|
||||
diameterM: number,
|
||||
): CulvertSetSpec {
|
||||
const structure = text(options[`${side}_type`]) ?? text(defaults[`${side}_type`]) ?? "기슭막이";
|
||||
const spec: CulvertSetSpec = { role: side, structure };
|
||||
@@ -100,7 +113,7 @@ function sideSpec(
|
||||
|
||||
const height = num(
|
||||
options[`${side}_revet_height_m`],
|
||||
num(defaults[`${side}_revet_height_m`], null),
|
||||
num(defaults[`${side}_revet_height_m`], pipeWallMinHeightM(diameterM)),
|
||||
);
|
||||
const length = num(
|
||||
options[`${side}_revet_length_m`],
|
||||
@@ -133,17 +146,18 @@ function culvertSet(
|
||||
): CulvertSetSpec {
|
||||
const defaults = registry.pipe ?? {};
|
||||
const diameterMm = num(options.pipe_diameter_mm, num(defaults.pipe_diameter_mm, 1000.0));
|
||||
const diameterM = pipeDiameterM(diameterMm);
|
||||
return {
|
||||
type: "pipe",
|
||||
pipe_kind: text(options.pipe_kind) ?? text(defaults.pipe_kind),
|
||||
diameter_m: pipeDiameterM(diameterMm),
|
||||
diameter_m: diameterM,
|
||||
min_cover_m: MIN_PIPE_COVER_M,
|
||||
// 관 기슭막이 기초 축 — **유입·유출 한 칸으로** 받는다. 한 공사에서 한쪽만 기초유로
|
||||
// 하는 일은 드물어 폼을 둘로 늘리지 않았다(2026-09-09). 실무가 갈라 쓰는 것이
|
||||
// 확인되면 그때 나눌 것.
|
||||
foundation: text(options.revet_foundation) ?? text(defaults.revet_foundation),
|
||||
inlet: sideSpec(options, defaults, "inlet"),
|
||||
outlet: sideSpec(options, defaults, "outlet"),
|
||||
inlet: sideSpec(options, defaults, "inlet", diameterM),
|
||||
outlet: sideSpec(options, defaults, "outlet", diameterM),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -451,10 +451,12 @@ def test_pipe_accessory_options_defined():
|
||||
"outlet_pitching_area_m2",
|
||||
):
|
||||
assert gone not in options, f"pipe.{gone}는 보호공으로 합쳐졌다"
|
||||
# 기본 치수 — 길이 10m·높이 2.5m.
|
||||
# 기본 치수 — 길이 10m. 높이는 **기본값 없음**(2026-09-14 브레인 판정): 비우면 관경 기준 최소
|
||||
# 높이로 횡단도·구조물도·표가 같은 값을 씀(옛 2.5 는 횡단도가 안 쓰던 근거 없는 값이라 걷음).
|
||||
for side in ("inlet", "outlet"):
|
||||
assert options[f"{side}_revet_length_m"].default == 10
|
||||
assert options[f"{side}_revet_height_m"].default == 2.5
|
||||
assert options[f"{side}_revet_height_m"].default is None
|
||||
assert "관경 기준" in options[f"{side}_revet_height_m"].empty_means
|
||||
# 유무·길이·관경·유입 구조 = 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"
|
||||
|
||||
@@ -34,7 +34,11 @@ def test_cross_reference_constants_match_plan():
|
||||
|
||||
|
||||
def test_empty_options_fall_back_to_registry_defaults():
|
||||
"""옵션이 빈 저장분은 레지스트리 기본값(Ø1000·기슭막이 H2.5/L10)으로 채운다."""
|
||||
"""옵션이 빈 저장분은 레지스트리 기본값(Ø1000·L10)으로 채운다.
|
||||
|
||||
높이는 2026-09-14 브레인 판정으로 **관경 기준 최소 높이**(Ø1000 → 1.5 + 근입 0.5 = 2.0) —
|
||||
횡단도가 그리는 그 높이. 옛 기본 2.5 는 횡단도가 안 쓰던 값이라 걷음.
|
||||
"""
|
||||
spec = _culvert_set(None)
|
||||
assert spec["type"] == "pipe"
|
||||
assert spec["diameter_m"] == 1.0
|
||||
@@ -42,11 +46,11 @@ def test_empty_options_fall_back_to_registry_defaults():
|
||||
for side in ("inlet", "outlet"):
|
||||
part = spec[side]
|
||||
assert part["structure"] == "기슭막이"
|
||||
assert part["revet_height_m"] == 2.5
|
||||
assert part["revet_height_m"] == 2.0
|
||||
assert part["revet_length_m"] == 10
|
||||
assert part["face_slope"] == REVET_FACE_SLOPE
|
||||
# 보호공 = 낙차고(2.5) × 2 = 5.0m, 두께 0.45m(사용자 확정).
|
||||
assert part["apron_length_m"] == 5.0
|
||||
# 보호공 = 낙차고(2.0) × 2 = 4.0m, 두께 0.45m(사용자 확정).
|
||||
assert part["apron_length_m"] == 4.0
|
||||
assert part["apron_thickness_m"] == 0.45
|
||||
# 레지스트리 기본 형태: 유입 찰 / 유출 메.
|
||||
assert spec["inlet"]["revet_form"] == "돌쌓기(찰)"
|
||||
@@ -61,7 +65,7 @@ def test_basin_inlet_skips_revet_and_apron():
|
||||
assert "apron_length_m" not in spec["inlet"]
|
||||
# 유출측은 그대로 세트를 갖춘다.
|
||||
assert spec["outlet"]["structure"] == "기슭막이"
|
||||
assert spec["outlet"]["apron_length_m"] == 5.0
|
||||
assert spec["outlet"]["apron_length_m"] == 4.0 # 관경 기준 높이 2.0 × 2
|
||||
|
||||
|
||||
def test_string_diameter_and_custom_height():
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
"""관 벽 최소 높이 거울 — 파이썬(`B06_Section_Engine_Culvert`) ↔ TS(`_Cross_Culvert_Const.ts`).
|
||||
|
||||
⚠ 횡단도(TS)가 그리는 높이와 구조물 표·구조물도 그림(파이썬)이 같은 벽을 다르게 그리면 결함
|
||||
(2026-09-14 브레인 판정). 상수 셋이 같고 식이 같으면 같은 값.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from B06_Section import B06_Section_Engine_Culvert as culvert # noqa: E402
|
||||
|
||||
TS = (ROOT / "B06_Section" / "B06_Section_UI_Cross_Culvert_Const.ts").read_text(encoding="utf-8")
|
||||
#: 세트 스펙 TS 짝(`common_util_culvert_sets.ts`)도 같은 상수를 들고 있음 — 셋을 함께 대조.
|
||||
SETS = (ROOT / "common_util" / "common_util_culvert_sets.ts").read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def _ts_const(source: str, name: str) -> float:
|
||||
return float(re.search(rf"export const {name} = ([\d.]+);", source).group(1))
|
||||
|
||||
|
||||
def test_상수_셋이_TS_와_같다() -> None:
|
||||
for name in ("REVET_FREEBOARD_M", "REVET_HEIGHT_STEP_M", "REVET_EMBED_DEPTH_M"):
|
||||
assert getattr(culvert, name) == _ts_const(TS, name) == _ts_const(SETS, name), name
|
||||
|
||||
|
||||
def test_TS_식_모양이_그대로다() -> None:
|
||||
"""식을 바꾸면 여기서 잡힘 — 파이썬 식도 함께 고칠 것."""
|
||||
assert "const raw = diameterM + REVET_FREEBOARD_M;" in TS
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
@@ -34,7 +34,9 @@ def test_관_하나에_기슭막이_둘이_기본값_사유와_함께_서고_관
|
||||
inlet, outlet = table["structures"]
|
||||
assert inlet["options"]["form"] == "돌쌓기(찰)" and outlet["options"]["form"] == "돌쌓기(메)"
|
||||
assert all(s["components"] and s["length_m"] == 10 for s in table["structures"])
|
||||
assert "등록부 기본값으로 섰음" in inlet["notes"][0]
|
||||
assert "기본값으로 섰음" in inlet["notes"][0]
|
||||
# 높이는 횡단도와 같은 관경 기준 최소 높이(Ø1000 → 2.0) — 그림 둘·표가 한 값(브레인 판정)
|
||||
assert inlet["height_m"] == outlet["height_m"] == 2.0 and "관경 기준" in inlet["notes"][0]
|
||||
# 관 줄은 관 연장만 — 기슭막이가 거기 섞이지 않음(두 번 안 셈).
|
||||
pipe = build_rows([point], [{"chainage_m": 100.0, "design": {"pipe_length_m": 8}}])
|
||||
assert [row["quantity"] for row in pipe["rows"]] == [8.0]
|
||||
|
||||
Reference in New Issue
Block a user