fix(M01): 식 해석기 흠 셋 — 단위 없는 맨 수 · 「식 = 이름」 꼴 · 비목 안 고른 덧줄
· 단위 칸에 「없음」(맨수 · - · % · 빈 글)을 골라 무차원으로 둘 수 있게 함 — 횟수·배율을 「회」 로 두어 「원/회」 가 남던 덧셈 검사 걸림을 풂 · 같은 단위끼리 나누면 맨 수가 되는 것도 맞음 · 줄을 「이름 = 식」 과 「식 = 이름」(A * B = C) 둘 다 받음 — 한쪽이 값 이름 하나면 그쪽이 이름 · 양쪽이 다 식이면 그 줄의 등호 자리를 짚음 · 괄호 안 등호와 비교 기호는 안 가름 · 비목 안 고른 덧줄을 「못 옮김」 으로 막지 않고 그 값의 「정할것」 에 「비목」 을 얹어 짚어 줌 · 돈 아닌 로직은 덧줄이 없어 안 물음 · 계약 _화면_계약.md 8장(다른 창이 7장을 먼저 씀) · 시험 test_m01_draft.py 18개 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V1MKKZKpUHTPKb513FneU8
This commit is contained in:
@@ -278,14 +278,14 @@ def logic_draft(
|
||||
save: bool = False,
|
||||
owner: str = OWNERS[0],
|
||||
) -> dict:
|
||||
"""줄로 적은 식 → 변수 · 검사(`_화면_계약.md` 7장).
|
||||
"""줄로 적은 식 → 변수 · 검사(`_화면_계약.md` 8장).
|
||||
|
||||
`head`(이름 · 결과단위 …)가 오면 로직 한 줄까지 미리 보이고, `save` 면 다 정해진 때만
|
||||
자체 로직으로 저장(키 GX 는 서버) · 덜 정했거나 걸리면 422 로 아무것도 안 씀.
|
||||
"""
|
||||
import master_draft as draft # store 가 scripts 를 길에 올린 뒤라야 함
|
||||
|
||||
got = draft.read(text, decided)
|
||||
got = draft.read(text, decided, head)
|
||||
if head is None:
|
||||
return {**got, "logic": None}
|
||||
row, bad = draft.옮기기(text, decided, head)
|
||||
|
||||
@@ -140,16 +140,18 @@
|
||||
|
||||
`POST /logic/draft` 받음 `{text, decided, head?, save?, owner?}` — 줄로 적은 식을 받아 변수를 뽑고 검사한 뒤 로직 한 줄로 옮김(해석기 `scripts/master_draft.py`).
|
||||
|
||||
- `text` = 줄마다 「이름 = 식」 · 빈 줄과 `#` 는 건너뜀 · **마지막 줄 왼쪽이 결과 변수**. 식 글자는 `_틀.md` 8장 범위만.
|
||||
- `text` = 줄마다 「이름 = 식」 **또는 「식 = 이름」**(`A * B = C`) · 한쪽이 값 이름 하나면 그쪽이 이름 · 양쪽이 다 식이면 그 줄의 등호 자리를 짚어 `못읽음`. 괄호 안 등호(`찾기(…, 뒷길이=높이)`)와 비교 기호(`==` `<=` `>=` `!=`)는 안 가름. 빈 줄과 `#` 는 건너뜀 · **마지막 줄의 값 이름이 결과 변수**. 식 글자는 `_틀.md` 8장 범위만.
|
||||
- `decided` = 변수마다 정한 것 `{이름: {무엇, 단위, 비목, 요소 | 찾기 | 값, 설명 · 고르기 · 범위}}`.
|
||||
- `무엇` — `마스터요소`(키 또는 재료 고르기 조건을 `요소` 에 · `비목` 도 정함) · `설계입력`(입력 칸이 됨) · `고정값`(`값` 을 중간으로) · `표찾기`(`찾기` = `{표, 값칸, 조건}` → `찾기(…).값칸` 을 중간으로).
|
||||
- `단위` — 마스터요소는 그 줄의 단위(「인」 · 「포」)를 적음. 식에서는 단가라 `원/단위` 로 셈.
|
||||
- **단위 없는 맨 수**(횟수 · 배율 · 비율)는 단위 칸에 `없음`(또는 `맨수` · `-` · `%` · 빈 글)을 골라 무차원으로 둠 — 「전용 4회」 를 「회」 로 두면 `원/회` 가 남아 덧셈 검사에 걸림. 칸 자체가 없으면 아직 안 정한 것(`정할것` 에 `단위`).
|
||||
- 비목 합 이름(`노무비` · `재료비` · `경비` · `계`)은 정하지 않음 — 덧줄에서만 쓰는 이름.
|
||||
- `head` = 로직 머리(`이름` · `결과단위` · `원문번호` · `출처` · `비고` · `끝수`). 없으면 검사만 함.
|
||||
- 줌 `{줄, 변수, 결과, 문제, 다됨, logic}` — `logic` = 옮긴 로직 한 줄(`head` 가 있을 때만 · 저장 전).
|
||||
- `변수` = `{이름, 처음, 갈래, 정의줄, 쓰임, 무엇, 단위, 비목, 정할것}` · `갈래` = `정의됨`(왼쪽에 나옴) · `미정`(쓰기만 함) · `비목합` · `정할것` = 아직 안 정한 칸 이름들.
|
||||
- `문제` = `{줄, 자리, 갈래, 말}` · `자리` = 그 줄의 글자 자리(모르면 null) · `갈래` = `못읽음` · `이름` · `두번정의` · `돌고돎` · `안쓰임`(= 결과가 하나인지) · `단위` · `덜정함` · `항` · `비목합`.
|
||||
- 단위 검사 — 곱셈은 단위를 곱하고 나눗셈은 나눔 · 덧셈·뺄셈과 `만약`·`최소`·`최대`·비교는 단위가 같아야 함. 「m \* m」 과 「㎡」 는 같게 봄. 모르는 단위는 안 따짐.
|
||||
- `변수` = `{이름, 처음, 갈래, 정의줄, 쓰임, 무엇, 단위, 비목, 정할것}` · `갈래` = `정의됨`(식이 있음) · `미정`(쓰기만 함) · `비목합` · `정할것` = 아직 안 정한 칸 이름들. `단위` 는 그 값이 **식에서** 가지는 단위(무차원은 빈 글).
|
||||
- `문제` = `{줄, 자리, 갈래, 말}` · `자리` = 그 줄의 글자 자리(모르면 null) · `갈래` = `못읽음` · `이름` · `두번정의` · `돌고돎` · `안쓰임`(= 결과가 하나인지) · `단위` · `덜정함` · `항` · `비목` · `비목합`.
|
||||
- 단위 검사 — 곱셈은 단위를 곱하고 나눗셈은 나눔(같은 단위끼리 나누면 맨 수) · 덧셈·뺄셈과 `만약`·`최소`·`최대`는 단위가 같아야 함 · 비교는 한쪽이 맨 수면 안 따짐(`높이 <= 3`). 「m \* m」 과 「㎡」 는 같게 봄. 모르는 단위는 안 따짐.
|
||||
- **덧줄이 될 값**(결과 식의 항 가운데 마스터요소가 없는 것)은 막지 않고 그 값의 `정할것` 에 `비목` 을 얹음 — 화면은 「비목을 골라 주세요」 로 보임.
|
||||
- `save` = true 면 `POST /logic/new` 와 같은 자리에 저장(키 `GX` 는 서버 · 소유 `owner`) → 답에 `{file, version, key, logic}` 이 더 옴. 문제가 하나라도 있으면 422 `{detail: {errors, 문제}}` 로 아무것도 안 씀.
|
||||
- 옮기는 법 — 돈 로직(`결과단위` 가 「원」 으로 시작)은 결과 식을 `+` 로 갈라 항마다: 마스터요소 변수 하나면 **호표 줄**(나머지 곱이 `수량`) · 비목만 정한 변수면 **덧줄** · 그 밖은 문제 `항`. 나머지 정의 줄은 `중간`(쓰는 차례대로). 돈 아닌 로직은 호표 없이 `중간` + `결과` 식.
|
||||
- 표 찾기는 글자로 치지 않음 — 변수 하나로 두고 `무엇` 을 `표찾기` 로 정할 때 모달이 `찾기` 묶음을 채움.
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""줄로 적은 식 → 변수 뽑기 · 검사 · 단위 맞춤 · 로직 틀로 옮기기 (`_화면_계약.md` 7장).
|
||||
"""줄로 적은 식 → 변수 뽑기 · 검사 · 단위 맞춤 · 로직 틀로 옮기기 (`_화면_계약.md` 8장).
|
||||
|
||||
수량 = 길이 * 폭
|
||||
자재비 = 수량 * 시멘트
|
||||
|
||||
· 줄마다 「이름 = 식」 · 빈 줄과 `#` 는 건너뜀 · 마지막 줄 왼쪽이 결과 변수
|
||||
· 줄마다 「이름 = 식」 또는 「식 = 이름」 · 빈 줄과 `#` 는 건너뜀 · 마지막 줄의 값 이름이 결과 변수
|
||||
· 식 글자는 `_틀.md` 8장 범위만 — 읽기는 `master_formula` 가 함
|
||||
· 표 찾기는 글자로 치지 않음 — 변수 하나로 두고 「무엇」 을 `표찾기` 로 정할 때 이음
|
||||
· 값은 안 씀 — 검사만 하고 `옮기기` 가 로직 한 줄을 만듦(저장은 `Store_Make.logic_new`)
|
||||
@@ -32,8 +32,9 @@ ALIAS = {
|
||||
"M": ("m", 1),
|
||||
}
|
||||
SQUARED = {2: "㎡", 3: "㎥"}
|
||||
DIMLESS = ("%", "할") # 비율은 단위가 없음 — 「1 + 증가율 / 100」 이 안 걸리게
|
||||
_ASSIGN = re.compile(r"^\s*([\w가-힣]+)\s*=(?!=)\s*(.*)$")
|
||||
# 단위가 없는 맨 수 — 횟수 · 배율 · 비율. 빈 글도 같음(「없음」 을 고른 것과 같게 봄)
|
||||
DIMLESS = ("%", "할", "없음", "맨수", "맨 수", "-")
|
||||
_NAME_ONLY = re.compile(r"^[\w가-힣]+$")
|
||||
|
||||
|
||||
# ── 자리를 적어 두는 읽기 ──────────────────────────────────────────────
|
||||
@@ -224,18 +225,51 @@ def find_text(pick: dict) -> str:
|
||||
|
||||
|
||||
# ── 읽기 · 검사 ───────────────────────────────────────────────────────
|
||||
def _is_name(text: str) -> bool:
|
||||
"""값 이름 하나 — 맨 수는 이름이 아님."""
|
||||
return bool(_NAME_ONLY.fullmatch(text)) and not mf._NUMBER.fullmatch(text)
|
||||
|
||||
|
||||
def _split(raw: str) -> int:
|
||||
"""줄을 가를 등호 자리 — 괄호 안과 비교 기호(`==` `<=` `>=` `!=`)는 건너뜀 · 없으면 -1."""
|
||||
depth = 0
|
||||
for i, ch in enumerate(raw):
|
||||
if ch in "([{":
|
||||
depth += 1
|
||||
elif ch in ")]}":
|
||||
depth -= 1
|
||||
elif ch == "=" and depth == 0:
|
||||
if raw[i - 1 : i] in ("<", ">", "!", "=") or raw[i + 1 : i + 2] == "=":
|
||||
continue
|
||||
return i
|
||||
return -1
|
||||
|
||||
|
||||
def _cut(text: str):
|
||||
"""(줄 번호, 왼쪽 이름, 식, 식이 시작하는 자리) — 빈 줄과 `#` 는 건너뜀."""
|
||||
"""(줄 번호, 이름, 식, 식이 시작하는 자리, 까닭) — 빈 줄과 `#` 는 건너뜀.
|
||||
|
||||
「이름 = 식」 과 「식 = 이름」 을 다 받음 — 한쪽이 값 이름 하나면 그쪽이 이름.
|
||||
"""
|
||||
for at, raw in enumerate(str(text).split("\n"), 1):
|
||||
if not raw.strip() or raw.lstrip().startswith("#"):
|
||||
continue
|
||||
m = _ASSIGN.match(raw)
|
||||
yield (
|
||||
at,
|
||||
(m.group(1) if m else ""),
|
||||
(m.group(2).strip() if m else raw.strip()),
|
||||
(m.start(2) if m else len(raw) - len(raw.lstrip())),
|
||||
)
|
||||
cut = _split(raw)
|
||||
if cut < 0:
|
||||
yield (
|
||||
at,
|
||||
"",
|
||||
raw.strip(),
|
||||
len(raw) - len(raw.lstrip()),
|
||||
"「이름 = 식」 이 아님 — 등호가 없음",
|
||||
)
|
||||
continue
|
||||
left, right = raw[:cut], raw[cut + 1 :]
|
||||
if _is_name(left.strip()):
|
||||
yield at, left.strip(), right.strip(), cut + 1 + len(right) - len(right.lstrip()), ""
|
||||
elif _is_name(right.strip()): # 「A * B = C」 — 오른쪽이 이름
|
||||
yield at, right.strip(), left.strip(), len(left) - len(left.lstrip()), ""
|
||||
else:
|
||||
yield at, "", raw.strip(), cut, "양쪽이 다 식 — 한쪽은 값 이름 하나여야 함"
|
||||
|
||||
|
||||
def _order(defined: dict, deps: dict) -> list[str]:
|
||||
@@ -255,14 +289,14 @@ def _order(defined: dict, deps: dict) -> list[str]:
|
||||
return out
|
||||
|
||||
|
||||
def _scan(text: str, 정함: dict | None = None) -> dict:
|
||||
def _scan(text: str, 정함: dict | None = None, 머리: dict | None = None) -> dict:
|
||||
"""줄 가르기 · 변수 · 검사 · 단위 — `read` 와 `옮기기` 가 같이 씀(나무를 들고 있음)."""
|
||||
정함 = 정함 or {}
|
||||
lines, 문제 = [], []
|
||||
for at, name, body, head in _cut(text):
|
||||
for at, name, body, head, why in _cut(text):
|
||||
one = {"줄": at, "이름": name, "식": body, "앞": head}
|
||||
if not name:
|
||||
문제.append(_trouble(at, head, "못읽음", "「이름 = 식」 이 아님"))
|
||||
if why:
|
||||
문제.append(_trouble(at, head, "못읽음", why))
|
||||
elif not body:
|
||||
문제.append(_trouble(at, head, "못읽음", f"「{name}」 오른쪽이 빔"))
|
||||
else:
|
||||
@@ -343,6 +377,7 @@ def _scan(text: str, 정함: dict | None = None) -> dict:
|
||||
got = _unit(one["나무"], units, one["줄"], one["앞"], 문제)
|
||||
units[name] = pinned if pinned is not None else got
|
||||
|
||||
덧줄후보 = _extra_names(by, 결과, 정함, 머리) if 결과 in by and 결과 not in loops else set()
|
||||
변수 = []
|
||||
for name in sorted(
|
||||
set(쓰임) | set(정의), key=lambda n: (min([쓰임.get(n, [10**9])[0], 정의.get(n, 10**9)]), n)
|
||||
@@ -359,16 +394,18 @@ def _scan(text: str, 정함: dict | None = None) -> dict:
|
||||
"무엇": spec.get("무엇") if 갈래 == "미정" else None,
|
||||
"단위": unit_text(units.get(name)),
|
||||
"비목": spec.get("비목"),
|
||||
"정할것": _todo(갈래, spec, units.get(name)),
|
||||
"정할것": _todo(갈래, spec, units.get(name), name in 덧줄후보),
|
||||
}
|
||||
)
|
||||
return {"줄": lines, "변수": 변수, "결과": 결과, "문제": 문제, "차례": order, "정의": 정의}
|
||||
|
||||
|
||||
def _todo(갈래: str, spec: dict, unit) -> list[str]:
|
||||
def _todo(갈래: str, spec: dict, unit, 덧줄: bool = False) -> list[str]:
|
||||
"""그 변수에 아직 안 정한 것 — 무엇 · 요소·찾기·값 · 단위 · 비목."""
|
||||
if 갈래 != "미정":
|
||||
if 갈래 == "비목합":
|
||||
return []
|
||||
if 갈래 == "정의됨": # 식이 있는 값 — 덧줄이 될 항이면 비목만 고르면 됨
|
||||
return ["비목"] if 덧줄 and spec.get("비목") not in COST_ITEMS else []
|
||||
what, need = spec.get("무엇"), []
|
||||
if what not in WHAT:
|
||||
need.append("무엇")
|
||||
@@ -386,9 +423,9 @@ def _todo(갈래: str, spec: dict, unit) -> list[str]:
|
||||
return need
|
||||
|
||||
|
||||
def read(text: str, 정함: dict | None = None) -> dict:
|
||||
def read(text: str, 정함: dict | None = None, 머리: dict | None = None) -> dict:
|
||||
"""줄로 적은 식 → `{줄, 변수, 결과, 문제, 다됨}` — 화면이 그대로 받는 모양(나무는 뺌)."""
|
||||
got = _scan(text, 정함)
|
||||
got = _scan(text, 정함, 머리)
|
||||
다됨 = not got["문제"] and not any(v["정할것"] for v in got["변수"])
|
||||
return {
|
||||
"줄": [{k: v for k, v in one.items() if k != "나무"} for one in got["줄"]],
|
||||
@@ -408,22 +445,40 @@ def _factors(node) -> list:
|
||||
return _factors(node[2]) + _factors(node[3]) if node[0] == "bin" and node[1] == "*" else [node]
|
||||
|
||||
|
||||
def _spread(node, by: dict, 정함: dict, 편: set) -> list:
|
||||
"""결과 항 — 항이 이름 하나이고 비목을 안 정했으면 그 식으로 펼침.
|
||||
def _spread(node, by: dict, 정함: dict, 편: set, 뿌리: str = "") -> list:
|
||||
"""결과 항 — (항, 펼치기 전 이름). 항이 이름 하나이고 비목을 안 정했으면 그 식으로 펼침.
|
||||
|
||||
호표 줄을 항 안에서 찾으려는 것.
|
||||
호표 줄을 항 안에서 찾으려는 것 · 못 펼친 항의 `뿌리` 가 덧줄이 될 값 이름.
|
||||
"""
|
||||
out = []
|
||||
for term in _terms(node):
|
||||
name = term[1] if term[0] == "name" else ""
|
||||
if name in by and name not in 편 and not (정함.get(name) or {}).get("비목"):
|
||||
편.add(name)
|
||||
out += _spread(by[name]["나무"], by, 정함, 편)
|
||||
out += _spread(by[name]["나무"], by, 정함, 편, name)
|
||||
else:
|
||||
out.append(term)
|
||||
out.append((term, 뿌리))
|
||||
return out
|
||||
|
||||
|
||||
def _has_element(term, 정함: dict) -> bool:
|
||||
return any(
|
||||
f[0] == "name" and (정함.get(f[1]) or {}).get("무엇") == "마스터요소"
|
||||
for f in _factors(term)
|
||||
)
|
||||
|
||||
|
||||
def _extra_names(by: dict, 결과: str, 정함: dict, 머리: dict | None) -> set[str]:
|
||||
"""결과 식의 항 가운데 마스터요소가 없어 덧줄이 될 값 이름 — 비목을 골라야 함."""
|
||||
if 머리 is not None and not str(머리.get("결과단위") or "").startswith("원"):
|
||||
return set() # 돈 아닌 로직은 덧줄이 없음
|
||||
return {
|
||||
뿌리
|
||||
for term, 뿌리 in _spread(by[결과]["나무"], by, 정함, set())
|
||||
if 뿌리 and not _has_element(term, 정함)
|
||||
}
|
||||
|
||||
|
||||
def _kind_of(요소) -> str:
|
||||
if isinstance(요소, dict):
|
||||
return "재료"
|
||||
@@ -447,13 +502,16 @@ def 옮기기(text: str, 정함: dict | None = None, 머리: dict | None = None)
|
||||
돈 아닌 로직은 호표 없이 `중간` + `결과` 식.
|
||||
"""
|
||||
정함, 머리 = 정함 or {}, dict(머리 or {})
|
||||
got = _scan(text, 정함)
|
||||
got = _scan(text, 정함, 머리)
|
||||
문제 = list(got["문제"])
|
||||
for v in got["변수"]:
|
||||
if v["정할것"]:
|
||||
문제.append(
|
||||
_trouble(
|
||||
v["처음"], 0, "덜정함", f"「{v['이름']}」 에 {' · '.join(v['정할것'])} 안 정함"
|
||||
v["처음"],
|
||||
0,
|
||||
"덜정함",
|
||||
f"「{v['이름']}」 에 아직 안 정한 것 — {' · '.join(v['정할것'])}",
|
||||
)
|
||||
)
|
||||
if 문제:
|
||||
@@ -475,7 +533,7 @@ def 옮기기(text: str, 정함: dict | None = None, 머리: dict | None = None)
|
||||
|
||||
덧줄이름, 편 = set(), set()
|
||||
if 돈:
|
||||
for term in _spread(by[결과]["나무"], by, 정함, 편):
|
||||
for term, 뿌리 in _spread(by[결과]["나무"], by, 정함, 편):
|
||||
facts = _factors(term)
|
||||
picks = [
|
||||
f
|
||||
@@ -520,6 +578,15 @@ def 옮기기(text: str, 정함: dict | None = None, 머리: dict | None = None)
|
||||
{"이름": name, "식": _expr(by[name]["나무"]), "비목": 정함[name]["비목"]}
|
||||
)
|
||||
덧줄이름.add(name)
|
||||
elif 뿌리 in by: # 덧줄이 될 값 — 막지 않고 비목만 짚음
|
||||
문제.append(
|
||||
_trouble(
|
||||
by[뿌리]["줄"],
|
||||
0,
|
||||
"비목",
|
||||
f"덧줄 「{뿌리}」 의 비목을 골라 주세요 — {' · '.join(COST_ITEMS)}",
|
||||
)
|
||||
)
|
||||
else:
|
||||
문제.append(
|
||||
_trouble(
|
||||
|
||||
@@ -133,6 +133,31 @@ def test_만약과_최소는_갈래마다_단위가_같아야_한다() -> None:
|
||||
assert md.read("값 = 최소(길이, 길이 * 2)", 정함)["문제"] == []
|
||||
|
||||
|
||||
def test_단위_없음을_골라_맨_수로_다룬다() -> None:
|
||||
"""횟수 · 배율 — 단위 칸에 「없음」 을 고르면 무차원(3-3 화면 흠 ①)."""
|
||||
회 = {"금액": {"무엇": "설계입력", "단위": "원"}, "전용": {"무엇": "설계입력", "단위": "회"}}
|
||||
got = md.read("몫 = 금액 / 전용\n계금액 = 몫 + 금액", 회)
|
||||
assert _var(got, "몫")["단위"] == "원/회" # 「회」 로 두면 덧셈에서 걸림
|
||||
assert [x["갈래"] for x in got["문제"]] == ["단위"]
|
||||
|
||||
맨수 = dict(회, 전용={"무엇": "설계입력", "단위": "없음"})
|
||||
깨끗 = md.read("몫 = 금액 / 전용\n계금액 = 몫 + 금액", 맨수)
|
||||
assert _var(깨끗, "몫")["단위"] == "원" and 깨끗["문제"] == []
|
||||
assert _var(깨끗, "전용")["단위"] == "" and _var(깨끗, "전용")["정할것"] == []
|
||||
for 글 in ("", "맨수", "-", "%"):
|
||||
쓴 = dict(회, 전용={"무엇": "설계입력", "단위": 글})
|
||||
assert md.read("몫 = 금액 / 전용\n계금액 = 몫 + 금액", 쓴)["문제"] == [], 글
|
||||
없으면 = dict(회, 전용={"무엇": "설계입력"}) # 단위 칸 자체가 없으면 아직 안 정한 것
|
||||
assert _var(md.read("몫 = 금액 / 전용", 없으면), "전용")["정할것"] == ["단위"]
|
||||
|
||||
|
||||
def test_나눗셈으로_단위가_없어진다() -> None:
|
||||
정함 = {"길이": {"무엇": "설계입력", "단위": "m"}, "넓이": {"무엇": "설계입력", "단위": "㎡"}}
|
||||
assert _var(md.read("폭 = 넓이 / 길이", 정함), "폭")["단위"] == "m"
|
||||
assert _var(md.read("비 = 길이 / 길이", 정함), "비")["단위"] == "" # 같은 단위끼리 나누면 맨 수
|
||||
assert md.read("합 = 길이 / 길이 + 2", 정함)["문제"] == []
|
||||
|
||||
|
||||
# ── ③ 그 밖 검사 ──────────────────────────────────────────────────────
|
||||
def test_서로_물고_도는_식을_잡는다() -> None:
|
||||
kinds = [x["갈래"] for x in md.read(LOOP)["문제"]]
|
||||
@@ -152,6 +177,29 @@ def test_두_번_정의_안_쓰임_결과_하나() -> None:
|
||||
assert md.read("이건 식이 아님")["문제"][0]["갈래"] == "못읽음"
|
||||
|
||||
|
||||
def test_식이_왼쪽에_와도_받는다() -> None:
|
||||
"""「A * B = C」 로 적는 사용자도 있음(3-3 화면 흠 ②)."""
|
||||
정함 = {"길이": {"무엇": "설계입력", "단위": "m"}, "폭": {"무엇": "설계입력", "단위": "m"}}
|
||||
got = md.read("길이 * 폭 = 넓이", 정함)
|
||||
assert got["문제"] == [] and got["결과"] == "넓이"
|
||||
assert _var(got, "넓이")["갈래"] == "정의됨" and _var(got, "넓이")["단위"] == "㎡"
|
||||
assert got["줄"][0]["식"] == "길이 * 폭"
|
||||
assert md.read("넓이 = 길이 * 폭", 정함)["변수"] == got["변수"] # 어느 쪽으로 적든 같음
|
||||
|
||||
양쪽 = md.read("길이 * 폭 = 넓이 * 2", 정함)["문제"]
|
||||
assert 양쪽[0]["갈래"] == "못읽음" and "양쪽이 다 식" in 양쪽[0]["말"]
|
||||
assert 양쪽[0]["자리"] == "길이 * 폭 = 넓이 * 2".index("=")
|
||||
assert md.read("길이 * 폭 = 4", 정함)["문제"][0]["갈래"] == "못읽음" # 맨 수는 이름이 아님
|
||||
|
||||
|
||||
def test_비교_기호와_찾기_안_등호는_안_가른다() -> None:
|
||||
정함 = {"높이": {"무엇": "설계입력", "단위": "m"}}
|
||||
assert md.read("값 = 만약(높이 <= 3, 1, 2)", 정함)["문제"] == []
|
||||
assert md.read("값 = 만약(높이 == 3, 1, 2)", 정함)["문제"] == []
|
||||
안쪽 = md.read("품 = 찾기(QF000421, 뒷길이=높이).석공", 정함)
|
||||
assert 안쪽["문제"] == [] and 안쪽["줄"][0]["이름"] == "품"
|
||||
|
||||
|
||||
# ── ⑤ 로직 틀로 옮기기 ────────────────────────────────────────────────
|
||||
def test_다_정하면_로직_틀로_옮긴다() -> None:
|
||||
row, bad = md.옮기기(GOOD, GOOD_정함, GOOD_머리)
|
||||
@@ -192,6 +240,22 @@ def test_덜_정했으면_안_옮긴다() -> None:
|
||||
assert row == {} and any(x["갈래"] == "덜정함" for x in bad)
|
||||
|
||||
|
||||
def test_비목_안_고른_덧줄은_막지_않고_짚어_준다() -> None:
|
||||
"""「못 옮김」 으로 막지 않고 그 값에 「비목」 을 정할 것으로 얹음(3-3 화면 흠 ③)."""
|
||||
빼기 = {k: v for k, v in GOOD_정함.items() if k != "잡재료비"}
|
||||
got = md.read(GOOD, 빼기, GOOD_머리)
|
||||
assert _var(got, "잡재료비")["정할것"] == ["비목"] and got["다됨"] is False
|
||||
assert [x["갈래"] for x in got["문제"]] == [] # 검사에는 안 걸림 — 고를 것이 남았을 뿐
|
||||
|
||||
row, bad = md.옮기기(GOOD, 빼기, GOOD_머리)
|
||||
assert row == {} and [x["갈래"] for x in bad] == ["덜정함"]
|
||||
assert "잡재료비" in bad[0]["말"] and "비목" in bad[0]["말"]
|
||||
assert _var(md.read(GOOD, GOOD_정함, GOOD_머리), "잡재료비")["정할것"] == []
|
||||
|
||||
안돈 = md.read(GOOD, 빼기, {"이름": "수량만", "결과단위": "㎥"}) # 돈 아닌 로직은 덧줄이 없음
|
||||
assert _var(안돈, "잡재료비")["정할것"] == []
|
||||
|
||||
|
||||
# ── 서버 길 ───────────────────────────────────────────────────────────
|
||||
def test_서버_길이_변수와_문제를_돌려_준다(client: TestClient) -> None:
|
||||
got = _post(client, {"text": GOOD, "decided": GOOD_정함})
|
||||
|
||||
Reference in New Issue
Block a user