Files
Aislo/resources/tester/test_827_2_material_clues.py
T
eomsangdonandClaude Opus 5.5 520bb33909 feat(827-3): 불러오기 재료 걸음 3 — 고를 재료 판 · 그 파일 안 고름(별칭 아님) · SK 상세 같은 파일 물음
- 새 M02_San_MatPick: 종 열쇠(이름 · 규격 · 단위 · 단서) · 고름 자리 = 개인 층 실무자료/재료고름/<파일 지문>.json([끝내기] 뒤 남음 · 같은 파일 다시 올리면 그대로) · 판(블록마다 재료찾기 판정 → 종 · 첫 판 한 번 셈 → 산근 저장본 칸) · 만들 때 얹기 · 이미 만든 SK 얹기(가져온 그대로인 줄만 · 바꿈 · 따로 고른 줄 그대로 · 풀면 가져온 그대로)
- 만들기: from-hopyo 가 그 파일 고름째 만듦(저장 안 한 판 고름 = 만들 때 저장) · 원본 지문 · 고른 줄 = 이음 + 재료찾기.고름 · 문서 셈 우리 값(엑셀 값 기억 없음) · 별도계상 = 품목만(줄 이음 · 단가 0 그대로)
- 문서 머리 가져온.재료(판정 · 후보 · 단서 · 처음 연결) · 가져온.지문
- 길: GET/POST hopyo-materials/{HP}(판 · [저장] · 만든 것 적용은 물음 예일 때만) · GET/POST material-picks/{지문}(SK 상세 물음)
- 화면: 단가산출 목록 위 「고를 재료」 판(후보 고름 · 찾기 · 고름 풀기 · n 블록에 적용 · 이어진 재료 접힘 · [저장]) · 저장 안 한 고름 = 떠날 때 물음 · 줄 쌓기 재료 줄 판정 배지 · 풍선말(판정 · 단서 · 후보) · 이름 단추 · [이음 고르기] 뒤 「같은 파일 n 줄」 물음
- 별도계상 설명 = 「별도계상 · 품목: 이름 규격」(엔진 이은 품목은 자재표 이름) — test_827_2 기대 글 고침(품목 이음 → 품목 이름)
- 시험 test_827_3 셋(파일 고름 블록 둘 · 다른 파일 0 · 별칭 · 마스터 그대로 · 풀기 · 다시 올림 유지 · 물음 예일 때만 · 따로 바꾼 줄 보존 · 별도계상 품목 + 단가 0)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-13 12:51:04 +09:00

200 lines
9.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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"}