feat(M01): 견본 자동값 길 · 값 빈 품목은 그 줄만 비움

- GET /api/m01/logic/auto — 고르기 목록 첫째 · 범위 최솟값 · 찾기 조건 값은 그 표 가장 낮은 줄 · 나누는 수 1 · 그 밖 0 (master_auto.py)
- run — 후보 0 · 값 빈 품목(덧줄 포함)은 단가·금액 null + 까닭 · 소계·계는 빈 줄 빼고 더함 · 없는 키는 지금처럼 멈춤
- 자동값으로 로직 1,354 전부 계산 · 멈춤 0 · 시험 test_m01_auto.py

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LcqZtEzEf1sYSU4FUmVLNJ
This commit is contained in:
2026-09-24 09:04:07 +09:00
co-authored by Claude Opus 5.5
parent 6eaa539fde
commit 79c42224e5
5 changed files with 234 additions and 16 deletions
+29 -16
View File
@@ -486,7 +486,7 @@ def run(master: Master, ref: str, given: dict, depth: int = 0) -> dict:
lines = []
for item in row.get("호표", []):
qty = evaluate(parse(item["수량"]), env, master, depth)
source = None
source = why = None
if item["종류"] == "로직":
bundle = evaluate(parse(item["요소"]), env, master, depth)
price = bundle["계"]
@@ -503,37 +503,49 @@ def run(master: Master, ref: str, given: dict, depth: int = 0) -> dict:
if isinstance(item["요소"], dict)
else fill(item["요소"], env)
)
# 후보 0 · 값 빈 품목은 그 줄만 단가·금액을 비우고 까닭을 닮 — 로직은 계속 · 합에서 뺌
# (없는 키는 틀린 로직 — 지금처럼 멈춤)
if isinstance(ref, str) and qty != 0:
master.get(ref)
try:
price, source = element(master, ref, env, item.get("단위"))
except FormulaError:
if qty != 0:
raise
price = None
why = (
None
if price is not None
else f"「{master.label(ref)}」 값 없음 — 관리자가 채울 값"
)
except FormulaError as e:
price, why = None, str(e)
if price is None and qty == 0:
price = Decimal(0) # 안 쓰는 재료 줄 — 값이 비어도 금액 0
if price is None:
raise FormulaError(f"「{master.label(ref)}」 값 없음 — 관리자가 채울 값")
split = {item["비목"]: qty * price}
price, why = Decimal(0), None # 안 쓰는 재료 줄 — 값이 비어도 금액 0
split = {item["비목"]: None if price is None else qty * price}
for k, v in split.items():
sums[k] += v
sums[k] += v or 0
lines.append(
{
"이름": item.get("이름", ""),
"단위": item.get("단위", ""),
"수량": qty,
"단가": price,
"금액": qty * price,
"금액": None if price is None else qty * price,
"비목": split,
**({"출처": source} if source else {}),
**({"까닭": why} if why else {}),
}
)
# 덧줄이 호표 줄 금액 하나를 빼고 더할 수 있게 — `줄.'호표 줄 이름'`
env["줄"] = {item["이름"]: item["금액"] for item in lines if item["이름"]}
# 덧줄이 호표 줄 금액 하나를 빼고 더할 수 있게 — `줄.'호표 줄 이름'`(빈 줄은 뺌)
env["줄"] = {x["이름"]: x["금액"] for x in lines if x["이름"] and x["금액"] is not None}
blank = {x["이름"]: x["까닭"] for x in lines if x.get("까닭")}
for extra in row.get("덧줄", []):
env.update(sums)
value = evaluate(parse(extra["식"]), env, master, depth)
env[extra["이름"]] = value
sums[extra["비목"]] += value
try: # 덧줄도 값 빈 품목·빈 줄을 부르면 그 줄만 비움
value, why = evaluate(parse(extra["식"]), env, master, depth), None
env[extra["이름"]] = value
except FormulaError as e:
gone = next((n for n in blank if n in extra["식"]), None)
value, why = None, f"「{gone}」 줄 비어 못 셈 — {blank[gone]}" if gone else str(e)
blank[extra["이름"]] = why
sums[extra["비목"]] += value or 0
lines.append(
{
"이름": extra["이름"],
@@ -542,6 +554,7 @@ def run(master: Master, ref: str, given: dict, depth: int = 0) -> dict:
"단가": value,
"금액": value,
"비목": {extra["비목"]: value},
**({"까닭": why} if why else {}),
}
)
sums = {k: cut(row, v, "계") for k, v in sums.items()} # 비목 합마다 끊고 계는 그 합