feat(778-4): 율 줄을 단가산출(SK)에 담음 — 고정 비율 더한줄 · 기준 묶음 · 줄 목록 · 율 위 율
- SK 더한줄 율 줄 허용(일위대가 비율 줄과 같은 꼴 `고정 비율 {기준, 비목}` · 수량 = 율)
- 기준 = 비목(노무비 · 재료비 · 경비) · 묶음(인력 · 재료 · 장비 = 그 줄 종류만 · 기계 노무 섞인 블록도 인력 줄만) ·
줄 목록(cid · add:#k · 앞 율 줄 = 율 위 율) · 검사 = 자기 · 뒤 율 줄 막음
- 밑 공용 함수 `M02_Labor_Fixed.rate_base` ↔ 짝 `Labor_Calc.ts` `rateBase`(일위대가 비율 줄 · Expand 같이 씀)
- SK 셈 `_rated` = 엔진 한 번 돈 줄로 밑 → 율 줄 값묶음 → 엔진 다시(율 줄 없는 문서 = 한 번 · 값 그대로)
- B09 pct 밑수 `bundle:` · `rows:`(파이썬 · TS 짝) · SK 블록 율 줄 = pct
- 굳힘 — 바탕 없는 SK 율 줄은 한 줄로 안 펼침(참조 그대로) · 겹침 때 기준 열쇠 새 차례
- 화면 — [율 줄 추가] 창(새 `M02_MasterTemplete_Sangeun_Rate.ts` · 부품 상자만) · 율 줄 = % 줄로 보임 ·
더한줄 지움 · 되살림 때 기준 열쇠 옮김
- 시험 — 새 test_778_4_rate_line(25) · 거울 헬퍼에 rateBase 길 · 전후 대조 SK 16 · 일위대가 118 · B09 대가 입력 14 같음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
/* M02 일위대가 수량 · 반영 · 합(`M02_MasterTemplete_Labor_Calc.ts` `settle`) 거울 헬퍼 —
|
||||
* 그 자리에서 트랜스파일해 Node 로 돌림(상대 경로 .ts 가져옴도 · B09 `rounddown` 751-3) ·
|
||||
* stdin = {답, 문서, 값, 열?} · stdout = settle 결과 JSON. */
|
||||
* stdin = {답, 문서, 값, 열?} · stdout = settle 결과 JSON · {밑: {기준, 줄}} = `rateBase`(778-4). */
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
|
||||
@@ -25,4 +25,7 @@ function load(at) {
|
||||
const box = { exports: load(file) };
|
||||
|
||||
const input = JSON.parse(fs.readFileSync(0, "utf8"));
|
||||
process.stdout.write(JSON.stringify(box.exports.settle(input.답, input.문서, input.값, input.열)));
|
||||
const out = input.밑
|
||||
? box.exports.rateBase(input.밑.기준, input.밑.줄)
|
||||
: box.exports.settle(input.답, input.문서, input.값, input.열);
|
||||
process.stdout.write(JSON.stringify(out));
|
||||
|
||||
@@ -0,0 +1,341 @@
|
||||
"""778-4 단가산출(SK) 율 줄 — 고정 비율 더한줄 · 기준 = 비목 · 묶음 · 줄 목록 · 율 위 율
|
||||
(요구 623 · 분석 621-D 2-4 · 5-2) · M01 읽기만 · 쓰는 일 없음.
|
||||
|
||||
- 서버 셈(`M02_Sangeun_Calc._rated`) = 손셈 · 묶음 인력 = 인력 줄만(기계 노무 섞인 블록 = 노무비 합과 다름)
|
||||
- 밑 공용 함수 `M02_Labor_Fixed.rate_base` ↔ `Labor_Calc.ts` `rateBase` 거울 · 일위대가 비율 줄 settle 거울(1e-9)
|
||||
- 검사 — 자기 · 뒤 율 줄 · 꼴 틀린 열쇠 · 모르는 기준 막음 · 율 줄 없는 문서 = 엔진 한 번(값 그대로)
|
||||
- B09 — 율 기준 → pct 밑수(`part:` · `bundle:` · `rows:`) · 파이썬 = TS · SK 블록 밑수 꼴
|
||||
- 굳힘 — 바탕 없는 SK 율 줄 = 한 줄로 안 펼침 · 겹침(`compose`) 열쇠 새 차례
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from decimal import Decimal
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from B09_Estimation import B09_Estimation_Labor_Source as source
|
||||
from B09_Estimation.B09_Estimation_Calc_Labor import compute_labor
|
||||
from M01_MasterData import M01_MasterData_Store as m01
|
||||
from M02_MasterTemplete import M02_Labor_Doc as labor
|
||||
from M02_MasterTemplete import M02_Labor_Fixed as fixed
|
||||
from M02_MasterTemplete import M02_Library_Flat as flat
|
||||
from M02_MasterTemplete import M02_Sangeun_Calc as calc
|
||||
from M02_MasterTemplete import M02_Sangeun_Doc as sd
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
IMPORTS = re.compile(r"^import[^;]*;\r?\n", re.MULTILINE)
|
||||
SUB = "GC000999" # 원/주 · 조경공 · 보통인부 + 고소작업차 로직 줄 + 덧줄 공구손료 3 %
|
||||
MACHINE = "GC000268" # 기계경비(손료 보정 없는 기종) — 노무비 몫 있음
|
||||
|
||||
|
||||
def _whole():
|
||||
return m01.loaded()[1]
|
||||
|
||||
|
||||
def _rate(basis, q=2, part="경비", name="공구손료"):
|
||||
fx = {"이름": name, "단가": {}, "이음": {}, "비율": {"기준": basis, "비목": part}}
|
||||
return {"고정": fx, "수량": q}
|
||||
|
||||
|
||||
def _doc(adds, **more):
|
||||
return {
|
||||
"키": "SK-000001",
|
||||
"이름": "율 시험",
|
||||
"단위": "㎥",
|
||||
"바탕": None,
|
||||
"더한줄": adds,
|
||||
**more,
|
||||
}
|
||||
|
||||
|
||||
def _run(doc):
|
||||
whole = _whole()
|
||||
return labor._json(calc.run(sd.clean(doc, None, whole), whole))
|
||||
|
||||
|
||||
LABOR = [
|
||||
{"종류": "인력", "요소": "LB000002", "수량": 0.5}, # 보통인부
|
||||
{"종류": "인력", "요소": "LB000003", "수량": 0.2}, # 특별인부
|
||||
{"종류": "로직", "로직": MACHINE, "입력값": {}, "수량": 0.29},
|
||||
]
|
||||
|
||||
|
||||
# ── 서버 셈 ──────────────────────────────────────────────────────────────────
|
||||
def test_묶음_인력은_인력_줄만_노무비와_다름():
|
||||
got = _run(_doc([*LABOR, _rate("인력"), _rate("노무비", 3, name="노무 율")]))
|
||||
by = {x["cid"]: x for x in got["줄"]}
|
||||
people = sum(Decimal(str(by[f"add:#{k}"]["금액"])) for k in (0, 1))
|
||||
mach = Decimal(str(by["add:#2"]["비목"]["노무비"]))
|
||||
assert mach > 0 # 기계 노무 섞인 블록
|
||||
tool, cost = by["add:#3"], by["add:#4"]
|
||||
assert tool["금액"] == pytest.approx(float(people * 2 / 100), abs=0.1)
|
||||
assert cost["금액"] == pytest.approx(float((people + mach) * 3 / 100), abs=0.1)
|
||||
assert tool["율"] == 2 and tool["규격"] == "인력의 %" and tool["묶음"] == "경비"
|
||||
assert tool["비목"]["경비"] == pytest.approx(tool["금액"])
|
||||
assert got["짝잃음"] == []
|
||||
|
||||
|
||||
def test_줄_목록_절_소계와_율_위_율():
|
||||
adds = [*LABOR, _rate("인력"), _rate(["add:#0", "add:#3"], 10, "재료비", "율 위 율")]
|
||||
adds.append(_rate(["add:#0", "add:#1"], 5, name="절 소계"))
|
||||
got = _run(_doc(adds))
|
||||
by = {x["cid"]: x for x in got["줄"]}
|
||||
first = Decimal(str(by["add:#0"]["금액"]))
|
||||
tool = (first + Decimal(str(by["add:#1"]["금액"]))) * 2 / 100
|
||||
assert by["add:#4"]["금액"] == pytest.approx(float((first + tool) * 10 / 100), abs=0.1)
|
||||
assert by["add:#4"]["비목"]["재료비"] == pytest.approx(by["add:#4"]["금액"])
|
||||
part = first + Decimal(str(by["add:#1"]["금액"]))
|
||||
assert by["add:#5"]["금액"] == pytest.approx(float(part * 5 / 100), abs=0.1)
|
||||
|
||||
|
||||
def test_바탕_SK_율_줄과_바탕_덧줄():
|
||||
whole = _whole()
|
||||
doc = {**_doc([_rate("인력", 2)]), "바탕": {"로직": SUB}, "단위": "주"}
|
||||
got = labor._json(calc.run(sd.clean(doc, None, whole), whole))
|
||||
bare = labor.run_line(whole, SUB, {})
|
||||
people = sum(Decimal(str(bare["줄"][k]["금액"])) for k in (0, 1))
|
||||
rate = next(x for x in got["줄"] if x["cid"] == "add:#0")
|
||||
assert rate["금액"] == pytest.approx(float(people * 2 / 100), abs=0.1)
|
||||
assert got["기준"]["경비"] == pytest.approx(
|
||||
float(Decimal(str(bare["기준"]["경비"])) + Decimal(str(rate["금액"]))), abs=1
|
||||
)
|
||||
|
||||
|
||||
def test_없는_기준_줄_짝잃음():
|
||||
got = _run(_doc([LABOR[0], _rate(["add:#0", "labor:LB000099#0"])]))
|
||||
assert got["짝잃음"] == [{"cid": "add:#1", "까닭": "율 기준 줄 없음 「labor:LB000099#0」"}]
|
||||
got = _run(_doc([LABOR[0], _rate([])])) # 기준 줄을 지운 뒤 = 밑 0 · 알림
|
||||
assert got["짝잃음"] == [{"cid": "add:#1", "까닭": "율 기준 줄 없음 「」"}]
|
||||
assert got["줄"][1]["금액"] == 0
|
||||
|
||||
|
||||
def test_율_줄_없으면_엔진_한_번(monkeypatch):
|
||||
calls = []
|
||||
real = labor.run_line
|
||||
monkeypatch.setattr(labor, "run_line", lambda *a, **k: calls.append(1) or real(*a, **k))
|
||||
_run(_doc(LABOR))
|
||||
assert len(calls) == 1
|
||||
_run(_doc([*LABOR, _rate("인력")]))
|
||||
assert len(calls) == 3
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"adds, why",
|
||||
[
|
||||
([LABOR[0], _rate(["add:#1"])], "자기"),
|
||||
([LABOR[0], _rate(["add:#2"]), _rate("인력")], "뒤 율"),
|
||||
([LABOR[0], _rate(["보통인부"])], "꼴 아님"),
|
||||
([LABOR[0], _rate("합계")], "비율"),
|
||||
],
|
||||
)
|
||||
def test_검사_막음(adds, why):
|
||||
with pytest.raises(sd.DocError, match=why):
|
||||
sd.clean(_doc(adds), None, _whole())
|
||||
|
||||
|
||||
# ── 밑 공용 함수 거울 ───────────────────────────────────────────────────────
|
||||
def _node(payload: dict):
|
||||
got = subprocess.run(
|
||||
["node", os.path.join(HERE, "helper_m02_labor_calc.cjs")],
|
||||
input=json.dumps(payload, ensure_ascii=False, default=float),
|
||||
capture_output=True, text=True, encoding="utf-8", check=True,
|
||||
) # fmt: skip
|
||||
return json.loads(got.stdout)
|
||||
|
||||
|
||||
ROWS = [
|
||||
{"키": "a", "묶음": "인력", "비목": {"노무비": 1000.5, "재료비": 0, "경비": 0}},
|
||||
{"키": "b", "묶음": "장비", "비목": {"노무비": 300.25, "재료비": 12.5, "경비": 700}},
|
||||
{"키": "c", "묶음": "재료", "비목": {"노무비": 0, "재료비": 4000, "경비": None}},
|
||||
{"키": "d", "묶음": None, "비목": {"노무비": 10, "재료비": 20, "경비": 30}},
|
||||
{"키": "r", "묶음": None, "비목": {"노무비": 0, "재료비": 0, "경비": 55.5}, "율": True},
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"basis", ["노무비", "재료비", "경비", "인력", "재료", "장비", ["a", "c"], ["a", "r"], ["zz"]]
|
||||
)
|
||||
def test_밑_파이썬과_TS_같음(basis):
|
||||
py = fixed.rate_base(basis, json.loads(json.dumps(ROWS)))
|
||||
assert _node({"밑": {"기준": basis, "줄": ROWS}}) == pytest.approx(float(py), rel=1e-9)
|
||||
|
||||
|
||||
def _fx(name, price, link=None, rate=None):
|
||||
fx = {"이름": name, "단위": "식", "단가": price, "이음": link or {}}
|
||||
return fx | ({"비율": rate} if rate else {})
|
||||
|
||||
|
||||
def test_일위대가_비율_줄_묶음_목록_settle_거울():
|
||||
plain = _fx("손 노임", {"노무비": 50000})
|
||||
tool = _fx("공구손료", {}, rate={"기준": "인력", "비목": "경비"})
|
||||
doc = labor.clean(
|
||||
{
|
||||
"담은로직": [
|
||||
{"고정": _fx("보통인부", {"노무비": 1}, {"노임": "LB000002"}), "수량": {"손": 0.5}},
|
||||
{"고정": plain, "수량": {"손": 0.3}},
|
||||
{"고정": tool, "수량": {"손": 2}},
|
||||
{"고정": _fx("목록 율", {}, rate={"기준": [fixed.fixed_id(tool)], "비목": "재료비"}),
|
||||
"수량": {"손": 10}},
|
||||
{"고정": _fx("노무 율", {}, rate={"기준": "노무비", "비목": "경비"}), "수량": {"손": 3}},
|
||||
],
|
||||
"자재": [],
|
||||
}
|
||||
) # fmt: skip
|
||||
answer = labor.calc(doc, {})
|
||||
py = labor.settle(answer, doc, {})
|
||||
ts = _node({"답": answer, "문서": doc, "값": {}})
|
||||
_same(json.loads(json.dumps(labor._json(py), default=float)), ts)
|
||||
lines = py["줄"]
|
||||
people = Decimal(str(lines[0]["반영"]["노무비"]))
|
||||
assert lines[2]["기준"]["경비"] == people # 인력 = 노임 이음 줄만(손 노임 뺌)
|
||||
assert lines[3]["기준"]["재료비"] == lines[2]["반영"]["계"] # 율 위 율
|
||||
assert lines[4]["기준"]["경비"] == people + Decimal(str(lines[1]["반영"]["노무비"]))
|
||||
|
||||
|
||||
def _same(a, b) -> None:
|
||||
if isinstance(a, dict):
|
||||
assert set(a) == set(b), set(a) ^ set(b)
|
||||
for k in a:
|
||||
_same(a[k], b[k])
|
||||
elif isinstance(a, list):
|
||||
assert len(a) == len(b)
|
||||
for x, y in zip(a, b):
|
||||
_same(x, y)
|
||||
elif isinstance(a, (int, float)) and not isinstance(a, bool):
|
||||
assert b == pytest.approx(a, rel=1e-9, abs=1e-9)
|
||||
else:
|
||||
assert a == b
|
||||
|
||||
|
||||
# ── B09 ─────────────────────────────────────────────────────────────────────
|
||||
def test_B09_율_기준_밑수_꼴():
|
||||
rate = {"이름": "공구손료", "비율": {"기준": "인력", "비목": "경비"}}
|
||||
assert source.fixed_line(rate, 2)["base"] == "bundle:labor"
|
||||
rate["비율"]["기준"] = "노무비"
|
||||
assert source.fixed_line(rate, 2)["base"] == "part:labor"
|
||||
rate["비율"]["기준"] = ["x", "y"]
|
||||
assert source.fixed_line(rate, 2)["kind"] == "blank" # 일위대가 블록 = 열쇠 차례 모름
|
||||
assert source.fixed_line(rate, 2, {"y": 4, "x": 1})["base"] == "rows:1,4"
|
||||
|
||||
|
||||
def test_B09_SK_블록_율_줄_pct():
|
||||
whole = _whole()
|
||||
adds = [{"종류": "인력", "요소": "LB000003", "수량": 0.1}, _rate("인력")]
|
||||
adds.append(_rate(["labor:LB000002#0", "add:#1"], 5, "재료비", "목록"))
|
||||
doc = sd.clean({**_doc(adds), "바탕": {"로직": SUB}, "단위": "주"}, None, whole)
|
||||
build = source.Builder()
|
||||
bid = build.sangeun(whole, {"층": "project", "키": "SK-000001"}, {**doc, "굳힌줄": {}}, {})
|
||||
lines = build.blocks[bid]["lines"]
|
||||
pct = {ln["name"]: ln for ln in lines if ln["kind"] == "pct"}
|
||||
assert pct["공구손료"]["base"] == "bundle:labor" and pct["공구손료"]["qty"] == 2
|
||||
assert pct["목록"]["base"] == "rows:1,4" and pct["목록"]["part"] == "material"
|
||||
assert [ln["kind"] for ln in lines[:5]] == ["labor", "labor", "machine", "labor", "pct"]
|
||||
|
||||
|
||||
BLOCK = {
|
||||
"id": "DGR", "cover": "S:r", "kind": "조합", "name": "율 블록", "spec": "", "unit": "㎥",
|
||||
"cite": "", "origin": {},
|
||||
"lines": [
|
||||
{"kind": "labor", "key": "L1", "name": "보통인부", "qty": 0.5},
|
||||
{"kind": "fixed", "name": "특별인부", "qty": 0.2, "own": {"labor": 100000.0},
|
||||
"link": {"labor": "L9", "material": None, "machine": None}},
|
||||
{"kind": "machine", "key": "E1", "name": "굴착기", "qty": 0.29},
|
||||
{"kind": "material", "key": "M1", "name": "시멘트", "qty": 3},
|
||||
{"kind": "pct", "name": "공구손료", "part": "expense", "base": "bundle:labor", "qty": 2},
|
||||
{"kind": "pct", "name": "율 위 율", "part": "material", "base": "rows:3,4", "qty": 10},
|
||||
{"kind": "pct", "name": "장비 율", "part": "expense", "base": "bundle:machine", "qty": 1},
|
||||
],
|
||||
} # fmt: skip
|
||||
BOOKS = {
|
||||
"labor": [{"key": "L1", "price": 172068}],
|
||||
"material": [{"key": "M1", "price": 5000}],
|
||||
"machine": [{"key": "E1", "labor": 30000, "material": 5000, "expense": 70000}],
|
||||
}
|
||||
|
||||
|
||||
def _ts_labor(daega, sheets):
|
||||
base = IMPORTS.sub("", (ROOT / "B09_Estimation/B09_Estimation_Calc_Base.ts").read_text("utf-8"))
|
||||
code = IMPORTS.sub(
|
||||
"", (ROOT / "B09_Estimation/B09_Estimation_Calc_Labor.ts").read_text("utf-8")
|
||||
)
|
||||
body = (
|
||||
f"\nconst d = {json.dumps(daega, ensure_ascii=False)};"
|
||||
f"\nconst s = {json.dumps(sheets, ensure_ascii=False)};"
|
||||
"\nconsole.log(JSON.stringify(computeLabor(d, s, {}, {})));"
|
||||
)
|
||||
got = subprocess.run(
|
||||
["node", "--experimental-strip-types", "--no-warnings",
|
||||
"--input-type=module-typescript", "-e", base + code + body],
|
||||
capture_output=True, text=True, encoding="utf-8", timeout=60,
|
||||
) # fmt: skip
|
||||
assert got.returncode == 0, got.stderr
|
||||
return json.loads(got.stdout)
|
||||
|
||||
|
||||
def test_B09_bundle_rows_파이썬과_TS_같음_손셈():
|
||||
lines = [{**ln, "cid": f"{ln['kind']}:{ln['name']}#0"} for ln in BLOCK["lines"]]
|
||||
daega = {"blocks": [{**BLOCK, "lines": lines}], "owners": {}}
|
||||
py = json.loads(json.dumps(compute_labor(daega, BOOKS, {}), ensure_ascii=False))
|
||||
_same(py, _ts_labor(daega, BOOKS))
|
||||
got = {ln["name"]: ln for ln in py["daega_blocks"][0]["lines"]}
|
||||
people = 86034.0 + 20000.0 # 보통인부 + 노임 이음 고정 줄
|
||||
assert got["공구손료"]["amounts"]["expense"] == pytest.approx(people * 0.02, abs=0.1)
|
||||
tool = got["공구손료"]["amounts"]["expense"]
|
||||
assert got["율 위 율"]["amounts"]["material"] == pytest.approx((15000 + tool) * 0.1, abs=0.1)
|
||||
mach = got["굴착기"]["amount"]
|
||||
assert got["장비 율"]["amounts"]["expense"] == pytest.approx(mach * 0.01, abs=0.1)
|
||||
assert py["gaps"] == []
|
||||
|
||||
|
||||
# ── 굳힘 ────────────────────────────────────────────────────────────────────
|
||||
def test_바탕_없는_SK_율_줄은_한_줄로_안_펼침():
|
||||
whole = _whole()
|
||||
y = sd.clean(_doc([LABOR[0], _rate("인력")]), None, whole)
|
||||
with pytest.raises(sd.DocError, match="율 줄"):
|
||||
flat.sk_adds(y, Decimal(2), whole, {})
|
||||
plain = sd.clean(_doc([LABOR[0]]), None, whole)
|
||||
assert flat.sk_adds(plain, Decimal(2), whole, {})[0]["수량"] == 1 # 율 없으면 그대로
|
||||
|
||||
|
||||
def test_겹침_율_열쇠_새_차례():
|
||||
whole = _whole()
|
||||
under = sd.clean(
|
||||
{**_doc([LABOR[0], _rate(["add:#0"])]), "바탕": {"로직": SUB}, "단위": "주"}, None, whole
|
||||
)
|
||||
seen = sd.patched(whole.logic(SUB), under)
|
||||
view = dict(zip(seen["정체"], sd.line_ids(seen["줄"]))) # 아래 차례 → 위 문서 cid
|
||||
over = {"더한줄": [LABOR[1], _rate(["add:#0", view["add:#1"], view["add:#0"]], 3)]}
|
||||
out = flat.compose(under, over, whole)
|
||||
keys = [((a.get("고정") or {}).get("비율") or {}).get("기준") for a in out["더한줄"]]
|
||||
assert keys == [None, ["add:#0"], None, ["add:#2", "add:#1", "add:#0"]]
|
||||
|
||||
|
||||
# ── 화면 — 더한줄 지움 · 되살림 때 율 기준 열쇠 옮김(`logic/M02_Sangeun_Undo.ts`) ──────────
|
||||
def test_화면_더한줄_지움_되살림_율_열쇠():
|
||||
code = IMPORTS.sub(
|
||||
"", (ROOT / "M02_MasterTemplete/logic/M02_Sangeun_Undo.ts").read_text("utf-8")
|
||||
)
|
||||
doc = _doc([LABOR[0], LABOR[1], _rate(["add:#1", "add:#0", "labor:LB000038#0"])])
|
||||
body = (
|
||||
f"\nconst doc = {json.dumps(doc, ensure_ascii=False)};"
|
||||
"\nconst key = () => doc.더한줄.at(-1).고정.비율.기준;"
|
||||
"\nforgetAdd(doc, 0, ''); const a = key(); reviveAdd(doc, 0);"
|
||||
"\nconsole.log(JSON.stringify([a, key()]));"
|
||||
)
|
||||
got = subprocess.run(
|
||||
["node", "--experimental-strip-types", "--no-warnings",
|
||||
"--input-type=module-typescript", "-e", code + body],
|
||||
capture_output=True, text=True, encoding="utf-8", timeout=60,
|
||||
) # fmt: skip
|
||||
assert got.returncode == 0, got.stderr
|
||||
assert json.loads(got.stdout) == [
|
||||
["add:#0", "labor:LB000038#0"], # 지운 줄 열쇠 버림 · 뒤 줄 당김
|
||||
["add:#1", "labor:LB000038#0"], # 되살림 = 뒤 줄 밂
|
||||
]
|
||||
Reference in New Issue
Block a user