- 수식 응답 새 칸 정의 — 만약 탄 갈래 표지 걸림 그대로 · 같은 기호 한 줄 · 연결 사람 말 · 값 · 단위 · DB 열쇠 없음 - 고침 할일 연결(입력 · 표 · 요소 · 로직 · 상수) · 모르는 기호 까닭 · 후보 길 M01 · M02 equation/links - 낡은 시험 test_796_1 「모르는 이름」 → 「모르는 기호」(까닭 문구 바뀜) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
126 lines
5.4 KiB
Python
126 lines
5.4 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""수식 변수 연결 지정(PLAN 800-3 · 요구 660-5) — 새 기호가 무엇과 이어지는지 고르는 서버 몫.
|
|
|
|
고침 `{할일: 연결, 열쇠, 갈래, 이름?, 기호?, 위?, 아래?, 뒤?, …}` 을 기존 `더함` 한 건으로 풂(`plan`):
|
|
입력 {기본값?, 단위?} → 입력 변수(옵션 칸에 생김)
|
|
표 {표, 칸, 조건: {열: 식글}} → 중간 줄 「찾기(표, 열=식…).칸」
|
|
요소 {요소: 키} → 중간 줄 「키」(인력 · 재료 · 기계 단가)
|
|
로직 {로직: 키, 인자?: {칸: 식}} → 중간 줄 「로직(키, 칸=식…)」
|
|
상수 {값: 수} → 중간 줄 수
|
|
후보 목록 = `candidates`(표 · 요소 · 로직) — 읽기만 · 값 · 열쇠는 이름 옆 보관용(화면은 이름만 보임).
|
|
셈 · 검사는 `master_equation_edit.apply` 가 한 벌로(여기는 글만 짓고 막을 것만 막음).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from decimal import Decimal, InvalidOperation
|
|
|
|
LIMIT = 30
|
|
KINDS = ("입력", "표", "요소", "로직", "상수")
|
|
ELEMS = {"인력": ("LB",), "재료": ("MT", "MO"), "기계": ("EQ",)}
|
|
TABLES = ("QC", "QF")
|
|
_KEY = re.compile(r"[A-Z]{2}\d{6}")
|
|
_COL = re.compile(r"^[^\W\d][\w]*$")
|
|
|
|
|
|
def _q(word: str) -> str:
|
|
return word if _COL.match(word) else f"'{word}'"
|
|
|
|
|
|
def _row(master, key: str):
|
|
try:
|
|
return master.get(key)
|
|
except Exception: # noqa: BLE001 — 마스터 없는 열쇠 = 어떤 오류든 「없음」
|
|
return None
|
|
|
|
|
|
def plan(master, ed: dict) -> tuple[dict | None, str]:
|
|
"""연결 고침 → (더함 고침, 까닭) — 까닭이 있으면 더함 없음."""
|
|
kind = ed.get("갈래")
|
|
add = {
|
|
"할일": "더함",
|
|
**{k: ed[k] for k in ("열쇠", "이름", "기호", "위", "아래", "단위", "뒤") if k in ed},
|
|
}
|
|
if kind not in KINDS:
|
|
return None, f"연결 갈래 「{kind}」 — {' · '.join(KINDS)}"
|
|
if kind == "입력":
|
|
add |= {"갈래": "입력"} | (
|
|
{"기본값": ed["기본값"]} if ed.get("기본값") not in (None, "") else {}
|
|
)
|
|
return add, ""
|
|
add["갈래"] = "중간"
|
|
if kind == "상수":
|
|
try:
|
|
add["식"] = format(Decimal(str(ed.get("값"))).normalize(), "f")
|
|
except (InvalidOperation, ValueError):
|
|
return None, "상수는 수로 적어야 함"
|
|
elif kind == "요소":
|
|
key = str(ed.get("요소") or "")
|
|
if (
|
|
not _KEY.fullmatch(key)
|
|
or key[:2] not in sum(ELEMS.values(), ())
|
|
or not _row(master, key)
|
|
):
|
|
return None, "요소를 골라야 함(인력 · 재료 · 기계)"
|
|
add["식"] = key
|
|
elif kind == "로직":
|
|
key = str(ed.get("로직") or "")
|
|
if not _KEY.fullmatch(key) or not key.startswith("G") or not _row(master, key):
|
|
return None, "부를 로직을 골라야 함"
|
|
args = "".join(
|
|
f", {_q(k)}={v}" for k, v in (ed.get("인자") or {}).items() if str(v).strip()
|
|
)
|
|
add["식"] = f"로직({key}{args})"
|
|
else:
|
|
key, col = str(ed.get("표") or ""), str(ed.get("칸") or "")
|
|
table = _row(master, key) if _KEY.fullmatch(key) else None
|
|
if not table or "조건" not in table or col not in (table.get("값칸") or {}):
|
|
return None, "표와 칸을 골라야 함"
|
|
given = ed.get("조건") or {}
|
|
miss = [c for c in table["조건"] if not str(given.get(c) or "").strip()]
|
|
if miss:
|
|
return None, f"표 조건을 채워야 함 — {' · '.join(miss)}"
|
|
cond = "".join(f", {_q(c)}={given[c]}" for c in table["조건"])
|
|
add["식"] = f"찾기({key}{cond}).{_q(col)}"
|
|
return add, ""
|
|
|
|
|
|
def _hit(text: object, words: list[str]) -> bool:
|
|
low = str(text or "").lower()
|
|
return all(w in low for w in words)
|
|
|
|
|
|
def candidates(master, ask: dict) -> dict:
|
|
"""후보 {갈래: 표 | 요소 | 로직, 검색, 분류?(요소)} → {후보: [{키, 이름, …}]} — 최대 `LIMIT` 건."""
|
|
kind, words = ask.get("갈래"), str(ask.get("검색") or "").lower().split()
|
|
out: list[dict] = []
|
|
if kind == "표":
|
|
pools = [(k, r) for p in TABLES for k, r in master.index.get(p, {}).items() if "조건" in r]
|
|
for key, r in pools:
|
|
if _hit(r.get("이름"), words):
|
|
out.append(
|
|
{
|
|
"키": key,
|
|
"이름": r.get("이름"),
|
|
"조건": list(r["조건"]),
|
|
"칸": list(r.get("값칸") or {}),
|
|
}
|
|
)
|
|
elif kind == "요소":
|
|
for p in ELEMS.get(str(ask.get("분류") or "인력"), ()):
|
|
for key, r in master.index.get(p, {}).items():
|
|
if (words or p != "MT") and _hit(f"{r.get('이름')} {r.get('규격') or ''}", words):
|
|
out.append({"키": key, "이름": r.get("이름"), "규격": r.get("규격") or ""})
|
|
elif kind == "로직":
|
|
for key, r in master.index.get("GC", {}).items():
|
|
if words and _hit(r.get("이름"), words):
|
|
out.append(
|
|
{
|
|
"키": key,
|
|
"이름": r.get("이름"),
|
|
"입력": [s["이름"] for s in r.get("입력") or []],
|
|
}
|
|
)
|
|
return {"후보": out[:LIMIT], "더있음": len(out) > LIMIT}
|