feat(M01): 흐름 로직 옛 엔진 한 곳 갈래 · 인력/기계/유가 조건 찾기 · 표 수 식 글 · 재료 키 찾기(PLAN 36-4 덧)
- master_formula.run 에 FLOW_RUN 자리 — Store_Flow 가 꽂음 · 조합 · 프로젝트 단가 · 읽는 식 · 옛 식의 로직(…) 도 흐름 로직이면 흐름 셈(단가는 마스터 · 프로젝트 사본 단가는 아직) - 흐름 → 옛 → 흐름 돌고 도는 부름 막음 · /logic/auto 흐름 로직 = 설계 예시값 - 조건 찾기 요소 인력 · 기계 · 유가전력(group_find · 이름 · 키 · 원문번호 같음/포함 · 기본 키 대체) - 표 값 칸 수 식 글(「(0.05)」 「1/1.30」) = 옛 엔진 파서로 수 · 재료 조건 찾기 field 키(자재 키 · 품번) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KmELpdXFDRZHhfbGuRhSmL
This commit is contained in:
@@ -132,6 +132,15 @@ def _element_value(
|
||||
f"「{box.get('name')} · {el.get('name')}」 {got['reason']} — 기본 품목으로 셈"
|
||||
)
|
||||
got = dv.slot_price({"mode": "fixed", "ref": el["default"]})
|
||||
elif group in dv.GROUP_RULE and mode == "rule": # 인력 · 기계 · 유가전력 조건 찾기
|
||||
rule = el.get("rule") or {}
|
||||
src = rule.get("from") or {}
|
||||
slot = {"field": rule.get("field"), "op": rule.get("op"), "scope": el.get("scope"),
|
||||
"ref": el.get("ref"), "unit": el.get("unit")} # fmt: skip
|
||||
got = dv.group_find(group, slot, ports.get(src.get("box"), {}).get(src.get("name")))
|
||||
if not got["ok"] and el.get("default"):
|
||||
errors.append(f"「{box.get('name')} · {el.get('name')}」 {got['reason']} — 기본으로 셈")
|
||||
got = dv.one_value({"group": group, "ref": el["default"], "unit": el.get("unit")})
|
||||
elif given is not None:
|
||||
return given
|
||||
else:
|
||||
|
||||
@@ -158,11 +158,11 @@ def _table(key: str, at: Any = None) -> dict:
|
||||
if not isinstance(at, int) or not 0 <= at < len(lines):
|
||||
return {"ok": False, "reason": f"「{table.get('이름')}」 에 {at} 번째 줄 없음"}
|
||||
lines = [lines[at]]
|
||||
rows = []
|
||||
rows, worth = [], set(table.get("값칸") or {})
|
||||
for r in lines:
|
||||
line = []
|
||||
for n in names:
|
||||
v = r.get(n)
|
||||
v = _arith(r.get(n)) if n in worth else r.get(n)
|
||||
if n in split:
|
||||
line += list(v) if isinstance(v, list) else [v, v]
|
||||
else:
|
||||
@@ -171,6 +171,16 @@ def _table(key: str, at: Any = None) -> dict:
|
||||
return {"ok": True, "rows": [head, *rows], "name": table.get("이름"), "unit": table.get("기준")}
|
||||
|
||||
|
||||
def _arith(v: Any) -> Any:
|
||||
"""값 칸 수 식 글(「(0.05)」 「1/1.30」) — 옛 셈처럼 수로(사칙연산 · 괄호만 · 옛 엔진 파서)."""
|
||||
if not isinstance(v, str) or not cm.mf._ARITH.fullmatch(v):
|
||||
return v
|
||||
try:
|
||||
return cm.mf.evaluate(cm.mf.parse(v), {}, None, 0)
|
||||
except (cm.mf.FormulaError, ArithmeticError):
|
||||
return v
|
||||
|
||||
|
||||
def _user(uid: str, local: list[dict]) -> dict:
|
||||
"""사용자 요소 값 — local = 다이어그램 안 것 + 공용 목록(부르는 쪽이 모음)."""
|
||||
one = next((r for r in local if r.get("id") == uid), None)
|
||||
@@ -336,7 +346,7 @@ def materials(q: str, sub: str, detail: str, page: int, size: int) -> dict:
|
||||
return {"total": len(hits), "page": page, "size": size, "items": [_brief_row(r) for r in part]}
|
||||
|
||||
|
||||
FIELDS_RULE, OPS_RULE = ("규격", "이름"), ("포함", "같음") # Model CfMaterialRule
|
||||
FIELDS_RULE, OPS_RULE = ("규격", "이름", "키"), ("포함", "같음") # Model CfMaterialRule
|
||||
|
||||
|
||||
def slot_price(slot: dict, value: Any = None, pick: str | None = None) -> dict:
|
||||
@@ -368,10 +378,13 @@ def slot_price(slot: dict, value: Any = None, pick: str | None = None) -> dict:
|
||||
want = _squash(value)
|
||||
sub, detail = str(scope.get("구분") or ""), str(scope.get("상세구분") or "")
|
||||
rows = _found(sub, detail, scope.get("찾는말") or "")
|
||||
if op == "같음":
|
||||
hits = [r for r in rows if _squash(r.get(field)) == want]
|
||||
else:
|
||||
hits = [r for r in rows if want in _squash(r.get(field))]
|
||||
numbers = _numbers() if field == "키" else {} # 키 = 자재 키 또는 품번(원문번호)
|
||||
|
||||
def pool(r: dict) -> list[str]:
|
||||
got = [r.get("키"), numbers.get(r.get("키"))] if field == "키" else [r.get(field)]
|
||||
return [_squash(x) for x in got if x]
|
||||
|
||||
hits = [r for r in rows if any(want == x if op == "같음" else want in x for x in pool(r))]
|
||||
if not hits:
|
||||
where = " · ".join(x for x in (sub, detail) if x) or "전체"
|
||||
return {"ok": False, "reason": f"맞는 재료 없음 — {where} 에서 {field}에 「{value}」 {op}"}
|
||||
@@ -386,6 +399,59 @@ def slot_price(slot: dict, value: Any = None, pick: str | None = None) -> dict:
|
||||
return {**got, "ref": hits[0].get("키"), "name": hits[0].get("이름")}
|
||||
|
||||
|
||||
GROUP_RULE = (
|
||||
"인력",
|
||||
"기계",
|
||||
"유가전력",
|
||||
) # 조건 찾기를 재료 밖에서도(옛 LB:{직종} · EQ:{기계} · MO:…)
|
||||
|
||||
|
||||
def _group_rows(group: str) -> list[dict]:
|
||||
"""그룹 줄 — 인력 · 기계 = 그 파일 · 유가전력 = 재료 안 MO 키."""
|
||||
files = store.loaded()[0]
|
||||
want = "재료" if group == "유가전력" else group
|
||||
return [
|
||||
r
|
||||
for d in files.values()
|
||||
if d.get("그룹") == want
|
||||
for r in d.get(store.items_key(d)) or []
|
||||
if group != "유가전력" or str(r.get("키")).startswith("MO")
|
||||
]
|
||||
|
||||
|
||||
def group_find(group: str, slot: dict, value: Any) -> dict:
|
||||
"""인력 · 기계 · 유가전력 조건 찾기 — 알약 값이 이름 · 키(원문번호) 와 같음(포함)인 줄 하나.
|
||||
|
||||
slot = {field?: 이름 | 키, op?: 같음 | 포함, scope?: {찾는말?}, ref?: 「키#칸」 = 칸 이어 씀}
|
||||
· field 가 없으면 키(원문번호) 또는 이름이 같은 줄 · 하나면 그 값(`one_value`).
|
||||
"""
|
||||
if value in (None, ""):
|
||||
return {"ok": False, "reason": "찾을 값이 비어 있음 — 이어진 알약을 확인해 주세요"}
|
||||
field, op, want = slot.get("field"), slot.get("op") or "같음", _squash(value)
|
||||
words = str((slot.get("scope") or {}).get("찾는말") or "").split()
|
||||
|
||||
def hit(r: dict) -> bool:
|
||||
hay = _squash(f"{r.get('이름')}{r.get('규격') or ''}")
|
||||
if not all(_squash(w) in hay for w in words):
|
||||
return False
|
||||
keys = [_squash(r.get("키")), _squash(r.get("원문번호"))]
|
||||
names = [_squash(r.get("이름"))]
|
||||
pool = keys if field == "키" else names if field == "이름" else keys + names
|
||||
return any(want == x if op == "같음" else want in x for x in pool if x)
|
||||
|
||||
hits = [r for r in _group_rows(group) if hit(r)]
|
||||
if len(hits) != 1:
|
||||
if not hits:
|
||||
return {"ok": False, "reason": f"맞는 {group} 없음 — 「{value}」"}
|
||||
names = ", ".join(f"{r.get('이름')} {r.get('규격') or ''}".strip() for r in hits[:5])
|
||||
return {"ok": False, "reason": f"맞는 {group} {len(hits)}개 — 좁혀 주세요({names} …)"}
|
||||
ref = str(hits[0].get("키"))
|
||||
suffix = str(slot.get("ref") or "").partition(MACHINE_SEP)[2]
|
||||
got = one_value({"group": group, "ref": f"{ref}{MACHINE_SEP}{suffix}" if suffix else ref,
|
||||
"unit": slot.get("unit") or ""}) # fmt: skip
|
||||
return {**got, "ref": ref, "name": hits[0].get("이름")}
|
||||
|
||||
|
||||
# ── 단위 · 금액 표준 표 ────────────────────────────────────────────────
|
||||
STANDARDS = {"unit": "단위표준.json", "money": "금액단위표준.json"}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user