계획서 4-13. 소광리 `07-구조도-소광리.xlsx` 다섯 탭의 **개소당 소계를 그대로** 옮김 (Ø800 A·C · Ø1000 A·C · Ø800 A+집수정). 비례로 늘리지 않음 — 그 규격에서만 맞는 값임. - 원단위표에 다섯 줄 추가. 거푸집 종류가 탭마다 갈려 이름을 뭉개지 않음 — Ø800 A·C 는 **유로폼**, Ø1000 과 A+집수정은 **합판거푸집(4회)**. - ⚠ 철근·면목 줄은 원본 날개벽 탭에 **없음**(집수정구조도 탭에만 있음). 지어내지 않음. - 등록부·배치 폼에 「관보호공 날개벽」 칸(A-TYPE·C-TYPE·A-TYPE+집수정). 비우면 안 셈. - ⚠ 겹쳐 세는 자리 — 「A-TYPE+집수정」은 집수정을 품은 형식이라 집수정 형식도 차 있으면 **사유로 알림**(값은 안 고침). 원단위표 `double_count_rules` 와 짝. ⓘ 집수정 Ø800 은 **이미 표에 있었음**(`pipe_inlet_basin` □형·콘크리트·800) — 다시 만들지 않음. 화면 실측 — 배수관 폼에 칸이 뜨고 선택지 셋 확인. 엔진 실행으로 날개벽 줄이 따로 서고 터파기 13.104 · 콘크리트 2.0595 · 유로폼 12.5376 이 나오는 것까지 봄. 시험 일곱 추가, 전체 1218 통과·실패 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
120 lines
4.9 KiB
Python
120 lines
4.9 KiB
Python
"""관보호공 날개벽 원단위 — 소광리 원본 다섯 탭에서 옮긴 값 (2026-09-09, 계획서 4-13).
|
|
|
|
값은 **개소당·붙박이**다. 원본 탭(`07-구조도-소광리.xlsx`)의 소계를 그대로 옮겼고
|
|
비례로 늘리지 않는다 — Ø800·Ø1000, A·C-TYPE, 그리고 「A-TYPE+집수정」 다섯뿐이다.
|
|
|
|
⚠ 겹쳐 세는 자리 하나 — 「A-TYPE+집수정」은 집수정을 **품은** 형식이라 집수정 줄을 따로
|
|
세우면 한 개소를 두 번 센다. 값을 고치지 않고 **사유로 드러낸다.**
|
|
⚠ 철근·면목 줄은 원본 날개벽 탭에 **없다**(집수정구조도 탭에만 있음) — 지어내지 않는다.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
if str(ROOT) not in sys.path:
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
from B08_Quantity.B08_Quantity_Engine_ObservedUnit import load_observed_table # noqa: E402
|
|
from B08_Quantity.B08_Quantity_Engine_UnitQuantity import ( # noqa: E402
|
|
ATTACHMENTS,
|
|
OBSERVED_SPEC_KEYS,
|
|
attachments_of,
|
|
wing_wall_double_count,
|
|
)
|
|
|
|
|
|
def _registry_choices() -> list[str]:
|
|
payload = json.loads(
|
|
(ROOT / "B05_Profile" / "B05_Profile_Structure_Types.json").read_text(encoding="utf-8")
|
|
)
|
|
types = payload if isinstance(payload, list) else payload["types"]
|
|
pipe = next(item for item in types if item["type_id"] == "pipe")
|
|
option = next(o for o in pipe["options"] if o["key"] == "wing_wall_type")
|
|
return list(option["choices"])
|
|
|
|
|
|
def test_다섯_형식이_표에_있다() -> None:
|
|
table = load_observed_table()
|
|
specs = {
|
|
(spec.get("wing_wall_type"), spec.get("pipe_diameter_mm"))
|
|
for spec in table.specs_for("pipe_wing_wall")
|
|
}
|
|
assert specs == {
|
|
("A-TYPE", "800"),
|
|
("C-TYPE", "800"),
|
|
("A-TYPE", "1000"),
|
|
("C-TYPE", "1000"),
|
|
("A-TYPE+집수정", "800"),
|
|
}
|
|
|
|
|
|
def test_원본_소계와_같다() -> None:
|
|
"""Ø800 A-TYPE — 원본 탭 소계(터파기 13.104 · 저판 0.952 + 벽체 1.1075 …)."""
|
|
found = load_observed_table().find(
|
|
"pipe_wing_wall", {"wing_wall_type": "A-TYPE", "pipe_diameter_mm": "800"}
|
|
)
|
|
assert found is not None
|
|
amounts = {c["name"]: c["amount"] for c in found["components"]}
|
|
assert amounts["터파기"] == 13.104
|
|
assert amounts["되메우기"] == 6.504
|
|
assert amounts["잔토처리"] == 6.61
|
|
assert amounts["기초잡석"] == 1.152
|
|
assert abs(amounts["콘크리트"] - (0.952 + 1.1075)) < 1e-9
|
|
assert abs(amounts["유로폼"] - (1.92 + 10.6176)) < 1e-9
|
|
|
|
|
|
def test_거푸집_종류가_탭마다_다르다() -> None:
|
|
"""Ø800 A 는 유로폼, Ø1000 은 합판(4회) — 단가가 갈리므로 이름을 뭉개지 않는다."""
|
|
table = load_observed_table()
|
|
small = table.find("pipe_wing_wall", {"wing_wall_type": "A-TYPE", "pipe_diameter_mm": "800"})
|
|
big = table.find("pipe_wing_wall", {"wing_wall_type": "A-TYPE", "pipe_diameter_mm": "1000"})
|
|
assert "유로폼" in [c["name"] for c in small["components"]]
|
|
assert "합판거푸집" in [c["name"] for c in big["components"]]
|
|
|
|
|
|
def test_철근_면목은_안_지어낸다() -> None:
|
|
table = load_observed_table()
|
|
for spec in table.specs_for("pipe_wing_wall"):
|
|
found = table.find("pipe_wing_wall", spec)
|
|
names = [c["name"] for c in found["components"]]
|
|
assert not any("철근" in name or "면목" in name for name in names), spec
|
|
|
|
|
|
def test_형식을_고르면_줄이_선다() -> None:
|
|
rows = attachments_of(
|
|
{
|
|
"structure_id": "s1",
|
|
"type_id": "pipe",
|
|
"options": {"wing_wall_type": "A-TYPE", "pipe_diameter_mm": "800"},
|
|
}
|
|
)
|
|
assert [row["type_id"] for row in rows] == ["pipe_wing_wall"]
|
|
# 안 고르면 줄이 없다 — 안 놓은 것과 같다.
|
|
assert attachments_of({"structure_id": "s2", "type_id": "pipe", "options": {}}) == []
|
|
|
|
|
|
def test_집수정_결합형은_두_번_세는_것을_알린다() -> None:
|
|
assert wing_wall_double_count({"wing_wall_type": "A-TYPE+집수정"}) is None
|
|
warn = wing_wall_double_count(
|
|
{"wing_wall_type": "A-TYPE+집수정", "inlet_basin_form": "□형(기본형)"}
|
|
)
|
|
assert warn is not None and "두 번" in warn
|
|
assert (
|
|
wing_wall_double_count({"wing_wall_type": "A-TYPE", "inlet_basin_form": "□형(기본형)"})
|
|
is None
|
|
)
|
|
|
|
|
|
def test_등록부_선택지와_표가_한_벌이다() -> None:
|
|
"""칸에서 고를 수 있는데 표에 없으면 「원단위 미확보」로 끝난다 — 둘을 붙여 둔다."""
|
|
table_types = {
|
|
spec.get("wing_wall_type") for spec in load_observed_table().specs_for("pipe_wing_wall")
|
|
}
|
|
assert set(_registry_choices()) == table_types
|
|
assert OBSERVED_SPEC_KEYS["pipe_wing_wall"] == ("wing_wall_type", "pipe_diameter_mm")
|
|
assert any(item[0] == "pipe_wing_wall" for item in ATTACHMENTS["pipe"])
|