Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ACSGaHdLgnkEkoA4LDtMU
269 lines
10 KiB
Python
269 lines
10 KiB
Python
"""단가산출 흐름도 입력 판 — 다른 로직 입력 = 마스터 로직 고르기(순수 부분 · `M02_DF_InputPanel_Build.ts`).
|
|
|
|
화면 TS 를 그 자리에서 트랜스파일해 node 로 돌림. 견본 로직 줄 · 흐름은 이 시험 안에 둠(마스터 내용에 기대지 않음).
|
|
"""
|
|
|
|
import json
|
|
import shutil
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
SOURCE = ROOT / "M02_MasterTemplete" / "DataFlow" / "M02_DF_InputPanel_Build.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 js = ts.transpileModule(fs.readFileSync(file, "utf8"), {
|
|
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
|
}).outputText;
|
|
const box = { exports: {} };
|
|
new Function("exports", "module", "require", js)(box.exports, box, () => ({}));
|
|
const m = box.exports;
|
|
(async () => {
|
|
const jobs = JSON.parse(fs.readFileSync(0, "utf8"));
|
|
const out = [];
|
|
for (const j of jobs) {
|
|
if (j.name === "wouldCycle") {
|
|
const graph = j.args[2];
|
|
out.push(await m.wouldCycle(j.args[0], j.args[1], async (k) => graph[k] ?? null));
|
|
} else out.push(await m[j.name](...j.args));
|
|
}
|
|
process.stdout.write(JSON.stringify(out));
|
|
})();
|
|
"""
|
|
|
|
|
|
def call(name: str, *args):
|
|
if shutil.which("node") is None:
|
|
pytest.skip("node 가 없음")
|
|
done = subprocess.run(
|
|
["node", "-e", RUNNER, str(ROOT), str(SOURCE)],
|
|
input=json.dumps([{"name": name, "args": list(args)}], ensure_ascii=False),
|
|
capture_output=True,
|
|
text=True,
|
|
encoding="utf-8",
|
|
check=True,
|
|
)
|
|
return json.loads(done.stdout)[0]
|
|
|
|
|
|
def row(key, name, book, chapter, no="", unit="원/㎡", **more):
|
|
return {
|
|
"file": f"로직_{book}.json",
|
|
"구분": book,
|
|
"상세구분": chapter,
|
|
"키": key,
|
|
"원문번호": no,
|
|
"이름": name,
|
|
"결과단위": unit,
|
|
**more,
|
|
}
|
|
|
|
|
|
ITEMS = [
|
|
row("GF000219", "돌쌓기 메쌓기(인력)", "산림품셈", "13장 구조물", "13-4-1"),
|
|
row("GF000220", "돌쌓기 찰쌓기(인력)", "산림품셈", "13장 구조물", "13-4-2"),
|
|
row("GF000025", "떼붙임(재배잔디)", "산림품셈", "05장 녹화", "5-12"),
|
|
row("GC000051", "보행자 안전통로", "건설품셈", "01장 공통", "1-3-9", "원/m"),
|
|
row("DF-0001", "내 흄관 로직", "흐름도", "내가 만든 것", "", "원/m"),
|
|
]
|
|
|
|
|
|
def keys(items):
|
|
return [i["키"] for i in items]
|
|
|
|
|
|
def test_찾기는_띄어쓰기로_나눈_말을_모두_포함():
|
|
got = call("filterLogics", ITEMS, {"text": "돌쌓기 메쌓기"})
|
|
assert keys(got) == ["GF000219"]
|
|
assert keys(call("filterLogics", ITEMS, {"text": "돌쌓기"})) == ["GF000219", "GF000220"]
|
|
assert keys(call("filterLogics", ITEMS, {"text": "돌 쌓기메쌓기"})) == [
|
|
"GF000219"
|
|
] # 띄어쓰기는 가리지 않음
|
|
assert keys(call("filterLogics", ITEMS, {"text": "13-4"})) == [
|
|
"GF000219",
|
|
"GF000220",
|
|
] # 원문번호
|
|
assert keys(call("filterLogics", ITEMS, {"text": "gc000051"})) == [
|
|
"GC000051"
|
|
] # 번호 · 대소문자
|
|
assert call("filterLogics", ITEMS, {"text": ""}) == ITEMS
|
|
|
|
|
|
def test_원문_장으로_거름():
|
|
assert keys(call("filterLogics", ITEMS, {"text": "", "book": "건설품셈"})) == ["GC000051"]
|
|
got = call("filterLogics", ITEMS, {"text": "", "book": "산림품셈", "chapter": "05장 녹화"})
|
|
assert keys(got) == ["GF000025"]
|
|
assert call("filterLogics", ITEMS, {"text": "메쌓기", "book": "건설품셈"}) == []
|
|
|
|
|
|
def test_원문_장_목록은_나온_차례():
|
|
assert call("logicBooks", ITEMS) == ["산림품셈", "건설품셈", "흐름도"]
|
|
assert call("logicChapters", ITEMS, "산림품셈") == ["13장 구조물", "05장 녹화"]
|
|
assert call("logicChapters", ITEMS, "") == [
|
|
"13장 구조물",
|
|
"05장 녹화",
|
|
"01장 공통",
|
|
"내가 만든 것",
|
|
]
|
|
|
|
|
|
def box(id_, kind, name, **more):
|
|
return {
|
|
"id": id_,
|
|
"kind": kind,
|
|
"name": name,
|
|
"color": "#fff",
|
|
"unit": "-",
|
|
"x": 0,
|
|
"y": 0,
|
|
**more,
|
|
}
|
|
|
|
|
|
FLOW = {
|
|
"key": "GF000219",
|
|
"name": "돌쌓기 메쌓기(인력)",
|
|
"resultUnit": "원/㎡",
|
|
"userElements": [],
|
|
"links": [],
|
|
"boxes": [
|
|
box(
|
|
"b1",
|
|
"input",
|
|
"입력",
|
|
input={
|
|
"mode": "variable",
|
|
"elements": [],
|
|
"variables": [
|
|
{"name": "뒷길이", "type": "number", "unit": "㎝", "example": 30},
|
|
{
|
|
"name": "돌",
|
|
"type": "choice",
|
|
"unit": "",
|
|
"choices": ["깬돌", "견치돌"],
|
|
"example": "깬돌",
|
|
},
|
|
],
|
|
},
|
|
),
|
|
box("o1", "output", "노무비", unit="원/㎡", output={"cost": "노무비", "unit": "원/㎡"}),
|
|
],
|
|
}
|
|
|
|
|
|
def test_흐름_로직_고르기는_출력_상자와_설계_때_정할_것():
|
|
ref = call("masterRef", "GF000219", FLOW, False, "원/㎡")
|
|
assert ref["key"] == "GF000219"
|
|
assert ref["outputs"] == [{"name": "노무비", "unit": "원/㎡"}]
|
|
assert ref["designs"] == [{"name": "뒷길이", "unit": "㎝"}, {"name": "돌", "unit": "-"}]
|
|
assert ref["examples"] == {"뒷길이": 30, "돌": "깬돌"}
|
|
|
|
|
|
def test_옛_로직_변환은_노무비_재료비_경비_계_넷():
|
|
ref = call("masterRef", "GF000219", FLOW, True, "원/㎡")
|
|
assert [o["name"] for o in ref["outputs"]] == ["노무비", "재료비", "경비", "계"]
|
|
assert {o["unit"] for o in ref["outputs"]} == {"원/㎡"}
|
|
assert [d["name"] for d in ref["designs"]] == ["뒷길이", "돌"] # 받는 알약은 흐름 그대로
|
|
|
|
|
|
def test_이어지지_않은_다른_로직_받는_알약도_설계_때_정할_것():
|
|
inner = {
|
|
"mode": "logic",
|
|
"elements": [],
|
|
"variables": [],
|
|
"logic": {
|
|
"key": "GF000250",
|
|
"outputs": [{"name": "작업량", "unit": "㎥/시간"}],
|
|
"designs": [{"name": "지역", "unit": "-"}, {"name": "기계", "unit": "-"}],
|
|
"examples": {"지역": "서울"},
|
|
},
|
|
}
|
|
flow = {**FLOW, "boxes": [*FLOW["boxes"], box("b2", "input", "작업량", input=inner)]}
|
|
flow["links"] = [
|
|
{"id": "l1", "from": {"box": "b1", "name": "돌"}, "to": {"box": "b2", "name": "기계"}}
|
|
]
|
|
ref = call("logicRefOf", "X", flow)
|
|
names = [d["name"] for d in ref["designs"]]
|
|
assert "지역" in names and "기계" not in names # 선이 이어진 받는 알약은 뺌
|
|
assert ref["examples"]["지역"] == "서울"
|
|
|
|
|
|
def test_자기_자신과_돌고_도는_로직은_막음():
|
|
graph = {
|
|
"A": {
|
|
"boxes": [
|
|
box(
|
|
"b",
|
|
"input",
|
|
"x",
|
|
input={"mode": "logic", "elements": [], "variables": [], "logic": {"key": "B"}},
|
|
)
|
|
]
|
|
},
|
|
"B": {
|
|
"boxes": [
|
|
box(
|
|
"b",
|
|
"input",
|
|
"x",
|
|
input={
|
|
"mode": "logic",
|
|
"elements": [],
|
|
"variables": [],
|
|
"logic": {"key": "SELF"},
|
|
},
|
|
)
|
|
]
|
|
},
|
|
"C": {"boxes": []},
|
|
}
|
|
assert call("wouldCycle", "SELF", "SELF", graph) is True # 자기 자신
|
|
assert call("wouldCycle", "SELF", "A", graph) is True # A → B → SELF
|
|
assert call("wouldCycle", "SELF", "C", graph) is False
|
|
assert call("wouldCycle", None, "A", graph) is False # 새 로직은 아직 키가 없어 돌 수 없음
|
|
assert call("wouldCycle", "SELF", "없음", graph) is False # 못 읽으면 막지 않음
|
|
|
|
|
|
def test_조건_찾기는_인력_기계_유가전력에_쓰고_이름_또는_키가_같은_줄():
|
|
labor = {"키": "LB000033", "원문번호": "1033", "이름": "석공"}
|
|
assert call("matchesRow", labor, "이름", "석공") is True
|
|
assert call("matchesRow", labor, "이름", "석 공") is True # 띄어쓰기는 가리지 않음
|
|
assert call("matchesRow", labor, "이름", "할석공") is False # 같음이라 일부만 맞아선 안 잡음
|
|
assert call("matchesRow", labor, "키", "lb000033") is True
|
|
assert call("matchesRow", labor, "키", "1033") is True # 원문번호도 키로 침
|
|
assert call("matchesRow", labor, "이름", "") is False # 알약 값이 비면 안 잡음
|
|
|
|
|
|
def test_조건_찾기_요소와_검사():
|
|
port = {"box": "b2", "name": "직종"}
|
|
el = call("groupRuleElement", "노임", "이름", port)
|
|
assert (
|
|
el["mode"] == "rule" and el["ref"] == "" and el["name"] == "노임" and el["slot"] == "노임"
|
|
)
|
|
assert el["rule"] == {"field": "이름", "op": "같음", "from": port}
|
|
assert call("checkGroupRule", el, []) is None
|
|
assert call("checkGroupRule", {**el, "name": ""}, []) == "자리 이름을 적어 주세요"
|
|
assert call("checkGroupRule", el, [el]) == "같은 이름의 알약이 이미 있음"
|
|
no_pill = call("groupRuleElement", "노임", "이름", {"box": "", "name": ""})
|
|
assert "변수 알약" in call("checkGroupRule", no_pill, [])
|
|
|
|
|
|
def test_옛_줄_입력과_자동값으로_다른_로직_입력을_읽음():
|
|
inputs = [{"이름": "뒷길이", "단위": "㎝"}, {"이름": "돌"}, {"이름": "쌓기"}]
|
|
auto = {"뒷길이": 25, "돌": "깬잡석", "쌓기": "", "남는": 1}
|
|
ref = call("rowRef", "GF000219", "돌쌓기 메쌓기(인력)", "원/㎡", inputs, auto)
|
|
assert ref["key"] == "GF000219"
|
|
assert [o["name"] for o in ref["outputs"]] == ["노무비", "재료비", "경비", "계"]
|
|
assert ref["designs"] == [
|
|
{"name": "뒷길이", "unit": "㎝"},
|
|
{"name": "돌", "unit": "-"},
|
|
{"name": "쌓기", "unit": "-"},
|
|
]
|
|
assert ref["examples"] == {"뒷길이": 25, "돌": "깬잡석"} # 빈 글 · 입력에 없는 이름은 뺌
|