# ruff: noqa: F811 — 임시 층 fixture(world)를 test_m02_labor_ref 에서 받아 씀 """814-1 지킴 — 불러오기 결정 열 가지(방향 장부 `_방향_불러오기.md` E 표 · 요구 742 ~ 750). ① 파일 하나(같은 공사 붙이기 걷음) ② 확장자 파일 = 단가산출표 판(산출근거 머리) · 일위대가 없음 · ④ 기계 세 줄 = 한 줄(STC · EST · 엑셀) · 조합 다음 줄 기계마다 한 항목 · ⑤ 계산식 기계 목록(빠진 기계 더함 · 기본값 그대로) · 조합 = 우리 조합 식 · ⑦ 못 이은 줄 까닭 ⑧ 유가 = 전국평균 + 지역 옵션 · 전력 = 일반용(갑)Ⅰ 저압 + 계약종별 옵션 · ⑨ 재료 지역 꼬리 뗀 정확 일치 ⑩ 가벼움(조합 첫 줄 규격 · 소계 검산 · 기계 찾기 판정 · 409 · 확정 글 · 전수 도우미). ③ ⑥ · 「없음」 띠 · 전수 도우미 = 연결 찾기와 함께 걷음(815-2). 원파일 · 실무 엑셀 없으면 그 시험만 skip(다른 PC). """ from __future__ import annotations import importlib.util import json import sys from pathlib import Path import pytest 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_797_1a_import_flags as practice # noqa: E402 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_Doc_Import as dim # noqa: E402 from M02_MasterTemplete import M02_Doc_NativeLayout as lay # noqa: E402 from M02_MasterTemplete import M02_Hopyo_Store as hs # noqa: E402 from M02_MasterTemplete import M02_Machine_Hourly as mh # noqa: E402 from M02_MasterTemplete import M02_MasterTemplete_Store as store # noqa: E402 from M02_MasterTemplete import M02_San_Emit as em # noqa: E402 from M02_MasterTemplete import M02_San_Link as link # noqa: E402 from M02_MasterTemplete import M02_Sangeun_FromHopyo as fh # noqa: E402 from M02_MasterTemplete import M02_Sangeun_Imported as imp # noqa: E402 from resources.tester.test_m02_labor_ref import world # noqa: E402, F401 — 같은 임시 층 SCRIPT = ROOT / "resources/master_data/scripts/build_기계목록_빠진기계_814_1.py" FAMILY = [f"GC0002{n}" for n in range(67, 77)] def excel(tag: str) -> dict: path = practice.FILES.get(tag) if not path or not path.exists(): pytest.skip(f"실무 엑셀 없음 {tag}") from M02_MasterTemplete import M02_San_Xlsx as sx return fn.pack(sx.read(path.name, path.read_bytes()), path.name) def doc_of(saved: dict, no: int) -> tuple[dict, dict, dict]: out = fn.emit(saved, no) sk, doc, got = t.doc_and_money(out) return out, doc, got def same_money(saved: dict, no: int, got: dict) -> bool: want = fn.numbers(saved)[no].get("저장") or {} return all(abs(float(got[k]) - float(want.get(c) or 0)) < 0.5 for c, k in t.COST[:3]) # ── ① ② 파일 하나 · 단가산출표 판 ────────────────────────────────────────── def test_one_file_and_list_sheet(world) -> None: assert not hasattr(fn, "same") and not hasattr(fh, "_natives") # 붙이기 길 걷음 stc = t._file(t.STC["봉화"]) folder = world["personal"] with store.at(folder): got = fh.upload_native(folder, stc.name, stc.read_bytes(), t.WHO) assert got["원파일"] and got["호표수"] == 0 and "붙음" not in got grid = hs.grid(folder, got["키"]) blocks = fn.numbers(t.native("STC", "봉화")) assert grid["보기"] == {"단가산출표": "s1"} and len(grid["단산"]) == len(blocks) head = grid["책"]["시트"][0]["칸"] assert [head[f"{c}1"]["값"] for c in "ABCDEFGH"] == list(fn._LIST_HEAD) first = next(iter(blocks.values())) assert head["A2"]["값"] == first["코드"] and head["H2"]["값"] == first["저장"]["T"] est = fn.hopyo(t.native("EST", "거창")) assert est["호표"] == [] and len(est["단산"]) == len(fn.numbers(t.native("EST", "거창"))) # ── ④ 세 줄 묶기 · 조합 그룹 ───────────────────────────────────────────── def _machine_split(doc: dict, out: dict) -> int: """문서에 남은 기계 비목 낱 줄(같은 기계 · 같은 수량 · 비목 셋이 따로) 묶음 수.""" names = {h["이름"]: h for h in out["로직"]["호표"]} seen: dict[tuple, set] = {} for m in doc["금액"]: res = (names.get(m.get("이름")) or {}).get("자원") or {} if res.get("갈래") == "기계" and m.get("비목") in ("노무비", "재료비", "경비"): seen.setdefault((res.get("코드"), str(m.get("수량"))), set()).add(m["비목"]) return sum(1 for v in seen.values() if len(v) == 3) def test_three_lines_one_machine_stc() -> None: saved = t.native("STC", "봉화") out, doc, got = doc_of(saved, 55) assert same_money(saved, 55, got) and _machine_split(doc, out) == 0 crawl = next(m for m in doc["금액"] if str(m["이름"]).startswith("크롤러드릴")) assert crawl["비목"] == "묶음" # 「노 무 비 · 재 료 비 · 경 비」 → 기계 한 줄 assert not any(str(h["이름"]).replace(" ", "") == "노무비" for h in out["로직"]["호표"]) def test_three_lines_excel_stmate_and_est_combo_group() -> None: saved = excel("봉화") out, doc, got = doc_of(saved, 55) assert same_money(saved, 55, got) and _machine_split(doc, out) == 0 est = t.native("EST", "main") out, doc, got = doc_of(est, 360) assert same_money(est, 360, got) titles = [it["제목"] for it in lay.stack_of(doc)] assert titles.count("덤프트럭 자동덮개시설 15톤용") == 1 # 다음 줄 셋 = 한 항목(엇갈림 없음) # ── ⑤ 계산식 기계 목록 · 조합 식 ───────────────────────────────────────── def _build(): spec = importlib.util.spec_from_file_location("build_814_1", SCRIPT) mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(mod) return mod def test_machine_lists_complete_and_defaults_kept() -> None: build = _build() add, skipped = build.plan() assert add == {} # 빠진 기계 모두 더함(다시 돌려도 같음) assert all(miss for *_, miss in skipped) # 못 넣음 = 기계표 빈 값(지어 넣지 않음) docs = json.loads(build.DOC.read_text("utf-8"))["줄"] for d in docs: if d["키"] in FAMILY: var = next(v for v in d["변수"] if v["열쇠"] == "기계") assert var["연결"]["값"] == var["연결"]["목록"][0] # 기본값 = 첫 기계 그대로 whole = imp._whole() eq = {r.get("원문번호"): k for k, r in whole.index["EQ"].items()} for code in ("0230-0007", "0211-0060", "6630-0005", "0213-0060"): # 브레이커 · 굴착기(타이어) … if code in eq: assert mh.hourly(eq[code])["ok"], code def test_combo_uses_our_combo_formula() -> None: saved = excel("거창") out, doc, got = doc_of(saved, 2) assert same_money(saved, 2, got) main = next(h for h in out["로직"]["호표"] if (h.get("자원") or {}).get("조합첫")) part = next(h for h in out["로직"]["호표"] if (h.get("자원") or {}).get("조합")) assert str(main["이음"]).startswith("로직(GC000271") and part["이음"] is None assert "조합" in link.why(part) assert main["규격"] != (main["자원"] or {}).get("규격") # 칩 글 = 그 기계 규격(조합 글 아님) # ── ⑧ ⑨ 유가 · 전력 · 재료 ─────────────────────────────────────────────── def test_oil_power_material_links() -> None: oil, _ = link.m01("재료", {"이름": "경 유", "규격": "", "단위": "ℓ"}) power, _ = link.m01("재료", {"이름": "전 력", "규격": "", "단위": "KWH"}) whole = imp._whole() assert whole.get(oil)["상세구분"] == "전국평균" assert whole.get(power)["상세구분"] == "일반용(갑)Ⅰ 저압" got = dim._oil(oil) assert got["키"] == "MO:경유:{S_지역}" and got[dim.OPTION]["연결"]["값"] == "전국평균" got = dim._oil(power) assert got["계약종별"] == "S_전력계약종별" assert got[dim.OPTION]["연결"]["값"] == "일반용(갑)Ⅰ 저압" ply, _ = link.m01("재료", {"이름": "합판(내수)", "규격": "12t×1220×2440mm", "단위": "㎡"}) nail, _ = link.m01("재료", {"이름": "철못", "규격": "N 25", "단위": "kg"}) assert whole.get(ply)["이름"] == "합판(내수), 서울" and whole.get(nail)["규격"] == "N 25" assert link.m01("재료", {"이름": "접착제", "규격": "", "단위": "kg"}) == (None, None) def test_oil_doc_region_option() -> None: saved = excel("2016사방") out, doc, got = doc_of(saved, 326) assert same_money(saved, 326, got) region = next(v for v in doc["변수"] if v["열쇠"] == "지역") assert region["연결"]["값"] == "전국평균" oil = next(v for v in doc["변수"] if str((v.get("연결") or {}).get("키", "")).startswith("MO:")) assert "엑셀" in oil["연결"] # 엑셀 값 = 검산용 # ── ⑦ 까닭 · 띠 ────────────────────────────────────────────────────────── def test_unlinked_reasons_and_tally(monkeypatch) -> None: from M02_MasterTemplete import M02_Hopyo_Row as hrow monkeypatch.setattr(hrow, "m01_picks", lambda kind, line: []) # 우리 자재표에 후보 없음 assert link.why({"자원": {"갈래": "재료", "이름": "없는자재글자", "규격": ""}}).startswith( "자재 없음" ) assert ( link.why({"자원": {"갈래": "재료", "이름": "구역화물 운임", "규격": ""}}) == "운임표 없음" ) gone = {"자원": {"갈래": "기계", "찾기": {"판정": "못 찾음", "까닭": "기종 모름"}}} assert link.why(gone) == "별칭 대기" sib = {"호표": [{"이름": "a", "참조": {"키": "SK-1"}, "자원": {"갈래": "단가산출"}}]} assert link.tally(sib) == {"됨": 1, "줄": 1, "조합": True} # ── ⑩ 가벼움 ───────────────────────────────────────────────────────────── def test_light_fixes(world) -> None: saved = excel("북부청임도") out = fn.emit(saved, 33) chk = imp.check(t.sk_of(out), t.whole()) assert chk["같음"] and all(x["같음"] for x in chk["줄"]) # 얼린 줄도 소계 식에 src = {"이름": "진동 로울러(핸드가이드식)", "규격": "0.7Ton", "단위": "hr"} link.m01("기계", src) assert src["찾기"]["판정"] and src["찾기"]["이름"] # 기계 찾기 판정 붙음 seen = [] folder = world["personal"] with store.at(folder): def first(key: str) -> None: # HP 를 목록에 보이기 전에 산근 보관(409 막음) seen.append(any(r["키"] == key for r in hs.rows(folder))) hs.save(folder, "가.xlsx", {"호표": [], "단산": []}, t.WHO, None, "지문814", first) assert seen == [False] origin = (ROOT / "M02_MasterTemplete/logic/M02_Sangeun_Origin.ts").read_text("utf-8") assert "엑셀 읽기 확정" in origin one = em.first_spec({"규격": "0.7+브레이카"}, [{"규격": "0.7㎥"}, {"규격": "0.7"}]) assert one["규격"] == "0.7㎥"