"""815-1 지킴 — 불러오기 이음(방향 장부 `_방향_불러오기.md` K 장 · 요구 753 ~ 765). K1 EST 원파일 자재 = 우리 자재 줄 `출처` 칸 EST 코드로 바로(글 대조 없음) · 코드 없음 = 이름 길 K2 M01 요소 줄 「별칭」 칸 풀이(`master_alias` — 콤마 · 전각 콤마 · 큰따옴표 조각 · 규격) → 기계 · 재료 · 인력 · 유가 이음(시험 안 임시 별칭 — 마스터 자료 안 고침) K3 표기 정규화(M/M = mm · 곱셈표 · 지름 · 괄호 · 빈칸 · 전각 · 지역 꼬리) · 정확 일치만 K5 견적 줄 = 우리 자재에 이어지면 우리 값(재료 줄) · 아니면 그대로 + 「견적」 표시 · 못 이음 수에서 뺌 금액 = 엑셀 단가 그대로 셈이 원파일 저장 금액과 같음(견적 · 코드 이음 블록). 원파일 없으면 그 시험만 skip(다른 PC). """ from __future__ import annotations import sys from contextlib import contextmanager from pathlib import Path HERE = Path(__file__).resolve().parent ROOT = HERE.parents[1] for p in (ROOT, HERE): if str(p) not in sys.path: sys.path.insert(0, str(p)) import test_810_2_native_doc as t # noqa: E402 from M02_MasterTemplete import M02_Doc_FromNative as fn # noqa: E402 from M02_MasterTemplete import M02_Elem_Alias as ea # noqa: E402 from M02_MasterTemplete import M02_Hopyo_Row as hrow # noqa: E402 from M02_MasterTemplete import M02_Hopyo_Units as units # noqa: E402 from M02_MasterTemplete import M02_Machine_Find as mf # noqa: E402 from M02_MasterTemplete import M02_San_Link as link # noqa: E402 from M02_MasterTemplete import M02_Sangeun_Imported as imp # noqa: E402 am = ea.am GONE = object() @contextmanager def aliases(table: str, pairs: dict[str, str]): """M01 줄 `별칭` 칸을 시험 동안만 바꿈(읽은 판 안 · 파일 안 씀) — 끝나면 되돌림.""" rows = ea.book()["줄"][table] old = {k: rows[k].get("별칭", GONE) for k in pairs} def clear() -> None: ea._book.cache_clear() hrow._m01_picks.cache_clear() for k, v in pairs.items(): rows[k]["별칭"] = v clear() try: yield finally: for k, v in old.items(): if v is GONE: rows[k].pop("별칭", None) else: rows[k]["별칭"] = v clear() def no_aliases(table: str): """그 표 별칭 칸을 시험 동안 모두 비움 — 진짜 마스터에 별칭이 더 들어가도 시험이 안 흔들림.""" return aliases(table, {k: "" for k in ea.book()["줄"][table]}) # ── 별칭 풀이 ───────────────────────────────────────────────────────────── def test_alias_names_rule() -> None: assert am.names("") == [] and am.names(None) == [] assert am.names(" 철선 , 결속선,보통철선 ,, 철선") == ["철선", "결속선", "보통철선"] assert am.names('"결속선(철선) #20, 0.9M/M", 철선') == ["결속선(철선) #20, 0.9M/M", "철선"] assert am.names("“크레인(트럭) 15Ton, 할증”") == ["크레인(트럭) 15Ton, 할증"] idx = am.index([{"키": "A", "별칭": "철선, 결속선"}, {"키": "B", "별칭": "결속선"}]) assert idx == {"철선": ["A"], "결속선": ["A", "B"]} def test_norm_samples() -> None: same = [ ("0.9M/M", "0.9mm", "0.9m/m", "0.9㎜", "0.9 MM"), ("600×1200", "600x1200", "600X1200", "600*1200", "600 × 1200"), ("Φ100", "φ100", "ø100", "D=100", "d = 100"), ("유로폼(패널)", "유로폼 패널", "유로폼[패널]", "유로폼패널"), ("#20", "#20", "# 20"), ] for group in same: assert len({am.norm(x) for x in group}) == 1, group assert am.norm("결속선(철선)") != am.norm("철선(결속선)") # 낱말 순서 = 별칭 몫 # ── K1 EST 코드 ──────────────────────────────────────────────────────────── def test_est_code_first() -> None: assert ea.est_code("M0005250069") == ["MT000011"] # 치 즐(0.7㎥) src = {"꼴": "EST", "종류": "자재", "갈래": "재료", "코드": "M0005250069", "이름": "엉뚱한 글", "규격": "", "단위": "EA"} # fmt: skip assert link.m01("재료", src) == ("MT000011", None) # 글 대조 없이 코드 gone = {**src, "코드": "M0000000123", "이름": "치즐(0.7m3)"} # 공사가 넣은 코드 = 이름 길 assert link.m01("재료", gone) == ("MT000011", None) assert ea.est_code("자재품목 M0005250069") == [] and ea.est_code(None) == [] # 공사 직접 번호 = 파일마다 다른 품목(거창 치즐 ↔ 우리 표 Air Hammer) → 코드로 안 이음 · 이름 길 assert ea.est_code("M0000000001") == [] local = {**src, "코드": "M0000000001", "이름": "치즐(0.7㎥)"} assert link.m01("재료", local) == ("MT000011", None) # ── K2 별칭 → 요소 ─────────────────────────────────────────────────────────── def test_alias_machine() -> None: with no_aliases("EQ"): assert mf.find("진공흡입기", "5kW")["판정"] == mf.NONE assert mf.find("크레인(트럭)", "15Ton")["판정"] != mf.ALIAS # 별칭 전 with ( no_aliases("EQ"), aliases("EQ", {"EQ000129": '"크레인(트럭) 15Ton"', "EQ000193": "아스팔트페이버(피니셔)"}), ): got = mf.find("크레인(트럭)", "15Ton") # 이름 + 규격 통째 별칭 = 그 EQ assert (got["판정"], got["키"]) == (mf.ALIAS, "EQ000129") assert mf.find("크레인(트럭)", "15Ton(할증125%)")["키"] == "EQ000129" # 할증 글 안 봄 got = mf.find("아스팔트페이버(피니셔)", "3.0m") # 이름 별칭 = 그 기종 → 규격으로 하나 assert (got["판정"], got["후보"][0]["이름"], got["후보"][0]["규격"]) == (mf.ALIAS, "아스팔트 피니셔", "3m") # fmt: skip with no_aliases("EQ"), aliases("EQ", {"EQ000129": "크레인 갑", "EQ000130": "크레인 갑"}): assert mf.find("크레인 갑")["판정"] == mf.MANY # 같은 별칭 둘 = 후보 여럿 def test_alias_material() -> None: line = {"이름": "결속선(철선)", "규격": "#20, 0.9M/M", "단위": "kg"} assert link.m01("재료", dict(line))[0] is None # 별칭 전 = 못 이음(낱말 순서 다름) with aliases("MT", {"MT000174": "결속선(철선)", "MT000175": "결속선(철선)"}): assert link.m01("재료", dict(line)) == ("MT000174", None) # 같은 품목 지역 줄 = 서울 assert hrow.m01_picks("재료", line)[0]["키"] == "MT000174" assert units.Units()._M({"묶음": "M", **line})["단위"] == {"자재": "MT000174"} assert link.m01("재료", {**line, "규격": "#8"})[0] is None # 규격 다름 = 안 이음 with aliases("MT", {"MT000194": '"결속선(철선) #20, 0.9M/M(200m/kg)"'}): got = link.m01("재료", {**line, "규격": "#20, 0.9M/M(200m/kg)", "단위": "M"}) assert got == ("MT000194", None) # 이름 + 규격 통째 별칭 def test_alias_labor_oil() -> None: with aliases("LB", {"LB000003": "산림특인, 특인"}): assert [x["키"] for x in hrow.m01_picks("인력", {"이름": "특인"})] == ["LB000003"] assert link.m01("인력", {"이름": "산림특인", "규격": "", "단위": "인"}) == ( "LB000003", None, ) assert units.Units()._L({"이름": "특인"})["단위"] == {"노임": "LB000003"} with aliases("MO", {"MO000002": "디젤유"}): assert link.m01("재료", {"이름": "디젤유", "규격": "", "단위": "L"}) == ("MO000002", None) # ── K3 표기 맞추기 ────────────────────────────────────────────────────────── def test_exact_norm() -> None: assert link.m01("재료", {"이름": "치즐(0.7m3)", "규격": "", "단위": "EA"}) == ("MT000011", None) got = link.m01("재료", {"이름": "보통철선(결속선)", "규격": "#20, 0.9M/M", "단위": "kg"}) assert got == ("MT000174", None) got = link.m01("재료", {"이름": "유로폼 패널", "규격": "600X1200mm", "단위": "매"}) assert ( got[0] is None or got[0] == "MT000467" ) # 규격 단위 꼬리(㎜) 다름 = 정확 일치 아님 · 지어 맞추기 없음 assert ( link.m01("재료", {"이름": "보통철선(결속선)", "규격": "#20, 1.0mm", "단위": "kg"})[0] is None ) # ── K5 견적 ─────────────────────────────────────────────────────────────── def test_quote_line() -> None: ours = {"꼴": "STmate", "갈래": "고정", "코드": "Q1", "이름": "치즐(0.6m3)", "규격": "", "단위": "EA", "비목": "M", "단가": {"M": 9}} # fmt: skip assert link.m01("고정", ours) == ( "MT000010", None, ) # 우리 값 있는 자재 = 재료 줄(우리 값 · 엑셀 = 검산) assert ours["갈래"] == "재료" and ours["견적"] is True empty = { **ours, "갈래": "고정", "이름": "치즐(0.7m3)", } # 우리 줄 값 칸 빔(MT000011) = 견적 값 그대로 assert link.m01("고정", empty)[0] is None and empty["갈래"] == "고정" keep = {"꼴": "EST", "갈래": "고정", "종류": "견적", "코드": "W0000001", "이름": "도선료", "규격": "8.0ton", "비목": "S"} # fmt: skip key, mark = link.m01("고정", keep) assert key is None and mark == f"{link.NOTE}{link.QUOTE}" and keep["갈래"] == "고정" assert link.why({"종류": "로직", "자원": keep}) == link.QUOTE def test_quote_money_and_tag() -> None: saved = t.native("EST", "거창") no = next(n for n, b in fn.numbers(saved).items() if b.get("이름") == "국가지점번호판") out = fn.emit(saved, no) quotes = [h for h in out["로직"]["호표"] if (h.get("자원") or {}).get("견적")] assert len(quotes) == 2 and all(not h.get("이음") for h in quotes) assert sum(str(m).endswith(link.QUOTE) for m in out["원본"]["표지"]) == 2 _sk, doc, got = t.doc_and_money(out) want = fn.numbers(saved)[no]["저장"] assert all(abs(float(got[k]) - float(want.get(c) or 0)) < 0.5 for c, k in t.COST) free = [x for x in imp.unlinked(_sk) if x.get("견적")] assert len(free) == 2 and all(x["까닭"] == link.QUOTE for x in free) notes = [v.get("설명") for v in doc["변수"] if v.get("설명") == link.QUOTE] assert len(notes) == 2 def test_quote_kept_money_same() -> None: saved = t.native("STC", "소광22") out = fn.emit( saved, 96 ) # 구조물헐기 — 치즐(0.7m3) 견적 · 우리 치즐 0.7 값 칸 빔 = 견적 값 그대로 + 표지 hit = [h for h in out["로직"]["호표"] if (h.get("자원") or {}).get("견적")] assert [(h["종류"], h["이음"]) for h in hit] == [("로직", None)] assert any(str(m).endswith(link.QUOTE) for m in out["원본"]["표지"]) _sk, _doc, got = t.doc_and_money(out) want = fn.numbers(saved)[96]["저장"] assert all(abs(float(got[k]) - float(want.get(c) or 0)) < 0.5 for c, k in t.COST) def test_est_local_code_block_money() -> None: """거창 = 자재가 모두 공사 직접 번호(M0000000…) → 이름 길 · 치즐(0.7㎥) = 우리 치즐 0.7(Air Hammer 아님) · 금액 = 원파일 저장 그대로(전 블록).""" saved = t.native("EST", "거창") chisel = set() for no in fn.numbers(saved): out = fn.emit(saved, no) for h in out["로직"]["호표"]: r = h.get("자원") or {} if r.get("종류") == "자재": assert str(r.get("코드")).startswith(ea.LOCAL) or r.get("코드") == "M0004113991" if r.get("코드") == "M0000000001": chisel.add(h["이음"]) _sk, _doc, got = t.doc_and_money(out) want = fn.numbers(saved)[no]["저장"] assert all(abs(float(got[k]) - float(want.get(c) or 0)) < 0.5 for c, k in t.COST), no assert chisel == {"MT000011"}