fix(b09): 운반공 줄 이름 = 거리이정표 항목명 그대로 · 행마다 개별 줄 (261-4)
- 「운반」 덧붙임 없앰 · 빈 항목명 = 거래처 · 그것도 없으면 묶음 이름 · 「운반」
- 자동 상자 둘째 행부터 `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
This commit is contained in:
@@ -131,16 +131,18 @@ def with_haul_routes(data: dict[str, Any], groups: list[dict[str, Any]]) -> dict
|
||||
|
||||
def route_bill_key(route: dict[str, Any], index: int = 0) -> str:
|
||||
"""거리이정표 행 → B09 운반공 줄 키(253-1) — 자동 상자 = 옛 키 · 사용자 상자 = `haul:r-{id}` ·
|
||||
한 상자 둘째 행부터 `haul:r-{id}.{n}`(경로 id 에 「.」 없음 — 안 겹침)."""
|
||||
한 상자 둘째 행부터 `.{n}`(261-4 자동 상자도 · 경로 id · 묶음 키에 「.」 없음 — 안 겹침)."""
|
||||
tail = f".{index}" if index else ""
|
||||
if route.get("haul"):
|
||||
return bill_key(str(route["haul"]))
|
||||
return f"haul:r-{route.get('id')}" + (f".{index}" if index else "")
|
||||
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) — [{key, name, ref, user, L1, L2}] ·
|
||||
"""거리이정표 표 행 하나 = 운반공 줄 하나(253-1 · 261-4 단가연결 무관) —
|
||||
[{key, name, ref, user, haul, L1, L2}] · name = 행 항목명 그대로(없으면 거래처 · 그것도 없으면 빈칸) ·
|
||||
L1 = 포장 계(2차 + 1차 + 포장) · L2 = 비포장(`compute_milestone` 결과) ·
|
||||
user = 사용자 상자(자동 상자는 총괄자재 묶음 줄이 이미 있음 · 둘째 행은 안 씀)."""
|
||||
user = 사용자 상자 · haul = 자동 상자 묶음 키(첫 행은 총괄자재 묶음 줄 이름 · 거리)."""
|
||||
routes = {r.get("id"): r for r in data.get("routes") or []}
|
||||
out, seen = [], set()
|
||||
for row in table:
|
||||
@@ -149,13 +151,13 @@ def haul_rows(data: dict[str, Any], table: list[dict[str, Any]]) -> list[dict[st
|
||||
if key in seen:
|
||||
continue
|
||||
seen.add(key)
|
||||
item = str(row.get("item") or "").strip()
|
||||
out.append(
|
||||
{
|
||||
"key": key,
|
||||
"name": f"{item}운반" if item else "운반",
|
||||
"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"),
|
||||
}
|
||||
|
||||
@@ -111,48 +111,45 @@ def haul_items(
|
||||
routes: list[dict[str, Any]] | None = None,
|
||||
) -> list[dict[str, Any]]:
|
||||
"""운반공 줄 — B08 운반 묶음(251-1 · 장비 둘 + 총괄자재 묶음 · 옛 키 haul:1~6 그대로) +
|
||||
거리이정표 사용자 상자 행(253-1 · `B08_Quantity_Haul.haul_rows` · 수량 빈칸) ·
|
||||
거리이정표 행(253-1 · `B08_Quantity_Haul.haul_rows` · 행 하나 = 줄 하나 · 둘째 행부터 수량 빈칸) ·
|
||||
이름 = 거리이정표 항목명 그대로(261-4 · 「운반」 안 붙임 · 비면 거래처 · 그것도 없으면 묶음 이름 · 「운반」) ·
|
||||
연결 = 거리이정표 단가연결(251-5 · `refs{줄 키: ref}` · 설계내역서 연결 덮개가 이김) ·
|
||||
`dist{L1, L2}` = 그 행 거리(253-3 「운반 단가」 모달 · 없으면 None)."""
|
||||
dists = {r["key"]: {"L1": r.get("L1"), "L2": r.get("L2")} for r in routes or []}
|
||||
rows = routes or []
|
||||
by_key = {r["key"]: r for r in rows}
|
||||
extra: dict[str, list[dict[str, Any]]] = {} # 자동 상자 둘째 행부터 — 묶음 줄 바로 뒤
|
||||
for r in rows:
|
||||
if r.get("haul") and r["key"] != bill_key(str(r["haul"])):
|
||||
extra.setdefault(str(r["haul"]), []).append(r)
|
||||
|
||||
def line(key: str, name: str, unit: str, qty: Any, note: str) -> dict[str, Any]:
|
||||
ref = (refs or {}).get(key)
|
||||
route = by_key.get(key) or {}
|
||||
return {
|
||||
"key": key,
|
||||
"section": "3",
|
||||
"name": name,
|
||||
"spec": "",
|
||||
"unit": unit,
|
||||
"qty": qty,
|
||||
"note": note,
|
||||
"ref": ref,
|
||||
"dist": {"L1": route["L1"], "L2": route["L2"]} if route else None,
|
||||
"why": WHY_LOGIC if (ref or {}).get("tab") == "logic" else WHY_HAUL,
|
||||
}
|
||||
|
||||
out = []
|
||||
for g in haul_groups(materials):
|
||||
qty = g["qty"]
|
||||
ref = (refs or {}).get(bill_key(g["key"]))
|
||||
logic = (ref or {}).get("tab") == "logic"
|
||||
key = bill_key(g["key"])
|
||||
name = (by_key.get(key) or {}).get("name") or g["name"]
|
||||
out.append(
|
||||
{
|
||||
"key": bill_key(g["key"]),
|
||||
"section": "3",
|
||||
"name": f"{g['name']}운반",
|
||||
"spec": "",
|
||||
"unit": g["unit"],
|
||||
"qty": qty,
|
||||
"note": "B08 총괄자재" if qty is not None else "",
|
||||
"ref": ref,
|
||||
"dist": dists.get(bill_key(g["key"])),
|
||||
"why": WHY_LOGIC if logic else WHY_HAUL,
|
||||
}
|
||||
)
|
||||
for r in routes or []:
|
||||
if not r.get("user"):
|
||||
continue
|
||||
ref = (refs or {}).get(r["key"])
|
||||
logic = (ref or {}).get("tab") == "logic"
|
||||
out.append(
|
||||
{
|
||||
"key": r["key"],
|
||||
"section": "3",
|
||||
"name": r["name"],
|
||||
"spec": "",
|
||||
"unit": "",
|
||||
"qty": None,
|
||||
"note": "B08 거리이정표",
|
||||
"ref": ref,
|
||||
"dist": dists.get(r["key"]),
|
||||
"why": WHY_LOGIC if logic else WHY_HAUL,
|
||||
}
|
||||
line(key, name, g["unit"], g["qty"], "B08 총괄자재" if g["qty"] is not None else "")
|
||||
)
|
||||
for r in extra.get(g["key"], []):
|
||||
out.append(line(r["key"], r.get("name") or g["name"], "", None, "B08 거리이정표"))
|
||||
for r in rows:
|
||||
if r.get("user"):
|
||||
out.append(line(r["key"], r.get("name") or "운반", "", None, "B08 거리이정표"))
|
||||
return out
|
||||
|
||||
|
||||
|
||||
@@ -190,8 +190,8 @@ def test_항목_짜기_B08_수량():
|
||||
"key": "pipe:revet|돌쌓기(찰)|2.0",
|
||||
}
|
||||
haul = {v["name"]: v["qty"] for k, v in items.items() if k.startswith("haul:")}
|
||||
assert haul == {"중기운반": None, "기계운반": None, "모래운반": 16, "자갈운반": None,
|
||||
"시멘트운반": None, "철근운반": 0.75} # fmt: skip
|
||||
assert haul == {"중기": None, "기계": None, "모래": 16, "자갈": None,
|
||||
"시멘트": None, "철근": 0.75} # fmt: skip 261-4 묶음 이름 그대로
|
||||
|
||||
|
||||
def test_거울_TS_가_같은_값():
|
||||
|
||||
@@ -44,8 +44,8 @@ def test_묶음_장비_옛_여섯_늘_수량_없는_새_묶음_없음():
|
||||
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 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)
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ def test_rows_one_per_table_row():
|
||||
assert list(got) == ["haul:3", "haul:r-u1", "haul:r-u1.1"]
|
||||
assert (got["haul:3"]["L1"], got["haul:3"]["L2"], got["haul:3"]["user"]) == (25.1, 0, False)
|
||||
assert (got["haul:r-u1"]["name"], got["haul:r-u1"]["L1"], got["haul:r-u1"]["L2"]) == (
|
||||
"혼합골재운반", 10, 0.8) # fmt: skip
|
||||
"혼합골재", 10, 0.8) # fmt: skip 261-4 항목명 그대로
|
||||
assert got["haul:r-u1.1"]["L2"] == 2.8 # 중점 = 시점 → 중점 2 km 비포장 더함
|
||||
assert got["haul:r-u1"]["ref"] == LOGIC
|
||||
|
||||
@@ -47,14 +47,14 @@ def test_bill_items_user_rows():
|
||||
extra = haul_user(rows)
|
||||
items = {i["key"]: i for i in haul_items(None, extra["haul_refs"], rows)}
|
||||
assert (
|
||||
"haul:3" in items and items["haul:3"]["name"] == "모래운반"
|
||||
) # 자동 = 총괄자재 묶음 그대로
|
||||
"haul:3" in items and items["haul:3"]["name"] == "모래"
|
||||
) # 자동 = 상자 항목명(261-4)
|
||||
user = items["haul:r-u1"]
|
||||
assert (user["section"], user["name"], user["qty"], user["ref"]) == (
|
||||
"3",
|
||||
"혼합골재운반",
|
||||
"혼합골재",
|
||||
None,
|
||||
LOGIC,
|
||||
)
|
||||
assert items["haul:r-u1.1"]["name"] == "깬돌운반" and items["haul:r-u1.1"]["ref"] == LOGIC
|
||||
assert items["haul:r-u1.1"]["name"] == "깬돌" and items["haul:r-u1.1"]["ref"] == LOGIC
|
||||
assert extra["haul_dist"]["haul:r-u1"] == {"L1": 10, "L2": 0.8}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
"""261-4 운반공 줄 이름 = 거리이정표 항목명 그대로(「운반」 안 붙임) · 행마다 개별 줄 · 단가연결 무관."""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
|
||||
|
||||
from B08_Quantity.B08_Quantity_Haul import haul_rows # noqa: E402
|
||||
from B08_Quantity.B08_Quantity_Milestone_Calc import compute_milestone # noqa: E402
|
||||
from B09_Estimation.B09_Estimation_Bill_Items import haul_items # noqa: E402
|
||||
from B09_Estimation.B09_Estimation_Router import haul_user # noqa: E402
|
||||
|
||||
LOGIC = {"tab": "logic", "code": "GF000134", "name": "골재운반", "given": {}}
|
||||
DATA = {
|
||||
"site": {"mid_station_m": 2000, "mid_road": "비포장"},
|
||||
"base": {"enabled": False, "legs": []},
|
||||
"routes": [
|
||||
# 자동 상자 — 항목명 고침 · 둘째 행(같은 이름)
|
||||
{"id": "h-sand", "name": "골재상", "haul": "sand", "via_base": False,
|
||||
"legs": [{"road": "포장", "km": 25.1}],
|
||||
"rows": [{"item": "강모래", "dest": "start"}, {"item": "강모래", "dest": "mid"}]},
|
||||
# 자동 상자 — 항목명 빈칸 · 거래처 없음 = 묶음 이름
|
||||
{"id": "h-gravel", "name": "", "haul": "gravel", "via_base": False,
|
||||
"legs": [], "rows": [{"item": "", "dest": "start"}]},
|
||||
# 사용자 상자 — 「운반」 이 든 항목명 · 같은 이름 두 행 · 빈 항목명(거래처) · 연결 없음
|
||||
{"id": "u1", "name": "석산", "via_base": False, "legs": [{"road": "포장", "km": 10}],
|
||||
"rows": [{"item": "전석운반", "dest": "start"}, {"item": "전석운반", "dest": "start"},
|
||||
{"item": " ", "dest": "start"}]},
|
||||
# 사용자 상자 — 항목명 · 거래처 모두 빈칸 · 연결 있음
|
||||
{"id": "u2", "name": "", "ref": LOGIC, "via_base": False, "legs": [],
|
||||
"rows": [{"item": "", "dest": "start"}]},
|
||||
],
|
||||
} # fmt: skip
|
||||
|
||||
|
||||
def _items() -> dict[str, dict]:
|
||||
rows = haul_rows(DATA, compute_milestone(DATA, None))
|
||||
return {i["key"]: i for i in haul_items(None, haul_user(rows)["haul_refs"], rows)}
|
||||
|
||||
|
||||
def test_자동_상자_이름_항목명():
|
||||
items = _items()
|
||||
assert items["haul:3"]["name"] == "강모래"
|
||||
assert items["haul:4"]["name"] == "자갈" # 항목 · 거래처 빈칸 = 묶음 이름
|
||||
assert items["haul:1"]["name"] == "중기" # 상자 없음 = 묶음 이름
|
||||
|
||||
|
||||
def test_자동_상자_둘째_행_개별_줄_묶음_바로_뒤():
|
||||
items = _items()
|
||||
keys = list(items)
|
||||
assert keys.index("haul:3.1") == keys.index("haul:3") + 1
|
||||
extra = items["haul:3.1"]
|
||||
assert (extra["name"], extra["qty"], extra["dist"]["L2"]) == ("강모래", None, 2)
|
||||
|
||||
|
||||
def test_사용자_상자_이름_항목명_같은_이름_두_줄():
|
||||
items = _items()
|
||||
assert items["haul:r-u1"]["name"] == items["haul:r-u1.1"]["name"] == "전석운반"
|
||||
assert items["haul:r-u1.2"]["name"] == "석산" # 빈 항목명 = 거래처
|
||||
assert items["haul:r-u2"]["name"] == "운반" and items["haul:r-u2"]["ref"] == LOGIC
|
||||
assert items["haul:r-u1"]["ref"] is None # 연결 없어도 줄
|
||||
|
||||
|
||||
def test_운반운반_없음():
|
||||
assert not any("운반운반" in i["name"] for i in _items().values())
|
||||
Reference in New Issue
Block a user