From 560df4ac83cffec970c122b4bb51fe073e8424c0 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Mon, 14 Sep 2026 12:22:35 +0900 Subject: [PATCH] =?UTF-8?q?fix(b08):=20=EA=B4=80=EC=B8=A1=20=EC=9B=90?= =?UTF-8?q?=EB=8B=A8=EC=9C=84=20=EC=A4=84=EB=8F=84=20=EA=B0=88=20=EA=B3=B3?= =?UTF-8?q?=20=EA=B8=B0=EB=B3=B8=EA=B0=92=20=EA=B8=88=EC=A7=80=20=E2=80=94?= =?UTF-8?q?=20destination=20=EC=97=86=EB=8A=94=20=EC=A4=84=EC=9D=80=20?= =?UTF-8?q?=EB=A7=89=EA=B3=A0=20=EC=82=AC=EC=9C=A0(=EC=A2=85=EC=A0=84=20or?= =?UTF-8?q?=20material=20=EB=A1=9C=20=EC=A1=B0=EC=9A=A9=ED=9E=88=20?= =?UTF-8?q?=EC=9E=90=EC=9E=AC=ED=96=89)=20=C2=B7=20=ED=91=9C=20=EC=A0=84?= =?UTF-8?q?=20=EC=A4=84=20=EA=B0=88=20=EA=B3=B3=20=EC=8B=9C=ED=97=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01BW6Jdsh18WPtYUR6THZJqn --- .../B08_Quantity_Engine_ObservedUnit.py | 12 +++++-- .../tester/test_b08_destination_no_default.py | 33 +++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/B08_Quantity/B08_Quantity_Engine_ObservedUnit.py b/B08_Quantity/B08_Quantity_Engine_ObservedUnit.py index a29c376f..aab337de 100644 --- a/B08_Quantity/B08_Quantity_Engine_ObservedUnit.py +++ b/B08_Quantity/B08_Quantity_Engine_ObservedUnit.py @@ -283,8 +283,16 @@ def expand_observed( source_key = str(found.get("source") or "") options = structure.get("options") or {} components: list[dict[str, Any]] = [] + blocked: list[str] = [] for item in found.get("components") or []: note = str(item.get("basis_note") or "") + if not item.get("destination"): + # ⛔ 갈 곳 기본값 금지(명세 13장) — 종전 `or "material"` 은 줄이 늘 때 조용히 자재로 샘. + blocked.append( + f"「{item['name']}」 관측 원단위 줄에 갈 곳이 없어 물량을 어느 축에도 안 보냄" + " — 표(`structure_unit_observed`)에 적을 것(명세 13장 갈 곳 기본값 금지)" + ) + continue weep = ( weep_component(found, options, scale, scale_note) if item["name"] == "물구멍관" @@ -298,7 +306,7 @@ def expand_observed( "name": item["name"], "unit": item["unit"], "amount": float(item["amount"]) * scale, - "destination": item.get("destination") or "material", + "destination": item["destination"], # 근거를 값 옆에 붙인다 — 관측값임을 화면·인계에서 바로 알아야 한다. "basis": f"관측 원단위 {item['amount']:g}/{found.get('unit')} × {scale_note}" + (f" ({note})" if note else ""), @@ -308,7 +316,7 @@ def expand_observed( "spec": str(item.get("spec") or ""), } ) - notes = [f"관측 원단위 적용 — {found.get('source_note') or source_key}"] + notes = [f"관측 원단위 적용 — {found.get('source_note') or source_key}", *blocked] # 줄에 붙은 사유(원문끼리 다름 등) — 구조물도 그림도 같은 글자를 씀. notes.extend(str(note) for note in found.get("notes") or []) earthwork_note = earthwork_missing_note(found) diff --git a/resources/tester/test_b08_destination_no_default.py b/resources/tester/test_b08_destination_no_default.py index e604b41a..67c4cd1c 100644 --- a/resources/tester/test_b08_destination_no_default.py +++ b/resources/tester/test_b08_destination_no_default.py @@ -53,3 +53,36 @@ def test_드러난_넷이_제자리로_감() -> None: sod, sod_notes = soil_guard({"form": "떼"}) assert {c.name: c.destination for c in sod}["떼"] == "material" assert not [note for note in sod_notes if "갈 곳이 표" in note] + + +def test_관측_원단위_줄도_갈_곳이_없으면_막히고_사유() -> None: + """관측 원단위 로더의 `destination or "material"` 도 같은 병 — 줄이 늘 때 바로 드러나게.""" + from B08_Quantity.B08_Quantity_Engine_ObservedUnit import ObservedUnitTable, expand_observed + + table = ObservedUnitTable( + entries=[ + { + "type_id": "시험구조물", + "unit": "개소", + "components": [ + {"name": "콘크리트", "unit": "㎥", "amount": 1.0, "destination": "unit_price"}, + {"name": "새자재", "unit": "㎥", "amount": 2.0}, + ], + } + ] + ) + components, notes = expand_observed("시험구조물", {}, {"options": {}}, table) + assert [c["name"] for c in components] == ["콘크리트"] + assert any("새자재" in note and "기본값 금지" in note for note in notes) + + +def test_관측_원단위_표의_전_줄에_갈_곳이_있음() -> None: + from B08_Quantity.B08_Quantity_Engine_ObservedUnit import load_observed_table + + missing = [ + (entry.get("type_id"), item.get("name")) + for entry in load_observed_table().entries + for item in entry.get("components") or [] + if not item.get("destination") + ] + assert missing == []