Files
Aislo/resources/tester/test_b08_box_culvert_unit.py
T
eomsangdonandClaude Opus 5 e40f4c9fd3 feat(b05·b08): BOX암거 구체 터파기 — 평균 터파기고 칸 하나 · 토공 축으로만 · 기초잡석 사유(브레인 판정 ①③)
- 등록부 trench_depth_m(평균 터파기고 · 기본값 없음 · 비면 사유)
- 터파기 = (밑폭 버림 폭 + 여유 0.5×2 + 윗폭 1:0.5) ÷ 2 × 깊이 × 연장(울진 「암거구체 토공(2×2)」) · 되메우기 = 터파기 − 든 것(잡석·버림·구체) · 잔토처리 = 든 것 — destination earthwork(묶음에 안 듦 · 토공집계로만)
- 울진 원문은 잔토에 구체 전체 · 되메우기 한쪽만 — 옹벽 식(터파기 − 든 것)으로 맞추고 사유에 적음
- 기초잡석 사유 「근거 둘(예제·울진 집계표)에는 잡석 줄이 없음 — 사용자 확정 3차 ② 로 세움」

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016VBGFXB9AbJBwXP19z75Qq
2026-09-15 02:59:51 +09:00

132 lines
6.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""BOX암거 전개식 — m당 × 연장 (2026-09-15 브레인 12장 D 판정 ①~⑤).
근거 둘(구조 같음)
산림과임업기술(임도) 5장 「구조물도에 의한 수량산출(예)」 — 구체 = (외곽 − 유수구 + 헌치) ·
기초 폭×두께 · 거푸집 외벽·유수구·받침판·귀면 · 동바리 유수구 단면
실무 울진 기번3 구조도 「암거수량집계표(2×2)」 m당 — 레미콘 2.84 · 버림 0.28 · 유로폼 11.132 ·
합판 0.2 · 철근 0.336t(H13 0.179 · H16 0.157) · 동바리 3.92 · 비계 벽체 5.2 + 시종점 13.52
⚠ 철근은 크기별 식이 없어 **2×2 관측값만** · 날개벽·토공은 사유.
"""
from __future__ import annotations
import pytest
from B08_Quantity.B08_Quantity_Engine_Handoff import build_handoff
from B08_Quantity.B08_Quantity_Engine_Pipe import facility_structures
from B08_Quantity.B08_Quantity_Engine_UnitQuantity import build_table, expand
ULJIN_2X2 = {
"body_width_m": 2.0,
"body_height_m": 2.0,
"wall_thickness_m": 0.3,
"slab_thickness_m": 0.3,
"haunch_m": 0.2,
"blinding_thickness_m": 0.1,
"length_m": 10.0,
}
def _amounts(options: dict) -> tuple[dict, list[str], object]:
result = expand({"type_id": "box_culvert", "options": options}, {"box_culvert": "BOX암거"})
return (
{(c.name, c.spec): c.amount for c in result.components},
result.notes,
result,
)
def test_울진_2x2_m당_값을_연장에_곱한다() -> None:
got, notes, result = _amounts(ULJIN_2X2)
assert got[("콘크리트", "")] == pytest.approx(2.84 * 10)
assert got[("버림콘크리트", "")] == pytest.approx(0.28 * 10)
assert got[("유로폼", "")] == pytest.approx(
11.1314 * 10, abs=0.01
) # 원문 11.132(귀면 0.566 반올림)
assert got[("합판거푸집", "")] == pytest.approx(0.2 * 10)
assert got[("이형철근", "D13")] == pytest.approx(179 * 10)
assert got[("이형철근", "D16")] == pytest.approx(157 * 10)
assert got[("동바리", "")] == pytest.approx(3.92 * 10)
# 비계 — 벽체 m당 5.2 × 연장 + 시종점 두 면 13.52 는 한 번(원문은 m당에 넣었음 · 사유).
assert got[("비계", "")] == pytest.approx(5.2 * 10 + 13.52)
assert (result.billing_unit, result.billing_quantity) == ("m", 10.0)
text = " ".join(notes)
assert "관측" in text and "날개벽" in text
def test_예제_두께_기본값으로_구체가_줄어든다() -> None:
got, _notes, _r = _amounts({**ULJIN_2X2, "slab_thickness_m": 0.25})
# (2.6 × 2.5 2 × 2 + 0.2² ÷ 2 × 4) = 2.58㎥/m
assert got[("콘크리트", "")] == pytest.approx(2.58 * 10)
def test_2x2_가_아니면_철근은_사유() -> None:
got, notes, _r = _amounts({**ULJIN_2X2, "body_width_m": 3.0})
assert not [key for key in got if key[0] == "이형철근"]
assert any("크기별 식 없음" in note and "2×2" in note for note in notes)
assert got[("콘크리트", "")] > 0 # 나머지는 섬
def test_연장이_없으면_안_선다() -> None:
got, notes, _r = _amounts({k: v for k, v in ULJIN_2X2.items() if k != "length_m"})
assert got == {}
assert any("연장" in note for note in notes)
def test_치수를_안_적으면_기본값으로_서되_미확정() -> None:
rows = facility_structures(
[{"chainage_m": 50.0, "facility": "box_culvert", "options": {"length_m": 8.0}}]
)
box = next(row for row in rows if row["type_id"] == "box_culvert")
assert box["unconfirmed"] and "측벽 두께" in box["unconfirmed"]
assert box["options"]["wall_thickness_m"] == 0.3 and box["options"]["slab_thickness_m"] == 0.25
stored = facility_structures(
[{"chainage_m": 50.0, "facility": "box_culvert", "options": ULJIN_2X2}]
)
assert not next(r for r in stored if r["type_id"] == "box_culvert").get("unconfirmed")
def test_구체_터파기는_평균_터파기고로_토공_축에만_선다() -> None:
"""브레인 판정(2026-09-15 ③) — 칸 하나(평균 터파기고) · 구조물 터파기는 토공집계로만(이중계상).
밑폭 = 버림 폭 2.8 + 여유 0.5×2 · 옆면 1:0.5(울진 「암거구체 토공(2×2)」) · 깊이 0.8 ·
든 것 = 버림 폭 × (기초잡석 0.2 + 버림 0.1) + 구체 폭 × (0.8 0.3) · 되메우기 = 터파기 − 든 것.
"""
table = build_table(
[{"type_id": "box_culvert", "options": {**ULJIN_2X2, "trench_depth_m": 0.8}}]
)
box = table["structures"][0]
earth = {c["name"]: c for c in box["components"] if c["destination"] == "earthwork"}
assert earth["터파기"]["amount"] == pytest.approx((3.8 + 4.6) / 2 * 0.8 * 10)
filled = 2.8 * (0.2 + 0.1) + 2.6 * (0.8 - 0.3)
assert earth["잔토처리"]["amount"] == pytest.approx(filled * 10)
assert earth["되메우기"]["amount"] == pytest.approx(((3.8 + 4.6) / 2 * 0.8 - filled) * 10)
assert box["trench_depth_m"] == 0.8
# 잡석 사유 — 근거 둘에는 없고 사용자 확정으로 섬(브레인 판정 ①).
assert any("잡석" in note and "사용자 확정 3차" in note for note in box["notes"])
# 묶음(내역 줄)에는 터파기가 안 들어감.
row = next(
r for r in build_handoff(unit_quantity_table=table)["work_items"] if "BOX" in r["name"]
)
assert not [p for p in row["composite_parts"] or [] if "터파기" in str(p.get("name"))]
# 칸이 비면 터파기가 안 서고 사유.
empty = build_table([{"type_id": "box_culvert", "options": ULJIN_2X2}])["structures"][0]
assert not [c for c in empty["components"] if c["destination"] == "earthwork"]
assert any("평균 터파기고" in note for note in empty["notes"])
def test_인계_묶음이_조각을_세운다() -> None:
table = build_table(
facility_structures(
[{"chainage_m": 50.0, "facility": "box_culvert", "options": ULJIN_2X2}]
),
{"box_culvert": "BOX암거"},
)
row = next(
r for r in build_handoff(unit_quantity_table=table)["work_items"] if "BOX" in r["name"]
)
parts = {part["code"].split("#")[0]: part for part in row["composite_parts"] or []}
assert {"FP-12-01-01", "FP-12-04", "FP-12-38", "FP-12-03", "FP-12-20", "FP-12-19"} <= set(parts)
assert parts["FP-12-03"]["quantity"] == pytest.approx((179 + 157) * 10 / 1000)
assert parts["FP-12-20"]["quantity"] == pytest.approx(3.92 * 10)