- M02_San_Link.hourly_text 가 「전력 계약종별=…」 를 따옴표 없이 적어 가져온 식 읽기(_link)가 못 읽음 → 이름을 따옴표로(읽는 쪽은 이미 받음) - 지킴 시험(test_820_1) 왕복 + EST main 504 번 = 저장 금액 · 우리 단가 셈 추가 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
174 lines
8.6 KiB
Python
174 lines
8.6 KiB
Python
"""820-1 지킴 — 품셈에 없는 소형 · 보조 기계 13 줄(EST 구성 씨앗 · 가격 갱신 필요 · 요구 785 ~ 787).
|
||
|
||
(a) 새 줄 · 값 — 발전기 손료계수 2362 공통(품셈 발전기 계열) · 브레이커 0.18 = 0.2㎥ 계수 · 가격 = 씨앗 ·
|
||
파이프 = 달러 × 환율(비고에 $ 값) · 비고 「갱신 필요」
|
||
(b) 계산식 — 손료 = 버림(가격 × 1000 × 손료계수 ÷ 10⁷) · 손료만 = GC000273 · 발전기 5.5 = GC000268 ·
|
||
파워트로웰 = GC001195(휘발유 + 잡품(주연료 줄의 %) + 전력) · 옛 엔진 = 새 수식 문서 · 시간당 이음 한 길
|
||
(c) 이음 — 가져온 이름 · 규격 → 새 줄 · 옛 기계 그대로
|
||
(d) 다른 문서 그대로 — 새 고르기는 목록 끝에만 · 기본값 그대로
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import importlib
|
||
import json
|
||
import sys
|
||
from decimal import Decimal
|
||
from pathlib import Path
|
||
|
||
ROOT = Path(__file__).resolve().parents[2]
|
||
for p in (ROOT, ROOT / "resources/master_data/scripts"):
|
||
if str(p) not in sys.path:
|
||
sys.path.insert(0, str(p))
|
||
|
||
from test_815_1_import_link import no_aliases # noqa: E402 — 진짜 마스터 별칭 빼고
|
||
|
||
from M01_MasterData import M01_MasterData_Store as m01 # noqa: E402
|
||
from M02_MasterTemplete import M02_Doc_Import as di # noqa: E402
|
||
from M02_MasterTemplete import M02_Machine_Find as mf # noqa: E402
|
||
from M02_MasterTemplete import M02_Machine_Hourly as mh # noqa: E402
|
||
|
||
ml = importlib.import_module("master_doc_link")
|
||
mc = importlib.import_module("master_doc_calc")
|
||
b802 = importlib.import_module("build_수식문서_802_2")
|
||
meq = importlib.import_module("master_equation")
|
||
|
||
NEW = {f"EQ{n:06d}" for n in range(656, 669)}
|
||
SEED = { # 키 → (이름, 규격, 가격, 손료계수, 시간당 식)
|
||
"EQ000656": ("발전기", "5.5㎾", 1063, 2362, "GC000268"),
|
||
"EQ000657": ("발전기", "7㎾", 1828.9, 2362, "GC000273"),
|
||
"EQ000658": ("발전기", "10㎾", 3213, 2362, "GC000273"),
|
||
"EQ000659": ("대형 브레이커", "0.18㎥", 4559, 6601, "GC000273"),
|
||
"EQ000660": ("에어 호스", "1.91㎝", 105, 5625, "GC000273"),
|
||
"EQ000661": ("모노펌프", "5-20㎥/hr", 4900, 4346, "GC000273"),
|
||
"EQ000662": ("파워트로웰", "3.73㎾", 2680, 5313, "GC001195"),
|
||
}
|
||
USD = {"150㎜×1m": 25, "150㎜×2m": 37, "150㎜×3m": 46,
|
||
"곡관 150㎜ 45°": 67, "곡관 150㎜ 60°": 84, "곡관 150㎜ 90°": 103} # fmt: skip
|
||
|
||
|
||
def _eq() -> dict[str, dict]:
|
||
return m01.loaded()[1].index["EQ"]
|
||
|
||
|
||
def _docs():
|
||
docs = ml.load_all()
|
||
return docs, ml.Links(m01.loaded()[1], docs)
|
||
|
||
|
||
def _loss(price, factor) -> Decimal:
|
||
return (Decimal(str(price)) * 1000 * Decimal(factor) / 10_000_000).quantize(Decimal(1), "ROUND_DOWN") # fmt: skip
|
||
|
||
|
||
def test_rows_and_values() -> None:
|
||
eq = _eq()
|
||
assert NEW <= set(eq)
|
||
for key, (name, spec, price, factor, _) in SEED.items():
|
||
row = eq[key]
|
||
assert (row["이름"], row["규격"]) == (name, spec), key
|
||
assert row["값"]["가격"] == Decimal(str(price)) and row["값"]["손료계수"] == factor, key
|
||
assert "갱신 필요" in row["비고"] and row["출처"]["가격"] == "자체", key
|
||
gens = [r for r in eq.values() if r.get("상세구분") == "발전기"]
|
||
assert {int(r["값"]["손료계수"]) for r in gens} == {2362} # 발전기 계열 공통
|
||
small, big = eq["EQ000659"]["값"], eq["EQ000031"]["값"]
|
||
for k in ("가격", "상각비계수", "정비비계수", "관리비계수", "손료계수"):
|
||
assert small[k] == big[k], k
|
||
pipes = {r["규격"]: r for r in eq.values() if r.get("상세구분") == "콘크리트펌프용 파이프"}
|
||
assert set(pipes) == set(USD)
|
||
rate = m01.loaded()[1].index["FX"]["FX000001"]["값"]
|
||
for spec, usd in USD.items():
|
||
row = pipes[spec]
|
||
assert row["값"]["손료계수"] == 4500 and f"${usd} 표기" in row["비고"]
|
||
assert abs(float(row["값"]["가격"]) - usd * float(rate) / 1000) < 1e-3
|
||
trowel = eq["EQ000662"]["값"]
|
||
assert (trowel["주연료종류"], trowel["주연료"], trowel["잡재료율"], trowel["전력"]) == ("휘발유", 1, 10, Decimal("3.73")) # fmt: skip
|
||
|
||
|
||
def test_formula_loss_and_one_way() -> None:
|
||
docs, links = _docs()
|
||
files, whole = m01.loaded()
|
||
logic = {r["키"]: r for _, r in b802.rows_of(files)}
|
||
for key in sorted(NEW):
|
||
row = _eq()[key]
|
||
got = mh.hourly(key)
|
||
assert got["ok"], (key, got)
|
||
want = SEED.get(key, (None, None, None, None, "GC000273"))[4]
|
||
assert got["로직"] == want, key
|
||
lg = logic[want]
|
||
given = meq.given_of(whole, lg)
|
||
changed = {"기계": row["원문번호"]} if mh.takes_machine(lg) else None
|
||
new = b802.new_answer(links, docs[want], changed)
|
||
old = b802.old_answer(whole, lg, {**given, **(changed or {})})
|
||
assert old == new and float(new["계"]) == got["계"], key # 옛 엔진 = 새 문서 = 시간당 이음
|
||
assert new["경비"] == _loss(row["값"]["가격"], row["값"]["손료계수"]), (
|
||
key
|
||
) # 손료 = 가격 × 계수
|
||
if want == "GC000273":
|
||
assert new["노무비"] == new["재료비"] == 0, key
|
||
assert di.hourly_link("EQ000662", "재료비")["키"] == "GC001195"
|
||
assert di.hourly_link("EQ000656", "노무비")["입력"]["기계"] == "'자체 발전기 5.5㎾'"
|
||
|
||
|
||
def test_trowel_lines() -> None:
|
||
docs, links = _docs()
|
||
got = mc.calc(docs["GC001195"], links)
|
||
lines = {x["이름"]: x for x in got["줄"]}
|
||
assert set(lines) == {"주연료", "전력", "잡재료비", "기계손료"}
|
||
assert lines["전력"]["수량"] == Decimal("3.73") and lines["주연료"]["수량"] == 1
|
||
assert lines["잡재료비"]["금액"] == (lines["주연료"]["금액"] * Decimal("0.1")).quantize(Decimal("0.1"), "ROUND_DOWN") # fmt: skip
|
||
assert got["노무비"] == 0 and got["경비"] == _loss(2680, 5313)
|
||
|
||
|
||
def test_find_links() -> None:
|
||
cases = {
|
||
("발전기", "5.5kW"): "EQ000656", ("발전기", "7kW"): "EQ000657",
|
||
("발전기", "10kW"): "EQ000658", ("발전기", "25kW"): "EQ000452",
|
||
("에어 호스", "1.91cm"): "EQ000660", ("에어호스", "1.91cm"): "EQ000660",
|
||
("모노펌프", "5-20㎥/hr"): "EQ000661", ("모노펌프", ""): "EQ000661",
|
||
("브레이카", "0.18㎥"): "EQ000659", ("대형브레이카", "0.2㎥"): "EQ000031",
|
||
("파워트로웰", "3.73kW"): "EQ000662",
|
||
("콘크리트펌프용 파이프", "150mm×1m"): "EQ000663",
|
||
("콘크리트펌프용 파이프", "150㎜×3m"): "EQ000665",
|
||
("콘크리트펌프용 파이프", "곡관 45˚"): "EQ000666",
|
||
("콘크리트펌프용 파이프", "곡관 150㎜ 90°"): "EQ000668",
|
||
} # fmt: skip
|
||
with no_aliases("EQ"):
|
||
for (name, spec), key in cases.items():
|
||
got = mf.find(name, spec)
|
||
assert (got["판정"], got["키"]) == (mf.LINKED, key), (name, spec, got)
|
||
|
||
|
||
def test_other_docs_same() -> None:
|
||
"""새 고르기는 기계경비식 목록 끝에만 · 기본값은 옛 기계 그대로."""
|
||
text = (ROOT / "resources/master_data/로직_건설품셈_08장_건설기계.json").read_text("utf-8")
|
||
codes = {r["원문번호"] for k, r in _eq().items() if k in NEW}
|
||
for row in json.loads(text)["줄"]:
|
||
for spec in row.get("입력") or []:
|
||
pick = [str(c) for c in spec.get("고르기") or []]
|
||
mine = [i for i, c in enumerate(pick) if c in codes]
|
||
if mine:
|
||
assert mine == list(range(len(pick) - len(mine), len(pick))), row["키"]
|
||
assert pick[0] not in codes, row["키"]
|
||
old = [k for k, r in _eq().items() if k not in NEW]
|
||
assert not [k for k in old if str(_eq()[k].get("원문번호") or "").startswith("자체 ")]
|
||
|
||
|
||
def test_blank_input_name_link_roundtrip() -> None:
|
||
"""820-2 — 빈칸 든 입력 이름(「전력 계약종별」)이 이음 글 → 읽기 왕복 · EST main 504 번 = 저장 금액 · 우리 단가 셈."""
|
||
from M02_MasterTemplete import M02_San_Link as sl
|
||
from M02_MasterTemplete import M02_Sangeun_Imported as imp
|
||
|
||
text = sl.hourly_text("EQ000662")
|
||
assert "'전력 계약종별'=" in text
|
||
node = di.dc.cv.mf.parse(text)
|
||
assert node[0] == "logic" and "전력 계약종별" in [k for k, _ in node[2]]
|
||
assert imp._link(text, "로직", "시험", imp._whole()) == text
|
||
import test_810_2_native_doc as t
|
||
|
||
out = t.fn.emit(t.native("EST", "main"), 504)
|
||
sk, _, got = t.doc_and_money(out)
|
||
assert float(got["계"]) == 546 # 원파일 저장 금액
|
||
sk[imp.KEY]["단가기준"] = "우리"
|
||
ours = di.run(di.derive(sk, None, t.whole()), sk, t.whole())
|
||
assert ours.get("셈됨") is not False and ours["계"] is not None
|