diff --git a/resources/master_data/scripts/master_text.py b/resources/master_data/scripts/master_text.py index 70d5a918..0d8f340e 100644 --- a/resources/master_data/scripts/master_text.py +++ b/resources/master_data/scripts/master_text.py @@ -21,6 +21,7 @@ from decimal import ROUND_HALF_UP, Decimal import master_copy as mc import master_formula as mf +import master_text_item as mti PLACES = Decimal("0.0001") # 화면 `formatNumber` 와 같음(소수 넷째 자리까지) PRICE_TAIL = {"인력": "노임", "기계": "손료"} # 단가 자리의 이름 꼬리(그 밖은 「단가」) @@ -545,9 +546,9 @@ def lines(files: dict, key: str, given: dict) -> dict: if result is not None: shown = result["줄"] env["줄"] = {r["이름"]: r["금액"] for r in shown if r["이름"]} - for item, line, label in zip(items, shown, labels): + for n, (item, line, label) in enumerate(zip(items, shown, labels)): 조각 = _formula_pieces(item["수량"], env, master, name_of, alias_by_name) - 요소 = _item_pieces(item, env, master) + 요소 = _item_pieces(item, env, master, line, n) for part, money in _shares(line): split = part if len(line["비목"]) > 1 else None one = { @@ -565,14 +566,14 @@ def lines(files: dict, key: str, given: dict) -> dict: 끝수 = _cut_note(row) 계 = result["계"] else: - for item, label in zip(items, labels): + for n, (item, label) in enumerate(zip(items, labels)): parts = ( list(mf.COST_ITEMS) if item.get("종류") == "로직" and "비목" not in item else [item.get("비목")] ) 조각 = _formula_pieces(item["수량"], env, master, name_of, alias_by_name) - 요소 = _item_pieces(item, env, master) + 요소 = _item_pieces(item, env, master, None, n) for part in parts: if part not in mf.COST_ITEMS: continue @@ -628,13 +629,16 @@ def _arg_text(node, env: dict, master) -> str: return fmt(value) -def _item_pieces(item: dict, env: dict, master) -> list[dict]: +def _item_pieces( + item: dict, env: dict, master, line: dict | None = None, index: int = 0 +) -> list[dict]: """줄의 단가 자리(요소 · 로직 부르기) 조각 — PLAN 3-1 요소조각.""" ref = item.get("요소") if item.get("종류") != "로직": name = str(item.get("이름") or "") - if isinstance(ref, dict): # 재료 고르기 조건 — 품명만 - return [{"kind": "요소", "글": name}] + if isinstance(ref, dict): # 재료 고르기 조건 — 잡힌 품목 이름 · 참조(고름 · 검색어 · 대표) + found = mti.piece(item, line, index, env, master) + return [found or {"kind": "요소", "글": name}] piece = _elem_piece(master, ref, env) return [{**piece, "글": name or piece["글"]}] try: diff --git a/resources/master_data/scripts/master_text_item.py b/resources/master_data/scripts/master_text_item.py new file mode 100644 index 00000000..c58bc165 --- /dev/null +++ b/resources/master_data/scripts/master_text_item.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +"""재료 줄 요소조각 — 잡힌 자재품목 이름 · 참조(PLAN 8-5). `master_text._item_pieces` 가 부름.""" + +from __future__ import annotations + +import re + +import master_formula as mf +import master_pick as mp + +_ERRORS = (mf.FormulaError, KeyError, TypeError) + + +def picked(item: dict, line: dict | None, index: int, env: dict, master) -> dict | None: + """재료 줄이 잡은 자재품목 줄 — 계산한 줄의 출처(`MT000018.거래가격`) 먼저 · + 빈 줄이면 고름 · 검색어 · 대표로 다시 찾음. 못 찾으면 None.""" + got = str((line or {}).get("출처") or "")[:8] + try: + if got[:2] == "MT": + return master.get(got) + cond = {k: mf.fill(v, env) if isinstance(v, str) else v for k, v in item["요소"].items()} + key = (mp.CHOSEN.get() or {}).get(str(index)) + if not key and cond.get("검색어"): + key = mp.pick(master, cond, env, item.get("단위")) + key = key or cond.get("대표") + return master.get(str(key)) if key and str(key)[:2] == "MT" else None + except _ERRORS: + return None + + +def piece(item: dict, line: dict | None, index: int, env: dict, master) -> dict | None: + """요소조각 — 글 = 품목 이름(지역 포함) · 참조 = 테이블 MT · 그 키. 못 찾으면 None.""" + row = picked(item, line, index, env, master) + if row is None: + return None + key = str(row["키"]) + name = re.sub(r"\s+", " ", str(row.get("이름") or "")).strip() + return {"kind": "요소", "글": name, "참조": {"테이블": key[:2], "키": key}} diff --git a/resources/tester/test_m01_text.py b/resources/tester/test_m01_text.py index fea9c4d6..a7472a7e 100644 --- a/resources/tester/test_m01_text.py +++ b/resources/tester/test_m01_text.py @@ -520,3 +520,27 @@ def test_표에_없는_입력_조합은_그_줄만_비우고_글은_그대로() assert len(blank) < len(lines) or got["계"] is not None calc = store.calc("GC001000", {**store.auto("GC001000")["값"], "시공구분": "기계시공"}) assert got["계"] == calc["sums"]["계"] + + +def _mat_piece(lines: list[dict], name: str) -> dict: + return next(x for x in lines if x["이름"] == name)["요소조각"][0] + + +def test_재료_줄_요소조각은_잡힌_품목_이름과_MT_참조() -> None: + piece = _mat_piece(_text_of("GF000159"), "결속선 0.9㎜") + assert piece["글"].startswith("어닐링철선(결속선)") and piece["글"].endswith("서울") + assert piece["참조"] == {"테이블": "MT", "키": "MT000209"} + auto = {k: v for k, v in store.auto("GF000159")["값"].items() if v is not None} + assert store.text("GF000159", auto)["계"] == store.calc("GF000159", auto)["sums"]["계"] + + +def test_고름으로_바꾼_재료_줄_요소조각은_고른_품목() -> None: + import check_master as cm + import master_pick as mp + + auto = {k: v for k, v in store.auto("GF000159")["값"].items() if v is not None} + items = cm.master().logic("GF000159")["호표"] + at = next(i for i, h in enumerate(items) if h.get("이름") == "결속선 0.9㎜") + with mp.choosing({at: "MT000174"}): + got = _all_lines(store.text("GF000159", auto)) + assert _mat_piece(got, "결속선 0.9㎜")["참조"]["키"] == "MT000174"