- M02_San_Xlsx_Ilwi(새) = 일위대가표 읽기 한 곳 — N 열 메타(COD · IPR · PRI · PRO · EQC · KWN · DVD) · O 열 차수 꼬리 기준 · 줄 꼴 = 828-1 M02_San_Ilwi 꼴 + 엑셀 칸(행 · 차수 · 비고 · 전체 · 율.코드 · 메타) · 숨은 칸 없음 = 옛 코드 찾기 · 단산 길 일위 빈 목록 + 표지 - 5 건 호표 136 · 줄 706 · STC 구성 줄과 코드 · 갈래 · 수량 · 율 · 밑줄 같음(다른 5 줄 = STC 별도계상 규칙이 비고 별산 표시를 놓침) - 호표 길(M02_Hopyo_Stmate._hopyos) · 단산 길(M02_San_Xlsx_Stmate.read 일위) · 단서(M02_San_MatFind.from_book) 모두 공용 읽기 · 단서 · 산근 · 단가 전후 같음 - M02_Hopyo_Fixed._ratio = 숨은 칸 율 밑 · 대상 비목 먼저(글 짐작 대신) — 호표 길 전후 바뀜 = 영덕 제11호표 산악할증 0% 한 줄(금액만 → 비율 노무비 · 금액 0 그대로) · 단산 항목 1564 판정 그대로 - 재료 판(census) = 일위 구성 줄 자재도 셈(M02_San_MatFind.ilwi_found · 종 칸 일위 = 호표 코드 · 화면 「일위대가 N 호표」) - 시험: 새 test_828_2_xlsx_ilwi_meta · test_827_2 단서 시트 보기에 「제N호표」 줄 넣음(실제 시트는 늘 있음 · 공용 읽기가 호표 머리부터 셈) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
201 lines
9.8 KiB
Python
201 lines
9.8 KiB
Python
"""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 = [
|
||
[" 제 17 호표", *[None] * 12, "_x0005_`BDCOD|B01217_x0007_`POSS|3_x0007_`POSE|4_x0007_`"],
|
||
["파형강관부설", "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"}
|