"""827-2 불러오기 재료 찾기 — 별도계상 품목 · 단서 넷 · 자동 규칙 A ~ F2 · 판정 네 갈래 · 별칭 안 만듦(요구 810 ~ 812). 판정 = `M02_San_MatFind`(`M02_San_Link.m01` 이 부름 · 줄 `자원["재료찾기"]`). 실무 원파일 시험은 파일 없으면 건너뜀. """ import sys from pathlib import Path import pytest ROOT = Path(__file__).resolve().parents[2] sys.path[:0] = [str(ROOT), str(ROOT / "resources" / "master_data" / "scripts"), str(ROOT / "resources" / "tester")] # fmt: skip from M02_MasterTemplete import M02_Elem_Alias as ea # noqa: E402 from M02_MasterTemplete import M02_Hopyo_MatSame as ms # noqa: E402 from M02_MasterTemplete import M02_San_Link as sl # noqa: E402 from M02_MasterTemplete import M02_San_MatFind as mf # noqa: E402 def _go(name, spec, unit, label="", code=""): src = {"이름": name, "규격": spec, "단위": unit, "갈래": "재료", "코드": code} key, mark = sl.m01("재료", src, label) return key, mark, src["재료찾기"] # ── 자동 규칙(걸음 2) — 맞는 보기 ────────────────────────────────────────── @pytest.mark.parametrize( "name,spec,unit,rule,key", [ ("유압식드릴스리브", "T38×190mm", "EA", "E", "MT005687"), ("중하중그레이팅", "395×995×52", "EA", "E", "MT007025"), ("공사안전PE드럼", "500×800", "EA", "E", "MT003311"), ("아세틸렌", "용접용", "kg", "C", "MT003163"), ("요소", "", "KG", "A", "MT006763"), ("보조강재(열연강판)", "T=9-12", "kg", "B", "MT030619"), ("미송(판재)", "", "M3", "F", "MT000366"), ("각재 (외송)", "", "M3", "F", "MT000339"), ("각재", "외송", "M3", "F2", "MT000339"), ], ) def test_rule_links(name, spec, unit, rule, key): got, _mark, found = _go(name, spec, unit) assert got == key and found["판정"] == mf.LINKED and found["방법"] == f"규칙 {rule}" # ── 틀리면 안 되는 보기 ─────────────────────────────────────────────────── def _keys_named(word): return {k for k, r in ea.book()["줄"]["MT"].items() if word in str(r.get("이름"))} def test_no_wrong_rule_links(): nawang = _keys_named("나왕") assert nawang for name, spec in ( ("각재(외송)", ""), ("각재 (외송)", ""), ("각재", "외송"), ("각재(외송)", "3.6×3.0×3.0"), ): assert _go(name, spec, "M3")[0] not in nawang me = ms._item("치즐", "0.7m3") assert mf._rule_b({"이름": "치즐", "규격": "0.7m3", "단위": "EA"}, me) in (None, "MT000011") assert _go("치즐", "0.7m3", "EA")[0] == "MT000011" # 이미 이은 줄 그대로 bit = _go("BUTTON BIT", "Ø38×76㎜", "EA") assert bit[0] is None and bit[2]["판정"] == mf.NONE rod = _go("PVC용접봉", "Ø3.2 (국산)", "KG") assert rod[0] is None and rod[2]["판정"] == mf.NONE for name in ("에폭시수지",): assert _go(name, "YD-128", "kg")[0] is None def test_doubted_three(): """조사 810-2 의심 셋 — 블레이드 · 드릴롯드는 풀고 · 참싸리(박피)는 사용자 수동연결 과녁(817-1 #33)대로.""" assert _go("블레이드", "d=320~400mm t=3.2", "EA")[0] != "MT005744" assert _go("유압식드릴롯드", "T38×R32x3660mm", "EA")[0] != "MT005695" assert _go("산림종자", "참싸리(박피)", "KG")[0] == "MT006664" assert _go("블레이드(커터)", 'd=320 400mm 14"xt3.2', "EA")[0] == "MT005749" # ── 판정 네 갈래 ────────────────────────────────────────────────────────── def test_four_verdicts(): assert _go("유압식드릴스리브", "T38×190mm", "EA")[2]["판정"] == mf.LINKED no_spec = _go("철물", "", "EA")[2] assert no_spec["판정"] == mf.NO_SPEC and no_spec["후보"][0]["키"] == "MT000511" many = _go("결속선(철선)", "#20 , 0.9M/M", "KG")[2] assert many["판정"] == mf.MANY and len(many["후보"]) > 1 and many["키"] is None for name, spec, unit in (("떼", "", "m2"), ("BUTTON BIT", "Ø38×76㎜", "EA")): assert _go(name, spec, unit)[2]["판정"] == mf.NONE linked = _go("결속선(철선)", "#20 , 0.9M/M", "KG")[2] assert set(linked) == {"판정", "방법", "키", "후보", "단서"} # ── 단서 넷(걸음 1) ─────────────────────────────────────────────────────── BOQ = [ # STC 내역서 자재 직접 줄(동부청영월 꼴) {"번": "1", "이름": "파형강관(2.7T)-영월", "규격": "Φ800-1RS", "단위": "M", "코드": "M01092"}, {"번": "2", "이름": "이형철근", "규격": "D13~D32", "단위": "Ton", "코드": "M00654"}, {"번": "3", "이름": "파형강관부설", "규격": "Φ800", "단위": "M", "코드": "D00071"}, ] def test_clue_label(): got, _mark, found = _go("철물", "", "EA", label="5) 철물(앵커용)") assert ( got == "MT000511" and found["방법"] == "단서" and found["단서"] == ["설명 글 — 철물(앵커용)"] ) def test_clue_head_and_boq_apart(): clues = mf.from_boq(BOQ) assert [c["코드"] for c in clues] == ["M01092", "M00654"] # 자재(M) 줄만 with mf.clues_on(clues, "파형강관부설 Φ800mm"): key, mark, found = _go("파형강관", "별도계상", "M") assert key is None and mark == f"{sl.NOTE}{sl.APART}" # 관급 · 일반 둘 = 사람 몫 · 이음 비움 assert found["판정"] == mf.NO_SPEC assert {c["키"] for c in found["후보"][:2]} == {"MT015181", "MT015282"} assert all(c.get("단서") == "단서" for c in found["후보"][:2]) assert found["단서"][0] == "산근 머리 — 파형강관부설 Φ800mm" with mf.clues_on(clues, "철근가공조립(현장) TYPE Ⅰ"): found = _go("이형철근", "별도계상", "Ton")[2] assert found["판정"] == mf.NO_SPEC and found["단서"] == [] # 범위 D13~D32 = 규격 하나 아님 def test_clue_book_sheet(): meta = "_x0007_`COD|{}_x0005_`EXI|0_x0005_`" rows = [ ["파형강관부설", "D=800mm", None, "m", *[None] * 8, "대가 17", None, "B01217"], [ "파형강관(800mm)", "별도계상", 1, "M", *[0] * 8, "별산자재 19 ", meta.format("M00222"), "M00222_1", ], # fmt: skip ["보통인부", None, 0.66, "인", *[0] * 8, "노무 1 ", meta.format("L00001"), "L00001_1"], ] got = mf.from_book(rows) assert got == [{"곳": "일위대가표", "코드": "M00222", "이름": "파형강관(800mm)", "규격": "별도계상", "머리": "파형강관부설 D=800mm"}] # fmt: skip with mf.clues_on(got, ""): found = _go("파형강관", "별도계상", "M", code="M00222")[2] assert found["판정"] == mf.NO_SPEC and all("800" in c["규격"] for c in found["후보"][:3]) assert any(x.startswith("일위대가표") for x in found["단서"]) def test_apart_linked_item_keeps_zero(): """별도계상 줄 = 품목이 하나로 정해지면 판정 키 · 줄 이음은 비움(셈 제외 · 단가 0 그대로).""" key, mark, found = _go("요소(별도계상)", "", "KG") assert key is None and found["키"] == "MT006763" and mark == f"{sl.NOTE}{sl.APART_ITEM}" why = sl.why( {"자원": {"갈래": "재료", "이름": "요소(별도계상)", "규격": "", "재료찾기": found}} ) assert why.startswith("별도계상 · 품목: 요") and why.endswith( "셈은 프로젝트에서" ) # 827-3 품목 이름까지 assert sl.tally({"호표": [{"이름": "요소", "자원": {"재료찾기": found}}]})["됨"] == 1 # ── 별칭 안 만듦 ────────────────────────────────────────────────────────── def test_no_alias_written(): before = {k: r.get("별칭") for k, r in ea.book()["줄"]["MT"].items()} for args in ( ("각재", "외송", "M3"), ("종자", "초본류", "KG"), ("철물", "", "EA"), ("떼", "", "m2"), ): _go(*args) assert {k: r.get("별칭") for k, r in ea.book()["줄"]["MT"].items()} == before # ── 실무 원파일(있을 때) — 단서가 원파일 · 엑셀에서 읽힘 ───────────────────── def test_real_files_carry_clues(): import test_797_1a_import_flags as practice import test_810_1_stc as c from M02_MasterTemplete import M02_Doc_FromNative as fn from M02_MasterTemplete import M02_San_Xlsx as sx stc, xl = c.FILES["동부청영월"], practice.FILES.get("울진대흥") if not stc.exists() or not xl or not xl.exists(): pytest.skip("실무 원파일 없음") saved = fn.read(stc.name, stc.read_bytes()) assert any(x["이름"].startswith("파형강관") for x in saved["단서"]) book = fn.pack(sx.read(xl.name, xl.read_bytes()), xl.name) assert any(x["곳"] == "일위대가표" and x.get("머리") for x in book["단서"]) out = fn.emit(saved, 71) # 파형강관부설 Φ800mm line = next( h for h in out["로직"]["호표"] if str((h.get("자원") or {}).get("규격")) == "별도계상" ) assert ( line["이음"] is None and not line.get("단가") and not any((line.get("값묶음") or {}).values()) ) assert {c["키"] for c in line["자원"]["재료찾기"]["후보"][:2]} == {"MT015181", "MT015282"}