feat(M01): 원 단위 입력 자동값 null · 그 입력 쓰는 줄만 비움

- master_auto: 단위에 원이 든 수 입력은 0 대신 null
- master_formula: 비어 있는 입력을 쓰는 호표·덧줄 줄만 단가·금액 null + 까닭(입력 비어 있음) · 로직 계속
- 시험 test_m01_auto.py 둘 더함 · GF000056

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JgUhN55Z3BCYhUJTjwTNfj
This commit is contained in:
2026-09-24 10:05:20 +09:00
co-authored by Claude Sonnet 5
parent bb4500b4c3
commit 7215770eb9
3 changed files with 44 additions and 3 deletions
+4 -1
View File
@@ -89,5 +89,8 @@ def values(master: mf.Master, row: dict) -> dict:
out[name] = spec["범위"][0]
else:
got = _lowest(master, finds.get(name, []))
out[name] = got if got is not None else Decimal(1 if name in divs else 0)
if got is None and "원" in str(spec.get("단위") or ""):
out[name] = None # 가격 입력 — 0 원처럼 보이지 않게 비워 둠
else:
out[name] = got if got is not None else Decimal(1 if name in divs else 0)
return out
@@ -32,6 +32,10 @@ class FormulaError(ValueError):
pass
class EmptyInput(FormulaError):
"""값을 안 넣은(null) 입력을 씀 — 그 줄만 비우고 로직은 계속(`run`)."""
# ── 읽기 ──────────────────────────────────────────────────────────────
class _Parser:
def __init__(self, text: str):
@@ -360,6 +364,8 @@ def evaluate(node, env: dict, master: Master, depth: int = 0):
if kind == "name":
if node[1] not in env:
raise FormulaError(f"모르는 이름 「{node[1]}」")
if env[node[1]] is None:
raise EmptyInput(f"입력 「{node[1]}」 비어 있음")
return env[node[1]]
if kind == "ref":
value = element(master, fill(node[1], env), env)[0]
@@ -464,7 +470,7 @@ def _inputs(row: dict, given: dict) -> dict:
raise FormulaError(f"입력 「{name}」={value} 고르기 밖")
if "범위" in spec:
low, high = spec["범위"]
if not low <= value <= high:
if value is not None and not low <= value <= high:
raise FormulaError(f"입력 「{name}」={value} 범위 [{low}, {high}] 밖")
env[name] = value
unknown = set(given) - set(env)
@@ -485,8 +491,23 @@ def run(master: Master, ref: str, given: dict, depth: int = 0) -> dict:
sums = dict.fromkeys(COST_ITEMS, Decimal(0))
lines = []
for item in row.get("호표", []):
qty = evaluate(parse(item["수량"]), env, master, depth)
source = why = None
try:
qty = evaluate(parse(item["수량"]), env, master, depth)
except EmptyInput as e: # 비어 있는 입력을 쓰는 줄만 비움
qty, price, why = None, None, str(e)
lines.append(
{
"이름": item.get("이름", ""),
"단위": item.get("단위", ""),
"수량": None,
"단가": None,
"금액": None,
"비목": {item["비목"]: None} if "비목" in item else {},
"까닭": why,
}
)
continue
if item["종류"] == "로직":
bundle = evaluate(parse(item["요소"]), env, master, depth)
price = bundle["계"]