Merge remote-tracking branch 'origin/sub_laptop_1' into main_desktop_1

# Conflicts:
#	B08_Quantity/B08_Quantity_Engine_UnitQuantity.py
This commit is contained in:
2026-09-09 11:37:03 +09:00
2 changed files with 402 additions and 7 deletions
@@ -170,13 +170,14 @@ def stone_coefficients(options: dict[str, Any], back_cm: int) -> tuple[dict[str,
# 돌쌓기 전개식의 상수 — 실무 수식에 박혀 있던 값을 뺀 것. # 돌쌓기 전개식의 상수 — 실무 수식에 박혀 있던 값을 뺀 것.
# ⚠⚠ **기슭막이 계열 전용이다. 다른 구조물로 넓히지 말 것**(2026-09-09). #: ⚠⚠ **기슭막이 계열(돌쌓기 찰·메) 전용이다. 다른 구조물로 넓히지 말 것**(2026-09-09).
# 골막이는 **두께식부터 다르다** — 소광리 숨김탭 「골막이(찰)(치수조서연결)」 C26: #: 골막이는 **두께식부터 다르다** — 소광리 숨김탭 「골막이(찰)(치수조서연결)」 C26:
# 평균두께 = {(0.45 + 0.1×H) + (0.45 + 0.4×H)} ÷ 2 #: 평균두께 = {(0.45 + 0.1×H) + (0.45 + 0.4×H)} ÷ 2
# 우리가 확정 ② 로 버린 옛 식과 **글자까지 같다.** 그 식은 틀린 식이 아니라 **골막이 식**이고, #: 우리가 확정 ② 로 버린 옛 식과 **글자까지 같다.** 그 식은 틀린 식이 아니라 **골막이 식**이고,
# 그것을 기슭막이에까지 쓰고 있었던 것이 어긋남의 정체였다. #: 그것을 기슭막이에까지 쓰고 있었던 것이 어긋남의 정체였다.
# ⚠ 골막이는 그 밖에도 셋이 더 다르다 — 밑수가 **돌쌓기 + 돌붙임**(기슭막이는 돌쌓기만) · #:상수만 보고 골막이에 가져다 쓰면 **값이 나오므로 아무 시험도 안 잡는다.**
# 단위가 **개소당**(기슭막이는 m당) · 정면적이 **사다리꼴**. **상수만 보고 가져다 쓰지 말 것.** #: ⚠ 골막이는 그 밖에도 셋이 더 다르다 — 밑수가 **돌쌓기 + 돌붙임**(기슭막이는 돌쌓기만) ·
#: 단위가 **개소당**(기슭막이는 m당) · 정면적이 **사다리꼴**.
STONE_MASONRY = { STONE_MASONRY = {
# ⚠ **곱하는 값이 아니라 검산 참고값이다** (2026-09-08 ㉘ 에서 고침). # ⚠ **곱하는 값이 아니라 검산 참고값이다** (2026-09-08 ㉘ 에서 고침).
# 실무 시트의 「돌쌓기 = 정면적 × 1.04」에서 그 1.04 가 **곧 기울기 몫**이다 # 실무 시트의 「돌쌓기 = 정면적 × 1.04」에서 그 1.04 가 **곧 기울기 몫**이다
@@ -961,6 +962,18 @@ EXPANDERS = {
"masonry_dry": lambda h, l, o, f=None, r="": stone_masonry(h, l, o, False, f, r), "masonry_dry": lambda h, l, o, f=None, r="": stone_masonry(h, l, o, False, f, r),
# 큰돌쌓기는 표준경사 표 대상이 아니라 성절토를 안 쓴다(교본 「1:0.3 이상」). # 큰돌쌓기는 표준경사 표 대상이 아니라 성절토를 안 쓴다(교본 「1:0.3 이상」).
"boulder_masonry": lambda h, l, o, f=None, r="": boulder_masonry(h, l, o), "boulder_masonry": lambda h, l, o, f=None, r="": boulder_masonry(h, l, o),
# 기슭막이는 **형태가 돌쌓기면 돌쌓기 식**이다 — 실무 정본 탭 제목이 「돌기슭막이(…찰쌓기…)」
# 이고 그 안의 계산이 같다. 갈래·사유는 `_UnitQuantity_Revetment` 가 든다(늦게 부른다 —
# 그쪽이 이 모듈의 `stone_masonry` 를 쓰므로 위에서 부르면 맞물린다).
"revetment": lambda h, l, o, f=None, r="": _revetment()(h, l, o, f, r),
# 골막이·바닥막이는 돌 계열이나 **돌쌓기 식을 그대로 못 쓴다** — 까닭은 그 모듈에 적었다.
"erosion_check": lambda h, l, o, f=None, r="": _stone_kin()("erosion_check"),
# 개거는 **m당** 원단위라 연장이 밑수다(정본 「개거(150-200)」·「L형수로-(201)」).
"open_ditch": lambda h, l, o, f=None, r="": _open_ditch()(l, o),
# 흙막이는 **아직 못 세움** — 정본에 떼흙막이 하나뿐인데 우리 형식 칸에 「떼」가 없음.
"soil_guard": lambda h, l, o, f=None, r="": _soil_guard()(o),
# 바닥막이는 **돌붙임 ㎡당**이라 높이·연장이 아니라 **면적**이 밑수다(정본 「돌붙임L3=…」).
"bed_sill": lambda h, l, o, f=None, r="": _bed_sill()(_num(o.get("area_m2"), 0.0), o),
# ⚠⚠ **큰돌쌓기(`boulder_masonry`)를 여기에 두지 않는다** (2026-09-07 발견). # ⚠⚠ **큰돌쌓기(`boulder_masonry`)를 여기에 두지 않는다** (2026-09-07 발견).
# 큰돌쌓기는 품셈 **13-6** 이고 돌쌓기는 **13-4** 다 — **규격 축이 다르다.** # 큰돌쌓기는 품셈 **13-6** 이고 돌쌓기는 **13-4** 다 — **규격 축이 다르다.**
# 돌쌓기는 **뒷길이**(35·45·55·60㎝), 큰돌쌓기는 **직경**(40~60·60~80·80~100㎝). # 돌쌓기는 **뒷길이**(35·45·55·60㎝), 큰돌쌓기는 **직경**(40~60·60~80·80~100㎝).
@@ -970,6 +983,42 @@ EXPANDERS = {
# 전개식·관측 원단위가 설 때까지 **미확보로 드러낸다.** # 전개식·관측 원단위가 설 때까지 **미확보로 드러낸다.**
} }
def _soil_guard():
"""흙막이 사유 함수를 늦게 가져온다."""
from B08_Quantity.B08_Quantity_Engine_UnitQuantity_Revetment import soil_guard
return soil_guard
def _open_ditch():
"""개거 전개 함수를 늦게 가져온다."""
from B08_Quantity.B08_Quantity_Engine_UnitQuantity_Revetment import open_ditch
return open_ditch
def _bed_sill():
"""바닥막이 전개 함수를 늦게 가져온다."""
from B08_Quantity.B08_Quantity_Engine_UnitQuantity_Revetment import bed_sill
return bed_sill
def _stone_kin():
"""골막이·바닥막이 사유 함수 — 늦게 가져온다(서로 부르는 것을 푸는 자리)."""
from B08_Quantity.B08_Quantity_Engine_UnitQuantity_Revetment import stone_kin_withheld
return stone_kin_withheld
def _revetment():
"""기슭막이 전개 함수를 늦게 가져온다 — 서로 부르는 것을 풀기 위한 자리."""
from B08_Quantity.B08_Quantity_Engine_UnitQuantity_Revetment import revetment
return revetment
#: 전개식을 일부러 안 두는 종류 — 왜 안 두는지 사람이 읽게 적는다. #: 전개식을 일부러 안 두는 종류 — 왜 안 두는지 사람이 읽게 적는다.
EXPANDER_WITHHELD: dict[str, str] = {} EXPANDER_WITHHELD: dict[str, str] = {}
@@ -0,0 +1,346 @@
"""기슭막이 전개 — **형태로 갈라 돌쌓기 식을 그대로 쓴다**(3단계, 2026-09-09).
실무 정본이 근거다 — `구조물도/기슭막이/04.구조도(기슭막이).xls` 의 탭 제목이
**「돌기슭막이(H=2.0m, 찰쌓기, 기초유)」** 이고, 그 안의 계산이 돌쌓기와 **같은 식**이다.
정면적 → 비탈면적(×√(1+n²)) → 평균두께 → 입적 → 돌 · 막자갈 · 고임돌 →
터파기(기초 + 비탈) → 되메우기 → 잔토
⇒ **기슭막이는 새 식이 아니라 「형태가 돌쌓기면 돌쌓기 식」**이다. 여기서 식을 다시 짜면
같은 계산이 두 벌이 되어 돌쌓기와 갈린다(CLAUDE.md 5장).
⚠ **형태가 돌쌓기가 아닌 갈래는 식이 없다** — 콘크리트·돌망태·통나무/목재틀·바자.
지어내지 않고 **왜 없는지**를 줄로 남긴다.
⚠ **뒷길이·돌 종류 칸이 기슭막이 등록부에 아직 없다**(2026-09-09 실측). 돌쌓기 식은 그 둘로
계수가 갈리는데, 없으면 `_back_length` 가 **조용히 45㎝ 로 돈다**. 값이 나오므로 아무도
못 알아채는 자리라 **그 사실을 줄로 드러낸다** — 「값이 있기는 하니 안 보이는」 그것.
"""
from __future__ import annotations
from typing import Any
from B08_Quantity.B08_Quantity_Engine_UnitQuantity import Component, stone_masonry
#: 형태 → 돌쌓기 갈래(찰이면 True). 여기 없는 형태는 전개식이 없다.
STONE_FORMS: dict[str, bool] = {
"돌쌓기(찰)": True,
"돌쌓기(메)": False,
}
#: 식이 없는 형태와 **왜 없는지**. 품셈 장이 다르거나 원단위가 원문에 없다.
WITHHELD_FORMS: dict[str, str] = {
"콘크리트": (
"콘크리트 기슭막이는 돌쌓기(품셈 13-4)가 아니라 콘크리트 구조물이라 전개식이 다름 "
"— 벽 두께·저판이 정본에 없어 물량이 서지 않음"
),
"돌망태": ("돌망태는 품셈 13-8 이고 규격 축이 망태 치수라 돌쌓기 표를 못 씀 — 원단위 미확보"),
"통나무·목재틀": (
"목재틀은 품셈 13-13 이고 밑수가 ㎥당 목공 품이라 돌쌓기 표를 못 씀 "
"— 각재·판재 자재가 카탈로그에 없어 값이 모자람"
),
"바자": "바자얽기(품셈 5-15)는 별도 일위대가를 만들어 잇기로 확정(2026-09-09 ⑧-4)",
}
#: 골막이·바닥막이 — **왜 아직 안 서는지**. 둘 다 돌 계열이나 돌쌓기 식을 그대로 못 쓴다.
#: ⚠ 근거 없는 식을 만들지 않는다(2026-09-09 확인). 칸·표가 오면 그때 붙인다.
#:
#: ⚠⚠ **골막이를 돌쌓기와 합치지 말 것** — 「같은 돌쌓기니 한 함수로」가 되기 쉬운 자리다.
#: 소광리 정본 「골막이(찰)(치수조서연결)」과 「돌기슭막이」를 나란히 재면 **넷이 다르다**:
#:
#: 두께식 골막이 (0.45+0.1H)/(0.45+0.4H) ↔ 기슭막이 뒷길이 기반(ℓ3+0.30 …)
#: 밑수 골막이 **돌쌓기 + 돌붙임** ↔ 기슭막이 돌쌓기만
#: 단위 골막이 **개소당** ↔ 기슭막이 m당 (확정 ⑦ 「통일 안 함」)
#: 정면적 골막이 **사다리꼴**(상장·하장) ↔ 기슭막이 직사각(H × 1)
#:
#: ⓘ 같은 것도 있다 — 막자갈 **2/3**·야면석 **0.88 ton/㎡**·물구멍 **2㎡/개소**·
#: 기울기 몫 **1.04**·고임돌 **0.15**·채움콘크리트 **0.2** 는 두 시트가 같은 값이다.
#: 우리 기본값이 어디서 왔는지의 답이기도 하다.
STONE_KIN_WITHHELD: dict[str, str] = {
"erosion_check": (
"골막이는 돌쌓기와 **두께식·밑수·단위가 다름**(소광리 정본 「골막이(찰)(치수조서연결)」 "
"실측) — 평균두께 {(0.45+0.1H)+(0.45+0.4H)}÷2 · 밑수 「돌쌓기+돌붙임」 · 개소당. "
"게다가 정면적이 사다리꼴이라 **상장·하장 칸이 있어야** 셈이 서는데 등록부에 없음"
),
}
#: 돌붙임 원단위 — 소광리 정본 「돌붙임L3=45(사방)」(찰) · 「돌붙임L3=30(야면석메붙임)」(메).
#: ⚠⚠ **돌쌓기 표(13-4)와 합치지 말 것.** 값이 겹쳐 보여 합치고 싶어지는 자리다:
#:
#: 돌쌓기(13-4) 밑수 **비탈면적** · 뒷길이 **일곱 규격** · **기울기 몫 √(1+n²)**
#: 돌붙임(13-5) 밑수 **평면적** · 뒷길이 **넷** · **기울기 몫 없음**
#: 메는 모르타르·채움콘크리트가 통째로 빠진다
#:
#: ⚠ 45㎝ 에서 고임돌 0.15 · 채움 0.20 이 **우연히 같다** — 그것이 합치고 싶어지는 까닭이고,
#: 합치면 뒷길이 25·30·60·75 에서 조용히 틀린다(돌붙임 표에 없는 규격이다).
#:
#: ⚠ **뒷길이는 지금 붙박이다** — 찰 0.45m · 메 0.30m. 정본 탭이 그 둘로만 있고 바닥막이
#: 등록부에 뒷길이 칸이 없다. 칸이 생기면 이 표를 열면 된다.
#: ⓘ 돌중량은 **관측값이 아니라 유도값**이다 — `뒷길이 × 0.77(채움률) × 2.65(비중)`.
#: 찰 0.45 × 0.77 × 2.65 = 0.918 ton/㎡. 메(야면석)는 0.42 로 **원문에 값이 직접 적혀 있어**
#: 같은 유도식이 안 맞는다(야면석은 공극이 커 ㎥당 무게가 다르다) — 그대로 옮긴다.
BED_SILL_FORMS: dict[str, dict[str, Any]] = {
"돌붙임(찰)": {
"back_len_m": 0.45,
"stone_name": "",
"stone_spec": "30×30×45㎝",
"stone_ton_per_m2": 0.45 * 0.77 * 2.65,
"wedge_stone_m3_per_m2": 0.15,
"mortar_m3_per_m2": 0.009,
"fill_concrete_m3_per_m2": 0.2,
"blinding_m3_per_m2": 0.1,
},
"돌붙임(메)": {
"back_len_m": 0.30,
"stone_name": "야면석",
"stone_spec": "20×20×30㎝",
"stone_ton_per_m2": 0.42,
"wedge_stone_m3_per_m2": 0.07,
# 메붙임은 모르타르·채움콘크리트·버림이 **없다**(정본 탭에 줄 자체가 없음).
"mortar_m3_per_m2": None,
"fill_concrete_m3_per_m2": None,
"blinding_m3_per_m2": None,
},
}
#: 돌쌓기 식이 계수를 가르는 데 쓰는 칸 — 기슭막이 등록부에 아직 없는 것을 알린다.
STONE_SPEC_KEYS: tuple[tuple[str, str], ...] = (
("back_len_cm", "뒷길이"),
("stone_kind", "돌 종류"),
)
def revetment(
height_m: float,
length_m: float,
options: dict[str, Any],
face: str | None = None,
face_reason: str = "",
) -> tuple[list[Component], list[str]]:
"""기슭막이 1구간 전개 — 형태가 돌쌓기면 그 식, 아니면 사유만."""
form = str(options.get("form") or "").strip()
if form not in STONE_FORMS:
why = WITHHELD_FORMS.get(form)
if why:
return [], [f"기슭막이 형태 「{form}」 — {why}"]
return [], [
f"기슭막이 형태가 정해지지 않았습니다(지금 「{form or '빈 값'}」) "
"— 형태를 골라야 물량이 섭니다"
]
components, notes = stone_masonry(
height_m, length_m, options, STONE_FORMS[form], face, face_reason
)
# ⚠ 계수를 가르는 칸이 없으면 **기본값으로 조용히 돈다** — 그 사실을 드러낸다.
missing = [label for key, label in STONE_SPEC_KEYS if not options.get(key)]
if missing and components:
notes.append(
f"⚠ 기슭막이 제원에 {' · '.join(missing)} 칸이 없어 **돌쌓기 기본값으로 섰음** "
"— 그 값이 고임돌·야면석·채움콘크리트 계수를 가름"
)
return components, notes
def stone_kin_withheld(type_id: str) -> tuple[list[Component], list[str]]:
"""골막이·바닥막이 — **식을 안 두는 까닭**만 낸다.
⚠ 돌 계열이라 `stone_masonry` 를 부르고 싶어지는 자리다. 실제로 부르면 **값이 나오므로**
아무 시험도 안 잡는다 — 오늘 큰돌쌓기에서 같은 일이 있었다(직경 60~80㎝ 가 「뒷길이
45㎝」 계수로 돌던 자리). 그래서 **일부러 안 부른다.**
"""
why = STONE_KIN_WITHHELD.get(type_id)
return [], [why] if why else []
#: 개거(겉도랑) 원단위 — 소광리 정본 「개거(150-200) (2)」·「L형수로-(201)」, **둘 다 m당**.
#: ⚠⚠ **두 표가 담는 것이 다르다.** 값이 비슷해 보여 합치고 싶어지는 자리다:
#:
#: 콘크리트 개거 150×200 터파기 · 유로폼 · 면목 **세 줄뿐** — **콘크리트 본체 줄이 없다**
#: 콘크리트 L형수로 H=0.2 터파기 · 되메우기 · 잔토 · **콘크리트** · PVC Φ50 · 이형철근 D13 ·
#: 거푸집 · 면목 **여덟 줄**
#:
#: ⓘ 같은 것 — **둘 다 m당**이고 거푸집 계열(유로폼·거푸집)과 면목이 있다.
#: ⚠ 개거 표에 콘크리트가 없는 것은 **빠뜨린 것이 아니라 원문 그대로**다. 0 으로 때우거나
#: L형수로 값을 옮겨 채우지 않는다(2026-09-09 판단 — 돌붙임(메)에서 세 줄을 안 만든 그 자리).
#:
#: ⓘ **「다른 탭에 본체가 있나」를 찾아봤고 없었다**(2026-09-09 53탭 전수):
#: · 「개거」 글자가 나오는 탭은 **둘뿐** — 이 탭과 「콘크리트개거」.
#: 뒤엣것은 **제목만 「콘크리트개거 (물넘이형)」이고 내용이 국가지점번호판 표**다.
#: · 탭 이름의 「(2)」는 **짝 번호가 아니라 엑셀 사본 표식**이다 — `Φ1000` ↔ `Φ1000(2)`,
#: `떼수로(윤주1.08)` ↔ `…(2)` 가 **내용까지 똑같다**(대조 확인). 「(1)」은 없다.
#: · 같은 시트의 「측구수로400/500」도 제목이 **「터파기 계산서」**이고 본체가 없다 —
#: **한 구조물을 부분 계산서로 쪼개는 버릇**이 있는 시트다.
#: ⇒ 그러므로 이 표는 **부분 계산서일 가능성이 남아 있다.** 다만 이 원본 안에서는
#: 본체 표를 못 찾았으므로 **「원문에 없음」으로 두고 그 사실을 화면에 적는다** —
#: 「콘크리트 개거인데 콘크리트가 0」이 우리 결함으로 읽히지 않게.
#:
#: ⚠ **규격이 붙박이다** — 정본에 「150×200」 하나뿐이고 `open_ditch` 등록부에 **옵션이 하나도
#: 없다**(규격 칸 없음). 칸이 생기면 이 표를 열면 된다.
OPEN_DITCH_FORMS: dict[str, dict[str, Any]] = {
"콘크리트 개거 150×200": {
"rows": (
("터파기", "", 0.3 * 0.1 * 1.0, "0.3 × 0.1 × 1"),
("유로폼", "", (0.2 + 0.2 + 0.15) * 1.0, "(0.2 + 0.2 + 0.15) × 1"),
("면목", "m", 2.0 * 1.0, "2 × 1"),
),
},
"콘크리트 L형수로 H=0.2": {
"rows": (
("터파기", "", 0.31645, "[{(1.11+1.51)÷2×0.2} + {(0.33×0.33)÷2}] × 1.0"),
("되메우기", "", 0.129975, "(0.2+0.30)÷2×0.25×1 + (0.2+0.19)÷2×0.33×1.0"),
("잔토처리", "", 0.186475, "터파기 되메우기"),
(
"콘크리트",
"",
0.1508,
"{(0.2+0.15)÷2×0.7} + (0.21×0.13)×1 + {(0.15+0.21)÷2×0.20}×1",
),
("이형철근 D13", "kg", 0.398, "0.2 × 2 × 0.995"),
("거푸집", "", 0.738, "0.2 + 0.33 + √(0.2² + 0.06²)"),
("면목", "m", 1.0, "1"),
),
# ⚠ 정본에 줄은 있으나 **수량 칸이 비어 있다**(PVC Φ50). 0 으로 만들지 않고 알린다.
"blank_rows": (("PVC 파이프 Φ50㎜", "m"),),
},
}
#: 떼흙막이 원단위 — 소광리 정본 「떼흙막이」, **개소당**. 치수는 전부 **평균 붙박이**다
#: (상단 1.5 · 하단 1.1 · 높이 0.5 · 두께 0.2 · 기슭 0.54 — 원문에 「(평균)」이라 적혀 있다).
#: ⚠⚠ **아직 못 쓴다** — 우리 `soil_guard` 형식이 「콘크리트 · 돌(찰) · 돌(메) · 블록쌓기 ·
#: 돌망태」 다섯이고 **「떼」가 없다.** 형식 칸이 생기면 이 표를 그대로 붙이면 된다.
#: ⚠ 나머지 다섯 형식은 **정본에 탭이 없다.** 돌(찰)/(메)에 기슭막이 식을 빌려 쓰지 않는다 —
#: 흙막이는 교본상 앞면 1:0.3·뒷면 수직·천단 30㎝ 라 축이 다르고, 무엇보다 **근거가 없다.**
SOIL_GUARD_SOD: dict[str, Any] = {
"unit_label": "개소당",
"rows": (
(
"",
"",
1.39,
"머리떼 1.5×0.2 + 바닥떼 1.1×0.2 + 정면떼 (1.5+1.1)÷2×0.5 "
"+ 양기슭바닥떼 0.54×0.2×2 · 규격 20×20㎝",
),
("바닥파기", "", 0.17, "(0.54×0.4×0.2×2) + (1.1×0.4×0.2)"),
),
}
def soil_guard(options: dict[str, Any]) -> tuple[list[Component], list[str]]:
"""흙막이 — **아직 못 세운다.** 어느 형식이 왜 안 되는지만 낸다."""
form = str(options.get("form") or "").strip()
if not form:
return [], ["흙막이 형식이 정해지지 않았습니다 — 형식을 골라야 물량이 섭니다"]
return [], [
f"흙막이 형태 「{form}」 — 정본(소광리 53탭)에 그 형식의 산출식이 없습니다. "
"있는 것은 **떼흙막이** 하나인데 우리 형식 칸에 「떼」가 없습니다. "
"⚠ 돌(찰)·돌(메)에 기슭막이 식을 빌려 쓰지 않습니다 — 흙막이는 앞면 1:0.3·뒷면 수직·"
"천단 30㎝(교본)라 축이 다르고 근거가 없습니다"
]
def open_ditch(length_m: float, options: dict[str, Any]) -> tuple[list[Component], list[str]]:
"""개거(겉도랑) 전개 — **m당** 원단위를 연장에 곱한다(소광리 정본 두 탭).
⚠ 규격을 고를 칸이 없어 지금은 **「콘크리트 개거 150×200」 붙박이**다. 그 사실을 줄로 낸다.
"""
from B08_Quantity.B08_Quantity_Engine_UnitQuantity import DESTINATION
if length_m <= 0:
return [], ["연장이 없어 전개하지 않음"]
spec = str(options.get("ditch_spec") or "콘크리트 개거 150×200").strip()
table = OPEN_DITCH_FORMS.get(spec)
if table is None:
return [], [
f"개거 규격 「{spec}」 원단위가 없습니다 — 있는 것: {' · '.join(OPEN_DITCH_FORMS)}"
]
components = [
Component(
name,
unit,
per_m * length_m,
DESTINATION.get(name, "quantity"),
f"{basis} × 연장 {length_m:g}m (m당 {per_m:g})",
)
for name, unit, per_m, basis in table["rows"]
]
notes: list[str] = []
if "ditch_spec" not in options:
notes.append(f"규격이 「{spec}」 붙박이입니다 — 개거 제원에 규격 칸이 없습니다")
if spec == "콘크리트 개거 150×200":
notes.append(
"⚠ 이 표에는 **콘크리트 본체 줄이 없습니다** — 정본 원문 그대로입니다"
"(터파기·유로폼·면목 세 줄뿐). 같은 원본 53탭에서 본체 표를 찾았으나 없었고"
"(「콘크리트개거」 탭은 제목만 그 이름이고 내용이 다른 표), 이 시트가"
"「측구수로 터파기 계산서」처럼 **부분 계산서를 쓰는 버릇**이 있어"
"**다른 문서에 본체가 있을 수 있습니다.** 지어내지 않았습니다"
)
for name, unit in table.get("blank_rows", ()):
notes.append(f"{name}({unit}) 은 정본에 줄은 있으나 **수량이 비어 있어** 안 세웠습니다")
return components, notes
def bed_sill(area_m2: float, options: dict[str, Any]) -> tuple[list[Component], list[str]]:
"""바닥막이 전개 — **돌붙임 ㎡당** 원단위를 면적에 곱한다(소광리 정본 두 탭).
⚠ 밑수가 **면적**이다. 돌쌓기처럼 높이·연장으로 셈하지 않는다 — 정본 표 제목이 「㎡당」이고
등록부 밑수도 `area_m2` 라 축이 그대로 맞는다.
⚠ 기울기 몫이 **없다** — 평면에 붙이는 것이라 √(1+n²) 를 곱하지 않는다.
"""
from B08_Quantity.B08_Quantity_Engine_UnitQuantity import DESTINATION
form = str(options.get("form") or "").strip()
table = BED_SILL_FORMS.get(form)
if table is None:
return [], [
f"바닥막이 형태 「{form or '빈 값'}」 — 돌붙임(찰)·돌붙임(메)만 원단위가 있습니다"
]
if area_m2 <= 0:
return [], ["면적이 없어 전개하지 않음"]
back = table["back_len_m"]
rows: list[tuple[str, str, float, str]] = [
("돌붙임", "", area_m2, f"면적 {area_m2:g}㎡ (평면적 — 기울기 몫 없음)"),
("입적", "", area_m2 * back, f"면적 × 두께 {back:g}m"),
(
table["stone_name"],
"ton",
area_m2 * table["stone_ton_per_m2"],
f"면적 × {table['stone_ton_per_m2']:.3f} ton/㎡ · {table['stone_spec']}"
+ (
f" (= 뒷길이 {back:g} × 0.77 × 2.65)"
if form == "돌붙임(찰)"
else " (원문 직접값 — 야면석은 유도식이 안 맞음)"
),
),
(
"고임돌",
"",
area_m2 * table["wedge_stone_m3_per_m2"],
f"면적 × {table['wedge_stone_m3_per_m2']} ㎥/㎡",
),
]
for name, key, unit in (
("모르터", "mortar_m3_per_m2", ""),
("채움콘크리트", "fill_concrete_m3_per_m2", ""),
("버림콘크리트", "blinding_m3_per_m2", ""),
):
value = table[key]
if value is not None:
rows.append((name, unit, area_m2 * value, f"면적 × {value} ㎥/㎡"))
# 터파기 — 정본은 「두께 × 1 × 1」이라 **면적 × 두께**와 같다.
rows.append(("터파기", "", area_m2 * back, f"면적 × 두께 {back:g}m"))
components = [
Component(name, unit, amount, DESTINATION.get(name, "quantity"), basis)
for name, unit, amount, basis in rows
]
notes = [f"뒷길이 {back:g}m 는 정본 탭 붙박이 값입니다 — 바닥막이 제원에 뒷길이 칸이 없습니다"]
if form == "돌붙임(찰)":
notes.append(
"버림 콘크리트 0.1 ㎥/㎡ 포함 — 정본 주기 「바닥 10㎝ 이상 콘크리트(버림) 포설 후 "
"돌붙임」(KDS 44 90 00 의 100㎜ 와 같음)"
)
return components, notes