diff --git a/resources/master_data/ref/_검증_텍스트_수식.md b/resources/master_data/ref/_검증_텍스트_수식.md index 9e1e7427..e0992513 100644 --- a/resources/master_data/ref/_검증_텍스트_수식.md +++ b/resources/master_data/ref/_검증_텍스트_수식.md @@ -47,6 +47,10 @@ - `GC001091`·`GC001096` 진공흡입 준설차 13톤 · 물탱크(살수차) 5,500ℓ · `GC001092` 트럭탑재형 크레인 5톤 · `GC001099` 진공흡입 준설차 13톤 - `GF000137` 트럭 10.5ton · `GF000241` 굴착기(무한궤도) 0.6㎥ +### 고친 뒤 (2026-09-21) + +`master_text.py` 에 몫 나누기를 `_shares()` 한 자리로 모아, 몫이 전부 0 이면 첫 비목에 0 원 한 줄을 남기고 까닭(「수량 0 — 이 조건에서는 안 쓰는 줄」)을 붙임 — 같은 대조를 다시 돌려 **③ 어긋남 83줄 → 0** · 나머지(①②④·터짐)도 그대로 0(1,291개 전부). + ## 딸린 사실 - 비목이 나뉜 하위 로직 줄은 비목마다 한 줄씩 같은 이름으로 나옴(계약대로 · 어긋남 아님). diff --git a/resources/master_data/scripts/master_text.py b/resources/master_data/scripts/master_text.py index 6b6c33d9..17cc4ddf 100644 --- a/resources/master_data/scripts/master_text.py +++ b/resources/master_data/scripts/master_text.py @@ -86,10 +86,10 @@ def _env_of(master, row: dict, given: dict, depth: int = 0) -> dict: def _why(item: dict, line: dict) -> str: """단가를 못 구했거나 0 인 줄의 까닭 한 마디.""" - if line["단가"] not in (0, None): - return "" if line["수량"] == 0: return "수량 0 — 이 조건에서는 안 쓰는 줄" + if line["단가"] not in (0, None): + return "" where = item.get("요소") where = "재료 고르기 조건" if isinstance(where, dict) else f"「{where}」" return f"단가 0 — {where} 값이 아직 없음" @@ -101,6 +101,15 @@ def _label(item: dict, line: dict) -> str: return f"{name}({spec})" if spec else name +def _shares(line: dict) -> list[tuple[str, Decimal]]: + """글로 적을 (비목, 몫) — 비목이 나뉜 로직 줄은 몫이 없는 비목을 건너뜀. + 몫이 전부 0 이면 호표 줄이 사라지지 않게 첫 비목에 0 원 한 줄을 남김.""" + parts = list(line["비목"].items()) + if len(parts) <= 1: + return parts + return [(part, money) for part, money in parts if money != 0] or parts[:1] + + def _line_text(item: dict, line: dict, env: dict, master, part: str | None) -> str: """「단가 * 수량식 = 금액」 — 비목이 나뉜 로직 줄은 그 비목 몫만.""" qty = render(mf.parse(item["수량"]), env, master) @@ -134,9 +143,7 @@ def lines(files: dict, key: str, given: dict) -> dict: env["줄"] = {r["이름"]: r["금액"] for r in shown if r["이름"]} items = row.get("호표") or [] for item, line in zip(items, shown): - for part, money in line["비목"].items(): - if money == 0 and len(line["비목"]) > 1: - continue # 비목이 나뉜 로직 줄 — 몫이 없는 비목엔 안 적음 + for part, money in _shares(line): split = part if len(line["비목"]) > 1 else None one = { "이름": _label(item, line), diff --git a/resources/tester/test_m01_text.py b/resources/tester/test_m01_text.py index f1839073..eed49ada 100644 --- a/resources/tester/test_m01_text.py +++ b/resources/tester/test_m01_text.py @@ -25,6 +25,7 @@ REAL = store.FOLDER STACK = "GF000219" # 13-4-1 메쌓기(인력) — 증가율이 걸린 줄 FORM = "GF000160" # 12-4 합판거푸집 — 재료 여럿 · 수량 0 줄 PUMP = "GF000182" # 12-17-1 펌프카 타설 — 하위 로직이 세 비목에 걸리고 덧줄이 있음 +IDLE = "GC000999" # 일반전정 — 세 비목에 걸린 장비 줄이 수량 0 이라 몫이 모두 0 @pytest.fixture @@ -95,6 +96,16 @@ def test_펌프카는_하위로직이_비목마다_한_줄씩이고_덧줄이_ assert extra["글"].endswith(f"= {mt.fmt(Decimal(str(extra['금액'])))}") and "%" in extra["글"] +def test_몫이_모두_0인_장비_줄도_글에_남는다(client: TestClient) -> None: + got, calc = _both(client, IDLE) + idle = [one for one in _all_lines(got) if one["이름"] == "고소작업차"] + assert len(idle) == 1 # 비목 셋 몫이 다 0 이라도 줄이 사라지지 않음 + assert Decimal(str(idle[0]["금액"])) == 0 and "수량 0" in idle[0]["까닭"] + assert idle[0]["글"].endswith(" * 0 = 0") + for group in got["groups"]: + assert Decimal(str(group["소계"])) == Decimal(str(calc["sums"][group["비목"]])) + + def test_멈추는_로직은_까닭_한_줄(client: TestClient) -> None: got = client.post("/api/m01/text", json={"key": STACK, "inputs": {}}).json() assert got["ok"] is False and "입력" in got["reason"]