feat(B09 단가 덮개): 노임 · 기계 단가 덮개 labor_cover · machine_cover — 셈 짝 · 저장 다듬기 · 거울 시험 (PLAN 707-3)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-31 15:39:16 +09:00
co-authored by Claude Opus 5.5
parent 4f4348c80e
commit 39e0ee6d85
4 changed files with 96 additions and 9 deletions
+31 -2
View File
@@ -21,6 +21,10 @@
중기사용료 = 손료(경비 · 기준가 천원 × 손료계수 ÷ 10,000) + 운전사(노무 · 인 × 시간 노임)
+ 연료(재료 · ℓ/hr × 적용 단가) + 잡품(재료 · 연료 금액 × % ÷ 100)
줄 = ROUNDDOWN(단가 × 수량, 1) · 비목 합 = ROUNDDOWN(Σ, 0) → 중기목록표
노임 · 기계 단가 덮개(707-3) = 사용자 값 `labor_cover` {노무 키: 일당} · `machine_cover`
{기계 키: {labor, material, expense}}(셋 다 수일 때만) — 굳힌 값 대신 ·
줄 `covered` · `book`(굳힌 값) ·
덮은 기계 = 중기사용료 줄은 그대로 · 비목 합 · 합 = 덮개 · 그 기계 「연결 없음」 뺌
빈칸 규칙 = 값을 낸 원천이 없으면 None(0 을 채우지 않음) · 그 칸을 쓰는 합도 None ·
「연결 없음」 목록(`gaps`)에 탭 · 줄 키 · 까닭을 남김(목차 탭이 이동 링크로 씀).
"""
@@ -39,6 +43,7 @@ SOURCES = ("관급", "물가정보", "물가자료", "유통물가", "거래가
#: 자재 줄 사용자 표시
SUPPLY = ("관급", "별도")
_PARTS = ("labor", "material", "expense")
def _scaled(value: float, places: int, fn) -> float:
@@ -95,10 +100,15 @@ def driver_hour(day: float | None, factor: dict[str, Any] | None) -> float | Non
return rounddown(day * factor["num"] / factor["den"], 0)
def labor_sheet(labor: list[dict[str, Any]], gaps: list) -> list[dict[str, Any]]:
def labor_sheet(
labor: list[dict[str, Any]], gaps: list, cover: dict[str, Any] | None = None
) -> list[dict[str, Any]]:
rows = []
for no, row in enumerate(labor, 1):
price = num(row.get("price"))
mine = num((cover or {}).get(row["key"]))
extra = {} if mine is None else {"covered": True, "book": price}
price = price if mine is None else mine
if price is None:
gaps.append(gap("labor", row["key"], row["name"], "노임 단가 없음"))
rows.append(
@@ -107,11 +117,22 @@ def labor_sheet(labor: list[dict[str, Any]], gaps: list) -> list[dict[str, Any]]
"code": row.get("code"),
"unit": row.get("unit") or "인",
"price": price,
**extra,
}
)
return rows
def machine_cover(cover: Any) -> dict[str, dict[str, float]]:
"""기계 덮개 — 노 · 재 · 경 셋 다 수인 줄만."""
out = {}
for key, cell in (cover if isinstance(cover, dict) else {}).items():
got = {p: num((cell or {}).get(p)) if isinstance(cell, dict) else None for p in _PARTS}
if None not in got.values():
out[key] = got
return out
def _head(row: dict[str, Any], no: int) -> dict[str, Any]:
return {
"no": no,
@@ -306,7 +327,7 @@ def basis_sheet(
def compute_base(inputs: dict[str, Any], user: dict[str, Any]) -> dict[str, Any]:
"""144 탭 일곱 + 「연결 없음」 목록."""
gaps: list[dict[str, str]] = []
labor_rows = labor_sheet(inputs.get("labor") or [], gaps)
labor_rows = labor_sheet(inputs.get("labor") or [], gaps, user.get("labor_cover"))
expense = expense_sheet(inputs.get("machines") or [], gaps)
compare = compare_sheet(inputs.get("materials") or [], user.get("materials") or {}, gaps)
basis = basis_sheet(inputs, labor_rows)
@@ -316,6 +337,13 @@ def compute_base(inputs: dict[str, Any], user: dict[str, Any]) -> dict[str, Any]
kind: by_key[key] for kind, key in (inputs.get("fuel_keys") or {}).items() if key in by_key
}
blocks = [machine_block(m, drivers, fuels, gaps) for m in inputs.get("machines") or []]
covers = machine_cover(user.get("machine_cover"))
for b in blocks:
if b["key"] in covers: # 707-3 덮개 = 비목 합 · 합(줄은 그대로)
b["book"] = {p: b[p] for p in (*_PARTS, "sum")}
b.update(covers[b["key"]], covered=True)
b["sum"] = total([b[p] for p in _PARTS])
gaps = [g for g in gaps if not (g["tab"] == "machine_blocks" and g["key"] in covers)]
machine = [
{
"no": no,
@@ -327,6 +355,7 @@ def compute_base(inputs: dict[str, Any], user: dict[str, Any]) -> dict[str, Any]
"labor": b["labor"],
"material": b["material"],
"expense": b["expense"],
**({"covered": True} if b.get("covered") else {}),
}
for no, b in enumerate(blocks, 1)
]