Files
Aislo/resources/tester/test_815_1_import_link.py
T
eomsangdonandClaude Opus 5.5 0392cd12ad feat(820-1): 품셈에 없는 소형·보조 기계 13 줄 · 파워트로웰 기계경비식 GC001195 · 가격 = EST 씨앗(갱신 필요)
- 기계.json EQ000656~668 더함 — 발전기 5.5·7·10㎾(손료계수 2362 발전기 계열) · 대형 브레이커 0.18㎥(0.2㎥ 계수·가격) · 에어 호스 1.91㎝ · 모노펌프 5-20㎥/hr · 파워트로웰 3.73㎾ · 콘크리트펌프용 파이프 여섯($ × 환율 FX000001 · 비고에 $ 값)
- 원문번호 「자체 이름 규격」 · 출처 가격 = 자체 · 비고 = EST 단가 구성 → 우리 줄 · 가격 갱신 필요
- 계산식 — 손료만 = GC000273 · 발전기 5.5 = GC000268 고르기 끝에 더함(814-1 빌더 · 로직 + 수식 문서) · 파워트로웰 = 새 GC001195(휘발유 + 잡품(주연료 줄 %) + 전력 + 손료)
- 발전기 7·10㎾ = 2016 사방품셈(안) 기계경비 가격 · 그 표대로 손료만
- M02_Machine_Hourly 시간당 부름에서 빈칸 든 입력 이름(전력 계약종별) 따옴표 — 파워트로웰 시간당 이음
- 기술정보 기계경비_산정 7 절(표)
- 낡은 시험 고침: 812_9 test_no_guess · 815_1 test_alias_machine 이 「에어 호스 · 모노펌프 = 우리 표에 없음」 예로 씀 → 아직 없는 기종(진공흡입기 · 스키드로더)으로
- 기존 수식 문서 1,514 기본 금액 전후 같음 · check_master 전후 같음

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

229 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 no_aliases(table: str):
"""그 표 별칭 칸을 시험 동안 모두 비움 — 진짜 마스터에 별칭이 더 들어가도 시험이 안 흔들림."""
return aliases(table, {k: "" for k in ea.book()["줄"][table]})
# ── 별칭 풀이 ─────────────────────────────────────────────────────────────
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:
with no_aliases("EQ"):
assert mf.find("진공흡입기", "5kW")["판정"] == mf.NONE
assert mf.find("크레인(트럭)", "15Ton")["판정"] != mf.ALIAS # 별칭 전
with (
no_aliases("EQ"),
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 no_aliases("EQ"), aliases("EQ", {"EQ000129": "크레인 갑", "EQ000130": "크레인 갑"}):
assert mf.find("크레인 갑")["판정"] == mf.MANY # 같은 별칭 둘 = 후보 여럿
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"}