feat(M02-일위대가조합): 조합 다이어그램 - 로직별 넣을 값 → 로직 카드 → 비용 구분 칸 · 미리 보기와 값 한 벌 · 카드 누르면 단가산출 로직 화면

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D2nUMUMTsqm93PbQtipo6c
This commit is contained in:
2026-10-02 09:34:08 +09:00
co-authored by Claude Sonnet 5.5
parent 7418571b63
commit af201fbbd3
7 changed files with 999 additions and 2 deletions
+110
View File
@@ -0,0 +1,110 @@
"""일위대가 조합 다이어그램 값 만들기(`M02_MasterTemplete_Labor_Diagram.ts` 의 `buildDiagramData`) — 미리 보기 줄 → 로직 카드 출력.
화면 TS 를 그 자리에서 트랜스파일해 node 로 돌림 — 상대 경로 TS 는 이어 트랜스파일 · 화면 부품(@ui)은 빈 흉내 ·
금액 모양(Logic_Money)도 빈 흉내(이 시험은 값 만들기만 봄). 견본 줄은 이 시험 안에 둠.
"""
import json
import shutil
import subprocess
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[2]
SOURCE = ROOT / "M02_MasterTemplete" / "M02_MasterTemplete_Labor_Diagram.ts"
RUNNER = r"""
const fs = require("fs");
const path = require("path");
const [root, file] = process.argv.slice(1);
const ts = require(path.join(root, "config", "node_modules", "typescript"));
const stub = new Proxy({}, { get: (_t, k) => (k === "currentLanguageIndex" ? 0 : () => undefined) });
const cache = {};
function load(f) {
if (cache[f]) return cache[f].exports;
const js = ts.transpileModule(fs.readFileSync(f, "utf8"), {
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
}).outputText;
const box = (cache[f] = { exports: {} });
const req = (id) => {
if (id.startsWith(".") && !id.includes("M01_MasterData")) {
const p = path.resolve(path.dirname(f), id);
return fs.existsSync(p + ".ts") ? load(p + ".ts") : stub;
}
return stub;
};
new Function("exports", "module", "require", js)(box.exports, box, req);
return box.exports;
}
const m = load(file);
const jobs = JSON.parse(fs.readFileSync(0, "utf8"));
process.stdout.write(JSON.stringify(jobs.map((j) => m.buildDiagramData(j))));
"""
def run(jobs: list[dict]) -> list:
if shutil.which("node") is None:
pytest.skip("node 가 없음")
done = subprocess.run(
["node", "-e", RUNNER, str(ROOT), str(SOURCE)],
input=json.dumps(jobs, ensure_ascii=False),
capture_output=True,
text=True,
encoding="utf-8",
check=True,
)
return json.loads(done.stdout)
ROWS = [
{
"key": "GC000001",
"name": "돌쌓기",
"unit": "원/㎡",
"inputs": [{"name": "높이", "unit": "m"}, {"name": "지역", "unit": ""}],
},
{
"key": "GF000002",
"name": "작업량",
"unit": "㎥/시간",
"inputs": [{"name": "지역", "unit": ""}],
},
{"key": "GC000003", "name": "막힌 로직", "unit": "원/m", "inputs": []},
]
def test_돈_값_멈춤_세_갈래():
lines = [
{"ok": True, "갈래": "돈", "노무비": 100, "재료비": 50, "경비": 0, "계": 150},
{"ok": True, "갈래": "결과", "결과": 8.946},
{"ok": False, "까닭": "표에 맞는 줄이 없음"},
]
(got,) = run([{"rows": ROWS, "values": {"GC000001": {"높이": "4"}}, "lines": lines}])
outs = [x["out"] for x in got["logics"]]
assert outs[0] == {"노무비": 100, "재료비": 50, "경비": 0, "계": 150}
assert outs[1] == {"값": 8.946}
assert outs[2] == {"멈춤": "표에 맞는 줄이 없음"}
def test_넣은_값은_로직별_빈_값은_빈_글():
(got,) = run(
[
{
"rows": ROWS,
"values": {"GC000001": {"높이": "4"}, "GF000002": {"지역": "서울"}},
"lines": None,
}
]
)
first, second, third = got["logics"]
assert [(i["name"], i["value"]) for i in first["inputs"]] == [("높이", "4"), ("지역", "")]
assert first["inputs"][0]["unit"] == "m"
assert [(i["name"], i["value"]) for i in second["inputs"]] == [("지역", "서울")]
assert third["inputs"] == []
def test_아직_안_셌으면_출력_없음():
(got,) = run([{"rows": ROWS, "values": {}, "lines": None}])
assert [x["out"] for x in got["logics"]] == [None, None, None]
assert [x["key"] for x in got["logics"]] == ["GC000001", "GF000002", "GC000003"]