Files
Aislo/resources/tester/test_814_1_import_decisions.py
T
eomsangdonandClaude Opus 5.5 6206b2cace refactor(815-2): 연결 찾기(기초 찾기) 전체 걷기 — 요구 762-1 · 763-1 · 방향 G1
- 서버 M02_Doc_Match · M02_Doc_Match_Note · M02_Doc_Match_Value · M02_Doc_MatchSk 지움 · 길 POST …/sangeun/{키}/match 지움
- 화면 판정 띠(M02_MasterTemplete_Sangeun_MatchBand · logic/M02_Sangeun_Match) · [연결 찾기] · [바꾸기] · [되돌리기] 단추 · 원문 카드 판정 글 지움
- 옛 SK 기록 `가져온.원본.연결후보` · 바꾼 SK `가져온보관` = 읽기만 그대로(카드 · 깨지지 않음 · 지우지 않음)
- 시험 지움: test_810_4_match · test_812_2_match_logic · test_812_4_rate_value · test_812_11_match_values · helper_810_4_sweep(걷은 모듈 지킴) · test_814_1 의 꼬리 뗀 맞대기 · 공구손료 비목 · 띠 글 · 도우미 줄(연결 찾기 몫)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 15:02:01 +09:00

221 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["호표수"] == 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㎥"