merge: origin/sub_laptop_1 받기 — 노임 준용 + 품셈재료 연결 양쪽 살림

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WRFJmmhPi4exAzHgPZHMT
This commit is contained in:
2026-09-19 23:42:11 +09:00
co-authored by Claude Sonnet 5
49 changed files with 25264 additions and 24935 deletions
+19 -10
View File
@@ -321,7 +321,7 @@ def evaluate(node, env: dict, master: Master, depth: int = 0):
raise FormulaError(f"모르는 이름 「{node[1]}")
return env[node[1]]
if kind == "ref":
value = master.value(fill(node[1], env))[0]
value = element(master, fill(node[1], env), env)[0]
if value is None:
raise FormulaError(f"{node[1]}」 값 없음 — 관리자가 채울 값")
if isinstance(value, list):
@@ -426,8 +426,8 @@ 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("호표", []):
alias = None
qty = evaluate(parse(item["수량"]), env, master, depth)
source = None
if item["종류"] == "로직":
bundle = evaluate(parse(item["요소"]), env, master, depth)
price = bundle[""]
@@ -437,15 +437,17 @@ def run(master: Master, ref: str, given: dict, depth: int = 0) -> dict:
else {item["비목"]: qty * price}
)
else:
node = parse(item["요소"])
if node[0] == "field": # 값 묶음 요소의 칸 — 「재료:시중물가:M….채택」
price = master.get(fill(node[1][1], env))[""][node[2]]
else:
price, alias = master.value(fill(item["요소"], env))
ref = fill(item["요소"], env)
try:
price, source = element(master, ref, env)
except FormulaError:
if qty != 0:
raise
price = None
if price is None and qty == 0:
price = Decimal(0) # 안 쓰는 재료 줄 — 값이 비어도 금액 0
if price is None:
raise FormulaError(f"{fill(item['요소'], env)}」 값 없음 — 관리자가 채울 값")
raise FormulaError(f"{ref}」 값 없음 — 관리자가 채울 값")
split = {item["비목"]: qty * price}
for k, v in split.items():
sums[k] += v
@@ -457,7 +459,7 @@ def run(master: Master, ref: str, given: dict, depth: int = 0) -> dict:
"단가": price,
"금액": qty * price,
"비목": split,
**({"출처": f"준용: {alias}"} if alias else {}),
**({"출처": source} if source else {}),
}
)
for extra in row.get("덧줄", []):
@@ -523,9 +525,12 @@ def _check_node(row, node, known: set, master: Master, where: str, out: list, ca
refs = _expand(row, part[1], master) or []
for ref in refs:
try:
master.get(ref)
link = master.get(ref).get("연결")
except FormulaError as e:
out.append(f"{where} · {e}")
continue
if isinstance(link, dict) and link.get("지역") == "입력" and REGION not in known:
out.append(f"{where} · 「{ref}」 는 입력 「{REGION}」 필요")
if kind == "field" and part[1][0] == "ref":
for ref in _expand(row, part[1][1], master) or []:
try:
@@ -633,3 +638,7 @@ def find_loops(graph: dict[str, set]) -> list[list[str]]:
if node not in state:
visit(node, [node])
return loops
# 품셈재료 단가 — 서로 부르는 모듈이라 끝에서 읽음
from master_material import LINKED, REGION, element # noqa: E402, F401