feat(b09): 일위대가표 품셈 규격 기계 · 직종 번호 = 「이름 규격」 · 번호 풍선(157-4)
- 품셈 블록 규격 글 = 기계 · 직종 원문번호 대신 「이름 규격」(Sangeun_Text._word = M02_Labor_Inputs._labels) · 옛 글 = spec_raw
- 대가 줄 · 일위대가목록표 · 일위대가표 규격 칸 풍선 = 「원문번호 …」(산근 탭 157-3 raw 재사용 · 내보냄)
- Calc_Labor 파이썬 · TS 짝 둘 다 spec_raw 있을 때만 실음(거울 시험 그대로) · 금액 · 줄 · 블록 id 그대로
- 시험 test_157_4 새로 · origin/sub_desktop_1(d02c1d3f0) 합친 뒤 걸음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QND7Co43B24zK2P937rdxt
This commit is contained in:
@@ -115,6 +115,7 @@ def _block(
|
||||
parts[part] = None if s is None else rounddown(s, 0)
|
||||
return {
|
||||
**{k: block.get(k) for k in ("id", "kind", "name", "spec", "unit", "cite")},
|
||||
**({"spec_raw": block["spec_raw"]} if block.get("spec_raw") else {}), # 157-4 풍선
|
||||
"key": block["id"],
|
||||
"lines": lines,
|
||||
**parts,
|
||||
@@ -171,6 +172,7 @@ def compute_labor(
|
||||
"kind": b["kind"],
|
||||
"name": b["name"],
|
||||
"spec": b["spec"],
|
||||
**({"spec_raw": b["spec_raw"]} if b.get("spec_raw") else {}),
|
||||
"unit": b["unit"],
|
||||
"sum": b["sum"],
|
||||
"labor": b["labor"],
|
||||
|
||||
@@ -115,6 +115,7 @@ function block(b: Row, books: Row, sums: Record<string, Row>, gaps: Gap[]): Row
|
||||
kind: b.kind,
|
||||
name: b.name,
|
||||
spec: b.spec,
|
||||
...(b.spec_raw ? { spec_raw: b.spec_raw } : {}), // 157-4 풍선(파이썬 짝)
|
||||
unit: b.unit,
|
||||
cite: b.cite,
|
||||
lines,
|
||||
@@ -160,6 +161,7 @@ export function computeLabor(daega: Row | null | undefined, sheets: Row, sangeun
|
||||
kind: b.kind,
|
||||
name: b.name,
|
||||
spec: b.spec,
|
||||
...(b.spec_raw ? { spec_raw: b.spec_raw } : {}),
|
||||
unit: b.unit,
|
||||
sum: b.sum,
|
||||
labor: b.labor,
|
||||
|
||||
@@ -26,6 +26,7 @@ from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from B09_Estimation import B09_Estimation_Sangeun as sg
|
||||
from B09_Estimation import B09_Estimation_Sangeun_Text as st
|
||||
from M02_MasterTemplete import M02_Item_Prices as item_prices
|
||||
from M02_MasterTemplete import M02_Labor_Doc as labor_doc
|
||||
from M02_MasterTemplete import M02_Labor_Ref as refs
|
||||
@@ -106,11 +107,16 @@ class Builder:
|
||||
line["spec"] = (eq.get(line["key"]) or {}).get("규격") or ""
|
||||
unit = str(logic.get("결과단위") or "").replace("원/", "")
|
||||
spec = " · ".join(f"{k} {v}" for k, v in sorted(given.items()))
|
||||
shown = " · ".join( # 157-4 기계 · 직종 원문번호 = 「이름 규격」(번호 = 화면 풍선 spec_raw)
|
||||
f"{k} {st._word(whole, k, v) if isinstance(v, str) else v}"
|
||||
for k, v in sorted(given.items())
|
||||
)
|
||||
return self.put(
|
||||
{
|
||||
"kind": "품셈",
|
||||
"name": title or logic.get("이름") or code,
|
||||
"spec": spec,
|
||||
"spec": shown,
|
||||
"spec_raw": spec,
|
||||
"unit": unit,
|
||||
"cite": logic.get("출처") or "",
|
||||
"origin": {"로직": code, "입력값": given, "켜쌓기율": rate, "고름": picks},
|
||||
@@ -197,7 +203,7 @@ class Builder:
|
||||
}
|
||||
block = self.blocks[bid]
|
||||
return {"kind": "daega", "key": bid, "name": block["name"], "spec": block["spec"],
|
||||
"unit": block["unit"], "qty": each} # fmt: skip
|
||||
"spec_raw": block.get("spec_raw"), "unit": block["unit"], "qty": each} # fmt: skip
|
||||
|
||||
def precombo(self, whole: Any, ref: dict, doc: dict, copies: dict, depth: int = 0) -> str:
|
||||
"""사전조합(SC) = 겹 대가 — 줄 수량 = 손(기본 1) · 로직 줄 = 품셈 호표."""
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
type TabId,
|
||||
type TabTable,
|
||||
} from "./B09_Estimation_UI_Tabs";
|
||||
import { raw } from "./B09_Estimation_UI_Tabs_Sangeun";
|
||||
|
||||
const EMPTY =
|
||||
"연결 없음 — 연결된 구조물 조합 사본(일위대가)이 없음 · B05 · B06 에서 조합을 고르고 [저장]";
|
||||
@@ -54,6 +55,7 @@ export const buildDaegaList: TabBuild = ({ sheets }): TabTable | string => {
|
||||
frozen: 3,
|
||||
hint: "호표 = 일위대가표 블록 합계 · 줄을 누르면 그 블록으로",
|
||||
link: (i: number): Link | null => (rows[i] ? { tab: "dg", key: String(rows[i].key) } : null),
|
||||
tip: (i, key) => (key === "spec" ? raw(rows[i]?.spec, rows[i]?.spec_raw) : null),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -69,6 +71,7 @@ export const buildDaegaBlocks: TabBuild = ({ sheets }): TabTable | string => {
|
||||
key: b.id,
|
||||
name: `제 ${b.no} 호표 ${b.name}`,
|
||||
spec: b.spec,
|
||||
spec_raw: b.spec_raw,
|
||||
unit: b.unit,
|
||||
note: b.kind,
|
||||
});
|
||||
@@ -82,6 +85,7 @@ export const buildDaegaBlocks: TabBuild = ({ sheets }): TabTable | string => {
|
||||
key: `${b.id}|${n}`,
|
||||
name: `${CHILD}${ln.name || ""}`,
|
||||
spec: ln.spec || "",
|
||||
spec_raw: ln.spec_raw,
|
||||
qty: ln.qty,
|
||||
unit: ln.unit || "",
|
||||
sum_price: ln.price,
|
||||
@@ -129,5 +133,7 @@ export const buildDaegaBlocks: TabBuild = ({ sheets }): TabTable | string => {
|
||||
hint: `단가 = B09 프로젝트 한 벌 굳힌 단가 · ${ROUNDS} · 줄 누름 = 단가를 낸 탭(대가 줄 = 그 블록)`,
|
||||
link: (i: number) => links[i] ?? null,
|
||||
rowClass: (i: number) => (titles.has(i) ? "is-title" : null),
|
||||
tip: (i: number, key: string) =>
|
||||
key === "spec" ? raw(rows[i]?.spec, rows[i]?.spec_raw) : null,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -29,8 +29,8 @@ const PART_NAME: Record<string, string> = {
|
||||
};
|
||||
const PARTS = ["labor", "material", "expense"] as const;
|
||||
|
||||
/** 157-3 규격 풍선 = 원문번호 글(「이름 규격」 으로 바꾼 칸만) */
|
||||
const raw = (shown: unknown, codes: unknown): string | null =>
|
||||
/** 157-3 규격 풍선 = 원문번호 글(「이름 규격」 으로 바꾼 칸만) · 일위대가표도 씀(157-4) */
|
||||
export const raw = (shown: unknown, codes: unknown): string | null =>
|
||||
codes && codes !== shown ? `원문번호 ${String(codes)}` : null;
|
||||
|
||||
const notes = (sheets: Row): string =>
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
"""157-4 B09 일위대가표 품셈 규격 — 기계 · 직종 원문번호 → 「이름 규격」 · 번호 = 풍선(157-3 `raw`).
|
||||
|
||||
- 품셈 블록 `spec` = 「기계 굴착기(무한궤도) 0.6㎥ · …」 · `spec_raw` = 옛 글(번호) · 줄 그대로
|
||||
- 일위대가목록표 · 일위대가표 규격 칸 풍선 = 「원문번호 …」(바꾼 칸만)
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
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 import B09_Estimation_Labor_Source as source # noqa: E402
|
||||
from M02_MasterTemplete import M02_Item_Prices as item_prices # noqa: E402
|
||||
|
||||
B09 = ROOT / "B09_Estimation"
|
||||
IMPORTS = re.compile(r"^import[^;]*;\n", re.MULTILINE)
|
||||
|
||||
|
||||
def _block(code, given):
|
||||
whole = item_prices.load_prices(item_prices.to_json(item_prices.collect([code])))
|
||||
builder = source.Builder()
|
||||
bid = builder.poomsem(whole, {"로직": code, "입력값": given})
|
||||
return builder.blocks[bid]
|
||||
|
||||
|
||||
def test_기계_번호는_이름_규격_번호는_spec_raw():
|
||||
given = {"기계": "0201-0060", "지역": "전국평균", "보정작업": "해당 없음"}
|
||||
block = _block("GC000267", given)
|
||||
assert block["spec"] == "기계 굴착기(무한궤도) 0.6㎥ · 보정작업 해당 없음 · 지역 전국평균"
|
||||
assert block["spec_raw"] == "기계 0201-0060 · 보정작업 해당 없음 · 지역 전국평균"
|
||||
assert len(block["lines"]) == 4
|
||||
|
||||
|
||||
def test_직종_번호도_이름():
|
||||
given = {"기계": "2106-0002", "지역": "전국평균", "조종원직종": "1048"}
|
||||
block = _block("GC000275", given)
|
||||
assert "1048" in block["spec_raw"] and "1048" not in block["spec"]
|
||||
assert not re.search(r"\d{4}-\d{4}", block["spec"])
|
||||
|
||||
|
||||
def test_규격_칸_풍선():
|
||||
spec, raw = "기계 굴착기(무한궤도) 0.6㎥", "기계 0201-0060"
|
||||
sheets = {
|
||||
"daega": [{"no": 1, "key": "DG:a", "kind": "품셈", "name": "n", "spec": spec,
|
||||
"spec_raw": raw, "unit": "시간"}],
|
||||
"daega_blocks": [{"id": "DG:a", "no": 1, "kind": "품셈", "name": "n", "spec": spec,
|
||||
"spec_raw": raw, "unit": "시간", "cite": "", "lines": [
|
||||
{"kind": "daega", "key": "DG:a", "name": "n", "spec": spec,
|
||||
"spec_raw": raw, "qty": 1, "unit": "시간", "price": 1,
|
||||
"amount": 1, "prices": {"labor": 1, "material": 0, "expense": 0},
|
||||
"amounts": {"labor": 1, "material": 0, "expense": 0}}],
|
||||
"labor": 1, "material": 0, "expense": 0, "sum": 1}],
|
||||
} # fmt: skip
|
||||
sangeun = (B09 / "B09_Estimation_UI_Tabs_Sangeun.ts").read_text("utf-8")
|
||||
src = re.search(r"^export const raw =.*?;$", sangeun, re.MULTILINE | re.DOTALL)[0] + "".join(
|
||||
IMPORTS.sub("", (B09 / f"B09_Estimation_{n}.ts").read_text("utf-8"))
|
||||
for n in ("UI_Tabs", "UI_Tabs_Labor")
|
||||
) # 산근 탭의 풍선 함수 `raw` 만(같은 이름 상수가 겹쳐 파일째는 못 잇음)
|
||||
body = f"""
|
||||
const sheets = {json.dumps(sheets, ensure_ascii=False)};
|
||||
const list = buildDaegaList({{ sheets }});
|
||||
const blocks = buildDaegaBlocks({{ sheets }});
|
||||
console.log(JSON.stringify({{
|
||||
list: list.tip(0, "spec"), listName: list.tip(0, "name"),
|
||||
head: blocks.tip(0, "spec"), line: blocks.tip(1, "spec"), total: blocks.tip(2, "spec"),
|
||||
}}));"""
|
||||
result = subprocess.run(
|
||||
["node", "--experimental-strip-types", "--no-warnings",
|
||||
"--input-type=module-typescript", "-e", src + body],
|
||||
capture_output=True, text=True, encoding="utf-8", timeout=60,
|
||||
) # fmt: skip
|
||||
assert result.returncode == 0, result.stderr
|
||||
got = json.loads(result.stdout)
|
||||
tip = "원문번호 기계 0201-0060"
|
||||
assert got == {"list": tip, "listName": None, "head": tip, "line": tip, "total": None}
|
||||
Reference in New Issue
Block a user