feat(master_data): 관경이 든 재료 참조를 고르기 조건으로 · 조건 값의 입력 자리 채움

- 엔진 — 고르기 조건 값의 `{입력}` 을 계산 때 채움(관경) · 검사에서는 그 칸을 뺌(`static`)
- 흄관 · 파형강관 두 줄을 고르기 조건으로 바꿈
- VR관(자재품목 단위 「본」 · 호표 「m」 환산 필요) · 고무링 · 커플링밴드 셋은 옛 참조 유지 — 조건만으로 줄이 안 좁혀짐
- 시험 하나 더함

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 23:10:42 +09:00
co-authored by Claude Opus 5
parent e2ea63e844
commit cdbfbb7e3f
4 changed files with 35 additions and 4 deletions
@@ -479,7 +479,12 @@ def run(master: Master, ref: str, given: dict, depth: int = 0) -> dict:
)
else:
# 고르기 조건 묶음은 그대로 넘김 — `element` 가 대표 줄·첫 줄로 풂
ref = item["요소"] if isinstance(item["요소"], dict) else fill(item["요소"], env)
# 조건 값의 `{입력}`(관경 등)도 채움 — 규격이 입력 따라 바뀌는 관 줄
ref = (
{k: fill(v, env) if isinstance(v, str) else v for k, v in item["요소"].items()}
if isinstance(item["요소"], dict)
else fill(item["요소"], env)
)
try:
price, source = element(master, ref, env, item.get("단위"))
except FormulaError:
@@ -704,6 +709,7 @@ from master_material import ( # noqa: E402, F401
candidates,
check_pick,
check_unit,
static,
cond_text,
element,
pick,
@@ -106,6 +106,11 @@ def _candidates(master: mf.Master, ref: str, row: dict, env: dict) -> list[str]:
# ── 재료 고르기 ────────────────────────────────────────────────────────
def static(cond: dict) -> dict:
"""검사용 조건 — 값에 `{입력}` 이 든 칸은 뺌(계산 때 채워짐)."""
return {k: v for k, v in cond.items() if not (isinstance(v, str) and "{" in v)}
def cond_text(cond: dict) -> str:
"""고르기 조건 한 글자 열쇠 — 「구분|상세구분|규격|대표」 · 화면 단가 미리보기가 줄을 찾는 이름."""
return "|".join(str(cond.get(k) or "") for k in PICK_KEYS)
@@ -147,6 +152,7 @@ def pick(master: mf.Master, cond: dict, env: dict, unit: str | None = None) -> s
def check_unit(master: mf.Master, where: str, cond: dict, unit: str | None) -> list[str]:
"""단위 경고 — 호표 줄·품셈재료 줄 단위와 맞는 후보가 없거나 대표 줄 단위가 다름.
틀린 데이터가 아니라 좁혀야 할 조건이라 `check_pick` 과 따로 봄(`check_master 단위`)."""
cond = static(cond)
rows = master.index.get(MARKET, {})
rep = str(cond.get("대표") or "")
if not unit or set(cond) - set(PICK_KEYS) or not cond.get("구분"):
@@ -163,9 +169,11 @@ def check_unit(master: mf.Master, where: str, cond: dict, unit: str | None) -> l
def check_pick(master: mf.Master, where: str, cond: dict, unit: str | None = None) -> list[str]:
"""고르기 조건 검사 — 칸 이름 · 구분·상세구분이 자재품목에 있는지 · 대표 줄 · 후보 0."""
"""고르기 조건 검사 — 칸 이름 · 구분·상세구분이 자재품목에 있는지 · 대표 줄 · 후보 0.
값에 `{입력}` 이 든 칸(관경 등)은 계산 때 채워지므로 검사에서 뺌."""
if set(cond) - set(PICK_KEYS) or not cond.get("구분"):
return [f"{where} · 재료 고르기 조건 모양 「{cond}」 — {' · '.join(PICK_KEYS)}"]
cond = static(cond)
out, rows = [], master.index.get(MARKET, {})
for slot in ("구분", "상세구분"):
if cond.get(slot) and not any(r.get(slot) == cond[slot] for r in rows.values()):
@@ -1989,7 +1989,11 @@
},
{
"종류": "재료",
"요소": "MP:흄관 ∅{관경}㎜",
"요소": {
"구분": "관·이음관",
"상세구분": "흄관",
"규격": "{관경}"
},
"이름": "흄관",
"단위": "m",
"수량": "찾기(QF000371, 구분='흄관', 관경=관경).적용",
@@ -2133,7 +2137,11 @@
"호표": [
{
"종류": "재료",
"요소": "MP:파형강관 ∅{관경}㎜",
"요소": {
"구분": "관·이음관",
"상세구분": "파형강관",
"규격": "{관경}"
},
"이름": "파형강관",
"단위": "m",
"수량": "찾기(QF000373, 구분='파형강관', 관경=관경).적용",
@@ -952,3 +952,12 @@ def test_단위_경고는_로직_검사와_따로():
assert cm.check_logics(files, whole) == []
warned = cm.check_units(files, whole)
assert warned and all("단위" in x for x in warned)
def test_고르기_조건의_입력_자리_채움(whole):
"""관경 같은 입력이 규격에 든 고르기 조건 — 계산 때 채워지고, 검사에서는 그 칸을 뺌."""
cond = {"구분": "관·이음관", "상세구분": "파형강관", "규격": "{관경}"}
assert mm.static(cond) == {"구분": "관·이음관", "상세구분": "파형강관"}
assert mf.check_pick(whole, "시험", cond) == []
채움 = {k: mf.fill(v, {"관경": Decimal(800)}) for k, v in cond.items()}
assert mm.candidates(whole, 채움, None, "m")