feat(b05,b06,b08): 독립 기슭막이 치수 한 벌(revetment_sabang) — 높이 설계자 입력·없으면 미확정 · B05 칸 밑 근거와 B08 사유가 같은 파일(사방교본 쪽·줄 번호) · 등록부 높이 2.5 걷음(B4)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016VBGFXB9AbJBwXP19z75Qq
This commit is contained in:
@@ -2041,9 +2041,10 @@
|
||||
"label": "높이",
|
||||
"input": "number",
|
||||
"unit": "m",
|
||||
"default": 2.5,
|
||||
"default": null,
|
||||
"required": false,
|
||||
"phase": "b05"
|
||||
"phase": "b05",
|
||||
"empty_means": "설계자 입력 — 계획홍수위 + 0.5~0.7m(사방기술교본 2-나:141 · 3-가:181 · 사방(계류) 교본 기준). 비우면 줄만 서고 미확정이라 금액에 안 들어감 · 옛 기본 2.5 는 근거 없어 걷음(2026-09-14 브레인 판정)"
|
||||
},
|
||||
{
|
||||
"key": "length_m",
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
* ========================================================================== */
|
||||
|
||||
import { FORD_MANNING_N, FORD_SLOPE } from "@config/config_frontend";
|
||||
import revetmentSabang from "../resources/data_masonry/revetment_sabang_2026-01-01.json";
|
||||
|
||||
/* ── 유입구 구조 ──────────────────────────────────────────────────────────── */
|
||||
|
||||
@@ -252,16 +253,43 @@ export interface RevetSideGroup {
|
||||
slot: HTMLElement;
|
||||
}
|
||||
|
||||
/** 독립 기슭막이 치수 근거 줄 — `revetment_sabang_*.json` 한 벌(B08 사유와 같은 파일 · 쪽·줄 번호).
|
||||
* ⚠ 파일 판이 바뀌면 이 import 도 옮길 것 — 시험(`test_b05_revetment_sabang_basis`)이 최신 판을 대조. */
|
||||
function sabangBasisLines(): string[] {
|
||||
const { items, scope } = revetmentSabang;
|
||||
const span = (values: number[], unit = ""): string => `${values[0]}~${values[1]}${unit}`;
|
||||
return [
|
||||
`높이 — ${items.height.rule}(${items.height.source}) · ${items.height.input}`,
|
||||
`계획비탈 1:${span(items.face_slope.standard_ratio)}(${items.face_slope.source}) — 제안값은 품셈 표준경사`,
|
||||
`둑마루 두께 ${span(items.crown_thickness.standard_m, "m")}(${items.crown_thickness.source}) — 콘크리트 기준 · 돌쌓기는 뒷길이 + 0.30 식`,
|
||||
`뒷채움 조약돌 두께 — ${items.backfill_pebble.source}`,
|
||||
scope,
|
||||
];
|
||||
}
|
||||
|
||||
export function createRevetSideGroup(keys: RevetmentKeys, formDefault: string): RevetSideGroup {
|
||||
const box = group("기슭막이");
|
||||
const kind = document.createElement("select");
|
||||
kind.replaceChildren(new Option("기슭막이", "기슭막이"));
|
||||
const fields = createRevetmentFields(keys, { ...REVET_COMMON_DEFAULTS, form: formDefault });
|
||||
// 높이는 제안 없음 — 설계자 입력(계획홍수위 + 여유고 · 2026-09-14 브레인 판정). 옛 2.5 는 근거 없어 걷음.
|
||||
const fields = createRevetmentFields(keys, {
|
||||
...REVET_COMMON_DEFAULTS,
|
||||
form: formDefault,
|
||||
height: "",
|
||||
});
|
||||
const basis = document.createElement("p");
|
||||
basis.className = "b05-route__note";
|
||||
basis.replaceChildren(
|
||||
...sabangBasisLines().flatMap((line, index) =>
|
||||
index ? [document.createElement("br"), line] : [line],
|
||||
),
|
||||
);
|
||||
const slot = document.createElement("div");
|
||||
slot.className = "b05-structure__adjust-slot";
|
||||
box.body.append(
|
||||
grid(labeled("구조", kind), fields.formField),
|
||||
...fields.rows,
|
||||
basis,
|
||||
// 단 수는 여기 두지 않는다 — 조정창에서 옮겨 오는 [추가 기슭막이(단)] 행과
|
||||
// 같은 값이라 두 벌이면 헷갈린다(2026-08-30 사용자).
|
||||
slot,
|
||||
@@ -301,9 +329,11 @@ export function createRevetmentFields(
|
||||
...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()}`);
|
||||
// 제안이 없는 칸(독립 기슭막이 높이)은 「설계자 입력」 — 값을 지어내지 않음.
|
||||
const heightHint = (): string => (suggestHeight() ? `제안 ${suggestHeight()}` : "설계자 입력");
|
||||
const height = numberInput("0.1", "0", heightHint());
|
||||
// 관 벽 높이 제안은 관경을 따라감 — 칸을 볼 때마다 다시 셈.
|
||||
height.addEventListener("focus", () => (height.placeholder = `제안 ${suggestHeight()}`));
|
||||
height.addEventListener("focus", () => (height.placeholder = heightHint()));
|
||||
// 기준측점 전/후(2026-08-23 사용자) — 구조물 패널(D군)과 같은 연동:
|
||||
// 전 + 후 = 길이, 나중에 고친 쪽이 살아남고 반대쪽이 재배분된다.
|
||||
const before = keys.before && length ? numberInput("0.1", "0", `제안 ${defaults.before}`) : null;
|
||||
@@ -363,7 +393,7 @@ export function createRevetmentFields(
|
||||
form.value = text(keys.form);
|
||||
if (length && keys.length) length.value = text(keys.length);
|
||||
height.value = text(keys.height);
|
||||
height.placeholder = `제안 ${suggestHeight()}`;
|
||||
height.placeholder = heightHint();
|
||||
if (before && keys.before) before.value = text(keys.before);
|
||||
if (after && keys.after) after.value = text(keys.after);
|
||||
syncSplit("length");
|
||||
|
||||
@@ -67,6 +67,13 @@ function facilityNotes(section: CrossSection): Array<[string, string]> {
|
||||
`B05 시설 칸에 안 적어 기본값으로 그림 — ${items.join(" · ")} · 적으면 그 값으로 그림`,
|
||||
]);
|
||||
}
|
||||
const own = section.culvert?.hidden_pipe ? section.culvert : null;
|
||||
if (own && [own.inlet, own.outlet].some((side) => !side.revet_height_m)) {
|
||||
notes.push([
|
||||
"⚠ 기슭막이 높이 없음 — 근입만 그림",
|
||||
"독립 기슭막이 높이는 설계자 입력 — 계획홍수위 + 0.5~0.7m(사방기술교본 2-나:141 · 3-가:181) · 비우면 수량도 미확정 · B05 시설 칸에 적으면 그림",
|
||||
]);
|
||||
}
|
||||
if (section.ford_pavement && !section.ford_pavement.depth_m) {
|
||||
notes.push([
|
||||
"⚠ 월류 높이 없음 — 파임 안 그림",
|
||||
|
||||
@@ -26,6 +26,9 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
#: 배관으로 보는 `facility` 값. 그 밖(BOX암거·물넘이·세월교·독립 기슭막이)은 관이 아니다.
|
||||
@@ -214,9 +217,52 @@ REVET_ROLES = (
|
||||
NOTE_DEFAULT_WALL = (
|
||||
"⚠ 시설 지점에 {filled} 을 안 적어 기본값으로 섰음 — 미확정이라 금액에 안 들어감"
|
||||
)
|
||||
#: 꼬리 — 관 벽은 횡단도와 같은 높이 · 독립 기슭막이는 아직 갈림(관경이 없음).
|
||||
#: 꼬리 — 관 벽은 횡단도와 같은 높이.
|
||||
NOTE_WALL_HEIGHT_PIPE = " · 높이는 횡단도와 같은 관경 기준 최소 높이(실제 설계값은 더 클 수 있음)"
|
||||
NOTE_WALL_HEIGHT_OWN = " · ⚠ 횡단도는 저장 높이가 없으면 근입 0.5m 만 그림 — 표 높이와 다름"
|
||||
|
||||
#: 독립 기슭막이 치수 **한 벌**(`resources/data_masonry/revetment_sabang_*.json`) — B05 칸 밑 근거와
|
||||
#: 이 사유가 같은 파일을 읽음(2026-09-14 브레인 판정). 높이는 **설계자 입력** — 등록부 기본 2.5 걷음.
|
||||
REVET_SABANG_DIR = Path(__file__).resolve().parents[1] / "resources" / "data_masonry"
|
||||
REVET_SABANG_PREFIX = "revetment_sabang_"
|
||||
UNCONFIRMED_OWN_HEIGHT = "높이를 안 적음 — {rule}({source}) 설계자 입력 · 적으면 금액이 섬"
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def load_revetment_sabang() -> dict[str, Any]:
|
||||
"""독립 기슭막이 치수 한 벌. 없으면 빈 표 — 사유가 근거 없이 짧아질 뿐 값은 안 바뀜."""
|
||||
files = sorted(REVET_SABANG_DIR.glob(REVET_SABANG_PREFIX + "*.json"))
|
||||
return json.loads(files[-1].read_text(encoding="utf-8")) if files else {}
|
||||
|
||||
|
||||
def _span(values: list[float], unit: str = "") -> str:
|
||||
return f"{values[0]:g}~{values[1]:g}{unit}"
|
||||
|
||||
|
||||
def own_revetment_basis() -> str:
|
||||
"""독립 기슭막이 근거 한 줄 — 높이·계획비탈·둑마루·뒷채움, 쪽·줄 번호 그대로."""
|
||||
data = load_revetment_sabang()
|
||||
items = data.get("items") or {}
|
||||
if not items:
|
||||
return ""
|
||||
height, slope = items["height"], items["face_slope"]
|
||||
crown, pebble = items["crown_thickness"], items["backfill_pebble"]
|
||||
return (
|
||||
f"ⓘ 독립 기슭막이 치수 근거 — 높이 {height['rule']}({height['source']})"
|
||||
f" · 계획비탈 1:{_span(slope['standard_ratio'])}({slope['source']})"
|
||||
f" · 둑마루 두께 {_span(crown['standard_m'], 'm')}({crown['source']} · 콘크리트 기준)"
|
||||
f" · 뒷채움 조약돌 두께 {pebble['source']} · {data.get('scope', '')}"
|
||||
)
|
||||
|
||||
|
||||
def own_height_unconfirmed() -> str:
|
||||
"""높이를 안 적은 독립 기슭막이의 미확정 까닭."""
|
||||
height = (load_revetment_sabang().get("items") or {}).get("height") or {}
|
||||
return UNCONFIRMED_OWN_HEIGHT.format(
|
||||
rule=height.get("rule", "계획홍수위 + 0.5~0.7m"),
|
||||
source=height.get("source", "사방기술교본"),
|
||||
)
|
||||
|
||||
|
||||
#: 금액에서 뺄 까닭(2026-09-14 브레인 판정) — 줄·표·그림은 서되 내역 합에는 안 듦.
|
||||
UNCONFIRMED_WALL = "벽 치수({filled})가 시설 지점에 없어 기본값으로 섰음 — 적으면 금액이 섬"
|
||||
NOTE_SIDE_UNKNOWN = (
|
||||
@@ -330,8 +376,13 @@ def facility_structures(points: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
values, filled = _revet_values(options, role, wall_defaults, legacy, min_height)
|
||||
notes = list(inlet_notes) if role == "inlet" else []
|
||||
if filled:
|
||||
tail = NOTE_WALL_HEIGHT_OWN if legacy else NOTE_WALL_HEIGHT_PIPE
|
||||
tail = "" if legacy else NOTE_WALL_HEIGHT_PIPE
|
||||
notes.append(NOTE_DEFAULT_WALL.format(filled=" · ".join(filled)) + tail)
|
||||
if legacy:
|
||||
# 독립 기슭막이 — 치수 한 벌 근거(사방 교본 기준 · 쪽·줄 번호)를 늘 붙임.
|
||||
basis = own_revetment_basis()
|
||||
if basis:
|
||||
notes.append(basis)
|
||||
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 ("좌", "우"):
|
||||
@@ -345,13 +396,17 @@ def facility_structures(points: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
for role, label, values, notes, withheld, filled in walls:
|
||||
before, after = _num_or_zero(values["before_m"]), _num_or_zero(values["after_m"])
|
||||
row = child_row(base, "revetment", label, f"{role}_revet")
|
||||
reasons = [UNCONFIRMED_WALL.format(filled=" · ".join(filled))] if filled else []
|
||||
if legacy and _blank(values["height_m"]):
|
||||
# 높이는 설계자 입력(계획홍수위 + 여유고) — 없으면 줄만 서고 금액 밖.
|
||||
reasons.append(own_height_unconfirmed())
|
||||
row.update(
|
||||
start_m=chainage - before,
|
||||
end_m=chainage + after,
|
||||
options={**(kept if legacy else {}), **values, "foundation": foundation},
|
||||
notes=notes,
|
||||
withheld=withheld,
|
||||
unconfirmed=UNCONFIRMED_WALL.format(filled=" · ".join(filled)) if filled else "",
|
||||
unconfirmed=" · ".join(reasons),
|
||||
)
|
||||
rows.append(row)
|
||||
return rows
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"dataset_id": "revetment_sabang",
|
||||
"effective_date": "2026-01-01",
|
||||
"note": "독립 기슭막이 치수 **한 벌** — 높이 · 계획비탈 · 둑마루 두께 · 뒷채움 조약돌 두께를 한 자리에 둠(2026-09-14 브레인 판정). B05 칸 밑 근거와 B08 사유가 이 파일 하나를 같이 읽음.",
|
||||
"scope": "⚠ 사방(계류) 교본 기준 — 임도 배수관 유입·유출부 적용은 확인 남음",
|
||||
"rank": "품셈(산림청고시 제2025-82호)은 행정규칙 · 사방기술교본은 참고자료 — 제안값은 품셈 표, 교본은 근거로 병기(브레인 판정 ㉯ · 운영지침 「프로그램 기본값 = 현행 법령·행정규칙」)",
|
||||
"items": {
|
||||
"height": {
|
||||
"label": "높이",
|
||||
"rule": "계획홍수위 + 0.5~0.7m",
|
||||
"freeboard_m": [0.5, 0.7],
|
||||
"input": "설계자 입력 · 비우면 미확정(금액에 안 들어감)",
|
||||
"source": "사방기술교본 2-나:141 · 3-가:181",
|
||||
"quote": "기슭막이 높이는 계획홍수위 이상으로 하여 홍수시에도 넘치지 않도록 시공한다, 일반적으로 계획홍수위 보다 0.5~0.7m 높게 하며, 계류의 만곡부에서는 凹안이 凸안에 비해 수위가 높아지므로 더 높게 시공한다"
|
||||
},
|
||||
"face_slope": {
|
||||
"label": "계획비탈",
|
||||
"standard_ratio": [0.3, 0.5],
|
||||
"extended_ratio": [1.0, 1.5],
|
||||
"source": "사방기술교본 2-나:129 · 3-가:151",
|
||||
"quote": "기슭막이의 계획비탈은 1:0.3∼0.5를 표준으로 하고, 사용 재료와 현장 연건에 따라 1:1.0∼1.5 정도로 계획할 수 있다",
|
||||
"applied": "제안값은 품셈 13-4-4 [주]⑪ 표준경사(원문 L7185~7191) — 이 범위는 근거로만 보임 · 표 값이 범위 밖이면 사유만(교본을 이기려 들지 않음)"
|
||||
},
|
||||
"crown_thickness": {
|
||||
"label": "둑마루 두께",
|
||||
"standard_m": [0.3, 0.5],
|
||||
"source": "사방기술교본 2-나:143 · 3-가:201 · 3-가:219(콘크리트기슭막이) · 2-나:135(콘크리트 0.3m)",
|
||||
"quote": "기슭막이 둑마루 두께는 0.3∼0.5m를 표준으로 하고, 둑마루 기울기는 원칙적으로 계획계상기울기와 일치하도록 시공한다",
|
||||
"applied": "⚠ 콘크리트 기슭막이 기준 — 돌쌓기 상부 두께는 식(뒷길이 + 0.30)으로 따로 셈 · 식을 안 바꾸고 근거로만(브레인 판정 ㉱)"
|
||||
},
|
||||
"backfill_pebble": {
|
||||
"label": "뒷채움 조약돌 두께",
|
||||
"source": "사방기술교본 2-나:133(「사방품셈 규정에 의하여」) → 품셈 13-4-4 [주]⑨ 원문 L7164~7171",
|
||||
"steps_m": [1.5, 3.0, 5.0, 7.0],
|
||||
"fill_cm": {
|
||||
"top": [20, 40],
|
||||
"bottom": [[30, 60], [45, 75], [60, 100], [80, 140]]
|
||||
},
|
||||
"cut_cm": [30, 40],
|
||||
"applied": "막자갈은 정본 뒷채움 폭(상 0.30 · 하 0.45)으로 셈 — 이 표는 근거로만 보임(`STONE_BACKFILL_WIDTH_M` 주석의 갈림)"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""독립 기슭막이 치수 한 벌 — B05 칸 밑 근거와 B08 사유가 **같은 파일**을 읽음 (2026-09-14 브레인 판정).
|
||||
|
||||
한 자리 = `resources/data_masonry/revetment_sabang_*.json`. TS 는 판 이름을 import 로 박으므로 판이
|
||||
바뀌면 import 도 옮겨야 함 — 여기서 최신 판과 대조. 쪽·줄 번호가 사유에 그대로 실리는지도 봄.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
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 B08_Quantity.B08_Quantity_Engine_Pipe import ( # noqa: E402
|
||||
own_height_unconfirmed,
|
||||
own_revetment_basis,
|
||||
)
|
||||
|
||||
DATA_DIR = ROOT / "resources" / "data_masonry"
|
||||
FIELDS = (ROOT / "B05_Profile" / "B05_Profile_UI_Drainage_Facility_Fields.ts").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
CHROME = (ROOT / "B06_Section" / "B06_Section_UI_Cross_Card_Chrome.ts").read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def test_TS_가_최신_판을_읽는다() -> None:
|
||||
latest = sorted(DATA_DIR.glob("revetment_sabang_*.json"))[-1].name
|
||||
imported = re.search(r'from "\.\./resources/data_masonry/(revetment_sabang_[^"]+)"', FIELDS)
|
||||
assert imported and imported.group(1) == latest
|
||||
|
||||
|
||||
def test_TS_가_쓰는_칸이_파일에_있다() -> None:
|
||||
latest = sorted(DATA_DIR.glob("revetment_sabang_*.json"))[-1]
|
||||
data = json.loads(latest.read_text(encoding="utf-8"))
|
||||
items = data["items"]
|
||||
assert data["scope"] and "사방(계류) 교본 기준" in data["scope"]
|
||||
assert items["height"]["rule"] == "계획홍수위 + 0.5~0.7m"
|
||||
assert items["face_slope"]["standard_ratio"] == [0.3, 0.5]
|
||||
assert items["crown_thickness"]["standard_m"] == [0.3, 0.5]
|
||||
assert "L7164" in items["backfill_pebble"]["source"]
|
||||
|
||||
|
||||
def test_B08_사유가_같은_근거를_쪽_줄_번호로() -> None:
|
||||
basis = own_revetment_basis()
|
||||
for ref in ("2-나:141", "3-가:181", "2-나:129", "3-가:151", "2-나:143", "L7164~7171"):
|
||||
assert ref in basis, ref
|
||||
assert "2-나:141" in own_height_unconfirmed()
|
||||
|
||||
|
||||
def test_B05_높이_칸은_제안_없이_설계자_입력() -> None:
|
||||
assert 'height: "",' in FIELDS and '"설계자 입력"' in FIELDS
|
||||
assert "기슭막이 높이 없음 — 근입만 그림" in CHROME
|
||||
@@ -68,7 +68,8 @@ def test_client_format_gisungmagi_maps_to_revetment():
|
||||
# 기준측점 전/후 5m — 레지스트리 기본값.
|
||||
assert (item.start_m, item.end_m) == (115.0, 125.0)
|
||||
assert item.options["form"] == "돌쌓기(메)"
|
||||
assert item.options["height_m"] == 2.5 and item.options["length_m"] == 10
|
||||
# 높이는 설계자 입력 — 등록부 기본 2.5 걷음(2026-09-14 브레인 판정 · 사방기술교본 2-나:141)
|
||||
assert "height_m" not in item.options and item.options["length_m"] == 10
|
||||
|
||||
|
||||
def test_client_format_escape_route_width_field_wins():
|
||||
|
||||
@@ -485,7 +485,9 @@ def test_revetment_range_options_like_slope_group():
|
||||
pipe_options = {option.key: option for option in by_id["pipe"].options}
|
||||
|
||||
assert item.placement == "point" and item.managed_by == "pipe_points"
|
||||
expected = {"height_m": 2.5, "length_m": 10, "before_m": 5, "after_m": 5}
|
||||
# ⚠ 높이는 2026-09-14 부터 **설계자 입력**(기본 없음) — 계획홍수위 + 0.5~0.7m(사방기술교본
|
||||
# 2-나:141 · 3-가:181) · 옛 2.5 는 근거 없어 걷음(브레인 판정).
|
||||
expected = {"height_m": None, "length_m": 10, "before_m": 5, "after_m": 5}
|
||||
for key, default in expected.items():
|
||||
assert key in options, f"revetment.{key} 옵션 없음"
|
||||
assert options[key].phase == "b05" and not options[key].required, key
|
||||
|
||||
@@ -247,7 +247,8 @@ def test_migrate_converts_legacy_stations(client, tmp_path):
|
||||
assert pipe["facility"] == "revetment" and pipe["chainage_m"] == 120.0
|
||||
# 구간·제원은 레지스트리 기본값 승계 — 기준측점 전/후 5m.
|
||||
assert (pipe["start_m"], pipe["end_m"]) == (115.0, 125.0)
|
||||
assert pipe["options"]["form"] == "돌쌓기(메)" and pipe["options"]["height_m"] == 2.5
|
||||
# 높이는 설계자 입력 — 등록부 기본 2.5 걷음(2026-09-14 브레인 판정)
|
||||
assert pipe["options"]["form"] == "돌쌓기(메)" and "height_m" not in pipe["options"]
|
||||
|
||||
|
||||
def test_migrate_without_pipe_points_defers_revetment(client):
|
||||
|
||||
@@ -95,7 +95,7 @@ def test_유입구가_기슭막이인데_집수정_형식이_남아_있으면_
|
||||
|
||||
|
||||
def test_독립_기슭막이_한쪽이면_두_칸이_같을_때만_세고_다르면_사유() -> None:
|
||||
same = {"facility": "revetment", "chainage_m": 50.0, "options": {"side": "좌"}}
|
||||
same = {"facility": "revetment", "chainage_m": 50.0, "options": {"side": "좌", "height_m": 2.0}}
|
||||
rows = _table([same])["structures"]
|
||||
assert [r["name"] for r in rows] == ["기슭막이 · 좌 벽"] and rows[0]["components"]
|
||||
differ = {**same, "options": {"side": "우", "inlet_revet_height_m": 3.0}}
|
||||
@@ -103,6 +103,17 @@ def test_독립_기슭막이_한쪽이면_두_칸이_같을_때만_세고_다르
|
||||
assert not row["components"] and "서버가 못 가림" in row["notes"][-1]
|
||||
|
||||
|
||||
def test_독립_기슭막이_높이는_설계자_입력_없으면_미확정과_사방_근거() -> None:
|
||||
"""2026-09-14 브레인 판정 — 높이 = 계획홍수위 + 0.5~0.7m(사방기술교본 2-나:141 · 3-가:181)."""
|
||||
point = {"facility": "revetment", "chainage_m": 50.0, "options": {"side": "좌"}}
|
||||
(row,) = _table([point])["structures"]
|
||||
assert "높이를 안 적음" in row["unconfirmed"] and "2-나:141" in row["unconfirmed"]
|
||||
assert any("사방(계류) 교본 기준" in note and "3-가:151" in note for note in row["notes"])
|
||||
with_height = {**point, "options": {"side": "좌", "height_m": 2.0, "form": "돌쌓기(메)"}}
|
||||
(row,) = _table([with_height])["structures"]
|
||||
assert "높이를 안 적음" not in row["unconfirmed"]
|
||||
|
||||
|
||||
def test_산출식_없는_세월교는_사유로_서고_터파기_빠짐_줄을_안_만든다() -> None:
|
||||
table = _table([{"facility": "ford_bridge", "chainage_m": 30.0, "options": {}}])
|
||||
assert "산출식이 아직 없습니다" in table["structures"][0]["notes"][0]
|
||||
|
||||
Reference in New Issue
Block a user