feat(M01): /calc 재료 줄마다 품목 칸(키·이름·규격·단위·값칸) 더함 (PLAN 8-5)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JgUhN55Z3BCYhUJTjwTNfj
This commit is contained in:
@@ -550,7 +550,7 @@ def run(master: Master, ref: str, given: dict, depth: int = 0) -> dict:
|
||||
sums = dict.fromkeys(COST_ITEMS, Decimal(0))
|
||||
lines = []
|
||||
for n, item in enumerate(row.get("호표", [])):
|
||||
source = why = warn = None
|
||||
source = why = extra = None
|
||||
try:
|
||||
qty = evaluate(parse(item["수량"]), env, master, depth)
|
||||
except EmptyInput as e: # 비어 있는 입력을 쓰는 줄만 비움
|
||||
@@ -584,7 +584,7 @@ def run(master: Master, ref: str, given: dict, depth: int = 0) -> dict:
|
||||
# (없는 키는 틀린 로직 — 지금처럼 멈춤) · 검색어 · 고름 · 단위 환산은 `master_pick`
|
||||
if isinstance(ref, str) and qty != 0:
|
||||
master.get(ref)
|
||||
price, source, why, warn = mp.priced(master, item, ref, env, n if depth == 0 else None)
|
||||
price, source, why, extra = mp.priced(master, item, ref, env, n if depth == 0 else None)
|
||||
if price is None and qty == 0:
|
||||
price, why = Decimal(0), None # 안 쓰는 재료 줄 — 값이 비어도 금액 0
|
||||
split = {item["비목"]: None if price is None else qty * price}
|
||||
@@ -600,7 +600,7 @@ def run(master: Master, ref: str, given: dict, depth: int = 0) -> dict:
|
||||
"비목": split,
|
||||
**({"출처": source} if source else {}),
|
||||
**({"까닭": why} if why else {}),
|
||||
**({"단위경고": warn} if warn else {}),
|
||||
**(extra or {}),
|
||||
}
|
||||
)
|
||||
# 덧줄이 호표 줄 금액 하나를 빼고 더할 수 있게 — `줄.'호표 줄 이름'`(빈 줄은 뺌)
|
||||
|
||||
@@ -129,7 +129,27 @@ def pick(master, cond: dict, env: dict, unit: str | None = None) -> str:
|
||||
|
||||
def priced(master, item: dict, ref, env: dict, index: int | None):
|
||||
"""재료 줄 단가 — (단가, 출처, 까닭, 단위경고). 고른 품목(`고름`)이 있으면 그것 · 검색어가 있으면 찾기 ·
|
||||
없으면 옛 길. 자재 단가는 줄 단위로 환산 · 성격이 다르면 그대로 두고 경고."""
|
||||
없으면 옛 길. 자재 단가는 줄 단위로 환산 · 성격이 다르면 그대로 두고 경고.
|
||||
넷째 = 줄에 덧붙일 칸 — `단위경고` · `품목`(잡힌 품목 키 · 이름 · 규격 · 단위 · 값 칸)."""
|
||||
price, source, why, warn = _priced(master, item, ref, env, index)
|
||||
extra = {"단위경고": warn} if warn else {}
|
||||
key, _, slot = (source or "").partition(".")
|
||||
try:
|
||||
row = master.get(key) if key else None
|
||||
except mm.mf.FormulaError:
|
||||
row = None
|
||||
if row:
|
||||
extra["품목"] = {
|
||||
"키": key,
|
||||
"이름": row.get("이름"),
|
||||
"규격": row.get("규격"),
|
||||
"단위": row.get("단위"),
|
||||
"값칸": slot,
|
||||
}
|
||||
return price, source, why, extra
|
||||
|
||||
|
||||
def _priced(master, item: dict, ref, env: dict, index: int | None):
|
||||
unit = item.get("단위")
|
||||
picked = (CHOSEN.get() or {}).get(str(index)) if index is not None else None
|
||||
try:
|
||||
|
||||
@@ -127,3 +127,19 @@ def test_기계_입력은_EQ_키도_원문번호로_풀어_받음(client) -> Non
|
||||
assert by_key["sums"] == by_number["sums"] # EQ 키 = 원문번호와 같은 계산
|
||||
bad = client.post("/api/m01/calc", json={"key": key, "inputs": {**base, "기계": "EQ999999"}})
|
||||
assert bad.json()["ok"] is False
|
||||
|
||||
|
||||
def test_재료_줄마다_품목_칸(client) -> None:
|
||||
key = "GF000160"
|
||||
body = {"key": key, "inputs": {k: str(v) for k, v in store.auto(key)["값"].items()}}
|
||||
lines = client.post("/api/m01/calc", json=body).json()["lines"]
|
||||
idx = next(
|
||||
i for i, x in enumerate(lines) if x.get("품목", {}).get("이름", "").startswith("합판(내수)")
|
||||
)
|
||||
first = lines[idx]["품목"]
|
||||
assert first["이름"] == "합판(내수), 서울" and first["값칸"]
|
||||
assert {"키", "이름", "규격", "단위", "값칸"} <= set(first)
|
||||
lines = client.post("/api/m01/calc", json={**body, "고름": {str(idx): "MT000019"}}).json()[
|
||||
"lines"
|
||||
]
|
||||
assert lines[idx]["품목"]["키"] == "MT000019" and "인천" in lines[idx]["품목"]["이름"]
|
||||
|
||||
Reference in New Issue
Block a user