Files
Aislo/resources/tester/test_264_2_b09_schedule_options.py
T
eomsangdonandClaude Opus 5.5 fe2ed66268 feat(B09): 264-2 공기 옵션 셈 짝 · 다듬기 · 시험
- 셈 짝(Py · TS) 새 옵션 — 착공일(그날부터 · A 날수 비례) · 기후 자료 고르기 · 쉬는 날 · 공휴일 빼기 · 투입 기본 · 반영률 기본 · 주 장비 기준(장비 시간 합) · 중복 C 끝수 · 마지막 달 비작업
- 원천 = 기후 셋(거창 25 · 울진 24 · 울진 소광 25 · 0에서 직접) · 단위당 장비 시간 합
- clean_schedule 새 칸(모르는 값 · 범위 밖 버림)
- 시험 test_264_2 — 기본값 = 지금 결과 · 옵션마다 값 바뀜 · 거울 TS = Py

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STqEqztUthVFBmEP18wBSz
2026-10-11 16:40:21 +09:00

211 lines
8.7 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.
"""PLAN 264 — B09 공기 옵션(착공일 · 기후 자료 · 쉬는 날 · 공휴일 빼기 · 투입 · 준비 정리 · 반영률 ·
주 장비 기준 · 중복 C 끝수 · 마지막 달) — 옵션마다 결과가 바뀜 · 다듬기 · 거울 TS = Py · 패널 훅."""
import json
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
sys.path.insert(0, str(Path(__file__).resolve().parent))
from B09_Estimation.draft.B09_Draft_Schedule_Calc import compute_schedule # noqa: E402
from B09_Estimation.draft.B09_Draft_Schedule_Source import ( # noqa: E402
CLIMATES,
clean_schedule,
schedule_inputs,
unit_resources,
)
from test_252_1_b09_schedule import GEOCHANG, SRC, _bill, _ref_user, _ts_schedule # noqa: E402
SRC2 = {**SRC, "climates": CLIMATES}
UNITS = {
"a": {
"machine": {"key": "M1", "name": "굴착기 0.7㎥", "hr": 0.0066},
"labor": 0.01,
"machine_sum": {"hr": 0.0086, "n": 2},
},
"b": {"machine": None, "labor": 1.5},
}
BILL = [
{"kind": "item", "key": "a", "qty": 1000, "section": "1.2", "level": 2},
{"kind": "item", "key": "b", "qty": 80, "section": "2.1", "level": 2},
{"kind": "item", "key": "h", "qty": 9, "section": "3", "level": 2},
]
SRC3 = {**SRC2, "units": UNITS}
YEAR_2026 = {"start": {"year": 2026, "month": 1}, "tidy": 400}
def _plain():
"""거창 재현 사용자 값에서 법정공휴일 덮개만 뺌(자동 B 를 봄)."""
user = _ref_user()
del user["holiday"]
return user
def _cal(user, src=SRC2, bill=None):
return compute_schedule(src, _bill(GEOCHANG) if bill is None else bill, user)["calendar"]
def _rows(user):
return {r["key"]: r for r in compute_schedule(SRC3, BILL, user)["work"]["rows"]}
def test_기본값_그대로():
base = compute_schedule(SRC, _bill(GEOCHANG), _ref_user())
assert compute_schedule(SRC2, _bill(GEOCHANG), _ref_user()) == base
picked = {**_ref_user(), "climate_set": "geochang25", "rest": "sun", "machine_basis": "max",
"c_round": "none", "last_off": "ratio", "holiday_off": [], "units": 1, "crew": 10,
"rate_haul": 0, "rate_base": 100, "hours": 8, "prep_docs": 2, "prep_setup": 2,
"tidy": 4} # fmt: skip
assert compute_schedule(SRC2, _bill(GEOCHANG), picked) == base
assert (base["work"]["total"], base["calendar"]["days"]) == (95, 136)
def test_1_착공일_그날부터():
user = {**_plain(), "start": {"year": 2026, "month": 3, "day": 15}}
cal = _cal(user)
first = cal["months"][0]
# 3.15 ~ 3.31 = 17일 · A = ROUNDUP(3.6 × 17 ÷ 31, 1) = 2.0 · 일요일 15 · 22 · 29
assert (first["days"], first["a"], first["b_auto"]) == (17, 2.0, 3)
assert cal["start"] == {"year": 2026, "month": 3, "day": 15}
assert cal["end"] > _cal(_plain())["end"]
assert cal["months"][1]["days"] == 30 # 둘째 달부터 달 전체
def test_2_기후_자료_고르기():
m = _cal({**_plain(), "climate_set": "uljin24"})["months"][0]
assert (m["rain"], m["heat"]) == (2.9, 0)
m = _cal({**_plain(), "climate_set": "sogwang25"})["months"][5 - 3]
assert (m["rain"], m["heat"]) == (2.1, 0) # 5월
assert all(m["a"] == 0 for m in _cal({**_plain(), "climate_set": "zero"})["months"])
user = {**_plain(), "climate_set": "uljin24", "climate": {"rain": [9] * 12}}
assert _cal(user)["months"][0]["rain"] == 9 # 달마다 덮개가 이김
def test_3_쉬는_날_토일():
base = _cal(_plain())["months"][0]["b_auto"]
assert _cal({**_plain(), "rest": "satsun"})["months"][0]["b_auto"] == base + 4 # 2026.3 토 4번
def test_4_공휴일_빼기():
def auto(off):
cal = _cal({**YEAR_2026, "holiday_off": off}, bill=[])
return [m["b_auto"] for m in cal["months"][:12]]
full = auto([])
assert full == [5, 7, 6, 4, 8, 6, 5, 7, 7, 7, 5, 5]
assert auto(["labor"])[4] == 7 # 5.1 노동절
assert auto(["substitute"])[4] == 7 # 5.25 부처님오신날 대체
assert auto(["election"])[5] == 5 # 6.3 지방선거
assert auto(["constitution"])[6] == 4 # 7.17 제헌절
may = _cal({**YEAR_2026, "holiday_off": ["labor"]}, bill=[])["months"][4]["names"]
assert not any("노동절" in n for n in may)
def test_5_일_작업시간():
assert _rows({})["a"]["days"] == 0.9
assert _rows({"hours": 4})["a"]["days"] == 1.7 # 1000 × 0.0066 ÷ 4 = 1.65
def test_6_투입_기본():
rows = _rows({"units": 2, "crew": 5})
assert (rows["a"]["put"], rows["a"]["days"]) == (2, 0.5) # 0.4125 → 0.5
assert (rows["b"]["put"], rows["b"]["days"]) == (5, 24)
rows = _rows({"crew": 5, "rows": {"b": {"crew": 20}}})
assert rows["b"]["days"] == 6 # 줄마다 덮개가 이김
def test_7_준비_정리_기타():
work = compute_schedule(SRC3, BILL, {"prep_docs": 3, "prep_setup": 1, "tidy": 6, "etc": 2})
w = work["work"]
assert (w["prep"], w["tidy"], w["etc"]) == (4, 6, 2)
assert w["total"] == 4 + round(0.9 + 12 + 0 + 2) + 6
def test_8_반영률_기본():
rows = _rows({"rate_haul": 100, "rate_base": 50})
assert (rows["h"]["rate"], rows["a"]["rate"], rows["b"]["rate"]) == (100, 50, 50)
assert rows["b"]["days"] == 6
assert _rows({"rate_base": 50, "rows": {"b": {"rate": 100}}})["b"]["days"] == 12
def test_9_주_장비_장비_시간_합():
a = _rows({"machine_basis": "sum"})["a"]
assert (a["per"], a["who"], a["days"]) == (0.0086, "굴착기 0.7㎥ 외 1종", 1.1) # 8.6 ÷ 8
assert _rows({})["a"]["per"] == 0.0066
def test_10_중복일수_끝수():
base = _cal(_plain())["months"]
got = _cal({**_plain(), "c_round": "round1"})["months"]
assert all(m["c"] == round(m["c"], 1) for m in got)
assert [m["c"] for m in got] != [m["c"] for m in base]
def test_11_마지막_달_비작업_전부():
base = _cal(_ref_user())["months"][-1]
last = _cal({**_ref_user(), "last_off": "all"})
assert (base["off_used"], base["off"]) == (7, 15)
assert last["months"][-1]["off_used"] == 15 and last["days"] == 136 + 8
def test_다듬기_새_칸():
raw = {
"units": 2, "crew": 0, "rate_haul": 50, "rate_base": 120,
"climate_set": "uljin24", "rest": "sat", "machine_basis": "sum",
"c_round": "round1", "last_off": "x", "holiday_off": ["labor", "junk", "election"],
"start": {"year": 2027, "month": 2, "day": 30},
} # fmt: skip
assert clean_schedule(raw) == {
"rate_haul": 50,
"units": 2,
"climate_set": "uljin24",
"machine_basis": "sum",
"c_round": "round1",
"holiday_off": ["election", "labor"],
"start": {"year": 2027, "month": 2},
}
got = clean_schedule({"start": {"year": 2027, "month": 3, "day": 15}, "holiday_off": "x"})
assert got == {"start": {"year": 2027, "month": 3, "day": 15}}
def test_원천_기후_셋_장비_합():
sheets = {
"bill": {"rows": [{"kind": "item", "key": "earth:4", "ref": {"tab": "sg", "key": "S1"}}]},
"sg": [{"key": "S1", "lines": [
{"kind": "machine", "ref": {"key": "M1"}, "qty": 0.0066},
{"kind": "machine", "ref": {"key": "M2"}, "qty": 0.002},
]}],
"machine": [],
} # fmt: skip
unit = unit_resources(sheets)["earth:4"]
assert unit["machine_sum"]["n"] == 2 and round(unit["machine_sum"]["hr"], 6) == 0.0086
got = schedule_inputs(sheets)
assert list(got["climates"]) == ["geochang25", "uljin24", "sogwang25", "zero"]
assert got["climates"]["geochang25"]["rain"] == got["climate"]["rain"]
def test_거울_TS_옵션():
cases = [
(SRC2, _bill(GEOCHANG), {**_plain(), "start": {"year": 2026, "month": 3, "day": 15},
"climate_set": "sogwang25", "rest": "satsun",
"holiday_off": ["substitute", "constitution"],
"c_round": "round1", "last_off": "all"}),
(SRC3, BILL, {"machine_basis": "sum", "units": 3, "crew": 7, "rate_haul": 30,
"rate_base": 80, "hours": 9, "prep_docs": 1, "tidy": 2, "etc": 1,
"climate_set": "zero", "start": {"year": 2027, "month": 12, "day": 31}}),
] # fmt: skip
for src, bill, user in cases:
py = json.loads(json.dumps(compute_schedule(src, bill, user), ensure_ascii=False))
assert _ts_schedule(src, bill, user) == py
def test_패널_훅_draft_없으면_빈칸():
tabs = (ROOT / "B09_Estimation/B09_Estimation_UI_Tabs.ts").read_text("utf-8")
shell = (ROOT / "B09_Estimation/B09_Estimation_UI_Shell.ts").read_text("utf-8")
ui = (ROOT / "B09_Estimation/draft/B09_Draft_Schedule_UI.ts").read_text("utf-8")
assert "{ builders: {}, extend: [], panels: [] }" in tabs # draft 없으면 카드 없음
assert "...DRAFT.panels.map((make) => make(() => user, setUser))" in shell
assert "DRAFT.panels.push(" in ui