Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1

This commit is contained in:
2026-10-10 00:15:10 +09:00
7 changed files with 207 additions and 35 deletions
+13 -4
View File
@@ -124,6 +124,7 @@ def test_엑셀_대가_블록_검산():
assert out["daega"][0] == {
"no": 1, "key": "DG14", "kind": "조합", "name": "돌붙임(채집)", "spec": "", "unit": "",
"sum": 101522, "labor": 77755, "material": 9519, "expense": 14248,
"blank": 0, "blank_names": [],
} # fmt: skip
assert daega_price(out, "pipe@100.0") == {
"no": "대가1",
@@ -147,11 +148,19 @@ def _blank_cases():
def test_빈칸_규칙():
"""166-6 — 블록 합 = 아는 줄 합 + 빈 줄 n · 내역 단가는 빈칸."""
out = compute_labor(_blank_cases(), SHEETS, {})
assert _block(out, "DG2")["sum"] is None and _block(out, "DG2")["labor"] is None
assert _block(out, "DG14")["sum"] is None # 산근 단가 없음(145-1 전)
assert _block(out, "DGZ")["sum"] is None
assert _block(out, "DGX")["sum"] is None or _block(out, "DGY")["sum"] is None # 순환 = 빈칸
dg2 = _block(out, "DG2")
assert (dg2["sum"], dg2["labor"], dg2["blank"]) == (6013, 5275, 2)
assert dg2["blank_names"] == ["L00032", "공구손료"] # 노임 없음 · 그 노무 밑수 % 줄
dg14 = _block(out, "DG14") # 산근 단가 없음(145-1 전)
assert (dg14["sum"], dg14["blank"]) == (53926, 3)
assert _sums(dg14) == (53926, 43539, 3842, 6545)
assert (_block(out, "DGZ")["sum"], _block(out, "DGZ")["blank"]) == (0, 1)
assert _block(out, "DGX")["blank"] + _block(out, "DGY")["blank"] >= 1 # 순환 = 빈 줄
assert daega_price(out, "pipe@100.0") == {
"no": "대가1", "key": "DG14", "labor": None, "material": None, "expense": None, "sum": None,
} # fmt: skip
whys = {g["why"] for g in out["gaps"]}
assert {"노임 단가 없음", "산근 단가 없음(145-1)", "수량 없음", "% 밑수 없음"} <= whys
+25 -1
View File
@@ -8,6 +8,7 @@
"""
import asyncio
import json
import sys
from pathlib import Path
@@ -54,7 +55,7 @@ LINES = {
{"kind": "machine", "key": "X1", "qty": 4.295},
{"kind": "hopyo", "key": "B", "qty": 0.5},
{"kind": "material", "key": "M4", "qty": 0.354},
{"kind": "expense", "key": "E16", "qty": 1.103},
{"kind": "expense", "key": "E16", "qty": 1.103}, # 합성 줄 — 산근 · 대가는 경비 줄을 안 냄(166-5)
]},
"B": {"lines": [{"kind": "labor", "key": "L1", "qty": 1.658},
{"kind": "labor", "key": "L9", "qty": 1}]},
@@ -155,3 +156,26 @@ def test_산근_블록_역전개_엑셀_반올림():
machine = s["sum_machine"][0] # 중기시간금액집계표 10 줄 노무비
assert (machine["qty"], machine["labor"], machine["sum"]) == (1436.14, 79992998, None)
assert s["unlinked"] == 3 # 토공 · 대가 전 구조물 · 수량 빈칸 = 전개 안 함
# ── 166-5 경비 집계표 — 실제 블록 꼴은 경비 자원 0 → 표 위 안내 ──
from test_155_b09_tabs import _node # noqa: E402
EXPENSE_NONE = "경비 갈래 자원 없음 — 중기 · 자재로 셈 · 경비 로직이 들어오면 채워짐"
def _expense_tab(summary):
return _node(
f"\nconst t = buildSumExpense({{ payload: {{ summary: {json.dumps(summary)} }} }});"
"\nconsole.log(JSON.stringify({ n: t.rows.length, hint: t.hint }));",
"UI_Tabs_Summary",
)
def test_166_5_실자료_경비_0_이면_안내():
s = compute_summary(SG_SHEETS, hopyo_lines(SG_SHEETS))
assert s["sum_expense"] == [] # 산근 블록 꼴 = 경비 갈래 줄 없음
out = _expense_tab(s)
assert out["n"] == 0 and out["hint"].startswith(EXPENSE_NONE)
filled = _expense_tab(compute_summary(SHEETS, LINES)) # 합성 경비 줄이 있으면 안내 없음
assert filled["n"] == 1 and EXPENSE_NONE not in filled["hint"]
@@ -0,0 +1,97 @@
"""166-6 일위대가목록표 블록 합 — 빈 줄 있는 블록 = 아는 줄 합 + 「빈 줄 n」 · 내역 단가엔 안 넘김.
견본 = 대가 A(노무 아는 줄 · 자재 단가 없음 줄) · 대가 B(A 를 부름 → 그 줄도 빈 줄) · 대가 C(빈 줄 없음).
파이썬 `B09_Estimation_Calc_Labor` ↔ TS 짝 거울 + 목록표 화면 「빈 줄 n」 · 풍선(Node 타입 지움).
"""
import json
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from B09_Estimation.B09_Estimation_Calc_Labor import compute_labor, daega_price # noqa: E402
from test_145_2_b09_labor_calc import _same, _ts # noqa: E402
from test_155_b09_tabs import _node # noqa: E402
SHEETS = {
"labor": [{"key": "L1", "price": 165545}],
"material": [{"key": "M1", "price": None}],
"machine": [],
"gaps": [],
}
def _b(bid, name, lines):
return {
"id": bid,
"kind": "조합",
"name": name,
"spec": "",
"unit": "m",
"cite": "",
"lines": lines,
}
DAEGA = {
"blocks": [
_b("A", "기슭막이", [
{"kind": "labor", "key": "L1", "qty": 0.5, "name": "보통인부"},
{"kind": "material", "key": "M1", "qty": 2, "name": "토목용부직포"},
]),
_b("B", "겹 대가", [
{"kind": "labor", "key": "L1", "qty": 0.1, "name": "보통인부"},
{"kind": "daega", "key": "A", "qty": 1, "name": "기슭막이"},
]),
_b("C", "아는 줄만", [{"kind": "labor", "key": "L1", "qty": 1, "name": "보통인부"}]),
],
"owners": {"pipe@100.0": "A", "pipe@200.0": "C"},
} # fmt: skip
def test_아는_줄_합_빈_줄_n():
out = compute_labor(DAEGA, SHEETS)
rows = {r["key"]: r for r in out["daega"]}
# 165,545 × 0.5 = 82,772.5 → 블록 ROUNDDOWN 0 · 자재 줄은 빈 줄
assert (rows["A"]["sum"], rows["A"]["labor"], rows["A"]["material"]) == (82772, 82772, 0)
assert (rows["A"]["blank"], rows["A"]["blank_names"]) == (1, ["토목용부직포"])
# 부른 대가에 빈 줄 → 그 줄도 빈 줄(아는 줄 합을 위로 안 넘김)
assert (rows["B"]["sum"], rows["B"]["blank"], rows["B"]["blank_names"]) == (
16554,
1,
["기슭막이"],
)
assert (rows["C"]["sum"], rows["C"]["blank"]) == (165545, 0)
# 목록표 = 블록 합계 그대로(같은 셈 한 벌)
for b in out["daega_blocks"]:
assert _same(
{k: rows[b["key"]][k] for k in ("sum", "blank")}, {"sum": b["sum"], "blank": b["blank"]}
)
# 내역 단가 = 빈 줄 블록은 빈칸 · 없는 블록은 그대로
assert daega_price(out, "pipe@100.0")["sum"] is None
assert daega_price(out, "pipe@100.0")["labor"] is None
assert daega_price(out, "pipe@200.0")["sum"] == 165545
def test_거울_파이썬_TS():
py = json.loads(json.dumps(compute_labor(DAEGA, SHEETS), ensure_ascii=False))
ts = _ts(DAEGA, SHEETS, {})
assert _same(py, ts["out"])
assert _same(daega_price(py, "pipe@100.0"), ts["price"])
def test_목록표_빈_줄_글_풍선():
sheets = json.dumps(compute_labor(DAEGA, SHEETS), ensure_ascii=False)
out = _node(
f"\nconst t = buildDaegaList({{ sheets: {sheets} }});"
"\nconsole.log(JSON.stringify({ notes: t.rows.map((r) => r.blank_note),"
" sums: t.rows.map((r) => r.sum), cols: t.columns.map((c) => c.key),"
" tips: t.rows.map((_, i) => t.tip(i, 'blank_note') ?? null) }));"
)
assert out["cols"][5:7] == ["sum", "blank_note"] # 합계 바로 옆
assert out["notes"] == ["빈 줄 1", "빈 줄 1", ""]
assert out["sums"] == [82772, 16554, 165545]
assert out["tips"] == ["빈 줄 — 토목용부직포", "빈 줄 — 기슭막이", None]