feat(B08): 바닥막이 전개 — 돌붙임 ㎡당 원단위 (3단계 셋째). 앞서 낸 사유가 틀렸음

⚠ **정정** — 어제 「돌붙임 원단위 미확보」로 사유를 냈으나 **소광리 정본에 식이 셋 있었음.**
안 찾아본 것을 「없다」고 적은 자리였음. 53탭 전수에서 드러남.

붙인 원단위 (정본 「돌붙임L3=45(사방)」 · 「돌붙임L3=30(야면석메붙임)」)
```
돌붙임(찰)  두께 0.45 · 돌 30×30×45㎝ **0.918 ton/㎡**(= 0.45 × 0.77 × 2.65) ·
            고임돌 0.15 · 모르타르 0.009 · 채움 0.2 + **버림 0.1** · 터파기 0.45   8줄
돌붙임(메)  두께 0.30 · 야면석 20×20×30㎝ **0.42 ton/㎡**(원문 직접값) ·
            고임돌 0.07 · 터파기 0.30                                          5줄
            ⚠ 모르타르·채움·버림은 **줄 자체가 없음** — 0 으로 만들지 않고 뺌
```
밑수는 **면적(㎡)** — 등록부 `area_m2` 와 축이 그대로 맞음. **기울기 몫을 안 곱함**(평면).

⚠⚠ **돌쌓기 표(13-4)와 합치지 못하게 못 박음** — 값이 겹쳐 보여 합치고 싶어지는 자리:
```
돌쌓기(13-4)  밑수 비탈면적 · 뒷길이 일곱 규격 · 기울기 몫 √(1+n²)
돌붙임(13-5)  밑수 **평면적** · 뒷길이 **넷** · **기울기 몫 없음** · 메는 모르타르·채움 없음
⚠ 45㎝ 에서 고임돌 0.15·채움 0.20 이 **우연히 같음** — 합치면 25·30·60·75 에서 조용히 틀림
```

ⓘ **돌중량이 관측값이 아니라 유도값임** — `뒷길이 × 0.77(채움률) × 2.65(비중)`.
   두 원본에서 같은 모양이 나왔음. 다만 **야면석은 그 식이 안 맞아**(공극이 커 ㎥당 무게가
   다름) 원문 직접값 0.42 를 그대로 옮김. ⇒ 확정 ⑤ 가 「값 고르기」가 아니라
   **「채움률·비중 두 인자 정하기」**로 바뀔 수 있음 — 우리 0.88 은 안 건드렸음.

⚠ 뒷길이가 **붙박이**(찰 0.45 · 메 0.30) — 정본 탭이 그 둘뿐이고 등록부에 칸이 없음.
   그 사실을 줄로 냄. 칸이 생기면 표를 열면 됨.

자체검증 — 새 시험 5건(정본 값 대조 · 메는 세 줄이 빠지는지 · 기울기 몫 없음),
회귀 591 통과 · 0 실패.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-09 08:36:47 +09:00
co-authored by Claude Opus 5
parent 60b7196754
commit 62f1e252d5
2 changed files with 112 additions and 5 deletions
@@ -942,7 +942,8 @@ EXPANDERS = {
"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"),
"bed_sill": lambda h, l, o, f=None, r="": _stone_kin()("bed_sill"),
# 바닥막이는 **돌붙임 ㎡당**이라 높이·연장이 아니라 **면적**이 밑수다(정본 「돌붙임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 발견).
# 큰돌쌓기는 품셈 **13-6** 이고 돌쌓기는 **13-4** 다 — **규격 축이 다르다.**
# 돌쌓기는 **뒷길이**(35·45·55·60㎝), 큰돌쌓기는 **직경**(40~60·60~80·80~100㎝).
@@ -953,6 +954,13 @@ EXPANDERS = {
}
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
@@ -63,10 +63,45 @@ STONE_KIN_WITHHELD: dict[str, str] = {
"실측) — 평균두께 {(0.45+0.1H)+(0.45+0.4H)}÷2 · 밑수 「돌쌓기+돌붙임」 · 개소당. "
"게다가 정면적이 사다리꼴이라 **상장·하장 칸이 있어야** 셈이 서는데 등록부에 없음"
),
"bed_sill": (
"바닥막이는 형태가 **돌붙임**(품셈 13-5)이라 돌쌓기(13-4) 표를 못 씀 — "
"밑수도 면적(㎡)이라 축이 다름. 돌붙임 원단위 미확보"
),
}
#: 돌붙임 원단위 — 소광리 정본 「돌붙임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,
},
}
#: 돌쌓기 식이 계수를 가르는 데 쓰는 칸 — 기슭막이 등록부에 아직 없는 것을 알린다.
@@ -116,3 +151,67 @@ def stone_kin_withheld(type_id: str) -> tuple[list[Component], list[str]]:
"""
why = STONE_KIN_WITHHELD.get(type_id)
return [], [why] if why else []
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