Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1
# Conflicts: # M01_MasterData/M01_MasterData_Router.py
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
"""712-1 지킴 — 인력 · 재료 · 경비 묶음 셈 때 파생 · 기계경비 판별 한 곳(`master_bundle`)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from decimal import Decimal
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT / "resources/master_data/scripts"))
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
import check_master as cm # noqa: E402
|
||||
import master_auto as mau # noqa: E402
|
||||
import master_bundle as mb # noqa: E402
|
||||
import master_formula as mf # noqa: E402
|
||||
|
||||
FILES = cm.load()
|
||||
MASTER = mf.Master(FILES)
|
||||
LOGICS = {r["키"]: r for d in FILES.values() if d.get("그룹") == "로직" for r in d.get("줄") or []}
|
||||
|
||||
|
||||
def test_every_line_bundled():
|
||||
"""호표 · 덧줄 전수 — 하위(못 가름) ≤ 31 · 나머지는 인력 · 재료 · 경비 · 기계경비 줄 = 경비."""
|
||||
count = dict.fromkeys(mb.BUNDLES, 0)
|
||||
machine = 0
|
||||
for row in LOGICS.values():
|
||||
for item in [*row.get("호표", []), *row.get("덧줄", [])]:
|
||||
name, is_mach = mb.bundle(MASTER, item)
|
||||
count[name] += 1
|
||||
machine += is_mach
|
||||
if is_mach:
|
||||
assert name == "경비"
|
||||
assert sum(count.values()) > 5_000
|
||||
assert count["하위"] <= 31, count
|
||||
assert machine >= 1_000, machine
|
||||
|
||||
|
||||
def test_machine_rule_one_place():
|
||||
"""기계경비 = 결과단위 원/시간 돈 로직 — 작업량(㎥/시간) · 이름 「시간당」 은 아님."""
|
||||
machines = {k for k, r in LOGICS.items() if mb.is_machine(r)}
|
||||
assert {"GC000267", "GC000268", "GF000145"} <= machines
|
||||
assert not machines & {"GF000102", "GC000422", "GC000260"} # 작업량 · ㎡/시간 · 원/㎡
|
||||
sys.path.insert(0, str(ROOT))
|
||||
from M02_MasterTemplete import M02_Hopyo_Ours as ours
|
||||
|
||||
assert all(ours.is_mach_logic(r) == mb.is_machine(r) for r in LOGICS.values())
|
||||
|
||||
|
||||
def test_run_lines_tagged():
|
||||
"""엔진 `run` 줄마다 `묶음` · 기계경비 줄은 `장비` + 비목 노 · 재 · 경 세 몫."""
|
||||
seen = set()
|
||||
for key in ("GF000169", "GF000103"):
|
||||
row = LOGICS[key]
|
||||
given = {k: v for k, v in mau.values(MASTER, row).items() if v is not None}
|
||||
got = mf.run(MASTER, key, given)
|
||||
for line in got["줄"]:
|
||||
assert line["묶음"] in mb.BUNDLES
|
||||
seen.add(line["묶음"])
|
||||
if line.get("장비"):
|
||||
assert set(line["비목"]) == set(mf.COST_ITEMS)
|
||||
total = sum((line["비목"].get(k) or Decimal(0)) for line in got["줄"] for k in line["비목"])
|
||||
assert abs(total - got["계"]) < 1
|
||||
assert {"인력", "재료", "하위"} <= seen
|
||||
@@ -0,0 +1,59 @@
|
||||
"""712-2 지킴 — 단위 한 벌(`master_unit`) · 다섯 자리가 같은 모듈을 씀 · 알약 배 전수."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from decimal import Decimal
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT / "resources/master_data/scripts"))
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
import check_master as cm # noqa: E402
|
||||
import master_basis as mbs # noqa: E402
|
||||
import master_formula as mf # noqa: E402
|
||||
import master_material as mm # noqa: E402
|
||||
import master_pick as mp # noqa: E402
|
||||
import master_unit as mu # noqa: E402
|
||||
|
||||
|
||||
def test_alias_and_count_group():
|
||||
assert mu.plain("EA") == mu.plain("ea") == "개"
|
||||
assert mu.plain("Ton") == mu.plain("t") == mu.plain("톤") == "톤"
|
||||
assert mu.plain("㎥") == "m3" and mu.plain("리터") == mu.plain("ℓ") == "ℓ"
|
||||
assert mu.same_kind("본", "개") and mu.same_kind("매", "EA")
|
||||
assert not mu.same_kind("조", "개") # 한 조 = 여럿 — 묶음 밖
|
||||
assert mu.note("본", "개") == "본 = 개(개수 1:1)"
|
||||
assert mm.plain_unit("EA") == mu.plain("EA")
|
||||
assert mp.convert is mu.convert and mp.UNITS is mu.UNITS
|
||||
|
||||
|
||||
def test_same_kind_convert():
|
||||
assert mu.convert(Decimal(1000), "톤", "kg") == Decimal(1) # 톤 당 1000 원 = kg 당 1 원
|
||||
assert mu.convert(Decimal(5), "kg", "톤") == Decimal(5000)
|
||||
assert mu.convert(Decimal(7), "m", "km") == Decimal(7000)
|
||||
assert mu.factor("kg", "ton") == Decimal("0.001") # B08 「/1000」(kg → ton) 손 식과 같은 값
|
||||
assert mu.note("kg", "톤") == "kg → 톤 ×1000"
|
||||
assert mu.convert(Decimal(3), "kg", "ℓ") == Decimal(3) # 밀도 = 경고만 · 그대로
|
||||
assert "성격 다름" in mu.note("ℓ", "㎾h")
|
||||
|
||||
|
||||
def test_volume_weight_only_when_picked():
|
||||
"""부피 ↔ 무게 = 단위중량 표 고른 줄이 있을 때만 · 기본 줄 없음."""
|
||||
master = mf.Master(cm.load())
|
||||
assert mu.factor("㎥", "톤") is None
|
||||
assert "단위중량(CC000337)" in mu.note("㎥", "톤")
|
||||
rho = mu.unit_weight(master, "모래", "건조")
|
||||
assert rho is not None and rho > 1000
|
||||
assert mu.factor("㎥", "톤", rho) == rho / 1000 # 1 ㎥ = ρ/1000 톤
|
||||
assert mu.convert(Decimal(100), "㎥", "톤", rho) == Decimal(100) * 1000 / rho
|
||||
assert mu.unit_weight(master, "없는 종별", "건조") is None
|
||||
|
||||
|
||||
def test_result_per_one_parser():
|
||||
assert mu.per("원/10㎡") == [(Decimal(10), "㎡")]
|
||||
assert mu.per("원/1,000본") == [(Decimal(1000), "본")]
|
||||
assert mu.per("원/㎡ · 원/㎥(단위 따름)") == [(Decimal(1), "㎡"), (Decimal(1), "㎥")]
|
||||
assert mu.per("㎥/시간") == []
|
||||
assert mbs.result_per("원/ton") == [(Decimal(1), "ton")]
|
||||
@@ -0,0 +1,62 @@
|
||||
"""714-1 지킴 — 계산서 줄 덧칸(쓰는 입력 · 묶음 · 기준단위 · 환산 근거) · M01 `/text` 와
|
||||
M02 `/logic-lib/sheet` 가 같은 줄 꼴(`master_sheet_line`)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from M01_MasterData import M01_MasterData_Store as store # noqa: E402
|
||||
from M01_MasterData import M01_MasterData_Store_Sheet as sheet # noqa: E402
|
||||
from M02_MasterTemplete import M02_Logic_Standard as standard # noqa: E402
|
||||
|
||||
msl = sheet.msl
|
||||
|
||||
|
||||
def _text(key: str) -> dict:
|
||||
auto = {k: v for k, v in store.auto(key)["값"].items() if v is not None}
|
||||
return sheet.text(key, auto)
|
||||
|
||||
|
||||
def _lines(got: dict, groups: str = "groups") -> list[dict]:
|
||||
return [line for g in got[groups] for line in g["줄"]]
|
||||
|
||||
|
||||
def test_line_keys_and_bundle():
|
||||
got = _text("GF000103") # 구조물터파기 — 인력 + 굴착기 기계경비 줄
|
||||
assert got["ok"] and got["기준단위"] == "㎥"
|
||||
lines = _lines(got)
|
||||
assert all({"쓰는입력", "묶음", "기준단위", "환산근거"} <= set(line) for line in lines)
|
||||
machine = [line for line in lines if line.get("장비")]
|
||||
assert machine and all(line["묶음"] == "경비" for line in machine)
|
||||
assert {"지역", "보정작업"} <= set(machine[0]["쓰는입력"])
|
||||
assert any(line["묶음"] == "인력" for line in lines)
|
||||
|
||||
|
||||
def test_uses_follow_middle_values():
|
||||
"""중간값을 거슬러 입력까지 — 돌쌓기 석공 품은 높이 · 쌓기 · 돌 입력을 씀."""
|
||||
lines = _lines(_text("GF000219"))
|
||||
assert {"높이", "쌓기", "돌"} <= set(lines[0]["쓰는입력"])
|
||||
|
||||
|
||||
def test_basis_note_tenfold():
|
||||
"""표 10㎡당 → 결과 원/㎡ 「÷10」 · 결과도 원/10㎡ 면 근거 없음."""
|
||||
one = _lines(_text("GC000037"))
|
||||
assert one[0]["기준단위"] == "10㎡" and one[0]["환산근거"] == "10㎡당 → 1㎡당 ÷10"
|
||||
ten = _text("GF000124")
|
||||
assert ten["기준단위"] == "10㎡" and all(not x["환산근거"] for x in _lines(ten))
|
||||
|
||||
|
||||
def test_two_routes_same_shape():
|
||||
"""M01 `/text` 와 M02 계산서 — 줄 차례 · 덧칸 값이 같음(금액 끝수만 다를 수 있음)."""
|
||||
for key in ("GF000169", "GF000103", "GF000219"):
|
||||
m01 = _lines(_text(key))
|
||||
m02 = _lines(standard.sheet(key, {}), "묶음")
|
||||
assert len(m01) == len(m02)
|
||||
for a, b in zip(m01, m02):
|
||||
assert set(b) == {"이름", "글", "금액", *msl.KEYS}
|
||||
assert a["이름"] == b["이름"]
|
||||
assert [a.get(k) for k in msl.KEYS] == [b.get(k) for k in msl.KEYS]
|
||||
Reference in New Issue
Block a user