- 등록부: 두께 칸 필수 걷고 「비면 B06 포장층 두께」 · 폭·확폭 면적 칸(비면 B06) · 줄눈 간격 제안 6m(교본 부록 4-7 4-6m · 소광) · 비닐·철망 고르기(기본값 없음) - 연장은 구간(끝 − 시작) — 물넘이로 나뉜 조각에 길이 칸이 복사되는 자리 - 묶음 12-06#두께 · 12-08 · 12-07-01(12-7-2 줄눈설치는 안 씀 — 소광 실무가 컷팅) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016VBGFXB9AbJBwXP19z75Qq
159 lines
6.7 KiB
Python
159 lines
6.7 KiB
Python
"""콘크리트 포장 전개식 — 면적 × 두께 · 거푸집 양면 · 수축줄눈 (2026-09-15 브레인 포장 둘 ①~⑤·ⓐ).
|
||
|
||
근거
|
||
임도기술교본 3-2 기본설계 — 포장 폭 3.0m · 두께 0.2m · 곡선부 확폭
|
||
임도기술교본 부록 4-7 7-1 3.5.1 — 「수축줄눈의 간격은 4-6m를 기준」
|
||
산림품셈 12-6 콘크리트 포장(인력) · 12-7-1 포장절단 · 12-8 콘크리트 포장 거푸집
|
||
실무 울진소광 「포장및난간개거」 — 면적 = B × L · 거푸집(1면기준) = 2L ·
|
||
수축줄눈 = 내림(L÷6) × B (103m·B 3.5 → 59.5 · 418m → 241.5) · 「포장확폭」 줄눈 = 면적 ÷ 6
|
||
판정 — 폭·확폭·두께는 **B06 에서 잇고 칸이 이김**(한 값 원칙) · 비닐·철망은 고르기(기본값 없음).
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import pytest
|
||
|
||
from B05_Profile.B05_Profile_Structures_Schema import structure_type_map
|
||
from B08_Quantity.B08_Quantity_Engine_Handoff import build_handoff
|
||
from B08_Quantity.B08_Quantity_Engine_UnitQuantity import build_table
|
||
from B08_Quantity.B08_Quantity_Engine_UnitQuantity_Pavement import pavement_from_designs
|
||
|
||
FILLED = {
|
||
"width_m": 3.0,
|
||
"widening_area_m2": 6.0,
|
||
"thickness_cm": 20,
|
||
"joint_spacing_m": 6,
|
||
"separation_sheet": "있음",
|
||
"wire_mesh": "없음",
|
||
}
|
||
|
||
|
||
def _row(options: dict, start: float = 100.0, end: float = 130.0) -> dict:
|
||
structure = {
|
||
"structure_id": "p1",
|
||
"type_id": "pavement_concrete",
|
||
"start_m": start,
|
||
"end_m": end,
|
||
# 구간을 물넘이로 나누면 길이 칸(10)이 조각마다 그대로 복사됨 — 연장은 구간에서 읽음.
|
||
"options": {"length_m": 10, **options},
|
||
}
|
||
return build_table([structure], {"pavement_concrete": "콘크리트 포장"})["structures"][0]
|
||
|
||
|
||
def test_칸이_찬_구간은_면적_두께_거푸집_줄눈이_선다() -> None:
|
||
row = _row(FILLED)
|
||
got = {c["name"]: c for c in row["components"]}
|
||
area = 3.0 * 30 + 6.0
|
||
assert (row["billing_unit"], row["billing_quantity"]) == ("㎡", pytest.approx(area))
|
||
assert got["콘크리트"]["amount"] == pytest.approx(area * 0.2)
|
||
assert got["콘크리트"]["destination"] == "material"
|
||
assert got["포장거푸집"]["amount"] == pytest.approx(2 * 30)
|
||
# 내림(30 ÷ 6) × 3.0 + 확폭 6 ÷ 6
|
||
assert got["수축줄눈"]["amount"] == pytest.approx(5 * 3.0 + 1.0)
|
||
assert got["비닐"]["amount"] == pytest.approx(area) and "철망" not in got
|
||
text = " ".join(row["notes"])
|
||
assert "12-7-2 줄눈설치는 안 씀 — 소광 실무가 컷팅" in text
|
||
|
||
|
||
@pytest.mark.parametrize(("length", "joints"), [(103.0, 59.5), (418.0, 241.5)])
|
||
def test_수축줄눈은_소광_관측과_같다(length: float, joints: float) -> None:
|
||
row = _row({**FILLED, "width_m": 3.5, "widening_area_m2": 0}, 0.0, length)
|
||
got = {c["name"]: c["amount"] for c in row["components"]}
|
||
assert got["수축줄눈"] == pytest.approx(joints)
|
||
|
||
|
||
def test_비닐_철망을_안_고르면_사유() -> None:
|
||
row = _row({k: v for k, v in FILLED.items() if k not in ("separation_sheet", "wire_mesh")})
|
||
names = {c["name"] for c in row["components"]}
|
||
assert "비닐" not in names and "철망" not in names and "콘크리트" in names
|
||
text = " ".join(row["notes"])
|
||
assert "비닐" in text and "철망" in text and "12-6" in text
|
||
|
||
|
||
DESIGNS = [
|
||
{"chainage_m": 90.0, "design": {"carriageway_standard_width_m": 3.0, "paved": False}},
|
||
{
|
||
"chainage_m": 100.0,
|
||
"design": {
|
||
"carriageway_standard_width_m": 3.0,
|
||
"widening_right_m": 1.0,
|
||
"paved": True,
|
||
"pavement_thickness_m": 0.2,
|
||
},
|
||
},
|
||
{
|
||
"chainage_m": 120.0,
|
||
"design": {
|
||
"carriageway_standard_width_m": 3.0,
|
||
"widening_left_m": 0.5,
|
||
"paved": True,
|
||
"pavement_thickness_m": 0.2,
|
||
},
|
||
},
|
||
{
|
||
"chainage_m": 140.0,
|
||
"design": {"carriageway_standard_width_m": 3.0, "paved": True, "pavement_thickness_m": 0.2},
|
||
},
|
||
]
|
||
|
||
|
||
def _structure(**options) -> dict:
|
||
return {
|
||
"type_id": "pavement_concrete",
|
||
"start_m": 100.0,
|
||
"end_m": 130.0,
|
||
"options": {"length_m": 10, **options},
|
||
}
|
||
|
||
|
||
def test_빈_칸은_B06_폭_확폭_두께를_잇는다() -> None:
|
||
filled = pavement_from_designs(_structure(), DESIGNS)
|
||
options = filled["options"]
|
||
assert options["width_m"] == pytest.approx(3.0)
|
||
assert options["thickness_cm"] == pytest.approx(20.0)
|
||
# 100 → 1.0 · 120 → 0.5 · 130 → 0.25(120~140 사이 보간): (1.0+0.5)/2×20 + (0.5+0.25)/2×10
|
||
assert options["widening_area_m2"] == pytest.approx(15.0 + 3.75)
|
||
assert any("B06" in note for note in filled["notes"])
|
||
# 상세 칸은 배치 폼에 없어 저장에 없음 — 등록부 제안값 6m 을 사유와 함께 채움.
|
||
assert options["joint_spacing_m"] == 6 and any("4-6m" in note for note in filled["notes"])
|
||
|
||
|
||
def test_칸을_적으면_칸이_이긴다() -> None:
|
||
filled = pavement_from_designs(_structure(width_m=3.5, widening_area_m2=0), DESIGNS)
|
||
assert filled["options"]["width_m"] == 3.5 and filled["options"]["widening_area_m2"] == 0
|
||
assert filled["options"]["thickness_cm"] == pytest.approx(20.0)
|
||
|
||
|
||
def test_B06_설계도_칸도_없으면_안_선다() -> None:
|
||
filled = pavement_from_designs(_structure(), [])
|
||
row = build_table([filled])["structures"][0]
|
||
assert row["components"] == []
|
||
assert any("폭" in note for note in row["notes"])
|
||
|
||
|
||
def test_인계_묶음은_12_6_두께_갈래_12_8_12_7_1() -> None:
|
||
table = build_table(
|
||
[{**_structure(**FILLED), "structure_id": "p1"}], {"pavement_concrete": "콘크리트 포장"}
|
||
)
|
||
row = next(
|
||
r for r in build_handoff(unit_quantity_table=table)["work_items"] if "포장" in r["name"]
|
||
)
|
||
parts = {part["code"]: part for part in row["composite_parts"] or []}
|
||
area = 3.0 * 30 + 6.0
|
||
assert parts["FP-12-06#20㎝"]["quantity"] == pytest.approx(area * 0.2)
|
||
assert parts["FP-12-08"]["quantity"] == pytest.approx(60.0)
|
||
assert parts["FP-12-07-01"]["quantity"] == pytest.approx(16.0)
|
||
|
||
|
||
def test_등록부_칸() -> None:
|
||
options = {o.key: o for o in structure_type_map()["pavement_concrete"].options}
|
||
assert not options["thickness_cm"].required and "B06" in options["thickness_cm"].empty_means
|
||
assert "B06" in options["width_m"].empty_means and "3.5" in options["width_m"].empty_means
|
||
assert "B06" in options["widening_area_m2"].empty_means
|
||
assert (
|
||
options["joint_spacing_m"].default == 6
|
||
and "4-6m" in options["joint_spacing_m"].default_basis
|
||
)
|
||
for key in ("separation_sheet", "wire_mesh"):
|
||
assert options[key].default is None and options[key].choices == ["있음", "없음"]
|