- 「운반」 덧붙임 없앰 · 빈 항목명 = 거래처 · 그것도 없으면 묶음 이름 · 「운반」
- 자동 상자 둘째 행부터 `haul:{묶음}.n` 개별 줄(묶음 줄 바로 뒤 · 수량 빈칸)
- 시험 기대값 고침(253_1 · 251_1 · 145_3) · 새 시험 test_261_4
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JywHxQuWPk6UYXTebyrBoU
166 lines
6.5 KiB
Python
166 lines
6.5 KiB
Python
"""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())
|
|
|
|
|
|
def with_haul_routes(data: dict[str, Any], groups: list[dict[str, Any]]) -> dict[str, Any]:
|
|
"""거리이정표 자료에 운반 행 상자 자동(251-2) — 묶음마다 거래처 상자가 없으면 넣음.
|
|
|
|
거래처 · 주소 빈칸 · 선 없음(linked false) · 항목 = 자재 이름 ·
|
|
저장은 [저장] 때(읽기만 합침)."""
|
|
have = {r.get("haul") for r in data.get("routes") or []}
|
|
routes = list(data.get("routes") or [])
|
|
for g in groups:
|
|
if g["key"] in have:
|
|
continue
|
|
routes.append(
|
|
{
|
|
"id": f"h-{g['key']}"[:40],
|
|
"name": "",
|
|
"address": "",
|
|
"to": "site",
|
|
"linked": False,
|
|
"dir": None,
|
|
"haul": g["key"],
|
|
"legs": [{"road": None, "km": None}],
|
|
"rows": [{"item": g["name"], "dest": "start", "note": ""}],
|
|
}
|
|
)
|
|
return {**data, "routes": routes}
|
|
|
|
|
|
def route_bill_key(route: dict[str, Any], index: int = 0) -> str:
|
|
"""거리이정표 행 → B09 운반공 줄 키(253-1) — 자동 상자 = 옛 키 · 사용자 상자 = `haul:r-{id}` ·
|
|
한 상자 둘째 행부터 `.{n}`(261-4 자동 상자도 · 경로 id · 묶음 키에 「.」 없음 — 안 겹침)."""
|
|
tail = f".{index}" if index else ""
|
|
if route.get("haul"):
|
|
return bill_key(str(route["haul"])) + tail
|
|
return f"haul:r-{route.get('id')}" + tail
|
|
|
|
|
|
def haul_rows(data: dict[str, Any], table: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
"""거리이정표 표 행 하나 = 운반공 줄 하나(253-1 · 261-4 단가연결 무관) —
|
|
[{key, name, ref, user, haul, L1, L2}] · name = 행 항목명 그대로(없으면 거래처 · 그것도 없으면 빈칸) ·
|
|
L1 = 포장 계(2차 + 1차 + 포장) · L2 = 비포장(`compute_milestone` 결과) ·
|
|
user = 사용자 상자 · haul = 자동 상자 묶음 키(첫 행은 총괄자재 묶음 줄 이름 · 거리)."""
|
|
routes = {r.get("id"): r for r in data.get("routes") or []}
|
|
out, seen = [], set()
|
|
for row in table:
|
|
route = routes.get(row.get("route")) or {}
|
|
key = route_bill_key(route, int(row.get("index") or 0))
|
|
if key in seen:
|
|
continue
|
|
seen.add(key)
|
|
out.append(
|
|
{
|
|
"key": key,
|
|
"name": str(row.get("item") or "").strip() or str(route.get("name") or "").strip(),
|
|
"ref": route.get("ref"),
|
|
"user": not route.get("haul"),
|
|
"haul": route.get("haul"),
|
|
"L1": row.get("paved"),
|
|
"L2": row.get("unpaved"),
|
|
}
|
|
)
|
|
return out
|