From 53c23d2739afbfadd47cc888d14762897af0c371 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Thu, 24 Sep 2026 11:13:20 +0900 Subject: [PATCH] =?UTF-8?q?feat(m01):=20/text=20=EC=A4=84=EB=A7=88?= =?UTF-8?q?=EB=8B=A4=20=EC=9A=94=EC=86=8C=EC=A1=B0=EA=B0=81=20=E2=80=94=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EB=B6=80=EB=A5=B4=EA=B8=B0=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20+=20=EC=9D=B8=EC=9E=90=20=EA=B8=80=20(PLAN=205?= =?UTF-8?q?=EC=9E=A5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01V1MKKZKpUHTPKb513FneU8 --- resources/master_data/scripts/master_text.py | 41 ++++++++++++++++++++ resources/tester/test_m01_text.py | 19 +++++++++ 2 files changed, 60 insertions(+) diff --git a/resources/master_data/scripts/master_text.py b/resources/master_data/scripts/master_text.py index ed121e79..b0d21d40 100644 --- a/resources/master_data/scripts/master_text.py +++ b/resources/master_data/scripts/master_text.py @@ -468,6 +468,7 @@ def lines(files: dict, key: str, given: dict) -> dict: env["줄"] = {r["이름"]: r["금액"] for r in shown if r["이름"]} for item, line in zip(items, shown): 조각 = _formula_pieces(item["수량"], env, master, name_of, alias_by_name) + 요소 = _item_pieces(item, env, master) for part, money in _shares(line): split = part if len(line["비목"]) > 1 else None one = { @@ -476,6 +477,7 @@ def lines(files: dict, key: str, given: dict) -> dict: "이름글": _line_names(item, line, env, master, split, name_of), "금액": money, "조각": 조각, + "요소조각": 요소, } why = _why(item, line) made[part].append({**one, "까닭": why} if why else one) @@ -491,6 +493,7 @@ def lines(files: dict, key: str, given: dict) -> dict: else [item.get("비목")] ) 조각 = _formula_pieces(item["수량"], env, master, name_of, alias_by_name) + 요소 = _item_pieces(item, env, master) for part in parts: if part not in mf.COST_ITEMS: continue @@ -502,6 +505,7 @@ def lines(files: dict, key: str, given: dict) -> dict: "이름글": _line_names(item, {}, env, master, split, name_of), "금액": None, "조각": 조각, + "요소조각": 요소, } ) for extra in row.get("덧줄") or []: @@ -529,6 +533,42 @@ def lines(files: dict, key: str, given: dict) -> dict: return _paired({"ok": True, "groups": groups, "계": 계, "별칭": marks}) +def _arg_text(node, env: dict, master) -> str: + """로직 부르기 인자 값을 사람이 읽는 글로 — 요소 키는 이름 · 입력 자리는 지금 값.""" + try: + value = mf.evaluate(node, env, master, 0) + except _CALC_ERRORS: # 값 없는 입력 자리 — 이름만 + return str(node[1]) if node[0] in ("name", "str") else "" + if isinstance(value, str): + for ref in (value, f"EQ:{value}"): # 기계 입력은 원문번호(0602-0025) + try: + return mc.plain(master.get(ref).get("이름")) or value + except mf.FormulaError: + pass + return value + return fmt(value) + + +def _item_pieces(item: dict, env: dict, master) -> list[dict]: + """줄의 단가 자리(요소 · 로직 부르기) 조각 — PLAN 3-1 요소조각.""" + ref = item.get("요소") + if item.get("종류") != "로직": + name = str(item.get("이름") or "") + if isinstance(ref, dict): # 재료 고르기 조건 — 품명만 + return [{"kind": "요소", "글": name}] + piece = _elem_piece(master, ref) + return [{**piece, "글": name or piece["글"]}] + try: + node = mf.parse(ref) + key = mf.fill(node[1], env) + name = str(master.logic(key).get("이름") or key) + except _CALC_ERRORS: + return [{"kind": "로직", "글": str(item.get("이름") or "")}] + args = " · ".join(f"{k} {_arg_text(v, env, master)}" for k, v in node[2]) + out = [{"kind": "로직", "글": name, "참조": {"테이블": key[:2], "키": key}}] + return out + ([{"kind": "글", "글": args}] if args else []) + + def _paired(answer: dict) -> dict: """줄마다 짝 번호 — 두 칸(이름 꼴 · 값 꼴)이 같은 차례로 놓이게 1 부터.""" at = 0 @@ -536,6 +576,7 @@ def _paired(answer: dict) -> dict: for one in group["줄"]: at += 1 one["짝"] = at + one.setdefault("요소조각", []) return answer diff --git a/resources/tester/test_m01_text.py b/resources/tester/test_m01_text.py index a373a04c..573aa449 100644 --- a/resources/tester/test_m01_text.py +++ b/resources/tester/test_m01_text.py @@ -358,3 +358,22 @@ def test_이름글_번호_별칭은_별칭표로_되돌려진다() -> None: raw_of = {m["이름"]: m["원문"] for m in got["별칭"]} back = mt.mc._unmask(left, raw_of) assert back.count("찾기(") == 9 and "촌락지대가" not in back + + +def test_요소조각_로직_부르기는_이름과_인자_글_날식_없음() -> None: + """GC001093 트럭 줄 — 로직 조각(키 GC000268) + 인자 글(기계 이름 · 지역 지금 값).""" + files = store.cm.load(folder=REAL) + row = store.cm.master(REAL).logic("GC001093") + given = dict(store.cm.시험입력(row)) + for got in (mt.lines(files, "GC001093", given), mt.lines(files, "GC001093", {})): + lines = _all_lines(got) + assert all("요소조각" in one for one in lines) + truck = [one for one in lines if one["이름"].startswith("트럭")] + assert truck + for one in truck: + head, args = one["요소조각"] + assert head["kind"] == "로직" and head["참조"]["키"] == "GC000268" + assert args["kind"] == "글" and "덤프트럭" in args["글"] + assert "GC000268" not in args["글"] and "(" not in args["글"].split("덤프트럭")[0] + labor = lines[0]["요소조각"] + assert labor[0]["kind"] == "요소" and labor[0]["글"] == "배관공(수도)"