Files
Aislo/resources/tester/test_814_1_import_decisions.py
T
eomsangdonandClaude Opus 5.5 43e9455688 feat(828-1): 원파일 일위대가 구성 줄 → 일위대가 길 · 중기 구성 → 내 기계 씨앗 · 낙찰률 줄 2 칸 좁힘
- ㉯ STC 일위 = HP 호표(엑셀 일위대가표 길과 같은 꼴 · 「일위대가표」 시트) → 목록 · 만들기 · 고를 재료 판(구성 줄 · 「일위대가 N」)
- ㉰ 구성표 중기 → 기계구성(엑셀 중기사용료와 같은 값 · 달러 손료 = 천원) · 같은 파일 구성 대조 같은구성 → STmate 기계 찾기
- ㉱ 낙찰률 줄 2 칸(원단가) = 구성 셈 · 일위 머리만(엑셀 일위대가표와 같음) · 산근 · 자원 = 1 칸 그대로(엑셀 단가산출근거와 같음 · 812-1 짝 0)
- 별도계상 = 자재 이름 · 규격 「별도계상」 · 「별산」(엑셀 비고 「별산…」 짝 · 828-2 대조)
- MyMachine 엑셀 중기사용료 잡품 줄 = 잡재료 · 잡재료비 이름도(원파일과 같은 값)
- 시험 test_828_1_stc_composition 새로 · test_814_1 낡은 칸 고침(원파일 호표 0 → 45 · 격자 일위대가표 시트) · test_810_1 거울 대조 글 칸 앞 빈칸 걷음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-13 20:47:55 +09:00

222 lines
12 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.
# 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["호표수"] == 45 and "붙음" not in got # 828-1 구성표 일위대가
grid = hs.grid(folder, got["키"])
blocks = fn.numbers(t.native("STC", "봉화"))
assert grid["보기"] == {"단가산출표": "s1", "일위대가표": "s2"}
assert len(grid["단산"]) == len(blocks) and len(grid["호표"]) == 45
head = next(s for s in grid["책"]["시트"] if s["id"] == "s1")["칸"]
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㎥"