feat(778-4): 율 줄을 단가산출(SK)에 담음 — 고정 비율 더한줄 · 기준 묶음 · 줄 목록 · 율 위 율

- SK 더한줄 율 줄 허용(일위대가 비율 줄과 같은 꼴 `고정 비율 {기준, 비목}` · 수량 = 율)
- 기준 = 비목(노무비 · 재료비 · 경비) · 묶음(인력 · 재료 · 장비 = 그 줄 종류만 · 기계 노무 섞인 블록도 인력 줄만) ·
  줄 목록(cid · add:#k · 앞 율 줄 = 율 위 율) · 검사 = 자기 · 뒤 율 줄 막음
- 밑 공용 함수 `M02_Labor_Fixed.rate_base` ↔ 짝 `Labor_Calc.ts` `rateBase`(일위대가 비율 줄 · Expand 같이 씀)
- SK 셈 `_rated` = 엔진 한 번 돈 줄로 밑 → 율 줄 값묶음 → 엔진 다시(율 줄 없는 문서 = 한 번 · 값 그대로)
- B09 pct 밑수 `bundle:` · `rows:`(파이썬 · TS 짝) · SK 블록 율 줄 = pct
- 굳힘 — 바탕 없는 SK 율 줄은 한 줄로 안 펼침(참조 그대로) · 겹침 때 기준 열쇠 새 차례
- 화면 — [율 줄 추가] 창(새 `M02_MasterTemplete_Sangeun_Rate.ts` · 부품 상자만) · 율 줄 = % 줄로 보임 ·
  더한줄 지움 · 되살림 때 기준 열쇠 옮김
- 시험 — 새 test_778_4_rate_line(25) · 거울 헬퍼에 rateBase 길 · 전후 대조 SK 16 · 일위대가 118 · B09 대가 입력 14 같음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-04 12:59:48 +09:00
co-authored by Claude Opus 5.5
parent 2f93dd8b11
commit d9a581659f
16 changed files with 825 additions and 53 deletions
+66 -16
View File
@@ -7,8 +7,11 @@
(노임 새 판 때 따라감) · 품(수량)은 그대로.
M02 셈(층 · 사본) = 노임 「값」 · 자재 기준단가로 이음 · 기계 = 줄 단가(기계 시간 단가 = B09 단가 책 몫).
출처 = `{파일, 호표, 줄, 판}`(글 · 수만) · 줄 가름 = 「고정|이름|규격|단위」(짝 `Labor_Calc.ts` `lineId`).
비율 줄(705-3 · 엑셀 % 줄) = `비율 {기준, 비목}` · 수량 = 율(%) — 반영[비목] = 다른 줄 반영합[기준] × 율 ÷ 100
(`ratios` · 짝 `Labor_Calc.ts` `ratioLines` · B09 = `pct` 줄).
비율 줄(705-3 · 엑셀 % 줄) = `비율 {기준, 비목}` · 수량 = 율(%) — 반영[비목] = 밑 × 율 ÷ 100
(`ratios` · 짝 `Labor_Calc.ts` `ratioLines` · B09 = `pct` 줄) · 단가산출(SK) 율 더한줄(778-4)도 이 꼴.
기준(778-4 · 밑 = `rate_base` · 짝 `Labor_Calc.ts` `rateBase`) = 비목(노무비 · 재료비 · 경비 = 율 줄 뺀
그 비목 합) · 묶음(인력 · 재료 · 장비 = 그 묶음 줄 노 · 재 · 경 합 · 고정 줄 묶음 = 이음) ·
줄 목록 [열쇠](그 줄 합 · 앞 율 줄 넣으면 율 위 율 · SC 열쇠 = 줄 가름 · SK = cid).
B09 대가 갈래 `fixed` = `B09_Estimation_Labor_Source.fixed_line` · 셈 `B09_Estimation_Calc_Labor`.
"""
@@ -27,6 +30,8 @@ COSTS = mf.COST_ITEMS # 노무비 · 재료비 · 경비
FIXED = "고정"
LINKS = {"노임": "LB", "자재": "MT", "기계": "EQ"}
SOURCE_KEYS = ("파일", "호표", "줄", "판")
GROUPS = {"노임": "인력", "자재": "재료", "기계": "장비"} # 율 기준 묶음(778-4) ← 고정 줄 이음
BASES = (*COSTS, *GROUPS.values())
def _bad(text: str) -> Exception:
@@ -82,9 +87,9 @@ def clean(fx: Any, where: str, whole: Any) -> dict[str, Any]:
link[k] = code or None
rate = fx.get("비율")
if rate is not None and not (
isinstance(rate, dict) and rate.get("기준") in COSTS and rate.get("비목") in COSTS
isinstance(rate, dict) and _basis(rate.get("기준")) and rate.get("비목") in COSTS
):
raise _bad(f"{where} · 고정 줄 비율은 {{기준, 비목}} — 노무비 · 재료비 · 경비")
raise _bad(f"{where} · 고정 줄 비율은 {{기준, 비목}} — 기준 = 비목 · 묶음 · 줄 목록")
src = fx.get("출처") if isinstance(fx.get("출처"), dict) else {}
kept = {k: v for k in SOURCE_KEYS if isinstance(v := src.get(k), (str, int, float))}
out = {"이름": name, "규격": str(fx.get("규격") or ""), "단위": str(fx.get("단위") or "")}
@@ -92,6 +97,42 @@ def clean(fx: Any, where: str, whole: Any) -> dict[str, Any]:
return {**out, "비율": {k: rate[k] for k in ("기준", "비목")}} if rate else out
def _basis(raw: Any) -> bool:
"""율 기준 — 비목 · 묶음 이름 · 줄 열쇠 목록(글 · 빈 목록 = 밑 0 · 기준 줄을 지운 뒤)."""
if isinstance(raw, list):
return all(isinstance(x, str) and x for x in raw)
return raw in BASES
def group_of(fx: dict) -> str | None:
"""고정 줄 묶음 — 첫 이음(노임 · 자재 · 기계) · 이음 없음 = None."""
link = fx.get("이음") or {}
return next((g for k, g in GROUPS.items() if link.get(k)), None)
def _all(money: dict | None) -> Decimal:
return sum((Decimal(str((money or {}).get(k) or 0)) for k in COSTS), Decimal(0))
def rate_base(basis: Any, rows: list[dict]) -> Decimal:
"""율 줄 밑(778-4 · 짝 `Labor_Calc.ts` `rateBase`) — `rows` [{키, 묶음, 비목 {노 · 재 · 경}, 율?}] ·
비목 = 율 줄 뺀 그 비목 합 · 묶음 = 율 줄 뺀 그 묶음 줄 셋 합 · 목록 = 그 열쇠 줄 셋 합."""
if isinstance(basis, list):
keep = set(basis)
return sum((_all(r["비목"]) for r in rows if r.get("키") in keep), Decimal(0))
rows = [r for r in rows if not r.get("율")]
if basis in COSTS:
return sum((Decimal(str((r["비목"] or {}).get(basis) or 0)) for r in rows), Decimal(0))
return sum((_all(r["비목"]) for r in rows if r.get("묶음") == basis), Decimal(0))
def rate_row(one: dict, key: Any, money: dict | None, rate: bool = False) -> dict[str, Any]:
"""`rate_base` 줄 하나 — 고정 줄 묶음 = 이음 · 그 밖 = 줄 `묶음`(SK 엔진 줄 · 자재 = 재료)."""
fx = one.get(FIXED)
group = group_of(fx) if isinstance(fx, dict) else one.get("묶음")
return {"키": key, "묶음": group, "비목": money, "율": rate}
def _has(whole: Any, code: str) -> bool:
try:
return bool(whole.get(code))
@@ -136,26 +177,35 @@ def line(whole: Any, fx: dict) -> dict[str, Any]:
return {**head, "기준": money, "이은": used, "까닭": None}
def ratio_row(rate: dict, qty: Decimal | None, under: dict) -> tuple[dict, dict | None]:
"""비율 줄 (기준, 반영) — 기준[비목] = 밑[기준] · 반영 = 기준 × 율 ÷ 100 ·
def ratio_row(rate: dict, qty: Decimal | None, under: Decimal) -> tuple[dict, dict | None]:
"""비율 줄 (기준, 반영) — 기준[비목] = 밑(`rate_base`) · 반영 = 기준 × 율 ÷ 100 ·
0.1 원 버림(751-3) · 수량 없음 = None."""
base = dict.fromkeys((*COSTS, "계"), Decimal(0))
base[rate["비목"]] = base["계"] = under[rate["기준"]]
base[rate["비목"]] = base["계"] = under
return base, None if qty is None else mcut.row({k: base[k] * qty / 100 for k in COSTS})
def ratios(lines: list[dict], reflects: list[dict | None], bases: list[dict | None]) -> None:
"""비율 줄(705-3) — `settle` 끝에 · 밑 = 비율 줄 뺀 반영합(줄 · 자재) · 배 100 · 기준합엔 안 셈 ·
`lines` · `reflects` · `bases` 앞 줄 = 같은 차례(짝 `Labor_Calc.ts` `ratioLines`)."""
at = {i for i, x in enumerate(lines) if (x.get(FIXED) or {}).get("비율")}
under = {
k: sum((r[k] for i, r in enumerate(reflects) if r and i not in at), Decimal(0))
for k in COSTS
}
for i in sorted(at):
base, done = ratio_row(lines[i][FIXED]["비율"], lines[i].get("수량"), under)
"""비율 줄(705-3) — `settle` 끝에 · 밑 = `rate_base`(율 줄 뺀 반영(줄 · 자재) · 앞 율 줄은 목록만) ·
배 100 · 기준합엔 안 셈 · `lines` · `reflects` · `bases` 앞 줄 = 같은 차례(짝 `Labor_Calc.ts`
`ratioLines`) · 자재 줄 = 묶음 재료 · 열쇠 없음."""
from M02_MasterTemplete import M02_Labor_Doc as labor
at = [i for i, x in enumerate(lines) if (x.get(FIXED) or {}).get("비율")]
rows = [
rate_row(lines[i], labor.line_id(lines[i]), r)
if i < len(lines)
else {"키": None, "묶음": "재료", "비목": r}
for i, r in enumerate(reflects)
if r and i not in at
]
for i in at:
rate = lines[i][FIXED]["비율"]
base, done = ratio_row(rate, lines[i].get("수량"), rate_base(rate["기준"], rows))
lines[i], reflects[i], bases[i] = (
{**lines[i], "기준": base, "배": 100, "반영": done},
done,
None,
)
if done:
rows.append(rate_row(lines[i], labor.line_id(lines[i]), done, True))