Files
Aislo/resources/tester/test_815_1_import_link.py
T
eomsangdonandClaude Opus 5.5 8edd8ad6ee fix(815-1 K1·K5): 공사 직접 번호(M0000000…) 코드 잇기 뺌 · 견적은 우리 값 있는 자재일 때만 바꿈
- EST 공사 파일 자재 239 중 235 = 공사 직접 번호 — 파일마다 다른 품목(거창 치즐(0.7㎥) M0000000001 ↔ 우리 표 Air Hammer) · 이름 길로
- 견적 줄이 값 칸 다섯이 빈 우리 자재(치즐 0.7 MT000011)로 이어지면 0 원 — 견적 값 그대로 + 표지
- 시험 test_815_1 — 공사 직접 번호 · 값 빈 견적 · 거창 전 블록 금액

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

220 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.
"""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 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:
assert mf.find("에어 호스", "1.91cm")["판정"] == mf.NONE
with 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 aliases("EQ", {"EQ000129": "크레인 갑", "EQ000130": "크레인 갑"}):
assert mf.find("크레인 갑")["판정"] == mf.MANY # 같은 별칭 둘 = 후보 여럿
assert mf.find("크레인(트럭)", "15Ton")["판정"] != mf.ALIAS # 되돌림
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"}