feat(b08): 운반 묶음 함수 — 총괄자재 → 운반 행 · B09 운반공 줄 같은 원천 (251-1)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LLCk2y5xE8YGVgjbTntWsY
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
"""B08 운반 행(PLAN 251-1 · 사용자 159번) — 총괄자재표 → 운반 묶음.
|
||||
|
||||
거리이정표 행 · B09 운반공 줄의 한 원천.
|
||||
묶음 = 총괄 줄 중 수량 있는 것 · 같은 묶음(이형철근 · 레미콘 · 깬돌 · 파형강관 ·
|
||||
커플링밴드)은 한 행 · 골재(모래 · 자갈 · 막자갈 · 혼합석) · 그 밖 줄은 따로 ·
|
||||
채집석 공제 뺌 · 장비 두 행(중기 · 기계) 늘.
|
||||
옛 B09 운반 6줄 키(`haul:1~6` · 중기 · 기계 · 모래 · 자갈 · 시멘트 · 철근)는 그 묶음에 그대로 —
|
||||
사용자 설계내역서 덮개가 고아가 되지 않게 · 그 여섯은 수량이 없어도 늘 있음.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from B08_Quantity.B08_Quantity_Material_Calc import DEDUCT_KEY
|
||||
|
||||
#: 장비 — (묶음 키, 이름, 단위, 옛 번호)
|
||||
EQUIP: list[tuple[str, str, str, int]] = [
|
||||
("equip_heavy", "중기", "대", 1),
|
||||
("equip_machine", "기계", "대", 2),
|
||||
]
|
||||
#: 옛 B09 운반 줄 번호 — 묶음 키 → n(`haul:n`)
|
||||
LEGACY = {
|
||||
"equip_heavy": 1,
|
||||
"equip_machine": 2,
|
||||
"sand": 3,
|
||||
"gravel": 4,
|
||||
"cement": 5,
|
||||
"rebar": 6,
|
||||
}
|
||||
#: 총괄 줄 키 머리 → (묶음 키, 이름) — 여러 규격 = 한 행
|
||||
PREFIX = [
|
||||
("rebar_", "rebar", "철근"),
|
||||
("remicon_", "remicon", "레미콘"),
|
||||
("stone_buy_", "stone", "깬돌"),
|
||||
("pipe_", "pipe", "파형강관"),
|
||||
("coupling_", "coupling", "커플링밴드"),
|
||||
]
|
||||
|
||||
|
||||
#: 총괄 고정 줄 이름 · 단위(줄에 이름이 없어도 같은 이름 · 옛 여섯 늘 행)
|
||||
NAMES = {
|
||||
"sand": ("모래", "㎥"),
|
||||
"gravel": ("자갈", "㎥"),
|
||||
"rubble": ("막자갈", "㎥"),
|
||||
"mixed_aggregate": ("혼합석", "㎥"),
|
||||
"cement": ("시멘트", "대"),
|
||||
"rebar": ("철근", "ton"),
|
||||
"wire_mesh": ("와이어메쉬", "㎡"),
|
||||
"sod": ("떼", "㎡"),
|
||||
"revet_block": ("호안블럭", "㎡"),
|
||||
"flume_400c": ("수로관", "본"),
|
||||
"steel_grating": ("스틸그레이팅", "EA"),
|
||||
}
|
||||
|
||||
|
||||
def _group(row: dict[str, Any]) -> tuple[str, str]:
|
||||
key = str(row.get("key") or "")
|
||||
for head, gkey, name in PREFIX:
|
||||
if key.startswith(head):
|
||||
return gkey, name
|
||||
if key in NAMES:
|
||||
return key, NAMES[key][0]
|
||||
return key, str(row.get("group") or row.get("spec") or key) # 맞춤 없는 사본 자재 = 자재 이름
|
||||
|
||||
|
||||
def bill_key(gkey: str) -> str:
|
||||
"""B09 운반 줄 키 — 옛 여섯은 `haul:n` 그대로 · 새 묶음 = `haul:<묶음 키>`."""
|
||||
return f"haul:{LEGACY[gkey]}" if gkey in LEGACY else f"haul:{gkey}"
|
||||
|
||||
|
||||
def haul_groups(materials: dict[str, Any] | None) -> list[dict[str, Any]]:
|
||||
"""총괄자재표(`material.rows`) → 운반 행 [{key, name, unit, qty}].
|
||||
|
||||
장비 둘 + 묶음(차례 = 총괄 줄 차례)."""
|
||||
rows = ((materials or {}).get("material") or {}).get("rows") or []
|
||||
out: dict[str, dict[str, Any]] = {
|
||||
g: {"key": g, "name": n, "unit": u, "qty": None} for g, n, u, _ in EQUIP
|
||||
}
|
||||
for row in rows:
|
||||
if row.get("key") == DEDUCT_KEY: # 채집석 공제(뺄 수량)
|
||||
continue
|
||||
gkey, name = _group(row)
|
||||
amount = row.get("amount")
|
||||
has = isinstance(amount, (int, float)) and amount > 0
|
||||
slot = out.get(gkey)
|
||||
if slot is None:
|
||||
if not has and gkey not in LEGACY:
|
||||
continue # 수량 없는 새 묶음 = 행 없음
|
||||
slot = out[gkey] = {
|
||||
"key": gkey,
|
||||
"name": name,
|
||||
"unit": row.get("unit") or "",
|
||||
"qty": None,
|
||||
}
|
||||
if has and (slot["unit"] in ("", row.get("unit"))):
|
||||
slot["unit"] = row.get("unit") or slot["unit"]
|
||||
slot["qty"] = round((slot["qty"] or 0) + float(amount), 6)
|
||||
for gkey in LEGACY: # 옛 여섯 = 늘
|
||||
if gkey not in out:
|
||||
name, unit = NAMES[gkey]
|
||||
out[gkey] = {"key": gkey, "name": name, "unit": unit, "qty": None}
|
||||
return list(out.values())
|
||||
@@ -14,6 +14,7 @@ from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from B08_Quantity.B08_Quantity_Haul import bill_key, haul_groups
|
||||
from B08_Quantity.B08_Quantity_Material_Calc import _structure_row
|
||||
|
||||
WHY_EARTH = "조합 밖 토공 산근 — 연결 없음"
|
||||
@@ -46,16 +47,6 @@ EARTH: list[tuple[str, str, str, tuple[str, str, str | None] | None]] = [
|
||||
STRUCTURE_SECTION = {"pipe:revet|": "2.1", "pipe:chute|": "2.3"}
|
||||
SKIP_COLUMNS = ("pipe:cp|",)
|
||||
|
||||
#: 운반공 — (명칭, 단위, B08 총괄자재 줄 키(합) | None)
|
||||
HAUL: list[tuple[str, str, tuple[str, ...] | None]] = [
|
||||
("중기운반", "대", None),
|
||||
("기계운반", "대", None),
|
||||
("모래운반", "㎥", ("sand",)),
|
||||
("자갈운반", "㎥", ("gravel",)),
|
||||
("시멘트운반", "대", ("cement",)),
|
||||
("철근운반", "ton", ("rebar_d13", "rebar_d16", "rebar_d19", "rebar_d22", "rebar_d25")),
|
||||
]
|
||||
|
||||
|
||||
def _same(row: dict[str, Any], want: tuple[str, str, str | None]) -> bool:
|
||||
group, kind, spec = want
|
||||
@@ -114,21 +105,17 @@ def structure_items(summary: dict[str, Any] | None) -> list[dict[str, Any]]:
|
||||
|
||||
|
||||
def haul_items(materials: dict[str, Any] | None) -> list[dict[str, Any]]:
|
||||
rows = ((materials or {}).get("material") or {}).get("rows") or []
|
||||
amount = {row.get("key"): row.get("amount") for row in rows}
|
||||
"""운반공 줄 — B08 운반 묶음(251-1 · 장비 둘 + 총괄자재 묶음 · 옛 키 haul:1~6 그대로)."""
|
||||
out = []
|
||||
for n, (name, unit, keys) in enumerate(HAUL, 1):
|
||||
qty = None
|
||||
if keys:
|
||||
found = [amount[k] for k in keys if amount.get(k) is not None]
|
||||
qty = round(sum(found), 6) if found else None
|
||||
for g in haul_groups(materials):
|
||||
qty = g["qty"]
|
||||
out.append(
|
||||
{
|
||||
"key": f"haul:{n}",
|
||||
"key": bill_key(g["key"]),
|
||||
"section": "3",
|
||||
"name": name,
|
||||
"name": f"{g['name']}운반",
|
||||
"spec": "",
|
||||
"unit": unit,
|
||||
"unit": g["unit"],
|
||||
"qty": qty,
|
||||
"note": "B08 총괄자재" if qty is not None else "",
|
||||
"ref": None,
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
"""PLAN 251-1 — 총괄자재표 → 운반 묶음(거리이정표 행 · B09 운반공 줄 한 원천)."""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from B08_Quantity.B08_Quantity_Haul import bill_key, haul_groups # noqa: E402
|
||||
from B09_Estimation.B09_Estimation_Bill_Items import haul_items # noqa: E402
|
||||
|
||||
|
||||
def _mats(rows):
|
||||
return {"material": {"rows": rows}}
|
||||
|
||||
|
||||
ROWS = [
|
||||
{"key": "sand", "group": "골재", "spec": "모래", "unit": "㎥", "amount": 16},
|
||||
{"key": "gravel", "group": "골재", "spec": "자갈", "unit": "㎥", "amount": None},
|
||||
{"key": "mixed_aggregate", "group": "골재", "spec": "혼합석", "unit": "㎥", "amount": 3.5},
|
||||
{"key": "remicon_25_24_15", "group": "레미콘", "spec": "25-24-15", "unit": "㎥", "amount": 2},
|
||||
{"key": "remicon_25_21_80", "group": "레미콘", "spec": "25-21-80", "unit": "㎥", "amount": 1},
|
||||
{"key": "rebar_d13", "group": "이형철근", "spec": "D13mm", "unit": "ton", "amount": 0.5},
|
||||
{"key": "rebar_d16", "group": "이형철근", "spec": "D16mm", "unit": "ton", "amount": 0.25},
|
||||
{"key": "cement", "group": "시멘트", "spec": "40kg/대", "unit": "대", "amount": None},
|
||||
{"key": "pipe_600", "group": "파형강관", "spec": "Φ600mm", "unit": "m", "amount": 12},
|
||||
{"key": "picked_deduct", "group": "채집석 공제", "unit": "㎥", "amount": 4},
|
||||
{"key": "MT000777", "group": "앵커볼트", "spec": "M16", "unit": "EA", "amount": 8},
|
||||
{"key": "sod", "group": "떼", "unit": "㎡", "amount": 0},
|
||||
]
|
||||
|
||||
|
||||
def test_묶음_장비_옛_여섯_늘_수량_없는_새_묶음_없음():
|
||||
got = {g["key"]: (g["name"], g["unit"], g["qty"]) for g in haul_groups(_mats(ROWS))}
|
||||
assert list(got)[:2] == ["equip_heavy", "equip_machine"] # 장비 둘 맨 앞
|
||||
assert got["sand"] == ("모래", "㎥", 16) and got["gravel"] == ("자갈", "㎥", None)
|
||||
assert got["mixed_aggregate"] == ("혼합석", "㎥", 3.5)
|
||||
assert got["remicon"] == ("레미콘", "㎥", 3) and got["rebar"] == ("철근", "ton", 0.75)
|
||||
assert got["cement"] == ("시멘트", "대", None) # 옛 여섯 = 수량 없어도 늘
|
||||
assert got["pipe"] == ("파형강관", "m", 12) and got["MT000777"] == ("앵커볼트", "EA", 8)
|
||||
assert "picked_deduct" not in got and "sod" not in got # 공제 · 수량 0 새 묶음 = 행 없음
|
||||
|
||||
|
||||
def test_B09_운반_줄_옛_키_그대로_새_묶음_키():
|
||||
items = {i["key"]: i for i in haul_items(_mats(ROWS))}
|
||||
assert [bill_key(k) for k in ("equip_heavy", "sand", "rebar")] == ["haul:1", "haul:3", "haul:6"]
|
||||
assert items["haul:3"]["name"] == "모래운반" and items["haul:3"]["qty"] == 16
|
||||
assert items["haul:remicon"]["name"] == "레미콘운반" and items["haul:remicon"]["unit"] == "㎥"
|
||||
assert all(i["section"] == "3" for i in items.values())
|
||||
assert {"haul:1", "haul:2", "haul:3", "haul:4", "haul:5", "haul:6"} <= set(items)
|
||||
Reference in New Issue
Block a user