feat(master_data): 로직 재료 줄 고르기 조건 — 구분·상세구분·규격 후보 · 대표 줄 · 후보 목록 API

- `_틀.md` 7장에 재료 고르기 문법 — 호표 `요소` 를 조건 묶음으로 · 규격 낱말 조건 · 대표 줄 지정
- 엔진 `candidates`·`pick`·`check_pick` — 조건 안 후보 목록(화면·테스트 컨테이너 공용) · 시험 계산은 대표 줄, 없으면 값 있는 첫 줄(자재지역 우선)
- 로직 검사에 고르기 조건 검사 — 칸 이름 · 자재품목에 없는 구분·상세구분 · 조건 밖 대표 · 후보 0
- M01 서버 `GET /api/m01/materials` — 구분·상세구분·규격·자재지역으로 좁힌 후보와 기본 줄
- 옛 방식 재료 줄(`MP…`·`MT…` 키)은 그대로 돎

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JgUhN55Z3BCYhUJTjwTNfj
This commit is contained in:
2026-09-20 21:33:34 +09:00
co-authored by Claude Opus 5
parent 565833bcb8
commit 999f3363db
8 changed files with 300 additions and 7 deletions
@@ -260,8 +260,10 @@ class Master:
raise FormulaError(f"없는 요소 「{ref}")
return row
def label(self, ref: str) -> str:
"""알림 글 — 키 옆에 이름."""
def label(self, ref) -> str:
"""알림 글 — 키 옆에 이름 · 고르기 조건은 조건 글."""
if isinstance(ref, dict):
return "재료 고르기 " + " · ".join(str(v) for v in ref.values() if v)
try:
row = self.get(ref)
except FormulaError:
@@ -476,7 +478,8 @@ def run(master: Master, ref: str, given: dict, depth: int = 0) -> dict:
else {item["비목"]: qty * price}
)
else:
ref = fill(item["요소"], env)
# 고르기 조건 묶음은 그대로 넘김 — `element` 가 대표 줄·첫 줄로 풂
ref = item["요소"] if isinstance(item["요소"], dict) else fill(item["요소"], env)
try:
price, source = element(master, ref, env)
except FormulaError:
@@ -654,6 +657,11 @@ def check_logic(master: Master, row: dict) -> tuple[list[str], set]:
item.get("종류") != "로직" and "비목" not in item
):
out.append(f"{where} · 비목 「{item.get('비목')}")
if isinstance(item["요소"], dict): # 재료 고르기 조건
if item.get("종류") != "재료":
out.append(f"{where} · 고르기 조건은 종류 「재료」 만")
out += check_pick(master, where + " 요소", item["요소"])
continue
one(item["요소"], where + " 요소", known)
group = mk.GROUP_OF.get(mk.id_of(str(item["요소"])))
if item.get("종류") != "로직" and group != item.get("종류"):
@@ -687,4 +695,14 @@ def find_loops(graph: dict[str, set]) -> list[list[str]]:
# 품셈재료 단가 — 서로 부르는 모듈이라 끝에서 읽음
from master_material import LINKED, REGION, SLOTS, TARIFF, element # noqa: E402, F401
from master_material import ( # noqa: E402, F401
LINKED,
PICK_KEYS,
REGION,
SLOTS,
TARIFF,
candidates,
check_pick,
element,
pick,
)