fix(m02): 일위대가 별도계상 자재 셈에서 뺌 · 원문 같은 책 절 모두 · 단위 까닭 시험(222-1)

- 로직 자재 별도계상(빈 칸 = 기본) = 기준에서 빼고 일위대가 상세 줄에서 지움 · 자재목록엔 남김
- 사용자 자재 별도계상 = 합에서 뺌(파이썬 · TS settle 짝 · 거울 시험)
- 사전조합 안 줄 · 프로젝트 사본 셈도 같은 길
- 별도계상 바꾸면 다시 셈(로직 자재 = 서버 · 사용자 자재 = 짝 셈)
- 원문 출처 = 로직 출처 + 호표 · 입력 출처 같은 책 절(산림품셈 12-1-2 · 12-1-3)
- 기존 시험 셋 = 직접계산 표시 · 대조용 apart 끔

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F3mdb91125mPoC6tpsQx4P
This commit is contained in:
2026-10-10 16:39:00 +09:00
co-authored by Claude Opus 5.5
parent 9085a51056
commit 9a850212d5
9 changed files with 230 additions and 14 deletions
+37 -3
View File
@@ -20,9 +20,11 @@
고름 = `{호표 차례: MT키}`(엔진 `choosing` 그대로) · 금액은 로직 재료비 안이라 합 · 알약에 안 더함(보기만) · 사용자 자재와
같은 품목 두 번은 [저장] 때 400.
승격(134-1) = 로직 줄 「단가승격」 표시(`true` 만 · 없음 = 단가산출) · 금액 무관 · B09 호표 몫.
자재 세 칸(148-4) = `MARKS` 허용 값만(그 밖 · 빈 것 버림 · 금액 무관) — 사용자 자재 = 줄 덧칸 ·
자재 세 칸(148-4) = `MARKS` 허용 값만(그 밖 · 빈 것 버림 · 별도계상만 금액에 닿음) — 사용자 자재 = 줄 덧칸 ·
로직 자재 = 로직 줄 `자재표시 {호표 차례: {세 칸}}`(차례가 재료 줄인 것만 · 틀리면 버림) ·
빈 칸 = 읽을 때 `MARK_DEFAULT`(`mark_of` · 저장은 비움 · 옛 「일반」 = 버림 → 사급 · 162-1).
별도계상(222-1) = 그 자재 금액은 기준 · 합에서 빠지고 로직 `줄` 에서 지움(`drop_apart` · `settle` ·
짝 `Labor_Calc.ts`) · `재료` · `자재`(자재목록 · B08 수량)엔 남음.
% 덧줄(149-4) = 식 「X * Y / 100」 덧줄에 덧칸 `율`(Y) · `규격`(「노무비의 %」 등 · `pct_extras`) — 표기만 ·
단위 · 수량 · 금액 그대로(「식 · 1」) · 화면이 율 · % 로 그림 · M01 시험계산 표도 이 함수.
"""
@@ -224,6 +226,35 @@ def mark_of(one: Any, key: str) -> str:
return marks(one).get(key) or MARK_DEFAULT[key]
def apart(one: Any) -> bool:
"""별도계상(222-1) — 세 칸 「별도계상」(빈 칸 = 기본) 자재는 일위대가 셈에서 빠짐(본 프로그램에서
따로 계상 · 자재목록은 상세 보기라 따로 안 더함)."""
return mark_of(one, "별도계상") == "별도계상"
def drop_apart(got: dict[str, Any], shown: Any) -> dict[str, Any]:
"""로직 줄 답(`run_line`)에서 별도계상 로직 자재 빼기(222-1) — 그 재료 줄 금액을 기준에서 빼고
`줄`(일위대가 상세)에서 지움 · `재료`(자재목록 · 세 칸 고침)는 그대로.
ponytail: 「재료비의 %」 덧줄은 엔진 값 그대로(뺀 재료 몫 안 고침) — 그런 로직이 별도계상을 쓰면 엔진에 넘김."""
shown = shown if isinstance(shown, dict) else {}
lines = got.get("줄") or []
out = {
n
for m in got.get("재료") or []
if (n := m["차례"]) < len(lines) and apart(shown.get(str(n)))
}
if not out or not got.get("기준"):
return got
base = {k: _number(v) or Decimal(0) for k, v in got["기준"].items()}
for n in out:
for k, v in (lines[n].get("비목") or {}).items():
cut = _number(v) or Decimal(0)
base[k] = base.get(k, Decimal(0)) - cut
base["계"] -= cut
kept = [x for i, x in enumerate(lines) if i not in out]
return {**got, "기준": _json(base), "줄": kept}
def _mat_marks(value: Any, logic: dict) -> dict[str, dict[str, str]] | None:
"""로직 자재 세 칸(148-4) `{호표 차례: {세 칸}}` — 맨 위 층 재료 줄 차례 · 허용 값만 ·
빈 것 = None."""
@@ -386,6 +417,7 @@ def settle(result: dict, doc: dict, given: dict[str, Any]) -> dict[str, Any]:
have = {str(k): v for k, raw in given.items() if (v := _number(raw)) is not None}
hands = {line_id(one): one.get("수량") for one in doc.get("담은로직") or []}
mhands = {one.get("코드"): one.get("수량") for one in doc.get("자재") or []}
aside = {one.get("코드") for one in doc.get("자재") or [] if apart(one)} # 222-1 합에서 뺌
zero = dict.fromkeys((*COSTS, "계"), Decimal(0))
lines, mats, bases, reflects = [], [], [], []
for line in result.get("줄") or []:
@@ -399,9 +431,11 @@ def settle(result: dict, doc: dict, given: dict[str, Any]) -> dict[str, Any]:
qty, source = _pick(mhands.get(one.get("코드")), one.get("알약"), have)
price = _number(one.get("기준단가"))
cost = price * qty if price is not None and qty is not None else None
mats.append({**one, "수량": qty, "수량출처": source, "반영": cost})
if one.get("코드") in aside:
continue
bases.append({**zero, "재료비": price, "계": price} if price is not None else None)
reflects.append({**zero, "재료비": cost, "계": cost} if cost is not None else None)
mats.append({**one, "수량": qty, "수량출처": source, "반영": cost})
return _json({"줄": lines, "자재": mats, "기준합": _sum(bases), "반영합": _sum(reflects)})
@@ -468,7 +502,7 @@ def calc(
"배": pill.get("배") or 1,
}
got = run_line(whole, code, one.get("입력값"), one.get("켜쌓기율"), one.get("고름"))
lines.append({**line, **got})
lines.append({**line, **drop_apart(got, one.get("자재표시"))})
mats = [
{
"코드": one["코드"],