Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016VBGFXB9AbJBwXP19z75Qq
533 lines
26 KiB
Python
533 lines
26 KiB
Python
"""구조물 원단위 — **돌쌓기·큰돌쌓기 전개식**(버림·터파기 줄 포함) · 2026-09-14 가름.
|
||
|
||
⚠ 식을 발명하지 않음 — 실무 정본 식을 옮긴 자리(`UnitQuantity` 머리글). 계수·판정은 `_StoneSpec`.
|
||
⚠ 터파기·되메우기·잔토는 **토공으로 가는 줄**(`DESTINATION`) — 내역 줄로 두 번 세지 않음.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import math
|
||
from typing import Any
|
||
|
||
from B08_Quantity.B08_Quantity_Engine_UnitQuantity_Base import DESTINATION, Component, _num
|
||
from B08_Quantity.B08_Quantity_Engine_UnitQuantity_StoneSpec import (
|
||
BLINDING_THICKNESS_M,
|
||
STONE_BACK_LENGTH_TABLE,
|
||
STONE_BACKFILL_VERIFIED_HEIGHT_M,
|
||
STONE_BACKFILL_WIDTH_M,
|
||
STONE_KIND_OPTION,
|
||
STONE_MASONRY,
|
||
STONE_PACKING_RATIO,
|
||
STONE_PILE_BASIS,
|
||
_back_length,
|
||
back_length_default_note,
|
||
dry_masonry_height_note,
|
||
face_slope_ratio,
|
||
fill_concrete_mpa,
|
||
STONE_SUPPLY_DEFAULT_NOTE,
|
||
STONE_SUPPLY_KEYS,
|
||
is_collected_stone,
|
||
load_stone_kind_table,
|
||
stone_coefficients,
|
||
stone_weight_per_m2,
|
||
wall_thickness,
|
||
wants_blinding,
|
||
weep_hole_spec,
|
||
)
|
||
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,
|
||
)
|
||
|
||
|
||
def _blinding_component(
|
||
base_width_m: float,
|
||
length_m: float,
|
||
options: dict[str, Any],
|
||
width_basis: str = "",
|
||
):
|
||
"""버림 콘크리트 한 줄. 넣지 않기로 했으면 `None`.
|
||
|
||
폭은 **잡석다짐 폭**(KCS 34 50 05)이고, 그 값은 확정 ⑪ 로 **구조물 하단 길이**다.
|
||
부르는 쪽이 그 폭과 까닭(`width_basis`)을 함께 넘긴다.
|
||
"""
|
||
if not wants_blinding(options) or base_width_m <= 0 or length_m <= 0:
|
||
return None
|
||
basis = (
|
||
f"기초 폭 {base_width_m:.2f}m × 연장 × 두께 {BLINDING_THICKNESS_M:.2f}m"
|
||
" · 두께는 KDS 44 90 00(100㎜) · 폭은 잡석다짐 폭(KCS 34 50 05)"
|
||
)
|
||
if width_basis:
|
||
basis = f"{basis} = {width_basis}"
|
||
return Component(
|
||
"버림콘크리트",
|
||
"㎥",
|
||
base_width_m * length_m * BLINDING_THICKNESS_M,
|
||
DESTINATION["버림콘크리트"],
|
||
basis,
|
||
)
|
||
|
||
|
||
#: 큰돌쌓기(품셈 13-6) 직경 갈래 — **저장 제원 `stone_cm` 과 글자까지 같다**(레지스트리 확인).
|
||
#: ⚠ 돌쌓기(13-4)의 **뒷길이** 축과 섞지 않는다. 앞서 섞여 있어 직경 60~80 짜리가
|
||
#: 「뒷길이 45㎝」 계수로 돌던 자리다.
|
||
BOULDER_DIAMETERS = ("40~60", "60~80", "80~100")
|
||
|
||
#: 큰돌쌓기 전개 상수. **재료 원단위는 품셈에 없다** — 13-6 [주]⑦ 「재료량은 설계수량을 적용한다」.
|
||
#: 그래서 여기서 내는 것은 **면적과 터파기 계열까지**이고 큰돌 자체는 미확보로 둔다.
|
||
BOULDER_MASONRY = {
|
||
# ⚠ 검산 참고값 — 곱하지 않는다. 까닭은 `STONE_MASONRY` 의 같은 칸 주석을 볼 것.
|
||
# 큰돌쌓기는 전면 기울기가 「1:0.3 **이상**」이라 특히 하드코딩하면 안 된다.
|
||
"sheet_check_factor_at_0_3": 1.04,
|
||
# ⚠ 터파기 여유폭·되메우기 두께는 **공용 한 벌**로 옮겼다
|
||
# (`common_util/common_util_excavation.py` — 화면 B06 과 짝인 TS 가 있다).
|
||
# 여기서 다시 두면 그림과 물량이 갈린다.
|
||
}
|
||
|
||
|
||
def _boulder_diameter(options: dict[str, Any]) -> str | None:
|
||
"""저장 제원의 직경 갈래. 표에 없는 값이면 **지어내지 않고 `None`**."""
|
||
raw = options.get("stone_cm")
|
||
text = str(raw).strip() if raw is not None else ""
|
||
return text if text in BOULDER_DIAMETERS else None
|
||
|
||
|
||
def boulder_masonry(
|
||
height_m: float, length_m: float, options: dict[str, Any]
|
||
) -> tuple[list[Component], list[str]]:
|
||
"""큰돌쌓기(품셈 13-6) 전개 — **치수로 낼 수 있는 것까지만** 낸다.
|
||
|
||
⚠ 재료 원단위가 품셈에 없다 — 13-6 [주]⑦ 「재료량은 설계수량을 적용한다」.
|
||
돌쌓기(13-4)처럼 「뒷길이별 돌중량·고임돌」 표가 **없으므로** 큰돌·고임돌·막자갈을
|
||
내지 않는다. 대신 **왜 못 내는지**를 알림으로 남긴다.
|
||
|
||
⚠ 고임돌·채움콘크리트 품은 **품에 포함**돼 있다(13-6-1 [주]①·13-6-2 [주]①) —
|
||
따로 세우면 이중계상이다.
|
||
|
||
⚠ 뒤채움(조약돌)은 **13-3 을 적용**한다(13-6 [주]⑧) — 이 표에서 찾지 않는다.
|
||
두께는 13-4-3 [주]⑨ 를 준용하는데 **직고별 범위값**(상부 20~40㎝ / 하부 30~140㎝)이라
|
||
한 값으로 못 정한다. 미확보로 둔다.
|
||
"""
|
||
notes: list[str] = []
|
||
if height_m <= 0 or length_m <= 0:
|
||
return [], ["높이·연장이 없어 전개하지 않음"]
|
||
|
||
diameter = _boulder_diameter(options)
|
||
if diameter is None:
|
||
return [], [
|
||
"큰돌쌓기 돌 직경이 아직 입력되지 않았습니다 — 구조물 상세 입력에서 "
|
||
f"{' · '.join(BOULDER_DIAMETERS)}㎝ 중 하나를 고르면 값이 섭니다"
|
||
]
|
||
|
||
# ⚠ 큰돌쌓기는 **표준경사표 대상이 아니다** — 교본이 「1:0.3 **이상**」으로만 두었다.
|
||
# 저장 제원에 값이 있으면 그것을 쓰고, 없으면 하한 0.3 으로 둔다.
|
||
slope_ratio = _num(options.get("face_slope_ratio"), 0.3)
|
||
face_area = height_m * length_m
|
||
# 기울기 몫은 **한 번만** — 13-4 와 같은 자리다(㉘).
|
||
masonry_area = face_area * math.hypot(1.0, slope_ratio)
|
||
|
||
components = [
|
||
Component(
|
||
"큰돌쌓기",
|
||
"㎡",
|
||
masonry_area,
|
||
DESTINATION["돌쌓기"],
|
||
f"정면적 × √(1+{slope_ratio}²) · 직경 {diameter}㎝ (품셈 13-6)",
|
||
)
|
||
]
|
||
|
||
# 터파기·되메우기·잔토 — 치수에서 나온다. 두께는 **직경 갈래의 위 끝**을 벽 두께로 본다.
|
||
# ⚠ 품셈에 큰돌쌓기 터파기 폭 규정이 없어 **돌쌓기와 같은 방식**(벽 두께 + 여유 0.2m)으로
|
||
# 낸다. 근거 문구에 그 사실을 적어 되짚을 수 있게 한다.
|
||
upper_cm = float(str(diameter).split("~")[-1])
|
||
thickness = upper_cm / 100.0
|
||
# 버림 콘크리트 — 돌쌓기와 같은 자리(사용자 확정 ⑭). 빼려면 저장 제원에서 「안 넣음」.
|
||
blinding = _blinding_component(
|
||
thickness,
|
||
length_m,
|
||
options,
|
||
f"하단 길이(직경 위끝) {thickness:.2f}m — 확정 ⑪ 준용",
|
||
)
|
||
if blinding is not None:
|
||
components.append(blinding)
|
||
components.extend(
|
||
_trench_components(
|
||
height_m,
|
||
length_m,
|
||
thickness,
|
||
options,
|
||
notes,
|
||
"⚠ 큰돌쌓기 터파기 폭 규정이 품셈에 없어 돌쌓기 방식을 준용",
|
||
)
|
||
)
|
||
|
||
notes.append(
|
||
"재료(큰돌) 원단위 미확보 — 품셈 13-6 [주]⑦ 「재료량은 설계수량을 적용한다」라 "
|
||
"돌쌓기(13-4)의 뒷길이별 돌중량 표에 해당하는 것이 없음"
|
||
)
|
||
notes.append("고임돌·채움콘크리트는 **품에 포함**(13-6 [주]①) — 따로 세우지 않음")
|
||
notes.append(
|
||
"뒤채움(조약돌)은 13-3 적용(13-6 [주]⑧). 두께는 13-4-3 [주]⑨ 준용인데 "
|
||
"직고별 범위값(상부 20~40㎝ / 하부 30~140㎝)이라 한 값으로 못 정함 — 미확보"
|
||
)
|
||
# ⚠ **채워져 있으면 그 사유를 붙이지 않는다**(2026-09-09). `bond` 는 레지스트리에 이미
|
||
# 있는 칸인데 조건 없이 붙이고 있어, 값을 고른 뒤에도 「못 고름」이 뜨고 있었다.
|
||
# ⚠ 얻는 것은 **노무 품 갈래뿐**이고 재료 원단위는 13-6 [주]⑦ 때문에 여전히 안 선다 —
|
||
# 그래도 고치는 까닭은 **사유가 거짓이면 사유 칸 전체를 못 믿게 되기 때문**이다.
|
||
if not str(options.get("bond") or "").strip():
|
||
notes.append("⚠ 메/찰 구분이 저장 제원에 없어 13-6-1(메)·13-6-2(찰) 중 어느 쪽인지 못 고름")
|
||
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(
|
||
height_m: float,
|
||
length_m: float,
|
||
options: dict[str, Any],
|
||
wet: bool,
|
||
face: str | None = None,
|
||
face_reason: str = "",
|
||
) -> tuple[list[Component], list[str]]:
|
||
"""돌쌓기(찰/메) 1구간 전개 — 실무 `기슭막이(찰쌓기, H=1.5, 기초무)` 시트의 식.
|
||
|
||
실측 대조(m당, H=1.5, 뒷길이 45㎝, 기울기 1:0.3):
|
||
정면적 1.5 · 비탈면적 1.57 · 평균두께 0.83 · 입적 1.245
|
||
터파기 1.55 · 되메우기 0.30 · 잔토 1.25
|
||
"""
|
||
notes: list[str] = []
|
||
if height_m <= 0 or length_m <= 0:
|
||
return [], ["높이·연장이 없어 전개하지 않음"]
|
||
|
||
back_cm = _back_length(options, wet=wet, height_m=height_m)
|
||
if back_cm not in STONE_BACK_LENGTH_TABLE:
|
||
# ⚠ 접지 않는다 — 다른 규격 계수가 조용히 도는 것보다 「없다」가 낫다.
|
||
return [], [
|
||
f"뒷길이 {back_cm}㎝ 는 품셈 표(25·30·35·45·55·60·75㎝)에 없어 물량이 서지 않습니다"
|
||
]
|
||
table = dict(STONE_BACK_LENGTH_TABLE[back_cm])
|
||
default_note = back_length_default_note(options, back_cm, wet=wet, height_m=height_m)
|
||
if default_note:
|
||
notes.append(default_note)
|
||
height_note = None if wet else dry_masonry_height_note(height_m)
|
||
if height_note:
|
||
notes.append(height_note)
|
||
# ⚠ **돌 종류로 계수가 갈린다** (품셈 13-4-3 · 13-4-4 [주]① · 교본 7-3).
|
||
# 안 고르면 종전 값(건설품셈 참고자료)으로 돌되 그 사실을 알린다 — 값을 못 낸다고
|
||
# 멈추면 이미 저장된 프로젝트가 통째로 빈다.
|
||
picked, kind_note = stone_coefficients(options, back_cm)
|
||
if kind_note:
|
||
notes.append(kind_note)
|
||
# ⚠ **고른 종류의 빈 칸은 빈 칸으로 덮는다.** `None` 이라고 안 덮으면 종전 값(깬돌 계열)이
|
||
# 남아 「야면석 75㎝」처럼 **원문에 「-」인 칸에 값이 서는** 일이 생긴다(만들다 잡음).
|
||
if picked.get("kind"):
|
||
for key in ("wedge_stone_m3_per_m2", "fill_concrete_m3_per_m2"):
|
||
table[key] = picked.get(key)
|
||
else:
|
||
for key in ("wedge_stone_m3_per_m2", "fill_concrete_m3_per_m2"):
|
||
if picked.get(key) is not None:
|
||
table[key] = picked[key]
|
||
# ⚠ **교본의 「뒤채움 몫」(깬돌·잡석 1/2 · 야면석 1/3)은 이제 막자갈 밑수가 아니다** —
|
||
# ⭐ 확정 5차 작은 것 3 이 막자갈을 **뒷채움 폭 사다리꼴**로 못 박았다. 표는 그대로
|
||
# 두되(다른 자리에서 쓸 수 있다) 여기서는 안 쓴다.
|
||
kind_label = str(picked.get("kind") or "")
|
||
# ⭐ 2026-09-13 브레인 판정 — **계수 열 고르기와 돌종류는 다른 축.** 「실무 관행」은 계수 열만
|
||
# 바꾸고 종류는 지워선 안 됨(야면석이 「돌」·계산식 무게로 조용히 서던 결함). 돌 규격·무게는
|
||
# **저장 제원의 종류**를 따름 — 아는 종류일 때만(모르는 글은 종전대로 규격 빈칸).
|
||
chosen_kind = str(options.get(STONE_KIND_OPTION) or "").strip()
|
||
stone_kind_name = (
|
||
chosen_kind if chosen_kind in (load_stone_kind_table().get("kinds") or []) else kind_label
|
||
)
|
||
# ⚠ `face_slope_ratio` 는 **2026-09-09 에 칸이 생겼다**(돌쌓기 계열 여섯 종류 · 표준도
|
||
# 제원 폼). 빈 값이 「자동」의 뜻이라 비어 있으면 아래 표준경사표가 돌고, 채우면 그 값이
|
||
# 이긴다. (키 이름 어긋남으로 저장값이 안 닿던 `back_len_cm` 사고와 구별할 것.)
|
||
# **기본 0.3 의 근거** — 교본 7-3 돌흙막이 기준: 「돌 찰쌓기 3.0m 이하 **1:0.3** /
|
||
# 돌 메쌓기 2.0m 이하 **1:0.3** / 큰돌쌓기 **1:0.3 이상**(전도 방지)」
|
||
# (`지식DB 02_상세설계/구조물/돌쌓기.md §1`, 값은 `data_masonry` 의 `face_slope`).
|
||
# 전면 기울기 — 품셈 표준경사표로 자동 판정하고, 저장 제원에 값이 있으면 그것이 이긴다.
|
||
slope_ratio, slope_basis = face_slope_ratio(
|
||
options, wet=wet, height_m=height_m, face=face, face_reason=face_reason
|
||
)
|
||
constants = STONE_MASONRY
|
||
|
||
face_area = height_m * length_m # 정면적
|
||
# 돌쌓기 면적 = 정면적 × √(1+n²) — 기울어진 만큼 길어진다. **기울기 몫은 한 번만.**
|
||
# 실무 시트의 「정면적 × 1.04」가 바로 이 값이다(1:0.3 에서 1.0440 ≈ 1.04).
|
||
masonry_area = face_area * math.hypot(1.0, slope_ratio)
|
||
# 벽 두께 — 실무 구조물도 식(확정 2차 ②). **뒷길이가 들어간다.**
|
||
# ⚠ 표준도 제원에 상부·하부 두께 칸이 있으면 **그 값이 식을 이긴다**(확정 ② 후반).
|
||
back_m = back_cm / 100.0 # 석적(아래)이 뒷길이를 m 로 쓴다
|
||
top_thickness, bottom_thickness, thickness_basis = wall_thickness(
|
||
options, back_cm=back_cm, height_m=height_m
|
||
)
|
||
thickness = (top_thickness + bottom_thickness) / 2.0
|
||
volume = face_area * thickness # 입적
|
||
|
||
components = [
|
||
Component(
|
||
"돌쌓기",
|
||
"㎡",
|
||
masonry_area,
|
||
DESTINATION["돌쌓기"],
|
||
f"정면적 × √(1+{slope_ratio:g}²) — 비탈면적 · {slope_basis}",
|
||
),
|
||
# 입적 — 정본 계산표 좌측 열의 이름 그대로다(확정 ⑦). 값을 새로 내는 것이 아니라
|
||
# 이미 쓰고 있던 밑수를 화면에 드러내는 줄이다.
|
||
Component(
|
||
"입적",
|
||
"㎥",
|
||
volume,
|
||
DESTINATION["입적"],
|
||
f"정면적 × 평균두께 {thickness:.2f} · {thickness_basis}",
|
||
),
|
||
]
|
||
# ⚠ 고임돌 계수가 **원문에서 「-」**인 칸이 있다(견치돌 25·30 · 야면석 75 · 깬돌 25).
|
||
# 그 규격에 그 돌을 안 쓴다는 뜻이라 **0 줄을 만들지 않는다** — 0 은 「없음」과
|
||
# 구별이 안 되고, 받는 쪽이 「값이 0 인 자재」로 읽는다.
|
||
if table["wedge_stone_m3_per_m2"] is None:
|
||
notes.append(
|
||
f"고임돌 계수가 품셈 표에 없습니다 — 뒷길이 {back_cm}㎝"
|
||
+ (f" · {kind_label}" if kind_label else "")
|
||
+ " 칸이 「-」입니다"
|
||
)
|
||
else:
|
||
components.append(
|
||
Component(
|
||
"고임돌",
|
||
"㎥",
|
||
masonry_area * _num(table["wedge_stone_m3_per_m2"]),
|
||
DESTINATION["고임돌"],
|
||
f"돌쌓기 × {table['wedge_stone_m3_per_m2']} ㎥/㎡ (뒷길이 {back_cm}㎝)"
|
||
+ (f" · {kind_label}" if kind_label else ""),
|
||
)
|
||
)
|
||
|
||
# 석적 — ⭐ 확정 5차 작은 것 4. 「체적」은 위 **입적** 줄이 이미 그것이고, 이쪽만 새 줄이다.
|
||
stone_pile = face_area * back_m * STONE_PACKING_RATIO
|
||
components.append(
|
||
Component(
|
||
"석적",
|
||
"㎥",
|
||
stone_pile,
|
||
DESTINATION["석적"],
|
||
f"정면적 × 뒷길이 {back_m:g}m × {STONE_PACKING_RATIO}(채움률) · {STONE_PILE_BASIS}",
|
||
)
|
||
)
|
||
|
||
# 돌 무게 — ⭐ 확정 5차 큰 것 7. **계산식이 기본**이고 야면석만 관측표다(헬퍼 한 벌).
|
||
# ⚠ 안 고른 경우도 **계산식**으로 선다 — 정본 여섯 탭이 종류를 안 적고 그 식을 쓰고,
|
||
# 그래야 정본 H=2.0 의 「1.92 톤」과 맞는다(관측표 0.88 로 서면 1.84 로 4 % 낮다).
|
||
stone_name, stone_ton, weight_tail, weight_source = stone_weight_per_m2(
|
||
back_cm, stone_kind_name, table["stone_ton_per_m2"]
|
||
)
|
||
weight_basis = f"돌쌓기 {weight_tail}" if weight_tail else ""
|
||
if not stone_kind_name:
|
||
notes.append(
|
||
"돌 종류를 안 골라 **계산식**(뒷길이 × 0.77 × 2.65)으로 섰습니다 — "
|
||
"야면석이면 계산식이 안 맞아 관측표로 갈립니다"
|
||
)
|
||
if stone_ton is None:
|
||
# 원본 표가 비어 있는 칸이다 — 지어내지 않고 알린다(PLAN 8-8 ㉮).
|
||
notes.append(f"뒷길이 {back_cm}㎝ 의 돌중량이 원본 표에 없어 {stone_name}을 내지 못함")
|
||
else:
|
||
components.append(
|
||
Component(
|
||
"돌",
|
||
"ton",
|
||
masonry_area * stone_ton,
|
||
DESTINATION["돌"],
|
||
weight_basis,
|
||
source=weight_source,
|
||
spec=stone_kind_name,
|
||
)
|
||
)
|
||
|
||
# 막자갈(뒷채움) — ⭐ 확정 5차 작은 것 3 「(상 + 하) ÷ 2 × H」. **뒷채움 폭**이지 벽 두께가
|
||
# 아니다. 옛 식(입적 − 몸통 − 고임돌)에서 갈아탄 자리다.
|
||
backfill_top = STONE_BACKFILL_WIDTH_M["top"]
|
||
backfill_bottom = STONE_BACKFILL_WIDTH_M["bottom"]
|
||
rubble = (backfill_top + backfill_bottom) / 2.0 * height_m * length_m
|
||
components.append(
|
||
Component(
|
||
"막자갈",
|
||
"㎥",
|
||
rubble,
|
||
DESTINATION["막자갈"],
|
||
f"(뒷채움 상 {backfill_top:g} + 하 {backfill_bottom:g}) ÷ 2 × H {height_m:g}m"
|
||
f" × 연장 {length_m:g}m · 정본 여섯 탭 공통값",
|
||
)
|
||
)
|
||
notes.append(
|
||
f"막자갈 뒷채움 폭이 정본(`04.구조도(기슭막이).xls`) 값 상 {backfill_top:g} · "
|
||
f"하 {backfill_bottom:g}m 붙박이입니다 — 구조물 제원에 뒷채움 폭 칸이 없습니다"
|
||
"(소광리는 같은 식에 0.30/0.60 을 씁니다)"
|
||
)
|
||
# ⚠ 근거 구간 밖은 **값이 서되 그 사실이 보여야 한다** — 값은 계속 나오므로 사유가
|
||
# 없으면 아무도 못 본다(2026-09-09 그물 침).
|
||
if height_m > STONE_BACKFILL_VERIFIED_HEIGHT_M:
|
||
notes.append(
|
||
f"직고 {height_m:g}m 는 뒷채움 폭 정본 확인 구간"
|
||
f"(H {STONE_BACKFILL_VERIFIED_HEIGHT_M:g}m 이하) **밖**입니다 — 붙박이 폭이라 "
|
||
"직고가 커져도 두께가 안 늘어 품셈 13-4-4 [주]⑨ 뒤채움 조약돌 두께 범위보다 "
|
||
"얇아집니다(그 표는 직고와 함께 두꺼워짐). 값은 정본 식대로 세우되 "
|
||
"**높은 벽의 뒷채움 폭은 실무자 확인이 필요합니다**"
|
||
)
|
||
|
||
if wet:
|
||
mpa, mpa_basis = fill_concrete_mpa(options)
|
||
components.append(
|
||
Component(
|
||
"채움콘크리트",
|
||
"㎥",
|
||
masonry_area * _num(table["fill_concrete_m3_per_m2"]),
|
||
DESTINATION["채움콘크리트"],
|
||
# ⚠ 돌 종류로 계수가 갈리는데(깬돌 0.2 · 야면석 0.15) 근거에 종류가 안
|
||
# 적히던 자리다 — 값은 바뀌는데 **왜 바뀌었는지가 안 보였다**(고임돌은
|
||
# 적고 있었다). 2026-09-09 맞춤.
|
||
f"돌쌓기 × {table['fill_concrete_m3_per_m2']} ㎥/㎡ (뒷길이 {back_cm}㎝)"
|
||
+ (f" · {kind_label}" if kind_label else "")
|
||
+ f" · {mpa_basis}",
|
||
spec=f"{mpa}",
|
||
)
|
||
)
|
||
components.append(
|
||
Component(
|
||
"모르터",
|
||
"㎥",
|
||
masonry_area * constants["mortar_m3_per_m2"],
|
||
DESTINATION["모르터"],
|
||
f"돌쌓기 × {constants['mortar_m3_per_m2']} ㎥/㎡ (줄눈)",
|
||
)
|
||
)
|
||
# ⚠ 여기서 멈춘다 — 모르터를 시멘트·모래로 쪼개지 않는다(㉢).
|
||
|
||
# 물구멍 — 벽면적 2㎡당 1개소, 개소당 관 길이 = **평균두께**(관이 벽을 가로지름).
|
||
# ⚠ 이것은 **관(파이프) 자재**이지 공제 대상이 아니다. 품셈 1-2-1 이 「공제하지 않는다」고
|
||
# 말하는 물구멍은 **콘크리트 체적에서 뺄 구멍**이고, 여기 값은 그 구멍에 넣는 **관 길이**다.
|
||
# ⚠⚠ **㉥ 이중계상** — 품셈 13-6-2·13-7-2 [주]③ 은 제잡비 **윗단** 값에
|
||
# 「물빼기 파이프 설치에 관계되는 노무비, 재료비를 포함한다」고 한다. 그 쪽을 쓰면
|
||
# 이 줄과 겹친다. **우리 선택은 이 줄을 세우고 제잡비는 아랫단(미설치)** 이다
|
||
# (`structure_unit_observed` 의 `double_count_rules`).
|
||
# 지금 쓰는 13-4 계열에는 제잡비 행 자체가 없어 겹치지 않는다(전수 확인).
|
||
# ⚠ 관종·지름은 미확정 — 법은 「지름 3~6㎝ 파이프」, 실무 관측은 Ø50. 규격이 정해지면
|
||
# 이름에 붙인다(`물구멍 Ø50`). 지어내지 않고 규격 없는 이름으로 둔다.
|
||
# ⚠ 「미확정」만 적으면 사용자가 무엇을 정해야 하는지 모른다 —
|
||
# **지금 무슨 값으로 돌고 있는지**를 함께 적는다(원단위 미확보와 같은 방식).
|
||
# ⭐ 2026-09-13 브레인 판정(PLAN 10장) — 개소당 관 길이 = **평균두께**. 정본이 그렇게 잡고
|
||
# 관은 벽을 가로지르므로 이치에도 맞음. 옛 상수 0.5m 는 그만큼 짧게 섰음.
|
||
# ⚠ 골막이(`_Revetment`)는 그 정본이 0.5 라 그대로 둠 — 여기는 기슭막이 계열만.
|
||
hole_area, hole_dia, hole_basis = weep_hole_spec(options)
|
||
components.append(
|
||
Component(
|
||
"물구멍관",
|
||
"m",
|
||
masonry_area / hole_area * thickness,
|
||
DESTINATION["물구멍관"],
|
||
f"돌쌓기 ÷ {hole_area:g}㎡/개소 × 평균두께 {thickness:.2f} m/개소 · {hole_basis}",
|
||
spec=f"Ø{hole_dia}",
|
||
)
|
||
)
|
||
|
||
# 채집석 — **캐서 쓰는 구조물**의 돌 체적. 사토에서 뺄 밑수이고 **여기서 빼지 않는다.**
|
||
# ⚠ 밑수가 확정 5차로 바뀌었다 — 면석 몸통은 이제 **석적**(정면적×뒷길이×0.77)이고
|
||
# 막자갈은 **뒷채움 사다리꼴**이다. 옛 「입적 − 몸통 − 고임돌」 몫이 아니다.
|
||
if all(options.get(key) in (None, "") for key in STONE_SUPPLY_KEYS):
|
||
# 폼이 기본값을 값으로 안 보내게 된 뒤(2026-09-14) 빈 조달이 조용히 「채집」이 되지 않게.
|
||
notes.append(STONE_SUPPLY_DEFAULT_NOTE)
|
||
if is_collected_stone(options):
|
||
wedge = masonry_area * _num(table["wedge_stone_m3_per_m2"]) # None 이면 0
|
||
collected = max(stone_pile + wedge + rubble, 0.0)
|
||
if collected > 0:
|
||
components.append(
|
||
Component(
|
||
"채집석",
|
||
"㎥",
|
||
collected,
|
||
DESTINATION["채집석"],
|
||
f"석적 {stone_pile:.3f} + 고임돌 {wedge:.3f} + 막자갈 {rubble:.3f} ㎥"
|
||
" · 현장 채집분 · ⚠ 여기서 빼지 않음 — 사토에서 한 번만 뺌",
|
||
)
|
||
)
|
||
|
||
# 버림 콘크리트 — 기초 바닥에 까는 얇은 층. 빼려면 저장 제원에서 「안 넣음」으로 둔다.
|
||
# 잡석다짐 폭 = **하단 길이**(확정 ⑪) — 벽에서는 하부 두께다. 버림도 같은 폭을 쓴다.
|
||
base_width = bottom_thickness
|
||
blinding = _blinding_component(
|
||
base_width,
|
||
length_m,
|
||
options,
|
||
f"하단 길이(하부 두께) {bottom_thickness:.2f}m — 확정 ⑪ 「기준 구조물에서 유도」",
|
||
)
|
||
if blinding is not None:
|
||
components.append(blinding)
|
||
# ⓘ 기초잡석은 `build_table` 이 버림 뒤에 세움(확정 3차 ② 두께 0.2 · PLAN 10장 「계상함」).
|
||
# 옛 「두께가 없어 안 섬」 사유는 걷음 — 줄과 사유가 함께 떠 서로 어긋났음(2026-09-13 화면 실측).
|
||
|
||
# 터파기·되메우기·잔토 — 토공으로 합산되는 값이다(내역 줄이 아니다).
|
||
components.extend(
|
||
_trench_components(height_m, length_m, thickness, options, notes, thickness_basis)
|
||
)
|
||
return components, notes
|