- 등록부 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
132 lines
6.3 KiB
Python
132 lines
6.3 KiB
Python
"""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)
|