Files
Aislo/resources/tester/test_b08_box_culvert_unit.py
eomsangdonandClaude Opus 5 15551b9ea1 fix(b08): BOX 비계 단위 ㎡→㎥(공㎥) — 원단위 성분·묶음 조각 FP-12-19 · 사유 「비계 폭 1.0m 관측(울진)」(브레인 승인)
울진 단위 칸 「공/m3」 · 시종점 식의 ×1.00 은 연장일 수 없어 폭 1.0m 로 역산 · 값은 그대로(면 × 1.0) · B09 12-19 쪽은 안 건드림

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

137 lines
6.7 KiB
Python
Raw Permalink 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)
# 비계는 공㎥(12-19 밑수) — 울진 단위 칸 「공/m3」 · 시종점 식 ×1.00 은 연장일 수 없어 폭 1.0m.
scaffold = next(c for c in result.components if c.name == "비계")
assert scaffold.unit == "㎥" and "비계 폭 1.0m 관측(울진)" in scaffold.basis
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)
assert parts["FP-12-19"]["unit"] == "㎥"
assert parts["FP-12-19"]["quantity"] == pytest.approx(5.2 * 10 + 13.52)