feat(m02-df): 36-0b 선 조건 여러 줄(그리고 · 또는) · 값 목록

- CfRule gate conds[{from · op · value}] · join 그리고/또는 · 옛 한 줄 꼴(from · op · value)도 읽음 — 옛 칸은 첫 조건과 같게 둠(makeGate)
- gateConds · makeGate · valueChoices(변수 고르기 · 재료 규격 · 표 칸 값 · 수 · 함수 결과는 null)
- 규칙 → 받는 칸 엑셀 식 IF(AND/OR(...),A1,NA()) · 선 이름표 「지반 = 토사 그리고 높이 ≥ 3 일 때만」 · 겹침 · 안 맞음은 조건 알약 묶음끼리
- 조건 값 열쇠 「상자|알약」 먼저(이름 겹침 막음) · M02_DF_Compute.py gate 같은 뜻(서버 = 화면 시험)
- 시험 test_m02_df_model 30개

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VBToTCGk5dqDKeXCEP3vhk
This commit is contained in:
2026-10-01 23:27:04 +09:00
co-authored by Claude Opus 5.5
parent 2c134b440f
commit 2e99091673
5 changed files with 249 additions and 26 deletions
+30 -11
View File
@@ -323,7 +323,7 @@ def _lookup(rule: dict, block: Any, conds: dict) -> tuple[Any, str | None]:
return None, "오류"
tests = []
for c in rule.get("conds") or []:
col, want = str(c.get("col")), conds.get((c.get("from") or {}).get("name"))
col, want = str(c.get("col")), _cond_of(conds, c.get("from") or {})
if col in head:
i = head.index(col)
tests.append(lambda row, i=i, want=want: _same(row[i], want))
@@ -362,12 +362,13 @@ def apply_rule(rule: dict, value: Any, conds: dict) -> tuple[Any, str | None]:
if kind == "lookup":
return _lookup(rule, value, conds)
if kind == "gate":
src = (rule.get("from") or {}).get("name")
return (
(value, None)
if _compare(conds.get(src), rule.get("op"), rule.get("value"))
else (None, "통과 안 됨")
)
rows, join = _gate_conds(rule)
hits = [
_compare(_cond_of(conds, c.get("from") or {}), c.get("op"), c.get("value"))
for c in rows
]
ok = any(hits) if join == "또는" else all(hits)
return (value, None) if ok else (None, "통과 안 됨")
n = _num_of(value)
if n is None:
return (None, None) if value is None else (None, "오류")
@@ -387,7 +388,23 @@ def _sources(rule: dict | None) -> list[dict]:
return []
if rule.get("kind") == "lookup":
return [c.get("from") or {} for c in rule.get("conds") or []]
return [rule.get("from") or {}] if rule.get("kind") == "gate" else []
if rule.get("kind") == "gate":
return [c.get("from") or {} for c in _gate_conds(rule)[0]]
return []
def _gate_conds(rule: dict) -> tuple[list[dict], str]:
"""조건 통과 줄 · 잇는 말 — conds · join · 옛 한 줄 꼴은 줄 하나(Model gateConds)."""
rows = rule.get("conds") or [
{"from": rule.get("from") or {}, "op": rule.get("op"), "value": rule.get("value")}
]
return rows, rule.get("join") or "그리고"
def _cond_of(conds: dict, port: dict) -> Any:
"""조건 알약 값 — 열쇠 「상자|알약」 이 먼저 · 없으면 알약 이름(Rule condOf)."""
key = f"{port.get('box')}|{port.get('name')}"
return conds[key] if key in conds else conds.get(port.get("name"))
# ── 계산(`M02_DF_Eval.evalLogic` 와 같은 차례) ─────────────────────────────
@@ -462,9 +479,11 @@ def run(lg: dict, values: dict, load: Loader, local: list[dict], stack: list | N
return v
if v is None and rule.get("kind") in ("scale", "round"):
return None
conds = {
p.get("name"): ports.get(p.get("box"), {}).get(p.get("name")) for p in _sources(rule)
}
conds = {}
for p in _sources(rule):
got_v = ports.get(p.get("box"), {}).get(p.get("name"))
conds[p.get("name")] = got_v
conds[f"{p.get('box')}|{p.get('name')}"] = got_v
got, why = apply_rule(rule, v, conds)
if why:
blocked.add(ln["to"].get("box"))