- 서버 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>
221 lines
12 KiB
Python
221 lines
12 KiB
Python
# 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㎥"
|