feat(B08): 개거(겉도랑) 전개 — 정본 두 표를 m당으로 (3단계 넷째)

소광리 정본 「개거(150-200) (2)」·「L형수로-(201)」 — **둘 다 m당**.

⚠⚠ **두 표가 담는 것이 다름.** 값이 비슷해 보여 합치고 싶어지는 자리라 못 박음:
```
콘크리트 개거 150×200   터파기 · 유로폼 · 면목  **세 줄뿐** — **콘크리트 본체 줄이 없음**
콘크리트 L형수로 H=0.2  터파기 · 되메우기 · 잔토 · **콘크리트** · PVC Φ50 · 이형철근 D13 ·
                       거푸집 · 면목  **여덟 줄**
ⓘ 같은 것 — 둘 다 m당이고 거푸집 계열(유로폼·거푸집)과 면목이 있음
```
⚠ 개거에 콘크리트가 없는 것은 **빠뜨린 것이 아니라 원문 그대로**임. 0 으로 때우거나
L형수로 값을 옮겨 채우지 않음 — 돌붙임(메)에서 세 줄을 안 만든 그 판단과 같음.
⚠ **PVC Φ50 은 줄만 있고 수량 칸이 빔** — 안 세우고 그 사실을 줄로 냄.

⚠ 규격이 **붙박이**(「콘크리트 개거 150×200」) — `open_ditch` 등록부에 **옵션이 하나도 없음**.
   `ditch_spec` 을 주면 L형수로로 갈림. 칸이 생기면 열면 됨.

**측구(`ditch_side`)는 안 건드림** — `design_owner: 횡단 설계` 라 B06 소관이고 `enabled: False`.
정본 「측구수로400/500」은 **「터파기 계산서」**라 터파기·잔토·되메우기 셋뿐임(본체 없음) —
그쪽이 쓸 때를 위해 적어 둠.

실전개 (연장 10m)
  개거   터파기 0.3㎥ · 유로폼 5.5㎡ · 면목 20m
  L형수로 터파기 3.1645 · 되메우기 1.2997 · 잔토 1.8647 · 콘크리트 1.508 ·
         이형철근 3.98㎏ · 거푸집 7.38㎡ · 면목 10m

자체검증 — 새 시험 5건(정본 값 대조 · 개거에 콘크리트가 없는지 · 잔토 = 터파기−되메우기 ·
빈 수량 줄 · 없는 규격), 회귀 596 통과 · 0 실패.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-09 11:28:30 +09:00
co-authored by Claude Opus 5
parent 62f1e252d5
commit 9f2e81babc
2 changed files with 90 additions and 0 deletions
@@ -942,6 +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"),
# 개거는 **m당** 원단위라 연장이 밑수다(정본 「개거(150-200)」·「L형수로-(201)」).
"open_ditch": lambda h, l, o, f=None, r="": _open_ditch()(l, 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 발견).
@@ -954,6 +956,13 @@ EXPANDERS = {
}
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
@@ -153,6 +153,87 @@ def stone_kin_withheld(type_id: str) -> tuple[list[Component], list[str]]:
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 판단 — 돌붙임(메)에서 세 줄을 안 만든 그 자리).
#:
#: ⚠ **규격이 붙박이다** — 정본에 「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"),),
},
}
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(
"⚠ 이 표에는 **콘크리트 본체 줄이 없습니다** — 정본 원문 그대로입니다"
"(터파기·유로폼·면목 세 줄뿐). 지어내지 않았습니다"
)
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]]:
"""바닥막이 전개 — **돌붙임 ㎡당** 원단위를 면적에 곱한다(소광리 정본 두 탭).