feat(B08): 벽 터파기를 공용 단면 함수로 교체 — 기초 유/무 축 반영

- 터파기·되메우기·잔토를 `common_util_excavation.wall_trench_area_m2` 한 벌로
  옮김(화면 B06 과 짝인 TS 가 같은 값을 냄) — 돌쌓기·큰돌쌓기 공용 `_trench_components`
- `options.foundation` 「기초유」 = 기초분 0.45㎥/m · 「기초버림」 = 0.07㎥/m
- 안 고른 프로젝트는 비탈분만 세우고 사유를 냄 — 한쪽으로 찍으면 0.45 가 임의로 굳음
- 되메우기 = (기초깊이 + H) × 0.2 × 연장 · 잔토 = 터파기 − 되메우기
- 터파기 여유폭·되메우기 두께 상수를 엔진에서 지우고 공용 모듈을 가리키게 함
- tmp/tests/test_b08_wall_trench.py 신설(7건)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-08 21:37:18 +09:00
co-authored by Claude Opus 5
parent 09a5eee338
commit b9c9490767
@@ -38,6 +38,14 @@ from dataclasses import dataclass, field
from typing import Any, Iterable from typing import Any, Iterable
from common_util.common_util_structure_face_role import structure_face_role from common_util.common_util_structure_face_role import structure_face_role
from common_util.common_util_excavation import (
BASIS_WALL,
WALL_BLINDING_DEPTH_M,
WALL_FOUNDATION_DEPTH_M,
WALL_TRENCH_CLEARANCE_M,
wall_trench_area_m2,
wall_trench_width_m,
)
from B08_Quantity.B08_Quantity_Engine_ObservedUnit import ( from B08_Quantity.B08_Quantity_Engine_ObservedUnit import (
BASIS_DERIVED, BASIS_DERIVED,
BASIS_OBSERVED, BASIS_OBSERVED,
@@ -159,8 +167,9 @@ STONE_MASONRY = {
# 적어 둔다 — 관측 원단위가 있는 종류는 그 표가 이기고, 없는 종류만 이 값으로 선다. # 적어 둔다 — 관측 원단위가 있는 종류는 그 표가 이기고, 없는 종류만 이 값으로 선다.
"weep_hole_length_m": 0.5, # 물구멍관 1개소당 관 길이 "weep_hole_length_m": 0.5, # 물구멍관 1개소당 관 길이
"mortar_m3_per_m2": 0.009, # 줄눈 모르터 (찰쌓기만) "mortar_m3_per_m2": 0.009, # 줄눈 모르터 (찰쌓기만)
"excavation_extra_m": 0.2, # 터파기 여유 # 터파기 여유폭·되메우기 두께는 **공용 한 벌**로 옮겼다
"backfill_thickness_m": 0.2, # 되메우기 두께 # (`common_util/common_util_excavation.py` — 화면 B06 과 짝인 TS 가 있다).
# 여기서 다시 두면 그림과 물량이 갈린다.
} }
# 성분이 어디로 가는가 — 이중계상을 막는 표시. # 성분이 어디로 가는가 — 이중계상을 막는 표시.
@@ -427,8 +436,9 @@ BOULDER_MASONRY = {
# ⚠ 검산 참고값 — 곱하지 않는다. 까닭은 `STONE_MASONRY` 의 같은 칸 주석을 볼 것. # ⚠ 검산 참고값 — 곱하지 않는다. 까닭은 `STONE_MASONRY` 의 같은 칸 주석을 볼 것.
# 큰돌쌓기는 전면 기울기가 「1:0.3 **이상**」이라 특히 하드코딩하면 안 된다. # 큰돌쌓기는 전면 기울기가 「1:0.3 **이상**」이라 특히 하드코딩하면 안 된다.
"sheet_check_factor_at_0_3": 1.04, "sheet_check_factor_at_0_3": 1.04,
"excavation_extra_m": 0.2, # 터파기 여유 # 터파기 여유폭·되메우기 두께는 **공용 한 벌**로 옮겼다
"backfill_thickness_m": 0.2, # 되메우기 두께 # (`common_util/common_util_excavation.py` — 화면 B06 과 짝인 TS 가 있다).
# 여기서 다시 두면 그림과 물량이 갈린다.
} }
@@ -469,7 +479,6 @@ def boulder_masonry(
# ⚠ 큰돌쌓기는 **표준경사표 대상이 아니다** — 교본이 「1:0.3 **이상**」으로만 두었다. # ⚠ 큰돌쌓기는 **표준경사표 대상이 아니다** — 교본이 「1:0.3 **이상**」으로만 두었다.
# 저장 제원에 값이 있으면 그것을 쓰고, 없으면 하한 0.3 으로 둔다. # 저장 제원에 값이 있으면 그것을 쓰고, 없으면 하한 0.3 으로 둔다.
slope_ratio = _num(options.get("face_slope_ratio"), 0.3) slope_ratio = _num(options.get("face_slope_ratio"), 0.3)
constants = BOULDER_MASONRY
face_area = height_m * length_m face_area = height_m * length_m
# 기울기 몫은 **한 번만** — 13-4 와 같은 자리다(㉘). # 기울기 몫은 **한 번만** — 13-4 와 같은 자리다(㉘).
masonry_area = face_area * math.hypot(1.0, slope_ratio) masonry_area = face_area * math.hypot(1.0, slope_ratio)
@@ -490,30 +499,18 @@ def boulder_masonry(
upper_cm = float(str(diameter).split("~")[-1]) upper_cm = float(str(diameter).split("~")[-1])
thickness = upper_cm / 100.0 thickness = upper_cm / 100.0
# 버림 콘크리트 — 돌쌓기와 같은 자리(사용자 확정 ⑭). 빼려면 저장 제원에서 「안 넣음」. # 버림 콘크리트 — 돌쌓기와 같은 자리(사용자 확정 ⑭). 빼려면 저장 제원에서 「안 넣음」.
blinding = _blinding_component(thickness + constants["excavation_extra_m"], length_m, options) blinding = _blinding_component(wall_trench_width_m(thickness) or thickness, length_m, options)
if blinding is not None: if blinding is not None:
components.append(blinding) components.append(blinding)
excavation = height_m * (thickness + constants["excavation_extra_m"]) * length_m
backfill = height_m * constants["backfill_thickness_m"] * length_m
components.extend( components.extend(
[ _trench_components(
Component( height_m,
"터파기", length_m,
"", thickness,
excavation, options,
DESTINATION["터파기"], notes,
f"높이 × (벽두께 {thickness:g}m + 0.2) × 연장 · ⚠ 큰돌쌓기 터파기 폭 규정이" "⚠ 큰돌쌓기 터파기 폭 규정이 품셈에 없어 돌쌓기 방식을 준용",
" 품셈에 없어 돌쌓기 방식을 준용", )
),
Component("되메우기", "", backfill, DESTINATION["되메우기"], "높이 × 0.2 × 연장"),
Component(
"잔토처리",
"",
excavation - backfill,
DESTINATION["잔토처리"],
"터파기 되메우기",
),
]
) )
notes.append( notes.append(
@@ -534,6 +531,66 @@ def boulder_masonry(
return components, notes return components, notes
def _trench_components(
height_m: float,
length_m: float,
thickness: float,
options: dict[str, Any],
notes: list[str],
extra_basis: str = "",
) -> list[Component]:
"""터파기·되메우기·잔토 — **공용 단면 함수 한 벌**(`common_util_excavation`).
⚠ 여기서 식을 다시 짜지 않는다 — 화면(B06 횡단도)이 그리는 단면과 같은 코드라야
그림과 물량이 안 갈린다. 평균두께는 이쪽이 낸 값을 **넘겨 준다**(저쪽은 두께를 모름).
⚠ 「기초유 / 기초버림」은 정본 탭 제목이고 **기초분 0.45 냐 0.07 이냐**를 가른다.
안 고른 프로젝트는 **비탈분만** 세우고 사유를 남긴다 — 한쪽으로 찍으면 임의값이
금액으로 굳는다(기초분이 통째로 0.45㎥/m 다).
"""
foundation = str(options.get("foundation") or "").strip()
if foundation in ("기초유", "기초버림"):
has_foundation = foundation == "기초유"
area = wall_trench_area_m2(height_m, thickness, has_foundation=has_foundation)
base_depth = WALL_FOUNDATION_DEPTH_M if has_foundation else WALL_BLINDING_DEPTH_M
base_basis = f"기초분({foundation}) + "
else:
width = wall_trench_width_m(thickness)
area = None if width is None else height_m * width
base_depth = 0.0
base_basis = ""
notes.append(
"⚠ 기초 유/무(기초유·기초버림)를 안 정해 **기초 터파기 몫이 안 섰음** — 비탈분만 세움"
)
if area is None:
notes.append("⚠ 높이·평균두께가 없어 터파기가 서지 않음")
return []
excavation = area * length_m
backfill = (base_depth + height_m) * WALL_TRENCH_CLEARANCE_M * length_m
basis = (
f"{base_basis}H × (평균두께 {thickness:.2f} + {WALL_TRENCH_CLEARANCE_M}) × 연장"
f" · {BASIS_WALL}"
)
if extra_basis:
basis = f"{basis} · {extra_basis}"
return [
Component("터파기", "", excavation, DESTINATION["터파기"], basis),
Component(
"되메우기",
"",
backfill,
DESTINATION["되메우기"],
f"(기초깊이 {base_depth:g} + H) × {WALL_TRENCH_CLEARANCE_M} × 연장",
),
Component(
"잔토처리",
"",
excavation - backfill,
DESTINATION["잔토처리"],
"터파기 되메우기",
),
]
def stone_masonry( def stone_masonry(
height_m: float, height_m: float,
length_m: float, length_m: float,
@@ -732,32 +789,14 @@ def stone_masonry(
) )
# 버림 콘크리트 — 기초 바닥에 까는 얇은 층. 빼려면 저장 제원에서 「안 넣음」으로 둔다. # 버림 콘크리트 — 기초 바닥에 까는 얇은 층. 빼려면 저장 제원에서 「안 넣음」으로 둔다.
base_width = thickness + constants["excavation_extra_m"] base_width = wall_trench_width_m(thickness) or thickness
blinding = _blinding_component(base_width, length_m, options) blinding = _blinding_component(base_width, length_m, options)
if blinding is not None: if blinding is not None:
components.append(blinding) components.append(blinding)
# 터파기·되메우기·잔토 — 토공으로 합산되는 값이다(내역 줄이 아니다). # 터파기·되메우기·잔토 — 토공으로 합산되는 값이다(내역 줄이 아니다).
excavation = height_m * (thickness + constants["excavation_extra_m"]) * length_m
backfill = height_m * constants["backfill_thickness_m"] * length_m
components.extend( components.extend(
[ _trench_components(height_m, length_m, thickness, options, notes, thickness_basis)
Component(
"터파기",
"",
excavation,
DESTINATION["터파기"],
f"높이 × (평균두께 {thickness:.2f}+0.2) × 연장 · {thickness_basis}",
),
Component("되메우기", "", backfill, DESTINATION["되메우기"], "높이 × 0.2 × 연장"),
Component(
"잔토처리",
"",
excavation - backfill,
DESTINATION["잔토처리"],
"터파기 되메우기",
),
]
) )
return components, notes return components, notes