"""㉰ 고정형 장 사유 · ㉱ 규격 다름 (2026-09-14 브레인 판정). ㉰ 고정형을 가져온 장에 **전개 사유(뒷길이 표준·돌 종류…)가 그대로 남아** 고정형 줄과 안 맞음 = 거짓 사유 → 항목 사유(원문 시점 수량·가산 행)를 띄우고 전개 사유는 **남긴 전개 줄(토공·버림·기초잡석·채집석)에 걸린 것만** 둠. ㉱ H=2.0 호표를 H=2.5 벽에 쓰면 벽 수량은 박힌 값(H2.0) · 토공·사토 공제는 장 제원(H2.5) — 밑수가 갈림 → 미확정과 같은 급으로 보임(빨간 테두리·「규격 다름」 배지) · 금액은 섬 · 막지 않음. """ from __future__ import annotations from pathlib import Path from B05_Profile.B05_Profile_Structures_Schema import StructureInstance from B08_Quantity.B08_Quantity_Engine_StmateRecipe import recipe_item from B08_Quantity.B08_Quantity_Engine_StructureSheet import build_standard_sheets from B08_Quantity.B08_Quantity_Engine_StructureTemplate import apply_templates from B08_Quantity.B08_Quantity_Engine_UnitQuantity import build_table ROOT = Path(__file__).resolve().parents[2] HOPYO = { "no": 6, "source_code": "B00010", "name": "기슭막이(깬잡석,찰쌓기 L3=45m)", "spec": "H=2.0m, 채집", "unit": "M", "basis": [], "contract_rows": 0, "rows": [ { "name": "깬잡석찰쌓기", "spec": "L3=45", "amount": 2.09, "unit": "M2", "remark": "", "source_code": "", }, { "name": "공구손료", "spec": "노무비의 %", "amount": 2, "unit": "%", "remark": "", "source_code": "", }, ], } NAMES = {"masonry_wet": "돌쌓기(찰)"} def _sheet(height: float) -> dict: wall = StructureInstance.model_validate( { "structure_id": "a", "type_id": "masonry_wet", "placement": "interval", "start_m": 0.0, "end_m": 10.0, "options": {"height_m": height, "foundation": "기초유"}, } ).model_dump() templates = { "masonry_wet": { **recipe_item(HOPYO, type_id="masonry_wet", file_name="a", project="p"), "code": "AX-ST-0000abcd", } } table = build_table([wall], NAMES, {}, {}, None, structure_templates=templates) payload = build_standard_sheets(table, {}) apply_templates(payload, {}, templates) return payload["sheets"][0] def test_고정형_장은_항목_사유를_띄우고_남의_사유를_걷는다() -> None: notes = " / ".join(_sheet(2.0)["notes"]) assert "원문 시점 수량" in notes and "가산 행 1줄" in notes assert "돌 종류를 안 골라" not in notes and "뒷채움 폭" not in notes def test_규격이_다르면_미확정_급으로_밑수_갈림을_알린다() -> None: sheet = _sheet(2.5) mismatch = sheet["spec_mismatch"] assert (mismatch["item_height_m"], mismatch["sheet_height_m"]) == (2.0, 2.5) assert "규격 다름" in sheet["notes"][0] assert "토공·사토 공제는 장 제원(H=2.5m)" in sheet["notes"][0] assert any(row["amount"] for row in sheet["rows"]) # 금액·수량은 막지 않음 def test_규격이_같으면_아무것도_안_뜬다() -> None: sheet = _sheet(2.0) assert not sheet.get("spec_mismatch") assert not any("규격 다름" in note for note in sheet["notes"]) def test_화면이_규격_다름을_빨간_배지로() -> None: ui = (ROOT / "B08_Quantity" / "B08_Quantity_UI_StructureSheet.ts").read_text(encoding="utf-8") assert "spec_mismatch" in ui and "규격 다름" in ui and "b08-unit__badge" in ui