- 기계.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>
229 lines
12 KiB
Python
229 lines
12 KiB
Python
"""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"}
|